fix: v0.2.4 健壮性与体验打磨 - 读取容错、10MB 大文件防护、横向滚动同步、Esc/外点关闭、二进制预警
This commit is contained in:
+44
-12
@@ -1,5 +1,5 @@
|
||||
import { join } from 'path'
|
||||
import { app, shell, BrowserWindow, Menu, ipcMain, dialog } from 'electron'
|
||||
import { app, shell, BrowserWindow, Menu, ipcMain, dialog, clipboard } from 'electron'
|
||||
import { electronApp, optimizer, is } from '@electron-toolkit/utils'
|
||||
import iconv from 'iconv-lite'
|
||||
import fs from 'fs'
|
||||
@@ -41,27 +41,50 @@ function createWindow(): void {
|
||||
}
|
||||
}
|
||||
|
||||
/** 单侧文件大小上限(10MB):超限直接拦截,避免超大文件卡死界面 */
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024
|
||||
|
||||
/**
|
||||
* 缓冲区二进制启发式判定:出现 NUL 字节立即判定;
|
||||
* 否则统计前 8KB 内不可见控制字符占比(> 5% 视为二进制)。
|
||||
*/
|
||||
function looksBinary(buf: Buffer): boolean {
|
||||
const len = Math.min(buf.length, 8000)
|
||||
if (len === 0) return false
|
||||
let suspicious = 0
|
||||
for (let i = 0; i < len; i++) {
|
||||
const b = buf[i]
|
||||
if (b === 0) return true
|
||||
if (b < 0x09 || (b > 0x0d && b < 0x20)) suspicious++
|
||||
}
|
||||
return suspicious / len > 0.05
|
||||
}
|
||||
|
||||
/**
|
||||
* 探测文本编码:优先 BOM,其次严格 UTF-8,
|
||||
* 失败则回退 GBK 解码,返回 { text, encoding }。
|
||||
* 失败则回退 GBK 解码,返回 { text, encoding, binary }。
|
||||
*/
|
||||
function decodeText(buf: Buffer): { text: string; encoding: string } {
|
||||
function decodeText(buf: Buffer): { text: string; encoding: string; binary: boolean } {
|
||||
if (buf.length >= 3 && buf[0] === 0xef && buf[1] === 0xbb && buf[2] === 0xbf) {
|
||||
return { text: buf.subarray(3).toString('utf8'), encoding: 'UTF-8 (BOM)' }
|
||||
return { text: buf.subarray(3).toString('utf8'), encoding: 'UTF-8 (BOM)', binary: false }
|
||||
}
|
||||
if (buf.length >= 2 && buf[0] === 0xfe && buf[1] === 0xff) {
|
||||
return { text: iconv.decode(buf.subarray(2), 'utf16-be'), encoding: 'UTF-16 BE' }
|
||||
return { text: iconv.decode(buf.subarray(2), 'utf16-be'), encoding: 'UTF-16 BE', binary: false }
|
||||
}
|
||||
if (buf.length >= 2 && buf[0] === 0xff && buf[1] === 0xfe) {
|
||||
return { text: buf.subarray(2).toString('utf16le'), encoding: 'UTF-16 LE' }
|
||||
return { text: buf.subarray(2).toString('utf16le'), encoding: 'UTF-16 LE', binary: false }
|
||||
}
|
||||
// 严格 UTF-8 探测
|
||||
try {
|
||||
const decoder = new TextDecoder('utf-8', { fatal: true })
|
||||
const text = decoder.decode(buf)
|
||||
return { text, encoding: 'UTF-8' }
|
||||
return { text, encoding: 'UTF-8', binary: false }
|
||||
} catch {
|
||||
return { text: iconv.decode(buf, 'gbk'), encoding: 'GBK' }
|
||||
const text = iconv.decode(buf, 'gbk')
|
||||
// GBK 覆盖面广,二进制内容也能解出“文字”;用字节启发式 + 替换符占比双保险
|
||||
const bad = (text.match(/\uFFFD/g) ?? []).length
|
||||
const binary = looksBinary(buf) || (text.length > 0 && bad / text.length > 0.05)
|
||||
return { text, encoding: 'GBK', binary }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +99,19 @@ ipcMain.handle('file:open', async (_event, side: 'left' | 'right' | null) => {
|
||||
})
|
||||
if (result.canceled || result.filePaths.length === 0) return null
|
||||
const filePath = result.filePaths[0]
|
||||
const buf = fs.readFileSync(filePath)
|
||||
const { text, encoding } = decodeText(buf)
|
||||
const name = filePath.split(/[\\/]/).pop() ?? filePath
|
||||
return { path: filePath, name, text, encoding }
|
||||
let buf: Buffer
|
||||
try {
|
||||
const stat = fs.statSync(filePath)
|
||||
if (stat.size > MAX_FILE_SIZE) {
|
||||
return { error: 'too-large', name, size: stat.size }
|
||||
}
|
||||
buf = fs.readFileSync(filePath)
|
||||
} catch {
|
||||
return { error: 'read-failed', name }
|
||||
}
|
||||
const { text, encoding, binary } = decodeText(buf)
|
||||
return { path: filePath, name, text, encoding, binary }
|
||||
})
|
||||
|
||||
/** IPC: 解码拖拽传入的原始字节(复用编码探测逻辑) */
|
||||
@@ -111,7 +143,7 @@ ipcMain.handle('file:show-in-folder', (_event, filePath: string) => {
|
||||
|
||||
/** IPC: 复制文本到剪贴板 */
|
||||
ipcMain.handle('clipboard:write', (_event, text: string) => {
|
||||
require('electron').clipboard.writeText(text)
|
||||
clipboard.writeText(text)
|
||||
return true
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user