dsh-vscode-mode 0.12.0 → 0.13.1
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 +148 -112
- package/assets/shell/dsh-open.cs +65 -149
- package/assets/shell/dsh-open.mjs +241 -0
- package/assets/shell/dsh-open.ps1 +28 -47
- package/assets/shell/dsh-open.sh +25 -66
- package/lib/client.js +30185 -29711
- package/lib/client.js.map +1 -1
- package/lib/index.js +3889 -2336
- package/lib/index.js.map +1 -1
- package/package.json +9 -8
- package/skills/dsh-vscodemode-mcp/SKILL.md +94 -132
- package/src/childEnv.ts +19 -0
- package/src/client/addToConversation.ts +4 -2
- package/src/client/ai/inlineProvider.ts +1 -8
- package/src/client/commandBridge.ts +6 -2
- package/src/client/externalOpen.ts +40 -19
- package/src/client/index.ts +17 -15
- package/src/client/keybindings.ts +18 -1
- package/src/client/monaco/loader.ts +3 -1
- package/src/client/openFlow.ts +36 -28
- package/src/client/openReceipt.ts +64 -0
- package/src/client/panelCommands.ts +101 -0
- package/src/client/pdf/pdfLoader.ts +4 -2
- package/src/client/receiptState.ts +120 -0
- package/src/client/rpc.ts +30 -4
- package/src/client/shortcutsOfficial.ts +39 -4
- package/src/client/sidebarBridge.ts +7 -0
- package/src/client/styles/mcp.css +1 -19
- package/src/client/ui/DiffBadge.ts +2 -1
- package/src/client/ui/EditorView.ts +237 -55
- package/src/client/ui/McpSettings.ts +32 -7
- package/src/client/ui/OfficialSideTab.ts +5 -2
- package/src/client/ui/QuickOpen.ts +10 -9
- package/src/client/ui/commandCatalog.ts +5 -4
- package/src/client/useOpenReceipt.ts +128 -0
- package/src/compat.ts +5 -3
- package/src/dap/manager.ts +46 -13
- package/src/devForm.ts +252 -92
- package/src/externalHandoff.ts +93 -52
- package/src/fileOpenSettings.ts +1 -24
- package/src/hostImport.ts +19 -0
- package/src/httpGuard.ts +101 -0
- package/src/index.ts +106 -97
- package/src/integrate.ts +56 -50
- package/src/lsp/transport.ts +84 -127
- package/src/mcp.ts +58 -9
- package/src/mcpIsolation.ts +211 -102
- package/src/mcpProject.ts +220 -133
- package/src/mcpRuntime.ts +343 -0
- package/src/openBridge.ts +69 -0
- package/src/openInbox.ts +207 -0
- package/src/paths.ts +8 -5
- package/src/processTree.ts +63 -0
- package/src/reveal.ts +5 -3
- package/src/routes.ts +156 -154
- package/src/rpc.ts +14 -13
- package/src/shared/appUrl.ts +19 -0
- package/src/shared/externalOpen.ts +71 -0
- package/src/shared/integration.ts +2 -0
- package/src/shared/keybindings.ts +8 -5
- package/src/shared/mcp.ts +4 -1
- package/src/shared/rpc.ts +7 -4
- package/src/shared/svn.ts +6 -8
- package/src/svnPlanTool.ts +51 -0
- package/src/unityBridge.ts +26 -3
- package/unity/com.dsh.editor/Editor/DshCodeEditor.cs +99 -203
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs +166 -0
- package/unity/com.dsh.editor/Editor/DshOpenWorker.cs.meta +2 -0
- package/unity/com.dsh.editor/README.md +16 -25
- package/unity/com.dsh.editor/package.json +2 -2
- package/src/client/ui/ShortcutSettings.ts +0 -294
package/README.md
CHANGED
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
保存·全部关闭(`Ctrl+F4` 关当前)/ 复制路径·复制相对路径 / 在文件资源管理器
|
|
45
45
|
(视图)中显示。关闭前未保存页签静默落盘;浏览器无法实现的条目(向右拆分 /
|
|
46
46
|
新窗口)不提供。
|
|
47
|
-
-
|
|
48
|
-
|
|
47
|
+
- **工作区搜索填充**:编辑器有选中时执行「工作区搜索」(当前键位见官方快捷键目录),
|
|
48
|
+
选中文本自动填入搜索框并立即搜索(多行选区取首行;填入后全选,便于直接改写)。
|
|
49
49
|
- **Markdown 预览**(v0.5.3):打开 `.md` / `.markdown` 后,工具栏「预览」按钮、
|
|
50
50
|
`Ctrl+Shift+V` 或命令栏「切换 Markdown 预览」切入 GFM 渲染(标题/列表/表格/代码块/
|
|
51
51
|
引用/KaTeX),「以源码打开」切回编辑。渲染走官方 `MarkdownText` 原语,零新增依赖、
|
|
@@ -65,8 +65,9 @@
|
|
|
65
65
|
分类 / 当前键位;需要打开文件的命令在空编辑器时自动隐藏。
|
|
66
66
|
|
|
67
67
|
- **单一数据源**:命令目录(`ui/commandCatalog`)驱动命令栏和官方快捷键目录;
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
键位查看/改绑统一走 DSH 官方快捷键配置(设置 → VSCodeMode → 通用 →
|
|
69
|
+
「打开官方快捷键配置」,或 `Ctrl+/`),插件不再自建录键界面,绑定由官方服务持久化。
|
|
70
|
+
旧版 DSH 没有该服务时继续使用原有窗口按键派发,旧 `keybindings` 设置仍可生效。
|
|
70
71
|
- **开放注册表**:`window.__edrvCommands__` 暴露 `register()` 注册命令;插件
|
|
71
72
|
不再提供独立的运行时键位注册,改用 DSH 官方 shortcuts 配置。
|
|
72
73
|
- **开箱 20 条命令**:保存 / 快速打开 / 在文件浏览器中打开 / 显示所有命令 /
|
|
@@ -166,8 +167,8 @@ cmaps 支持中文渲染);工具条进入注释编辑:✎ 文本框 / 🖌
|
|
|
166
167
|
### 9. 文件管理侧边栏(类 VSCode 活动栏 + 面板)
|
|
167
168
|
|
|
168
169
|
编辑器内嵌活动栏 + 可拖拽调宽面板区;首期「资源管理器」= 懒加载目录树
|
|
169
|
-
(展开实时读取、点文件打开、差异角标、活动文件高亮、`edrv:refresh` / 手动
|
|
170
|
-
|
|
170
|
+
(展开实时读取、点文件打开、差异角标、活动文件高亮、`edrv:refresh` / 手动 ⟳,
|
|
171
|
+
可通过命令栏切换显隐并持久化),侧栏形态默认收起以节省面板宽度。
|
|
171
172
|
|
|
172
173
|
- 拖拽调宽下限 = 最小宽度(默认 300,设置 → VSCodeMode → 通用「编辑器」
|
|
173
174
|
可调 180–560),拖拽低于最小宽度自动收起。
|
|
@@ -209,8 +210,19 @@ cmaps 支持中文渲染);工具条进入注释编辑:✎ 文本框 / 🖌
|
|
|
209
210
|
### 11. MCP 可视化管理(设置 → VSCodeMode)
|
|
210
211
|
|
|
211
212
|
子 Tab「我的 MCP」(profile 全局)+「项目 MCP」(各项目根 `.mcp.json`,
|
|
212
|
-
对齐 Claude Code / Cursor
|
|
213
|
-
|
|
213
|
+
对齐 Claude Code / Cursor,随仓库共享):支持 stdio / streamable-http 的新增、
|
|
214
|
+
刷新、启停、删除和运行状态查看。
|
|
215
|
+
|
|
216
|
+
- **项目作用域**(v0.13.0):工具、资源与服务器指令均按 agent 所属工作区隔离;
|
|
217
|
+
跨工作区子会话不会因继承父 scope 而获得外项目 MCP。全局 MCP 保持全局可用。
|
|
218
|
+
- **实例与状态**:每个活动 agent、每个启用项目服务各有一个插件实例;没有活动
|
|
219
|
+
agent 时只保存配置(`configured`、实例数 0)。`instanceCount` 是挂载实例数,
|
|
220
|
+
`unverified` 表示没有公开连接状态证据,不能把 fiber ACTIVE 当成已连接。
|
|
221
|
+
零工具可能是合法的纯资源服务器。
|
|
222
|
+
- **配置保护**:reconcile 尊重 `disabled`;读取/解析错误保留最后有效连接,
|
|
223
|
+
不把不可读文件当成空配置。保存保留未知字段和未改动的脱敏凭据。
|
|
224
|
+
|
|
225
|
+
完整配置、迁移与排错见随包 [MCP 技能](skills/dsh-vscodemode-mcp/SKILL.md)。
|
|
214
226
|
|
|
215
227
|
### 12. 插件自带技能组(v0.3.3)
|
|
216
228
|
|
|
@@ -274,85 +286,67 @@ DSH 启动回放 `~/.dsh/sessions` 全部会话(V8 展开约 10×,可能 OOM
|
|
|
274
286
|
选择重新加载或用编辑器内容覆盖;冲突未处理期间自动保存被抑制。
|
|
275
287
|
- 老 host(无该 RPC)自动降级为无同步、无护栏,不报错。
|
|
276
288
|
|
|
277
|
-
### 15. 系统集成(v0.1.53
|
|
278
|
-
|
|
279
|
-
设置 → VSCodeMode → 通用 →「系统集成」卡片,两项能力:
|
|
289
|
+
### 15. 系统集成(v0.1.53;v0.13.0 更新打开链路)
|
|
280
290
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
直接打开所选文件;DSH 未运行时弹提示。
|
|
291
|
+
设置 → VSCodeMode → 通用 →「系统集成」卡片提供文件管理器右键菜单与 Unity
|
|
292
|
+
外部脚本编辑器。两者现在共用按 profile 隔离的本机私有 OPEN 请求/ACK 通道,
|
|
293
|
+
无需向 launcher 或 Unity 提供 Host Cookie,也不依赖固定 HTTP 端口。
|
|
285
294
|
|
|
286
|
-
|
|
287
|
-
com.unity.ide.traeCN 机制实现 `IExternalEditor` 虚拟安装,无需本地 exe),
|
|
288
|
-
设置页一键安装 / 更新为 Unity 内嵌包(复制到 `<项目>/Packages/com.dsh.editor`,
|
|
289
|
-
自动发现、不改 manifest.json);Unity Preferences → External Tools 选
|
|
290
|
-
「DSH 文件编辑」后,双击脚本 / Console 报错跳转即在浏览器打开对应文件与行列。
|
|
291
|
-
|
|
292
|
-
**深链契约**(launcher / Unity 包 / client 三端共用):
|
|
293
|
-
|
|
294
|
-
```
|
|
295
|
-
http://127.0.0.1:3080/?edrvOpen=1&edrvPaths=<enc1>[,<enc2>…][&edrvLine=N][&edrvColumn=M]
|
|
296
|
-
```
|
|
295
|
+
**① 文件管理器右键菜单**
|
|
297
296
|
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
- Windows 提供文件、目录、目录空白处三类入口;Linux 提供 Nautilus 脚本与
|
|
298
|
+
Dolphin 服务菜单;macOS 提供 Finder Automator 快速操作配方。
|
|
299
|
+
- **注册和移除必须由用户在设置页显式操作**。插件 reload/更新只升级已注册的
|
|
300
|
+
launcher 文件及配置,保留所选 profile;不会自动重注册或撤销系统菜单。
|
|
301
|
+
卸载插件前如需清理菜单,请先点「移除注册」。
|
|
302
|
+
- Windows 优先使用编译后的 C# launcher,缺少编译器时回退 PowerShell;
|
|
303
|
+
POSIX 使用 Bash launcher。三个入口统一调用随包 `dsh-open.mjs` producer,
|
|
304
|
+
从配置读取所选 profile、私有收件箱和运行程序。
|
|
305
|
+
- 已打开的客户端可以领取请求并就地打开。需要唤起时,Desktop 使用官方
|
|
306
|
+
`dsh://open` 入口,Web 使用用户选定的完整应用地址(含实际端口和部署子路径)。
|
|
307
|
+
**唤起窗口或领取请求本身不代表打开成功**。
|
|
300
308
|
|
|
301
|
-
|
|
302
|
-
文件夹补引用):
|
|
309
|
+
**打开与回执规则**
|
|
303
310
|
|
|
304
|
-
|
|
|
311
|
+
| 阶段 | 行为 |
|
|
305
312
|
|---|---|
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
**Unity 包细节**:包源随插件分发于 `unity/com.dsh.editor/`(`package.json` +
|
|
344
|
-
`Editor/DshCodeEditor.cs` + asmdef,`"unity": "2019.4"` 基线;虚拟安装
|
|
345
|
-
`dsh-editor://vscode-mode`,`OpenProject` → `Application.OpenURL(深链)`,不生成
|
|
346
|
-
csproj);**打开过滤**:仅文本 / 代码类扩展名(白名单 + `EditorSettings.projectGenerationUserExtensions`
|
|
347
|
-
用户自定义扩展)交 DSH 打开,其余返回 `false` 交还 Unity 原生(双击预制体进
|
|
348
|
-
预制体模式、双击场景开场景),对齐官方 `DefaultExternalCodeEditor` 行为;
|
|
349
|
-
安装 / 更新 = 整目录复制 / 替换为 `<项目>/Packages/com.dsh.editor`(目标已存在
|
|
350
|
-
且 name 不是 `com.dsh.editor` 时拒绝覆盖),Unity 已打开时切回窗口自动刷新;
|
|
351
|
-
卸载 = 删除该目录;手动兜底:Package Manager → Add package from disk 选
|
|
352
|
-
`unity/com.dsh.editor`;登记清单存 `~/.dsh/dsh-vscode-mode/unity-projects.json`;
|
|
353
|
-
RPC:`edrv.unity.list / add / remove / install`。
|
|
354
|
-
安全说明:深链可在编辑器中查看任意绝对路径文件(与用户手动打开等价);保存仍
|
|
355
|
-
受会话沙箱 `policyOf` 约束,工作区外保存被拒是预期行为。
|
|
313
|
+
| 投递 | producer 将带请求 id、目标 profile、路径和可选行列的 OPEN 请求写入当前用户私有队列 |
|
|
314
|
+
| 领取 | 客户端领取带 lease 的请求,按工作区/会话路由到文件编辑器 |
|
|
315
|
+
| 确认 | 只有目标路径对应的实际 Monaco model 或预览内容成功就绪,才提交匹配请求与 lease 的成功 ACK |
|
|
316
|
+
| 失败/超时 | 返回失败或未确认;不会把一个已展开的空页签视为成功 |
|
|
317
|
+
|
|
318
|
+
请求按 profile 与请求身份匹配,队列内容受类型、大小、时效和本机权限检查约束;
|
|
319
|
+
producer 只承担「打开」请求,不是通用 RPC 代理。旧 `edrvOpen/edrvPaths` URL
|
|
320
|
+
参数仍作为兼容入口保留,已不是 launcher 和 Unity 的当前主链。
|
|
321
|
+
|
|
322
|
+
文件与目录仍按现有工作区路由:工作区内文件进入相应会话;无会话时可创建会话;
|
|
323
|
+
目录根据是否位于已注册工作区内选择复用或新建。选择取消会结束该请求。行列定位
|
|
324
|
+
与真正加载的目标文件绑定,避免切换文件时把其他 model 的内容当成完成证据。
|
|
325
|
+
|
|
326
|
+
**② Unity 外部脚本编辑器(内嵌包 `com.dsh.editor` 0.3.0)**
|
|
327
|
+
|
|
328
|
+
1. 在设置卡片中选择 Unity 项目,执行安装/更新,将随包 UPM 目录复制到
|
|
329
|
+
`<项目>/Packages/com.dsh.editor`;无需修改项目 manifest。
|
|
330
|
+
2. 首次安装会自动准备当前 profile 的独立 bridge,并写入本机项目提示
|
|
331
|
+
`<项目>/UserSettings/dsh-editor.ini`;**无需先注册系统右键菜单**。
|
|
332
|
+
3. 在 Unity Preferences → External Tools 选择「DSH 文件编辑」。双击文本脚本、
|
|
333
|
+
Console 报错行列或使用打开项目入口,会通过统一 producer 等待 ACK。
|
|
334
|
+
4. Unity 的「DSH 桥接配置」可填写绝对路径覆盖自动选择。优先级为:UI 显式覆盖 →
|
|
335
|
+
项目 UserSettings 提示 → 提示缺失时的旧 shell 配置回退;损坏提示会报错,
|
|
336
|
+
不会静默切换到另一个 profile。
|
|
337
|
+
|
|
338
|
+
UserSettings 提示包含本机 profile 路径,不适合作为团队共享配置。换机器或切换
|
|
339
|
+
profile 后应重新安装/更新桥接,或在 Unity UI 中显式指定对应 bridge 配置。
|
|
340
|
+
|
|
341
|
+
Unity 包保留文本/代码扩展名白名单与用户自定义扩展;Prefab、场景等资产仍交还
|
|
342
|
+
Unity 原生处理,不生成 csproj。Unity API 与 EditorPrefs 在主线程使用;后台任务
|
|
343
|
+
负责 producer 与 IO。安装目标已有同名目录但不是本包时拒绝覆盖,卸载仅删除该包目录。
|
|
344
|
+
|
|
345
|
+
- 管理 RPC:`edrv.integration.status/register/unregister`、`edrv.unity.list/add/remove/install`。
|
|
346
|
+
- 保存文件仍遵循当前会话沙箱策略;外部打开成功不扩大文件写入权限。
|
|
347
|
+
- 当前验证边界:Windows 官方 Desktop 已完成隔离启动与侧栏挂载等检查;Unity
|
|
348
|
+
仅有 C# 编译桩与 9 项 hint 探针,尚未执行真实 Unity EditMode。Linux/macOS
|
|
349
|
+
尚无本轮现场验证。具体完成项与待验收项见 [0.13 发布说明](docs/release-0.13.0.md)。
|
|
356
350
|
|
|
357
351
|
### 16. SVN 集成(v0.5.0)
|
|
358
352
|
|
|
@@ -432,17 +426,19 @@ TortoiseSVN(`TortoiseProc.exe`)仅作 Windows 过渡增强:自研能力覆
|
|
|
432
426
|
|
|
433
427
|
## 安装
|
|
434
428
|
|
|
435
|
-
官方 `dsh plugin`
|
|
429
|
+
官方 `dsh plugin` 方式,三选一。下面以 `web` profile 为例;Desktop 或其他命名
|
|
430
|
+
profile 请替换为实际目标,不要把示例目录当成当前运行 profile。升级前可阅读
|
|
431
|
+
[0.13.0 发布与迁移说明](docs/release-0.13.0.md)。
|
|
436
432
|
|
|
437
433
|
```bash
|
|
438
434
|
# ① Git 安装(clone + prepare 构建;推荐打固定 tag)
|
|
439
|
-
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.
|
|
435
|
+
dsh plugin --profile web add github:Lenonss/DSH_VsCodeMode#v0.13.1
|
|
440
436
|
|
|
441
437
|
# ② npm 注册表(发布到 npm 后)
|
|
442
438
|
dsh plugin --profile web add dsh-vscode-mode
|
|
443
439
|
|
|
444
440
|
# ③ GitHub Release tgz 直装
|
|
445
|
-
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.
|
|
441
|
+
dsh plugin --profile web add https://github.com/Lenonss/DSH_VsCodeMode/releases/download/v0.13.1/dsh-vscode-mode-0.13.1.tgz
|
|
446
442
|
```
|
|
447
443
|
|
|
448
444
|
> `dsh plugin ...` 是 pnpm 转发器:git 安装会克隆仓库、执行该包 `prepare` 脚本
|
|
@@ -479,14 +475,16 @@ bundles 层后重启 DSH 即自动把插件行挂进 loader 树。**不要**再
|
|
|
479
475
|
|
|
480
476
|
- **文件树**:资源管理器面板点击打开;**文件链接**(DSH 0.1.5+):聊天 / 文件树
|
|
481
477
|
里的文件链接直达右侧栏编辑器(含行号定位)。
|
|
482
|
-
-
|
|
483
|
-
`Esc`
|
|
478
|
+
- **快速打开**:执行插件 QuickOpen 命令(键位见「设置 → 快捷键」)——`↑↓` 选择、
|
|
479
|
+
`Enter` 打开高亮项、`Esc` 关闭,鼠标悬停与高亮同步;`Ctrl+P` 由官方文件切换使用。
|
|
484
480
|
- **差异入口**:对话输入框上方 dock 的「差异 N 个文件 · 查看下一个」按钮。
|
|
485
481
|
|
|
486
482
|
### 快捷键速查
|
|
487
483
|
|
|
488
484
|
以下是桌面端常见默认键位;Web / Linux 的浏览器或系统保留键可能不绑定。
|
|
489
|
-
|
|
485
|
+
实际生效键位以 **DSH 官方快捷键配置**为准:设置 → VSCodeMode → 通用 →「打开官方
|
|
486
|
+
快捷键配置」(或 `Ctrl+/`)。插件命令与官方命令同表展示、由官方统一校验冲突;
|
|
487
|
+
旧版 DSH 无官方 shortcuts 时沿用原有键位。
|
|
490
488
|
|
|
491
489
|
| 快捷键 | 功能 |
|
|
492
490
|
|---|---|
|
|
@@ -498,7 +496,7 @@ bundles 层后重启 DSH 即自动把插件行挂进 loader 树。**不要**再
|
|
|
498
496
|
| `F12` / `Shift+F12` | 转到定义 / 查找所有引用 |
|
|
499
497
|
| `Ctrl+点击` / `Ctrl+hover` | 引用导航 / 可导航标识符提示 |
|
|
500
498
|
| 自定义键位 | 切换文件管理侧边栏(`Ctrl+B` 与 DSH 自身侧栏命令冲突,插件默认不绑定) |
|
|
501
|
-
| `Ctrl+Shift+F` |
|
|
499
|
+
| 以当前官方目录为准(Web 采用平台允许的组合) | 工作区搜索(有选中则填入搜索框);不保证所有平台都使用 `Ctrl+Shift+F` |
|
|
502
500
|
| `Ctrl+Shift+V` | 切换 Markdown 预览(仅活动文件为 .md 时生效) |
|
|
503
501
|
| `Ctrl+F4` | 关闭当前页签 |
|
|
504
502
|
| `Alt+←` / `Alt+→` | 后退 / 前进(跨文件恢复焦点位置) |
|
|
@@ -531,17 +529,25 @@ bundles 层后重启 DSH 即自动把插件行挂进 loader 树。**不要**再
|
|
|
531
529
|
|
|
532
530
|
## 配置
|
|
533
531
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
532
|
+
键位配置统一走 DSH 官方快捷键:设置 → VSCodeMode → 通用 →「打开官方快捷键配置」
|
|
533
|
+
(等价 `Ctrl+/`;官方入口不可用时按钮就地给出手动打开方式)。录键、冲突校验、
|
|
534
|
+
按平台默认值、恢复默认都在官方弹窗内完成,插件不再自建录键页面。键位持久化由官方
|
|
535
|
+
服务负责(Desktop:`userData/keybindings.json`;Web:`localStorage` 的
|
|
536
|
+
`dsh.keybindings.v1`)。旧版插件 `keybindings` 配置的单候选自定义值按当前平台
|
|
537
|
+
一次性尝试迁入官方存储;旧配置始终保留供其他平台及人工恢复,多候选、冲突、
|
|
538
|
+
不支持或未绑定的旧值不会被部分迁入或删除。
|
|
539
|
+
|
|
540
|
+
v0.13.0 在 0.12 官方接管的基础上修正真实 `normalized` 绑定与物理 Control/Meta
|
|
541
|
+
的转换;首次打开命令面板/QuickOpen 等入口按当前会话 scope 路由,不要求编辑器先挂载。
|
|
542
|
+
Web 工作区搜索使用官方当前平台允许的三修饰符组合,最终显示与冲突判断以官方目录为准。
|
|
543
|
+
v0.13.1 起插件不再自建录键界面(原独立「快捷键」设置页与共享录制模块已移除),
|
|
544
|
+
改由通用页的「打开官方快捷键配置」入口直达官方弹窗。
|
|
539
545
|
|
|
540
546
|
设置 →「VSCodeMode」页:
|
|
541
547
|
|
|
542
548
|
| 分区 | 说明 |
|
|
543
549
|
|---|---|
|
|
544
|
-
| 通用 | 编辑器宽度(180–560)等 / 系统集成卡片 / 文件链接使用工具 / 开发形态开关 |
|
|
550
|
+
| 通用 | 编辑器宽度(180–560)等 / 系统集成卡片 / 文件链接使用工具 / 开发形态开关 / 官方快捷键配置入口 |
|
|
545
551
|
| 我的 MCP / 项目 MCP | MCP 服务器可视化管理(stdio / streamable-http) |
|
|
546
552
|
| 性能优化 | 会话盘点 / 移出归档 / 压缩调优 / 侧车摘要指引 |
|
|
547
553
|
| 兼容性 | 兼容性报告(外部插件探测、路由 / 重复装配护栏)、插件技能组装载状态、旧版形态引导 |
|
|
@@ -575,21 +581,27 @@ npm pack # 产物 tgz(含 lib/assets/src/cordis.patch.yml)
|
|
|
575
581
|
|
|
576
582
|
### 开发形态(devForm)
|
|
577
583
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
可经 RPC 自动开启 / 关闭:
|
|
584
|
+
开发形态 = 当前运行 profile 以 `link:` 依赖和链接入口指向源码目录。设置 → 通用
|
|
585
|
+
可查看和切换形态;切换成功后需要重启 DSH 才能加载新的 Host 代码。
|
|
581
586
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
587
|
+
- **定位当前 profile**:优先使用官方 `profileContext.dir`;旧宿主仅在依赖本插件的
|
|
588
|
+
profile 候选唯一时回退。服务信息无效或候选有歧义时拒绝切换,不猜测 `web` 目录。
|
|
589
|
+
- **失败恢复**:写入前备份 manifest、lockfile 和原安装入口;安装失败时恢复原内容
|
|
590
|
+
与链接身份。若恢复本身失败,会保留备份位置供处理,不能宣称已经恢复成功。
|
|
591
|
+
- **开发目录校验**:要求绝对路径、正确包身份,且不位于被替换的 profile
|
|
592
|
+
`node_modules` 内;移除链接只处理链接本身,不递归删除源码。
|
|
593
|
+
- **运行程序**:有官方 `profileContext.packageManager` 时使用它提供的程序、参数和
|
|
594
|
+
显式环境(含 Desktop 自带运行方式);旧宿主再回退 PATH。
|
|
595
|
+
|
|
596
|
+
程序化调用继续使用插件已有 RPC,但须经当前已鉴权应用的同源通道;未携带有效
|
|
597
|
+
Host 身份的裸 HTTP/curl 请求不再是支持的接入方式。请求地址应保留应用子路径,
|
|
598
|
+
Desktop 应沿用当前 `dsh-app:` 应用上下文,不拼接固定 `localhost` 端口。
|
|
599
|
+
|
|
600
|
+
| 方法 | `args` | 行为 |
|
|
601
|
+
|---|---|---|
|
|
602
|
+
| `vscode.devFormGet` | `{}` | 读取当前形态,兼容性报告也包含 `devForm` |
|
|
603
|
+
| `vscode.devFormSet` | `{ "enabled": true, "path": "<源码绝对路径>" }` | 开启开发形态;按返回的 `restart` 重启 |
|
|
604
|
+
| `vscode.devFormSet` | `{ "enabled": false }` | 切回当前插件版本的正式依赖;按返回的 `restart` 重启 |
|
|
593
605
|
|
|
594
606
|
### 源码结构
|
|
595
607
|
|
|
@@ -637,8 +649,11 @@ pdf.js vendor + 图标 + launcher)、`skills/`(插件技能组)、`src/`
|
|
|
637
649
|
- **持久化**:工作区旁车 `.dsh-edit-review.json`(version 2,按 cwd 分桶,写前
|
|
638
650
|
合并,v1 自动迁移);归档 `.dsh-edit-review-archive.json`(按 path+batch
|
|
639
651
|
合并批次,含每 hunk 决策与 before)。
|
|
640
|
-
- **RPC
|
|
641
|
-
|
|
652
|
+
- **RPC 与资源路由**:`/edrv` 接口先经官方 `connection.requestRejection(req)`
|
|
653
|
+
校验 Host 身份,认证能力缺失时拒绝请求。RPC 校验方法、JSON content-type、
|
|
654
|
+
envelope 和对象参数,同时检查 Content-Length 与实际读取字节上限;响应不缓存。
|
|
655
|
+
Client 按应用 document base 解析 RPC、资源与 vendor URL,保留反向代理子路径,
|
|
656
|
+
支持 HTTP(S) 和 Desktop 的 `dsh-app:` 来源。
|
|
642
657
|
- **批次 / 融合 / 归档**:每次新 edit/write 递增文件 batch,早于最新批次的未归档
|
|
643
658
|
差异自动"融合"归档;每条差异处理完成立即单条归档;DiffLauncher「归档」页按
|
|
644
659
|
批次浏览 + 回滚。
|
|
@@ -659,8 +674,15 @@ pdf.js vendor + 图标 + launcher)、`skills/`(插件技能组)、`src/`
|
|
|
659
674
|
`client/` 加组件,其余模块零改动;`monaco/*` 是可复用的编辑器服务(资源树 /
|
|
660
675
|
对比 / 诊断面板共用);`edrvSidebarPanels` 注册表承载后续面板(搜索 / 差异 /
|
|
661
676
|
时间线),`edrv.listDir` 为通用目录树 API。
|
|
662
|
-
-
|
|
663
|
-
`
|
|
677
|
+
- **Host 模块来源**:动态加载优先锚定运行中 DSH 的入口安装树;已解析模块的执行
|
|
678
|
+
错误不会被旧开发依赖副本掩盖。本轮官方 Host 审计与现场基线是 `0.1.7-rc.2`,
|
|
679
|
+
依赖声明、源码能力探测和现场验证是不同层次,详见 [版本适配矩阵](docs/version-adaptation.md)。
|
|
680
|
+
- **子进程环境与回收**:LSP/DAP 等共用环境清理,按变量名排除父环境中的
|
|
681
|
+
KEY/PASSWORD/SECRET/TOKEN 与 `DSH_*` 隐式信息;用户显式配置的环境仍会传递。
|
|
682
|
+
Windows 使用进程树终止,POSIX 使用独立进程组;回收是 best effort,不能保证
|
|
683
|
+
清除主动脱离进程组或受系统权限限制的后代进程。
|
|
684
|
+
- **代码更新生效**:Host 改动需要重启 DSH;Client 需要重建产物并刷新原应用页面。
|
|
685
|
+
只有对应开发构建 watcher 和 HMR 接收端同时工作时,才可依赖自动更新。
|
|
664
686
|
|
|
665
687
|
### CI(GitHub Actions)
|
|
666
688
|
|
|
@@ -746,6 +768,20 @@ Keep All / Undo All 却是亮的。修复:单文件 Keep / Undo 覆盖冲突
|
|
|
746
768
|
完整变更见 [GitHub Releases](https://github.com/Lenonss/DSH_VsCodeMode/releases)。
|
|
747
769
|
近期关键版本:
|
|
748
770
|
|
|
771
|
+
- **v0.13.1**:**快捷键入口收敛到官方弹窗**——移除插件自建的独立「快捷键」设置页
|
|
772
|
+
(含行内录键编辑器与共享录制模块 `shortcutRecorder.ts`),改在 VSCodeMode → 通用页
|
|
773
|
+
提供「打开官方快捷键配置」一键跳转(`openOfficialShortcuts()`;官方入口不可用时
|
|
774
|
+
就地给出手动打开方式)。键位查看、录键、冲突校验与恢复默认全部由 DSH 官方弹窗承担,
|
|
775
|
+
避免同一套录键逻辑两处维护;0.13.0 的绑定转换修复与 Web 三修饰符默认值不变。
|
|
776
|
+
- **v0.13.0**:**Host 鉴权与 Desktop 适配、项目 MCP 三面隔离、外部打开回执**——
|
|
777
|
+
`/edrv` 路由接入官方认证与有界请求体读取,URL 保留应用子路径并支持 `dsh-app:`;
|
|
778
|
+
项目 MCP 改为每 agent 挂载,隔离工具/资源/指令,新增配置态、未验证状态与实例数;
|
|
779
|
+
开发形态切换使用实际 `profileContext` 并支持失败恢复;子进程环境清理与进程树
|
|
780
|
+
回收收敛;在 **0.12 已有官方快捷键接管**上修复 normalized 绑定、录制收尾及
|
|
781
|
+
按会话 scope 首开。C#/PowerShell/Bash launcher 与 Unity 0.3.0 共用私有
|
|
782
|
+
OPEN/lease ACK 链路,真正加载目标 model/预览后才确认;Unity 首装自动配置
|
|
783
|
+
profile bridge,无需注册 OS 菜单。迁移、部署、回滚和验证边界见
|
|
784
|
+
[0.13.0 发布说明](docs/release-0.13.0.md)。
|
|
749
785
|
- **v0.12.0**:**用户规则双目录兼容 + 官方快捷键接管**——用户规则同时读取
|
|
750
786
|
`DSH_HOME/rules/` 和 `~/.dsh/rules/`,同名以当前目录为准,旧规则原地管理,
|
|
751
787
|
列表与提示词注入采用同一优先级;快捷键迁移到 DSH 官方 shortcuts 目录及独立设置页,
|
package/assets/shell/dsh-open.cs
CHANGED
|
@@ -1,182 +1,98 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// 流程:读 dsh-open.ini base → TcpClient 探测(1s 超时,未运行弹 MessageBox)
|
|
4
|
-
// → 投递待打开请求到 host(edrv.external.handoff):有已打开 DSH 页面时由其就地
|
|
5
|
-
// 执行打开规则(不重复开页);2s 未领取(页面刚关/后台节流)则取回请求并回退打开新页。
|
|
6
|
-
// 深链契约见插件 src/shared/externalOpen.ts(edrvOpen/edrvPaths/edrvLine/edrvColumn)。
|
|
7
|
-
// 作者 ddj 2026-09-08
|
|
1
|
+
// Windows C# 5 launcher; shared producer owns private queue validation and ACK handling.
|
|
2
|
+
// @author ddj 2026-09-28
|
|
8
3
|
using System;
|
|
9
4
|
using System.Collections.Generic;
|
|
10
5
|
using System.Diagnostics;
|
|
11
6
|
using System.IO;
|
|
12
|
-
using System.Net;
|
|
13
|
-
using System.Net.Sockets;
|
|
14
7
|
using System.Text;
|
|
15
|
-
using System.Threading;
|
|
16
|
-
using System.Web.Script.Serialization;
|
|
17
8
|
using System.Windows.Forms;
|
|
18
9
|
|
|
19
10
|
internal static class DshOpen
|
|
20
11
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
12
|
+
/// <summary>Run the configured producer and report unconfirmed opens. @author ddj 2026-09-28</summary>
|
|
13
|
+
/// <param name="args">Paths supplied by Explorer.</param>
|
|
14
|
+
/// <returns>Zero only when the producer confirms an ACK (or no paths were supplied).</returns>
|
|
24
15
|
[STAThread]
|
|
25
|
-
private static
|
|
16
|
+
private static int Main(string[] args)
|
|
26
17
|
{
|
|
27
|
-
|
|
28
|
-
|
|
18
|
+
if (args.Length == 0) return 0;
|
|
19
|
+
try { return Run(args); }
|
|
20
|
+
catch (Exception error)
|
|
29
21
|
{
|
|
30
|
-
MessageBox.Show(
|
|
31
|
-
|
|
32
|
-
"DSH 文件编辑",
|
|
33
|
-
MessageBoxButtons.OK,
|
|
34
|
-
MessageBoxIcon.Information);
|
|
35
|
-
return;
|
|
22
|
+
MessageBox.Show(error.Message, "DSH 文件编辑", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
23
|
+
return 1;
|
|
36
24
|
}
|
|
37
|
-
List<string> paths = CollectPaths(args);
|
|
38
|
-
if (paths.Count == 0) return;
|
|
39
|
-
if (HandoffAndConfirm(baseUrl, paths)) return;
|
|
40
|
-
OpenUrl(BuildUrl(baseUrl, paths));
|
|
41
25
|
}
|
|
42
26
|
|
|
43
|
-
/// <summary
|
|
44
|
-
|
|
27
|
+
/// <summary>Read installed metadata and wait for the queue producer. @author ddj 2026-09-28</summary>
|
|
28
|
+
/// <param name="paths">Paths to open.</param><returns>Confirmed producer exit status.</returns>
|
|
29
|
+
private static int Run(string[] paths)
|
|
45
30
|
{
|
|
46
|
-
|
|
47
|
-
|
|
31
|
+
string folder = AppDomain.CurrentDomain.BaseDirectory;
|
|
32
|
+
string ini = Path.Combine(folder, "dsh-open.ini");
|
|
33
|
+
Dictionary<string, string> config = ReadIni(ini);
|
|
34
|
+
string executable = RequiredPath(config, "node");
|
|
35
|
+
string helper = RequiredPath(config, "helper");
|
|
36
|
+
string mode;
|
|
37
|
+
if (!config.TryGetValue("nodeMode", out mode) || (mode != "node" && mode != "electron"))
|
|
38
|
+
throw new InvalidDataException("Missing nodeMode in DSH bridge configuration.");
|
|
39
|
+
var arguments = new StringBuilder(Quote(helper) + " --config " + Quote(ini) + " --");
|
|
40
|
+
foreach (string path in paths) arguments.Append(" " + Quote(Path.GetFullPath(path)));
|
|
41
|
+
var start = new ProcessStartInfo(executable, arguments.ToString());
|
|
42
|
+
start.UseShellExecute = false;
|
|
43
|
+
start.CreateNoWindow = true;
|
|
44
|
+
start.RedirectStandardError = true;
|
|
45
|
+
if (mode == "electron") start.EnvironmentVariables["ELECTRON_RUN_AS_NODE"] = "1";
|
|
46
|
+
else start.EnvironmentVariables.Remove("ELECTRON_RUN_AS_NODE");
|
|
47
|
+
using (Process child = Process.Start(start))
|
|
48
48
|
{
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
if (!string.IsNullOrEmpty(path)) paths.Add(path);
|
|
49
|
+
string error = child.StandardError.ReadToEnd();
|
|
50
|
+
child.WaitForExit();
|
|
51
|
+
if (child.ExitCode != 0) throw new IOException(string.IsNullOrWhiteSpace(error) ? "DSH OPEN was not confirmed." : error);
|
|
52
|
+
return 0;
|
|
54
53
|
}
|
|
55
|
-
return paths;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/// <summary>投递待打开请求;返回 true = 已确认由既有 DSH 页面执行(无需开新页)。</summary>
|
|
59
|
-
private static bool HandoffAndConfirm(string baseUrl, List<string> paths)
|
|
60
|
-
{
|
|
61
|
-
string resp = PostRpc(baseUrl, "edrv.external.handoff", "{\"paths\":" + JsonArrayOf(paths) + "}");
|
|
62
|
-
var data = ParseJson(resp);
|
|
63
|
-
if (data == null || ToInt(data, "clients") <= 0) return false;
|
|
64
|
-
Thread.Sleep(2000);
|
|
65
|
-
string token = data.ContainsKey("token") ? data["token"] as string : null;
|
|
66
|
-
string state = PostRpc(baseUrl, "edrv.external.pendingState",
|
|
67
|
-
"{\"token\":" + JsonString(token) + ",\"take\":true}");
|
|
68
|
-
var st = ParseJson(state);
|
|
69
|
-
return st != null && ToBool(st, "delivered");
|
|
70
54
|
}
|
|
71
55
|
|
|
72
|
-
|
|
56
|
+
/// <summary>Read an existing INI; missing metadata fails closed. @author ddj 2026-09-28</summary>
|
|
57
|
+
/// <param name="file">Installed config path.</param><returns>Case-insensitive values.</returns>
|
|
58
|
+
private static Dictionary<string, string> ReadIni(string file)
|
|
73
59
|
{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/// <summary>POST /edrv/rpc;失败返回 null(回退开新页,不阻塞)。</summary>
|
|
78
|
-
private static string PostRpc(string baseUrl, string method, string argsJson)
|
|
79
|
-
{
|
|
80
|
-
try
|
|
60
|
+
var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
61
|
+
foreach (string line in File.ReadAllLines(file, Encoding.UTF8))
|
|
81
62
|
{
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
req.ContentType = "application/json";
|
|
85
|
-
req.Timeout = 4000;
|
|
86
|
-
byte[] body = Encoding.UTF8.GetBytes("{\"method\":\"" + method + "\",\"args\":" + argsJson + "}");
|
|
87
|
-
req.ContentLength = body.Length;
|
|
88
|
-
using (Stream stream = req.GetRequestStream()) stream.Write(body, 0, body.Length);
|
|
89
|
-
using (WebResponse res = req.GetResponse())
|
|
90
|
-
using (StreamReader reader = new StreamReader(res.GetResponseStream(), Encoding.UTF8))
|
|
91
|
-
return reader.ReadToEnd();
|
|
63
|
+
int equal = line.IndexOf('=');
|
|
64
|
+
if (equal > 0) values[line.Substring(0, equal).Trim()] = line.Substring(equal + 1).Trim();
|
|
92
65
|
}
|
|
93
|
-
|
|
66
|
+
return values;
|
|
94
67
|
}
|
|
95
68
|
|
|
96
|
-
/// <summary
|
|
97
|
-
|
|
69
|
+
/// <summary>Require a fully qualified existing file. @author ddj 2026-09-28</summary>
|
|
70
|
+
/// <param name="config">INI values.</param><param name="key">Metadata key.</param><returns>Validated path.</returns>
|
|
71
|
+
private static string RequiredPath(Dictionary<string, string> config, string key)
|
|
98
72
|
{
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
object value;
|
|
107
|
-
return data.TryGetValue(key, out value) ? Convert.ToInt32(value) : 0;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
private static bool ToBool(Dictionary<string, object> data, string key)
|
|
111
|
-
{
|
|
112
|
-
object value;
|
|
113
|
-
return data.TryGetValue(key, out value) && Convert.ToBoolean(value);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private static string JsonString(string value)
|
|
117
|
-
{
|
|
118
|
-
return "\"" + (value ?? string.Empty).Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
private static string JsonArrayOf(List<string> items)
|
|
122
|
-
{
|
|
123
|
-
var parts = new List<string>();
|
|
124
|
-
foreach (string item in items) parts.Add(JsonString(item));
|
|
125
|
-
return "[" + string.Join(",", parts.ToArray()) + "]";
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/// <summary>读取同目录 dsh-open.ini 的 base= 行;缺失或非法回退默认地址。</summary>
|
|
129
|
-
private static string ReadBase()
|
|
130
|
-
{
|
|
131
|
-
try
|
|
132
|
-
{
|
|
133
|
-
string ini = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "dsh-open.ini");
|
|
134
|
-
if (!File.Exists(ini)) return DefaultBase;
|
|
135
|
-
foreach (string line in File.ReadAllLines(ini, Encoding.UTF8))
|
|
136
|
-
{
|
|
137
|
-
string trimmed = line.Trim();
|
|
138
|
-
if (trimmed.StartsWith("base=", StringComparison.OrdinalIgnoreCase) && trimmed.Length > 5)
|
|
139
|
-
{
|
|
140
|
-
string value = trimmed.Substring(5).Trim();
|
|
141
|
-
if (value.Length > 0) return value;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
catch (Exception) { /* ini 读取失败按默认地址 */ }
|
|
146
|
-
return DefaultBase;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/// <summary>TcpClient 探测 base 地址对应端口是否可达(1s 超时)。</summary>
|
|
150
|
-
private static bool ServerAlive(string baseUrl)
|
|
151
|
-
{
|
|
152
|
-
try
|
|
153
|
-
{
|
|
154
|
-
Uri uri = new Uri(baseUrl);
|
|
155
|
-
int port = uri.IsDefaultPort
|
|
156
|
-
? (string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase) ? 443 : 80)
|
|
157
|
-
: uri.Port;
|
|
158
|
-
using (TcpClient client = new TcpClient())
|
|
159
|
-
{
|
|
160
|
-
if (!client.ConnectAsync(uri.Host, port).Wait(1000)) return false;
|
|
161
|
-
return client.Connected;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch (Exception) { return false; }
|
|
73
|
+
string value;
|
|
74
|
+
if (!config.TryGetValue(key, out value) || !Path.IsPathRooted(value) ||
|
|
75
|
+
!string.Equals(Path.GetFullPath(value), value, StringComparison.OrdinalIgnoreCase) || !File.Exists(value))
|
|
76
|
+
throw new InvalidDataException("Missing absolute " + key + " in DSH bridge configuration.");
|
|
77
|
+
if (key == "node" && !string.Equals(Path.GetExtension(value), ".exe", StringComparison.OrdinalIgnoreCase))
|
|
78
|
+
throw new InvalidDataException("DSH requires a stored Node or Electron executable, not a command shim.");
|
|
79
|
+
return value;
|
|
165
80
|
}
|
|
166
81
|
|
|
167
|
-
/// <summary
|
|
168
|
-
|
|
82
|
+
/// <summary>Quote Windows argv with backslash-before-quote handling. @author ddj 2026-09-28</summary>
|
|
83
|
+
/// <param name="value">Literal argument.</param><returns>One command-line argument.</returns>
|
|
84
|
+
private static string Quote(string value)
|
|
169
85
|
{
|
|
170
|
-
|
|
171
|
-
int
|
|
172
|
-
foreach (
|
|
86
|
+
var result = new StringBuilder("\"");
|
|
87
|
+
int slashes = 0;
|
|
88
|
+
foreach (char ch in value)
|
|
173
89
|
{
|
|
174
|
-
if (
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
count++;
|
|
90
|
+
if (ch == '\\') { slashes++; continue; }
|
|
91
|
+
result.Append('\\', ch == '"' ? slashes * 2 + 1 : slashes);
|
|
92
|
+
result.Append(ch);
|
|
93
|
+
slashes = 0;
|
|
179
94
|
}
|
|
180
|
-
|
|
95
|
+
result.Append('\\', slashes * 2);
|
|
96
|
+
return result.Append('"').ToString();
|
|
181
97
|
}
|
|
182
98
|
}
|