feat(editor): 拆分 EditorHost/TextEditor — code/markdown 成为注册 viewer,fs.read 增加 head 字节
- 新 src/client/EditorHost.tsx:'editor' tab 组件。matchFileViewer → 按 fetchStrategy 分发(纯逻辑在 editor-load.ts)→ 渲染 viewer.component; binary 无匹配时渲染共享 BinaryDownload。标题栏保留在 host - 新 src/client/TextEditor.tsx:code/markdown 的 viewer 组件(CodeMirror + 保存/脏点/截断横幅 + md 预览/编辑切换),内容由 host 经 fsRead 传入, 自身不再 fetch;按 viewerId === 'markdown' 分支(不再解析扩展名) - 新 src/client/editor-load.ts:planFirstMatch / planFsReadOutcome / decodeHead 纯函数,策略分发可单测 - 新 src/client/binary-download.tsx:共享下载兜底组件(viewer 注册与 host fallback 同一 UI) - host src/index.ts:fs.read 二进制响应增加 head 字段(base64,前 4KB, 唯一 host 触碰,设计文档 detect 机制所需);客户端 FsBinaryResult 同步 - builtins:注册 8 个 viewer(新增 markdown exts md/markdown、code catch-all -100);binary-download 增加 NUL detect + 复用 BinaryDownload - 删除 src/client/EditorView.tsx(职责一分为二);陈旧注释同步 - 新增 tests/editor-load.spec.ts(12 用例)
This commit is contained in:
1 parent
db0ba36610
commit
0467101faf
12 files changed
+565
-314
No files matched your search
+19
-5
@@ -123,8 +123,19 @@ async function resolveGitPath(cwd: string, raw: string): Promise<string> {
|
||||
return requireAbsolute(join(root, raw))
|
||||
}
|
||||
|
||||
/** Text read of a file with the size cap; binary detection via NUL probe. */
|
||||
async function readText(path: string, readLimit: number): Promise<{ content: string; truncated: boolean; binary: boolean; size: number }> {
|
||||
/** How many leading bytes a binary read returns for client-side detect sniffing. */
|
||||
const READ_HEAD_LIMIT = 4096
|
||||
|
||||
/** Text read of a file with the size cap; binary detection via NUL probe.
|
||||
* Binary reads also return the first {@link READ_HEAD_LIMIT} bytes (base64)
|
||||
* so the client can re-match viewers by content (`detect`). */
|
||||
async function readText(path: string, readLimit: number): Promise<{
|
||||
content: string
|
||||
truncated: boolean
|
||||
binary: boolean
|
||||
size: number
|
||||
head?: string
|
||||
}> {
|
||||
const info = await stat(path).catch((error: unknown) => {
|
||||
throw new SidebarError('fs-error', `cannot read "${path}": ${error instanceof Error ? error.message : String(error)}`, 400)
|
||||
})
|
||||
@@ -141,7 +152,10 @@ async function readText(path: string, readLimit: number): Promise<{ content: str
|
||||
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0)
|
||||
const slice = buffer.subarray(0, bytesRead)
|
||||
const binary = slice.includes(0)
|
||||
return { content: binary ? '' : slice.toString('utf8'), truncated, binary, size }
|
||||
const head = binary
|
||||
? slice.subarray(0, Math.min(slice.length, READ_HEAD_LIMIT)).toString('base64')
|
||||
: undefined
|
||||
return { content: binary ? '' : slice.toString('utf8'), truncated, binary, size, head }
|
||||
} finally {
|
||||
await handle.close()
|
||||
}
|
||||
@@ -194,8 +208,8 @@ function buildApi(
|
||||
// Relative paths are git-derived (status/diff report repo-root-relative
|
||||
// names; the untracked diff view reads the file through this route).
|
||||
const path = await resolveGitPath(cwd, requireString(payload, 'path'))
|
||||
const { content, truncated, binary, size } = await readText(path, resolved.readLimit)
|
||||
if (binary) return { kind: 'binary', size, truncated }
|
||||
const { content, truncated, binary, size, head } = await readText(path, resolved.readLimit)
|
||||
if (binary) return { kind: 'binary', size, truncated, head }
|
||||
return { kind: 'text', content, truncated }
|
||||
},
|
||||
'fs.write': async (payload) => {
|
||||
|
||||
Reference in new issue
Block a user