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
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react'
|
|
11
11
|
import { rpc } from '../rpc.js'
|
|
12
|
-
import {
|
|
12
|
+
import { bindPanelCommand } from '../panelCommands.js'
|
|
13
|
+
import { ariaOf, chordOf, useKeybindingsVersion } from '../keybindings.js'
|
|
13
14
|
import { stepIndex } from './searchNav.js'
|
|
14
15
|
|
|
15
16
|
/**
|
|
@@ -66,19 +67,18 @@ export function QuickOpen(props) {
|
|
|
66
67
|
}
|
|
67
68
|
// Esc 关闭(仅收起浮窗;历史 capture 行为保留,不参与命令派发、不吞键)
|
|
68
69
|
const onEscape = (e) => {
|
|
69
|
-
if (e.key !== 'Escape') return
|
|
70
|
+
if (e.isComposing || e.keyCode === 229 || e.getModifierState?.('AltGraph') || e.key !== 'Escape') return
|
|
70
71
|
setOpen(false)
|
|
71
72
|
inputRef.current?.blur?.()
|
|
72
73
|
}
|
|
73
|
-
//
|
|
74
|
-
// 派发 `edrv.command.quickOpen` 事件到此处(历史窗口 capture 键位自检移除,防双开)。
|
|
74
|
+
// 官方与旧版键位统一走会话级命令接收器;挂载时只消费本会话的待打开请求。
|
|
75
75
|
window.addEventListener('keydown', onEscape, true)
|
|
76
|
-
|
|
76
|
+
const offCommand = bindPanelCommand('quickOpen', { sessionId }, openBox)
|
|
77
77
|
return () => {
|
|
78
78
|
window.removeEventListener('keydown', onEscape, true)
|
|
79
|
-
|
|
79
|
+
offCommand()
|
|
80
80
|
}
|
|
81
|
-
}, [])
|
|
81
|
+
}, [sessionId])
|
|
82
82
|
|
|
83
83
|
// 高亮项滚入浮窗可视区(候选集或高亮变化时;nearest 避免整页跳动)
|
|
84
84
|
React.useEffect(() => {
|
|
@@ -99,7 +99,7 @@ export function QuickOpen(props) {
|
|
|
99
99
|
* @param e 输入框键盘事件
|
|
100
100
|
*/
|
|
101
101
|
const onInputKey = (e) => {
|
|
102
|
-
if (e.nativeEvent?.isComposing) return
|
|
102
|
+
if (e.nativeEvent?.isComposing || e.nativeEvent?.keyCode === 229 || e.getModifierState?.('AltGraph')) return
|
|
103
103
|
if (e.key === 'Escape') { setOpen(false); return }
|
|
104
104
|
if (!showPop || !list.length) return
|
|
105
105
|
if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
|
|
@@ -135,7 +135,8 @@ export function QuickOpen(props) {
|
|
|
135
135
|
|
|
136
136
|
return React.createElement('div', { className: 'edrv-search-wrap' },
|
|
137
137
|
React.createElement('input', {
|
|
138
|
-
ref: inputRef, className: 'edrv-search', placeholder: '搜索文件
|
|
138
|
+
ref: inputRef, className: 'edrv-search', placeholder: '搜索文件' + (chordOf('edrv.quickOpen') ? ' (' + chordOf('edrv.quickOpen') + ')' : ''),
|
|
139
|
+
'aria-keyshortcuts': ariaOf('edrv.quickOpen'),
|
|
139
140
|
value: q,
|
|
140
141
|
role: 'combobox',
|
|
141
142
|
'aria-expanded': showPop && list.length > 0,
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* 与既有 `edrv:` 刷新/主题事件分属不同命名空间,互不干扰)。
|
|
7
7
|
* 作者 ddj 2026年09月10号
|
|
8
8
|
*/
|
|
9
|
+
import { panelAvailable, runPanelCommand } from '../panelCommands.js'
|
|
9
10
|
import { hasEditorModel, hasOpenTabs } from '../editorModelState.js'
|
|
10
11
|
import { activeEditorPath } from '../activePathStore.js'
|
|
11
12
|
import { isMarkdownPath } from '../markdownPreview.js'
|
|
@@ -201,8 +202,8 @@ export const EDITOR_COMMANDS: readonly CommandDef[] = [
|
|
|
201
202
|
},
|
|
202
203
|
{
|
|
203
204
|
id: 'edrv.quickOpen', label: '快速打开文件', category: '文件', order: 20,
|
|
204
|
-
available:
|
|
205
|
-
run: () =>
|
|
205
|
+
available: () => panelAvailable('quickOpen'),
|
|
206
|
+
run: () => { runPanelCommand('quickOpen') },
|
|
206
207
|
},
|
|
207
208
|
{
|
|
208
209
|
id: 'edrv.toggleSidebar', label: '切换侧边栏', category: '视图', order: 10,
|
|
@@ -211,8 +212,8 @@ export const EDITOR_COMMANDS: readonly CommandDef[] = [
|
|
|
211
212
|
},
|
|
212
213
|
{
|
|
213
214
|
id: 'edrv.searchInFiles', label: '在工作区中搜索', category: '视图', order: 20,
|
|
214
|
-
available:
|
|
215
|
-
run: () =>
|
|
215
|
+
available: () => panelAvailable('searchInFiles'),
|
|
216
|
+
run: () => { runPanelCommand('searchInFiles') },
|
|
216
217
|
},
|
|
217
218
|
{
|
|
218
219
|
id: 'edrv.toggleMarkdownPreview', label: '切换 Markdown 预览', category: '视图', order: 25,
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/** Correlated editor readiness for external file opens. @author ddj 2026年09月28号 */
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import { finishOpen, hasOpen, peekOpen } from './openReceipt.js'
|
|
4
|
+
import type { EditorRequest } from './openReceipt.js'
|
|
5
|
+
|
|
6
|
+
export interface ReceiptHost {
|
|
7
|
+
sessionId: string
|
|
8
|
+
open(request: EditorRequest): void | Promise<void>
|
|
9
|
+
ready(request: EditorRequest): boolean
|
|
10
|
+
error(request: EditorRequest): string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** One mounted editor owns at most one claimed external request. */
|
|
14
|
+
export class OpenMonitor {
|
|
15
|
+
private pending: EditorRequest | undefined
|
|
16
|
+
private frame: number | undefined
|
|
17
|
+
private stopped = false
|
|
18
|
+
private opening = false
|
|
19
|
+
/** @private @author ddj 2026年09月28号 Claim a request arriving after mount. */
|
|
20
|
+
private listener = (): void => { this.claim() }
|
|
21
|
+
|
|
22
|
+
/** @author ddj 2026年09月28号 @param read Read current component state without a stale closure. */
|
|
23
|
+
constructor(private readonly read: () => ReceiptHost) {}
|
|
24
|
+
|
|
25
|
+
/** @public @author ddj 2026年09月28号 Attach the listener and claim a pre-mount request. */
|
|
26
|
+
start(): void {
|
|
27
|
+
window.addEventListener('edrv:open-request', this.listener)
|
|
28
|
+
this.claim()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @public @author ddj 2026年09月28号 Fail claimed work and cancel the frame on unmount. */
|
|
32
|
+
stop(): void {
|
|
33
|
+
this.stopped = true
|
|
34
|
+
window.removeEventListener('edrv:open-request', this.listener)
|
|
35
|
+
if (this.frame !== undefined) cancelAnimationFrame(this.frame)
|
|
36
|
+
this.frame = undefined
|
|
37
|
+
if (this.pending) finishOpen(this.pending.requestId, false, '编辑器已关闭')
|
|
38
|
+
this.pending = undefined
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @private @author ddj 2026年09月28号 Schedule at most one readiness/queue check. */
|
|
42
|
+
private arm(): void {
|
|
43
|
+
if (!this.stopped && this.frame === undefined) this.frame = requestAnimationFrame(this.check)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Settle only the request this instance still owns; queued work runs next frame.
|
|
48
|
+
* @private @author ddj 2026年09月28号
|
|
49
|
+
* @param request Claimed request, captured before asynchronous work.
|
|
50
|
+
* @param success Whether actual readiness was confirmed.
|
|
51
|
+
* @param error Failure description.
|
|
52
|
+
*/
|
|
53
|
+
private finish(request: EditorRequest, success: boolean, error?: string): void {
|
|
54
|
+
if (this.stopped || this.pending !== request) return
|
|
55
|
+
this.pending = undefined
|
|
56
|
+
this.opening = false
|
|
57
|
+
if (hasOpen(request.requestId)) finishOpen(request.requestId, success, error)
|
|
58
|
+
this.arm()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Observe startup without letting a late completion affect a new claim.
|
|
63
|
+
* @private @author ddj 2026年09月28号
|
|
64
|
+
* @param request Startup's request identity.
|
|
65
|
+
* @param operation Optional asynchronous editor startup.
|
|
66
|
+
*/
|
|
67
|
+
private watchOpen(request: EditorRequest, operation: void | Promise<void>): void {
|
|
68
|
+
if (!operation) return
|
|
69
|
+
this.opening = true
|
|
70
|
+
/** @author ddj 2026年09月28号 Release readiness only for this live claim. */
|
|
71
|
+
const ready = (): void => {
|
|
72
|
+
if (!this.stopped && this.pending === request) this.opening = false
|
|
73
|
+
}
|
|
74
|
+
/** @author ddj 2026年09月28号 @param error Correlated startup rejection. */
|
|
75
|
+
const fail = (error: unknown): void => this.finish(request, false, String(error))
|
|
76
|
+
void Promise.resolve(operation).then(ready, fail)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** @private @author ddj 2026年09月28号 Claim this session; exceptions fail the exact request. */
|
|
80
|
+
private claim(): void {
|
|
81
|
+
if (this.stopped || (this.pending && hasOpen(this.pending.requestId))) return
|
|
82
|
+
this.pending = undefined
|
|
83
|
+
this.opening = false
|
|
84
|
+
try {
|
|
85
|
+
const host = this.read()
|
|
86
|
+
const request = peekOpen(host.sessionId)
|
|
87
|
+
if (!request) return
|
|
88
|
+
this.pending = request
|
|
89
|
+
this.watchOpen(request, host.open(request))
|
|
90
|
+
this.arm()
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (this.pending) this.finish(this.pending, false, String(error))
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** @private @author ddj 2026年09月28号 Check readiness; expired claims and exceptions cannot ACK later work. */
|
|
97
|
+
private check = (): void => {
|
|
98
|
+
this.frame = undefined
|
|
99
|
+
if (this.stopped) return
|
|
100
|
+
const request = this.pending
|
|
101
|
+
if (!request || !hasOpen(request.requestId)) { this.pending = undefined; this.claim(); return }
|
|
102
|
+
try {
|
|
103
|
+
const host = this.read()
|
|
104
|
+
if (request.sessionId !== host.sessionId) { this.finish(request, false, '会话已切换'); return }
|
|
105
|
+
if (this.opening) { this.arm(); return }
|
|
106
|
+
const error = host.error(request)
|
|
107
|
+
if (error || host.ready(request)) { this.finish(request, !error, error ?? undefined); return }
|
|
108
|
+
this.arm()
|
|
109
|
+
} catch (error) { this.finish(request, false, String(error)) }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Bind a component while reading its latest model state.
|
|
115
|
+
* @author ddj 2026年09月28号
|
|
116
|
+
* @param host Mounted editor actions and readiness checks.
|
|
117
|
+
*/
|
|
118
|
+
export function useOpenReceipt(host: ReceiptHost): void {
|
|
119
|
+
const latest = React.useRef(host)
|
|
120
|
+
latest.current = host
|
|
121
|
+
/** @author ddj 2026年09月28号 @returns Listener and animation-frame cleanup. */
|
|
122
|
+
function mount(): () => void {
|
|
123
|
+
const monitor = new OpenMonitor(() => latest.current)
|
|
124
|
+
monitor.start()
|
|
125
|
+
return () => monitor.stop()
|
|
126
|
+
}
|
|
127
|
+
React.useEffect(mount, [host.sessionId])
|
|
128
|
+
}
|
package/src/compat.ts
CHANGED
|
@@ -132,8 +132,10 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
|
|
|
132
132
|
]
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。
|
|
136
|
-
|
|
135
|
+
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。
|
|
136
|
+
* 0.1.7-rc.2 已在官方已签名打包 Desktop 上实机装配(认证路由、同源 RPC、项目 MCP
|
|
137
|
+
* 与私有打开队列均生效);更高版本仍按能力探测运行并提示。 */
|
|
138
|
+
const TESTED_DSH_MAX = '0.1.7-rc.2'
|
|
137
139
|
|
|
138
140
|
/** Config volatile 标记起效的版本线(此前设置走 section 安装,与 volatile 无关)。 */
|
|
139
141
|
const CONFIG_VOLATILE_MIN = '0.1.7-alpha.1'
|
|
@@ -210,7 +212,7 @@ export async function buildReport(
|
|
|
210
212
|
guards,
|
|
211
213
|
adapters,
|
|
212
214
|
warnings,
|
|
213
|
-
devForm: readDevForm(),
|
|
215
|
+
devForm: readDevForm(ctx),
|
|
214
216
|
dshVersion,
|
|
215
217
|
}
|
|
216
218
|
}
|
package/src/dap/manager.ts
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
19
19
|
*/
|
|
20
20
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
21
|
+
import { childEnv } from '../childEnv.js'
|
|
22
|
+
import { stopTree } from '../processTree.js'
|
|
21
23
|
import { join } from 'node:path'
|
|
22
24
|
import { createFrameParser, encodeMessage } from '../lsp/jsonrpc.js'
|
|
23
25
|
import { sourceBaseOf, sourceKeyOf, sourcePathOf, normalizeSourcePath } from './sourcePath.js'
|
|
@@ -288,9 +290,18 @@ export class DapSession {
|
|
|
288
290
|
|
|
289
291
|
// ---------------------------------------------------------------- 内部实现
|
|
290
292
|
|
|
291
|
-
/**
|
|
293
|
+
/**
|
|
294
|
+
* 启动独立适配器进程组并接入协议,清理隐式宿主凭据。
|
|
295
|
+
* @private
|
|
296
|
+
* @author ddj 2026年09月28号
|
|
297
|
+
* @param spec 适配器规格
|
|
298
|
+
* @throws spawn 同步失败
|
|
299
|
+
*/
|
|
292
300
|
private spawnAdapter(spec: DapAdapterSpec): void {
|
|
293
|
-
const child = spawn(spec.command, spec.args, {
|
|
301
|
+
const child = spawn(spec.command, spec.args, {
|
|
302
|
+
stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true,
|
|
303
|
+
detached: process.platform !== 'win32', env: this.adapterEnv(),
|
|
304
|
+
})
|
|
294
305
|
this.child = child
|
|
295
306
|
const parser = createFrameParser()
|
|
296
307
|
child.stdout?.on('data', (chunk: Buffer) => {
|
|
@@ -300,11 +311,16 @@ export class DapSession {
|
|
|
300
311
|
log.debug('[dap-adapter] ' + chunk.toString('utf8').trim())
|
|
301
312
|
this.stderrTail = (this.stderrTail + chunk.toString('utf8')).slice(-STDERR_TAIL_CAP)
|
|
302
313
|
})
|
|
303
|
-
child.on('error', (error) =>
|
|
314
|
+
child.on('error', (error) => {
|
|
315
|
+
if (this.child === child) this.fail('适配器进程错误:' + String(error))
|
|
316
|
+
})
|
|
317
|
+
child.stdin?.on('error', (error) => {
|
|
318
|
+
if (this.child === child) this.fail('适配器 stdin 错误:' + String(error))
|
|
319
|
+
})
|
|
304
320
|
child.on('exit', (code) => {
|
|
305
|
-
if (this.child
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
if (this.child !== child) return
|
|
322
|
+
if (this.phase === 'idle' || this.phase === 'terminated') this.killChild()
|
|
323
|
+
else this.fail('适配器退出(code=' + code + ')' + this.stderrSuffix())
|
|
308
324
|
})
|
|
309
325
|
}
|
|
310
326
|
|
|
@@ -529,18 +545,35 @@ export class DapSession {
|
|
|
529
545
|
this.setPhase('terminated')
|
|
530
546
|
}
|
|
531
547
|
|
|
532
|
-
/**
|
|
548
|
+
/**
|
|
549
|
+
* 清理父环境后合并 launch 配置显式 env,null 表示移除该项。
|
|
550
|
+
* @private
|
|
551
|
+
* @author ddj 2026年09月28号
|
|
552
|
+
* @returns 适配器的环境副本
|
|
553
|
+
*/
|
|
554
|
+
private adapterEnv(): NodeJS.ProcessEnv {
|
|
555
|
+
const explicit: NodeJS.ProcessEnv = {}
|
|
556
|
+
const raw = this.config?.raw?.env
|
|
557
|
+
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
558
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
559
|
+
if (typeof value === 'string' || value === null) explicit[key] = value ?? undefined
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return childEnv(explicit)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* 解除会话拥有权并终止适配器进程树;不先杀根进程。
|
|
567
|
+
* @private
|
|
568
|
+
* @author ddj 2026年09月28号
|
|
569
|
+
* @description 同时清理所有待处理请求与计时器。
|
|
570
|
+
*/
|
|
533
571
|
private killChild(): void {
|
|
534
572
|
const child = this.child
|
|
535
573
|
this.child = null
|
|
536
574
|
for (const entry of this.pending.values()) { clearTimeout(entry.timer); entry.resolve(null) }
|
|
537
575
|
this.pending.clear()
|
|
538
|
-
if (
|
|
539
|
-
try { child.kill() } catch { /* 忽略 */ }
|
|
540
|
-
if (process.platform === 'win32' && child.pid) {
|
|
541
|
-
try { spawn('taskkill', ['/PID', String(child.pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }) } catch { /* 忽略 */ }
|
|
542
|
-
}
|
|
543
|
-
setTimeout(() => { try { child.kill('SIGKILL') } catch { /* 忽略 */ } }, 1500).unref?.()
|
|
576
|
+
if (child) stopTree(child, (line) => log.warn('[dap] ' + line))
|
|
544
577
|
}
|
|
545
578
|
|
|
546
579
|
/** 相位切换(running→running 等幂等跳过)。 */
|