Skip to content

feat(editor): 文件内查找——接入 @codemirror/search(Cmd/Ctrl+F) - #587

Open
suiyideali wants to merge 1 commit into
omdsh-dev:mainfrom
suiyideali:fix/editor-find
Open

feat(editor): 文件内查找——接入 @codemirror/search(Cmd/Ctrl+F)#587
suiyideali wants to merge 1 commit into
omdsh-dev:mainfrom
suiyideali:fix/editor-find

Conversation

@suiyideali

Copy link
Copy Markdown

现状

  • src/client/TextEditor.tsx 只 import 了 @codemirror/state / view / commands,编辑器没有任何查找面板:Cmd/Ctrl+F 不被 CodeMirror 接管,长文件里浏览器原生查找只能命中已渲染(虚拟化)的行。
  • package.jsondependencies@codemirror/search@^6.7.1死依赖:全仓库 src 零 import(grep -rn "@codemirror/search" src/ 无输出)。
  • 上游 main 尚无实现:git log --all -S searchKeymap 空,无 search / find 分支。
  • README.md / README_EN.md 快捷键表都没有查找条目。

改动点

  1. 接入扩展src/client/TextEditor.tsx):共享 extensions 数组新增 ...cmSearchExtensions() + searchPhrases.of()。搜索扩展对 code / markdown 两个 viewer、只读「预览」与编辑模式共用同一份扩展表,不是仅可编辑的分支。search({ top: true }) 把面板固定在编辑器顶部;keymap.of(searchKeymap) 提供 Mod-f 打开、Mod-g / Shift-Mod-g 下一个 / 上一个、Mod-Alt-g 跳转到行、Escape 关闭。
  2. 新增 src/client/cm-search.ts:扩展装配 + 令牌化面板主题 + CmSearchPhrases(phrases 的 Compartment)。
  3. 优先级不被破坏:search 的 keymap 注册在编辑器自有 keymap.of([...]) 之前。与 defaultKeymap 唯一冲突键是 Escape(默认 simplifySelection)——放前面让「面板打开时 Escape 关面板」成立,而面板未开时 closeSearchPanel 返回 false 自动回落到原行为;Mod-s 只存在于编辑器 keymap,位置与优先级零变化(单测断言 api.fsWrite 仍被调用)。
  4. i18n:新增 15 个 search* key——zh/enlocales.ts,其余 19 份词典全量补译tests/locales.spec.ts 的 key-set 相等断言)。面板文案由 CodeMirror 的 phrases facet 提供,语言切换经 Compartment 就地 reconfigure(文档 / 撤销历史 / keymap / 滚动不动)。locales.ts 新增 localeSignature(),TextEditor 同时订阅 DSH locale 与 better-locale override——tab-cell memo 只比较 DSH localeRevision,ja override 切换不会触发子组件重渲染,靠父组件重渲染会漏。
  5. 文档README.md / README_EN.md 快捷键表各加一行「文件内查找 / Find in file」。
  6. 测试:新增 tests/editor-find.spec.tsx(4 例);tests/e2e/mount.e2e.ts 打开 seed 文件后补一段真机断言。

验证输出(真实粘贴)

$ pnpm install
Done in 21.9s                                    # Node v26.5.0 / pnpm 9.14.4

$ pnpm typecheck
> tsc --noEmit                                   # 无输出 = 通过

$ pnpm lint
> eslint .                                       # 无输出 = 通过

$ pnpm test
 Test Files  7 failed | 118 passed (125)
      Tests  52 failed | 1271 passed | 9 skipped (1332)

# 未改动的 main 基线(git stash 后同命令)
 Test Files  7 failed | 117 passed (124)
      Tests  52 failed | 1267 passed | 9 skipped (1328)

失败项两边完全同集agent-pty / smokeposix_spawnp failed,node-pty 在本机沙箱里无法 spawn)+ free-window / sidebar-auto-activation / bottom-auto-terminal / free-window-restored-id(jsdom 的 localStorage 在 Node 26 下不可用)。本 PR 只新增 4 例,全绿。

构建体积(pnpm buildlib/ 被 gitignore,故只提交源码——git ls-files lib 为空):

lib/client-editor.js    2,101,799 → 2,158,095   +56,296  (+2.68%)   ← 新代码全在这里
lib/client.js             923,817 →   924,831    +1,014  (+0.11%)   ← 只有 zh/en 词典 15 条
lib/client-locale.js      705,875 →   716,930   +11,055             ← 19 份词典补译

$ grep -c "cmSearchExtensions\|@codemirror/search" lib/client.js        → 0
$ grep -c "searchFind\|cmSearchExtensions\|CmSearchPhrases" lib/client-editor.js → 9

@codemirror/search 只进懒加载 client-editor.js,核心 bundle 零引入。chunk 契约测试:

$ pnpm vitest run tests/chunk-artifact.spec.ts tests/bundle-route.spec.ts \
    tests/chunk-loader.spec.ts tests/manifest-consistency.spec.ts
 Test Files  4 passed (4)
      Tests  37 passed (37)

挂载冒烟(真实 Chromium,pnpm build && pnpm pack && pnpm test:mount):

  ✓  11 tests/e2e/mount.e2e.ts:144:1 › plugin mounts into the DSH shell and survives a built-in tab sweep (13.7s)
  1 failed
    tests/e2e/desktop-layout.e2e.ts:35:1 › right panel keeps desktop session actions in their header positions
  17 passed (1.2m)

mount.e2e.ts 里新增的断言走通:文件树打开 seed 文件 → 点击 .cm-contentMeta/Control+f.cm-panels-top .cm-search 可见 → Escape 关闭(真实浏览器 + 懒加载 client-editor.js chunk)。唯一失败的 desktop-layout.e2e.ts 是 DSH 宿主「选择工作区目录」对话框未出现,在未改动的 main 上以同一报错复现(该用例不打开任何文件,与编辑器无关)。本地跑该 lane 需要 pnpm 11 + PLAYWRIGHT_BROWSERS_PATH(本机 playwright 缓存目录只读),与代码无关。

取舍

  1. 面板固定顶部search({ top: true }),与浏览器/IDE 查找条一致,且不遮挡光标行(CodeMirror 默认在底部)。
  2. 只读预览同样能查找:search 在共享扩展表里,代码查看器(无编辑开关、默认就是「预览」态)与 markdown 源码模式都可用。但 markdown/HTML 的渲染预览是另一套 DOM——CodeMirror 表面此时 display:nonemount.e2e.ts 有互斥断言),Cmd+F 走浏览器原生查找(对渲染后的 DOM 有效),本 PR 不劫持它。若希望「在渲染预览里也走 CodeMirror 查找」,必须先自动切到原文模式,会改变现有模式语义,留待维护者定夺。
  3. i18n 走词典而非内联字符串:CodeMirror 面板文案虽是第三方短语,但属用户可见 UI,按 AGENTS §5 落进 betterSidebar 词典。代价是新增 15 key 需要 20 个词典文件全量补译(与仓库既有做法一致,例如 changesFoldLoading 那次 20 文件 21 处);en 值刻意与 CodeMirror 上游默认文案逐字一致,保证英文体验零变化。
  4. 面板配色:CodeMirror 自带面板用 &light 灰白配色(编辑器主题未声明 dark),直接接入会在深色皮肤里浮出一个白框,因此新增令牌化面板主题(只覆盖 chrome:面板底/边框、输入框、按钮、关闭键;匹配高亮保留 CodeMirror 的半透明默认色,深浅两种皮肤都可读)。
  5. 范围外:终端搜索(@xterm/addon-search)另开 PR;未 bump version、未打 tag、未发 npm。

- src/client/TextEditor.tsx: 共享扩展表新增 search({top:true}) + keymap.of(searchKeymap)
  (Mod-f 打开、面板固定编辑器顶部、Mod-g / Shift-Mod-g 下一个 / 上一个、Mod-Alt-g
  跳转到行、Escape 关闭)。搜索扩展与代码 / markdown 两个 viewer、只读「预览」与
  编辑模式共用同一份扩展表,不存在仅可编辑的分支;search 注册在编辑器自有 keymap
  之前,Escape 优先关面板(面板未开时 closeSearchPanel 返回 false,仍回落到
  simplifySelection),保存键 Mod-s 位置与优先级不变。
- src/client/cm-search.ts(新增): 面板 chrome 走 --dsw-* 令牌(CodeMirror 自带
  面板按 &light 上灰白配色,深色皮肤下会浮出一个白框);phrases facet 放进
  Compartment,语言切换就地 reconfigure(文档 / 撤销历史 / keymap / 滚动不动)。
- i18n: 新增 15 个 search* key——zh/en 在 locales.ts,其余 19 份词典全量补译
  (tests/locales.spec.ts 的 key-set 相等断言);locales.ts 新增 localeSignature(),
  TextEditor 同时订阅 DSH locale 与 better-locale override(tab-cell memo 不比较
  override,只靠父组件重渲染会漏掉 ja 切换)。
- README / README_EN: 快捷键表各加一行「文件内查找 / Find in file」。
- tests/editor-find.spec.tsx(新增): 扩展含 search keymap、Mod-f 开顶部面板、
  Escape 关闭、语言热切换后重开面板文案跟随、Mod-s 保存键仍生效。
- tests/e2e/mount.e2e.ts: 打开 seed 文件后补 Ctrl/Cmd+F → .cm-search 面板可见、
  Escape 关闭的真机断言(真实 Chromium + 懒加载 client-editor.js chunk)。

背景:package.json 里 @codemirror/search 一直是死依赖(src 零 import),编辑器
没有任何查找面板——Cmd+F 不被 CodeMirror 接管,长文件里浏览器原生查找只能命中
已渲染的行。本次只接入文件内查找,终端搜索(@xterm/addon-search)另开 PR。
@suiyideali

Copy link
Copy Markdown
Author

@Menghuan1918 打扰一下:本 PR 的 workflow run 目前停在 action_required(0s,三个 job 一个都没启动)——首次贡献者的 fork PR 需要维护者点一次 Approve and run workflows,麻烦帮忙批准一下,让 CI 真正跑起来。

同时补一份「提交前自检」(make check = typecheck → lint → build → test → check:consumer-types)的真实输出,按 CI 的 Node 版本(ci.yml 三个 job 均为 node-version: 22)跑的:

$ node --version
v22.23.2

$ pnpm typecheck              # 无输出 = 通过
$ pnpm lint                   # 无输出 = 通过
$ pnpm build                  # 通过
$ pnpm check:consumer-types
[check-consumer-types] OK: the client/service declaration surface is node-free and self-contained.

$ node node_modules/vitest/vitest.mjs run
 Test Files  2 failed | 123 passed (125)
      Tests  26 failed | 1297 passed | 9 skipped (1332)

上面 2 个失败文件是 tests/agent-pty.spec.tstests/smoke.spec.ts,原因是本机执行环境禁止分配 PTY(直接 node -e "require('node-pty').spawn('/bin/sh',['-c','echo hi'])" 同样报 posix_spawnp failed),与改动无关,CI 的 ubuntu runner 上不受此限。

本 PR 新增的两个 spec 在 Node 22 下全绿:

 ✓ tests/editor-find.spec.tsx (4 tests) 122ms
 ✓ tests/locales.spec.ts (16 tests) 16ms

一个提醒:若在本地用 Node 26 跑 pnpm test,会额外看到 26 个失败(tests/free-window.spec.tsx / tests/sidebar-crash.spec.tsxlocalStorage.clear()Cannot read properties of undefined,Node 26 的全局 localStorage 需要 --localstorage-file 才可用)。这与本 PR 无关:在未改动的 main 上 git stash 后逐项复现(52 failed | 1267 passed),换 Node 22 后这批全部消失。

挂载冒烟本地也跑过(真实 Chromium + pnpm build && pnpm pack 产物挂载):17 passed / 1 failed,唯一失败项 desktop-layout.e2e.ts › right panel keeps desktop session actions in their header positions 在未改动的 main 上以同一报错复现(该用例不打开任何文件,与编辑器无关)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant