dsh-vscode-mode 0.6.0 → 0.8.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 +40 -6
- package/assets/icon.svg +7 -0
- package/cordis.patch.yml +12 -0
- package/lib/client.js +9317 -7494
- package/lib/client.js.map +1 -1
- package/lib/index.js +1295 -173
- package/lib/index.js.map +1 -1
- package/locale/en.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +3 -1
- package/src/client/compat.ts +134 -4
- package/src/client/copyText.ts +28 -0
- package/src/client/fileClipboard.ts +46 -0
- package/src/client/imagePreview.ts +76 -2
- package/src/client/index.ts +68 -39
- package/src/client/monaco/lsp/lspClient.ts +46 -0
- package/src/client/monaco/lsp/providers.ts +245 -1
- package/src/client/nativeOpenStore.ts +91 -0
- package/src/client/officialSidebar.ts +11 -45
- package/src/client/openWithDialog.ts +66 -0
- package/src/client/outline/index.ts +1 -1
- package/src/client/pdf/pdfPanel.ts +18 -0
- package/src/client/searchSeed.ts +36 -0
- package/src/client/settingsContext.ts +6 -4
- package/src/client/sidebar/contextMenu.ts +51 -10
- package/src/client/sidebar/menuItems.ts +473 -50
- package/src/client/sidebar/panels/FileExplorer.ts +1 -1
- package/src/client/sidebar/panels/SearchPanel.ts +24 -3
- package/src/client/sidebar/panels/SvnPanel.ts +1 -1
- package/src/client/sidebar/panels/index.ts +1 -1
- package/src/client/sidebar/types.ts +6 -0
- package/src/client/styles/editor.css +5 -3
- package/src/client/svnStore.ts +6 -1
- package/src/client/tabActions.ts +22 -10
- package/src/client/ui/EditorView.ts +198 -25
- package/src/client/ui/McpSettings.ts +30 -0
- package/src/client/ui/OfficialSideTab.ts +1 -0
- package/src/client/ui/PerfSettings.ts +4 -1
- package/src/client/ui/PromptDialog.ts +63 -0
- package/src/client/ui/SideEditorTab.ts +1 -0
- package/src/client/ui/commandCatalog.ts +14 -0
- package/src/client/ui/icons.ts +72 -0
- package/src/compat.ts +2 -2
- package/src/dshVersion.ts +2 -1
- package/src/fileOpenSettings.ts +163 -40
- package/src/index.ts +15 -1
- package/src/lsp/client.ts +24 -0
- package/src/lsp/rpc.ts +57 -0
- package/src/lsp/server.ts +272 -2
- package/src/perf.ts +90 -4
- package/src/routes.ts +13 -1
- package/src/rpc.ts +294 -36
- package/src/shared/fsNames.ts +115 -0
- package/src/shared/keybindings.ts +2 -0
- package/src/shared/lsp.ts +79 -0
- package/src/shared/nativeOpen.ts +91 -0
- package/src/shared/rpc.ts +158 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* dsh-vscode-mode client — 名称输入弹窗(新建文件/新建文件夹/重命名共用)。
|
|
4
|
+
* 复用 ModalShell(.vsm-mcp-dialog 视觉 + data-edrv-view 作用域 + Esc/遮罩关闭);
|
|
5
|
+
* 确认返回输入文本(首尾空白已去),取消/Esc/遮罩/空输入返回 null。
|
|
6
|
+
* 重命名时默认选中主名(不含扩展名),与 VS Code 行为一致。
|
|
7
|
+
* 作者 ddj 2026年09月22号
|
|
8
|
+
*/
|
|
9
|
+
import React from 'react'
|
|
10
|
+
import { createRoot } from 'react-dom/client'
|
|
11
|
+
import { ModalShell } from './ModalShell.js'
|
|
12
|
+
|
|
13
|
+
/** 输入弹窗卡片(ModalShell 内容)。 */
|
|
14
|
+
function PromptCard({ title, initial, done }) {
|
|
15
|
+
const inputRef = React.useRef(null)
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
const el = inputRef.current
|
|
18
|
+
if (!el) return
|
|
19
|
+
el.focus()
|
|
20
|
+
const dot = String(initial).lastIndexOf('.')
|
|
21
|
+
el.setSelectionRange(0, dot > 0 ? dot : String(initial).length)
|
|
22
|
+
}, [])
|
|
23
|
+
const submit = () => done(String(inputRef.current?.value ?? '').trim())
|
|
24
|
+
return React.createElement(React.Fragment, null,
|
|
25
|
+
React.createElement('h3', null, title),
|
|
26
|
+
React.createElement('label', null, '名称',
|
|
27
|
+
React.createElement('input', {
|
|
28
|
+
ref: inputRef,
|
|
29
|
+
defaultValue: initial,
|
|
30
|
+
onKeyDown: (event) => {
|
|
31
|
+
if (event.key !== 'Enter') return
|
|
32
|
+
event.preventDefault()
|
|
33
|
+
submit()
|
|
34
|
+
},
|
|
35
|
+
})),
|
|
36
|
+
React.createElement('div', { className: 'vsm-mcp-dialog-actions' },
|
|
37
|
+
React.createElement('button', { onClick: () => done(null) }, '取消'),
|
|
38
|
+
React.createElement('button', { className: 'vsm-primary', onClick: submit }, '确定')))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 弹出名称输入弹窗。
|
|
43
|
+
* @author ddj 2026年09月22号
|
|
44
|
+
* @param title 弹窗标题
|
|
45
|
+
* @param initial 初始名称(重命名预填原名;新建为空串)
|
|
46
|
+
* @returns 用户输入(已去首尾空白);取消/空输入返回 null
|
|
47
|
+
*/
|
|
48
|
+
export function promptName(title, initial) {
|
|
49
|
+
return new Promise((resolve) => {
|
|
50
|
+
const host = document.createElement('div')
|
|
51
|
+
document.body.appendChild(host)
|
|
52
|
+
const root = createRoot(host)
|
|
53
|
+
const done = (value) => {
|
|
54
|
+
root.unmount()
|
|
55
|
+
host.remove()
|
|
56
|
+
resolve(value ? value : null)
|
|
57
|
+
}
|
|
58
|
+
root.render(React.createElement(ModalShell, {
|
|
59
|
+
onClose: () => done(null),
|
|
60
|
+
width: 'min(420px, calc(100vw - 40px))',
|
|
61
|
+
}, React.createElement(PromptCard, { title, initial: String(initial ?? ''), done })))
|
|
62
|
+
})
|
|
63
|
+
}
|
|
@@ -80,6 +80,19 @@ function prevEditorRowDef(): CommandDef {
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* 转到行(转发 Monaco 原生 gotoLine widget;补命令栏与快捷键设置页入口)。
|
|
85
|
+
* @author ddj 2026年09月22号
|
|
86
|
+
* @returns 命令定义
|
|
87
|
+
*/
|
|
88
|
+
function gotoLineDef(): CommandDef {
|
|
89
|
+
return {
|
|
90
|
+
id: 'edrv.goToLine', label: '转到行', category: '导航', order: 45,
|
|
91
|
+
keybinding: 'Ctrl+G', available: needsModel,
|
|
92
|
+
run: () => emit('goToLine'),
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
83
96
|
/**
|
|
84
97
|
* 添加选中内容为引用(把当前选区追加进对话输入框;无选区则状态栏提示)。
|
|
85
98
|
* 有活动编辑器模型才可用,保证对话框内按 Ctrl+U 不被本命令吞掉。
|
|
@@ -317,6 +330,7 @@ function debugPaletteDefs(): CommandDef[] {
|
|
|
317
330
|
export const BRIDGE_COMMANDS: readonly CommandDef[] = [
|
|
318
331
|
nextEditorRowDef(),
|
|
319
332
|
prevEditorRowDef(),
|
|
333
|
+
gotoLineDef(),
|
|
320
334
|
addSelectionRefDef(),
|
|
321
335
|
closeTabDef(),
|
|
322
336
|
...debugPaletteDefs(),
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode client — UI 原语图标跨版本出口(消费方导出名保持 ≤0.1.6 旧名)。
|
|
3
|
+
* 背景:DSH 0.1.7 图标命名重构——`Icon<Name>16`(0.1.6 及更旧)→
|
|
4
|
+
* `Icon<Name>Regular/Medium`(0.1.7+,size 参数化 + 双笔画档),旧名在 0.1.7
|
|
5
|
+
* 全树已不存在(整树 grep 实证)。解析策略:namespace import 属性探测,旧名优先
|
|
6
|
+
* (现网 0.1.6)→ 新名次之(0.1.7+)→ 皆缺走通用占位(第三态不炸渲染);
|
|
7
|
+
* 打包期 external 不校验成员存在性,两代运行时均稳(能力探测,不做版本判定)。
|
|
8
|
+
* 调用点零语义变化:导出名与旧垫片一致,仅改 import 来源。
|
|
9
|
+
* @author ddj 2026年09月22号
|
|
10
|
+
*/
|
|
11
|
+
import React from 'react'
|
|
12
|
+
import * as primitives from '@deepseek-ai/dsh-client-ui-primitives'
|
|
13
|
+
import type { EdrvIconProps } from '@deepseek-ai/dsh-client-ui-primitives'
|
|
14
|
+
|
|
15
|
+
/** 图标组件形状(与垫片 EdrvIconProps 兼容)。 */
|
|
16
|
+
type IconComponent = (props: EdrvIconProps) => unknown
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 通用占位图标(两代皆缺的第三态兜底:空态方框,不抛错不白屏)。
|
|
20
|
+
* @author ddj 2026年09月22号
|
|
21
|
+
* @param props 图标 props(size/className 透传)
|
|
22
|
+
* @returns React 元素
|
|
23
|
+
*/
|
|
24
|
+
function fallbackIcon(props: EdrvIconProps): unknown {
|
|
25
|
+
return React.createElement('svg', {
|
|
26
|
+
width: props.size ?? 16,
|
|
27
|
+
height: props.size ?? 16,
|
|
28
|
+
viewBox: '0 0 16 16',
|
|
29
|
+
className: props.className,
|
|
30
|
+
'aria-hidden': true,
|
|
31
|
+
focusable: false,
|
|
32
|
+
}, React.createElement('rect', {
|
|
33
|
+
x: 3.5, y: 3.5, width: 9, height: 9, rx: 2,
|
|
34
|
+
fill: 'none', stroke: 'currentColor', strokeWidth: 1.2, opacity: 0.45,
|
|
35
|
+
}))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 按候选名序列解析图标组件(首个存在的函数命中;皆缺返回占位)。
|
|
40
|
+
* @author ddj 2026年09月22号
|
|
41
|
+
* @param names 候选导出名(旧名在前、新名在后)
|
|
42
|
+
* @returns 图标组件
|
|
43
|
+
*/
|
|
44
|
+
function iconOf(...names: string[]): IconComponent {
|
|
45
|
+
const table = primitives as unknown as Record<string, unknown>
|
|
46
|
+
for (const name of names) {
|
|
47
|
+
const hit = table[name]
|
|
48
|
+
if (typeof hit === 'function') return hit as IconComponent
|
|
49
|
+
}
|
|
50
|
+
return fallbackIcon
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 两代语义等价的图标对(旧名 → 0.1.7 Regular 档;Medium 档不用于本插件 16px 场景)
|
|
54
|
+
/** 目录闭合图标(官方文件树同款)。 */
|
|
55
|
+
export const IconFolderClose16 = iconOf('IconFolderClose16', 'IconFolderCloseRegular')
|
|
56
|
+
/** 目录展开图标(官方文件树同款)。 */
|
|
57
|
+
export const IconFolderOpen16 = iconOf('IconFolderOpen16', 'IconFolderOpenRegular')
|
|
58
|
+
/** 目录轮廓图标(活动栏「文件管理」)。 */
|
|
59
|
+
export const IconFolderOpenOutline16 = iconOf('IconFolderOpenOutline16', 'IconFolderOpenOutlineRegular')
|
|
60
|
+
/** 刷新图标(面板工具条)。 */
|
|
61
|
+
export const IconRefreshOutline16 = iconOf('IconRefreshOutline16', 'IconRefreshOutlineRegular')
|
|
62
|
+
/** 搜索图标(活动栏「搜索」)。 */
|
|
63
|
+
export const IconSearchOutline16 = iconOf('IconSearchOutline16', 'IconSearchOutlineRegular')
|
|
64
|
+
/** 列表+笔图标(活动栏「规则」)。 */
|
|
65
|
+
export const IconListPenOutline16 = iconOf('IconListPenOutline16', 'IconListPenOutlineRegular')
|
|
66
|
+
/** 代码图标(活动栏「大纲」)。 */
|
|
67
|
+
export const IconCodeOutline16 = iconOf('IconCodeOutline16', 'IconCodeOutlineRegular')
|
|
68
|
+
/** 分支图标(SVN 面板)。 */
|
|
69
|
+
export const IconBranchOutline16 = iconOf('IconBranchOutline16', 'IconBranchOutlineRegular')
|
|
70
|
+
|
|
71
|
+
// 两代同名存活的原语:直通再导出(0.1.6/0.1.7 实证均在)
|
|
72
|
+
export { FileTypeIcon, classifyFileType } from '@deepseek-ai/dsh-client-ui-primitives'
|
package/src/compat.ts
CHANGED
|
@@ -133,7 +133,7 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
|
|
136
|
-
const TESTED_DSH_MAX = '0.1.
|
|
136
|
+
const TESTED_DSH_MAX = '0.1.7-alpha.1'
|
|
137
137
|
|
|
138
138
|
/** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略。 */
|
|
139
139
|
export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
|
@@ -149,7 +149,7 @@ export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
|
|
149
149
|
const strategy = settingsInstallStrategy()
|
|
150
150
|
adapters.push({
|
|
151
151
|
name: '设置 section 安装(版本适配)',
|
|
152
|
-
active: strategy === 'legacy' || strategy === 'service',
|
|
152
|
+
active: strategy === 'legacy' || strategy === 'service' || strategy === 'forms',
|
|
153
153
|
note: settingsInstallNote(),
|
|
154
154
|
})
|
|
155
155
|
return adapters
|
package/src/dshVersion.ts
CHANGED
|
@@ -85,13 +85,14 @@ export function inDshRange(version: DshVersion | null | undefined, range: DshRan
|
|
|
85
85
|
/**
|
|
86
86
|
* 版本线标签:报告与文档展示版本归属;不可解析返回 '未知'。
|
|
87
87
|
* 逐线自新到旧匹配,先命中先返回(区间无上界,故顺序即优先级)。
|
|
88
|
-
* @author ddj 2026年09月02号 / 2026年09月18号
|
|
88
|
+
* @author ddj 2026年09月02号 / 2026年09月18号 / 2026年09月22号
|
|
89
89
|
* @param input 版本串(如 '0.1.6-alpha.2')
|
|
90
90
|
* @returns 版本线标签
|
|
91
91
|
*/
|
|
92
92
|
export function familyLabel(input: string): string {
|
|
93
93
|
const version = parseDshVersion(input)
|
|
94
94
|
if (!version) return '未知'
|
|
95
|
+
if (inDshRange(version, { from: '0.1.7-alpha.1' })) return '0.1.7-alpha.1 及更新(设置=profile Config + configForms 客户端面 + 会话 V4)'
|
|
95
96
|
if (inDshRange(version, { from: '0.1.6-alpha.2' })) return '0.1.6-alpha.2 及更新(会话多实例共存 + 回合改动卡片 + Office 侧栏预览 + 侧栏浏览器)'
|
|
96
97
|
if (inDshRange(version, { from: '0.1.6-alpha.1' })) return '0.1.6-alpha 及更新(MCP SDK v2 + Web 侧边栏终端 + 文件链接默认侧栏预览)'
|
|
97
98
|
if (inDshRange(version, { from: '0.1.5-alpha.1' })) return '0.1.5-alpha 及更新(官方右侧 Sidebar 编辑区 + sidebar.panellist)'
|
package/src/fileOpenSettings.ts
CHANGED
|
@@ -2,10 +2,11 @@
|
|
|
2
2
|
* dsh-vscode-mode host — 文件链接打开工具 + 快捷键的持久化设置。
|
|
3
3
|
* 依赖守卫:schemastery 动态加载;@deepseek-ai/dsh-settings 仅作 legacy 探测——
|
|
4
4
|
* rc 线导出 installSettingsSection free function,0.1.2-alpha 起移除(改由 settings
|
|
5
|
-
* 服务的 installSection
|
|
5
|
+
* 服务的 installSection 方法承载),0.1.7 再移除 installSection(设置并入 profile
|
|
6
|
+
* 插件 Config schema,见 runSettingsInstall 四策略:legacy/service/forms/none)。
|
|
6
7
|
* 缺失/任一策略失败时插件仍可加载(fileOpenTool 降级为配置值,compat 报告可见),
|
|
7
8
|
* 全程 try/catch,不产生未捕获 rejection。
|
|
8
|
-
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号
|
|
9
|
+
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号 / 2026年09月22号
|
|
9
10
|
*/
|
|
10
11
|
import { createRequire } from 'node:module'
|
|
11
12
|
import { pathToFileURL } from 'node:url'
|
|
@@ -15,6 +16,7 @@ import { EDITOR_LIMIT_DEFAULT } from './shared/editorLimit.js'
|
|
|
15
16
|
import { INTEGRATION_BASE_DEFAULT } from './shared/integration.js'
|
|
16
17
|
import { TORTOISE_DIR_DEFAULT } from './shared/svn.js'
|
|
17
18
|
import type { AiConfigPatch, AiConfigView } from './shared/ai.js'
|
|
19
|
+
import { DEFAULT_NATIVE_CSV } from './shared/nativeOpen.js'
|
|
18
20
|
import { log } from './log.js'
|
|
19
21
|
|
|
20
22
|
export const FILE_OPEN_SETTINGS_NS = 'dsh-vscode-mode'
|
|
@@ -170,11 +172,12 @@ export function loadSettingsDeps(importFn: (specifier: string) => Promise<unknow
|
|
|
170
172
|
}
|
|
171
173
|
|
|
172
174
|
/** 设置 section 安装策略(版本适配机制的观测值)。 */
|
|
173
|
-
export type SettingsInstallStrategy = 'legacy' | 'service' | 'none' | 'unknown'
|
|
175
|
+
export type SettingsInstallStrategy = 'legacy' | 'service' | 'forms' | 'none' | 'unknown'
|
|
174
176
|
|
|
175
177
|
const INSTALL_UNMOUNTED = '设置 section 尚未装配'
|
|
176
178
|
const INSTALL_LEGACY = 'rc 线:dsh-settings.installSettingsSection'
|
|
177
179
|
const INSTALL_SERVICE = '0.1.2-alpha 线:settings 服务 installSection'
|
|
180
|
+
const INSTALL_FORMS = '0.1.7 线:设置并入插件 Config schema(SettingsForms)'
|
|
178
181
|
const INSTALL_NONE = '两路均不可用:设置持久化降级为配置值'
|
|
179
182
|
|
|
180
183
|
interface ObservedInstall { strategy: SettingsInstallStrategy; note: string }
|
|
@@ -201,15 +204,54 @@ export function resetSettingsInstallObserved(): void {
|
|
|
201
204
|
observedInstall = { strategy: 'unknown', note: INSTALL_UNMOUNTED }
|
|
202
205
|
}
|
|
203
206
|
|
|
207
|
+
/**
|
|
208
|
+
* 判定设置写冲突错误(0.1.7 SettingsConflictError:code 常量 / 构造器名双通道)。
|
|
209
|
+
* @author ddj 2026年09月22号
|
|
210
|
+
* @param error 捕获到的错误
|
|
211
|
+
* @returns 是否为 revision 冲突
|
|
212
|
+
*/
|
|
213
|
+
function isConflictError(error: unknown): boolean {
|
|
214
|
+
if (!error || typeof error !== 'object') return false
|
|
215
|
+
const e = error as { code?: unknown; name?: unknown }
|
|
216
|
+
return e.code === 'SETTINGS_CONFLICT' || e.name === 'SettingsConflictError'
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* 0.1.7 forms 策略:订阅 settings/document-updated 事件,目标 namespace 变化时
|
|
221
|
+
* 把 describe 最新值推给 hooks(setSource + onChange),替代旧 installSection 的
|
|
222
|
+
* 变更回调;disposer 经 ctx.effect 随插件 fiber 卸载(热重载不泄漏)。
|
|
223
|
+
* @author ddj 2026年09月22号
|
|
224
|
+
* @param ctx DSH host 上下文
|
|
225
|
+
* @param provider settings 服务(SettingsForms)
|
|
226
|
+
* @param ns 目标设置命名空间
|
|
227
|
+
* @param hooks 设置源绑定与变更回调
|
|
228
|
+
*/
|
|
229
|
+
function watchDocUpdates(ctx: Ctx, provider: SettingsProvider, ns: string, hooks: { setSource: (source: () => unknown) => void; onChange: () => void }): void {
|
|
230
|
+
try {
|
|
231
|
+
const bus = ctx as { on?: (name: string, handler: (updatedNs: unknown, revision: unknown) => void) => unknown; effect?: (fn: () => unknown) => unknown }
|
|
232
|
+
if (typeof bus.on !== 'function') return
|
|
233
|
+
const off = bus.on('settings/document-updated', (updatedNs: unknown) => {
|
|
234
|
+
if (updatedNs !== ns) return
|
|
235
|
+
hooks.setSource(() => readSectionValue(provider, ns))
|
|
236
|
+
hooks.onChange()
|
|
237
|
+
})
|
|
238
|
+
if (typeof bus.effect === 'function' && typeof off === 'function') bus.effect(() => off)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
log.warn('settings/document-updated 订阅失败:' + String(error))
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
204
244
|
/**
|
|
205
245
|
* 设置 section 版本自适应安装(核心策略分派)。
|
|
206
246
|
* legacy:dsh-settings 仍导出 installSettingsSection(rc 线)→ 原样调用,行为与旧版一致。
|
|
207
247
|
* service:该导出已移除(0.1.2-alpha 起)→ 经 ctx.inject(['settings']) 走服务方法
|
|
208
248
|
* provider.installSection(owner, ns, schema, entry, hooks)(等义封装,含
|
|
209
|
-
* base 层与 fiber
|
|
249
|
+
* base 层与 fiber 卸载回退)。
|
|
250
|
+
* forms:0.1.7 起 installSection 移除,设置并入 profile 插件 Config(SettingsForms
|
|
251
|
+
* 的 describe+update 在场即成立)→ 不装 section,改订阅 document-updated。
|
|
210
252
|
* none:两条路由都不存在 → 仅记录并告警,调用方按配置值运行。
|
|
211
253
|
* 全程不抛错、不产生未捕获 rejection。
|
|
212
|
-
* @author ddj 2026年09月02号
|
|
254
|
+
* @author ddj 2026年09月02号(2026年09月22号 增补 forms 策略)
|
|
213
255
|
* @param ctx DSH host 上下文(inject 可选探测)
|
|
214
256
|
* @param ns 设置命名空间
|
|
215
257
|
* @param schema schemastery schema
|
|
@@ -253,6 +295,13 @@ export async function runSettingsInstall(
|
|
|
253
295
|
const provider = typeof sc.get === 'function' ? sc.get('settings') : sc.settings
|
|
254
296
|
const install = (provider as { installSection?: unknown } | undefined)?.installSection
|
|
255
297
|
if (typeof install !== 'function') {
|
|
298
|
+
// 0.1.7:installSection 已移除;SettingsForms(describe+update)在场走 forms 策略
|
|
299
|
+
const forms = provider as SettingsProvider | undefined
|
|
300
|
+
if (typeof forms?.describe === 'function' && typeof forms?.update === 'function') {
|
|
301
|
+
recordInstall('forms', INSTALL_FORMS)
|
|
302
|
+
watchDocUpdates(ctx, forms, ns, hooks)
|
|
303
|
+
return
|
|
304
|
+
}
|
|
256
305
|
log.warn('settings 服务无 installSection(DSH 版本 API 变化),section ' + ns + ' 降级为配置值')
|
|
257
306
|
recordInstall('none', INSTALL_NONE)
|
|
258
307
|
return
|
|
@@ -286,6 +335,101 @@ function keybindingsShape(z: SettingsDeps['z']): Record<string, unknown> {
|
|
|
286
335
|
return shape
|
|
287
336
|
}
|
|
288
337
|
|
|
338
|
+
/**
|
|
339
|
+
* 设置节 describe 项的形状校验:value 须为普通对象。
|
|
340
|
+
* ns 在两代语义不同(旧=自装 section 名,0.1.7=profile entry id),同名撞车时
|
|
341
|
+
* 以形状兜底防误命中非设置数据;0.1.7 下本插件 entry 的 Config 值与旧 section
|
|
342
|
+
* 值同为设置键对象,两形状天然兼容。
|
|
343
|
+
* @author ddj 2026年09月22号
|
|
344
|
+
* @param value describe 项的 value 字段
|
|
345
|
+
* @returns 是否具备设置节形状
|
|
346
|
+
*/
|
|
347
|
+
function isSectionShaped(value: unknown): boolean {
|
|
348
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** describe 项最小形状(读写工具共用)。 */
|
|
352
|
+
export interface SectionDescriptor { ns?: string; value?: unknown; revision?: number }
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 按 ns + 形状校验查找设置节(host 读路径统一入口)。
|
|
356
|
+
* @author ddj 2026年09月22号
|
|
357
|
+
* @param provider settings 服务(可空)
|
|
358
|
+
* @param ns 设置命名空间
|
|
359
|
+
* @returns 命中的 describe 项;未命中返回 undefined
|
|
360
|
+
*/
|
|
361
|
+
export function sectionOf(provider: SettingsProvider | undefined, ns: string): SectionDescriptor | undefined {
|
|
362
|
+
const items = provider?.describe?.({ redactSecrets: true })
|
|
363
|
+
if (!items) return undefined
|
|
364
|
+
return items.find((item) => item.ns === ns && isSectionShaped(item.value))
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/** 读设置节存储值(未就绪/形状不符返回 undefined)。 */
|
|
368
|
+
function readSectionValue(provider: SettingsProvider | undefined, ns: string): unknown {
|
|
369
|
+
return sectionOf(provider, ns)?.value
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** 设置写入结果(ok=false 时带原因;conflict=true 表示经冲突重试后成功)。 */
|
|
373
|
+
export interface SectionWriteResult { ok: boolean; conflict?: boolean; error?: string }
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* 带冲突自愈的设置写入:0.1.7 SettingsConflictError(revision 拒写)时重读
|
|
377
|
+
* revision 重试一次;其余错误与服务缺失一律结构化返回,绝不抛未捕获异常。
|
|
378
|
+
* @author ddj 2026年09月22号
|
|
379
|
+
* @param provider settings 服务(可空)
|
|
380
|
+
* @param ns 设置命名空间
|
|
381
|
+
* @param patch 写入字段
|
|
382
|
+
* @param expectedRevision 读侧携带的 revision(缺省用 describe 最新值)
|
|
383
|
+
* @returns 写入结果
|
|
384
|
+
*/
|
|
385
|
+
export async function updateSection(provider: SettingsProvider | undefined, ns: string, patch: object, expectedRevision?: number): Promise<SectionWriteResult> {
|
|
386
|
+
if (!provider?.update) return { ok: false, error: 'settings 服务无 update' }
|
|
387
|
+
const first = expectedRevision ?? sectionOf(provider, ns)?.revision
|
|
388
|
+
try {
|
|
389
|
+
await provider.update(ns, patch, first)
|
|
390
|
+
return { ok: true }
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (!isConflictError(error)) return { ok: false, error: String(error) }
|
|
393
|
+
}
|
|
394
|
+
const fresh = sectionOf(provider, ns)?.revision
|
|
395
|
+
try {
|
|
396
|
+
await provider.update(ns, patch, fresh)
|
|
397
|
+
return { ok: true, conflict: true }
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return { ok: false, conflict: true, error: String(error) }
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* 构建设置 section schema(install 路径与插件 Config 声明共用,保证两代形状同源)。
|
|
405
|
+
* 字段集 = fileOpenTool/keybindings/sidebarMinWidth/maxOpenEditors/integrationBaseUrl/
|
|
406
|
+
* aiInline/aiProvider/aiModel/aiEffort/svnPath/tortoisePath/nativeOpenExts,全部带默认值
|
|
407
|
+
* (Config 启动校验在 undefined/空配置下自动填充,rc/alpha 两代 cordis 均通过)。
|
|
408
|
+
* @author ddj 2026年09月22号
|
|
409
|
+
* @param z schemastery 命名空间(静态 import 或动态加载均可)
|
|
410
|
+
* @returns schemastery object schema
|
|
411
|
+
*/
|
|
412
|
+
export function buildSettingsSchema(z: SettingsDeps['z']): unknown {
|
|
413
|
+
return z.object({
|
|
414
|
+
fileOpenTool: z.string().default(FILE_OPEN_DEFAULT),
|
|
415
|
+
keybindings: z.object(keybindingsShape(z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
416
|
+
sidebarMinWidth: z.number().default(300),
|
|
417
|
+
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
418
|
+
maxOpenEditors: z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
419
|
+
integrationBaseUrl: z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
420
|
+
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
421
|
+
aiInline: z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
|
422
|
+
aiProvider: z.string().default(AI_CONFIG_DEFAULT.provider),
|
|
423
|
+
aiModel: z.string().default(AI_CONFIG_DEFAULT.model),
|
|
424
|
+
aiEffort: z.string().default(AI_CONFIG_DEFAULT.effort),
|
|
425
|
+
// SVN 能力:svn CLI 覆盖(空 = PATH)与 TortoiseSVN 目录(Windows 过渡增强)
|
|
426
|
+
svnPath: z.string().default(''),
|
|
427
|
+
tortoisePath: z.string().default(TORTOISE_DIR_DEFAULT),
|
|
428
|
+
// 原生打开范围(逗号分隔后缀;默认 = 让位清单并集,csv/tsv 决策见 shared/nativeOpen.ts)
|
|
429
|
+
nativeOpenExts: z.string().default(DEFAULT_NATIVE_CSV),
|
|
430
|
+
})
|
|
431
|
+
}
|
|
432
|
+
|
|
289
433
|
function normalizeValue(value: unknown): string {
|
|
290
434
|
if (typeof value !== 'string' || value.trim() === '') return FILE_OPEN_DEFAULT
|
|
291
435
|
return value.trim()
|
|
@@ -337,27 +481,12 @@ export async function installOpenSettingsSection(
|
|
|
337
481
|
/* 装载失败按缺失处理 */
|
|
338
482
|
}
|
|
339
483
|
if (!deps) return false
|
|
340
|
-
const schema = deps.z
|
|
341
|
-
fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
|
|
342
|
-
keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
343
|
-
sidebarMinWidth: deps.z.number().default(300),
|
|
344
|
-
// 页签数量上限(0 = 不限制;超限时淘汰最久未使用的页签,固定页签除外)
|
|
345
|
-
maxOpenEditors: deps.z.number().default(EDITOR_LIMIT_DEFAULT),
|
|
346
|
-
integrationBaseUrl: deps.z.string().default(INTEGRATION_BASE_DEFAULT),
|
|
347
|
-
// AI 内联补全(默认关;provider/model 空 = 自动路由;effort 空 = 跟随模型默认)
|
|
348
|
-
aiInline: deps.z.boolean().default(AI_CONFIG_DEFAULT.enabled),
|
|
349
|
-
aiProvider: deps.z.string().default(AI_CONFIG_DEFAULT.provider),
|
|
350
|
-
aiModel: deps.z.string().default(AI_CONFIG_DEFAULT.model),
|
|
351
|
-
aiEffort: deps.z.string().default(AI_CONFIG_DEFAULT.effort),
|
|
352
|
-
// SVN 能力:svn CLI 覆盖(空 = PATH)与 TortoiseSVN 目录(Windows 过渡增强)
|
|
353
|
-
svnPath: deps.z.string().default(''),
|
|
354
|
-
tortoisePath: deps.z.string().default(TORTOISE_DIR_DEFAULT),
|
|
355
|
-
})
|
|
484
|
+
const schema = buildSettingsSchema(deps.z)
|
|
356
485
|
const strategy = await runSettingsInstall(ctx, ns, schema, entry, {
|
|
357
486
|
setSource: (source) => hooks.setSource(source as () => FileOpenSettings),
|
|
358
487
|
onChange: hooks.onChange,
|
|
359
488
|
}, loader)
|
|
360
|
-
return strategy === 'legacy' || strategy === 'service'
|
|
489
|
+
return strategy === 'legacy' || strategy === 'service' || strategy === 'forms'
|
|
361
490
|
}
|
|
362
491
|
|
|
363
492
|
/** AI 配置脏值读取(settings 未就绪时回退默认)。 */
|
|
@@ -385,8 +514,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
385
514
|
let provider: SettingsProvider | undefined
|
|
386
515
|
const notify = (value: unknown): void => { current = normalizeValue(value); onChange(current) }
|
|
387
516
|
const syncRevision = (): void => {
|
|
388
|
-
|
|
389
|
-
revision = descriptor?.revision
|
|
517
|
+
revision = sectionOf(provider, FILE_OPEN_SETTINGS_NS)?.revision
|
|
390
518
|
}
|
|
391
519
|
const setSource = (source: () => FileOpenSettings): void => notify(source().fileOpenTool)
|
|
392
520
|
const settingsChange = (): void => syncRevision()
|
|
@@ -394,8 +522,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
394
522
|
/** AI 配置当前值(settings 未就绪回退默认)。 */
|
|
395
523
|
let aiCurrent: AiConfigView = { ...AI_CONFIG_DEFAULT }
|
|
396
524
|
const aiSync = (): void => {
|
|
397
|
-
const
|
|
398
|
-
const stored = descriptor?.value
|
|
525
|
+
const stored = readSectionValue(provider, FILE_OPEN_SETTINGS_NS)
|
|
399
526
|
aiCurrent = stored !== undefined ? aiValueOf(stored) : aiCurrent
|
|
400
527
|
}
|
|
401
528
|
|
|
@@ -409,7 +536,7 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
409
536
|
}
|
|
410
537
|
/** 读取 settings 存储值(describe 未就绪返回 undefined)。 */
|
|
411
538
|
const storedValue = (): unknown => {
|
|
412
|
-
return provider
|
|
539
|
+
return readSectionValue(provider, FILE_OPEN_SETTINGS_NS)
|
|
413
540
|
}
|
|
414
541
|
|
|
415
542
|
void installOpenSettingsSection(ctx, FILE_OPEN_SETTINGS_NS, {
|
|
@@ -438,31 +565,27 @@ export function setupOpenSettings(ctx: Ctx, config: unknown, onChange: (value: s
|
|
|
438
565
|
get revision() { return revision },
|
|
439
566
|
update: async (value: string, expectedRevision?: number): Promise<void> => {
|
|
440
567
|
const next = normalizeValue(value)
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
const
|
|
444
|
-
const stored = descriptor?.value as { fileOpenTool?: unknown } | undefined
|
|
568
|
+
const result = await updateSection(provider, FILE_OPEN_SETTINGS_NS, { fileOpenTool: next }, expectedRevision)
|
|
569
|
+
if (!result.ok) log.warn('fileOpenTool 设置写入失败:' + (result.error ?? '未知原因'))
|
|
570
|
+
const stored = readSectionValue(provider, FILE_OPEN_SETTINGS_NS) as { fileOpenTool?: unknown } | undefined
|
|
445
571
|
notify(stored?.fileOpenTool ?? next)
|
|
446
572
|
syncRevision()
|
|
447
573
|
},
|
|
448
574
|
ai: () => aiCurrent,
|
|
449
575
|
svn: () => svnCurrent,
|
|
450
576
|
aiUpdate: async (patch: AiConfigPatch, expectedRevision?: number): Promise<AiConfigView> => {
|
|
451
|
-
if (!provider?.update) {
|
|
452
|
-
// settings 不可用:内存态生效(重启回落默认),保持与 fileOpenTool 的降级语义一致
|
|
453
|
-
if (patch.enabled !== undefined) aiCurrent.enabled = patch.enabled
|
|
454
|
-
if (patch.provider !== undefined) aiCurrent.provider = patch.provider
|
|
455
|
-
if (patch.model !== undefined) aiCurrent.model = patch.model
|
|
456
|
-
if (patch.effort !== undefined) aiCurrent.effort = patch.effort
|
|
457
|
-
return aiCurrent
|
|
458
|
-
}
|
|
459
577
|
const stored = { ...aiCurrent }
|
|
460
578
|
const body: Record<string, unknown> = {}
|
|
461
579
|
if (patch.enabled !== undefined) { stored.enabled = patch.enabled; body.aiInline = patch.enabled }
|
|
462
580
|
if (patch.provider !== undefined) { stored.provider = patch.provider; body.aiProvider = patch.provider }
|
|
463
581
|
if (patch.model !== undefined) { stored.model = patch.model; body.aiModel = patch.model }
|
|
464
582
|
if (patch.effort !== undefined) { stored.effort = patch.effort; body.aiEffort = patch.effort }
|
|
465
|
-
await provider
|
|
583
|
+
const result = await updateSection(provider, FILE_OPEN_SETTINGS_NS, body, expectedRevision)
|
|
584
|
+
if (!result.ok) {
|
|
585
|
+
// settings 不可用/写入失败:内存态生效(重启回落默认),与 fileOpenTool 降级语义一致
|
|
586
|
+
aiCurrent = stored
|
|
587
|
+
return aiCurrent
|
|
588
|
+
}
|
|
466
589
|
aiSync()
|
|
467
590
|
return aiCurrent
|
|
468
591
|
},
|
package/src/index.ts
CHANGED
|
@@ -14,7 +14,9 @@ import { newSearcher } from './search/orchestrator.js'
|
|
|
14
14
|
import { newContentSearcher } from './search/content.js'
|
|
15
15
|
import { installIsolation } from './mcpIsolation.js'
|
|
16
16
|
import { cwdOf } from './registry.js'
|
|
17
|
-
import { setupOpenSettings } from './fileOpenSettings.js'
|
|
17
|
+
import { setupOpenSettings, buildSettingsSchema } from './fileOpenSettings.js'
|
|
18
|
+
import type { SettingsDeps } from './fileOpenSettings.js'
|
|
19
|
+
import z from '@deepseek-ai/schemastery'
|
|
18
20
|
import { shellMenuLifecycle } from './integrate.js'
|
|
19
21
|
import { disposeIndex } from './treeIndex.js'
|
|
20
22
|
import { sweepTreeCache } from './paths.js'
|
|
@@ -36,6 +38,18 @@ import type { Ctx } from './store.js'
|
|
|
36
38
|
export const name = "dsh-vscode-mode"
|
|
37
39
|
export const inject = ['sessions', 'fs', 'webServer', 'loader', 'tools', 'workspaceRegistry', 'agents']
|
|
38
40
|
|
|
41
|
+
/**
|
|
42
|
+
* 插件 Config schema(cordis 从 module 导出读 `plugin.Config` 做启动校验;
|
|
43
|
+
* DSH 0.1.7 起设置页按此 schema 自动生成表单,设置值 = profile 插件配置)。
|
|
44
|
+
* 字段集与 installOpenSettingsSection 的 section schema 同源(buildSettingsSchema),
|
|
45
|
+
* 全字段带默认值——undefined/空配置经 schemastery 校验自动填充(rc/alpha 两代
|
|
46
|
+
* cordis 均读该导出,实测 4.0.0-rc.8 与 0.1.7 行为一致,校验必过)。
|
|
47
|
+
* 运行时解析:@deepseek-ai/schemastery 经插件自身 node_modules(link/dev/npm 安装
|
|
48
|
+
* 三形态均在 peer/dev 依赖面内,先于宿主树命中)。
|
|
49
|
+
* @author ddj 2026年09月22号
|
|
50
|
+
*/
|
|
51
|
+
export const Config = buildSettingsSchema(z as unknown as SettingsDeps['z'])
|
|
52
|
+
|
|
39
53
|
/**
|
|
40
54
|
* 装配插件:挂事件监听、注册路由、安装兼容层。
|
|
41
55
|
* @author ddj 2026年08月20号
|
package/src/lsp/client.ts
CHANGED
|
@@ -62,6 +62,9 @@ export function createLspClient(
|
|
|
62
62
|
workspaceSymbol: false,
|
|
63
63
|
hover: false,
|
|
64
64
|
semanticTokens: false,
|
|
65
|
+
completion: false,
|
|
66
|
+
completionResolve: false,
|
|
67
|
+
signatureHelp: false,
|
|
65
68
|
semanticTokenTypes: [],
|
|
66
69
|
semanticTokenModifiers: [],
|
|
67
70
|
}
|
|
@@ -164,6 +167,8 @@ export function createLspClient(
|
|
|
164
167
|
documentSymbolProvider?: unknown
|
|
165
168
|
workspaceSymbolProvider?: unknown
|
|
166
169
|
hoverProvider?: unknown
|
|
170
|
+
completionProvider?: { resolveProvider?: unknown; triggerCharacters?: unknown }
|
|
171
|
+
signatureHelpProvider?: { triggerCharacters?: unknown; retriggerCharacters?: unknown }
|
|
167
172
|
semanticTokensProvider?: {
|
|
168
173
|
legend?: { tokenTypes?: unknown; tokenModifiers?: unknown }
|
|
169
174
|
}
|
|
@@ -185,6 +190,22 @@ export function createLspClient(
|
|
|
185
190
|
references: { dynamicRegistration: false },
|
|
186
191
|
documentSymbol: { dynamicRegistration: false },
|
|
187
192
|
hover: { dynamicRegistration: false },
|
|
193
|
+
// 补全能力声明:snippetSupport 让服务器回 InsertAsSnippet 片段;
|
|
194
|
+
// resolveSupport.properties 声明我方支持 completionItem/resolve 惰性补全
|
|
195
|
+
// (EmmyLua 据此把 documentation 挪到 resolve 阶段,列表载荷显著变小)。
|
|
196
|
+
completion: {
|
|
197
|
+
dynamicRegistration: false,
|
|
198
|
+
contextSupport: true,
|
|
199
|
+
completionItem: {
|
|
200
|
+
snippetSupport: true,
|
|
201
|
+
documentationFormat: ['markdown', 'plaintext'],
|
|
202
|
+
resolveSupport: { properties: ['documentation', 'detail', 'additionalTextEdits'] },
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
signatureHelp: {
|
|
206
|
+
dynamicRegistration: false,
|
|
207
|
+
signatureInformation: { documentationFormat: ['markdown', 'plaintext'] },
|
|
208
|
+
},
|
|
188
209
|
semanticTokens: {
|
|
189
210
|
dynamicRegistration: false,
|
|
190
211
|
requests: { range: false, full: { delta: false } },
|
|
@@ -205,6 +226,9 @@ export function createLspClient(
|
|
|
205
226
|
workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
|
|
206
227
|
hover: Boolean(caps.hoverProvider),
|
|
207
228
|
semanticTokens: Boolean(caps.semanticTokensProvider),
|
|
229
|
+
completion: Boolean(caps.completionProvider),
|
|
230
|
+
completionResolve: Boolean(caps.completionProvider?.resolveProvider),
|
|
231
|
+
signatureHelp: Boolean(caps.signatureHelpProvider),
|
|
208
232
|
semanticTokenTypes: Array.isArray(legend?.tokenTypes)
|
|
209
233
|
? legend.tokenTypes.filter((item): item is string => typeof item === 'string')
|
|
210
234
|
: [...LSP_SEMANTIC_TOKEN_TYPES],
|
package/src/lsp/rpc.ts
CHANGED
|
@@ -436,6 +436,63 @@ export function createLspRpc(deps: LspRpcDeps): { handlers: Partial<RpcHandlerMa
|
|
|
436
436
|
}
|
|
437
437
|
},
|
|
438
438
|
|
|
439
|
+
'edrv.lsp.completion': async (args) => {
|
|
440
|
+
const lang = langOfPath(args.path)
|
|
441
|
+
if (!lang) return { ok: true, completions: undefined }
|
|
442
|
+
const sc = await rootOf(args.sessionId)
|
|
443
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
444
|
+
try {
|
|
445
|
+
const server = serverOf(sc.root, lang)
|
|
446
|
+
if (!server) return { ok: true, completions: undefined }
|
|
447
|
+
const completions = await server.completion(
|
|
448
|
+
wsPath(sc.root, args.path),
|
|
449
|
+
args.position.line,
|
|
450
|
+
args.position.character,
|
|
451
|
+
args.context,
|
|
452
|
+
)
|
|
453
|
+
if (!completions) return { ok: true, completions: undefined }
|
|
454
|
+
const truncated = completions.items.length > 500
|
|
455
|
+
return {
|
|
456
|
+
ok: true,
|
|
457
|
+
completions: truncated ? { ...completions, items: completions.items.slice(0, 500), truncated: true } : completions,
|
|
458
|
+
}
|
|
459
|
+
} catch (error) {
|
|
460
|
+
return { ok: false, error: 'LSP 补全查询失败:' + String(error) }
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
'edrv.lsp.resolveCompletion': async (args) => {
|
|
465
|
+
const lang = langOfPath(args.path)
|
|
466
|
+
if (!lang) return { ok: true, item: undefined }
|
|
467
|
+
const sc = await rootOf(args.sessionId)
|
|
468
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
469
|
+
try {
|
|
470
|
+
const server = serverOf(sc.root, lang)
|
|
471
|
+
if (!server) return { ok: true, item: args.item }
|
|
472
|
+
const item = await server.resolveCompletion(wsPath(sc.root, args.path), args.item)
|
|
473
|
+
return { ok: true, item: item ?? undefined }
|
|
474
|
+
} catch (error) {
|
|
475
|
+
return { ok: false, error: 'LSP 补全解析失败:' + String(error) }
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
|
|
479
|
+
'edrv.lsp.signatureHelp': async (args) => {
|
|
480
|
+
const lang = langOfPath(args.path)
|
|
481
|
+
if (!lang) return { ok: true, signatureHelp: undefined }
|
|
482
|
+
const sc = await rootOf(args.sessionId)
|
|
483
|
+
if ('err' in sc) return { ok: false, error: sc.err }
|
|
484
|
+
try {
|
|
485
|
+
const server = serverOf(sc.root, lang)
|
|
486
|
+
if (!server) return { ok: true, signatureHelp: undefined }
|
|
487
|
+
return {
|
|
488
|
+
ok: true,
|
|
489
|
+
signatureHelp: (await server.signatureHelp(wsPath(sc.root, args.path), args.position.line, args.position.character)) ?? undefined,
|
|
490
|
+
}
|
|
491
|
+
} catch (error) {
|
|
492
|
+
return { ok: false, error: 'LSP 签名帮助查询失败:' + String(error) }
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
|
|
439
496
|
'edrv.lsp.semanticTokens': async (args) => {
|
|
440
497
|
const lang = langOfPath(args.path)
|
|
441
498
|
if (!lang) return { ok: true, tokens: undefined }
|