# dsh-composer-pro(DSH 输入框增强 · Composer Pro) **[中文](README.md) · [English](README.en.md)** ![概览](docs/banner.svg) ![补全与配对](docs/demo.gif) *动图四帧:词表补全+行内灰字提示(`inp` → `input`)· 文件路径补全 · 选中一段打括号把它框起来 · 上下文候选* 把 DeepSeek Harness(DSH)Web 输入框的手感对齐 **VS Code**:括号引号自动配对、选中一段再打括号就环绕起来、 光标右边已是闭符时再打它直接跳过、退格删掉一对空括号;再给一个补全弹层——**草稿里出现过的词、各语言关键字与标签、 工作区文件路径、settings 里配的模型名**,打满两个字母自己弹。 标准 DSH bundle 插件:一条命令安装、**不 require 任何第三方包**、**不写任何文件**、**不改宿主一行源码**,也不需要任何令牌。 浏览器半边是纯 DOM(不依赖 react、不引编辑器库),服务端半边只有两个只读路由。 ## 装在哪、怎么装 | 你要装在哪 | 装法 | 注意 | |---|---|---| | **DSH Web(`dsh web`)** | `dsh plugin --profile web add https://github.com/mengge237/dsh-composer-pro` | 推荐。也可以装本机源码目录:`dsh plugin --profile web add "E:/S_Software/deepseek-harness/plugins/dsh-composer-pro"` | | **官方桌面端(Electron)** | 用**桌面端自己的插件管理器**装 | 别用 `dsh plugin --profile desktop`(那条路被 Electron 独占,CLI 会拒绝);桌面端会话里让 DSH 自己装 | | **开发(改代码)** | 源码目录 + `file:` 装法 | 客户端半边**每次请求现读磁盘**,改完刷新页面即生效;**服务端半边(`lib/index.js`、`lib/words.js`)改完要重启 `dsh web`** | 卸载:`dsh plugin --profile web remove dsh-composer-pro`(不留任何数据文件,没有要清理的东西)。 ## 特性 ### 编辑手感(对齐 VS Code) - **自动配对**:`(` `[` `{` `"` `'` 反引号成对插入,光标落在中间;**全角也一样**(`(` `【` `「` `《` `“` `‘`),中文输入法下走 input 事件补配对(IME 合成的字符不一定带对应 keydown);光标前是标识符或路径字符时**不**配对(保护 `call(` 与 `E:/path` 这类写法) - **环绕选区**:选中一段再打开括号 → `(选中内容)`,并且**选区还原到内层内容**上,接着打字就是改内容; 半角走 keydown、**全角/中文输入法走 `beforeinput`**(IME 合成的字符没有可用的 keydown,只有 beforeinput 能在 DOM 变更前拿到要插的字符);`Shift` 组合键打出的 `{` `"` 同样有效 - **行内灰字提示**:首位候选比已打的前缀多出来的那截,用灰字贴在光标后面(VSCode inline suggestion 手感),`Tab` 收下、`Esc` 收起 - **跳过闭符**:光标右边已经是该配对的闭符时,再打它只移动光标,不重复插 - **删空对**:光标夹在 `()` 中间按退格,一次删掉两侧 - **语言提示**:草稿里写了代码围栏(```` ```js ```` / ```` ```python ````)就按该语言上词表 ### 补全弹层的位置与手感(对齐 VSCode) - **锚在光标上**,不是输入框左边缘:一行长文时不会把弹层甩到半屏外 - **优先落在光标下方**;下方放不下(输入框在窗口底部,通常如此)才翻到**光标上方 6px**,永远不盖住你正在写的那一行 - **宽度固定一档、高度封顶可滚**:宽度不随候选长短抖动,打字快时不会一跳一跳 - **行内排版照 VS Code**:左边类型图标、中间标签(命中字符加粗)、**右边**一个淡类型词——类型信息一律在右侧, 不会跑到候选名前面去 - **候选列表没变就不重画**:连续按键时不会闪,也不会出现两个弹层 ### 补全候选(四类) - **本文词**:草稿里已经出现过的标识符,按出现次数排序,**本地即时算、不等网络**(VSCode 的 word based suggestions 口径) - **排序口径 = VS Code 的模糊打分**:`lib/fuzzy.js` **逐字取自 microsoft/vscode**(MIT,见 THIRD-PARTY.md)—— 前缀、词边界、驼峰、连续命中都加分,间隔与分散扣分;命中位置由它给出,列表里**命中的字符加粗**(就是 VS Code 那个样子) - **语言词表**:HTML 标签 112、JS 关键字 47、Python 129、Java 56、CSS 属性 600、HTML 全局属性 27(数据来源见「来源与许可」) - **中文词也算候选**:草稿与上下文里的**汉字词**(按二元/三元组拆,中文没空格分词)同样按前缀提示—— 写过「关于文档的说明」,后面打「文」就提示「文档」,跟写代码时提示变量一个手感 - **上下文候选**:页面上可见的对话文本里出现过的标识符(≥4 字符)也按前缀补出来,右侧类型标「上下文」—— 刚讨论过的函数名、文件名不用再手打一遍(只读页面文本,2 秒缓存,不动宿主 DOM) - **语言推断**:没写围栏时先按上下文猜(`def`/`import`/`.py` → python,`
` → html,`const`/`await` → javascript), 猜不出再退到习惯 - **语言习惯**:没写围栏时按**你实际在用的语言**上词表——草稿里写过 ```` ```js ```` 或接受过某套词表的候选都会记一笔(存本机 `localStorage`,不上传任何东西) - **工作区文件**:深度 ≤3、跳过 `node_modules`,按**路径段前缀**匹配(见「候选匹配规则」) - **模型名与片段**:从 settings 真源读出的模型 id,加上几个常用代码片段 ### 触发与接受 - 打满 **2 个标识符字符自动弹**;`Ctrl+Space` 手动弹;行首或空白后打 `/`、`@` 也弹 - **`Tab` 接受**(或鼠标点某一行);`↑↓` 选择;`Esc` 关闭 - **回车不吃弹层**:聊天框里回车就是发送。这条是 2026-10-16 真机事故换来的——原来回车也算接受,弹层开着时连按回车会把候选一遍遍插进正文 ### 性能 - 工作区文件索引与模型 id 各带 **20 秒 TTL 缓存**:首次全盘走一遍约 23ms,之后命中 0~1ms - 每个按键只发一次本地请求(loopback),后发先至的旧响应按序号丢弃 ## 键位(对照 VS Code) | 操作 | 键 / 触发 | 行为 | |---|---|---| | 自动配对 | `(` `[` `{` `"` `'` 反引号;全角 `(` `【` `「` `《` `“` `‘` | 光标前是路径/标识符字符时**不**配对;光标**右边紧跟文字**时只插这一个开符(在一段文字前打前引号就只出一个引号);右边已是同一个闭符时跳过 | | 环绕选区 | 选中后打任意开括号 | `选区` → `(选区)`,选区保持在内层 | | 跳过闭合 | 光标右已是应得的闭符时再打它 | 不重复插,移过去 | | 删空对 | 光标夹在 `()` 中间按退格 | 一次删两侧 | | 补全 | 打满 2 个标识符字符 / `Ctrl+Space` | 弹层:本文词优先,其后语言词表 / 文件 / 模型 / 片段 | | 接受 | **只有 `Tab`** 或点击某一行 | 回车保持发送;接受后弹层不会自己复开 | | 关闭与选择 | `Esc` / `↑` `↓` | `Shift+Enter` 仍是换行 | ## 候选匹配规则 - **文件候选只认「某个路径段以查询开头」**:`attrib` → `5_临时/attrib-index.diff` 排第一;段内中段匹配要求查询 ≥3 个字符 - **不做松散模糊(纯子序列)**:路径本来就长,子序列几乎必中,等于「打一个字给你一堆别的文件」,会逼着人逐个核对 - 子序列只留给词表 / 模型 / 片段这类短候选,且查询至少 3 个字符 ## 已知边界 - **服务端半边改代码要重启 `dsh web`**(ESM 已载入内存不重读);客户端半边刷新页面即生效 - 语言词表的键位(如 `inp` → `input`)在**服务端**,因此首次装好或改完词表后,要重启一次才全线生效 - 只在 **0.2.0-rc.2** 的 web 与桌面端两个 profile 上实测过(宿主版本更新后请重跑自检) - 老式 `textarea` 形态的输入框只有离线测试覆盖,真机上没样本 - **不改宿主 composer 的数据模型**:所以 `Alt+↑/↓` 移动行、`Ctrl+D` 复制行、`Ctrl+/` 注释切换这类要动 Lexical 文档模型的操作**没有做**,别当已有功能 - 补全只做「插入文本」,不做语义分析:它不知道你的代码里哪些符号可以用 ## 目录结构 | 路径 | 用途 | |---|---| | `lib/index.js` | 服务端半边:两条只读路由(`/dsh-composer-pro/words`、`/config.json`)与本机信任栅栏 | | `lib/client.js` | 浏览器半边:keydown 捕获层、配对与环绕、补全弹层(纯 DOM,无第三方依赖) | | `lib/words.js` | 各语言词表(静态数组,出处与 commit 写在文件头) | | `test/` | 五支测试 + 真机验收报告(`REPORT-2026-10-16.md`) | | `docs/` | README 配图 | | `THIRD-PARTY.md` | 借用数据的来源与**许可证原文** | | `cordis.patch.yml` | DSH bundle 声明(`dsh.bundle.patch`) | ## 开发与测试 ```bash npm test # 五支,61 项,0 网络:数据层 / 路由与信任栅栏 / bundle 形状 / 假 DOM 行为测试 / 许可与合规 ``` - `test/selftest.mjs`:候选排序、模型 id 解析、文件收集封顶、workspace.json v2 形状、词表命中 - `test/server-routes.mjs`:路由形状、**本机信任栅栏**(非 loopback Host 与跨站 Origin 一律 403)、方法闸、`config.json` 载荷 - `test/client-smoke.mjs`:bundle 头与导出形状、键位判据、本文词与围栏识别 - `test/client-behavior.mjs`:**假 DOM 驱动真监听**——配对、环绕、跳闭合、删空对、Ctrl+Space 取数、Tab 接受替换、回车放行、接受后不复开 - `test/compliance.mjs`:**许可与条例自检(9 项)**——自家 MIT 署名、每个借用件在 THIRD-PARTY.md 里都有仓库+许可+钉住的 commit、 MIT/CC0 原文照录、逐字搬来的代码带出处头(含「改了什么」)、运行时零第三方依赖、仓里没有密钥、版本号与更新日志对得上、 登记表那侧 `npm` 键合法、**上游版权声明原样保留且排在我们的说明之前**。反向对照过:删掉台账里一行、把版本号改成 9.9.9,它都会红 - 真机验收(headless 打真 composer,11 项):结果与复现命令见 [test/REPORT-2026-10-16.md](test/REPORT-2026-10-16.md) 改这块前先读 README 的「形态与边界」与语言词表文件头:宿主 composer 是 **Lexical**,它不认 `sel.modify(...)`, 选区操作必须走「摊平字符偏移 + 显式 Range」,且我们的监听在捕获阶段、早于宿主处理,插入后改选区要推一拍。 ## 常见问题 - **打 `inp` 没出现 `input`?** 语言词表在服务端半边:装好之后(或改过词表之后)要重启一次 `dsh web` - **为什么回车不接受候选?** 聊天框里回车是发送键;接受请用 `Tab`(0.2.2 起如此,之前回车也算接受,会连插候选) - **选中一段再打括号没框住?** 0.3.0 起已修:半角走 keydown、全角与中文输入法走 beforeinput;若你用的是老版本,先升级 - **为什么不再给一堆不相关的文件?** 0.2.2 起文件候选只认路径段前缀;要更宽的匹配请直接用 `/` 或 `@` 触发 - **它会改我的文件吗?** 不会。只读工作区文件清单与 settings 真源,一个字节都不写 - **桌面端怎么装?** 桌面端是独立 profile,用桌面端自己的插件管理器装(`dsh plugin --profile desktop` 会被拒) ## 更新日志 - **0.6.1**(2026-10-16):**IME 合成期间不补全**——打拼音时(如 `ye`)不再被 JS 关键字 `yield` 抢, 弹层与行内灰字一起收起,上屏后再按中文内容重新判一次(本人 2026-10-16 报的「后面显示 eld」) - **0.6.0**(2026-10-16):① **中文词补全**——草稿/上下文里的汉字词按二元·三元组拆开后参与前缀匹配(中文没有空格分词; 长句尾巴按 1~4 字后缀各试一次),写过「文档」后面打「文」就能提示;触发条件也认汉字; ② **引号成串修复**——补上 VS Code 的引号规则(前一个字符是同引号时只插一个),并按「一次按键只认最后一次」 守住延后的光标复位、按住键的自动重复不再配对、apply 加单例防止重复挂载(重复挂载会让一次按键执行两遍) - **0.5.0**(2026-10-16):① **上下文关联**——新增「上下文候选」(把页面上可见的对话文本里的标识符按前缀补出来,类型标「上下文」)与「语言推断」(没写围栏时按 def/import/.py、
、const/await 这些线索猜语言,再退到习惯);② **行内排版照 VS Code**:左边类型图标、中间标签(命中加粗)、右边淡类型词,类型不再跑到前面 - **0.4.3**(2026-10-16):修引号手感——① 光标右边紧跟文字时**只插一个开符**(在已有文字前打前引号不再被补成一对);② 右边已经是同一个引号时**跳过**而不是再套一对;③ 插入后光标**立刻**摆到中间(原来只延后一拍,快速连打两个引号会看不到右边已有闭符、再插一对),延后那一拍改成按绝对位置复位(不然会把光标挪到开符之前) - **0.4.2**(2026-10-16):`lib/fuzzy.js` 顶部**原样补回上游逐字的版权与许可头**(MIT 要求保留声明,之前被我自己的出处说明顶掉了); 合规闸加一条判据盯死它。排序实现仍直接用上游那份(MIT 合规,不存在侵权,无需另写一套) - **0.4.1**(2026-10-16):加**合规自检闸** `test/compliance.mjs`(8 项:署名/出处/许可证原文/出处头/零依赖/无密钥/版本一致/登记表条例),挂进 `npm test`;仓公开前先跑它 - **0.4.0**(2026-10-16):排序换成 **VS Code 的 fuzzyScore**(lib/fuzzy.js 逐字取自 microsoft/vscode,MIT):前缀/词边界/驼峰/连续加分、间隔与分散扣分;列表按它的样子重画(命中字符加粗、右侧一个淡类型词),并去掉「本文出现 N 次」这类啰嗦标注 - **0.3.0**(2026-10-16):框选 + 打符号修复(Shift 组合键不再被挡、全角/中文输入法改走 beforeinput);新增**行内灰字提示**(首位候选的后缀贴在光标后);新增**语言习惯**(没写围栏时按你实际用过的语言上词表) - **0.2.3**(2026-10-16):补全角/中文成对符(`(` `【` `「` `《` `“` `‘`,中文输入法下走 input 事件补配对);补全弹层改成**贴光标定位**(优先下方、放不下才翻上方 6px、宽度固定、高度封顶可滚、列表没变不重画) - **0.2.2**(2026-10-16):回车不再被弹层吃掉;接受候选后弹层不再自动复开;文件候选改「路径段前缀」匹配;文件索引与模型 id 加 20s 缓存 - **0.2.1**(2026-10-16):修 Lexical 选区(改显式 Range)与捕获阶段时序(选区调整推一拍);环绕选区后选区还原到内层 - **0.2.0**(2026-10-16):新增本文词补全、语言词表(HTML/JS/Python/Java/CSS)、打两个字符自动弹、围栏语言提示 - **0.1.0**(2026-10-15):首版——自动配对、环绕选区、跳闭合、删空对、Ctrl+Space 弹层(文件与模型名) ## 来源与许可 - **交互设计**参照 Visual Studio Code(MIT,microsoft/vscode):word-boundary 抑制、surround、type-over closer、删除空对、 word based suggestions 的排序口径——**未复制其源码**,只对齐行为语义 - **词表数据**借用自 monaco-editor、codemirror/lang-html、codemirror/lang-css、MDN browser-compat-data, 每条都在 [THIRD-PARTY.md](THIRD-PARTY.md) 里写明了仓库、commit、取用文件与**许可证原文**;抽取是静态的,运行时不依赖这些项目 - 本插件自身:MIT(见 [LICENSE](LICENSE)) --- [English README](README.en.md)