dsh-sidebar-editor

DSH PLUGINUI 与外观

仓库 godlike985/dsh-sidebar-editor

UI 与外观 未标注验证等级 安全审查:未标注 可信度:未标注 可安装插件

在 DSH 右侧栏彩色预览工作区代码文件、按名字搜索文件,并把选中行以 @路径 L10-L20 形式一键引用进对话框。

一句话选型结论

按资料字段整理

dsh-sidebar-editor 是UI 与外观类的 DSH 可安装插件,未做沙盒验证,暂无安全审查结论,列出 5 项功能,没有现成安装命令,需要按仓库 README 接入。验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么;涉及密钥或系统权限的,建议先在隔离环境试装。

分类UI 与外观 收录类型可安装插件 验证等级未标注验证等级 安全审查未标注
验证等级 UNKNOWN 未标注验证等级
GitHub 星标 1 复刻 0 · 未关闭 issue 0
最近更新 2026-10-10 创建于 2026-10-10
README 36.4 KB 正文体量,越大说明说明书写得越细

安装

可安装插件
在 DSH 里执行,装完用 dsh plugin list 确认已挂上;标了 L4 / L5 的命令已在沙盒里真实跑通
  1. 1
    确认版本与运行环境

    仓库没有标注兼容版本与 Node.js 要求,先确认你的 DSH 版本能装,再继续。

  2. 2
    执行安装命令

    在 DSH 里执行上面的命令;多包仓库要注意区分具体子包名,别装错包。

  3. 3
    确认已挂载

    用 dsh plugin list 查看列表里是否出现这个插件,出现即挂载成功。

  4. 4
    试跑并检查权限

    加载后跑一个最小用例,确认它能正常工作;同时看它申请了哪些文件、命令或网络权限。

兼容 DSH 版本未标注版本不匹配时安装会报错
Node.js 要求未标注来自仓库 package.json 的 engines 字段
前置依赖无需要同时安装的同级依赖
npm 包名未发布到 npm可用 npm view 查看包信息

能做什么

5 项功能
字段来自项目仓库与公开索引,中文说明由本站标注

dsh-sidebar-editor:DeepSeek Harness 侧栏里的代码预览与引用面板

  • 侧栏彩色代码预览与行选
  • 选中行一键引用进对话框
  • 文件树右键引用文件或目录
  • 按名字搜索并定位工作区文件
  • 文件内查找与字号调节

上游标签

dsh-plugin

验证等级与安全审查

未标注验证等级
自动化流程结论,不是人工审核
验证等级未标注验证等级上游未给出验证结论,安装前请自行确认
安全审查未标注静态分析结论,未标注不代表已通过审查
仓库健康度未标注按仓库活跃度与最近更新时间给出
可信度未标注综合仓库活跃度与验证结果

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 列出了 5 项功能,能直接看出它解决什么问题。

先想清楚的地方

  • 安全审查还没结论,装之前先看仓库改了什么、要什么权限。
  • 没有现成安装命令,要按仓库 README 手动接入。
  • 验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么。

更适合

  • 需要在UI 与外观场景里给 DSH 补能力的人。
  • 能接受在本地装依赖、愿意自己确认权限的人。

不太适合

  • 只使用 DSH 官方内置能力、不接受第三方插件的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
插件验证等级安全审查星标兼容版本安装命令
dsh-sidebar-editor当前 未做沙盒验证 未标注 1 未标注 无
awesome-dsh-plugin L4 · 沙盒安装通过 低风险 1.8 万 0.1.1-rc.2 有
deepseek-balance-whale-widget L4 · 沙盒安装通过 低风险 4,492 0.1.7-rc.2 等 2 个 有
dsh-better-sidebar L4 · 沙盒安装通过 低风险 4,112 0.1.7-rc.2 等 10 个 有
dsh-deep-whale L4 · 沙盒安装通过 低风险 2,470 未标注 有
deepseek-design L4 · 沙盒安装通过 低风险 1,438 未标注 有

仓库档案

UI 与外观
字段来自项目仓库与公开索引,中文说明由本站标注
作者 / 维护方godlike985仓库 dsh-sidebar-editor · 默认分支 main
分类UI 与外观interface
收录类型可安装插件声明了 dsh.bundle、可用 dsh plugin add 安装的条目
开发语言JavaScript仓库主语言
许可证MIT商用或二次分发前请确认条款原文
星标 / 复刻 / issue1 / 0 / 0收录时的数值,非实时
创建 / 更新2026-10-10 · 2026-10-10最后推送 2026-10-10
本站收录2026-10-10首次进入本站目录的时间

README 全文

36.4 KB
作者原文,中文版优先;只做安全转义,不改内容

侧栏代码引用搜索工具(dsh-sidebar-editor)

在 DSH 右侧栏的文档面板里彩色预览工作区代码文件,并把选中的行一键引用到对话框。
可编辑与保存的能力仍在 bundle 里,但默认关闭(原因见下方「面板的形态:预览优先」)。

这是一个 DSH 插件 bundle(dsh-sidebar-editor),免构建:客户端半边是纯 classic script,宿主半边只注册一条路由。

状态

预览、分类着色、行选、引用、右键菜单(含剪切/复制/粘贴/全选)、大文件降级、以及宿主保存路由都已实现;
其中编辑器与保存默认不启用。离线自检 node E:\DSH\_tools\selfcheck-sidebar-editor.js 覆盖 300 项断言(客户端)+ 29 项断言(宿主路由),
包含后缀覆盖契约、hook 顺序纪律、着色分类与注入防护、右键菜单、标准编辑项、三种引用形态、文件树行右键、拖选成范围、搜索面板、tab 条滚轮与自动揭露、
「出厂默认形态」、大文件降级边界、宿主路由的完整信封与全部失败码映射。

它做了什么

  • 注册文档预览实现 dsh-sidebar-editor/code,属于 extension 档。后缀的认领规则是:
    内置「代码」渲染器认领的每一个后缀,我全都认领,只排除被其他渲染器专门接管的那几个。
    因为 extension 档排在 builtin 之前,这些文件打开时默认就是编辑器;
    而内置「代码」渲染器仍然匹配同一个后缀,所以渲染器下拉里始终有它,随时可切回去看 Shiki 高亮。
    这条规则同时保证了两件事:任何原本以只读代码视图打开的文件都不会"没有编辑器",
    且任何被我接管的文件都"有路可回"。
  • 唯一的例外是刻意不认领其他内置渲染器已占用的后缀:md/markdown(Markdown 预览)、
    html/htm(沙箱 HTML 预览)、svg 与位图(图片)、pdf、xlsx/xls/csv/tsv(表格)、
    Office 文档 —— 认领它们会把这些文件的默认预览从用户手里抢走。
  • 另外额外认领了十几个内置表里没有的文本后缀(txt、astro、json5、sass、.gitignore、
    .editorconfig、.npmrc、Cargo.lock …)。这些文件我是唯一候选,所以没有渲染器下拉,
    只能靠面板里的「只读」开关切换视图。自检里有一条断言把"超出内置表的后缀"限制在 20 个以内。
  • 宿主半边注册一条精确 Fetch 路由 POST /api/sidebarEditor/save。
  • 面板里没有任何 DSH 客户端包的 import:编辑器、行号槽、轻量高亮、地址解析全部自带,
    颜色只用 --dsw-alias-* 与 --shiki-token-* 主题 token。

面板能做什么

能力 说明
预览 自带分类着色(注释 / 字符串 / 数字与字面量 / 关键字 / 函数名与定义 / 装饰器与类名 / 属性 / 标点 / markup 标签与属性),按行渲染,可点选行
选行 点一行选中、Shift+点击 扩选,鼠标拖选任意多行也直接成为引用范围;已选 L5-L9 实时显示
右键 面板内的右键菜单是插件自绘的,除三种引用外还带 剪切 / 复制 / 粘贴 / 全选(预览形态只保留引用与「复制」);右键某行会先把该行选为范围
引用选中行 把 @路径 L10-L20 插入到对话输入框
引用整个文件 工具条按钮与菜单项,插 @路径(不带行号)
引用所在目录 菜单项,插 @目录/(带尾斜杠);文件就在工作区根目录时该项不出现
文件树右键 直接在最右侧文件树的行上右键 → 「引用整个文件」/「引用这个目录」,外加「复制绝对路径」。不要求先打开面板
文件面板搜索框 文件树的工具栏里(路径栏那一排的最左)多一个搜索框:输入即按名字匹配工作区文件(文件与文件夹都匹配),结果浮层列出「名字 + 所在目录」。点文件结果 → 先在文件树里展开到它、滚动进视野、高亮那一行,然后才打开该文件;点文件夹结果 → 不打开任何东西,只在树里展开定位并高亮(已经展开的不再收起)。Esc 或点外面收起
文件内查找 打开文件后,工具条上的「查找」按钮(面板有焦点时 Ctrl+F):在当前文件内按行匹配、命中行加底色、1/N 行 计数、↑↓/Enter 逐个跳(到头回绕),跳到的行同时成为选中行,可直接引用
搜索快捷键 Ctrl/Cmd+Shift+P 打开「文件」tab 并聚焦搜索框(命令 sidebarEditor.filesearch)。键位在设置 → 快捷键(或 Mod+/)里改、清除、恢复默认,插件不持有键位副本
条带滚动 右侧边栏 tab 开多了时:滚轮停在 tab 条上即左右滚动;新激活的 tab 会自动滚进视野(原来它只会停在视野外)
大文件 超过 4000 行时关闭着色只出纯文本,并在工具条上说明;行仍然可选可引用
字号 工具条右侧的 A− / A+ 步进器调代码字号,10–22px,默认 13px,选择会被记住
编辑(默认关闭) `` + 行号槽,关闭软换行(保证行号与内容严格对齐),支持横向滚动
保存(随编辑,默认关闭) 把缓冲区写回磁盘,带版本守卫;冲突时挂横幅提供「重新载入」与「强制覆盖」
守卫(随编辑,默认关闭) 文件未完全载入或超过 512 KiB / 20000 行时自动转只读,并说明原因

面板的形态:预览优先

默认形态是「预览 + 引用」,编辑与保存藏在 bundle 里由开关控制(client.js 里的 editingEnabled())。
理由有两条,第一条是实测的:

  • **受控 ` 会毁掉浏览器自己的撤销栈。** 这个面板每次输入都把value写回控件 (React 受控输入),而程序化写value` 会清空原生 undo 历史 —— 于是 Ctrl+Z 什么都不做。
    要修就得自己维护快照撤销栈(编辑/粘贴/剪切都要接入,并处理合并与光标位置),
    等于在侧栏里重造半个编辑器。
  • 侧栏本来就不是写代码的地方。 它窄、没有项目级跳转与索引,真正的编辑应该发生在 IDE 里;
    在侧栏里"能改"反而制造了"改完要记得保存、还要处理冲突"的负担。

想把它变回可编辑面板,只需让 editingEnabled() 返回 true:编辑器、保存路由、冲突横幅、
版本守卫全都还在,自检里也一直有覆盖(测试通过一个宿主侧全局量切换两种形态,两种都跑)。

面板右上方仍然是「引用选中行」按钮;保存 与 编辑/只读 开关在默认形态下不出现。

文件树右键是怎么做到的(没有行级席位)

文件树没有给插件留行级席位,也没有自己的右键菜单(它的文档写着"只有列目录……没有搜索、
拖拽、重命名、右键菜单"),所以"在树上右键引用"看起来是做不到的。但它暴露了两样东西:

// 每个行元素上
// 滚动容器上

于是插件可以只读地使用这个契约:在 document 上监听 contextmenu,用
event.target.closest('[data-files-entry]') 认出是哪一行,取 data-files-path 减去
data-files-root 得到工作区相对路径 —— 全程不写树自己的 DOM,也不依赖它的内部组件。

菜单由注册进 conversation.composer.dock 的伴生组件渲染({kind:"list", scope:"session"},
所以 inputActions 会照常送达),并用 ReactDOM.createPortal 挂到 document.body,
以免 composer 的某个祖先把它裁掉或改变定位基准。面板与它共用同一份样式表 ——
样式表因此改成插件级注入(document.head),不再由面板组件自己渲染,
否则"没打开任何面板就右键文件树"时菜单会没有样式。

代价说清楚:在这棵树上右键会顶掉浏览器的原生菜单(不然两个菜单叠在一起),
所以插件补了「复制绝对路径」(按根自己的分隔符拼:Windows 给 E:\DSH\src\app.ts,POSIX 保持正斜杠)。这与面板内的取舍一致 —— 压掉什么就补上什么。

搜索:为什么最后没有用官方的 fileReferences

第一版搜索走的是输入框 @ 补全用的那条接缝(remote.fileReferences.list),在你这台机器上
什么都搜不到。原因写在官方包里:那条 seam 本身不拥有文件系统访问,没有挂载提供方时
UI 只能得到空的列表
;浏览器侧还要经 Session Controller 适配器先解析 Agent(第一参数的
语义是 agent 的工作目录)。它返回的空列表和"没有匹配"长得一模一样,所以第一版看起来只是"没结果"。

于是搜索改成由本插件自己的宿主半边遍历工作区:

  • fs.listDir 逐层走(这条 seam 明确不提供递归/glob),始终在沙箱围栏给出的 workspaceRoot
    之内,不读文件内容;
  • 固定上限:跳过 node_modules/.git/dist/build/.venv/__pycache__ 等依赖与构建目录,
    深度 ≤ 12、文件 ≤ 20000、单文件内容 ≤ 512 KiB、每文件 ≤ 3 处、总数 ≤ 200;
  • 内容搜索用 fs.readText 过滤 —— 它对二进制、超限、不可读文件返回 null 而不是抛错,
    正好就是这里需要的过滤器;
  • 列表按 root 缓存 15 秒,面板重开是瞬时的。

代价要写清楚:这条遍历不认识 .gitignore(用固定跳过表代替),也不做模糊评分
(只在客户端按"命中文件名优先于命中父目录"排序)。若以后要忽略规则感知,正路是让
fileReferences 的提供方在本机生效,再把面板切回去。

定位为什么要排在打开之前

右侧边栏是单列的:ctx.sidebarRight.openResource() 会把资源的文档 tab 揭到前面
(placeResource 的注释原话是 "claim it, place it, reveal the column"),「文件」tab 随之退到后台、
本插件的搜索框组件被卸载。第一版把"打开"放在"定位"之前,症状是实测出来的:

  • 树在没人看的时候被展开,你切回「文件」tab 只看到一棵已经展开的树,不知道它为什么展开;
  • 提示语浮层挂在搜索框组件上,组件一卸载就没了 —— 所以连"哪一步失败了"都看不到。

打开也因此不能和写提示语挤在同一个 tick:setNote 之后立刻 openResource,React 还没绘制,
组件就被卸载了。所以文件打开要延后 NOTE_READ_MS(900ms),让你先读到结果。

根那一级没有行(定位曾经 100% 失败的原因)

revealInFileTree 一开始沿祖先往下走,第一件事是找根那一行:

let current = base;                                  // 'E:\DSH'
const parent = treeRowOf(root, current);             // 真实树里 → undefined
if (parent === undefined) return { ok:false, reason:'no-row:' + current };

但真实 DOM 里根不是一行(dsh-client-ui-sidebar-files 的 FilesBody):

// ← 没有 data-files-path
// 标题标签,值是字符串 "true"
{renderSlot("sidebar.right.tab.files.actions")}

所以每一级都还没开始就返回 no-row:E:\DSH —— 定位从来没有成功过,跟路径、文件类型都无关。
根那一级的子项在面板挂载时就列出来了,也没有 toggle 可按,所以现在第一段直接
waitForTreeRow 等它出现,不再经过祖先行。

这个 bug 在自检里躲了很久,因为假树把根也当成了一行(rendered 里含 'E:\\DSH'),
和真实 DOM 不一致 —— 就是那段 KNOWN RED … fake tree has not yet been made to agree with the walk
注释在说的事。现在假树的 reset() / expandAll() 都不再渲染根那一行。验证方式:把上面那段分支
删掉再跑自检,8 条断言会失败,其中一条正是
no-row:E:\DSH(DSH_SELFTEST_BUNDLE 可以把自检指向改坏的副本,见"离线自检")。

高亮还要扛住重挂:data-se-tree-hit 是打在别的包拥有的树行上的属性,而那棵树切换 tab 回来时
会把行重新渲染一遍,属性不继承。所以落点路径由 rememberLocatedRow() 记住,再用一个
MutationObserver(只看 childList,不看属性,避免自己写的属性把观察器喂回自己)在那一行重新
出现时把高亮补回去。展开状态本来就存在树自己的 tab store 里,因此补高亮不需要重走一遍。

入口、模式与快捷键

搜索只有一个入口:文件面板工具栏里那个搜索框,挂在官方留的
sidebar.right.tab.files.actions 子席位(路径栏那一排)。它用 order:-1 排在那一排的最左
(Woyo-G_header 是 display:flex,路径栏靠 flex:1 把工具推到右边),结果用 portal 浮层列出,
只按文件名匹配。

> 已删除:搜索页 tab 与内容模式。 早期版本另有一个占满整格的「搜索页」页类型 tab
> (sidebarRightTabs.register + guide 入口)和「文件名 / 内容」两种模式。产物里已经没有它们,
> 自检那条 'the page-type tab stays gone' 断言就是钉这件事的。宿主的
> /api/sidebarEditor/search 仍然实现 mode:'content'(index.js 的 searchWorkspaceContent),
> 但客户端没有任何一处调用它 —— 想恢复内容搜索,缺的是 UI,不是路由。

快捷键由平台管,不由本插件管。 命令 sidebarEditor.filesearch 注册进 DSH 自己的
shortcuts 服务,带一套默认键:桌面 Ctrl/Cmd+Shift+P,Web 再加 Alt(浏览器把 Ctrl+P
留给打印)。运行时不冲突 —— 只有官方文件树用 KeyP(Mod+P 与 Web 的 Mod+Alt+P)。

  • 按下去先打开/聚焦「文件」tab(sidebarRight.openTabFromTarget('files', target)),再轮询等
    搜索框挂载后 focus() + select(),所以是"一键开始输入",不是"打开一个面板";
  • 会话没解析出来时命令不执行,原因交给快捷键面板显示(search.commandNoSession);
  • 改键 / 清除 / 恢复默认都在「设置 → 快捷键」或 Mod+/:Web 写 dsh.keybindings.v1,
    桌面写 userData/keybindings.json。插件不保存任何键位副本,所以不会出现"设置改了但插件还用
    旧键"这种分叉;冲突的组合由那个面板直接拒绝。
  • 因此 dsh.client.inject 里多了一个 @deepseek-ai/dsh-client-shortcuts,ctx.inject 也多了
    一组 ['shortcuts', 'sidebarRight'] —— 服务必须点名才拿得到,见下面踩坑一。

正文的 props 里没有 inputActions 就不显示「引用」按钮 —— 那个按钮只有在会话级属性真的送到
这个席位时才有意义,所以是运行时探测而不是硬依赖。

踩过的坑一:服务必须写进 inject。 第一版把这个注册块放在
ctx.inject(['remote.fileReferences', 'sidebarRight'], …) 里,却在回调中调用
scope.sidebarRightTabs.register —— Cordis 按 inject 门控服务,没列出的服务在回调里是
undefined,于是 .register 抛 TypeError,tab 类型根本没注册,而插件其余部分照常工作,
界面上只表现为"搜索框不存在"。harness 当时无条件暴露所有服务,所以没拦住;现在它会按 inject
门控(点号依赖放行其命名空间,例如 remote.fileReferences 放行 remote),把 inject 改回
出错的版本就能立刻复现那句 TypeError。

踩过的坑二:同一个 bundle 里只能有一个 fetch 桩。 加了搜索路由之后,harness 里原本给
保存路由用的 fetch 桩写在我新桩的后面,把我的覆盖掉了 —— 表现是"面板状态正常但请求数为 0"。
两个桩现在合成一个,按 URL 分流。

让 tab 条能被滚轮左右滚

右侧边栏的 tab 条本来就可以横向滚动(overflow-x:auto),但:

  • 滚动条是故意藏起来的(scrollbar-width:none 加一条 ::-webkit-scrollbar{display:none});
  • 这个构建里没有任何 JS 管理它的滚动 —— 我把整份归档(8276 个文本条目)扫了一遍,
    data-dockkit-strip-scroll 只出现在 CSS 里、JS 里一次都没有,那套渐隐提示其实是死代码;
  • 浏览器只在按住 Shift 滚轮或触控板横向滑动时才把纵向滚动映射成横向。

于是鼠标用户实际上够不到跑出视野的 tab,更糟的是新开的 tab 没人帮它滚进视野(scrollLeft /
scrollIntoView 在归档里与条带无关)。

没有加可见滚动条:条带容器只有 28px 高(.tabStrip{height:28px}),
scrollbar-width:thin 会吃掉约 8px 并把 tab 胶囊裁掉 —— 所以改用滚轮映射:

  • 原生 wheel 监听装在 document 的捕获阶段且 passive:false。必须原生:React 的
    onWheel 是被动的,写成 prop 的话 preventDefault() 不生效,边栏会跟着一起纵向滚。
  • 命中判定用 [data-dockkit-strip-tabs](条带滚动容器的稳定属性,取自 shell 的 JSX),
    不看哈希类名。
  • 横向滚轮 / Ctrl+滚轮(缩放) / 已经滚到头 / 没有溢出 —— 这四种一律把事件交还给页面,
    不抢别人的滚动。
  • 另外用 MutationObserver(attributeFilter:['aria-selected'])盯住激活变化,
    让当前 tab 以 inline:"nearest", block:"nearest" 滚进视野 —— 只横向动,不牵动纵向。

拖选多行也会成为引用范围

只读视图里最自然的手势是用鼠标拖选若干行,但那产生的是浏览器自己的 DOM 选区,
不是点击 —— 最初只实现了"点一行 / Shift+点击扩选",拖完之后 已选 还停在上一次点击的那一行,
引用出来就是 L4 而不是 L2-L4。修法是把 DOM 选区读回来:

  • 每一行带 data-se-line(这个属性只属于本面板,所以别处的选区不会被误认);
  • 鼠标抬起(mouseup)或键盘选区结束(keyup)时,用选区的起止节点映射成行号区间,取小者为 start;
  • 点击会紧跟在拖选之后到达,所以点击处理里先问一次选区:多行选区优先,
    否则才按点击的那一行选 —— 这条次序正是"范围被缩成一行"的根源。

为什么要自己做一个字号步进器

平台有字号设置(设置 →「通用」→ 正文字号,10–22 px,默认 14 px),
但 dsh-client-ui-theme 的文档写明它只调整正文与低一档的流内文本,
「小号文本和代码保持固定字号」 —— 所以调那个设置,面板里的代码不会变。代码要变大变小只能在面板里调。

实现要点:字号与行高是一对 CSS 变量(--dsh-se-size / --dsh-se-line),
行号槽、编辑区、只读行三者共用同一个行高,否则数字会和文字错位;
行高由字号按固定比例算出(13px → 21px)。选择存在 localStorage(键
dsh-sidebar-editor.code-font-size),在禁止存储的文档里会退化为"本次挂载内有效"而不是报错。
自检里有一条断言专门盯这个耦合:把行高钉死成常量后,它会立刻失败。

关于文件树搜索(本插件不做,但值得记下来)

右侧文件树故意没有搜索,这不是缺失而是设计:dsh-client-ui-sidebar-files 的文档直说
「只有列目录。没有搜索、产物过滤、拖拽、重命名、右键菜单或当前文件高亮。」插件库里也没有任何插件补上它。

平台真正提供"按名字找文件"的地方是输入框:敲 @ 会打开文件引用补全菜单,
支持边打边过滤、键盘选择、逐级下钻(dsh-client-ui-input-trigger),选中即插入引用。
本插件的「引用选中行」走的是同一条插入路径,所以两者可以混用。

如果确实想要一个可搜索的文件面板,技术上可行:插件可以注册自己的右侧 tab 类型与正文
(ctx.sidebarRightTabs.register({ id, kind, … }) + ctx.slots.register({ name: 'sidebar.right.pane.tab', key: id })),
随包发布的引导页走的正是这条公开路径。那是一个新功能(需要自己的文件列举与过滤),本插件目前不做。

引用是怎么实现的(重要)

DSH 的 @ 引用语法没有行号:只有 @path / @"path with spaces" / @dir/,
而且引用只是提示词文本,发送时不展开、也不附带文件内容。

所以本插件插入的是一个纯文本引用 token:@src/app.ts L10-L20。

三种引用对应语法的三种形态:

动作 插入的文本 语法形态
引用选中行 @src/app.ts L10-L20 @path + 纯文本行号
引用整个文件 @src/app.ts @path
引用所在目录 @src/ @dir/(尾斜杠是语法的一部分)

含空格的路径会加引号,目录的尾斜杠放在引号里面(@"my dir/deep/")——
这一点是本插件自己推的(目录行的序列化形式只规定"带尾斜杠的 @dir/ mention"),
如果哪天发现 DSH 实际要求 @"my dir/deep"/,改一行即可。

  • @src/app.ts 会被输入框重新扫描成 DSH 原生的引用 token(有 hover / 点击预览),
    紧跟在后面的 L10-L20 是普通文本;模型能直接读懂这一行。
  • 没有走"引用 chip"那条路,这是刻意的:发送时每个 chip 的文本会用它自己的 ref
    经 source codec 重新序列化(serialize: ref => ref),挂在 chip 上的行号永远到不了模型。
    证据:dsh-client-ui-conversation 的 sinkSerialized 用
    inputTriggers.serializeReference(o.source, o.ref) 重建文本。

插入走的是会话作用域插槽的文档化标准 prop inputActions:
captureInsertion() 取当前光标 span → insertText(text, span) 插入。
该 prop 由 ctx.uiSession.provide({ props: ['inputActions'] }) 物化进每个 session 作用域插槽
(不是某个 inject face),与 conversation.composer.bar 拿到的是同一个来源。

如果该 prop 不存在,退化路径只在草稿为空时通过常驻输入机
conversation.input.shell(sessionId).setDraft() 追加;草稿非空时拒绝执行并报错,
避免把已有的 chip 冲掉。

保存是怎么实现的

浏览器                                    宿主
fetch('api/sidebarEditor/save')  ──►  POST /api/sidebarEditor/save
{ absolutePath, text, version }        connection.admit(request)   ← Host/Origin 围栏
+ 浏览器会话签名 cookie      ← 未认证 401 / 未信任 403
ctx.fs.resolve(absolutePath)
ctx.fs.writeText(target, text,
{ kind: 'replaceIfVersion', version })
← FS_SANDBOX_DENIED 403 / FS_STALE_VERSION 409

要点:

  • 路由在 /api 前缀下,所以自动被 connection.admit() 的围栏与浏览器会话 cookie 认证包住,
    和官方包 dsh-client-ui-deliverables 的 /api/changes.diff 走同一条路。
  • 浏览器侧 URL 必须写成去掉前导斜杠的相对形式(api/...):桌面端用 file:// 加载壳、
    经 IPC 桥接 fetch,带前导斜杠会指到文件系统根。官方包的客户端半边正是这么转换的。
  • 写入必须带上会话身份,否则一定被拒。这是实测踩到的坑:沙箱的 per-call 策略是
    ctx.sandboxPolicy.resolve(),不带会话时工作区根回落到部署默认的 process.cwd()
    ——也就是宿主进程的启动目录,而不是 E:\DSH。于是哪怕文件就在会话工作区里,
    写入也会因为"不在此根之下"被判 FS_SANDBOX_DENIED。
    修法是客户端把 sessionId(从资源地址里解析出来的)一起发上来,宿主用
    ctx.sessions.get(sessionId).header.cwd + ctx.sandboxPolicy.resolve({ session })
    得到该会话的模式与工作区根,再作为 per-call 策略传给 writeText。
    这条链路与官方 dsh-api-workspace-files 的 workspaceFileScope 解析器完全一致。
    副作用是正确的:会话被切成只读模式时会返回独立的 read-only-mode,而不是笼统的沙箱拒绝。
  • 版本守卫是双保险:replaceIfVersion 既满足"覆盖前必须先读过文件"的观察前置条件
    (否则 FS_NOT_OBSERVED),又提供乐观并发——文件在读取之后被改过就抛 FS_STALE_VERSION。
  • 沙箱兜底:workspace-write 下只有该会话工作区根(与临时目录)之内能写,
    越界是直接失败并回传实际使用的可写根,而不是弹窗要权限。
  • 不会被自己的读回滚:保存成功后,如果共享读取器还在送保存前的那一份内容,
    adopt 逻辑靠"我们刚覆盖掉的那段旧文本"把它识别出来并跳过,避免刚保存的内容在缓冲区里被回滚。
    磁盘版本变化有三种表现:缓冲区干净 → 静默跟随;缓冲区有未保存改动 → 冲突横幅(重新载入 / 强制覆盖);
    两者都不满足时(读取器还没追上我们的写入)→ 什么都不做。

保存失败时横幅在说什么

宿主返回的是稳定错误码,客户端只按码分支,所以每条文案都对应一个确定的原因:

横幅 含义 该往哪查
该路径不在可写工作区内…当前可写根:X 围栏用的根是 X,目标不在其下 看 X 是不是本会话工作区
…(未能解析会话,按部署默认根判定) sessions.get(sessionId) 没拿到会话,只能回落到部署默认根 检查客户端发上来的 sessionId 与宿主会话 id 是否一致
当前会话处于只读模式 会话被切成只读,写入被有意拒绝 切回可写模式
宿主保存路由未注册(HTTP 404) 宿主半边没挂上,路由根本不存在 重启应用让宿主模块重新加载
文件在磁盘上已被改动 乐观并发拦下了这次覆盖 「重新载入」或「强制覆盖」
覆盖前必须先读取该文件 缺少读取前置条件(FS_NOT_OBSERVED) 先载入完整文件再保存

宿主半边把操作实现为一个函数 saveWorkspaceFile(ctx, input, signal),路由调用它;
将来要加 agent 工具时调用同一个函数即可(符合插件规范里"一个操作、两个调用者")。

安装

第一次安装需要在 GUI 里操作一次(插件安装要跑 pnpm 并改 profile 清单):

  1. 打开发送侧边栏的插件页。
  2. 点「添加插件」,填入这个绝对路径:
E:\DSH\plugins\dsh-sidebar-editor
  1. 点安装。装完 bundle 的客户端半边会立即生效(无需重启,实测如此)。
  2. 改了宿主半边(index.js)之后需要重启一次 DSH —— 宿主模块不进客户端 HMR,
    详见下面「实测过的热更边界」。只改 client.js 不必重启,也不必重新安装。
  3. 装好后可在设置 → 内置插件 → 插件列表里看到 sidebar-editor 行。

验收清单

  1. 用右侧栏文件树打开 E:\DSH 里任意 .ts / .py / .json 文件
    → 顶部出现「编辑 / 只读」「保存」「引用选中行」,渲染器下拉里同时有「编辑器」和「代码」。
  2. 选中第 10–20 行 → 点「引用选中行」→ 输入框出现 @文件 L10-L20,@文件 可点击预览。
  3. 在编辑器里右键 → 弹出插件自绘的菜单 → 点「引用选中行」效果与工具条按钮一致。
  4. 切到「只读」→ 观察分类着色(关键字 / 字符串 / 数字 / 函数名 / 装饰器 / 类名 / 标点各自成色);
    点第 5 行、Shift+点击第 9 行 → 显示 已选 L5-L9;在某一行上右键可直接设为引用范围。
  5. 改一行内容 → 「保存」变为可用 → 点保存 → 文件在磁盘上真的变了,横幅显示「已保存」。
    保存被拒时看横幅里的可写根:写往会话工作区根之外是设计如此,不是 bug。
  6. 保存后让 agent 改同一个文件,再回到面板改一行 → 出现冲突横幅
    → 「重新载入」取磁盘版本,「强制覆盖」把自己的版本写上去。
  7. 打开 .md 或 .png → 仍是内置 Markdown / 图片预览(没有被抢走)。
  8. 把会话权限切成只读,再改一行保存 → 横幅应显示「当前会话处于只读模式」,而不是笼统的沙箱拒绝。
  9. 打开右侧「文件」面板 → 搜索框在工具栏最左(路径栏 E:\DSH 之前)→ 输入某文件名 → 点结果
    → 先看到文件树展开到那一行并高亮(提示语「已打开并在文件树中定位到 …」,停留约 0.9s),
    紧接着文件被打开、右栏切到该文档;再切回「文件」tab,那一行仍然是高亮的(属性扛住了重挂)。
    输入某文件夹名 → 点结果 → 不打开任何文件,右栏留在「文件」tab,树展开定位并高亮,
    提示语是「已在文件树中定位到 …」。
    把「文件」面板关掉(或树不在屏幕上)再点一次文件结果 → 文件立刻打开,
    提示语变成「没有打开文件树:请在右侧打开「文件」面板后再试」。
  10. 按 Ctrl+Shift+P → 右栏切到「文件」tab 且搜索框获得焦点(已有内容被全选,可直接覆写);
    打开设置 → 快捷键(或 Mod+/)→ 搜 filesearch 或「搜索工作区文件」→ 改成一个别的组合
    → 新组合立刻生效、旧组合失效;再「恢复默认」应回到 Ctrl+Shift+P。会话未选中时按这个键
    → 命令不执行,快捷键面板给出「请先选择会话」。

二次开发(实测过的热更边界)

  • 客户端半边(client.js)改完即生效:不需要重启,也不需要重新安装。
    dsh-client-hmr 的 node 半边按 500 ms 轮询 bundle 文件,改动会推一个新的 rev,
    浏览器随之取到新代码。实测:插件装进 profile 后,在应用未重启的情况下渲染器就已经接管了。
  • 宿主半边(index.js)改动需要重启应用。dsh-hmr 那一行的配置是 root: [],
    其注释写明「Profile configuration reloads by default; module roots are opt-in」——
    也就是说它监听 profile 配置,但不监听插件模块文件,所以改 index.js 不会重建宿主模块。
    (想让它热更,可以把插件目录加进 profile 里 hmr.root;那属于你自己的配置层,插件本身没改。)
  • 离线自检:
node E:\DSH\_tools\selfcheck-sidebar-editor.js

它用 React 桩把 client.js 真正加载、apply()、挂载并渲染组件,并且用假的 ctx.fs /
ctx.sessions / ctx.sandboxPolicy 跑通宿主半边的路由信封(会话围栏、只读模式拒绝、
六种失败码映射、畸形与非 JSON 请求体、超限 413)。
桩自己实现了 React 的 hook 顺序规则(顺序或数量变了就报错,并有一条断言证明这个守卫真的会触发),
另外断言了只读高亮把文件内容当文本转义而不是当 HTML 插入。
- 变异测试:DSH_SELFTEST_BUNDLE=<副本路径> 可以让自检指向一个改坏的副本,
用来证明某条断言真的会因为缺了它守护的那段代码而失败,而不是恰好为真。
例如把右键菜单的关闭守卫去掉后,自检会稳定报出 3 项失败;把编辑开关钉死成 true
会报出 8 项。文件是 UTF-8 且含中文,用 PowerShell 读写副本时要显式指定 UTF-8,
否则 Get-Content 会按本地代码页解码成乱码。
- 桩里必须有假宿主节点:组件通过 ref 读 value / selectionStart / selectionEnd,
桩如果不给 ref 一个节点,这些代码路径会静默空转,围绕它们的断言就全是假的。
自检会给每个带 ref 的宿主元素绑一个跨渲染保持的假节点(值跟随受控 prop)。

为什么菜单要按「按下 → 点击」两段来测

真实浏览器里 mousedown 先于 click。菜单的关闭逻辑挂在 document 的 mousedown 上,
如果它不区分"按在菜单里面",那么按下的那一瞬间菜单就被卸载了,
浏览器随后派发的 click 落在已移除的按钮上——菜单项看起来完全没反应。
这不是假想:第一版就是这样,而当时的测试直接调用 props.onClick(),
跳过了事件序列,所以一路全绿。修法是两层:关闭回调忽略来自 .dsh-se-menu 内部的事件,
菜单自身再 preventDefault() + stopPropagation();测试改成先派发 document 的 mousedown
再触发点击,并在没有修复的副本上验证过它确实会失败。

为什么右键菜单里要自己做剪切/复制/粘贴

插件没有向应用自身右键菜单加项的接口(客户端各包文档里搜不到任何 context menu 服务或插槽),
而同时弹出两个菜单会叠在一起。所以 preventDefault() 只有在"我的菜单取代了被我压掉的那个"时才算诚实 ——
于是标准编辑项由插件自己实现:navigator.clipboard 优先,失败时回落到
document.execCommand('copy');读取剪贴板可能被浏览器拒绝,那时会明确提示"请直接用 Ctrl+V",
而不是静默失败。只读模式没有可编辑缓冲区,因此只留「引用」与「复制」。
- 沙箱围栏的真实性检查(直接驱动应用自带的实现,不用假代码):

node E:\DSH\_tools\fence-check-sidebar-editor.mjs

它调用 SandboxedFileSystem.prototype.checkedTarget 与 writableRoots 本身,
证明:会话根策略允许写会话工作区内、拒绝写其外;部署根回落正是当初拒绝保存的原因;
只读会话拒绝一切写入;danger-full-access 不加围栏;前缀相似的兄弟目录不会被误判为在根之内。
它需要 E:\DSH\_dsh_src 那棵解包出来的包树(用 E:\DSH\_tools\asar.js 生成)。
- 两种形态都在自检里:bundle 里的 editingEnabled() 会读宿主侧全局量
__DSH_SIDEBAR_EDITOR_EDITING__(应用里没有这个全局量,所以出厂就是预览形态)。
自检把它设成 true 跑完整套编辑器/保存/冲突断言,再设回 false 断言出厂形态 ——
因此"关闭编辑"不会让那半边代码失去覆盖。变异掉这个开关(让它恒为 true)会让 8 项断言失败。

卸载

在插件页把 dsh-sidebar-editor 取消选择或卸载,然后重启。

已知限制

  • 超过 4000 行的文件不出着色,只出纯文本(DOM 规模换响应速度);行仍然可选可引用,工具条上有说明。
  • 着色是单遍正则分词,不是语法分析:多行结构只对块注释与 Python 三引号字符串做了跨行延续,
    模板字符串、嵌套语言的着色可能不够精确。要精确着色可以随时切到内置「代码」渲染器(Shiki)。
  • 编辑形态关闭软换行:为了行号严格对齐,长行横向滚动而不是折行。该形态默认不启用,
    且没有撤销栈(受控 `` 会清空原生 undo 历史),重新启用前需要自己实现快照撤销。
  • 面板内的操作行是插件自绘的(没有占用文档面板头部的 sidebar.right.tab.document.action 插槽),
    与宿主头部按钮的像素级一致度有限。
  • 文件超过 512 KiB 或 20000 行转只读;未完全载入前也保持只读(避免保存时截断未读到的尾部)。
  • 保存只对会话工作区根之内的文件生效(这就是沙箱的定义)。工作区外的文件仍可打开、
    可引用、可只读浏览,但保存会以「不在可写工作区内」失败并回传实际可写根。
  • 一个 DSH 升级就失效的风险点:inputActions 与 conversation.input 属于客户端内部约定,
    后者甚至没写进 README;插件对两者都做了存在性检查并会显示明确错误,而不是静默失败。
    同理,宿主半边的会话围栏依赖 ctx.sessions.get(id).header.cwd 与
    ctx.sandboxPolicy.resolve({ session }),这两个都是官方读写路径在用的接口,但同样不在插件契约里。

常见问题

7 问
答案由本站依据条目字段整理,不引用上游原文
dsh-sidebar-editor 怎么安装?

本站没有抓到该条目的安装命令,请到仓库主页确认安装方式;这类条目多数是生态应用或尚未发布安装包的项目。

dsh-sidebar-editor 的验证等级是什么意思?

未标注验证等级。上游未给出验证结论,安装前请自行确认

dsh-sidebar-editor 安全吗?

未标注。

dsh-sidebar-editor 装之前要先确认什么?

先看它会读写哪些文件、执行哪些命令、连接哪些网络地址;验证等级只说明装得上、跑得起来,不等于安全。涉及密钥或系统权限的,建议先在隔离环境试装。

dsh-sidebar-editor 用的是什么许可证?

上游标注为 MIT。商用或二次分发前请到仓库确认许可证原文。

dsh-sidebar-editor 有哪些功能?

侧栏彩色代码预览与行选、选中行一键引用进对话框、文件树右键引用文件或目录、按名字搜索并定位工作区文件、文件内查找与字号调节。

这条记录的数据可靠吗?

本站按仓库名去重后合并整理,字段以作者仓库与 npm 元数据为准;安装命令、仓库地址与 README 保持原文,带 L4 / L5 标记的安装命令已在隔离沙盒里真实跑通。

同名或近似条目

1 条
不同作者的仓库分开列,请确认你要装的是哪一个
UI 与外观
按星标排序,可作替代方案

awesome-dsh-plugin

awesome-dsh-plugin

A curated list of plugins for DeepSeek Harness (dsh) · DeepSeek Harnes

L4 · 沙盒安装通过 1.8 万 星

deepseek-balance-whale-widget

DeepSeek-Balance-Whale-Widget

DSH 界面右下角的常驻小鲸鱼余额挂件,实时显示 DeepSeek 账户余额、今日已用与每轮对话消耗,支持拖拽吸附、Q 弹动画与音效,开箱即

L4 · 沙盒安装通过 4,492 星

dsh-better-sidebar

DSH-better-sidebar

面向 DeepSeek Harness 的服务化侧边栏工作台插件,把文件编辑、终端、Git 变更、子代理任务与侧边对话整合进右侧栏与底部面板

L4 · 沙盒安装通过 4,112 星

dsh-deep-whale

dsh-deep-whale

DSH Web 鲸鱼娘皮肤系列(深海女仆工坊 maid-atelier)。

L4 · 沙盒安装通过 2,470 星

deepseek-design

deepseek-design

可视化设计工作室,支持网站、App 原型、海报、信息卡、报告和杂志的模板创建、元素编辑、选区级 AI 草稿衔接与导出。

L4 · 沙盒安装通过 1,438 星

superdesign-skill

superdesign-skill

Superdesign为DeepSeek Harness等编码代理提供设计判断力,将AI生成的平庸UI转化为可交付的精致前端,支持设计系统、

L3 · 安装规范通过 647 星

插件的元信息来自作者仓库;分类、验证等级与安全结论的中文说明为中文整理,最近核验 —。