mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
528 lines
21 KiB
Markdown
528 lines
21 KiB
Markdown
# Claude Code 源码调研:自定义 Ink、滚动、渲染与 MCP
|
||
|
||
> 调研对象:`/Users/gawain/Documents/codebase/opensource/claude-code`
|
||
> 目标:拆解 Claude Code 的 TUI 技术栈,识别哪些能力值得 qwen-code 吸收,哪些能力属于高维护成本的长期路线。
|
||
|
||
**事实边界**:除特别注明的外部终端资料外,本文件结论均基于本地源码树当前状态。上游若发生重构、文件移动或行为变更,需要重新核对后再引用到实施文档中。
|
||
|
||
## 1. 结论摘要
|
||
|
||
Claude Code 的 TUI 优势不是某一个组件特别强,而是它拥有一套**自定义终端渲染基础设施**:
|
||
|
||
1. **启动层**:大量并行预取、feature-gated require、尽量避免把非关键路径阻塞在首屏之前
|
||
2. **终端层**:自定义 Ink 实现,拥有自己的 screen buffer、diff、同步输出、硬件滚动、输入协议升级、XTVERSION 检测
|
||
3. **滚动层**:`ScrollBox` + `useVirtualScroll` + `VirtualMessageList` 形成一套面向超长会话的滚动系统
|
||
4. **渲染层**:`marked` + token cache + streaming block split + Suspense 高亮
|
||
5. **状态层**:MCP 更新批量化、远端 transport 自动重连、工具/命令/资源变更通知增量刷入 UI
|
||
|
||
对 qwen-code 来说,这份调研给出两个清晰判断:
|
||
|
||
- **短中期可直接借鉴**:启动并行化、同步输出 gating、Markdown token cache、流式块边界、滚动量化、MCP 批量更新、终端能力探测
|
||
- **长期高风险路线**:完全自研 Ink/diff renderer、DECSTBM scroll region、screen buffer + prevScreen blit、搜索/选择/滚动深度耦合的一整套基础设施
|
||
|
||
换句话说,Claude Code 不是一个“复制粘贴的目标实现”,而是一张非常清楚的技术路线图。
|
||
|
||
## 2. 关键文件地图
|
||
|
||
| 维度 | 文件 |
|
||
| --- | --- |
|
||
| 启动入口 | `src/main.tsx` |
|
||
| render options | `src/utils/renderOptions.ts` |
|
||
| 交互辅助 | `src/interactiveHelpers.tsx` |
|
||
| 自定义 Ink App | `src/ink/components/App.tsx` |
|
||
| 终端能力与输出 | `src/ink/terminal.ts` |
|
||
| frame diff | `src/ink/log-update.ts` |
|
||
| 输出缓冲 | `src/ink/output.ts` |
|
||
| isolated render | `src/ink/render-to-screen.ts` |
|
||
| 滚动容器 | `src/ink/components/ScrollBox.tsx` |
|
||
| 虚拟滚动 | `src/hooks/useVirtualScroll.ts` |
|
||
| 消息虚拟列表 | `src/components/VirtualMessageList.tsx` |
|
||
| 消息主视图 | `src/components/Messages.tsx` |
|
||
| Markdown | `src/components/Markdown.tsx` |
|
||
| 代码高亮 | `src/components/HighlightedCode.tsx` |
|
||
| query 主循环 | `src/query.ts` |
|
||
| 流式工具执行 | `src/services/tools/StreamingToolExecutor.ts` |
|
||
| MCP 连接上下文 | `src/services/mcp/MCPConnectionManager.tsx` |
|
||
| MCP 动态管理 | `src/services/mcp/useManageMCPConnections.ts` |
|
||
|
||
## 3. 启动与 bootstrap 策略
|
||
|
||
### 3.1 启动入口大量利用“顶层并行副作用”
|
||
|
||
`src/main.tsx` 在最前面就做了三件非常激进的事:
|
||
|
||
1. `profileCheckpoint('main_tsx_entry')`
|
||
2. `startMdmRawRead()`
|
||
3. `startKeychainPrefetch()`
|
||
|
||
源码注释写得很明确:这些副作用故意在其他重 imports 之前启动,好让:
|
||
|
||
- MDM 配置读取
|
||
- macOS keychain 读取
|
||
|
||
与后续的大量模块加载并行,而不是串行等待。
|
||
|
||
对 qwen-code 的启示:
|
||
|
||
- 如果某些读取天然昂贵且结果稍后才用到,就应尽早 fire-and-forget
|
||
- 启动期的并行化不应只发生在 async 函数里,顶层副作用也是工具
|
||
|
||
### 3.2 feature-gated require 降低了首屏模块成本
|
||
|
||
`src/main.tsx` 大量使用:
|
||
|
||
- `feature('FLAG') ? require('./module.js') : null`
|
||
|
||
来做死代码消除和冷路径裁剪,比如:
|
||
|
||
- coordinator mode
|
||
- assistant mode
|
||
- proactive mode
|
||
- transcript classifier
|
||
|
||
这说明 Claude Code 非常重视一个事实:**即便功能很多,也不能让所有功能都参与冷启动路径**。这与 qwen-code 当前文档中的“产物体积优化”是强一致的,应在 `01-performance.md` 中明确加粗。
|
||
|
||
### 3.3 render options 兼顾 piped stdin 和交互 TTY
|
||
|
||
`src/utils/renderOptions.ts` 提供 `getBaseRenderOptions()`:
|
||
|
||
- 若 stdin 不是 TTY
|
||
- 且不是 CI / MCP / Windows
|
||
- 则尝试打开 `/dev/tty` 作为交互输入源
|
||
|
||
这说明 Claude Code 把“管道输入 + 交互 UI”视为正式场景。对 qwen-code 的启示:
|
||
|
||
- 如果未来要增强 REPL / prompt queue / pasted transcript 场景,stdin override 机制应单独设计
|
||
- 不要默认假设 `process.stdin` 永远等于交互输入
|
||
|
||
### 3.4 renderAndRun 把“首屏出现”和“后台预取”分开
|
||
|
||
`src/interactiveHelpers.tsx` 中:
|
||
|
||
- 先 `root.render(element)`
|
||
- 再 `startDeferredPrefetches()`
|
||
- 再等待 `root.waitUntilExit()`
|
||
|
||
这与 qwen-code 未来需要的方向高度一致:**关键路径和 deferred prefetch 必须明确分层**。像 MCP resource prefetch、analytics、插件扫描、技能索引等都应尽量晚于首屏。
|
||
|
||
## 4. 自定义终端与输出管线
|
||
|
||
### 4.1 `src/ink/terminal.ts`:把终端能力当成 first-class capability
|
||
|
||
Claude Code 在 `src/ink/terminal.ts` 中定义了多种能力探测:
|
||
|
||
- `isProgressReportingAvailable()`:判断 `OSC 9;4` 进度协议
|
||
- `isSynchronizedOutputSupported()`:判断 DECSET 2026 同步输出
|
||
- `isXtermJs()`:结合 `TERM_PROGRAM` 与 XTVERSION 判断 xterm.js 系终端
|
||
- `supportsExtendedKeys()`:是否启用 kitty keyboard / modifyOtherKeys
|
||
- `hasCursorUpViewportYankBug()`:Windows / WT_SESSION 滚动 bug 检测
|
||
|
||
其中同步输出的策略非常务实:
|
||
|
||
- tmux 直接视为不支持,避免“BSU/ESU 穿透外层终端但 atomicity 已被 tmux 打散”的伪支持
|
||
- `SYNC_OUTPUT_SUPPORTED` 模块级计算一次,不在每帧重新判断
|
||
|
||
这比“看某个终端是不是大概率支持”更成熟。qwen-code 的 DECSET 2026 设计文档应补入这一层 gating 原则。
|
||
|
||
### 4.2 `src/ink/components/App.tsx`:输入协议升级和终端重连恢复
|
||
|
||
Claude 自定义 Ink App 在 raw mode 启用时做了很多事:
|
||
|
||
- `EBP`:bracketed paste
|
||
- `EFE`:focus reporting
|
||
- `ENABLE_KITTY_KEYBOARD`
|
||
- `ENABLE_MODIFY_OTHER_KEYS`
|
||
- 通过 `TerminalQuerier` 异步发送 `xtversion()`
|
||
|
||
并且它还处理了一个很现实的问题:
|
||
|
||
- 通过 `STDIN_RESUME_GAP_MS = 5000` 检测 tmux detach / SSH reconnect / laptop wake
|
||
- gap 后重新 re-assert 终端模式
|
||
|
||
这是 qwen-code 当前设计里明显缺失的一层:**终端模式可能被外部环境悄悄重置,单次启动时设置一次并不够**。
|
||
|
||
### 4.3 `writeDiffToTerminal()` 已经是单 write + 可选同步输出
|
||
|
||
`src/ink/terminal.ts` 的 `writeDiffToTerminal()`:
|
||
|
||
- 把 diff patches 序列化到一个字符串 buffer
|
||
- 可选前后包裹 `BSU` / `ESU`
|
||
- 最终只做一次 `terminal.stdout.write(buffer)`
|
||
|
||
这与 qwen-code 当前的 `stdout.write` monkeypatch 完全不是一个层级:
|
||
|
||
- qwen-code 是“拦截并改写已有输出”
|
||
- Claude 是“从 frame diff 到 terminal write 由自己控制”
|
||
|
||
因此,短期内 qwen-code 应聚焦:
|
||
|
||
- 输出层统计
|
||
- 单帧 write 合并
|
||
- 安全 gating
|
||
|
||
而不是直接跳到“完全拥有 terminal write pipeline”。
|
||
|
||
## 5. Diff、双缓冲与硬件滚动
|
||
|
||
### 5.1 `src/ink/log-update.ts` 是 Claude 防闪烁的核心
|
||
|
||
这个文件做了几件关键事情:
|
||
|
||
1. 维护 `prev` / `next` frame diff
|
||
2. 仅在必要时 full reset
|
||
3. 在 alt-screen + 安全条件下使用 DECSTBM scroll optimization
|
||
4. 在内容进入 scrollback 或 resize 复杂变化时,明确接受 full reset
|
||
|
||
最重要的不是“它能 diff”,而是它对**哪些场景不值得 diff** 有很清楚的边界判断,例如:
|
||
|
||
- viewport 缩短或宽度变化时直接 full reset
|
||
- 需要修改已经进入 scrollback 的行时直接 full reset
|
||
- 在 `decstbmSafe` 为假时,不冒险走 scroll region
|
||
|
||
这对 qwen-code 文档的意义很大:未来如果做 diff patch,必须同时写清楚**退化到 full reset 的条件**。
|
||
|
||
### 5.2 DECSTBM 不是“有就赚”,它依赖 atomicity
|
||
|
||
Claude 的 `log-update.ts` 注释写得很直白:
|
||
|
||
- 如果 DECSTBM 到 diff 的序列不能被原子包裹
|
||
- 终端会先显示“滚动了一半的中间状态”
|
||
- 反而形成可见的垂直跳跃
|
||
|
||
因此它把 `decstbmSafe` 作为一个显式条件。
|
||
|
||
这直接修正了很多文档里常见的误区:**硬件滚动只有在同步输出、缓冲与时序控制都具备时才值得开启**。qwen-code 的 `02-screen-flickering.md` 应明确把 DECSTBM 继续放在 Phase 3,而不是提前。
|
||
|
||
### 5.3 `src/ink/output.ts` 体现了“先收集操作,再写入 Screen”
|
||
|
||
`Output` 不是直接往终端写,而是先收集操作:
|
||
|
||
- `write`
|
||
- `blit`
|
||
- `shift`
|
||
- `clear`
|
||
- `noSelect`
|
||
- `clip` / `unclip`
|
||
|
||
随后在 `get()` 阶段把这些操作真正落到 `Screen` buffer。
|
||
|
||
另一个非常关键的优化是它保留了跨帧 `charCache`:
|
||
|
||
- grapheme cluster
|
||
- width
|
||
- styleId
|
||
- hyperlink
|
||
|
||
这使得多数不变行在后续帧里不必重复 tokenize + stringWidth + style 处理。
|
||
|
||
对 qwen-code 的启示:
|
||
|
||
- 即使暂时不做完整双缓冲,也可以先在 code/markdown 渲染层引入内容级 cache
|
||
- 未来如果做 screen buffer,char/style cache 会是成本回收的重要来源
|
||
|
||
## 6. 滚动与长会话体系
|
||
|
||
### 6.1 `ScrollBox` 的核心思想:滚动不走 React state
|
||
|
||
`src/ink/components/ScrollBox.tsx` 是 Claude 长会话体验的关键组件。它的要点包括:
|
||
|
||
- `scrollTo` / `scrollBy` 直接操作 DOM node 上的 `scrollTop`
|
||
- `pendingScrollDelta` 累积滚轮输入
|
||
- `queueMicrotask()` 合并同一输入批次内的多次变更
|
||
- `scheduleRenderFrom(el)` 只通知 renderer 重绘
|
||
- `stickyScroll` 作为稳定信号,区分“手动打破贴底”与“渲染器跟随到底部”
|
||
|
||
这是一种很明确的设计取舍:
|
||
|
||
- 高频滚动事件不走 React state
|
||
- React 只负责在需要换 mounted range 时介入
|
||
|
||
这对 qwen-code 的虚拟滚动方案非常重要:**滚轮事件如果每 tick 都走 React setState,后面所有优化都会被抵消**。
|
||
|
||
### 6.2 `useVirtualScroll()` 把滚动性能问题拆到了常数级
|
||
|
||
`src/hooks/useVirtualScroll.ts` 里有一整套很值得记录的参数化策略:
|
||
|
||
- `OVERSCAN_ROWS = 80`
|
||
- `SCROLL_QUANTUM = OVERSCAN_ROWS >> 1`
|
||
- `MAX_MOUNTED_ITEMS = 300`
|
||
- `SLIDE_STEP = 25`
|
||
|
||
并且做了多项高价值细节处理:
|
||
|
||
- 用 `useSyncExternalStore` 订阅滚动
|
||
- snapshot 用 **quantized target scrollTop**,不是每个 wheel tick 都触发 React commit
|
||
- resize 时**按列宽比例缩放高度缓存**,而不是直接清空
|
||
- resize 后冻结旧 range 两帧,避免 mount churn 二次闪烁
|
||
- 使用 clamp bounds 防止异步重挂载期间出现空白 spacer
|
||
|
||
这是 Claude 在“长会话 + 动态高度消息”问题上最有参考价值的部分。qwen-code 未来做虚拟滚动时,应该优先借鉴这里的:
|
||
|
||
### 6.3 Claude 对“大输出可读性”和“滚动稳定性”的核心取舍
|
||
|
||
如果只看 issue 表象,很容易把 Claude 的优势理解成“它用了 synchronized output,所以不闪”。源码表明并不是这么简单:
|
||
|
||
1. `ScrollBox` 让高频滚动不经过 React state
|
||
2. `useVirtualScroll()` 通过 quantized snapshot、overscan、height cache 和 range freeze 控制 mounted range
|
||
3. `Messages.tsx` / `VirtualMessageList.tsx` 把长会话视为一个正式的一等场景,而不是附着在主 transcript 上的补丁
|
||
|
||
对 qwen-code 的含义是:
|
||
|
||
- 如果想解决 `#1479` / `#2748` 这类“长输出不可读、生成时不能自由回看”的问题,不能只靠 ANSI 优化
|
||
- 需要把“长内容滚动容器”本身提到架构层
|
||
|
||
### 6.4 Claude 的 Markdown/streaming 设计说明:防闪烁不只是终端问题
|
||
|
||
`src/components/Markdown.tsx` 有两条和 qwen 当前问题高度相关的经验:
|
||
|
||
1. 模块级 token cache(500 条)降低了重挂载和回滚时的重复 parse 成本
|
||
2. `StreamingMarkdown` 使用 stable prefix / unstable suffix,只让最后一个增长中的块反复 re-parse
|
||
|
||
这给 qwen-code 一个很重要的修正:
|
||
|
||
- 工具输出、长 markdown、子 agent 详情之所以闪,不只是终端输出序列不够原子
|
||
- 也是因为 parser / render tree 在不断吞下越来越大的内容块
|
||
|
||
因此,Claude 的经验更适合被拆成两条路线吸收:
|
||
|
||
- **终端层**:同步输出、单 write、保守 gating
|
||
- **渲染层**:token cache、stable prefix、bounded detail container
|
||
|
||
1. scroll quantization
|
||
2. resize height scaling
|
||
3. frozen range
|
||
4. clamp bounds
|
||
|
||
而不是只抄一个 overscan list。
|
||
|
||
### 6.3 `VirtualMessageList` 不只负责滚动,还负责搜索/定位/hover 成本控制
|
||
|
||
`src/components/VirtualMessageList.tsx` 里还能看到一类容易被忽略的优化:
|
||
|
||
- `fallbackLowerCache`:缓存可搜索文本的 lowercase 结果
|
||
- `stickyPromptText()`:WeakMap 缓存 sticky prompt 文本
|
||
- `scanElement()` / `MatchPosition`:为搜索高亮进行 isolated render + 精确定位
|
||
- comment 中明确指出曾经的 per-item closure 造成 GC 压力,因此重构为稳定回调
|
||
|
||
这意味着 Claude 的“虚拟列表”并不是一个纯视觉容器,而是把:
|
||
|
||
- 滚动
|
||
- 搜索
|
||
- 悬停
|
||
- 点击
|
||
- sticky header / sticky prompt
|
||
|
||
全部视为同一个性能问题的一部分。
|
||
|
||
对 qwen-code 的含义是:如果未来要做 transcript 搜索、copy mode、message actions,最好从一开始就和虚拟滚动的设计一起考虑。
|
||
|
||
## 7. Markdown、高亮与流式渲染
|
||
|
||
### 7.1 `Markdown.tsx` 是 Claude 最可直接迁移的设计之一
|
||
|
||
`src/components/Markdown.tsx` 同时做了四件事:
|
||
|
||
1. `marked` lexer 解析 Markdown
|
||
2. `TOKEN_CACHE_MAX = 500` 的模块级 token cache
|
||
3. 快速路径 `hasMarkdownSyntax()`,无语法迹象时跳过完整 lexer
|
||
4. 表格 token 走 `<MarkdownTable>`,非表格内容走 `formatToken()`
|
||
|
||
这对 qwen-code 的启示极为直接:
|
||
|
||
- `marked` 迁移不该只讨论“parser 能不能工作”
|
||
- 应把 token cache、plain-text fast path、table special-case 一起设计
|
||
|
||
### 7.2 `StreamingMarkdown` 证明“块级稳定前缀”是成熟路径
|
||
|
||
Claude 的 `StreamingMarkdown` 实现与 Gemini 的 `findLastSafeSplitPoint()` 思路相通,但更彻底:
|
||
|
||
- `stablePrefixRef` 持有只增不减的稳定前缀
|
||
- 每次仅对“不稳定尾部”调用 `marked.lexer()`
|
||
- 最后一个 top-level block 视为 growing block
|
||
- stable 部分和 unstable 部分分别渲染
|
||
|
||
对 qwen-code 的结论是:
|
||
|
||
- 我们现有的“安全分割点”方向是对的
|
||
- 但文档需要明确最终目标是 **stable prefix + unstable suffix**
|
||
- 这可以同时服务于性能和防闪烁
|
||
|
||
### 7.3 高亮是异步资源,但 UI 不必阻塞
|
||
|
||
Claude 的 `Markdown` 组件使用:
|
||
|
||
- `getCliHighlightPromise()`
|
||
- `<Suspense fallback={<MarkdownBody highlight={null} />}>`
|
||
|
||
这形成了一个非常实用的策略:
|
||
|
||
- 首帧先用无高亮版本保证内容出现
|
||
- 高亮资源就绪后再增强
|
||
|
||
同时 `HighlightedCode.tsx` 也体现了类似思路:
|
||
|
||
- 尝试 `expectColorFile()`
|
||
- 成功则按 theme + width render
|
||
- 失败走 `HighlightedCodeFallback`
|
||
|
||
qwen-code 当前文档对“同步基线 + 异步预热”的方案,与 Claude 的现实做法是一致的,可以更有底气地推进。
|
||
|
||
## 8. MCP 管理与渐进更新
|
||
|
||
### 8.1 `MCPConnectionManager` 只是 context,真正的核心在 `useManageMCPConnections()`
|
||
|
||
`src/services/mcp/MCPConnectionManager.tsx` 很轻,它主要把:
|
||
|
||
- `reconnectMcpServer`
|
||
- `toggleMcpServer`
|
||
|
||
暴露给 UI。
|
||
|
||
真正重要的是 `src/services/mcp/useManageMCPConnections.ts`。
|
||
|
||
### 8.2 MCP 更新是批量刷入的,而不是每次变更都 setState
|
||
|
||
Claude 在 `useManageMCPConnections.ts` 里把 MCP 状态更新做成了**16ms 批处理窗口**:
|
||
|
||
- `MCP_BATCH_FLUSH_MS = 16`
|
||
- 收集 `PendingUpdate[]`
|
||
- `setTimeout(flushPendingUpdates, 16)`
|
||
- 一次性更新 clients / tools / commands / resources
|
||
|
||
这点非常值得 qwen-code 借鉴,因为它解决的是一个常见但隐蔽的问题:
|
||
|
||
- 多个 server 同时回调 connect / tool list changed / resource list changed
|
||
- 如果每次都 setState,会引发 UI 抖动和重复 render
|
||
|
||
### 8.3 对 MCP 通知协议的支持很完整
|
||
|
||
Claude 会监听多类 MCP 通知:
|
||
|
||
- `ToolListChangedNotificationSchema`
|
||
- `PromptListChangedNotificationSchema`
|
||
- `ResourceListChangedNotificationSchema`
|
||
- channel / permission 相关通知
|
||
- elicitation handler
|
||
|
||
并在变更时:
|
||
|
||
- 清对应 cache
|
||
- 拉取新 tools / commands / resources
|
||
- 增量更新 AppState
|
||
|
||
这对 qwen-code 的启示是:**MCP 不应该只在启动 discover 一次**。一旦要支持真正长期运行的 TUI,会话期间的工具/资源变更也要有设计。
|
||
|
||
### 8.4 远端 transport 自动重连是重要的产品级能力
|
||
|
||
`useManageMCPConnections.ts` 还做了:
|
||
|
||
- 对远端 transport 的自动重连
|
||
- 指数退避
|
||
- 最大尝试次数
|
||
- server disable / enable 时取消旧 timer
|
||
- 手动 reconnect / toggle
|
||
|
||
这意味着 Claude 把 MCP server 当作“长期存在、可能断线”的运行中依赖,而不是一次性启动资源。qwen-code 当前文档在这部分仍偏向“启动阶段问题”,需要补成“生命周期问题”。
|
||
|
||
## 9. Query / Tool 执行与 UI 稳定性
|
||
|
||
### 9.1 `StreamingToolExecutor` 把工具执行并发和 UI 顺序解耦
|
||
|
||
`src/services/tools/StreamingToolExecutor.ts` 的几个关键点:
|
||
|
||
- 并发安全工具可并行执行
|
||
- 非并发安全工具必须独占
|
||
- 结果按工具到达顺序缓冲并依次吐出
|
||
- progress messages 单独即时产出
|
||
- streaming fallback / user interruption / sibling error 有不同的 synthetic error 路径
|
||
|
||
这件事对 TUI 性能很重要,因为它避免了“工具执行状态改变顺序混乱,UI 到处特判”的局面。qwen-code 如果未来增强 tool 流式显示,也应把执行调度与渲染顺序分层处理。
|
||
|
||
### 9.2 `query.ts` 显示 Claude 把流式、compact、tool、budget 当成一个整体状态机
|
||
|
||
`src/query.ts` 不是单纯的 API stream loop,它把这些都融合在一个 query state machine 中:
|
||
|
||
- auto compact
|
||
- reactive compact
|
||
- tool orchestration
|
||
- token budget
|
||
- stop hooks
|
||
- streaming tool executor
|
||
|
||
对 qwen-code 文档的启示是:一些“看似 UI 问题”的闪烁和卡顿,根源可能在 query / tool / compact 的事件节奏。如果只在组件层打补丁,最终收益会受限。
|
||
|
||
## 10. 如何在 qwen-code 中使用这份调研
|
||
|
||
这份调研最适合用来指导三类判断:
|
||
|
||
1. **哪些能力可以作为短中期参考实现**
|
||
例如同步输出 gating、token cache、stable prefix、MCP batch flush。
|
||
|
||
2. **哪些能力属于长期路线而不是近期承诺**
|
||
例如完全自研 diff renderer、DECSTBM scroll region、深度耦合的搜索/选择/滚动体系。
|
||
|
||
3. **什么时候必须把“退化条件”写进设计**
|
||
Claude 的很多收益不是来自“总能更聪明地 diff”,而是来自“知道什么时候该 full reset、什么时候该保守禁用”。
|
||
|
||
在实际实施时,应与以下文档配套阅读:
|
||
|
||
- `01-performance.md`:看冷启动、MCP 生命周期与 deferred work 如何落地
|
||
- `02-screen-flickering.md`:看同步输出与底层 render 路线如何分阶段推进
|
||
- `03-rendering-extensibility.md`:看 parser、streaming、高亮、虚拟滚动如何吸收 Claude 的经验
|
||
- `06-implementation-rollout-checklist.md`:看哪些结论能进入当前灰度,哪些仍只能作为长期方向
|
||
|
||
## 10. 对 qwen-code 的可执行建议
|
||
|
||
### 10.1 近期就能吸收的能力
|
||
|
||
1. **启动并行化**
|
||
- 参考顶层预取、副作用并行、feature-gated require
|
||
2. **终端能力 gating**
|
||
- 支持 synchronized output / extended keys / xterm.js 检测
|
||
3. **Markdown token cache + plain-text fast path**
|
||
4. **Streaming stable prefix / unstable suffix**
|
||
5. **MCP 批量状态更新**
|
||
6. **虚拟滚动的 scroll quantization / resize scaling / clamp 思路**
|
||
|
||
### 10.2 中期可以部分迁移的能力
|
||
|
||
1. `ScrollBox` 风格的 DOM-mutation scroll path
|
||
2. transcript 搜索与虚拟列表协同设计
|
||
3. 远端 MCP reconnect / list-changed 增量更新
|
||
4. fallback-first 的异步高亮加载
|
||
|
||
### 10.3 长期才值得考虑的能力
|
||
|
||
1. 自定义 screen buffer
|
||
2. prevScreen blit
|
||
3. full diff patch pipeline
|
||
4. DECSTBM scroll region
|
||
5. 完整替换 Ink 内核
|
||
|
||
## 11. 不建议直接照搬的部分
|
||
|
||
1. **完整自定义 Ink 栈**
|
||
- 维护成本极高
|
||
- 与 Claude 其他基础设施深度耦合
|
||
2. **把滚动、搜索、选择、message actions 一次性全做**
|
||
- qwen-code 应先聚焦长会话滚动和防闪烁主路径
|
||
3. **在现阶段直接上 DECSTBM**
|
||
- 没有同步输出与 frame ownership 做前提,会适得其反
|
||
|
||
## 12. 对现有 TUI 优化文档的具体修订要求
|
||
|
||
1. `01-performance.md`
|
||
- 加入 Claude 的启动并行化、top-level prefetch、feature-gated import 经验
|
||
- 把 MCP 设计从“discover once”扩展到“批量更新 + 运行期变更 + reconnect”
|
||
2. `02-screen-flickering.md`
|
||
- 强调 synchronized output 的 gating 原则
|
||
- 明确 DECSTBM 只能放在同步输出和 diff patch 之后
|
||
3. `03-rendering-extensibility.md`
|
||
- 增加 `marked` token cache、fast path、streaming stable prefix
|
||
- 增加虚拟滚动实现细节,避免停留在概念层
|
||
|
||
## 13. 一句话判断
|
||
|
||
Claude Code 提供的最大价值,不是“它已经把 CLI 做得很复杂”,而是它把 **启动、终端、滚动、渲染、MCP 生命周期** 串成了一套完整的工程体系。qwen-code 不需要复制它的整套内核,但完全可以沿着同一张路线图,分阶段把收益最大的能力先补起来。
|
||
|
||
进一步针对“是否直接拷贝 Claude 魔改 Ink”的工程、许可证和可维护性评估,见 [15-complete-tui-flicker-closure-plan.md](./15-complete-tui-flicker-closure-plan.md)。该文档的结论是:不建议复制实现代码,应迁移设计原则并实现 qwen-owned renderer / managed viewport。
|