dsh-vscode-mode 0.1.59 → 0.1.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -11
- package/lib/client.js +445 -20
- package/lib/client.js.map +1 -1
- package/package.json +2 -2
- package/src/client/fileOpeners.ts +41 -1
- package/src/client/index.ts +91 -23
- package/src/client/officialSidebar.ts +281 -0
- package/src/client/ui/EditorView.ts +1 -1
- package/src/client/ui/OfficialSideTab.ts +73 -0
package/README.md
CHANGED
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
仿 VSCode 的 **Agent 文件编辑器 + 差异审查** DSH 插件:
|
|
6
6
|
|
|
7
|
-
- **侧边栏「文件编辑」Tab**(v0.1.
|
|
8
|
-
|
|
9
|
-
类主流 Code Agent
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
- **侧边栏「文件编辑」Tab**(v0.1.60,推荐):检测到 **DSH 0.1.5+ 官方右侧 Sidebar**(`ctx.sidebarRightTabs` /
|
|
8
|
+
`ctx.sidebarRight` 服务)时,编辑器注册为官方侧边栏 Tab——**AI 对话(中央)与文件编辑(右侧栏)同屏**,
|
|
9
|
+
类主流 Code Agent 布局;支持官方多标签/分栏/浮出/全屏,打开路由即开即展开;官方 Sidebar 引导页提供入口。
|
|
10
|
+
**优先级**:官方侧边栏与 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 同时可用时**官方优先**
|
|
11
|
+
(better-sidebar 桥已归档,仅作旧版 DSH 回退,不再新增能力)。旧版 DSH(< 0.1.5)沿用 better-sidebar 侧栏形态,
|
|
12
|
+
两者皆缺时回退旧「中央文件编辑页签」形态(插件零新依赖仍完整可用),
|
|
13
|
+
编辑器顶部与设置页「兼容性」子 Tab 会给出对应引导。
|
|
14
|
+
**文件链接(DSH 0.1.5+)**:聊天/文件树的文件链接经官方 `ctx.sidebarRight.openResource` 直达右侧栏——
|
|
15
|
+
「文件链接使用工具」为 自动/VSCodeMode 时本插件以 extension 档认领 `dsh-resource://file/**`(链接在本插件
|
|
16
|
+
Monaco 编辑器打开,含行号定位);为「官方侧边栏」档时走官方文本查看器(不认领);畸形地址自动回落官方查看器。
|
|
12
17
|
文件页签(脏点/关闭/「+」打开)+ `Ctrl+P` 快速打开(QuickOpen)+ **Monaco Editor**
|
|
13
18
|
(语法高亮/行号/`Ctrl+F`/`Ctrl+G`/`Ctrl+S`/700ms 防抖自动保存)+ 顶部工具栏(路径/语言/Ln,Col/保存状态/差异/侧边栏/刷新)
|
|
14
19
|
+ **导航历史**(后退/前进:跨文件恢复焦点位置,工具栏 `←`/`→` 按钮、键盘 `Alt+←/→` 与 `Ctrl+Alt+-`/`Ctrl+Shift+-`、
|
|
@@ -65,7 +70,8 @@
|
|
|
65
70
|
|
|
66
71
|

|
|
67
72
|
|
|
68
|
-
> dsh-vscode-mode
|
|
73
|
+
> dsh-vscode-mode 侧边栏编辑形态:右侧栏(DSH 0.1.5+ 官方 Sidebar / 旧版 betterSidebar)内的
|
|
74
|
+
> Monaco 文件编辑器与中央 AI 对话同屏,
|
|
69
75
|
> 差异条统一挂在对话输入框上方的原生 dock(编辑器未打开=「差异 N 个文件 · 查看下一个」,
|
|
70
76
|
> 打开后=完整 Keep / Undo 操作条)。
|
|
71
77
|
|
|
@@ -148,9 +154,11 @@ src/
|
|
|
148
154
|
├── rpc.ts Host RPC 分发表(类型化 handler 表替代巨型 switch,含 compat)
|
|
149
155
|
├── routes.ts Host webServer 路由(/edrv/rpc、/edrv/assets/*、/edrv/vendor/*,带冲突护栏)
|
|
150
156
|
└── client/
|
|
151
|
-
├── index.ts Client 入口:slot 注册(inject=['slots','timer']
|
|
157
|
+
├── index.ts Client 入口:slot 注册(inject=['slots','timer']);官方/betterSidebar/中央页签三形态互斥分流(官方优先)
|
|
152
158
|
├── compat.ts ★ Client 兼容层:设置桥三级降级(webUiSettings→settingsScope)、slot 安全注册、openPath 链式补丁、外部插件常量
|
|
153
|
-
├── sidebarBridge.ts ★
|
|
159
|
+
├── sidebarBridge.ts ★ 侧边栏编辑区桥(归档):可选探测 ctx.betterSidebar、注册「文件编辑」Tab、打开路由/角标计数(纯函数可单测)
|
|
160
|
+
├── officialSidebar.ts ★ 官方右侧 Sidebar 桥(DSH 0.1.5+,唯一维护面):探测 sidebarRightTabs/sidebarRight、
|
|
161
|
+
│ 两段式注册(类型定义 + keyed slot 正文)、openTab 路由(纯函数可单测)
|
|
154
162
|
├── rpc.ts Client 类型化 fetch 包装 + 诊断日志
|
|
155
163
|
├── events.ts 窗口事件助手(edrv:refresh/open-editor/show-launcher;侧栏路由优先、旧页签回退)
|
|
156
164
|
├── state/ records.ts(摘要/计数/空差异)+ regions.ts(差异区域/行裁剪)纯函数
|
|
@@ -164,7 +172,8 @@ src/
|
|
|
164
172
|
│ + types.ts(SidebarPanelDef/SidebarCtx)+ panels/FileExplorer.ts(文件树面板 #1)
|
|
165
173
|
│ + panels/SearchPanel.ts(搜索面板)+ panels/RulesPanel.ts(规则面板:用户/项目规则 + 开关)
|
|
166
174
|
├── styles/editor.css 编辑区样式(tsdown CSS-inline 注入;含侧栏形态/引导条)
|
|
167
|
-
└── ui/ EditorView(编排,tab/side 双形态)/
|
|
175
|
+
└── ui/ EditorView(编排,tab/side 双形态)/ OfficialSideTab(官方 Sidebar Tab 包装)
|
|
176
|
+
/ SideEditorTab(betterSidebar Tab 包装,归档)/ QuickOpen
|
|
168
177
|
/ DiffBox(chat/editor 双模式)/ ConversationDiffDock / DiffBarEmpty / DiffLauncher
|
|
169
178
|
/ DiffBadge / McpSettings(含「兼容性」子 Tab)
|
|
170
179
|
```
|
|
@@ -203,8 +212,9 @@ curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/jso
|
|
|
203
212
|
- **RPC**:静态包经 webServer 精确路由 `/edrv/rpc`,Client 同源 fetch;载荷形状由 `shared/rpc` 类型化。
|
|
204
213
|
- **批次/融合/归档**:每次新 edit/write 递增文件 batch,早于最新批次的未归档差异自动"融合"归档;
|
|
205
214
|
每条差异处理完成(采纳/拒绝/被覆盖)立即单条归档;DiffLauncher「归档」页按批次浏览 + 回滚。
|
|
206
|
-
- **Client
|
|
207
|
-
|
|
215
|
+
- **Client 挂点**:官方右侧 Sidebar Tab(DSH 0.1.5+:类型定义 id `dsh-vscode-mode`、kind `edrvEditor`,
|
|
216
|
+
正文挂 keyed slot `sidebar.right.pane.tab`;优先级最高)> betterSidebar Tab `edrv-editor`(归档,仅旧版回退)
|
|
217
|
+
> `conversation.view` 页签(id `edrv-editor`)+ `conversation.session.header.utilities`(id `edrv-diff-badge`)+ `conversation.input.dock`
|
|
208
218
|
(id `edrv-diff-dock`,唯一差异栏:编辑器未打开=紧凑按钮,打开后=完整操作条)。内部路由/slot/事件/CSS 前缀沿用 `edrv-*`(防回归),包身份为 `dsh-vscode-mode`。
|
|
209
219
|
- **⚠️ Host 改动需重启 DSH 应用**(Node ESM 模块缓存);Client 经 `dsh-client-hmr` 热重载。
|
|
210
220
|
|
package/lib/client.js
CHANGED
|
@@ -6343,7 +6343,7 @@ window.__ModuleLoader__.load({
|
|
|
6343
6343
|
const copyInstallCmd = () => {
|
|
6344
6344
|
if (navigator.clipboard?.writeText) navigator.clipboard.writeText(SIDEBAR_INSTALL_CMD).catch(() => {});
|
|
6345
6345
|
};
|
|
6346
|
-
const sideHintEl = layout === "tab" && sideHint && !hintDismissed ? react.default.createElement("div", { className: "edrv-side-hint" }, react.default.createElement("span", { className: "edrv-side-hint-text" }, "
|
|
6346
|
+
const sideHintEl = layout === "tab" && sideHint && !hintDismissed ? react.default.createElement("div", { className: "edrv-side-hint" }, react.default.createElement("span", { className: "edrv-side-hint-text" }, "DSH 0.1.5+ 自带官方右侧侧边栏(升级即用);旧版可安装 dsh-better-sidebar 启用侧边栏编辑(对话+编辑同屏):"), react.default.createElement("code", { className: "edrv-side-hint-cmd" }, SIDEBAR_INSTALL_CMD), react.default.createElement("button", {
|
|
6347
6347
|
className: "edrv-pill edrv-pill-ghost",
|
|
6348
6348
|
title: "复制安装命令",
|
|
6349
6349
|
onClick: copyInstallCmd
|
|
@@ -6883,14 +6883,254 @@ window.__ModuleLoader__.load({
|
|
|
6883
6883
|
};
|
|
6884
6884
|
}
|
|
6885
6885
|
//#endregion
|
|
6886
|
+
//#region src/client/officialSidebar.ts
|
|
6887
|
+
/**
|
|
6888
|
+
* dsh-vscode-mode client — 官方右侧 Sidebar 桥:探测 DSH 0.1.5-alpha.1+ 的
|
|
6889
|
+
* ctx.sidebarRightTabs / ctx.sidebarRight 服务,两段式注册「文件编辑」Tab
|
|
6890
|
+
* (类型定义进注册表,正文进 keyed slot sidebar.right.pane.tab)。
|
|
6891
|
+
* 可选依赖模式:服务缺失时调用方回退 better-sidebar / 中央页签;本模块不注册任何东西。
|
|
6892
|
+
* 维护约定:官方侧边栏(DSH 0.1.5+)是侧边栏编辑形态的唯一维护面;
|
|
6893
|
+
* dsh-better-sidebar 桥(sidebarBridge.ts)已归档,仅作旧版 DSH 回退,不再新增能力。
|
|
6894
|
+
* 纯逻辑(探测/导航参数解析)可单测,注册装配仅存在于 install 路径。
|
|
6895
|
+
* 作者 ddj 2026年09月09号
|
|
6896
|
+
*/
|
|
6897
|
+
/** 官方 Tab 类型注册表服务名(ctx.get 用;不进 inject——旧版 DSH 无此包,inject 会停等)。 */
|
|
6898
|
+
const OFFICIAL_TABS_SERVICE = "sidebarRightTabs";
|
|
6899
|
+
/** 官方导航控制器服务名(ctx.get 用)。 */
|
|
6900
|
+
const OFFICIAL_SERVICE = "sidebarRight";
|
|
6901
|
+
/** 官方 keyed slot 名(Tab 正文挂点,由官方侧边栏的 rightbar seat 声明)。 */
|
|
6902
|
+
const OFFICIAL_SLOT_NAME = "sidebar.right.pane.tab";
|
|
6903
|
+
/** 本插件 Tab 类型定义 id(官方约定用包名;正文 slot 的 key 与之相同)。 */
|
|
6904
|
+
const OFFICIAL_TAB_ID = "dsh-vscode-mode";
|
|
6905
|
+
/** 本插件 Tab 的类型判别符(ctx.sidebarRight.openTab 的 kind)。 */
|
|
6906
|
+
const OFFICIAL_TAB_KIND = "edrvEditor";
|
|
6907
|
+
/** Tab 标题(与 better-sidebar 形态一致)。 */
|
|
6908
|
+
const OFFICIAL_TAB_TITLE = "文件编辑";
|
|
6909
|
+
/** file 地址认领的 patterns(extension 档接管官方 textpreview 的 fallback 认领,注销自动回落)。 */
|
|
6910
|
+
const OFFICIAL_FILE_PATTERN = "dsh-resource://file/**";
|
|
6911
|
+
/** file 资源类型的定义 id(正文 slot 的 key)。 */
|
|
6912
|
+
const OFFICIAL_FILE_TAB_ID = "dsh-vscode-mode/file";
|
|
6913
|
+
/** file 资源类型的 kind(与页类型 edrvEditor 区分:同 kind 只允许一份 extension)。 */
|
|
6914
|
+
const OFFICIAL_FILE_KIND = "edrvEditorFile";
|
|
6915
|
+
/**
|
|
6916
|
+
* 结构化探测官方右侧 Sidebar 服务(可选依赖:缺失/降级返回 undefined)。
|
|
6917
|
+
* 服务仅存在于 DSH 0.1.5-alpha.1+,探测命中 ≡ 版本判定成立。
|
|
6918
|
+
* @author ddj 2026年09月09号
|
|
6919
|
+
* @param ctx 客户端服务上下文
|
|
6920
|
+
* @returns 注册表与控制器对,或 undefined
|
|
6921
|
+
*/
|
|
6922
|
+
function detectOfficial(ctx) {
|
|
6923
|
+
try {
|
|
6924
|
+
const tabs = ctx.get(OFFICIAL_TABS_SERVICE);
|
|
6925
|
+
const service = ctx.get(OFFICIAL_SERVICE);
|
|
6926
|
+
if (!tabs || typeof tabs.register !== "function") return void 0;
|
|
6927
|
+
if (!service || typeof service.openTab !== "function") return void 0;
|
|
6928
|
+
return {
|
|
6929
|
+
tabs,
|
|
6930
|
+
service
|
|
6931
|
+
};
|
|
6932
|
+
} catch {
|
|
6933
|
+
return;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
/**
|
|
6937
|
+
* 从官方导航参数安全解析打开请求(缺字段/坏类型一律降级为空请求,不抛错)。
|
|
6938
|
+
* @author ddj 2026年09月09号
|
|
6939
|
+
* @param params openTab 传入的 params(正文经 navigation.params 读回)
|
|
6940
|
+
* @returns 待打开路径与是否聚焦首个差异
|
|
6941
|
+
*/
|
|
6942
|
+
function resolveNavOpen(params) {
|
|
6943
|
+
const value = params ?? {};
|
|
6944
|
+
return {
|
|
6945
|
+
path: typeof value?.openPath === "string" && value.openPath ? value.openPath : null,
|
|
6946
|
+
focusDiff: value?.focusDiff === true
|
|
6947
|
+
};
|
|
6948
|
+
}
|
|
6949
|
+
/** 逐段 component 解码(失败返回 null,对齐官方 parseFileAddress 的容错语义)。 */
|
|
6950
|
+
function decodeSegment(raw) {
|
|
6951
|
+
if (raw === void 0) return null;
|
|
6952
|
+
try {
|
|
6953
|
+
return decodeURIComponent(raw);
|
|
6954
|
+
} catch {
|
|
6955
|
+
return null;
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
/**
|
|
6959
|
+
* 解析官方 file 资源地址(镜像 dsh-util-workspace-path 的 parseFileAddress 语法,不 import 官方包)。
|
|
6960
|
+
* session 域:`dsh-resource://file/session/<sessionId>/<逐段编码的相对路径>`;
|
|
6961
|
+
* absolute 域:`dsh-resource://file/absolute/<去前导 / 的绝对路径>`(POSIX 补回前导 /,
|
|
6962
|
+
* Windows 盘符 `C:/` 原样,UNC 保留空首段 `//server/share`)。
|
|
6963
|
+
* @author ddj 2026年09月09号
|
|
6964
|
+
* @param address 候选地址
|
|
6965
|
+
* @returns 文件路径(session 域附带 sessionId);非 file 地址/畸形/解码失败返回 null
|
|
6966
|
+
*/
|
|
6967
|
+
function parseOfficialFileAddress(address) {
|
|
6968
|
+
if (typeof address !== "string" || !address.startsWith("dsh-resource://file/")) return null;
|
|
6969
|
+
const [scope, ...tail] = address.slice(20).split("/");
|
|
6970
|
+
const kind = decodeSegment(scope);
|
|
6971
|
+
if (kind === "session") {
|
|
6972
|
+
const sessionId = decodeSegment(tail[0]);
|
|
6973
|
+
const segments = tail.slice(1).map(decodeSegment);
|
|
6974
|
+
if (!sessionId || segments.length === 0 || segments.some((s) => s === null)) return null;
|
|
6975
|
+
const path = segments.join("/");
|
|
6976
|
+
return path ? {
|
|
6977
|
+
path,
|
|
6978
|
+
sessionId
|
|
6979
|
+
} : null;
|
|
6980
|
+
}
|
|
6981
|
+
if (kind === "absolute") {
|
|
6982
|
+
const unc = tail[0] === "" && tail.length > 1;
|
|
6983
|
+
const segments = (unc ? tail.slice(1) : tail).map(decodeSegment);
|
|
6984
|
+
if (segments.length === 0 || segments[0] === "" || segments.some((s) => s === null)) return null;
|
|
6985
|
+
const list = segments;
|
|
6986
|
+
if (unc) return { path: "//" + list.join("/") };
|
|
6987
|
+
if (/^[A-Za-z]:$/.test(list[0])) return { path: list.join("/") };
|
|
6988
|
+
return { path: "/" + list.join("/") };
|
|
6989
|
+
}
|
|
6990
|
+
return null;
|
|
6991
|
+
}
|
|
6992
|
+
/**
|
|
6993
|
+
* file 地址的 Tab chip 标题:解码后的 basename(解码失败回退原始串切片)。
|
|
6994
|
+
* @author ddj 2026年09月09号
|
|
6995
|
+
* @param address file 资源地址
|
|
6996
|
+
* @returns 标题文本
|
|
6997
|
+
*/
|
|
6998
|
+
function officialFileTitle(address) {
|
|
6999
|
+
const source = parseOfficialFileAddress(address)?.path ?? (typeof address === "string" ? address : "");
|
|
7000
|
+
const normalized = source.replace(/\\/g, "/");
|
|
7001
|
+
return normalized.slice(normalized.lastIndexOf("/") + 1) || source;
|
|
7002
|
+
}
|
|
7003
|
+
/** component 编码一段(对齐官方 encodeSegment:盘符冒号保持字面)。 */
|
|
7004
|
+
function encodeSegment(segment) {
|
|
7005
|
+
return encodeURIComponent(segment).replace(/%3A/gi, ":");
|
|
7006
|
+
}
|
|
7007
|
+
/**
|
|
7008
|
+
* 构造官方 file 资源地址(镜像 fileAddressFor 的分派:绝对路径→absolute 域,相对→session 域)。
|
|
7009
|
+
* @author ddj 2026年09月09号
|
|
7010
|
+
* @param path 绝对或工作区相对路径(两种分隔符均可)
|
|
7011
|
+
* @param sessionId 会话 id(相对路径必需;绝对路径可省)
|
|
7012
|
+
* @returns `dsh-resource://file/…` 地址
|
|
7013
|
+
* @throws 路径为空,或相对路径缺 sessionId
|
|
7014
|
+
*/
|
|
7015
|
+
function buildFileAddress(path, sessionId) {
|
|
7016
|
+
const posix = (typeof path === "string" ? path : "").replace(/\\/g, "/");
|
|
7017
|
+
if (!posix.trim()) throw new Error("buildFileAddress:路径为空");
|
|
7018
|
+
if (posix.startsWith("/") || posix.startsWith("//") || /^[A-Za-z]:[/\\]/.test(posix)) {
|
|
7019
|
+
const unc = posix.startsWith("//");
|
|
7020
|
+
const body = posix.replace(/^\/+/, "");
|
|
7021
|
+
return "dsh-resource://file/absolute/" + (unc ? "/" : "") + body.split("/").map(encodeSegment).join("/");
|
|
7022
|
+
}
|
|
7023
|
+
if (!sessionId) throw new Error("buildFileAddress:相对路径需要会话 id");
|
|
7024
|
+
const relative = posix.replace(/^(?:\.\/)+/, "").replace(/^\/+/, "");
|
|
7025
|
+
return "dsh-resource://file/session/" + encodeSegment(sessionId) + "/" + relative.split("/").map(encodeSegment).join("/");
|
|
7026
|
+
}
|
|
7027
|
+
/**
|
|
7028
|
+
* 注册官方 Tab 正文(keyed slot,key=定义 id;slots.inject 等待 rightbar seat 声明)。
|
|
7029
|
+
* @author ddj 2026年09月09号
|
|
7030
|
+
* @param slots slots 服务
|
|
7031
|
+
* @param key 正文注册键(= 类型定义 id)
|
|
7032
|
+
* @param renderTab 正文组件(包装后的 OfficialSideTab)
|
|
7033
|
+
* @returns 注销函数;注册抛错返回 null
|
|
7034
|
+
*/
|
|
7035
|
+
function registerBodySlot(slots, key, renderTab) {
|
|
7036
|
+
try {
|
|
7037
|
+
const disposer = slots.inject(OFFICIAL_SLOT_NAME, () => slots.register({
|
|
7038
|
+
name: OFFICIAL_SLOT_NAME,
|
|
7039
|
+
key
|
|
7040
|
+
}, renderTab));
|
|
7041
|
+
return typeof disposer === "function" ? disposer : null;
|
|
7042
|
+
} catch (error) {
|
|
7043
|
+
log.warn("官方侧边栏正文 slot 注册失败(" + String(error) + ")");
|
|
7044
|
+
return null;
|
|
7045
|
+
}
|
|
7046
|
+
}
|
|
7047
|
+
/**
|
|
7048
|
+
* 注册官方右侧 Sidebar「文件编辑」Tab 并接管打开路由(两段式 + 路由)。
|
|
7049
|
+
* 调用方须把返回值挂进 ctx.effect;类型注册抛错时返回 null,由调用方回退旧形态。
|
|
7050
|
+
* @author ddj 2026年09月09号
|
|
7051
|
+
* @param options 装配参数(探测命中的一对服务 + slots + 正文组件 + guide 文案)
|
|
7052
|
+
* @returns 卸载器(反注册类型与正文 + 撤销路由);类型注册失败返回 null
|
|
7053
|
+
*/
|
|
7054
|
+
function installOfficial(options) {
|
|
7055
|
+
const { tabs, service, slots, renderTab, guide } = options;
|
|
7056
|
+
const open = (path, focusDiff) => {
|
|
7057
|
+
try {
|
|
7058
|
+
service.openTab(OFFICIAL_TAB_KIND, { params: {
|
|
7059
|
+
openPath: path || void 0,
|
|
7060
|
+
focusDiff
|
|
7061
|
+
} });
|
|
7062
|
+
} catch (error) {
|
|
7063
|
+
log.warn("官方侧边栏打开失败(" + String(error) + ")");
|
|
7064
|
+
return false;
|
|
7065
|
+
}
|
|
7066
|
+
return true;
|
|
7067
|
+
};
|
|
7068
|
+
let typeDisposer = null;
|
|
7069
|
+
try {
|
|
7070
|
+
typeDisposer = tabs.register({
|
|
7071
|
+
id: OFFICIAL_TAB_ID,
|
|
7072
|
+
kind: OFFICIAL_TAB_KIND,
|
|
7073
|
+
priority: "extension",
|
|
7074
|
+
title: () => OFFICIAL_TAB_TITLE,
|
|
7075
|
+
guide: [{
|
|
7076
|
+
order: 20,
|
|
7077
|
+
title: guide.title,
|
|
7078
|
+
description: guide.description
|
|
7079
|
+
}]
|
|
7080
|
+
});
|
|
7081
|
+
} catch (error) {
|
|
7082
|
+
log.warn("官方侧边栏 Tab 类型注册失败(" + String(error) + "),回退旧形态");
|
|
7083
|
+
return null;
|
|
7084
|
+
}
|
|
7085
|
+
setEnsureSideEditor(open);
|
|
7086
|
+
const bodyDisposer = registerBodySlot(slots, OFFICIAL_TAB_ID, renderTab);
|
|
7087
|
+
return () => {
|
|
7088
|
+
if (typeof typeDisposer === "function") typeDisposer();
|
|
7089
|
+
if (typeof bodyDisposer === "function") bodyDisposer();
|
|
7090
|
+
setEnsureSideEditor(null);
|
|
7091
|
+
};
|
|
7092
|
+
}
|
|
7093
|
+
/**
|
|
7094
|
+
* 注册官方 file 资源地址认领(extension 档接管 `dsh-resource://file/**`,
|
|
7095
|
+
* 聊天文件链接/文件树打开改落本插件编辑器;注销后官方 textpreview 自动恢复)。
|
|
7096
|
+
* 认领与否由「文件链接使用工具」设置驱动(见 fileOpeners.shouldClaimFiles)。
|
|
7097
|
+
* @author ddj 2026年09月09号
|
|
7098
|
+
* @param options 装配参数(探测命中的注册表 + slots + 正文组件)
|
|
7099
|
+
* @returns 卸载器(反注册类型与正文);类型注册抛错返回 null(回落官方查看器)
|
|
7100
|
+
*/
|
|
7101
|
+
function registerOfficialFileClaim(options) {
|
|
7102
|
+
const { tabs, slots, renderTab } = options;
|
|
7103
|
+
let typeDisposer = null;
|
|
7104
|
+
try {
|
|
7105
|
+
typeDisposer = tabs.register({
|
|
7106
|
+
id: OFFICIAL_FILE_TAB_ID,
|
|
7107
|
+
kind: OFFICIAL_FILE_KIND,
|
|
7108
|
+
patterns: [OFFICIAL_FILE_PATTERN],
|
|
7109
|
+
priority: "extension",
|
|
7110
|
+
canOpen: (address) => parseOfficialFileAddress(address) !== null,
|
|
7111
|
+
title: (address) => officialFileTitle(address)
|
|
7112
|
+
});
|
|
7113
|
+
} catch (error) {
|
|
7114
|
+
log.warn("官方 file 地址认领注册失败(" + String(error) + "),链接走官方查看器");
|
|
7115
|
+
return null;
|
|
7116
|
+
}
|
|
7117
|
+
const bodyDisposer = registerBodySlot(slots, OFFICIAL_FILE_TAB_ID, renderTab);
|
|
7118
|
+
return () => {
|
|
7119
|
+
if (typeof typeDisposer === "function") typeDisposer();
|
|
7120
|
+
if (typeof bodyDisposer === "function") bodyDisposer();
|
|
7121
|
+
};
|
|
7122
|
+
}
|
|
7123
|
+
//#endregion
|
|
6886
7124
|
//#region src/client/fileOpeners.ts
|
|
6887
7125
|
/**
|
|
6888
7126
|
* dsh-vscode-mode client — 文件打开器注册表与自动选择。
|
|
6889
|
-
* @author ddj 2026年08月24号
|
|
7127
|
+
* @author ddj 2026年08月24号 / 2026年09月09号
|
|
6890
7128
|
*/
|
|
6891
7129
|
const AUTO_OPEN_TOOL = "auto";
|
|
6892
7130
|
const VSCODE_OPEN_TOOL = "dsh-vscode-mode";
|
|
6893
7131
|
const SIDEBAR_OPEN_TOOL = SIDEBAR_PLUGIN;
|
|
7132
|
+
/** 官方右侧侧边栏打开器 id(「文件链接使用工具」下拉的官方选项;DSH 0.1.5+)。 */
|
|
7133
|
+
const OFFICIAL_OPEN_TOOL = "dsh-official-sidebar";
|
|
6894
7134
|
/**
|
|
6895
7135
|
* 写入外部打开器并触发注册表通知。
|
|
6896
7136
|
* @author ddj 2026年08月24号
|
|
@@ -7016,6 +7256,36 @@ window.__ModuleLoader__.load({
|
|
|
7016
7256
|
const normalized = path.replace(/\\/g, "/");
|
|
7017
7257
|
return normalized.slice(normalized.lastIndexOf("/") + 1) || path;
|
|
7018
7258
|
}
|
|
7259
|
+
/**
|
|
7260
|
+
* 文件链接是否由本插件认领官方 file 地址:自动/VSCodeMode 档接管(链接进本插件编辑器),
|
|
7261
|
+
* 官方侧边栏/system/better-sidebar 档不认领(官方查看器或旧版路由生效)。
|
|
7262
|
+
* @author ddj 2026年09月09号
|
|
7263
|
+
* @param selected 「文件链接使用工具」当前值
|
|
7264
|
+
* @returns 是否认领
|
|
7265
|
+
*/
|
|
7266
|
+
function shouldClaimFiles(selected) {
|
|
7267
|
+
return selected === "auto" || selected === "dsh-vscode-mode";
|
|
7268
|
+
}
|
|
7269
|
+
/**
|
|
7270
|
+
* 创建「官方侧边栏」文件打开器(DSH 0.1.5+,经 ctx.sidebarRight.openResource 打开官方查看器)。
|
|
7271
|
+
* 仅官方服务探测命中时注册;下拉选项随注册动态出现(McpSettings 动态渲染)。
|
|
7272
|
+
* @author ddj 2026年09月09号
|
|
7273
|
+
* @param service 官方导航控制器
|
|
7274
|
+
* @returns 文件打开器
|
|
7275
|
+
*/
|
|
7276
|
+
function officialSidebarOpener(service) {
|
|
7277
|
+
return {
|
|
7278
|
+
id: OFFICIAL_OPEN_TOOL,
|
|
7279
|
+
label: "官方侧边栏",
|
|
7280
|
+
description: "在 DSH 官方右侧侧边栏查看器中打开(0.1.5+)",
|
|
7281
|
+
priority: 90,
|
|
7282
|
+
isAvailable: () => typeof service.openResource === "function",
|
|
7283
|
+
open: (path, context) => {
|
|
7284
|
+
if (typeof service.openResource !== "function") throw new Error("官方侧边栏 openResource 能力不可用");
|
|
7285
|
+
service.openResource(buildFileAddress(path, context.sessionId));
|
|
7286
|
+
}
|
|
7287
|
+
};
|
|
7288
|
+
}
|
|
7019
7289
|
//#endregion
|
|
7020
7290
|
//#region src/client/settingsContext.ts
|
|
7021
7291
|
/**
|
|
@@ -9578,6 +9848,68 @@ window.__ModuleLoader__.load({
|
|
|
9578
9848
|
});
|
|
9579
9849
|
}
|
|
9580
9850
|
//#endregion
|
|
9851
|
+
//#region src/client/ui/OfficialSideTab.ts
|
|
9852
|
+
/**
|
|
9853
|
+
* dsh-vscode-mode client — OfficialSideTab:DSH 官方右侧 Sidebar「文件编辑」Tab 正文包装。
|
|
9854
|
+
* 双用途:页类型 Tab(edrvEditor)经 navigation.params(openPath/focusDiff)打开;
|
|
9855
|
+
* file 资源类型 Tab(edrvEditorFile,认领 dsh-resource://file/**)经 navigation.address
|
|
9856
|
+
* 解码出真实路径并透传 params.line 行号定位。派发既有 edrv:open-editor 事件复用
|
|
9857
|
+
* EditorView 打开流;按会话 key 保证切会话干净重建。
|
|
9858
|
+
* Tab 正文切换后重挂载时按最新导航恢复打开(对齐 better-sidebar 形态的种子行为)。
|
|
9859
|
+
* 作者 ddj 2026年09月09号
|
|
9860
|
+
*/
|
|
9861
|
+
/**
|
|
9862
|
+
* 官方侧边栏 Tab 正文组件(keyed slot sidebar.right.pane.tab 装配)。
|
|
9863
|
+
* @param props slot 框架注入(useTabInfo/sessionId)+ 装配依赖(schedule 等)
|
|
9864
|
+
* @returns EditorView 侧栏形态
|
|
9865
|
+
*/
|
|
9866
|
+
function OfficialSideTab(props) {
|
|
9867
|
+
const sessionId = props?.sessionId;
|
|
9868
|
+
const useTabInfo = props?.useTabInfo;
|
|
9869
|
+
let navigation;
|
|
9870
|
+
if (typeof useTabInfo === "function") try {
|
|
9871
|
+
navigation = useTabInfo()?.tab?.navigation;
|
|
9872
|
+
} catch {
|
|
9873
|
+
navigation = void 0;
|
|
9874
|
+
}
|
|
9875
|
+
const revision = typeof navigation?.revision === "number" ? navigation.revision : -1;
|
|
9876
|
+
const revisionRef = react.default.useRef(-1);
|
|
9877
|
+
react.default.useEffect(() => {
|
|
9878
|
+
setSideEditorMounted(true);
|
|
9879
|
+
return () => setSideEditorMounted(false);
|
|
9880
|
+
}, []);
|
|
9881
|
+
react.default.useEffect(() => {
|
|
9882
|
+
if (revision === revisionRef.current) return;
|
|
9883
|
+
revisionRef.current = revision;
|
|
9884
|
+
if (revision <= 0) return;
|
|
9885
|
+
const fileHit = parseOfficialFileAddress(navigation?.address);
|
|
9886
|
+
if (fileHit) {
|
|
9887
|
+
const line = navigation?.params?.line;
|
|
9888
|
+
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
|
|
9889
|
+
path: fileHit.path,
|
|
9890
|
+
line: typeof line === "number" ? line : void 0
|
|
9891
|
+
} }));
|
|
9892
|
+
return;
|
|
9893
|
+
}
|
|
9894
|
+
const request = resolveNavOpen(navigation?.params);
|
|
9895
|
+
window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
|
|
9896
|
+
path: request.path,
|
|
9897
|
+
focusDiff: request.focusDiff
|
|
9898
|
+
} }));
|
|
9899
|
+
});
|
|
9900
|
+
return react.default.createElement(EditorView, {
|
|
9901
|
+
key: sessionId,
|
|
9902
|
+
sessionId,
|
|
9903
|
+
layout: "side",
|
|
9904
|
+
schedule: props?.schedule,
|
|
9905
|
+
addToConversation: props?.addToConversation,
|
|
9906
|
+
sidebarPanels: props?.sidebarPanels,
|
|
9907
|
+
outlineSources: props?.outlineSources,
|
|
9908
|
+
fileMenuItems: props?.fileMenuItems,
|
|
9909
|
+
sessions: props?.sessions
|
|
9910
|
+
});
|
|
9911
|
+
}
|
|
9912
|
+
//#endregion
|
|
9581
9913
|
//#region src/client/sidebar/registry.ts
|
|
9582
9914
|
/** 校验面板定义并写入注册表(缺 id/render 抛 TypeError)。 */
|
|
9583
9915
|
function panelRegister(entries, notify, panel) {
|
|
@@ -12232,8 +12564,8 @@ window.__ModuleLoader__.load({
|
|
|
12232
12564
|
//#region src/client/index.ts
|
|
12233
12565
|
/**
|
|
12234
12566
|
* dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
|
|
12235
|
-
*
|
|
12236
|
-
*
|
|
12567
|
+
* 挂点:官方右侧 Sidebar「文件编辑」Tab(DSH 0.1.5+,对话+编辑同屏)> betterSidebar Tab
|
|
12568
|
+
* (归档,仅旧版 DSH 回退)> conversation.view 中央页签 + conversation.input.dock 差异条 + header 差异角标。
|
|
12237
12569
|
* 与 Host 通信:同源 fetch('/edrv/rpc')(shared/rpc 契约)。
|
|
12238
12570
|
*
|
|
12239
12571
|
* ⚠️ 跨版本 slot 装配(2026-08-21):新版 DSH 的 slots 系统要求 slot 必须由父 entry
|
|
@@ -12309,6 +12641,10 @@ window.__ModuleLoader__.load({
|
|
|
12309
12641
|
};
|
|
12310
12642
|
/** 可选探测 betterSidebar 服务(不进 inject:缺失会让插件停靠等待,杀死回退路径)。 */
|
|
12311
12643
|
let sideService = detectSidebarService(ctx);
|
|
12644
|
+
/** 可选探测官方右侧 Sidebar(DSH 0.1.5-alpha.1+;探测命中 ≡ 版本判定,不进 inject 同上)。 */
|
|
12645
|
+
let officialService = detectOfficial(ctx);
|
|
12646
|
+
/** 官方 file 地址认领的当前注销器(null=未认领;由 syncFileClaim 依设置值切换)。 */
|
|
12647
|
+
let claimDisposer = null;
|
|
12312
12648
|
/** 「添加到对话」动作集:编辑区/Tab 右键菜单注入文件引用与代码块(conversation 服务缺失时各动作安全降级)。 */
|
|
12313
12649
|
const addToConversation = createAddToConversation(ctx);
|
|
12314
12650
|
/** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
|
|
@@ -12334,9 +12670,19 @@ window.__ModuleLoader__.load({
|
|
|
12334
12670
|
note: registry.get("dsh-better-sidebar") !== void 0 ? "已注册(优先级 80)" : "未检测到侧边栏打开能力"
|
|
12335
12671
|
},
|
|
12336
12672
|
{
|
|
12337
|
-
name: "
|
|
12673
|
+
name: "侧边栏编辑区(官方 Sidebar)",
|
|
12674
|
+
active: officialService !== void 0,
|
|
12675
|
+
note: officialService !== void 0 ? "编辑区=官方右侧 Sidebar Tab(对话+编辑同屏,DSH 0.1.5+)" : "官方侧边栏服务未探测到(DSH < 0.1.5-alpha.1 时属预期)"
|
|
12676
|
+
},
|
|
12677
|
+
{
|
|
12678
|
+
name: "侧边栏编辑区(dsh-better-sidebar,归档)",
|
|
12338
12679
|
active: sideService !== void 0,
|
|
12339
|
-
note: sideService !== void 0 ? "编辑区=侧边栏 Tab
|
|
12680
|
+
note: sideService !== void 0 ? "编辑区=侧边栏 Tab(旧版 DSH 回退形态)" : "未检测到;DSH ≥ 0.1.5 优先官方侧边栏,旧版可安装(" + SIDEBAR_INSTALL_CMD + ")"
|
|
12681
|
+
},
|
|
12682
|
+
{
|
|
12683
|
+
name: "文件链接官方认领(dsh-resource://file)",
|
|
12684
|
+
active: claimDisposer !== null,
|
|
12685
|
+
note: claimDisposer !== null ? "聊天文件链接由本插件编辑器接管(官方侧边栏内打开)" : "链接走官方查看器或旧版路由(未认领)"
|
|
12340
12686
|
}
|
|
12341
12687
|
];
|
|
12342
12688
|
ctx.provide("fileOpeners", registry);
|
|
@@ -12364,12 +12710,39 @@ window.__ModuleLoader__.load({
|
|
|
12364
12710
|
const sidebar = scanSidebar(ctx);
|
|
12365
12711
|
return sidebar ? registry.register(sidebar) : void 0;
|
|
12366
12712
|
}, "vscode-mode: sidebar file opener");
|
|
12713
|
+
const officialRenderTab = (props) => react.default.createElement(OfficialSideTab, Object.assign({}, props, {
|
|
12714
|
+
schedule,
|
|
12715
|
+
addToConversation,
|
|
12716
|
+
sidebarPanels,
|
|
12717
|
+
outlineSources,
|
|
12718
|
+
fileMenuItems,
|
|
12719
|
+
sessions
|
|
12720
|
+
}));
|
|
12721
|
+
/** 官方 file 地址认领同步:自动/VSCodeMode 档认领(链接进本插件编辑器),其余交官方查看器。 */
|
|
12722
|
+
const syncFileClaim = () => {
|
|
12723
|
+
const official = officialService;
|
|
12724
|
+
const want = official !== void 0 && shouldClaimFiles(selected);
|
|
12725
|
+
if (want && claimDisposer === null && official) {
|
|
12726
|
+
const disposer = registerOfficialFileClaim({
|
|
12727
|
+
tabs: official.tabs,
|
|
12728
|
+
slots: ctx.slots,
|
|
12729
|
+
renderTab: officialRenderTab
|
|
12730
|
+
});
|
|
12731
|
+
if (disposer !== null) claimDisposer = ctx.effect(() => disposer, "vscode-mode: official file claim");
|
|
12732
|
+
return;
|
|
12733
|
+
}
|
|
12734
|
+
if (!want && claimDisposer !== null) {
|
|
12735
|
+
claimDisposer();
|
|
12736
|
+
claimDisposer = null;
|
|
12737
|
+
}
|
|
12738
|
+
};
|
|
12367
12739
|
ctx.effect(() => {
|
|
12368
12740
|
if (!settings) return void 0;
|
|
12369
12741
|
const sync = () => {
|
|
12370
12742
|
const snapshot = settings.getSnapshot();
|
|
12371
12743
|
if (snapshot.status === "loading") return;
|
|
12372
12744
|
selected = autoValue(snapshot.value?.fileOpenTool);
|
|
12745
|
+
syncFileClaim();
|
|
12373
12746
|
window.dispatchEvent(new CustomEvent("edrv:file-open-tool-change", { detail: { value: selected } }));
|
|
12374
12747
|
};
|
|
12375
12748
|
sync();
|
|
@@ -12442,11 +12815,50 @@ window.__ModuleLoader__.load({
|
|
|
12442
12815
|
sessions
|
|
12443
12816
|
})));
|
|
12444
12817
|
};
|
|
12818
|
+
let officialDisposer = null;
|
|
12445
12819
|
let sideDisposer = null;
|
|
12446
12820
|
let legacyDisposer = null;
|
|
12821
|
+
const installOfficialForm = () => {
|
|
12822
|
+
const official = officialService;
|
|
12823
|
+
if (officialDisposer !== null || !official) return;
|
|
12824
|
+
if (sideDisposer !== null) {
|
|
12825
|
+
sideDisposer();
|
|
12826
|
+
sideDisposer = null;
|
|
12827
|
+
}
|
|
12828
|
+
if (legacyDisposer !== null) {
|
|
12829
|
+
legacyDisposer();
|
|
12830
|
+
legacyDisposer = null;
|
|
12831
|
+
}
|
|
12832
|
+
const outcome = installOfficial({
|
|
12833
|
+
tabs: official.tabs,
|
|
12834
|
+
service: official.service,
|
|
12835
|
+
slots: ctx.slots,
|
|
12836
|
+
renderTab: officialRenderTab,
|
|
12837
|
+
guide: {
|
|
12838
|
+
title: () => OFFICIAL_TAB_TITLE,
|
|
12839
|
+
description: () => "Monaco 文件编辑器:对话与编辑同屏(差异审查/跳转/大纲)"
|
|
12840
|
+
}
|
|
12841
|
+
});
|
|
12842
|
+
if (outcome === null) {
|
|
12843
|
+
officialService = void 0;
|
|
12844
|
+
applySideForm();
|
|
12845
|
+
return;
|
|
12846
|
+
}
|
|
12847
|
+
officialDisposer = ctx.effect(() => outcome, "vscode-mode: official sidebar editor tab");
|
|
12848
|
+
if (typeof official.service.openResource === "function") ctx.effect(() => registry.register(officialSidebarOpener(official.service)), "vscode-mode: official sidebar opener");
|
|
12849
|
+
syncFileClaim();
|
|
12850
|
+
};
|
|
12447
12851
|
const applySideForm = () => {
|
|
12852
|
+
if (officialService) {
|
|
12853
|
+
installOfficialForm();
|
|
12854
|
+
return;
|
|
12855
|
+
}
|
|
12448
12856
|
if (sideService) {
|
|
12449
12857
|
if (sideDisposer !== null) return;
|
|
12858
|
+
if (officialDisposer !== null) {
|
|
12859
|
+
officialDisposer();
|
|
12860
|
+
officialDisposer = null;
|
|
12861
|
+
}
|
|
12450
12862
|
if (legacyDisposer !== null) {
|
|
12451
12863
|
legacyDisposer();
|
|
12452
12864
|
legacyDisposer = null;
|
|
@@ -12472,28 +12884,41 @@ window.__ModuleLoader__.load({
|
|
|
12472
12884
|
},
|
|
12473
12885
|
registerLegacyFallback: registerLegacyTab
|
|
12474
12886
|
}), "vscode-mode: sidebar editor tab");
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
12480
|
-
|
|
12481
|
-
setEnsureSideEditor(null);
|
|
12482
|
-
legacyDisposer = registerLegacyTab();
|
|
12887
|
+
return;
|
|
12888
|
+
}
|
|
12889
|
+
if (legacyDisposer !== null) return;
|
|
12890
|
+
if (officialDisposer !== null) {
|
|
12891
|
+
officialDisposer();
|
|
12892
|
+
officialDisposer = null;
|
|
12483
12893
|
}
|
|
12894
|
+
if (sideDisposer !== null) {
|
|
12895
|
+
sideDisposer();
|
|
12896
|
+
sideDisposer = null;
|
|
12897
|
+
}
|
|
12898
|
+
setEnsureSideEditor(null);
|
|
12899
|
+
legacyDisposer = registerLegacyTab();
|
|
12484
12900
|
};
|
|
12485
12901
|
applySideForm();
|
|
12486
12902
|
let sideRetries = 0;
|
|
12487
12903
|
const retrySideService = () => {
|
|
12488
|
-
if (
|
|
12904
|
+
if (officialService !== void 0 || sideRetries >= 15) return;
|
|
12489
12905
|
sideRetries += 1;
|
|
12490
12906
|
schedule(() => {
|
|
12491
|
-
if (
|
|
12492
|
-
|
|
12493
|
-
if (
|
|
12494
|
-
log.info("
|
|
12907
|
+
if (officialService !== void 0) return;
|
|
12908
|
+
officialService = detectOfficial(ctx);
|
|
12909
|
+
if (officialService !== void 0) {
|
|
12910
|
+
log.info("检测到官方右侧 Sidebar(DSH 0.1.5+),切换官方侧边栏编辑形态");
|
|
12495
12911
|
applySideForm();
|
|
12496
|
-
|
|
12912
|
+
return;
|
|
12913
|
+
}
|
|
12914
|
+
if (sideService === void 0) {
|
|
12915
|
+
sideService = detectSidebarService(ctx);
|
|
12916
|
+
if (sideService !== void 0) {
|
|
12917
|
+
log.info("检测到 dsh-better-sidebar,切换侧边栏编辑形态(官方未探测到,后续仍优先官方)");
|
|
12918
|
+
applySideForm();
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
retrySideService();
|
|
12497
12922
|
}, 2e3);
|
|
12498
12923
|
};
|
|
12499
12924
|
retrySideService();
|