dsh-vscode-mode 0.5.2 → 0.6.0
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 +78 -5
- package/lib/client.js +5579 -236
- package/lib/client.js.map +1 -1
- package/lib/index.js +2358 -126
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/client/compat.ts +1 -1
- package/src/client/dap/BpWidget.ts +262 -0
- package/src/client/dap/bpMenu.ts +91 -0
- package/src/client/dap/bpRowMenu.ts +53 -0
- package/src/client/dap/breakpoints.ts +222 -0
- package/src/client/dap/decorate.ts +128 -0
- package/src/client/dap/hintLine.ts +29 -0
- package/src/client/dap/hover.ts +166 -0
- package/src/client/dap/hoverMode.ts +159 -0
- package/src/client/dap/hoverTree.ts +722 -0
- package/src/client/dap/launchInsert.ts +229 -0
- package/src/client/dap/launchSnippetProvider.ts +206 -0
- package/src/client/dap/modelPath.ts +23 -0
- package/src/client/dap/panelSplit.ts +105 -0
- package/src/client/dap/pidPick.ts +24 -0
- package/src/client/dap/store.ts +571 -0
- package/src/client/dap/toolbarDrag.ts +51 -0
- package/src/client/dap/trace.ts +22 -0
- package/src/client/dap/variableTree.ts +62 -0
- package/src/client/editorLimit.ts +31 -0
- package/src/client/index.ts +13 -0
- package/src/client/markdownPreview.ts +24 -0
- package/src/client/md/componentType.ts +28 -0
- package/src/client/md/mdPanel.ts +98 -0
- package/src/client/monaco/lsp/index.ts +15 -0
- package/src/client/monaco/lsp/providers.ts +2 -0
- package/src/client/monaco/theme.ts +15 -0
- package/src/client/searchSeed.ts +64 -0
- package/src/client/sidebar/panels/DebugPanel.ts +613 -0
- package/src/client/sidebar/panels/SearchPanel.ts +70 -15
- package/src/client/sidebar/panels/SvnPanel.ts +167 -23
- package/src/client/sidebar/panels/index.ts +19 -0
- package/src/client/sidebar/types.ts +2 -0
- package/src/client/styles/editor.css +186 -1
- package/src/client/svnActions.ts +18 -1
- package/src/client/svnLog.ts +3 -3
- package/src/client/svnStatus.ts +140 -2
- package/src/client/tabActions.ts +54 -0
- package/src/client/ui/EditorView.ts +711 -29
- package/src/client/ui/McpSettings.ts +29 -0
- package/src/client/ui/SideBySideDiff.tsx +214 -29
- package/src/client/ui/SvnDiffPanel.ts +21 -8
- package/src/client/ui/SvnLogDialog.ts +44 -2
- package/src/client/ui/SvnPatchDialog.ts +63 -0
- package/src/client/ui/SvnSumDialog.ts +77 -0
- package/src/client/ui/commandCatalog.ts +54 -0
- package/src/client/ui/horizontalWheel.ts +85 -0
- package/src/client/ui/svnExport.ts +71 -0
- package/src/client-primitives.d.ts +34 -0
- package/src/dap/configSnippets.ts +269 -0
- package/src/dap/discovery.ts +198 -0
- package/src/dap/launchConfig.ts +127 -0
- package/src/dap/manager.ts +588 -0
- package/src/dap/processList.ts +76 -0
- package/src/dap/protocol.ts +80 -0
- package/src/dap/provider.ts +49 -0
- package/src/dap/rpc.ts +192 -0
- package/src/dap/sourcePath.ts +82 -0
- package/src/fileOpenSettings.ts +3 -0
- package/src/index.ts +7 -3
- package/src/lsp/providers.ts +3 -2
- package/src/reveal.ts +31 -20
- package/src/rpc.ts +11 -3
- package/src/search/ripgrep.ts +127 -11
- package/src/shared/dap.ts +262 -0
- package/src/shared/editorLimit.ts +46 -0
- package/src/shared/keybindings.ts +10 -0
- package/src/shared/rpc.ts +40 -2
- package/src/shared/svn.ts +147 -0
- package/src/shared/svnActions.ts +20 -0
- package/src/svn.ts +320 -11
- package/src/workspace.ts +0 -75
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — DAP(Debug Adapter Protocol)消息纯类型与守卫。
|
|
3
|
+
* 帧格式与 LSP 相同(Content-Length 头),编解码直接复用 lsp/jsonrpc.ts。
|
|
4
|
+
* 消息形状只覆盖本插件消费的子集(DAP 3.x 常用面 + emmylua 适配器扩展)。
|
|
5
|
+
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
6
|
+
*/
|
|
7
|
+
/** DAP 消息基型(请求/响应/事件的公共头)。 */
|
|
8
|
+
export interface DapMessage {
|
|
9
|
+
seq?: number
|
|
10
|
+
type: 'request' | 'response' | 'event'
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** DAP 响应(按 request_seq 关联)。 */
|
|
14
|
+
export interface DapResponse extends DapMessage {
|
|
15
|
+
type: 'response'
|
|
16
|
+
request_seq: number
|
|
17
|
+
success: boolean
|
|
18
|
+
command: string
|
|
19
|
+
message?: string
|
|
20
|
+
body?: unknown
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** DAP 事件。 */
|
|
24
|
+
export interface DapEvent extends DapMessage {
|
|
25
|
+
type: 'event'
|
|
26
|
+
event: string
|
|
27
|
+
body?: unknown
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** DAP 请求(客户端 → 适配器)。 */
|
|
31
|
+
export interface DapRequest extends DapMessage {
|
|
32
|
+
type: 'request'
|
|
33
|
+
command: string
|
|
34
|
+
arguments?: unknown
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 客户端 → 适配器请求帧构造。 */
|
|
38
|
+
export function dapRequest(seq: number, command: string, args?: unknown): DapRequest {
|
|
39
|
+
return args === undefined
|
|
40
|
+
? { seq, type: 'request', command }
|
|
41
|
+
: { seq, type: 'request', command, arguments: args }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 响应守卫。 */
|
|
45
|
+
export function isDapResponse(msg: DapMessage): msg is DapResponse {
|
|
46
|
+
return msg.type === 'response' && typeof (msg as Partial<DapResponse>).request_seq === 'number'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 事件守卫。 */
|
|
50
|
+
export function isDapEvent(msg: DapMessage): msg is DapEvent {
|
|
51
|
+
return msg.type === 'event' && typeof (msg as Partial<DapEvent>).event === 'string'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** 事件体安全取值。 */
|
|
55
|
+
export function eventBody(event: DapEvent): Record<string, unknown> {
|
|
56
|
+
return event.body && typeof event.body === 'object' ? event.body as Record<string, unknown> : {}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 响应体安全取值。 */
|
|
60
|
+
export function responseBody(response: DapResponse): Record<string, unknown> {
|
|
61
|
+
return response.body && typeof response.body === 'object' ? response.body as Record<string, unknown> : {}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* initialize 请求参数(适配器协商口径:path 格式、行列 1 基)。
|
|
66
|
+
* @author ddj 2026年09月29号
|
|
67
|
+
* @param adapterId 适配器 id
|
|
68
|
+
*/
|
|
69
|
+
export function initializeArgs(adapterId: string): Record<string, unknown> {
|
|
70
|
+
return {
|
|
71
|
+
adapterID: adapterId,
|
|
72
|
+
locale: 'zh-cn',
|
|
73
|
+
linesStartAt1: true,
|
|
74
|
+
columnsStartAt1: true,
|
|
75
|
+
pathFormat: 'path',
|
|
76
|
+
supportsVariableType: true,
|
|
77
|
+
supportsVariablePaging: false,
|
|
78
|
+
supportsRunInTerminalRequest: false,
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 调试适配器规格解析:按调试类型从扩展清单声明(discovery)
|
|
3
|
+
* 解析 spawn 规格,通用不限定语言:
|
|
4
|
+
* - runtime='node' → node 跑 js 入口(emmylua 形态,等价旧 process.execPath 硬编码);
|
|
5
|
+
* - 无 runtime → program 即原生可执行(clrdbg/monodbg 形态,args 为清单声明如 --interpreter=vscode);
|
|
6
|
+
* - 其它 runtime 明确不支持(decl.available=false 带原因,不做假启动)。
|
|
7
|
+
* 旧版仅探测 tangzx.emmylua 两型(attach/new),由清单驱动后自然覆盖并扩展到任意适配器。
|
|
8
|
+
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
9
|
+
*/
|
|
10
|
+
import { debuggerDecls } from './discovery.js'
|
|
11
|
+
|
|
12
|
+
/** 适配器启动规格(spawn 参数)。 */
|
|
13
|
+
export interface DapAdapterSpec {
|
|
14
|
+
/** 调试类型(= initialize.adapterID)。 */
|
|
15
|
+
type: string
|
|
16
|
+
/** 可执行文件(node 型 = node 本体;直启型 = 适配器本体)。 */
|
|
17
|
+
command: string
|
|
18
|
+
/** 启动参数(node 型 = [program];直启型 = 清单声明 args)。 */
|
|
19
|
+
args: string[]
|
|
20
|
+
/** 扩展根目录(emmylua 兼容 shim 注入 extensionPath 用)。 */
|
|
21
|
+
extensionPath: string
|
|
22
|
+
/** 来源扩展 id(publisher.name;emmylua 兼容 shim 判定)。 */
|
|
23
|
+
extensionId: string
|
|
24
|
+
/** 可下断点扩展名(断点下发语言过滤;空 = 不限)。 */
|
|
25
|
+
exts: string[]
|
|
26
|
+
/** 探测描述(错误提示用)。 */
|
|
27
|
+
detail: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 解析某调试类型的适配器启动规格。
|
|
32
|
+
* @author ddj 2026年09月29号 / 2026年09月21号
|
|
33
|
+
* @param type 调试类型(launch.json type = 清单 contributes.debuggers.type)
|
|
34
|
+
* @param force 强制重扫扩展清单(测试隔离用)
|
|
35
|
+
* @param home DSH home(缺省真实;测试可注入)
|
|
36
|
+
* @returns 启动规格;类型未发现 / 不可用 / runtime 不支持返回 null(原因见 discovery 声明)
|
|
37
|
+
*/
|
|
38
|
+
export function resolveAdapterSpec(type: string, force = false, home?: string): DapAdapterSpec | null {
|
|
39
|
+
const decl = debuggerDecls(force, home).find((d) => d.type === type)
|
|
40
|
+
if (!decl || !decl.available) return null
|
|
41
|
+
const base = { type, extensionPath: decl.extensionPath, extensionId: decl.extensionId, exts: decl.exts, detail: decl.extensionPath }
|
|
42
|
+
if (decl.runtime === 'node') {
|
|
43
|
+
return { ...base, command: process.execPath, args: [decl.program, ...decl.args] }
|
|
44
|
+
}
|
|
45
|
+
if (!decl.runtime) {
|
|
46
|
+
return { ...base, command: decl.program, args: [...decl.args] }
|
|
47
|
+
}
|
|
48
|
+
return null
|
|
49
|
+
}
|
package/src/dap/rpc.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — 调试 RPC(edrv.dap.*)装配。
|
|
3
|
+
* 单例 DapSession + 适配器发现(扩展清单驱动,通用不限定语言)+ 进程枚举 +
|
|
4
|
+
* launch.json 读取(全量透传)+ findFile 反向匹配(chunkname → ripgrep 定向查找源文件)。
|
|
5
|
+
* 作者 ddj 2026年09月29号 / 2026年09月21号
|
|
6
|
+
*/
|
|
7
|
+
import { join } from 'node:path'
|
|
8
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
9
|
+
import type { Ctx } from '../store.js'
|
|
10
|
+
import type { RpcHandlerMap } from '../shared/rpc.js'
|
|
11
|
+
import { DAP_ACTIONS, DAP_LAUNCH_REL, DAP_LEGACY_LAUNCH_REL, requestOfConfig, type DapAction, type DapDebugConfig } from '../shared/dap.js'
|
|
12
|
+
import { normalizeSourcePath, rankSourceCandidates } from './sourcePath.js'
|
|
13
|
+
import { findFilesByChunk } from '../search/ripgrep.js'
|
|
14
|
+
import { hasCmdPlaceholder, parseLaunchConfigs } from './launchConfig.js'
|
|
15
|
+
import { dapConfigSnippets, annotateSnippets } from './configSnippets.js'
|
|
16
|
+
import { filterProcesses, listProcesses } from './processList.js'
|
|
17
|
+
import { resolveAdapterSpec } from './provider.js'
|
|
18
|
+
import { debuggerDecls } from './discovery.js'
|
|
19
|
+
import { DapSession } from './manager.js'
|
|
20
|
+
import { debugRecord } from '../debugLog.js'
|
|
21
|
+
import { log } from '../log.js'
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 解析工作区根为 subprocess 执行世界可见的真实路径。
|
|
25
|
+
* DAP 会话无 DSH Session 对象,不能让 fs.resolve('.') 落回 host 进程目录,
|
|
26
|
+
* 因此显式以 cwd 为基准解析 `.`(= cwd 本身)。
|
|
27
|
+
* @author ddj 2026年09月21号
|
|
28
|
+
* @param ctx DSH 上下文
|
|
29
|
+
* @param workspacePath 工作区绝对路径
|
|
30
|
+
* @returns 可交给 rg 的根路径;fs 不可用或解析失败返回空串
|
|
31
|
+
*/
|
|
32
|
+
async function rootPathOf(ctx: Ctx, workspacePath: string): Promise<string> {
|
|
33
|
+
const fs = ctx.get('fs')
|
|
34
|
+
if (!fs || !workspacePath) return ''
|
|
35
|
+
try {
|
|
36
|
+
const target = await fs.resolve('.', { cwd: workspacePath })
|
|
37
|
+
return fs.processPath(target) || ''
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return ''
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 创建调试 RPC(单例会话随插件装配创建,ctx.effect 里调 disposeDap 清理)。
|
|
45
|
+
* @author ddj 2026年09月29号
|
|
46
|
+
* @param ctx DSH 上下文
|
|
47
|
+
*/
|
|
48
|
+
export function createDapRpc(ctx: Ctx): { handlers: Partial<RpcHandlerMap>; dispose: () => void } {
|
|
49
|
+
const session = new DapSession({
|
|
50
|
+
findFiles: async (workspacePath, chunk) => {
|
|
51
|
+
const clean = normalizeSourcePath(chunk)
|
|
52
|
+
if (!clean) return []
|
|
53
|
+
// 工作区根需转换成 subprocess 执行世界可见的真实路径,再交给 rg 定向查找
|
|
54
|
+
const root = await rootPathOf(ctx, workspacePath)
|
|
55
|
+
if (!root) {
|
|
56
|
+
debugRecord(ctx, workspacePath, '[DEBUG findFiles] chunk=' + clean + ' root=(无法解析) candidates=0', 'debug')
|
|
57
|
+
return []
|
|
58
|
+
}
|
|
59
|
+
const candidates = await findFilesByChunk(ctx, root, clean)
|
|
60
|
+
const ranked = rankSourceCandidates(clean, candidates, workspacePath)
|
|
61
|
+
log.debug('[dap-trace] findFiles chunk=' + clean + ' cwd=' + workspacePath + ' root=' + root + ' candidates=' + ranked.length + ' first=' + (ranked[0] ?? ''))
|
|
62
|
+
debugRecord(ctx, workspacePath, '[DEBUG findFiles] chunk=' + clean + ' root=' + root + ' candidates=' + ranked.length + ' first=' + (ranked[0] ?? ''), 'debug')
|
|
63
|
+
return ranked
|
|
64
|
+
},
|
|
65
|
+
trace: (workspacePath, message) => debugRecord(ctx, workspacePath, message, 'debug'),
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const handlers: Partial<RpcHandlerMap> = {
|
|
69
|
+
'edrv.dap.configs': async (args) => {
|
|
70
|
+
const configs = await readLaunchConfigs(ctx, args.workspacePath)
|
|
71
|
+
const adapters = debuggerDecls().map((d) => ({ type: d.type, label: d.label, available: d.available, reason: d.reason }))
|
|
72
|
+
return { ok: true, configs, adapters, source: configs.length ? 'launchjson' : 'none' }
|
|
73
|
+
},
|
|
74
|
+
'edrv.dap.snippets': async () => ({
|
|
75
|
+
ok: true,
|
|
76
|
+
// 与工具条同源裁决:不可用适配器的模板带 reason,client 下拉过滤
|
|
77
|
+
snippets: annotateSnippets(dapConfigSnippets(), debuggerDecls()),
|
|
78
|
+
}),
|
|
79
|
+
'edrv.dap.processes': async (args) => {
|
|
80
|
+
const items = filterProcesses(await listProcesses(), args.processName)
|
|
81
|
+
return { ok: true, items }
|
|
82
|
+
},
|
|
83
|
+
'edrv.dap.start': async (args) => {
|
|
84
|
+
const config = args.config
|
|
85
|
+
if (hasCmdPlaceholder(config)) {
|
|
86
|
+
return { ok: false, error: '配置含 ${command:...} 占位(VS Code 命令),本插件无法解析,请在 launch.json 中改为具体值' }
|
|
87
|
+
}
|
|
88
|
+
const spec = resolveAdapterSpec(config.type)
|
|
89
|
+
if (!spec) return { ok: false, error: unknownTypeMessage(config.type) }
|
|
90
|
+
let pid = args.pid && args.pid > 0 ? args.pid : undefined
|
|
91
|
+
if (requestOfConfig(config) === 'attach' && !pid && !config.processId) {
|
|
92
|
+
const resolved = await resolvePid(config.processName)
|
|
93
|
+
if (typeof resolved === 'string') return { ok: false, error: resolved }
|
|
94
|
+
pid = resolved
|
|
95
|
+
}
|
|
96
|
+
session.start(spec, config, args.workspacePath, pid)
|
|
97
|
+
return { ok: true, phase: 'starting' }
|
|
98
|
+
},
|
|
99
|
+
'edrv.dap.stop': async () => {
|
|
100
|
+
session.stop()
|
|
101
|
+
return { ok: true }
|
|
102
|
+
},
|
|
103
|
+
'edrv.dap.poll': async (args) => ({ ok: true, ...session.poll(args.since) }),
|
|
104
|
+
'edrv.dap.setBreakpoints': async (args) => {
|
|
105
|
+
const abs = join(args.workspacePath, args.file.replace(/\\/g, '/'))
|
|
106
|
+
return { ok: true, breakpoints: await session.setBreakpoints(abs, args.points) }
|
|
107
|
+
},
|
|
108
|
+
'edrv.dap.stackTrace': async () => ({ ok: true, frames: await session.stackTrace() }),
|
|
109
|
+
'edrv.dap.scopes': async (args) => ({ ok: true, scopes: await session.scopes(args.frameId) }),
|
|
110
|
+
'edrv.dap.variables': async (args) => ({ ok: true, variables: await session.variables(args.ref) }),
|
|
111
|
+
'edrv.dap.evaluate': async (args) => ({ ok: true, ...(await session.evaluate(args.expression, args.frameId)) }),
|
|
112
|
+
'edrv.dap.command': async (args) => {
|
|
113
|
+
if (!DAP_ACTIONS.includes(args.action as DapAction)) return { ok: false, error: '未知动作:' + args.action }
|
|
114
|
+
if (args.action === 'disconnect') session.stop()
|
|
115
|
+
else session.action(args.action as DapAction)
|
|
116
|
+
return { ok: true }
|
|
117
|
+
},
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { handlers, dispose: () => session.dispose() }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* processName → pid:唯一命中直用;0 命中或多命中返回错误文案(客户端引导用 processes 列表选择)。
|
|
125
|
+
* @author ddj 2026年09月29号
|
|
126
|
+
* @param processName 进程名(标题/文件名包含匹配)
|
|
127
|
+
*/
|
|
128
|
+
async function resolvePid(processName?: string): Promise<number | string> {
|
|
129
|
+
const items = filterProcesses(await listProcesses(), processName)
|
|
130
|
+
if (items.length === 1) return items[0].pid
|
|
131
|
+
if (items.length === 0) return '未找到匹配进程:' + (processName || '(空)')
|
|
132
|
+
return '匹配到 ' + items.length + ' 个进程,请先在进程列表中选择(edrv.dap.processes)'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* 读工作区调试配置:优先 `.dsh/launch.json`(插件专属);缺失时从旧共用
|
|
137
|
+
* `.vscode/launch.json` 一次性迁移(只读 legacy 源、永不回写;复制失败当次
|
|
138
|
+
* 仍用 legacy 内容响应不丢配置,下次读重试);都缺失 → 空表。
|
|
139
|
+
* @author ddj 2026年09月22号
|
|
140
|
+
* @param ctx DSH 上下文
|
|
141
|
+
* @param workspacePath 工作区绝对路径
|
|
142
|
+
* @returns 解析后的配置列表
|
|
143
|
+
*/
|
|
144
|
+
async function readLaunchConfigs(ctx: Ctx, workspacePath: string): Promise<DapDebugConfig[]> {
|
|
145
|
+
const fs = ctx.get('fs')
|
|
146
|
+
if (!fs || !workspacePath) return []
|
|
147
|
+
try {
|
|
148
|
+
const target = await fs.resolve(DAP_LAUNCH_REL, { cwd: workspacePath })
|
|
149
|
+
let text = ''
|
|
150
|
+
let exists = true
|
|
151
|
+
try {
|
|
152
|
+
text = String(await fs.readText(target) ?? '')
|
|
153
|
+
} catch {
|
|
154
|
+
exists = false
|
|
155
|
+
}
|
|
156
|
+
if (exists) return parseLaunchConfigs(text, workspacePath)
|
|
157
|
+
// 新文件缺失 → 读旧共用源:命中则本次以内存内容响应(写入失败不丢配置,下次读重试)
|
|
158
|
+
let legacy = ''
|
|
159
|
+
let legacyExists = true
|
|
160
|
+
try {
|
|
161
|
+
const source = await fs.resolve(DAP_LEGACY_LAUNCH_REL, { cwd: workspacePath })
|
|
162
|
+
legacy = String(await fs.readText(source) ?? '')
|
|
163
|
+
} catch {
|
|
164
|
+
legacyExists = false
|
|
165
|
+
}
|
|
166
|
+
if (!legacyExists) return []
|
|
167
|
+
try {
|
|
168
|
+
const realPath = fs.processPath(target)
|
|
169
|
+
await mkdir(join(realPath, '..'), { recursive: true })
|
|
170
|
+
await writeFile(realPath, legacy, 'utf8')
|
|
171
|
+
log.debug('[dap] 已迁移 ' + DAP_LEGACY_LAUNCH_REL + ' → ' + DAP_LAUNCH_REL + '(' + workspacePath + ')')
|
|
172
|
+
} catch (error) {
|
|
173
|
+
log.debug('[dap] launch.json 迁移失败(下次读重试,本次以旧源响应):' + String(error))
|
|
174
|
+
}
|
|
175
|
+
return parseLaunchConfigs(legacy, workspacePath)
|
|
176
|
+
} catch (error) {
|
|
177
|
+
log.debug('[dap] launch.json 读取失败(视为无配置):' + String(error))
|
|
178
|
+
return []
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 未知调试类型的错误文案:列出已识别类型(含不可用原因),引导用户安装/排查。
|
|
184
|
+
* @author ddj 2026年09月21号
|
|
185
|
+
* @param type launch.json 中的调试类型
|
|
186
|
+
*/
|
|
187
|
+
function unknownTypeMessage(type: string): string {
|
|
188
|
+
const known = debuggerDecls()
|
|
189
|
+
.map((d) => d.type + (d.available ? '' : '(' + (d.reason ?? '不可用') + ')'))
|
|
190
|
+
.join(' / ')
|
|
191
|
+
return '未发现调试类型「' + type + '」的适配器(已识别:' + (known || '无,请安装带调试器的扩展') + ')'
|
|
192
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — DAP source 路径归一化与候选排序。
|
|
3
|
+
* 适配器可能上报绝对路径、工作区相对路径、file:// URI 或 Lua chunkname;
|
|
4
|
+
* 这里统一转换,避免调用栈只显示 rawFile 但无法跳转。
|
|
5
|
+
* 作者 ddj 2026年09月29号
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 统一路径分隔符并去掉 chunkname 的 @ 前缀。 */
|
|
9
|
+
export function normalizeSourcePath(value: unknown): string {
|
|
10
|
+
let text = String(value ?? '').trim()
|
|
11
|
+
if (!text) return ''
|
|
12
|
+
if (/^file:\/\//i.test(text)) text = fileUriPath(text)
|
|
13
|
+
return text.replace(/^@/, '').replace(/\\/g, '/')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** 将 file:// URI 转成本地路径(兼容 Windows 盘符与 UNC)。 */
|
|
17
|
+
function fileUriPath(value: string): string {
|
|
18
|
+
try {
|
|
19
|
+
const url = new URL(value)
|
|
20
|
+
let path = decodeURIComponent(url.pathname)
|
|
21
|
+
if (url.hostname && url.hostname !== 'localhost') path = '//' + url.hostname + path
|
|
22
|
+
if (/^\/[A-Za-z]:/.test(path)) path = path.slice(1)
|
|
23
|
+
return path
|
|
24
|
+
} catch {
|
|
25
|
+
return value.replace(/^file:\/\//i, '')
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 取路径 basename(大小写保持原样)。 */
|
|
30
|
+
export function sourceBaseOf(value: unknown): string {
|
|
31
|
+
const path = normalizeSourcePath(value)
|
|
32
|
+
return path.split('/').pop() ?? ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 规范化路径键(大小写不敏感,供候选缓存使用)。 */
|
|
36
|
+
export function sourceKeyOf(value: unknown): string {
|
|
37
|
+
return normalizeSourcePath(value).toLowerCase()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 把 source 路径转换为工作区相对路径;工作区外路径保留规范化绝对路径,
|
|
42
|
+
* 不再返回空串,调用方仍可交给现有 tabPathOf/absoluteOf 继续处理。
|
|
43
|
+
*/
|
|
44
|
+
export function sourcePathOf(value: unknown, workspacePath: string): string {
|
|
45
|
+
const source = normalizeSourcePath(value)
|
|
46
|
+
if (!source) return ''
|
|
47
|
+
const root = normalizeSourcePath(workspacePath).replace(/\/+$/, '')
|
|
48
|
+
if (root && source.toLowerCase().startsWith((root + '/').toLowerCase())) {
|
|
49
|
+
return source.slice(root.length + 1)
|
|
50
|
+
}
|
|
51
|
+
return source
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 对 findFile 候选按 chunkname 相关度排序:完整路径后缀 > 路径片段 > basename/stem,
|
|
56
|
+
* 同分时保持确定性字典序,避免同名 Lua 文件随机取第一项。
|
|
57
|
+
*/
|
|
58
|
+
export function rankSourceCandidates(chunk: string, files: string[], workspacePath: string): string[] {
|
|
59
|
+
const needle = normalizeSourcePath(chunk).toLowerCase()
|
|
60
|
+
const needleParts = needle.split('/').filter(Boolean)
|
|
61
|
+
const needleBase = sourceBaseOf(needle).toLowerCase()
|
|
62
|
+
const needleStem = needleBase.replace(/\.[^.]+$/, '')
|
|
63
|
+
return [...files].sort((a, b) => scoreSource(b, needle, needleParts, needleBase, needleStem, workspacePath) - scoreSource(a, needle, needleParts, needleBase, needleStem, workspacePath) || a.localeCompare(b))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** 计算单个候选相关度。 */
|
|
67
|
+
function scoreSource(file: string, needle: string, parts: string[], base: string, stem: string, workspacePath: string): number {
|
|
68
|
+
const rel = sourcePathOf(file, workspacePath).toLowerCase()
|
|
69
|
+
const fileBase = sourceBaseOf(rel).toLowerCase()
|
|
70
|
+
const fileStem = fileBase.replace(/\.[^.]+$/, '')
|
|
71
|
+
let score = 0
|
|
72
|
+
if (needle && (rel === needle || rel.endsWith('/' + needle) || needle.endsWith('/' + rel))) score += 100000
|
|
73
|
+
if (base && fileBase === base) score += 10000
|
|
74
|
+
if (stem && fileStem === stem) score += 5000
|
|
75
|
+
const relParts = rel.split('/').filter(Boolean)
|
|
76
|
+
let suffix = 0
|
|
77
|
+
for (let i = 1; i <= Math.min(parts.length, relParts.length); i++) {
|
|
78
|
+
if (parts[parts.length - i] !== relParts[relParts.length - i]) break
|
|
79
|
+
suffix += 1
|
|
80
|
+
}
|
|
81
|
+
return score + suffix * 100
|
|
82
|
+
}
|
package/src/fileOpenSettings.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { createRequire } from 'node:module'
|
|
|
11
11
|
import { pathToFileURL } from 'node:url'
|
|
12
12
|
import type { Ctx } from './store.js'
|
|
13
13
|
import { KEYBINDING_DEFAULTS } from './shared/keybindings.js'
|
|
14
|
+
import { EDITOR_LIMIT_DEFAULT } from './shared/editorLimit.js'
|
|
14
15
|
import { INTEGRATION_BASE_DEFAULT } from './shared/integration.js'
|
|
15
16
|
import { TORTOISE_DIR_DEFAULT } from './shared/svn.js'
|
|
16
17
|
import type { AiConfigPatch, AiConfigView } from './shared/ai.js'
|
|
@@ -340,6 +341,8 @@ export async function installOpenSettingsSection(
|
|
|
340
341
|
fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
|
|
341
342
|
keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
342
343
|
sidebarMinWidth: deps.z.number().default(300),
|
|
344
|
+
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
345
|
+
maxOpenEditors: deps.z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
343
346
|
integrationBaseUrl: deps.z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
344
347
|
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
345
348
|
aiInline: deps.z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,6 @@ import { handleRpc } from './rpc.js'
|
|
|
13
13
|
import { newSearcher } from './search/orchestrator.js'
|
|
14
14
|
import { newContentSearcher } from './search/content.js'
|
|
15
15
|
import { installIsolation } from './mcpIsolation.js'
|
|
16
|
-
import { dropFileIndex } from './workspace.js'
|
|
17
16
|
import { cwdOf } from './registry.js'
|
|
18
17
|
import { setupOpenSettings } from './fileOpenSettings.js'
|
|
19
18
|
import { shellMenuLifecycle } from './integrate.js'
|
|
@@ -27,6 +26,7 @@ import { disposeAllServers, hookExitReclaim } from './lsp/transport.js'
|
|
|
27
26
|
import { createAiRpc } from './ai/rpc.js'
|
|
28
27
|
import { createFileVersions } from './fileVersions.js'
|
|
29
28
|
import { createSvnRpc } from './svn.js'
|
|
29
|
+
import { createDapRpc } from './dap/rpc.js'
|
|
30
30
|
import { installRulesSection } from './rules.js'
|
|
31
31
|
import { installSkillGroup } from './skills.js'
|
|
32
32
|
import type { RpcHandlerMap } from './shared/rpc.js'
|
|
@@ -70,6 +70,9 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
70
70
|
/** SVN RPC(检测/更新/Tortoise 发射;settings 提供 svnPath/tortoisePath)。 */
|
|
71
71
|
const svnRpc = createSvnRpc({ ctx, settings: openSettings })
|
|
72
72
|
const svnHandlers: Partial<RpcHandlerMap> = svnRpc.handlers as Partial<RpcHandlerMap>
|
|
73
|
+
/** 调试 RPC(DAP 桥单例:spawn 扩展适配器 + 事件缓冲 + findFile 反向匹配)。 */
|
|
74
|
+
const dapRpc = createDapRpc(ctx)
|
|
75
|
+
const dapHandlers: Partial<RpcHandlerMap> = dapRpc.handlers
|
|
73
76
|
/** 文件磁盘新鲜度观察器(客户端轮询 edrv.versions;变化时顺带失效目录树缓存)。 */
|
|
74
77
|
const fileVersions = createFileVersions(ctx)
|
|
75
78
|
|
|
@@ -81,7 +84,6 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
81
84
|
const cwd = cwdOf(session as never)
|
|
82
85
|
if (cwd) {
|
|
83
86
|
registry.delete(cwd)
|
|
84
|
-
dropFileIndex(cwd)
|
|
85
87
|
searcher.dispose(cwd)
|
|
86
88
|
contentSearcher.dispose(cwd)
|
|
87
89
|
disposeIndex(cwd)
|
|
@@ -90,7 +92,7 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
90
92
|
if (typeof sid === 'string') lspRpc.disposeSession(sid)
|
|
91
93
|
})
|
|
92
94
|
|
|
93
|
-
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions, svnHandlers), (warning) => warnings.push(warning))
|
|
95
|
+
registerRoutes(ctx, config, (method, args) => handleRpc(ctx, registry, method, args, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions, svnHandlers, dapHandlers), (warning) => warnings.push(warning))
|
|
94
96
|
installIsolation(ctx)
|
|
95
97
|
// 系统集成生命周期:启动自动恢复右键菜单注册(marker 存在时);插件卸载/reload 清理注册痕迹
|
|
96
98
|
ctx.effect(() => shellMenuLifecycle(ctx))
|
|
@@ -104,6 +106,8 @@ export function apply(ctx: Ctx, config?: unknown): void {
|
|
|
104
106
|
})
|
|
105
107
|
// 卸载时清空文件版本基准表(观察器为模块内单例,不清会跨装配残留陈旧版本)
|
|
106
108
|
ctx.effect(() => () => fileVersions.dispose())
|
|
109
|
+
// 卸载/重启时结束调试会话并强杀适配器子进程(防残留注入器/适配器孤儿)
|
|
110
|
+
ctx.effect(() => () => dapRpc.dispose())
|
|
107
111
|
// 宿主进程退出回收:ctx.effect 清理不覆盖进程退出,缺此注册会留下跨重启的孤儿服务器
|
|
108
112
|
hookExitReclaim()
|
|
109
113
|
|
package/src/lsp/providers.ts
CHANGED
|
@@ -214,8 +214,9 @@ export function sdkEnvOf(sdkRoot: string): Record<string, string> {
|
|
|
214
214
|
return env
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
-
/** 读扩展目录 package.json 的 version(读取失败不影响入口使用,返回 undefined)。
|
|
218
|
-
|
|
217
|
+
/** 读扩展目录 package.json 的 version(读取失败不影响入口使用,返回 undefined)。
|
|
218
|
+
* 导出供 dap/discovery 复用(扩展清单版本排序同口径,避免第二份实现)。 */
|
|
219
|
+
export function manifestVersionOf(extDir: string): string | undefined {
|
|
219
220
|
try {
|
|
220
221
|
const manifest = JSON.parse(readFileSync(join(extDir, 'package.json'), 'utf8')) as { version?: unknown }
|
|
221
222
|
return typeof manifest.version === 'string' ? manifest.version : undefined
|
package/src/reveal.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-vscode-mode host — 在 OS 文件浏览器中打开/定位路径(reveal 能力)。
|
|
3
|
-
* 纯函数 revealCommand 平台分发 +
|
|
4
|
-
* (argv 数组、无 shell 插值,沿 workspace/revert 的 subprocess 契约)。
|
|
3
|
+
* 纯函数 revealCommand 平台分发 + revealSpawnOpts 启动选项 + revealInExplorer 发射。
|
|
5
4
|
* 文件 → 资源管理器选中定位;目录 → 打开目录;Linux 无通用定位协议 → 打开所在目录。
|
|
6
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* 刻意**不经** ctx.subprocess.spawn:DSH 的 Windows Job runner 硬编码
|
|
7
|
+
* `windowsHide: true`(dsh-subprocess-local 的 launchWindowsJob 与 runner 内部 spawn),
|
|
8
|
+
* 连 explorer.exe 的 GUI 窗口一并隐藏——RPC 回 ok:true 但窗口根本不出现。
|
|
9
|
+
* GUI 拉起属 fire-and-forget,用 node child_process + detached + unref 才贴合语义。
|
|
10
|
+
* 作者 ddj 2026年08月27号 / 2026年09月20号
|
|
7
11
|
*/
|
|
12
|
+
import { spawn } from 'node:child_process'
|
|
8
13
|
import { dirname } from 'node:path'
|
|
9
|
-
import type { Ctx } from './store.js'
|
|
10
14
|
|
|
11
15
|
/** reveal 结果(沿 revert.ts 的 Result 风格)。 */
|
|
12
16
|
export type RevealResult = { ok: true } | { ok: false; error: string }
|
|
@@ -35,28 +39,35 @@ export function revealCommand(absPath: string, isDir: boolean, platform: NodeJS.
|
|
|
35
39
|
}
|
|
36
40
|
|
|
37
41
|
/**
|
|
38
|
-
*
|
|
39
|
-
* Explorer
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
42
|
+
* 构造 opener 子进程启动选项。
|
|
43
|
+
* `windowsHide` 必须为 **false**:置 true 会把 Explorer 等 GUI 窗口一起隐藏,
|
|
44
|
+
* 症状即「RPC 回 ok:true 但窗口不出现」(本文件头注释记录的回归)。
|
|
45
|
+
* @author ddj 2026年09月20号
|
|
46
|
+
* @param cwd 子进程工作目录(打开文件时用其父目录)
|
|
47
|
+
* @returns child_process.spawn 选项
|
|
48
|
+
*/
|
|
49
|
+
export function revealSpawnOpts(cwd: string): { cwd: string; stdio: 'ignore'; windowsHide: false; detached: true } {
|
|
50
|
+
return { cwd, stdio: 'ignore', windowsHide: false, detached: true }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 发射 opener 拉起 OS 文件浏览器(fire-and-forget 语义)。
|
|
55
|
+
* Explorer 为 GUI 分离进程,非零退出码不视为失败,故只等 spawn 事件即返回;
|
|
56
|
+
* 子进程脱离父进程组并 unref,避免阻塞宿主退出。
|
|
57
|
+
* @author ddj 2026年08月27号 / 2026年09月20号
|
|
42
58
|
* @param absPath 绝对路径
|
|
43
59
|
* @param isDir 是否为目录
|
|
44
60
|
* @returns 成功或失败原因
|
|
45
61
|
*/
|
|
46
|
-
export async function revealInExplorer(
|
|
47
|
-
const
|
|
48
|
-
if (!
|
|
49
|
-
return { ok: false, error: '打开文件浏览器不可用:缺少 subprocess 服务' }
|
|
50
|
-
}
|
|
51
|
-
const { argv } = revealCommand(absPath, isDir)
|
|
62
|
+
export async function revealInExplorer(absPath: string, isDir: boolean): Promise<RevealResult> {
|
|
63
|
+
const [program, ...args] = revealCommand(absPath, isDir).argv
|
|
64
|
+
if (!program) return { ok: false, error: '打开失败:opener 命令为空' }
|
|
52
65
|
try {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
graceMs: 10000,
|
|
66
|
+
await new Promise<void>((resolve, reject) => {
|
|
67
|
+
const child = spawn(program, args, revealSpawnOpts(dirname(absPath)))
|
|
68
|
+
child.once('spawn', () => { child.unref(); resolve() })
|
|
69
|
+
child.once('error', (error) => { reject(error) })
|
|
58
70
|
})
|
|
59
|
-
await handle.done
|
|
60
71
|
return { ok: true }
|
|
61
72
|
} catch (error) {
|
|
62
73
|
return { ok: false, error: '打开失败:' + String(error) }
|
package/src/rpc.ts
CHANGED
|
@@ -256,6 +256,7 @@ export function buildHandlers(
|
|
|
256
256
|
aiHandlers?: Partial<RpcHandlerMap>,
|
|
257
257
|
fileVersions?: FileVersions,
|
|
258
258
|
svnHandlers?: Partial<RpcHandlerMap>,
|
|
259
|
+
dapHandlers?: Partial<RpcHandlerMap>,
|
|
259
260
|
): RpcHandlerMap {
|
|
260
261
|
return {
|
|
261
262
|
// edrv.lsp.* 由 createLspRpc 一次性提供(tracker 跨请求保留),这里并入。
|
|
@@ -264,6 +265,8 @@ export function buildHandlers(
|
|
|
264
265
|
...((aiHandlers ?? {}) as RpcHandlerMap),
|
|
265
266
|
// svn.* 由 createSvnRpc 提供(SVN 检测/更新/Tortoise 发射),这里并入。
|
|
266
267
|
...((svnHandlers ?? {}) as RpcHandlerMap),
|
|
268
|
+
// edrv.dap.* 由 createDapRpc 提供(调试会话单例),这里并入。
|
|
269
|
+
...((dapHandlers ?? {}) as RpcHandlerMap),
|
|
267
270
|
'edrv.list': async (args) => {
|
|
268
271
|
const sc = await requireSession(ctx, args.sessionId)
|
|
269
272
|
if ('err' in sc) return { ok: false, error: sc.err }
|
|
@@ -333,6 +336,7 @@ export function buildHandlers(
|
|
|
333
336
|
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
334
337
|
try {
|
|
335
338
|
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
339
|
+
debugRecord(ctx, sc.cwd, '[DEBUG path.resolve] input=' + String(args.path ?? '') + ' resolved=' + fs.processPath(target), 'debug')
|
|
336
340
|
const info = await fs.stat(target)
|
|
337
341
|
if (!info || info.type !== 'file') {
|
|
338
342
|
// 带上解析后的真实路径:跳转失败时可直接看出是路径解析错还是目标本身不存在
|
|
@@ -366,6 +370,7 @@ export function buildHandlers(
|
|
|
366
370
|
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
367
371
|
try {
|
|
368
372
|
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
373
|
+
debugRecord(ctx, sc.cwd, '[DEBUG path.resolve] input=' + String(args.path ?? '') + ' resolved=' + fs.processPath(target), 'debug')
|
|
369
374
|
const info = await fs.stat(target)
|
|
370
375
|
if (!info || info.type !== 'file') return { ok: false, error: '文件不存在' }
|
|
371
376
|
if ((info.size ?? 0) > READ_CAP) return { ok: false, error: '文件过大(>8MB),不支持差异重建' }
|
|
@@ -399,6 +404,7 @@ export function buildHandlers(
|
|
|
399
404
|
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
400
405
|
try {
|
|
401
406
|
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
407
|
+
debugRecord(ctx, sc.cwd, '[DEBUG path.resolve] input=' + String(args.path ?? '') + ' resolved=' + fs.processPath(target), 'debug')
|
|
402
408
|
// 版本守卫(客户端带回上次读取的版本):文件在读取后被外部改过则拒绝写入,
|
|
403
409
|
// 避免用陈旧缓冲静默覆盖外部改动;客户端未带 rev(旧版/无版本后端)时行为不变。
|
|
404
410
|
const rev = typeof args.rev === 'string' && args.rev ? args.rev : null
|
|
@@ -421,6 +427,7 @@ export function buildHandlers(
|
|
|
421
427
|
if (!fs) return { ok: false, error: '缺少 fs' }
|
|
422
428
|
try {
|
|
423
429
|
const target = await resolveTarget(ctx, sc.session, args.path)
|
|
430
|
+
debugRecord(ctx, sc.cwd, '[DEBUG path.resolve] input=' + String(args.path ?? '') + ' resolved=' + fs.processPath(target), 'debug')
|
|
424
431
|
const info = await fs.stat(target)
|
|
425
432
|
if (!info || info.type !== 'file') return { ok: false, error: '文件不存在' }
|
|
426
433
|
// fs 服务仅提供文本写(writeText/editText),二进制回写经 node:fs 直写解析后的
|
|
@@ -540,7 +547,7 @@ export function buildHandlers(
|
|
|
540
547
|
},
|
|
541
548
|
'edrv.dlog.reveal': async () => {
|
|
542
549
|
// 打开日志根目录(OS 文件管理器;目录形态 → 直接打开)
|
|
543
|
-
const revealed = await revealInExplorer(
|
|
550
|
+
const revealed = await revealInExplorer(pluginLogRoot(), true)
|
|
544
551
|
if (!revealed.ok) return { ok: false, error: revealed.error }
|
|
545
552
|
return { ok: true, opened: true }
|
|
546
553
|
},
|
|
@@ -598,7 +605,7 @@ export function buildHandlers(
|
|
|
598
605
|
const info = await fs.stat(target)
|
|
599
606
|
if (!info) return { ok: false, error: '路径不存在' }
|
|
600
607
|
const abs = fs.processPath(target)
|
|
601
|
-
const outcome = await revealInExplorer(
|
|
608
|
+
const outcome = await revealInExplorer(abs, info.type === 'directory')
|
|
602
609
|
if (!outcome.ok) return { ok: false, error: outcome.error }
|
|
603
610
|
return { ok: true, revealed: abs }
|
|
604
611
|
} catch (error) {
|
|
@@ -951,8 +958,9 @@ export async function handleRpc<M extends RpcMethod>(
|
|
|
951
958
|
aiHandlers?: Partial<RpcHandlerMap>,
|
|
952
959
|
fileVersions?: FileVersions,
|
|
953
960
|
svnHandlers?: Partial<RpcHandlerMap>,
|
|
961
|
+
dapHandlers?: Partial<RpcHandlerMap>,
|
|
954
962
|
): Promise<RpcResult<M>> {
|
|
955
|
-
const handlers = buildHandlers(ctx, registry, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions, svnHandlers)
|
|
963
|
+
const handlers = buildHandlers(ctx, registry, searcher, contentSearcher, lspHandlers, aiHandlers, fileVersions, svnHandlers, dapHandlers)
|
|
956
964
|
const handler = handlers[method]
|
|
957
965
|
if (!handler) return { ok: false, error: '未知方法: ' + String(method) } as RpcResult<M>
|
|
958
966
|
return handler(args)
|