feat: 0.8.0 文件夹对比深度增强与字符级逐组预算制 - 按扩展名多选过滤(第四维过滤与其他条件取交集、N/总数计数切换)、目录行双击聚合对比(差异导航限定子树内批量审查、双击文件回全局、空差异提示与折叠态自愈)、增量重扫(size+mtime指纹一致零IO沿用、语义开关翻转按semSame纯内存组合零IO、unreadable不缓存与截断弃缓存、重新扫描按钮)、字符级变更组逐组预算制(超限组仅行级配对输出不再牵连其余组、charModePartial部分降级标记与报告说明、全部组降级仍整体降级)、拖拽导入E2E(dropFiles合成DataTransfer覆盖decodeBuffer IPC全链路4用例)、文档与版本号同步
This commit is contained in:
+41
-18
@@ -5,7 +5,7 @@ import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import fs from 'fs'
|
||||
import { parseState, clampBounds, type AppState } from './windowState'
|
||||
import { decodeText, type DecodeResult } from './decode'
|
||||
import { scanFolders, SEMANTIC_MAX_BYTES } from './folderScan'
|
||||
import { scanFolders, SEMANTIC_MAX_BYTES, type ScanCacheEntry } from './folderScan'
|
||||
|
||||
/**
|
||||
* 文本类扩展名清单(小写,不含点):
|
||||
@@ -264,24 +264,47 @@ ipcMain.handle('folder:pick', async () => {
|
||||
return dirPath
|
||||
})
|
||||
|
||||
/** IPC: 扫描对比两个文件夹(递归对齐 + 字节级判定 + 可选语义判等;异步 fs 与解码 worker 不阻塞主进程) */
|
||||
ipcMain.handle('folder:scan', async (_event, leftDir: string, rightDir: string, semantic: boolean) => {
|
||||
try {
|
||||
return await scanFolders(leftDir, rightDir, semantic
|
||||
? {
|
||||
// 语义判等解码复用解码 worker(大缓冲后台解码,与文件打开共用同一管线)
|
||||
semantic: {
|
||||
maxBytes: SEMANTIC_MAX_BYTES,
|
||||
isTextFile: isTextRel,
|
||||
decode: async (buf) => (await decodeMaybeWorker(buf)).text
|
||||
}
|
||||
}
|
||||
: {})
|
||||
} catch {
|
||||
// 目录不存在/不可读等:转为错误标记,渲染端提示
|
||||
return { error: 'scan-failed' as const }
|
||||
/** IPC: 扫描对比两个文件夹(递归对齐 + 字节级判定 + 可选语义判等 + 增量缓存;异步 fs 与解码 worker 不阻塞主进程) */
|
||||
ipcMain.handle(
|
||||
'folder:scan',
|
||||
async (
|
||||
_event,
|
||||
leftDir: string,
|
||||
rightDir: string,
|
||||
semantic: boolean,
|
||||
previous?: unknown
|
||||
) => {
|
||||
try {
|
||||
// 增量缓存防御性校验:仅接受数组形态(渲染端从上次扫描结果携带,损坏即全量重扫)
|
||||
const cache =
|
||||
Array.isArray(previous) && previous.every(
|
||||
(c) =>
|
||||
typeof c === 'object' &&
|
||||
c !== null &&
|
||||
typeof (c as ScanCacheEntry).rel === 'string' &&
|
||||
typeof (c as ScanCacheEntry).status === 'string'
|
||||
)
|
||||
? (previous as ScanCacheEntry[])
|
||||
: undefined
|
||||
return await scanFolders(leftDir, rightDir, {
|
||||
...(cache ? { previous: cache } : {}),
|
||||
...(semantic
|
||||
? {
|
||||
// 语义判等解码复用解码 worker(大缓冲后台解码,与文件打开共用同一管线)
|
||||
semantic: {
|
||||
maxBytes: SEMANTIC_MAX_BYTES,
|
||||
isTextFile: isTextRel,
|
||||
decode: async (buf) => (await decodeMaybeWorker(buf)).text
|
||||
}
|
||||
}
|
||||
: {})
|
||||
})
|
||||
} catch {
|
||||
// 目录不存在/不可读等:转为错误标记,渲染端提示
|
||||
return { error: 'scan-failed' as const }
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
/** IPC: 按绝对路径读取文本文件(文件夹对比双击进入单文件对比;与 file:open 同一读取管线) */
|
||||
ipcMain.handle('file:read-by-path', async (_event, filePath: string) => {
|
||||
|
||||
Reference in New Issue
Block a user