npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-ide

v0.3.3

Published

Right-Sidebar code review + GitLens plugin for DeepSeek Harness: an embedded CodeMirror editor with a per-line blame gutter.

Readme

dsh-ide — 给 DSH 侧边栏换上一个真正的代码编辑器

English

这是什么

dsh-ide 是 DeepSeek Harness(DSH)的一个插件。DSH Web 界面右侧边栏里打开代码文件时,默认给你的是一块纯文本查看面板;dsh-ide 把它换成真正的编辑器(CodeMirror 6),并在编辑器上补齐阅读和修改代码时最需要的那些上下文:逐行 Git blame、光标行的内嵌 Git 标注、面板内的 Git 历史与提交差异、大纲树、Ctrl/Cmd+点击跳转到定义、以及不依赖外部工具的静态检查。

它解决的问题很具体:在 DSH 侧边栏里看代码或改代码时,一块纯文本面板回答不了「这一行是谁什么时候写的」「这个文件最近改了什么」「文件结构长什么样」「这个符号定义在哪」;想回答就得离开 DSH,切到别的编辑器。dsh-ide 让你不离开 DSH 就拿到接近 IDE 的这些信息,并且改完可以直接 Ctrl/Cmd+S 存回磁盘。


功能一览

编辑器

| 能力 | 说明 | |---|---| | CodeMirror 6 编辑器 | 语法高亮、行号、撤销/重做、多选区与多光标、矩形(列)选区、拖放光标、输入法(IME)输入、Tab 缩进、不可见特殊字符可视化 | | 保存 | Ctrl/Cmd+S 写回磁盘(可改绑);状态条显示 已同步 / 未保存 / 已保存,保存失败时显示真实原因 | | 文件内查找 / 替换 | Ctrl/Cmd+F 打开的是插件自己的面板(中文、和工作区查找同一套控件与样式):查询框 + Aa / .* / \b + 匹配计数 + ↑/↓(Enter / Shift+Enter)+ 替换 / 全部替换。匹配用琥珀色底 + 下划线标出(自己控制配色,不再依赖编辑器默认的低对比高亮);选中一个词时同文件内的相同词也会高亮 | | Ctrl/Cmd + 悬停显示可跳转 | 按住 Ctrl/Cmd 把鼠标移到标识符上,它会渲染成带虚线下划线的链接(背景微微泛蓝、光标变成手型)——点击就是跳转到定义,先给反馈再执行 | | 工作区查找 / 替换 | Ctrl/Cmd+Shift+F 打开:在整个会话工作区里查找(大小写 / 正则 / 全词三种模式,结果按文件分组,点一行跳到那里),并把「替换为… + 全部替换」做在同一个面板里。替换只作用于面板里列出的那些行,且宿主写入前会逐行核对那行是否仍然是搜索时看到的内容——期间被外部改过的行会被跳过并在结果里报出来 | | 主题 | 编辑器与悬停提示都走 DSH 的 CSS 令牌(--dsw-alias-*),跟随皮肤;令牌缺失时回退到内置深色配色 |

Git

| 能力 | 说明 | |---|---| | 逐行 blame gutter | 每一行左侧显示 短sha · 相对时间(如 3f9a1c2 · 2 天前);悬停给出该次提交的完整信息;工作区里还没提交的行显示「未提交」。点击某一行打开这次提交的 Git 卡片。整列可以在设置里关掉,或用快捷键 Ctrl+Alt+H 切换 | | 代码变动标记 | 两处都标:每行左侧一根彩条(绿=新增、蓝=修改、红=这一行之后删除了代码,三种都是同样粗细的竖条),以及编辑器右边缘的概览标尺(整篇改动分布,点一下跳到对应位置——IDE 的那条)。基线是 HEAD(工作区相对上次提交的全部改动,已暂存与未暂存都算);未纳入版本控制的文件整篇按「新增」标绿。两处一起用 Ctrl+Alt+C 或设置里的开关切换 | | 变动视图 | 状态条「变动」按钮打开:按行号顺序列出这个文件相对 HEAD 的每一处改动(+ / ~ / − + 行号 + 该行代码预览),点一行就跳到那行;状态条同时给出 +n −m,diff 被上限截断时写明「变动不完整」 | | 光标行的内嵌 Git 标注 | 光标所在行的行尾跟着显示同一份 短sha · 相对时间(悬停给出作者与提交标题),点它直接打开该提交。可以在设置里关掉,也可以用快捷键切换 | | 面板内的 Git 视图 | 点状态条「历史」,或点某一行 blame 进入。包含:该文件的历史列表(git log --follow,默认 50 条)、提交卡片(短 sha / 作者 / 邮箱 / 相对时间 / summary / 正文 / +n −n)、以及这次提交对本文件的 diff 分块(+ / - / 上下文三色);每行的新文件行号可点击,跳回编辑器对应行并关闭视图 | | 刷新 Git | 外部提交过、或文件被外部改过之后,重新读取每一行的提交归属,重画 gutter 与内嵌标注 | | 不完整提示 | blame 输出被上限截断时,状态条显示「blame 不完整」,而不是把部分归属当成全部 |

大纲与跳转

| 能力 | 说明 | |---|---| | 大纲树 | 面板左侧的大纲按缩进/标题层级叠成树:Python 的 def 挂在所属 class 下、JS/TS 的方法挂在类下、TOML/INI/PO 的键挂在所属节下、YAML/JSON 按缩进挂。每个节点按种类配字形与颜色(类 / 接口 / 函数 / 方法 / 类型 / 枚举 / 常量 / 标题 / 键 / 节 / 宏 / 对象 / 词条),可单个节点折叠,面板头部有「展开」「折叠」全部;点节点名跳行,点折叠箭头只折叠不跳行 | | 跳转到定义 | Ctrl/Cmd+点击一个标识符:先在当前缓冲区里找声明行(瞬时),找不到再让宿主在会话工作区内搜索(仓库里用 git grep,非仓库退回有上限的目录遍历)。找不到时状态条直说「没有找到 X 的定义」,不会静默不动 | | 返回 / 前进 | 导航历史是整个会话共享的(上限 100 条),不是每个文件各一份——从文件树打开另一个文件后按「← 返回」,会回到上一个文件和你离开时的行。记录来源:Ctrl/Cmd+点击、大纲点击、Git diff 行号、工作区查找命中、带行号的侧边栏导航;离开一个文件时会记住光标所在行。没得回退时状态条直说,不会静默无反应 |

静态检查

| 能力 | 说明 | |---|---| | 导入解析(零依赖) | 宿主自己解析:TS/JS/Vue/Svelte 的相对/绝对导入(含 /index.* 与 NodeNext 的 ./x.js → x.ts)、沿 node_modules 向上找裸包、Node 内置模块白名单、Python 的相对导入。找不到的用 @codemirror/lint 画波浪线,悬停显示文字 | | ruff(可选) | 机器上有 ruff 时,Python 文件额外跑 ruff check --output-format=json(当前缓冲区经 stdin 检查,不落临时文件),规则按族归类为 error / warning / info。没有 ruff 就只做导入检查,状态条写「仅导入检查」 | | 触发方式 | 编辑停下 400ms 后自动跑一次;也可以点状态条「重新检查」立刻重跑。状态条给出「N 错误 · M 提示」,检查被上限截断时显示「检查不完整」 |

字体与设置

| 能力 | 说明 | |---|---| | 字体族与字号 | 代码字体(CSS family 列表,按需加引号并保证等宽兜底)与代码字号 8–32px | | 检测本机字体 | Chromium 的 Local Font Access API(需要授权),按 mono/code/fira/jetbrains/cascadia 等关键词筛出候选等宽字体,点击即应用;浏览器不支持时提示手动填写字体名 | | 导入字体文件 | .ttf / .otf / .woff / .woff2 上传到宿主保存(页面无法写文件),页面每次加载时由宿主重新注册;设置面板列出已保存字体,可一键应用或删除 | | 可配置快捷键 | 文件内查找、工作区查找、代码变动标记、左侧 blame 列、内嵌 Git 信息、返回、前进、大纲、刷新 Git、保存,十项都能改绑 |

它认领哪些文件

认领(在这些文件上接管编辑器):

| 类别 | 扩展名 | |---|---| | Python | py pyi | | JS / TS 系 | js jsx mjs cjs ts tsx vue svelte | | 数据与配置 | json jsonc yaml yml toml ini cfg cnf conf properties | | SQL | sql | | 脚本 | sh bash zsh ps1 bat | | 其它语言 | go rs java kt cs rb php c h cc cpp hpp hh cxx | | gettext | po pot |

刻意不认领的两类(这是有意的取舍,不是遗漏):

  • md / markdown / mdx:留给 better-sidebar 的 Markdown 预览器,它有预览/编辑切换、Mermaid 渲染和目录大纲,而这些在纯编辑器里没有。
  • .env:那套渲染器会对凭据形态的内容做遮罩,纯编辑器不会。

代价是这两类文件在这个插件里没有 blame、没有导入检查。HTML、图片、PDF 等也照旧由各自的预览器处理。另外,个别被认领的扩展名(如 rb、php、ps1、bat)目前没有高亮语法,会以纯文本显示。


安装

发布到 npm 之后,两条路都只需要一条命令——优先走这条:

# Web / CLI(profile 名换成你要用的那个)
npx @deepseek-ai/dsh plugin --profile web add [email protected]

桌面版:打开应用的插件管理窗口,添加 [email protected](桌面版的插件管理器只接受 npm registry 的包名/精确版本,见下面「桌面版」一节)。

包名 dsh-ide、access: public,publishConfig.registry 已写死指向 registry.npmjs.org,所以即使本机 registry 是镜像源,npm publish 也发到正确的地址。

发布到 npm(维护者)

npm publish --dry-run                              # 先看会发出去什么(24 个文件,约 425 kB)
npm login --registry=https://registry.npmjs.org    # 本机默认 registry 是 npmmirror(只读镜像),登录必须显式指向 npmjs
npm publish                                        # publishConfig 已指向 npmjs,不必再加 --registry

每次发布都要递增 version(npm 不允许覆盖已发布的版本号);发布后用 npm view dsh-ide version 确认。

如果发布返回 403 ... Two-factor authentication or granular access token with bypass 2fa enabled is required:账号的 2FA 模式是「授权 + 写入」,~/.npmrc 里的经典 token 不能绕过它。二选一:

npm publish --otp=123456                           # 现取一个 30 秒验证码
# 或者:npmjs.com → Access Tokens → 生成 Granular Access Token,
#       勾选 "Allow this token to bypass two-factor authentication",
#       再 npm config set //registry.npmjs.org/:_authToken=<新 token>,然后 npm publish

从源码安装(开发用;未发布也能装)

1. 构建

在插件目录里:

npm install
npm run build

npm run build 依次做三件事:编译宿主半片(tsc,输出 lib/index.js、lib/host/*.js 和 lib/types/**)、对浏览器半片做类型检查、再用 esbuild 把 src/client/index.tsx 连同 CodeMirror 打成 lib/client.js(CodeMirror 是内联进 bundle 的,不需要 CDN 或额外安装)。

2. 装进一个 DSH profile

npx @deepseek-ai/dsh plugin --profile web add C:\path\to\dsh-ide

把路径换成这个目录的绝对路径。如果 dsh 已经在 PATH 上,可以把 npx @deepseek-ai/dsh 简写成 dsh。

这条命令做两件事:在 profile 目录里调用 pnpm,把这个目录写成一条 link: 依赖(例如 "dsh-ide": "link:C:/path/to/dsh-ide"),然后把 dsh-ide 追加进该 profile package.json 的 dsh.profile.bundles 列表。插件自己带的 cordis.patch.yml 提供这一层的声明,所以不需要手改 profile 的 patch 文件。

因为是 link: 而不是拷贝,之后改代码只需要重新 npm run build,不需要重新安装。

3. 重启 DSH,再刷新页面

dsh web

为什么必须重启:profile 的 bundle 列表和宿主半片注册的路由,都是在 DSH 进程启动时读取的。为什么还要刷新页面:浏览器半片(lib/client.js)是页面加载时取回的。所以——

  • 只改了浏览器半片:重新 npm run build,刷新页面即可;
  • 动了宿主半片(src/index.ts、src/host/**)或增删了 bundle 行:需要重启 DSH 进程,再刷新页面。

4. 卸载

npx @deepseek-ai/dsh plugin --profile web remove dsh-ide

dsh plugin 把后面的参数原样转给 profile 目录里的 pnpm(remove 是 pnpm 的 uninstall 别名),随后按已安装状态重新对齐 dsh.profile.bundles:依赖没了,对应的 bundle 行也会被移除。同样要重启 DSH 并刷新页面才生效。

它需要一个什么样的侧边栏

dsh-ide 自己不带侧边栏,它需要宿主的侧边栏提供「文件查看器注册表」。

  • 装了 dsh-better-sidebar:dsh-ide 注册一个优先级高于内置 code 查看器的 viewer,代码文件在这个编辑器里打开,其它文件继续用原来的预览器。本条是在本机验证过的路径(本机 profile 里是 dsh-better-sidebar 0.19.1)。
  • 没装:插件退回用 DSH Core 右侧栏自带的 tab 注册表来认领同一批扩展名。这条兜底路径的注册与渲染现在有测试覆盖了(scripts/verify-apply.mjs 用假 context 真的跑一遍 apply():断言 tab 类型的 id/kind/优先级、整地址 glob、canOpen 只接受带会话的地址、座位名、两种地址各自渲染出编辑器还是「无会话」提示,以及两种模式不会同时注册);在浏览器里真的出现在右侧栏这一层仍未在本机验证(本机装了 better-sidebar,走的必定是上面那条),所以不承诺它在所有组合下都可用。它只接受带会话的地址(也就是从文件树打开的地址);不带会话的地址会提示「这个地址不带会话,无法在此编辑(请从文件树打开)」。

桌面版(Electron)

桌面版用的是同一套机制:一个 profile、一条依赖、dsh.profile.bundles 里的一行。区别是那个 profile 归 Electron 所有,规则更严:

  • dsh plugin --profile desktop ... 会被启动器直接拒绝(profile "desktop" is managed exclusively by the Electron application),CLI 不能装、也不能改这个 profile。
  • 桌面版自己的插件管理器只接受 npm registry 的包名(可带精确版本或 tag):它读的 packageNameFromSpec() 明确拒绝 file:、路径、:// 和带空格的 spec,然后跑 pnpm add <spec> --save-exact --ignore-scripts。所以没有发布到 registry 的本地插件,走不了那个界面。
  • 组合时它还有一条围栏:profile 里每个启用的 bundle,其真实路径必须落在 profile 目录之内(desktop-host 的 allowLinkedPackages 关掉时)。也就是说 Web profile 那种 link:C:/.../dsh-ide 的写法在桌面版会启动失败——除非你跑的是工作区开发模式(pnpm run dev:desktop / start:desktop,它会传 --allow-linked-profile)。

本机(打包版桌面应用 + $DSH_HOME/profiles/desktop)实际验证过的安装方式,一行命令:

npm run build            # 改了 src/ 之后
npm run install:desktop  # 打包 → 装进 profile → 补 lockfile → 记录 lockHash
npm run verify:desktop   # 自查,不用重启应用就知道会不会启动失败

scripts/install-desktop.mjs 做的正是「那个插件管理器会留下的状态」:把 tarball 放进 profile 并解到 node_modules/dsh-ide(桌面版是 hoisted 布局,必须是真目录而不是软链)、在 package.json 里加 "dsh-ide": "file:dsh-ide-0.3.3.tgz" 与 dsh.profile.bundles 的一行、按 pnpm 的写法补 pnpm-lock.yaml 的 importers/packages/snapshots 三处(integrity = sha512-<base64(sha512(tgz))>)、最后把 desktop-runtime-state.json 的 lockHash 更新成新 lockfile 的 sha256(这个值一致,应用就走「已准备」路径,不会重新准备 profile)。重复执行是安全的。

装完完全退出再启动桌面版才生效。

⚠️ 本机装的桌面版还有一个「第三方插件管理器」(@linxin666/dsh-client-ui-plugin-manager + dshmarket,来自 @linxin666/dsh-web-all),它才是那个 profile 的实际管理者:本机实测它会在自己的事务里重写 package.json/pnpm-lock.yaml(改前留下 package.json.bak-dsh-ide),并丢掉不是它自己装的条目——我第一次手工装的那份就是这样在第二天被清掉的。它自己的安装框(「安装目标」)其实接受 file: / link: / git 源(sourceKindOf() 把 link:/file:/git:/github: 归为非 registry 来源,只拦 shell 元字符),所以在那之后:

  • 想让它管着这个插件:在那个界面的安装框里填 file:C:/Users/xiechunlong/person_data/codes/AIprojects/dsh-ide/dist/dsh-ide-0.3.3.tgz(用 file: 而不是 link:,因为 link: 指到 profile 之外会让打包版桌面应用启动失败)。
  • 或者等它发布到 npm 之后,在那里填 [email protected] —— 这是唯一完全受支持的路径。
  • 在那之前,别在那个管理器里对 dsh-ide 做增删,否则会把我装的那份清掉(再跑一次 npm run install:desktop 即可恢复)。

npm run verify:desktop(scripts/verify-desktop-install.mjs)逐条复核桌面版自己的规则:bundle 是否都在 profile 目录内、有没有软链、cordis.patch.yml / 宿主入口 / 客户端 bundle 是否齐全、dsh.client.platform 是否为 web、host 包是否声明成 peer 而不是 dependency、lockHash 是否与 lockfile 一致。

重启桌面版(完全退出再启动)后,插件与 Web 版行为一致:同一份 lib/client.js、同一批 $DSH_HOME/dsh-ide/ 下的字体与偏好设置。

本机的一个既有阻塞:这个桌面 profile 的 lockfile 里有一个包的发布时间落在 pnpm 的 minimumReleaseAge 窗口内([email protected]),所以任何由 pnpm 驱动的安装/改动(包括桌面版插件管理器自己的 pnpm add)都会以 ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION 失败。等这个包"变老"或者调整该策略后才会恢复;上面的手工流程不受影响(只有校验那一步需要 --config.minimum-release-age=0)。


使用

一次典型的阅读/修改过程

  1. 在侧边栏的文件树里点开一个代码文件,面板里打开的就是这个 IDE 视图(编辑器 + 左侧 blame gutter)。
  2. 读归属:gutter 每行是 短sha · 相对时间;光标停在哪一行,行尾就跟着显示同一份信息。点 gutter 那一行或行尾标注,打开这次提交的卡片与差异。
  3. 跳转:Ctrl/Cmd+点击一个标识符跳到它的定义;用「← 返回」回到跳转之前的位置,再「前进 →」走回去。
  4. 看历史:点状态条「历史」,上方是该文件的提交列表,点一条看它对这个文件改了什么;点 diff 里的行号回到编辑器对应行。
  5. 看结构:点「大纲」,在树里点节点跳行。
  6. 改代码:停下约 400ms 后会自动跑一次静态检查,波浪线处悬停看说明;改完按 Ctrl/Cmd+S 保存。

状态条按钮

| 按钮 | 作用 | |---|---| | 保存 | 把当前缓冲区写回磁盘;没有未保存改动时不可点。在 better-sidebar 模式下,保存按钮由面板头部渲染(侧边栏根据插件上报的状态绘制,带未保存标记),状态条里就不重复出现 | | 重新检查 | 立刻重跑一次静态检查(平时的自动检查在编辑停下 400ms 后触发) | | ← 返回 | 回到上一个跳转位置;没有可回退的历史时不可点 | | 前进 → | 返回之后再往前走一步 | | 大纲 / 隐藏大纲 | 显示或隐藏左侧大纲树(隐藏时不再重算) | | 变动 / 收起变动 | 打开或收起「代码变动」视图:按行列出相对 HEAD 的增/改/删,点一行跳到那行 | | 全局查找 / 收起查找 | 打开或收起「工作区查找 / 替换」面板 | | 历史 | 打开面板内的 Git 视图:该文件历史 + 提交卡片 + 提交对本文件的差异 | | 字体/快捷键 / 收起设置 | 打开或收起设置浮层(字体、字号、两个 Git 开关、快捷键表) | | 刷新 Git | 重新读取每一行的提交归属(外部提交或外部改过文件之后点它) |

状态条上还会出现这些文字:已同步 / 未保存 / 已保存、Ctrl/Cmd+S 保存、未纳入版本控制、blame 不完整、保存失败:<原因>、检查中…、N 错误 · M 提示、检查不完整、仅导入检查 / 无问题,以及跳转提示(例如 符号 → 相对路径:行号)。Git 视图里另有 读取提交…、没有提交历史(文件可能未纳入版本控制) 和 该提交没有改动这个文件(可能只是重命名,或是合并提交)。

设置面板

设置面板有两个入口,渲染的是同一个组件、同一份存储:

  • 状态条右侧的「字体/快捷键」按钮(两种集成模式都有,这是常驻入口);
  • 装了 better-sidebar 时,侧边栏设置卡里属于本 viewer 的那一项。

所以不存在「某一项设置只有一个入口才能改」的情况。面板内容:代码字体、代码字号(8–32px,失焦时才提交,避免逐键夹取)、检测本机字体、导入字体文件、已保存字体列表、「光标行末尾显示内嵌 Git 信息」开关,以及快捷键表。

快捷键

默认值如下,按本机平台显示(Windows/Linux 上是 Ctrl,macOS 上是 Cmd)。

| 命令 | 默认 | 说明 | |---|---|---| | 文件内查找 / 替换 | Ctrl+F | 打开当前文件的查找与替换面板 | | 工作区查找 / 替换 | Ctrl+Shift+F | 在整个工作区查找,并可批量替换列出的那些行 | | 展开/折叠代码变动标记 | Ctrl+Alt+C | 切换相对 HEAD 的增/改/删彩色条 | | 展开/折叠左侧 blame 列 | Ctrl+Alt+H | 切换每行 短sha · 相对时间 那一列 | | 展开/折叠内嵌 Git 信息 | Ctrl+Alt+B | 切换光标行末尾的作者/时间标注 | | 返回上一个位置 | Ctrl+Shift+← 或 Alt+← | 回到跳转之前的位置 | | 前进 | Ctrl+Shift+→ 或 Alt+→ | 返回之后再往前走一步 | | 显示/隐藏大纲 | Ctrl+Alt+O | 切换左侧大纲树 | | 刷新 Git 信息 | Ctrl+Alt+G | 重新读取每行的提交归属 | | 保存 | Ctrl+S | 写回磁盘 |

返回/前进各给了两个备选:Ctrl+Shift+←/→ 与 Alt+←/→。原因是没有任何一个组合在所有宿主里都能到达页面——Alt+←/→ 同时是浏览器的前进/后退,浏览器会自己执行历史跳转,而页面无法取消它(preventDefault 拦不住),实测「点按钮能回退、按快捷键没反应」就是它;Ctrl+Alt+←/→ 则是 Intel 显卡驱动的屏幕旋转热键。一个绑定可以写多个备选(用 , 分隔),命中任意一个即触发;面板里显示成 Ctrl+Shift+← / Alt+←。

怎么改绑:打开设置面板,点那一行的输入框,然后直接按下想用的组合键——不用手打字符串(手打一个永远匹配不上的绑定是这类功能最糟的失败模式)。按 Esc 清除绑定,右边的 × 也清除,清除后显示「未绑定」。不带真实修饰键的组合(例如单个字母)会被拒绝,否则编辑区里的普通输入会被这个绑定吃掉。按键别名是等价的(Left 与 ArrowLeft 是同一个键)。面板里显示的是本机真实按键名(Ctrl+Alt+H、Alt+←),存储形式是等价的 mod+alt+h。

改过的键不会挡住改进后的默认值:偏好里除了快捷键表,还记录「哪几个命令是你亲手改的」。没有这份记录时(旧版本写下的表),一个等于旧默认值的条目会被当作「当时没改过」而跟随新默认——否则新默认永远到不了已有安装;你亲手改过的(例如 alt+b)会被保留。这条记录在首次加载时由旧表推断一次并写回。

快捷键在编辑器有焦点时由编辑器处理;焦点停在面板里的按钮上时也不会失效(面板容器兜住这一层)。

关于「返回 / 前进」的几条规则(每一条都是按实机反馈修出来的):

  • 只记录跳转,不记录滚动。没有可回退的位置时状态条会写「没有可返回的位置(历史只记录跳转)」,而不是静默无反应。
  • 光标所在的那一行才算「我刚才在哪」。这条位置是随光标实时记录的,不是「写进历史时那一行」——从文件树点开另一个文件离开当前文件时,记录的是你正在读的那一行,所以返回会回到你真正看的位置,而不是回到文件开头。
  • 同一个文件内部的跳转也进历史(点大纲、点变动列表、点查找结果、本文件内的跳转定义)。以前「同一个文件就更新当前条目、不新增」的规则会把这类跳转整个吃掉,于是既回不去也前进不了。
  • 返回之后的「前进」一定还在:打开一个文件不算一次跳转。以前只要返回跨越了文件,打开目标文件时就会往历史里追加一条,把刚走过的那条前进支路截断——表现就是「明明返回成功了,前进却说没有可前进的内容」。
  • 跳转 / 返回 / 前进之后,光标会落在目标行并被滚动到中间,键盘焦点也直接交给文件:不用再点一下文件才能用快捷键或直接打字(列表、大纲、搜索结果这些点击本来就把焦点留在了按钮上)。

数据存放位置

| 内容 | 位置 | |---|---| | 导入的字体文件 | $DSH_HOME/dsh-ide/fonts/<文件名> | | 编辑器偏好:字体、字号、左侧 blame 列与内嵌 Git 开关、快捷键表,以及「亲手改过哪几个键」的记录 | $DSH_HOME/dsh-ide/prefs.json |

$DSH_HOME 没有设置时回退到 ~/.dsh。

这些都在宿主侧,既不属于某个浏览器,也不属于某个工作区,所以刷新页面、换一个浏览器、换一个工作区之后,设置和导入过的字体都还在(导入的字体在每次页面加载时由宿主重新注册回页面)。偏好文件以「写临时文件再改名」的方式更新,避免崩溃时留下半截文件被读成「没有设置」。早期版本存在浏览器 localStorage 里的字体选择,会在宿主还是空的时候被迁移一次。


已知限制

  • 没有语言服务器:「跳转到定义」和「大纲」都是文本/正则级的,宏、动态导入、别名等复杂情况会找不到或落到不精确的行。
  • 导入检查不解析别名:tsconfig 的路径别名、包的 exports/imports 映射都不求值,注释和字符串里的 from '...' 也不会剔除;别名与工作区包只报为提示,不报为错误。Python 的绝对导入被有意跳过(目标可能在 site-packages),只检查相对导入。
  • ruff 是可选的,但项目自带的就够:查找顺序是 <工作区>/.venv、venv、env(Windows 上是 Scripts\ruff.exe/ruff.cmd,.cmd/.bat 会经 cmd.exe 执行),最后才是 PATH 上的 ruff。所以只要你的项目里装了 ruff(uv / poetry / pdm / pip 建的 venv 都算),Python 文件就是全量 ruff 规则检查,不需要全局安装;前三者都找不到时才退回「仅导入检查」,状态条会写出来。跑 ruff 时 cwd 是会话工作区根,所以项目自己的 pyproject.toml / ruff.toml 配置同样生效。TS/JS 目前没有类似的外部检查器(只有导入解析),这一项是后续可以补的。
  • 文件在「嵌套仓库 / worktree」里也认得出:每条按文件的 git 命令(blame、历史、提交差异、工作区变动)都在该文件所在目录运行,而不是会话根。工作区里可能有被外层仓库 .gitignore 掉的嵌套仓库或 git worktree(本机就是 worktrees/ref-tape),从会话根看这种路径对 git 是不存在的——那会让「改动」看起来是干净的、blame 什么都答不出来。
  • 工作区查找的边界:优先 git grep(仓库里快、且尊重 .gitignore),非仓库退回有上限的目录遍历(800 文件 / 单文件 512 KB / 2 秒),此时读取任何扩展名的文本文件、以「含 NUL 字节即视为二进制」跳过;结果上限 200 处、查询最长 200 字符。正则模式在仓库里走的是 git grep -E(POSIX ERE),与 JavaScript 正则语法不完全一致(例如 \d),所以某些模式在仓库与非仓库下的行集可能略有差别;每行的列号与出现次数始终由 JavaScript 匹配器计算,显示是一致的。替换一次最多 50 个文件 / 2000 处 / 替换文本 1000 字符。
  • 有大小上限:单次请求体上限 32 MiB,更大的文件保存会失败(客户端在上传前就用同一个上限拒绝,并直接说出文件大小,不会先传几十兆再报错);大于 256 KiB 的文件只扫描前 256 KiB 的导入,说明符最多 200 条,超限时状态条显示「检查不完整」。
  • md / markdown / mdx 和 .env 不由本插件接管(原因见上),因此它们没有 blame 和导入检查。
  • 主题与语法配色是尽力映射 DSH 的 CSS 令牌,令牌缺失时回退到内置深色配色;界面文案目前是硬编码中文。

完整版(每一项的根因、实测数字、被推翻的早期结论,以及复现命令)见 docs/审查与修复记录.md。


开发

npm run build      # 构建:宿主 tsc + 客户端类型检查 + esbuild 打包 lib/client.js
npm run verify     # 类型检查 + 八套检查:
                   #   verify-host(宿主行为:认证围栏、分发、工作区限定、读写、偏好、冷会话)
                   #   verify-imports(导入解析,真实临时目录树)
                   #   verify-outline(大纲树、地址解析、字体、快捷键语法)
                   #   verify-nav(返回/前进历史:同文件跳转也是条目、跳转后前进仍在、离开时记的是当前光标行)
                   #   verify-git(真实仓库上的 log/show 解析)
                   #   verify-bundle(客户端 bundle 契约:注册 id、工厂、导出面)
                   #   verify-apply(挂载测试:真的跑 apply(),覆盖模式 A / 模式 B 的注册与渲染)
                   #   verify-fresh(产物新鲜度:重建并与 lib/ 逐字节比对)
npm run selftest   # 宿主解析器夹具(blame / log / ruff 输出形态与归桶边界);
                   # 在 git 仓库目录内运行会追加真实 git 检查,否则只跑夹具

源码位置:宿主入口是 src/index.ts,宿主实现都在 src/host/**(路由、git、导入解析、ruff、字体、偏好);浏览器半片在 src/client/**(编辑器、大纲、快捷键、偏好、传输层)。lib/ 是构建产物,profile 实际加载的就是它——改完源码必须重新构建,否则页面跑的还是旧产物。

更完整的实现说明、集成模式与三轮审查记录见 docs/审查与修复记录.md。