dsh-vscode-mode 0.1.36 → 0.1.43
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/lib/client.js +730 -29
- package/lib/client.js.map +1 -1
- package/lib/index.js +699 -88
- package/lib/index.js.map +1 -1
- package/package.json +1 -5
- package/src/client/monaco/loader.ts +12 -1
- package/src/client/monaco/lsp/lspClient.ts +65 -11
- package/src/client/monaco/lsp/providers.ts +34 -1
- package/src/client/monaco/theme.ts +229 -0
- package/src/client/ui/EditorView.ts +21 -2
- package/src/client/ui/LspSettings.ts +39 -7
- package/src/client/ui/McpSettings.ts +5 -1
- package/src/compat.ts +46 -5
- package/src/dshVersion.ts +121 -0
- package/src/fileOpenSettings.ts +143 -24
- package/src/host-deps.d.ts +5 -5
- package/src/index.ts +2 -1
- package/src/lsp/client.ts +29 -2
- package/src/lsp/manager.ts +15 -0
- package/src/lsp/providers.ts +203 -24
- package/src/lsp/rpc.ts +86 -4
- package/src/lsp/server.ts +80 -3
- package/src/lsp/settings.ts +12 -6
- package/src/paths.ts +7 -0
- package/src/rpc.ts +4 -1
- package/src/shared/compat.ts +9 -3
- package/src/shared/lsp.ts +24 -0
- package/src/shared/rpc.ts +6 -2
package/src/compat.ts
CHANGED
|
@@ -10,8 +10,9 @@ import { readFileSync } from 'node:fs'
|
|
|
10
10
|
import { dirname, join } from 'node:path'
|
|
11
11
|
import { fileURLToPath } from 'node:url'
|
|
12
12
|
import { entriesOf } from './mcp.js'
|
|
13
|
-
import { loadSettingsDeps } from './fileOpenSettings.js'
|
|
13
|
+
import { loadSettingsDeps, settingsInstallNote, settingsInstallStrategy } from './fileOpenSettings.js'
|
|
14
14
|
import { readDevForm } from './devForm.js'
|
|
15
|
+
import { compareDshVersions, detectDshVersion, familyLabel, parseDshVersion } from './dshVersion.js'
|
|
15
16
|
import type { CompatAdapter, CompatReport } from './shared/compat.js'
|
|
16
17
|
import type { Ctx } from './store.js'
|
|
17
18
|
import { ROUTE_PREFIX } from './paths.js'
|
|
@@ -127,24 +128,64 @@ export function detectGuards(ctx: Ctx): CompatAdapter[] {
|
|
|
127
128
|
]
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
/** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
|
|
132
|
+
const TESTED_DSH_MAX = '0.1.2-alpha.4'
|
|
133
|
+
|
|
134
|
+
/** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略。 */
|
|
135
|
+
export function versionAdapters(dshVersion: string): CompatAdapter[] {
|
|
136
|
+
const adapters: CompatAdapter[] = []
|
|
137
|
+
const parsed = parseDshVersion(dshVersion)
|
|
138
|
+
if (dshVersion === '') {
|
|
139
|
+
adapters.push({ name: 'DSH 版本', active: false, note: '未探测到版本号(能力探测模式运行)' })
|
|
140
|
+
} else if (!parsed) {
|
|
141
|
+
adapters.push({ name: 'DSH 版本', active: false, note: '无法解析版本号 ' + dshVersion + '(能力探测模式运行)' })
|
|
142
|
+
} else {
|
|
143
|
+
adapters.push({ name: 'DSH 版本', active: true, note: dshVersion + ' · ' + familyLabel(dshVersion) })
|
|
144
|
+
}
|
|
145
|
+
const strategy = settingsInstallStrategy()
|
|
146
|
+
adapters.push({
|
|
147
|
+
name: '设置 section 安装(版本适配)',
|
|
148
|
+
active: strategy === 'legacy' || strategy === 'service',
|
|
149
|
+
note: settingsInstallNote(),
|
|
150
|
+
})
|
|
151
|
+
return adapters
|
|
152
|
+
}
|
|
153
|
+
|
|
130
154
|
/**
|
|
131
155
|
* 构建完整兼容性报告(RPC 与启动日志共用)。
|
|
132
|
-
* @author ddj 2026年08月24号
|
|
156
|
+
* @author ddj 2026年08月24号 / 2026年09月02号
|
|
133
157
|
* @param ctx DSH host 上下文
|
|
134
|
-
* @param options 测试注入:depsAvailable 跳过动态导入、version
|
|
158
|
+
* @param options 测试注入:depsAvailable 跳过动态导入、version 固定插件版本、dshVersion 固定 DSH 版本
|
|
135
159
|
* @returns 兼容性报告
|
|
136
160
|
*/
|
|
137
161
|
export async function buildReport(
|
|
138
162
|
ctx: Ctx,
|
|
139
|
-
options?: { depsAvailable?: boolean; version?: string },
|
|
163
|
+
options?: { depsAvailable?: boolean; version?: string; dshVersion?: string },
|
|
140
164
|
): Promise<CompatReport> {
|
|
141
165
|
const deps = options?.depsAvailable ?? (await loadSettingsDeps()) !== null
|
|
166
|
+
const dshVersion = options?.dshVersion ?? detectDshVersion()
|
|
142
167
|
const external = detectExternal(ctx, deps)
|
|
143
168
|
const guards = detectGuards(ctx)
|
|
169
|
+
const adapters = versionAdapters(dshVersion)
|
|
144
170
|
const warnings: string[] = []
|
|
145
171
|
if (!deps) warnings.push('未安装 @deepseek-ai/dsh-settings:文件打开工具设置持久化不可用(降级为配置值)')
|
|
172
|
+
const strategy = settingsInstallStrategy()
|
|
173
|
+
if (strategy === 'none') warnings.push('设置 section 安装不可用:' + settingsInstallNote())
|
|
174
|
+
const parsed = parseDshVersion(dshVersion)
|
|
175
|
+
const testedMax = parseDshVersion(TESTED_DSH_MAX)
|
|
176
|
+
if (parsed && testedMax && compareDshVersions(parsed, testedMax) > 0) {
|
|
177
|
+
warnings.push('DSH ' + dshVersion + ' 高于已实测版本(' + TESTED_DSH_MAX + '):设置 API 按能力探测运行,异常时请回报适配矩阵')
|
|
178
|
+
}
|
|
146
179
|
for (const guard of guards) {
|
|
147
180
|
if (!guard.active && guard.note) warnings.push(guard.note)
|
|
148
181
|
}
|
|
149
|
-
return {
|
|
182
|
+
return {
|
|
183
|
+
pluginVersion: options?.version ?? pluginVersionOf(),
|
|
184
|
+
external,
|
|
185
|
+
guards,
|
|
186
|
+
adapters,
|
|
187
|
+
warnings,
|
|
188
|
+
devForm: readDevForm(),
|
|
189
|
+
dshVersion,
|
|
190
|
+
}
|
|
150
191
|
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-vscode-mode host — DSH 核心版本探测与区间匹配(版本适配机制的地基)。
|
|
3
|
+
* 约定:以运行时可解析的 @deepseek-ai/dsh-settings 包版本代表 DSH 核心版本——
|
|
4
|
+
* 核心包发布锁步同版(rc.2 / alpha.2 全树同版实证),且该包是插件动态依赖面、
|
|
5
|
+
* exports 已放行 ./package.json。解析失败按候选降级(dsh-web-app / dsh-base / dsh),
|
|
6
|
+
* 全部失败返回空串(报告显示"未探测",功能按能力探测运行)。
|
|
7
|
+
* 纯函数(parse / compare / inRange / familyLabel)可在 node 环境单测。
|
|
8
|
+
* 作者 ddj 2026-09-02
|
|
9
|
+
*/
|
|
10
|
+
import { readFileSync } from 'node:fs'
|
|
11
|
+
import { createRequire } from 'node:module'
|
|
12
|
+
|
|
13
|
+
/** 归一化版本:主/次/补丁 + 预发布标识数组(0.1.2-alpha.2 → pre=['alpha','2'])。 */
|
|
14
|
+
export interface DshVersion {
|
|
15
|
+
major: number
|
|
16
|
+
minor: number
|
|
17
|
+
patch: number
|
|
18
|
+
pre: string[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/
|
|
22
|
+
|
|
23
|
+
/** 解析版本串;非法返回 null(不抛错)。 */
|
|
24
|
+
export function parseDshVersion(input: string): DshVersion | null {
|
|
25
|
+
const match = VERSION_RE.exec(input.trim())
|
|
26
|
+
if (!match) return null
|
|
27
|
+
return {
|
|
28
|
+
major: Number(match[1]),
|
|
29
|
+
minor: Number(match[2]),
|
|
30
|
+
patch: Number(match[3]),
|
|
31
|
+
pre: match[4] ? match[4].split('.') : [],
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 预发布标识比较:纯数字按值;数字段 < 字母段;字母按字典序(semver 规则)。 */
|
|
36
|
+
function comparePreId(a: string, b: string): number {
|
|
37
|
+
const numericA = /^\d+$/.test(a)
|
|
38
|
+
const numericB = /^\d+$/.test(b)
|
|
39
|
+
if (numericA && numericB) return Number(a) - Number(b)
|
|
40
|
+
if (numericA !== numericB) return numericA ? -1 : 1
|
|
41
|
+
return a < b ? -1 : a > b ? 1 : 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 比较两个已解析版本:a<b → -1;相等 → 0;a>b → 1。 */
|
|
45
|
+
export function compareDshVersions(a: DshVersion, b: DshVersion): number {
|
|
46
|
+
for (const key of ['major', 'minor', 'patch'] as const) {
|
|
47
|
+
if (a[key] !== b[key]) return a[key] < b[key] ? -1 : 1
|
|
48
|
+
}
|
|
49
|
+
const samePre = a.pre.length === b.pre.length && a.pre.every((part, index) => part === b.pre[index])
|
|
50
|
+
if (samePre) return 0
|
|
51
|
+
if (a.pre.length === 0) return 1
|
|
52
|
+
if (b.pre.length === 0) return -1
|
|
53
|
+
const length = Math.max(a.pre.length, b.pre.length)
|
|
54
|
+
for (let i = 0; i < length; i++) {
|
|
55
|
+
const left = a.pre[i]
|
|
56
|
+
const right = b.pre[i]
|
|
57
|
+
if (left === undefined) return -1
|
|
58
|
+
if (right === undefined) return 1
|
|
59
|
+
const cmp = comparePreId(left, right)
|
|
60
|
+
if (cmp !== 0) return cmp
|
|
61
|
+
}
|
|
62
|
+
return 0
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** 版本区间(含端点):from/to 为空表示该侧不设界。 */
|
|
66
|
+
export interface DshRange {
|
|
67
|
+
from?: string
|
|
68
|
+
to?: string
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** 版本是否落在区间内(入参解析失败一律不命中)。 */
|
|
72
|
+
export function inDshRange(version: DshVersion | null | undefined, range: DshRange): boolean {
|
|
73
|
+
if (!version) return false
|
|
74
|
+
if (range.from) {
|
|
75
|
+
const lower = parseDshVersion(range.from)
|
|
76
|
+
if (!lower || compareDshVersions(version, lower) < 0) return false
|
|
77
|
+
}
|
|
78
|
+
if (range.to) {
|
|
79
|
+
const upper = parseDshVersion(range.to)
|
|
80
|
+
if (!upper || compareDshVersions(version, upper) > 0) return false
|
|
81
|
+
}
|
|
82
|
+
return true
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 版本线标签:报告与文档展示版本归属;不可解析返回 '未知'。 */
|
|
86
|
+
export function familyLabel(input: string): string {
|
|
87
|
+
const version = parseDshVersion(input)
|
|
88
|
+
if (!version) return '未知'
|
|
89
|
+
if (inDshRange(version, { from: '0.1.2-alpha.1' })) return '0.1.2-alpha 及更新(设置 API=settings.installSection)'
|
|
90
|
+
return '0.1.0/0.1.1 rc 线(设置 API=installSettingsSection)'
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** 探测候选:按与插件运行环境相关性排序,逐个尝试解析 package.json。 */
|
|
94
|
+
const VERSION_CANDIDATES = [
|
|
95
|
+
'@deepseek-ai/dsh-settings/package.json',
|
|
96
|
+
'@deepseek-ai/dsh-web-app/package.json',
|
|
97
|
+
'@deepseek-ai/dsh-base/package.json',
|
|
98
|
+
'@deepseek-ai/dsh/package.json',
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
let detectedVersion: string | undefined
|
|
102
|
+
|
|
103
|
+
/** 探测运行中 DSH 核心版本(模块级缓存;失败空串,不抛错)。 */
|
|
104
|
+
export function detectDshVersion(): string {
|
|
105
|
+
if (detectedVersion !== undefined) return detectedVersion
|
|
106
|
+
detectedVersion = ''
|
|
107
|
+
const require = createRequire(import.meta.url)
|
|
108
|
+
for (const specifier of VERSION_CANDIDATES) {
|
|
109
|
+
try {
|
|
110
|
+
const file = require.resolve(specifier)
|
|
111
|
+
const pkg = JSON.parse(readFileSync(file, 'utf8')) as { version?: unknown }
|
|
112
|
+
if (typeof pkg.version === 'string' && parseDshVersion(pkg.version) !== null) {
|
|
113
|
+
detectedVersion = pkg.version
|
|
114
|
+
break
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
/* exports 未放行或包缺失:尝试下一个候选 */
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return detectedVersion
|
|
121
|
+
}
|
package/src/fileOpenSettings.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* dsh-vscode-mode host — 文件链接打开工具 + 快捷键的持久化设置。
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* 依赖守卫:schemastery 动态加载;@deepseek-ai/dsh-settings 仅作 legacy 探测——
|
|
4
|
+
* rc 线导出 installSettingsSection free function,0.1.2-alpha 起移除(改由 settings
|
|
5
|
+
* 服务的 installSection 方法承载,见 runSettingsInstall 三策略)。
|
|
6
|
+
* 缺失/任一策略失败时插件仍可加载(fileOpenTool 降级为配置值,compat 报告可见),
|
|
7
|
+
* 全程 try/catch,不产生未捕获 rejection。
|
|
8
|
+
* 作者 ddj 2026年08月24号 / 2026年08月26号 / 2026年09月02号
|
|
6
9
|
*/
|
|
7
10
|
import type { Ctx } from './store.js'
|
|
8
11
|
import { KEYBINDING_DEFAULTS } from './shared/keybindings.js'
|
|
@@ -20,7 +23,7 @@ type SettingsProvider = {
|
|
|
20
23
|
|
|
21
24
|
/** 动态依赖的装载结果(宽松类型:dsh-settings 无本地类型声明)。 */
|
|
22
25
|
export interface SettingsDeps {
|
|
23
|
-
installSettingsSection
|
|
26
|
+
installSettingsSection?: (ctx: Ctx, ns: string, schema: unknown, entry: object, hooks: object) => void
|
|
24
27
|
z: {
|
|
25
28
|
object: (shape: Record<string, unknown>) => { default: (value: Record<string, unknown>) => unknown }
|
|
26
29
|
string: () => { default: (value: string) => unknown }
|
|
@@ -33,19 +36,9 @@ export type SettingsDepsLoader = () => Promise<SettingsDeps | null>
|
|
|
33
36
|
let depsPromise: Promise<SettingsDeps | null> | undefined
|
|
34
37
|
|
|
35
38
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* @returns keybindings 字段形状
|
|
40
|
-
*/
|
|
41
|
-
function keybindingsShape(z: SettingsDeps['z']): Record<string, unknown> {
|
|
42
|
-
const shape: Record<string, unknown> = {}
|
|
43
|
-
for (const [id, chord] of Object.entries(KEYBINDING_DEFAULTS)) shape[id] = z.string().default(chord)
|
|
44
|
-
return shape
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* 动态加载设置依赖(模块级缓存;任一缺失返回 null 而非抛错)。
|
|
39
|
+
* 动态加载设置依赖(模块级缓存;任一缺失/失败返回 null 而非抛错)。
|
|
40
|
+
* installSettingsSection 仅在 rc 线 dsh-settings 中存在时提供(alpha 起移除,
|
|
41
|
+
* 属性探测得到 undefined,不抛错)。
|
|
49
42
|
* @author ddj 2026年08月24号
|
|
50
43
|
* @returns 设置依赖或 null
|
|
51
44
|
*/
|
|
@@ -53,14 +46,132 @@ export function loadSettingsDeps(): Promise<SettingsDeps | null> {
|
|
|
53
46
|
if (!depsPromise) {
|
|
54
47
|
depsPromise = Promise.all([import('@deepseek-ai/dsh-settings'), import('schemastery')])
|
|
55
48
|
.then(([dshSettings, schemastery]) => ({
|
|
56
|
-
|
|
57
|
-
|
|
49
|
+
// dsh-settings 类型声明随版本变化(rc.8 有 d.ts、alpha 已移除导出),统一经 unknown 松绑
|
|
50
|
+
installSettingsSection: (dshSettings as unknown as { installSettingsSection?: SettingsDeps['installSettingsSection'] }).installSettingsSection,
|
|
51
|
+
z: (schemastery as unknown as { default: SettingsDeps['z'] }).default,
|
|
58
52
|
}))
|
|
59
53
|
.catch(() => null)
|
|
60
54
|
}
|
|
61
55
|
return depsPromise
|
|
62
56
|
}
|
|
63
57
|
|
|
58
|
+
/** 设置 section 安装策略(版本适配机制的观测值)。 */
|
|
59
|
+
export type SettingsInstallStrategy = 'legacy' | 'service' | 'none' | 'unknown'
|
|
60
|
+
|
|
61
|
+
const INSTALL_UNMOUNTED = '设置 section 尚未装配'
|
|
62
|
+
const INSTALL_LEGACY = 'rc 线:dsh-settings.installSettingsSection'
|
|
63
|
+
const INSTALL_SERVICE = '0.1.2-alpha 线:settings 服务 installSection'
|
|
64
|
+
const INSTALL_NONE = '两路均不可用:设置持久化降级为配置值'
|
|
65
|
+
|
|
66
|
+
interface ObservedInstall { strategy: SettingsInstallStrategy; note: string }
|
|
67
|
+
let observedInstall: ObservedInstall = { strategy: 'unknown', note: INSTALL_UNMOUNTED }
|
|
68
|
+
|
|
69
|
+
/** 记录最近一次安装策略(供兼容报告展示;失败路径也记录,便于诊断)。 */
|
|
70
|
+
function recordInstall(strategy: SettingsInstallStrategy, note: string): SettingsInstallStrategy {
|
|
71
|
+
observedInstall = { strategy, note }
|
|
72
|
+
return strategy
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** 读取观测到的安装策略(供报告;测试可用 reset 复位)。 */
|
|
76
|
+
export function settingsInstallStrategy(): SettingsInstallStrategy {
|
|
77
|
+
return observedInstall.strategy
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** 读取观测到的安装策略说明文案。 */
|
|
81
|
+
export function settingsInstallNote(): string {
|
|
82
|
+
return observedInstall.note
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 复位安装策略观测(测试隔离用)。 */
|
|
86
|
+
export function resetSettingsInstallObserved(): void {
|
|
87
|
+
observedInstall = { strategy: 'unknown', note: INSTALL_UNMOUNTED }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 设置 section 版本自适应安装(核心策略分派)。
|
|
92
|
+
* legacy:dsh-settings 仍导出 installSettingsSection(rc 线)→ 原样调用,行为与旧版一致。
|
|
93
|
+
* service:该导出已移除(0.1.2-alpha 起)→ 经 ctx.inject(['settings']) 走服务方法
|
|
94
|
+
* provider.installSection(owner, ns, schema, entry, hooks)(等义封装,含
|
|
95
|
+
* base 层与 fiber 卸载回退)。回调内方法缺失再降级记录 none。
|
|
96
|
+
* none:两条路由都不存在 → 仅记录并告警,调用方按配置值运行。
|
|
97
|
+
* 全程不抛错、不产生未捕获 rejection。
|
|
98
|
+
* @author ddj 2026年09月02号
|
|
99
|
+
* @param ctx DSH host 上下文(inject 可选探测)
|
|
100
|
+
* @param ns 设置命名空间
|
|
101
|
+
* @param schema schemastery schema
|
|
102
|
+
* @param entry 初始设置值(作为 base 层)
|
|
103
|
+
* @param hooks 设置源绑定与变更回调
|
|
104
|
+
* @param loader 依赖装载器(测试注入)
|
|
105
|
+
* @returns 采用/观测到的策略
|
|
106
|
+
*/
|
|
107
|
+
export async function runSettingsInstall(
|
|
108
|
+
ctx: Ctx,
|
|
109
|
+
ns: string,
|
|
110
|
+
schema: unknown,
|
|
111
|
+
entry: object,
|
|
112
|
+
hooks: { setSource: (source: () => unknown) => void; onChange: () => void; validate?: unknown },
|
|
113
|
+
loader: SettingsDepsLoader = loadSettingsDeps,
|
|
114
|
+
): Promise<SettingsInstallStrategy> {
|
|
115
|
+
let deps: SettingsDeps | null = null
|
|
116
|
+
try {
|
|
117
|
+
deps = await loader()
|
|
118
|
+
} catch {
|
|
119
|
+
/* 装载失败按缺失处理 */
|
|
120
|
+
}
|
|
121
|
+
if (!deps) {
|
|
122
|
+
console.warn('[dsh-vscode-mode] 设置依赖不可用,section ' + ns + ' 未安装(配置回退)')
|
|
123
|
+
return recordInstall('none', INSTALL_NONE)
|
|
124
|
+
}
|
|
125
|
+
const legacy = deps.installSettingsSection
|
|
126
|
+
if (typeof legacy === 'function') {
|
|
127
|
+
try {
|
|
128
|
+
legacy(ctx, ns, schema, entry, hooks)
|
|
129
|
+
return recordInstall('legacy', INSTALL_LEGACY)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.warn('[dsh-vscode-mode] legacy 设置安装失败,尝试服务路由:' + String(error))
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const ctxInject = (ctx as unknown as { inject?: (services: string[], callback: (sctx: unknown) => void) => unknown }).inject
|
|
135
|
+
if (typeof ctxInject !== 'function') return recordInstall('none', INSTALL_NONE)
|
|
136
|
+
try {
|
|
137
|
+
ctxInject(['settings'], (sctx: unknown) => {
|
|
138
|
+
const sc = sctx as { get?: (name: string) => unknown; settings?: unknown }
|
|
139
|
+
const provider = typeof sc.get === 'function' ? sc.get('settings') : sc.settings
|
|
140
|
+
const install = (provider as { installSection?: unknown } | undefined)?.installSection
|
|
141
|
+
if (typeof install !== 'function') {
|
|
142
|
+
console.warn('[dsh-vscode-mode] settings 服务无 installSection(DSH 版本 API 变化),section ' + ns + ' 降级为配置值')
|
|
143
|
+
recordInstall('none', INSTALL_NONE)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
install.call(provider, ctx, ns, schema, entry, hooks)
|
|
148
|
+
recordInstall('service', INSTALL_SERVICE)
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.warn('[dsh-vscode-mode] settings.installSection 安装失败(' + String(error) + '),section ' + ns + ' 降级为配置值')
|
|
151
|
+
recordInstall('none', INSTALL_NONE)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
} catch (error) {
|
|
155
|
+
console.warn('[dsh-vscode-mode] settings 服务路由不可用(' + String(error) + ')')
|
|
156
|
+
return recordInstall('none', INSTALL_NONE)
|
|
157
|
+
}
|
|
158
|
+
// inject 回调若同步执行(settings 已就绪)会覆写观测值;仍未执行(unknown)时乐观按 service 记录
|
|
159
|
+
if (observedInstall.strategy !== 'unknown') return observedInstall.strategy
|
|
160
|
+
return recordInstall('service', '已调度 settings 服务 installSection(等待 settings 就绪)')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 按命令目录构建 keybindings 显式键形状(每键独立默认值)。
|
|
165
|
+
* @author ddj 2026年08月26号
|
|
166
|
+
* @param z schemastery 命名空间
|
|
167
|
+
* @returns keybindings 字段形状
|
|
168
|
+
*/
|
|
169
|
+
function keybindingsShape(z: SettingsDeps['z']): Record<string, unknown> {
|
|
170
|
+
const shape: Record<string, unknown> = {}
|
|
171
|
+
for (const [id, chord] of Object.entries(KEYBINDING_DEFAULTS)) shape[id] = z.string().default(chord)
|
|
172
|
+
return shape
|
|
173
|
+
}
|
|
174
|
+
|
|
64
175
|
function normalizeValue(value: unknown): string {
|
|
65
176
|
if (typeof value !== 'string' || value.trim() === '') return FILE_OPEN_DEFAULT
|
|
66
177
|
return value.trim()
|
|
@@ -71,8 +182,8 @@ function configValue(config: unknown): string {
|
|
|
71
182
|
}
|
|
72
183
|
|
|
73
184
|
/**
|
|
74
|
-
* 安装设置 section
|
|
75
|
-
* @author ddj 2026年08月24号
|
|
185
|
+
* 安装设置 section(版本自适应;任一策略不可用返回 false 不抛错)。
|
|
186
|
+
* @author ddj 2026年08月24号 / 2026年09月02号
|
|
76
187
|
* @param ctx DSH host 上下文
|
|
77
188
|
* @param ns 设置命名空间
|
|
78
189
|
* @param entry 初始设置值
|
|
@@ -87,14 +198,22 @@ export async function installOpenSettingsSection(
|
|
|
87
198
|
hooks: { setSource: (source: () => FileOpenSettings) => void; onChange: () => void },
|
|
88
199
|
loader: SettingsDepsLoader = loadSettingsDeps,
|
|
89
200
|
): Promise<boolean> {
|
|
90
|
-
|
|
201
|
+
let deps: SettingsDeps | null = null
|
|
202
|
+
try {
|
|
203
|
+
deps = await loader()
|
|
204
|
+
} catch {
|
|
205
|
+
/* 装载失败按缺失处理 */
|
|
206
|
+
}
|
|
91
207
|
if (!deps) return false
|
|
92
208
|
const schema = deps.z.object({
|
|
93
209
|
fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
|
|
94
210
|
keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS }),
|
|
95
211
|
})
|
|
96
|
-
|
|
97
|
-
|
|
212
|
+
const strategy = await runSettingsInstall(ctx, ns, schema, entry, {
|
|
213
|
+
setSource: (source) => hooks.setSource(source as () => FileOpenSettings),
|
|
214
|
+
onChange: hooks.onChange,
|
|
215
|
+
}, loader)
|
|
216
|
+
return strategy === 'legacy' || strategy === 'service'
|
|
98
217
|
}
|
|
99
218
|
|
|
100
219
|
/**
|
package/src/host-deps.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ declare module 'schemastery' {
|
|
|
3
3
|
export default schema
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
declare module '@deepseek-ai/dsh-settings' {
|
|
7
|
-
export function installSettingsSection(...args: any[]): void
|
|
8
|
-
export function settingsNamespace(value: string): string
|
|
9
|
-
}
|
|
10
|
-
|
|
11
6
|
declare module '@deepseek-ai/schemastery' {
|
|
12
7
|
const schema: any
|
|
13
8
|
export default schema
|
|
14
9
|
}
|
|
10
|
+
|
|
11
|
+
// @deepseek-ai/dsh-settings 不在此声明:rc 线导出 installSettingsSection /
|
|
12
|
+
// settingsNamespace,0.1.2-alpha 起移除(改由 settings 服务 installSection 方法承载)。
|
|
13
|
+
// fileOpenSettings.ts 以动态导入 + 属性探测方式访问,见 SettingsDeps.installSettingsSection。
|
|
14
|
+
|
package/src/index.ts
CHANGED
|
@@ -92,7 +92,8 @@ async function logCompatSummary(ctx: Ctx, warnings: string[]): Promise<void> {
|
|
|
92
92
|
for (const warning of warnings) report.warnings.push(warning)
|
|
93
93
|
const logger = ctx.logger
|
|
94
94
|
if (!logger?.info) return
|
|
95
|
-
const
|
|
95
|
+
const dsh = report.dshVersion ? 'DSH ' + report.dshVersion : 'DSH 未探测'
|
|
96
|
+
const head = '[' + PLUGIN_NAME + '] 兼容性:' + dsh + ' · ' + report.external.length + ' 项外部适配 / ' + report.guards.length + ' 项护栏 / ' + (report.adapters?.length ?? 0) + ' 项版本适配'
|
|
96
97
|
if (report.warnings.length) {
|
|
97
98
|
logger.warn?.(head + ',警告 ' + report.warnings.length + ' 条:' + report.warnings.join(';'))
|
|
98
99
|
} else {
|
package/src/lsp/client.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* 作者 ddj 2026-08-27
|
|
6
6
|
*/
|
|
7
7
|
import { createFrameParser, encodeMessage, requestMessage, notifyMessage } from './jsonrpc.js'
|
|
8
|
-
import type
|
|
8
|
+
import { LSP_SEMANTIC_TOKEN_TYPES, LSP_SEMANTIC_TOKEN_MODIFIERS, type LspServerCapabilities } from '../shared/lsp.js'
|
|
9
9
|
|
|
10
10
|
export interface LspClientEvents {
|
|
11
11
|
onReady?: (capabilities: LspServerCapabilities) => void
|
|
@@ -54,7 +54,16 @@ export function createLspClient(
|
|
|
54
54
|
let nextId = 1
|
|
55
55
|
let alive = transport.alive
|
|
56
56
|
const pending = new Map<number, { resolve: (v: unknown) => void; reject: (e: Error) => void; timer: ReturnType<typeof setTimeout> }>()
|
|
57
|
-
let serverCapabilities: LspServerCapabilities = {
|
|
57
|
+
let serverCapabilities: LspServerCapabilities = {
|
|
58
|
+
definition: false,
|
|
59
|
+
references: false,
|
|
60
|
+
documentSymbol: false,
|
|
61
|
+
workspaceSymbol: false,
|
|
62
|
+
hover: false,
|
|
63
|
+
semanticTokens: false,
|
|
64
|
+
semanticTokenTypes: [],
|
|
65
|
+
semanticTokenModifiers: [],
|
|
66
|
+
}
|
|
58
67
|
|
|
59
68
|
const log = (line: string): void => events.onLog?.(line)
|
|
60
69
|
const parser = createFrameParser()
|
|
@@ -153,6 +162,9 @@ export function createLspClient(
|
|
|
153
162
|
documentSymbolProvider?: unknown
|
|
154
163
|
workspaceSymbolProvider?: unknown
|
|
155
164
|
hoverProvider?: unknown
|
|
165
|
+
semanticTokensProvider?: {
|
|
166
|
+
legend?: { tokenTypes?: unknown; tokenModifiers?: unknown }
|
|
167
|
+
}
|
|
156
168
|
}
|
|
157
169
|
}>('initialize', {
|
|
158
170
|
processId: process.pid,
|
|
@@ -170,16 +182,31 @@ export function createLspClient(
|
|
|
170
182
|
references: { dynamicRegistration: false },
|
|
171
183
|
documentSymbol: { dynamicRegistration: false },
|
|
172
184
|
hover: { dynamicRegistration: false },
|
|
185
|
+
semanticTokens: {
|
|
186
|
+
dynamicRegistration: false,
|
|
187
|
+
requests: { range: false, full: { delta: false } },
|
|
188
|
+
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
189
|
+
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS],
|
|
190
|
+
formats: ['relative'],
|
|
191
|
+
},
|
|
173
192
|
},
|
|
174
193
|
},
|
|
175
194
|
})
|
|
176
195
|
const caps = init?.capabilities ?? {}
|
|
196
|
+
const legend = caps.semanticTokensProvider?.legend
|
|
177
197
|
serverCapabilities = {
|
|
178
198
|
definition: Boolean(caps.definitionProvider),
|
|
179
199
|
references: Boolean(caps.referencesProvider),
|
|
180
200
|
documentSymbol: Boolean(caps.documentSymbolProvider),
|
|
181
201
|
workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
|
|
182
202
|
hover: Boolean(caps.hoverProvider),
|
|
203
|
+
semanticTokens: Boolean(caps.semanticTokensProvider),
|
|
204
|
+
semanticTokenTypes: Array.isArray(legend?.tokenTypes)
|
|
205
|
+
? legend.tokenTypes.filter((item): item is string => typeof item === 'string')
|
|
206
|
+
: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
207
|
+
semanticTokenModifiers: Array.isArray(legend?.tokenModifiers)
|
|
208
|
+
? legend.tokenModifiers.filter((item): item is string => typeof item === 'string')
|
|
209
|
+
: [...LSP_SEMANTIC_TOKEN_MODIFIERS],
|
|
183
210
|
}
|
|
184
211
|
client.notify('initialized', {})
|
|
185
212
|
events.onReady?.(serverCapabilities)
|
package/src/lsp/manager.ts
CHANGED
|
@@ -25,6 +25,10 @@ export interface LspManager {
|
|
|
25
25
|
release(root: string, languageId: string): void
|
|
26
26
|
/** 释放工作区下全部语言(session/disposed 用)。 */
|
|
27
27
|
releaseRoot(root: string): void
|
|
28
|
+
/** 重置单个工作区语言 server(重新检测 provider 用)。 */
|
|
29
|
+
reset(root: string, languageId: string): void
|
|
30
|
+
/** 重置全部工作区的指定语言 server。 */
|
|
31
|
+
resetLanguage(languageId: string): void
|
|
28
32
|
/** 状态快照。 */
|
|
29
33
|
statusAll(): LspServerStatus[]
|
|
30
34
|
/** 查询已注册的工作区+语言服务器,不增加引用计数。 */
|
|
@@ -110,6 +114,17 @@ export function createLspManager(logger?: (line: string) => void): LspManager {
|
|
|
110
114
|
}
|
|
111
115
|
},
|
|
112
116
|
|
|
117
|
+
reset(root: string, languageId: string): void {
|
|
118
|
+
const entry = entries.get(keyOf(root, languageId))
|
|
119
|
+
if (entry) dropEntry(entry)
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
resetLanguage(languageId: string): void {
|
|
123
|
+
for (const entry of [...entries.values()]) {
|
|
124
|
+
if (entry.server.languageId === languageId) dropEntry(entry)
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
|
|
113
128
|
statusAll(): LspServerStatus[] {
|
|
114
129
|
return [...entries.values()].map((entry) => entry.server.status())
|
|
115
130
|
},
|