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/index.js CHANGED
@@ -128,18 +128,9 @@ const FILE_OPEN_SETTINGS_NS = "dsh-vscode-mode";
128
128
  const FILE_OPEN_DEFAULT = "auto";
129
129
  let depsPromise;
130
130
  /**
131
- * 按命令目录构建 keybindings 显式键形状(每键独立默认值)。
132
- * @author ddj 2026年08月26号
133
- * @param z schemastery 命名空间
134
- * @returns keybindings 字段形状
135
- */
136
- function keybindingsShape(z) {
137
- const shape = {};
138
- for (const [id, chord] of Object.entries(KEYBINDING_DEFAULTS)) shape[id] = z.string().default(chord);
139
- return shape;
140
- }
141
- /**
142
- * 动态加载设置依赖(模块级缓存;任一缺失返回 null 而非抛错)。
131
+ * 动态加载设置依赖(模块级缓存;任一缺失/失败返回 null 而非抛错)。
132
+ * installSettingsSection 仅在 rc 线 dsh-settings 中存在时提供(alpha 起移除,
133
+ * 属性探测得到 undefined,不抛错)。
143
134
  * @author ddj 2026年08月24号
144
135
  * @returns 设置依赖或 null
145
136
  */
@@ -150,6 +141,101 @@ function loadSettingsDeps() {
150
141
  })).catch(() => null);
151
142
  return depsPromise;
152
143
  }
144
+ const INSTALL_UNMOUNTED = "设置 section 尚未装配";
145
+ const INSTALL_LEGACY = "rc 线:dsh-settings.installSettingsSection";
146
+ const INSTALL_SERVICE = "0.1.2-alpha 线:settings 服务 installSection";
147
+ const INSTALL_NONE = "两路均不可用:设置持久化降级为配置值";
148
+ let observedInstall = {
149
+ strategy: "unknown",
150
+ note: INSTALL_UNMOUNTED
151
+ };
152
+ /** 记录最近一次安装策略(供兼容报告展示;失败路径也记录,便于诊断)。 */
153
+ function recordInstall(strategy, note) {
154
+ observedInstall = {
155
+ strategy,
156
+ note
157
+ };
158
+ return strategy;
159
+ }
160
+ /** 读取观测到的安装策略(供报告;测试可用 reset 复位)。 */
161
+ function settingsInstallStrategy() {
162
+ return observedInstall.strategy;
163
+ }
164
+ /** 读取观测到的安装策略说明文案。 */
165
+ function settingsInstallNote() {
166
+ return observedInstall.note;
167
+ }
168
+ /**
169
+ * 设置 section 版本自适应安装(核心策略分派)。
170
+ * legacy:dsh-settings 仍导出 installSettingsSection(rc 线)→ 原样调用,行为与旧版一致。
171
+ * service:该导出已移除(0.1.2-alpha 起)→ 经 ctx.inject(['settings']) 走服务方法
172
+ * provider.installSection(owner, ns, schema, entry, hooks)(等义封装,含
173
+ * base 层与 fiber 卸载回退)。回调内方法缺失再降级记录 none。
174
+ * none:两条路由都不存在 → 仅记录并告警,调用方按配置值运行。
175
+ * 全程不抛错、不产生未捕获 rejection。
176
+ * @author ddj 2026年09月02号
177
+ * @param ctx DSH host 上下文(inject 可选探测)
178
+ * @param ns 设置命名空间
179
+ * @param schema schemastery schema
180
+ * @param entry 初始设置值(作为 base 层)
181
+ * @param hooks 设置源绑定与变更回调
182
+ * @param loader 依赖装载器(测试注入)
183
+ * @returns 采用/观测到的策略
184
+ */
185
+ async function runSettingsInstall(ctx, ns, schema, entry, hooks, loader = loadSettingsDeps) {
186
+ let deps = null;
187
+ try {
188
+ deps = await loader();
189
+ } catch {}
190
+ if (!deps) {
191
+ console.warn("[dsh-vscode-mode] 设置依赖不可用,section " + ns + " 未安装(配置回退)");
192
+ return recordInstall("none", INSTALL_NONE);
193
+ }
194
+ const legacy = deps.installSettingsSection;
195
+ if (typeof legacy === "function") try {
196
+ legacy(ctx, ns, schema, entry, hooks);
197
+ return recordInstall("legacy", INSTALL_LEGACY);
198
+ } catch (error) {
199
+ console.warn("[dsh-vscode-mode] legacy 设置安装失败,尝试服务路由:" + String(error));
200
+ }
201
+ const ctxInject = ctx.inject;
202
+ if (typeof ctxInject !== "function") return recordInstall("none", INSTALL_NONE);
203
+ try {
204
+ ctxInject(["settings"], (sctx) => {
205
+ const sc = sctx;
206
+ const provider = typeof sc.get === "function" ? sc.get("settings") : sc.settings;
207
+ const install = provider?.installSection;
208
+ if (typeof install !== "function") {
209
+ console.warn("[dsh-vscode-mode] settings 服务无 installSection(DSH 版本 API 变化),section " + ns + " 降级为配置值");
210
+ recordInstall("none", INSTALL_NONE);
211
+ return;
212
+ }
213
+ try {
214
+ install.call(provider, ctx, ns, schema, entry, hooks);
215
+ recordInstall("service", INSTALL_SERVICE);
216
+ } catch (error) {
217
+ console.warn("[dsh-vscode-mode] settings.installSection 安装失败(" + String(error) + "),section " + ns + " 降级为配置值");
218
+ recordInstall("none", INSTALL_NONE);
219
+ }
220
+ });
221
+ } catch (error) {
222
+ console.warn("[dsh-vscode-mode] settings 服务路由不可用(" + String(error) + ")");
223
+ return recordInstall("none", INSTALL_NONE);
224
+ }
225
+ if (observedInstall.strategy !== "unknown") return observedInstall.strategy;
226
+ return recordInstall("service", "已调度 settings 服务 installSection(等待 settings 就绪)");
227
+ }
228
+ /**
229
+ * 按命令目录构建 keybindings 显式键形状(每键独立默认值)。
230
+ * @author ddj 2026年08月26号
231
+ * @param z schemastery 命名空间
232
+ * @returns keybindings 字段形状
233
+ */
234
+ function keybindingsShape(z) {
235
+ const shape = {};
236
+ for (const [id, chord] of Object.entries(KEYBINDING_DEFAULTS)) shape[id] = z.string().default(chord);
237
+ return shape;
238
+ }
153
239
  function normalizeValue(value) {
154
240
  if (typeof value !== "string" || value.trim() === "") return FILE_OPEN_DEFAULT;
155
241
  return value.trim();
@@ -158,8 +244,8 @@ function configValue(config) {
158
244
  return normalizeValue(config?.fileOpenTool);
159
245
  }
160
246
  /**
161
- * 安装设置 section(动态依赖;缺失返回 false 不抛错)。
162
- * @author ddj 2026年08月24号
247
+ * 安装设置 section(版本自适应;任一策略不可用返回 false 不抛错)。
248
+ * @author ddj 2026年08月24号 / 2026年09月02号
163
249
  * @param ctx DSH host 上下文
164
250
  * @param ns 设置命名空间
165
251
  * @param entry 初始设置值
@@ -168,14 +254,19 @@ function configValue(config) {
168
254
  * @returns 是否成功安装
169
255
  */
170
256
  async function installOpenSettingsSection(ctx, ns, entry, hooks, loader = loadSettingsDeps) {
171
- const deps = await loader();
257
+ let deps = null;
258
+ try {
259
+ deps = await loader();
260
+ } catch {}
172
261
  if (!deps) return false;
173
- const schema = deps.z.object({
262
+ const strategy = await runSettingsInstall(ctx, ns, deps.z.object({
174
263
  fileOpenTool: deps.z.string().default(FILE_OPEN_DEFAULT),
175
264
  keybindings: deps.z.object(keybindingsShape(deps.z)).default({ ...KEYBINDING_DEFAULTS })
176
- });
177
- deps.installSettingsSection(ctx, ns, schema, entry, hooks);
178
- return true;
265
+ }), entry, {
266
+ setSource: (source) => hooks.setSource(source),
267
+ onChange: hooks.onChange
268
+ }, loader);
269
+ return strategy === "legacy" || strategy === "service";
179
270
  }
180
271
  /**
181
272
  * 注册设置命名空间,并提供 host 侧的读取与更新状态。
@@ -370,6 +461,101 @@ async function runPnpmInstall(ctx, profileDir) {
370
461
  }
371
462
  }
372
463
  //#endregion
464
+ //#region src/dshVersion.ts
465
+ /**
466
+ * dsh-vscode-mode host — DSH 核心版本探测与区间匹配(版本适配机制的地基)。
467
+ * 约定:以运行时可解析的 @deepseek-ai/dsh-settings 包版本代表 DSH 核心版本——
468
+ * 核心包发布锁步同版(rc.2 / alpha.2 全树同版实证),且该包是插件动态依赖面、
469
+ * exports 已放行 ./package.json。解析失败按候选降级(dsh-web-app / dsh-base / dsh),
470
+ * 全部失败返回空串(报告显示"未探测",功能按能力探测运行)。
471
+ * 纯函数(parse / compare / inRange / familyLabel)可在 node 环境单测。
472
+ * 作者 ddj 2026-09-02
473
+ */
474
+ const VERSION_RE = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
475
+ /** 解析版本串;非法返回 null(不抛错)。 */
476
+ function parseDshVersion(input) {
477
+ const match = VERSION_RE.exec(input.trim());
478
+ if (!match) return null;
479
+ return {
480
+ major: Number(match[1]),
481
+ minor: Number(match[2]),
482
+ patch: Number(match[3]),
483
+ pre: match[4] ? match[4].split(".") : []
484
+ };
485
+ }
486
+ /** 预发布标识比较:纯数字按值;数字段 < 字母段;字母按字典序(semver 规则)。 */
487
+ function comparePreId(a, b) {
488
+ const numericA = /^\d+$/.test(a);
489
+ const numericB = /^\d+$/.test(b);
490
+ if (numericA && numericB) return Number(a) - Number(b);
491
+ if (numericA !== numericB) return numericA ? -1 : 1;
492
+ return a < b ? -1 : a > b ? 1 : 0;
493
+ }
494
+ /** 比较两个已解析版本:a<b → -1;相等 → 0;a>b → 1。 */
495
+ function compareDshVersions(a, b) {
496
+ for (const key of [
497
+ "major",
498
+ "minor",
499
+ "patch"
500
+ ]) if (a[key] !== b[key]) return a[key] < b[key] ? -1 : 1;
501
+ if (a.pre.length === b.pre.length && a.pre.every((part, index) => part === b.pre[index])) return 0;
502
+ if (a.pre.length === 0) return 1;
503
+ if (b.pre.length === 0) return -1;
504
+ const length = Math.max(a.pre.length, b.pre.length);
505
+ for (let i = 0; i < length; i++) {
506
+ const left = a.pre[i];
507
+ const right = b.pre[i];
508
+ if (left === void 0) return -1;
509
+ if (right === void 0) return 1;
510
+ const cmp = comparePreId(left, right);
511
+ if (cmp !== 0) return cmp;
512
+ }
513
+ return 0;
514
+ }
515
+ /** 版本是否落在区间内(入参解析失败一律不命中)。 */
516
+ function inDshRange(version, range) {
517
+ if (!version) return false;
518
+ if (range.from) {
519
+ const lower = parseDshVersion(range.from);
520
+ if (!lower || compareDshVersions(version, lower) < 0) return false;
521
+ }
522
+ if (range.to) {
523
+ const upper = parseDshVersion(range.to);
524
+ if (!upper || compareDshVersions(version, upper) > 0) return false;
525
+ }
526
+ return true;
527
+ }
528
+ /** 版本线标签:报告与文档展示版本归属;不可解析返回 '未知'。 */
529
+ function familyLabel(input) {
530
+ const version = parseDshVersion(input);
531
+ if (!version) return "未知";
532
+ if (inDshRange(version, { from: "0.1.2-alpha.1" })) return "0.1.2-alpha 及更新(设置 API=settings.installSection)";
533
+ return "0.1.0/0.1.1 rc 线(设置 API=installSettingsSection)";
534
+ }
535
+ /** 探测候选:按与插件运行环境相关性排序,逐个尝试解析 package.json。 */
536
+ const VERSION_CANDIDATES = [
537
+ "@deepseek-ai/dsh-settings/package.json",
538
+ "@deepseek-ai/dsh-web-app/package.json",
539
+ "@deepseek-ai/dsh-base/package.json",
540
+ "@deepseek-ai/dsh/package.json"
541
+ ];
542
+ let detectedVersion;
543
+ /** 探测运行中 DSH 核心版本(模块级缓存;失败空串,不抛错)。 */
544
+ function detectDshVersion() {
545
+ if (detectedVersion !== void 0) return detectedVersion;
546
+ detectedVersion = "";
547
+ const require = createRequire(import.meta.url);
548
+ for (const specifier of VERSION_CANDIDATES) try {
549
+ const file = require.resolve(specifier);
550
+ const pkg = JSON.parse(readFileSync(file, "utf8"));
551
+ if (typeof pkg.version === "string" && parseDshVersion(pkg.version) !== null) {
552
+ detectedVersion = pkg.version;
553
+ break;
554
+ }
555
+ } catch {}
556
+ return detectedVersion;
557
+ }
558
+ //#endregion
373
559
  //#region src/shared/rpc.ts
374
560
  /** webServer 精确路由。 */
375
561
  const RPC_PATH = "/edrv/rpc";
@@ -485,6 +671,14 @@ function pluginCacheRoot(home = dshHome()) {
485
671
  function workspaceCacheDir(workspaceId, home = dshHome()) {
486
672
  return join(pluginCacheRoot(home), "workspace", workspaceId);
487
673
  }
674
+ /** 用户级缓存目录(跨工作区共享;当前无实例,预留供全局偏好类缓存使用)。 */
675
+ function userCacheDir(home = dshHome()) {
676
+ return join(pluginCacheRoot(home), "user");
677
+ }
678
+ /** LSP provider 发现结果缓存文件(用户级:user/lsp-specs.v<schema>.json,跨会话/跨重启共享)。 */
679
+ function lspSpecCacheFile(home = dshHome(), schema = 4) {
680
+ return join(userCacheDir(home), "lsp-specs.v" + schema + ".json");
681
+ }
488
682
  /** 插件日志根(~/.dsh/dsh-vscode-mode/logs,用户级)。 */
489
683
  function pluginLogRoot(home = dshHome()) {
490
684
  return join(home, PLUGIN_ID, "logs");
@@ -737,26 +931,63 @@ function detectGuards(ctx) {
737
931
  note: duplicates[0] ?? "唯一装配"
738
932
  }];
739
933
  }
934
+ /** 已实测覆盖的最高 DSH 版本(适配矩阵上界,超过则提示,见 buildReport)。 */
935
+ const TESTED_DSH_MAX = "0.1.2-alpha.4";
936
+ /** 版本适配机制状态行:DSH 版本探测 + 设置 section 安装策略。 */
937
+ function versionAdapters(dshVersion) {
938
+ const adapters = [];
939
+ const parsed = parseDshVersion(dshVersion);
940
+ if (dshVersion === "") adapters.push({
941
+ name: "DSH 版本",
942
+ active: false,
943
+ note: "未探测到版本号(能力探测模式运行)"
944
+ });
945
+ else if (!parsed) adapters.push({
946
+ name: "DSH 版本",
947
+ active: false,
948
+ note: "无法解析版本号 " + dshVersion + "(能力探测模式运行)"
949
+ });
950
+ else adapters.push({
951
+ name: "DSH 版本",
952
+ active: true,
953
+ note: dshVersion + " · " + familyLabel(dshVersion)
954
+ });
955
+ const strategy = settingsInstallStrategy();
956
+ adapters.push({
957
+ name: "设置 section 安装(版本适配)",
958
+ active: strategy === "legacy" || strategy === "service",
959
+ note: settingsInstallNote()
960
+ });
961
+ return adapters;
962
+ }
740
963
  /**
741
964
  * 构建完整兼容性报告(RPC 与启动日志共用)。
742
- * @author ddj 2026年08月24号
965
+ * @author ddj 2026年08月24号 / 2026年09月02号
743
966
  * @param ctx DSH host 上下文
744
- * @param options 测试注入:depsAvailable 跳过动态导入、version 固定版本号
967
+ * @param options 测试注入:depsAvailable 跳过动态导入、version 固定插件版本、dshVersion 固定 DSH 版本
745
968
  * @returns 兼容性报告
746
969
  */
747
970
  async function buildReport(ctx, options) {
748
971
  const deps = options?.depsAvailable ?? await loadSettingsDeps() !== null;
972
+ const dshVersion = options?.dshVersion ?? detectDshVersion();
749
973
  const external = detectExternal(ctx, deps);
750
974
  const guards = detectGuards(ctx);
975
+ const adapters = versionAdapters(dshVersion);
751
976
  const warnings = [];
752
977
  if (!deps) warnings.push("未安装 @deepseek-ai/dsh-settings:文件打开工具设置持久化不可用(降级为配置值)");
978
+ if (settingsInstallStrategy() === "none") warnings.push("设置 section 安装不可用:" + settingsInstallNote());
979
+ const parsed = parseDshVersion(dshVersion);
980
+ const testedMax = parseDshVersion(TESTED_DSH_MAX);
981
+ if (parsed && testedMax && compareDshVersions(parsed, testedMax) > 0) warnings.push("DSH " + dshVersion + " 高于已实测版本(0.1.2-alpha.4):设置 API 按能力探测运行,异常时请回报适配矩阵");
753
982
  for (const guard of guards) if (!guard.active && guard.note) warnings.push(guard.note);
754
983
  return {
755
984
  pluginVersion: options?.version ?? pluginVersionOf(),
756
985
  external,
757
986
  guards,
987
+ adapters,
758
988
  warnings,
759
- devForm: readDevForm()
989
+ devForm: readDevForm(),
990
+ dshVersion
760
991
  };
761
992
  }
762
993
  //#endregion
@@ -4378,7 +4609,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
4378
4609
  const info = await fs.stat(target);
4379
4610
  if (!info || info.type !== "file") return {
4380
4611
  ok: false,
4381
- error: "文件不存在"
4612
+ error: "文件不存在",
4613
+ resolvedPath: fs.processPath(target)
4382
4614
  };
4383
4615
  if ((info.size ?? 0) > 8388608) return {
4384
4616
  ok: false,
@@ -5453,6 +5685,50 @@ function disposeAllServers() {
5453
5685
  running.clear();
5454
5686
  }
5455
5687
  //#endregion
5688
+ //#region src/shared/lsp.ts
5689
+ /** LSP semantic token 标准类型(host 归一化后,client 与 Monaco 共用)。 */
5690
+ const LSP_SEMANTIC_TOKEN_TYPES = [
5691
+ "namespace",
5692
+ "type",
5693
+ "class",
5694
+ "enum",
5695
+ "interface",
5696
+ "struct",
5697
+ "typeParameter",
5698
+ "parameter",
5699
+ "variable",
5700
+ "property",
5701
+ "enumMember",
5702
+ "event",
5703
+ "function",
5704
+ "method",
5705
+ "macro",
5706
+ "label",
5707
+ "comment",
5708
+ "string",
5709
+ "keyword",
5710
+ "number",
5711
+ "regexp",
5712
+ "operator",
5713
+ "decorator"
5714
+ ];
5715
+ /** LSP semantic token 标准修饰符(host 归一化后,client 与 Monaco 共用)。 */
5716
+ const LSP_SEMANTIC_TOKEN_MODIFIERS = [
5717
+ "declaration",
5718
+ "definition",
5719
+ "readonly",
5720
+ "static",
5721
+ "deprecated",
5722
+ "abstract",
5723
+ "async",
5724
+ "modification",
5725
+ "documentation",
5726
+ "defaultLibrary",
5727
+ "global",
5728
+ "local",
5729
+ "mutable"
5730
+ ];
5731
+ //#endregion
5456
5732
  //#region src/lsp/client.ts
5457
5733
  /**
5458
5734
  * dsh-vscode-mode host — LSP 客户端(JSON-RPC over stdio)。
@@ -5484,7 +5760,10 @@ function createLspClient(transport, events = {}) {
5484
5760
  references: false,
5485
5761
  documentSymbol: false,
5486
5762
  workspaceSymbol: false,
5487
- hover: false
5763
+ hover: false,
5764
+ semanticTokens: false,
5765
+ semanticTokenTypes: [],
5766
+ semanticTokenModifiers: []
5488
5767
  };
5489
5768
  const log = (line) => events.onLog?.(line);
5490
5769
  createFrameParser();
@@ -5614,16 +5893,30 @@ function createLspClient(transport, events = {}) {
5614
5893
  definition: { dynamicRegistration: false },
5615
5894
  references: { dynamicRegistration: false },
5616
5895
  documentSymbol: { dynamicRegistration: false },
5617
- hover: { dynamicRegistration: false }
5896
+ hover: { dynamicRegistration: false },
5897
+ semanticTokens: {
5898
+ dynamicRegistration: false,
5899
+ requests: {
5900
+ range: false,
5901
+ full: { delta: false }
5902
+ },
5903
+ tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
5904
+ tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS],
5905
+ formats: ["relative"]
5906
+ }
5618
5907
  }
5619
5908
  }
5620
5909
  }))?.capabilities ?? {};
5910
+ const legend = caps.semanticTokensProvider?.legend;
5621
5911
  serverCapabilities = {
5622
5912
  definition: Boolean(caps.definitionProvider),
5623
5913
  references: Boolean(caps.referencesProvider),
5624
5914
  documentSymbol: Boolean(caps.documentSymbolProvider),
5625
5915
  workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
5626
- hover: Boolean(caps.hoverProvider)
5916
+ hover: Boolean(caps.hoverProvider),
5917
+ semanticTokens: Boolean(caps.semanticTokensProvider),
5918
+ semanticTokenTypes: Array.isArray(legend?.tokenTypes) ? legend.tokenTypes.filter((item) => typeof item === "string") : [...LSP_SEMANTIC_TOKEN_TYPES],
5919
+ semanticTokenModifiers: Array.isArray(legend?.tokenModifiers) ? legend.tokenModifiers.filter((item) => typeof item === "string") : [...LSP_SEMANTIC_TOKEN_MODIFIERS]
5627
5920
  };
5628
5921
  client.notify("initialized", {});
5629
5922
  events.onReady?.(serverCapabilities);
@@ -5697,7 +5990,10 @@ function createLspServer(spec, root, languageId, logger) {
5697
5990
  references: false,
5698
5991
  documentSymbol: false,
5699
5992
  workspaceSymbol: false,
5700
- hover: false
5993
+ hover: false,
5994
+ semanticTokens: false,
5995
+ semanticTokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
5996
+ semanticTokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
5701
5997
  };
5702
5998
  let disposed = false;
5703
5999
  let readyWait = Promise.resolve(false);
@@ -5708,9 +6004,15 @@ function createLspServer(spec, root, languageId, logger) {
5708
6004
  source: spec.kind,
5709
6005
  phase: "idle",
5710
6006
  reason: spec.ready ? void 0 : spec.reason,
5711
- root
6007
+ root,
6008
+ version: spec.version,
6009
+ providerName: spec.providerName ?? (spec.version ? "语言服务器扩展" : void 0)
5712
6010
  };
5713
6011
  const log = (line) => logger?.("[" + languageId + "@" + root + "] " + line);
6012
+ const providerInfo = {
6013
+ version: spec.version,
6014
+ providerName: spec.providerName ?? (spec.version ? "EmmyLua" : void 0)
6015
+ };
5714
6016
  const setPhase = (next, reason) => {
5715
6017
  phase = next;
5716
6018
  lastStatus = {
@@ -5719,6 +6021,7 @@ function createLspServer(spec, root, languageId, logger) {
5719
6021
  phase: next,
5720
6022
  reason,
5721
6023
  root,
6024
+ ...providerInfo,
5722
6025
  ...progressFields()
5723
6026
  };
5724
6027
  server.onStateChange?.(lastStatus);
@@ -5913,6 +6216,17 @@ function createLspServer(spec, root, languageId, logger) {
5913
6216
  if (!result || !result.contents) return null;
5914
6217
  return { contents: stringifyHoverContents(result.contents) };
5915
6218
  },
6219
+ /** 查询全文 semantic tokens,并将服务器 legend 归一到插件固定 legend。 */
6220
+ async semanticTokens(path) {
6221
+ const doc = docs.get(path);
6222
+ if (!doc || !await readyWait || !capabilities.semanticTokens) return null;
6223
+ const result = await ensureClient().request("textDocument/semanticTokens/full", { textDocument: { uri: doc.uri } });
6224
+ if (!result || !Array.isArray(result.data)) return null;
6225
+ return {
6226
+ data: normalizeSemanticData(result.data, capabilities.semanticTokenTypes ?? [], capabilities.semanticTokenModifiers ?? []),
6227
+ resultId: typeof result.resultId === "string" ? result.resultId : void 0
6228
+ };
6229
+ },
5916
6230
  async dispose() {
5917
6231
  disposed = true;
5918
6232
  resolveReady?.(false);
@@ -6021,6 +6335,39 @@ function toInt(value) {
6021
6335
  const n = Number(value);
6022
6336
  return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
6023
6337
  }
6338
+ /** 把服务器 legend 下的 5 元组流转换为插件固定 legend 下的 5 元组流。 */
6339
+ function normalizeSemanticData(data, types, modifiers) {
6340
+ const out = [];
6341
+ let previousLine = 0;
6342
+ let previousCharacter = 0;
6343
+ let outputLine = 0;
6344
+ let outputCharacter = 0;
6345
+ for (let i = 0; i + 4 < data.length; i += 5) {
6346
+ const deltaLine = toInt(data[i]);
6347
+ const deltaCharacter = toInt(data[i + 1]);
6348
+ const length = toInt(data[i + 2]);
6349
+ const sourceType = types[toInt(data[i + 3])] ?? "variable";
6350
+ const sourceModifiers = toInt(data[i + 4]);
6351
+ const line = previousLine + deltaLine;
6352
+ const character = deltaLine === 0 ? previousCharacter + deltaCharacter : deltaCharacter;
6353
+ const typeIndex = Math.max(0, LSP_SEMANTIC_TOKEN_TYPES.indexOf(sourceType));
6354
+ let modifierBits = 0;
6355
+ for (let bit = 0; bit < modifiers.length; bit++) {
6356
+ if ((sourceModifiers & 1 << bit) === 0) continue;
6357
+ const modifier = modifiers[bit];
6358
+ const targetBit = LSP_SEMANTIC_TOKEN_MODIFIERS.indexOf(modifier);
6359
+ if (targetBit >= 0) modifierBits |= 1 << targetBit;
6360
+ }
6361
+ const outputDeltaLine = line - outputLine;
6362
+ const outputDeltaCharacter = outputDeltaLine === 0 ? character - outputCharacter : character;
6363
+ out.push(outputDeltaLine, outputDeltaCharacter, length, typeIndex, modifierBits);
6364
+ previousLine = line;
6365
+ previousCharacter = character;
6366
+ outputLine = line;
6367
+ outputCharacter = character;
6368
+ }
6369
+ return out;
6370
+ }
6024
6371
  //#endregion
6025
6372
  //#region src/lsp/manager.ts
6026
6373
  /**
@@ -6079,6 +6426,13 @@ function createLspManager(logger) {
6079
6426
  releaseRoot(root) {
6080
6427
  for (const entry of [...entries.values()]) if (entry.server.root === root) dropEntry(entry);
6081
6428
  },
6429
+ reset(root, languageId) {
6430
+ const entry = entries.get(keyOf(root, languageId));
6431
+ if (entry) dropEntry(entry);
6432
+ },
6433
+ resetLanguage(languageId) {
6434
+ for (const entry of [...entries.values()]) if (entry.server.languageId === languageId) dropEntry(entry);
6435
+ },
6082
6436
  statusAll() {
6083
6437
  return [...entries.values()].map((entry) => entry.server.status());
6084
6438
  },
@@ -6388,7 +6742,9 @@ function extensionProviderFor(languageId) {
6388
6742
  kind: "extension",
6389
6743
  argv: p.argv,
6390
6744
  cwd: p.cwd,
6391
- ready: true
6745
+ ready: true,
6746
+ version: p.version,
6747
+ providerName: p.version ? "EmmyLua" : "语言服务器扩展"
6392
6748
  };
6393
6749
  }
6394
6750
  /** PATH 中查找可执行文件(不含扩展名校验,交由 spawn 报错)。 */
@@ -6416,6 +6772,25 @@ function findDotnet() {
6416
6772
  for (const c of ["C:/Program Files/dotnet/dotnet.exe", "C:/Program Files (x86)/dotnet/dotnet.exe"]) if (existsSync(c)) return c;
6417
6773
  return null;
6418
6774
  }
6775
+ /** EmmyLua 自动发现候选(tangzx.emmylua-* / server/emmylua_ls)。 */
6776
+ function candidateEmmyLua(home = dshHome()) {
6777
+ const candidates = [];
6778
+ for (const dir of vscodeExtensionsDirs(home)) for (const extDir of listMatchingDirs(dir, "tangzx.emmylua-")) {
6779
+ const server = join(extDir, "server", "emmylua_ls" + exeSuffix());
6780
+ if (!existsSync(server)) continue;
6781
+ let version;
6782
+ try {
6783
+ const manifest = JSON.parse(readFileSync(join(extDir, "package.json"), "utf8"));
6784
+ if (typeof manifest.version === "string") version = manifest.version;
6785
+ } catch (error) {}
6786
+ candidates.push({
6787
+ path: server,
6788
+ version
6789
+ });
6790
+ }
6791
+ candidates.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.path.localeCompare(b.path));
6792
+ return candidates[0] ?? null;
6793
+ }
6419
6794
  /** LuaLS 自动发现候选(VSCode 扩展目录 + extmgr 目录下 sumneko.lua-* 的 server/bin 可执行)。 */
6420
6795
  function candidateLuaServers(home = dshHome()) {
6421
6796
  const plat = platformServerDir();
@@ -6497,30 +6872,41 @@ function resolveLuaProvider(manual, home = dshHome()) {
6497
6872
  cwd: manualResolved.cwd,
6498
6873
  ready: true
6499
6874
  };
6500
- const ext = extensionProviderFor("lua");
6501
- if (ext) return ext;
6502
- const candidates = candidateLuaServers(home);
6503
- if (candidates.length) return {
6504
- ...base,
6505
- kind: "discover",
6506
- argv: [candidates[0]],
6507
- cwd: void 0,
6508
- ready: true
6509
- };
6510
- const inPath = findInPath("lua-language-server" + exeSuffix());
6511
- if (inPath) return {
6512
- ...base,
6513
- kind: "discover",
6514
- argv: [inPath],
6515
- ready: true
6516
- };
6517
- return {
6518
- ...base,
6519
- kind: "none",
6520
- argv: [],
6521
- ready: false,
6522
- reason: manualResolved.reason ?? "未发现 lua-language-server(可安装 LuaLS 或手动指定路径)"
6523
- };
6875
+ return resolveCached("lua", manual, home, (h) => {
6876
+ const ext = extensionProviderFor("lua");
6877
+ if (ext) return ext;
6878
+ const emmy = candidateEmmyLua(h);
6879
+ if (emmy) return {
6880
+ ...base,
6881
+ kind: "extension",
6882
+ argv: [emmy.path],
6883
+ ready: true,
6884
+ version: emmy.version,
6885
+ providerName: "EmmyLua"
6886
+ };
6887
+ const candidates = candidateLuaServers(h);
6888
+ if (candidates.length) return {
6889
+ ...base,
6890
+ kind: "discover",
6891
+ argv: [candidates[0]],
6892
+ cwd: void 0,
6893
+ ready: true
6894
+ };
6895
+ const inPath = findInPath("lua-language-server" + exeSuffix());
6896
+ if (inPath) return {
6897
+ ...base,
6898
+ kind: "discover",
6899
+ argv: [inPath],
6900
+ ready: true
6901
+ };
6902
+ return {
6903
+ ...base,
6904
+ kind: "none",
6905
+ argv: [],
6906
+ ready: false,
6907
+ reason: manualResolved.reason ?? "未发现 lua-language-server(可安装 LuaLS 或手动指定路径)"
6908
+ };
6909
+ });
6524
6910
  }
6525
6911
  /**
6526
6912
  * 解析 C# provider(优先级:手动 > 扩展源 > 自动发现;需 dotnet + Roslyn dll;或 OmniSharp)。
@@ -6539,40 +6925,163 @@ function resolveCSharpProvider(manual, home = dshHome()) {
6539
6925
  cwd: manualResolved.cwd,
6540
6926
  ready: true
6541
6927
  };
6542
- const ext = extensionProviderFor("csharp");
6543
- if (ext) return ext;
6544
- const discovered = candidateCSharpServers(home);
6545
- if (discovered.roslynDll && discovered.dotnet) return {
6546
- ...base,
6547
- kind: "discover",
6548
- argv: [
6549
- discovered.dotnet,
6550
- discovered.roslynDll,
6551
- "--stdio"
6552
- ],
6553
- ready: true
6554
- };
6555
- if (discovered.omnisharpExe) return {
6556
- ...base,
6557
- kind: "discover",
6558
- argv: [discovered.omnisharpExe, "--stdio"],
6559
- ready: true
6560
- };
6561
- const reasons = [];
6562
- if (!discovered.dotnet) reasons.push("未找到 dotnet");
6563
- if (!discovered.roslynDll && !discovered.omnisharpExe) reasons.push("未发现 ms-dotnettools.csharp 扩展");
6564
- return {
6565
- ...base,
6566
- kind: "none",
6567
- argv: [],
6568
- ready: false,
6569
- reason: reasons.join(";") + "(可手动指定 Roslyn/OmniSharp 入口)"
6570
- };
6928
+ return resolveCached("csharp", manual, home, (h) => {
6929
+ const ext = extensionProviderFor("csharp");
6930
+ if (ext) return ext;
6931
+ const discovered = candidateCSharpServers(h);
6932
+ if (discovered.roslynDll && discovered.dotnet) return {
6933
+ ...base,
6934
+ kind: "discover",
6935
+ argv: [
6936
+ discovered.dotnet,
6937
+ discovered.roslynDll,
6938
+ "--stdio"
6939
+ ],
6940
+ ready: true
6941
+ };
6942
+ if (discovered.omnisharpExe) return {
6943
+ ...base,
6944
+ kind: "discover",
6945
+ argv: [discovered.omnisharpExe, "--stdio"],
6946
+ ready: true
6947
+ };
6948
+ const reasons = [];
6949
+ if (!discovered.dotnet) reasons.push("未找到 dotnet");
6950
+ if (!discovered.roslynDll && !discovered.omnisharpExe) reasons.push("未发现 ms-dotnettools.csharp 扩展");
6951
+ return {
6952
+ ...base,
6953
+ kind: "none",
6954
+ argv: [],
6955
+ ready: false,
6956
+ reason: reasons.join(";") + "(可手动指定 Roslyn/OmniSharp 入口)"
6957
+ };
6958
+ });
6571
6959
  }
6572
6960
  const PROVIDER_RESOLVERS = {
6573
6961
  lua: resolveLuaProvider,
6574
6962
  csharp: resolveCSharpProvider
6575
6963
  };
6964
+ /** 进程级缓存(home|languageId → 条目),同一 DSH home 的不同会话共享。 */
6965
+ const specCache = /* @__PURE__ */ new Map();
6966
+ /** 磁盘缓存是否已载入本进程(home → loaded),不同 profile 不串缓存。 */
6967
+ const diskLoaded = /* @__PURE__ */ new Set();
6968
+ /** 自动发现成功结果的复用时长;过期后才检查目录指纹。 */
6969
+ const SPEC_CACHE_TTL_MS = 18e5;
6970
+ /** 未发现结果的短缓存时长,避免缺失服务器时反复扫描 PATH。 */
6971
+ const SPEC_NEGATIVE_CACHE_MS = 6e4;
6972
+ /** 生成带 home 的缓存键,避免不同 profile 的 provider 结果串用。 */
6973
+ function cacheEntryKey(home, languageId) {
6974
+ return home + "|" + languageId;
6975
+ }
6976
+ /**
6977
+ * 构造缓存 key:语言 + 手动配置(manual 结果不缓存,故只参与"是否走缓存"判定)。
6978
+ * @author ddj 2026年08月28号
6979
+ * @param languageId 语言 id
6980
+ * @param manual 手动配置
6981
+ * @returns 缓存 key(manual 有效时返回空串表示跳过缓存)
6982
+ */
6983
+ function cacheKeyOf(languageId, manual) {
6984
+ const pathValue = manual?.path?.trim() ?? "";
6985
+ const commandValue = manual?.command?.trim() ?? "";
6986
+ if (pathValue || commandValue) return "";
6987
+ return languageId;
6988
+ }
6989
+ /**
6990
+ * 环境指纹:扩展目录清单 + 平台。目录增删/换平台即失效(防缓存陈旧指向已删扩展)。
6991
+ * @author ddj 2026年08月28号
6992
+ * @param home DSH home
6993
+ * @returns 指纹串
6994
+ */
6995
+ function fingerprintOf(home) {
6996
+ const marks = vscodeExtensionsDirs(home).map((dir) => {
6997
+ try {
6998
+ return readdirSync(dir).sort().join(",");
6999
+ } catch (error) {
7000
+ return "?";
7001
+ }
7002
+ });
7003
+ return process.platform + "|" + marks.join("|");
7004
+ }
7005
+ /**
7006
+ * 读取磁盘缓存(幂等:每进程只载一次;schema 文件名已隔离版本)。
7007
+ * @author ddj 2026年08月28号
7008
+ * @param home DSH home
7009
+ */
7010
+ function loadDiskCache(home) {
7011
+ if (diskLoaded.has(home)) return;
7012
+ diskLoaded.add(home);
7013
+ const file = lspSpecCacheFile(home);
7014
+ try {
7015
+ const raw = JSON.parse(readFileSync(file, "utf8"));
7016
+ for (const [languageId, entry] of Object.entries(raw)) if (entry && typeof entry === "object" && entry.spec && typeof entry.fingerprint === "string") specCache.set(cacheEntryKey(home, languageId), entry);
7017
+ } catch (error) {}
7018
+ }
7019
+ /**
7020
+ * 写回磁盘缓存(best-effort;写失败不影响本次解析结果)。
7021
+ * @author ddj 2026年08月28号
7022
+ * @param home DSH home
7023
+ */
7024
+ function saveDiskCache(home) {
7025
+ const file = lspSpecCacheFile(home);
7026
+ try {
7027
+ mkdirSync(dirname(file), { recursive: true });
7028
+ const out = {};
7029
+ const prefix = home + "|";
7030
+ for (const [key, entry] of specCache) if (key.startsWith(prefix)) out[key.slice(prefix.length)] = entry;
7031
+ writeFileSync(file, JSON.stringify(out), "utf8");
7032
+ } catch (error) {}
7033
+ }
7034
+ /**
7035
+ * 带缓存执行 provider 解析(自动发现路径专用)。
7036
+ * @author ddj 2026年08月28号
7037
+ * @param languageId 语言 id
7038
+ * @param manual 手动配置
7039
+ * @param home DSH home
7040
+ * @param resolve 真解析函数(未命中时调用)
7041
+ * @returns provider 规格
7042
+ */
7043
+ function resolveCached(languageId, manual, home, resolve) {
7044
+ const key = cacheKeyOf(languageId, manual);
7045
+ if (!key) return resolve(home);
7046
+ loadDiskCache(home);
7047
+ const fullKey = cacheEntryKey(home, key);
7048
+ const hit = specCache.get(fullKey);
7049
+ if (hit && isCacheFresh(hit)) return hit.spec;
7050
+ const fingerprint = fingerprintOf(home);
7051
+ if (hit && hit.fingerprint === fingerprint) {
7052
+ hit.at = Date.now();
7053
+ return hit.spec;
7054
+ }
7055
+ const spec = resolve(home);
7056
+ specCache.set(fullKey, {
7057
+ spec,
7058
+ fingerprint,
7059
+ at: Date.now()
7060
+ });
7061
+ saveDiskCache(home);
7062
+ return spec;
7063
+ }
7064
+ /** 判断缓存条目是否仍可直接复用,不触发扩展目录扫描。 */
7065
+ function isCacheFresh(entry) {
7066
+ const age = Date.now() - entry.at;
7067
+ const ttl = entry.spec.kind === "none" ? SPEC_NEGATIVE_CACHE_MS : SPEC_CACHE_TTL_MS;
7068
+ if (age < 0 || age >= ttl) return false;
7069
+ const executable = entry.spec.argv[0];
7070
+ return !entry.spec.ready || !executable || existsSync(executable);
7071
+ }
7072
+ /**
7073
+ * 清空 provider 发现缓存(扩展安装/卸载、配置变更后调用;进程级 + 删磁盘文件)。
7074
+ * @author ddj 2026年08月28号
7075
+ * @param home DSH home(缺省真实)
7076
+ */
7077
+ function clearProviderCache(home = dshHome()) {
7078
+ const prefix = home + "|";
7079
+ for (const key of specCache.keys()) if (key.startsWith(prefix)) specCache.delete(key);
7080
+ diskLoaded.delete(home);
7081
+ try {
7082
+ rmSync(lspSpecCacheFile(home), { force: true });
7083
+ } catch (error) {}
7084
+ }
6576
7085
  //#endregion
6577
7086
  //#region src/lsp/config.ts
6578
7087
  const LSP_SETTINGS_NS = "dsh-vscode-mode.languageServers";
@@ -6736,6 +7245,21 @@ function createDocTracker() {
6736
7245
  }
6737
7246
  sessionDocs.delete(sessionId);
6738
7247
  return [...toRelease];
7248
+ },
7249
+ /** 清除指定语言的文档归属,让当前客户端重新同步时重新 acquire。 */
7250
+ resetLanguage(languageId) {
7251
+ for (const [docKey, owners] of openDocs) {
7252
+ if (!docKey.includes("|" + languageId + "|")) continue;
7253
+ for (const sessionId of owners) forgetDoc(sessionId, docKey);
7254
+ openDocs.delete(docKey);
7255
+ }
7256
+ for (const key of [...rootLangRefs.keys()]) if (key.endsWith("|" + languageId)) rootLangRefs.delete(key);
7257
+ },
7258
+ /** 返回指定会话已打开的文档路径,用于重检测后恢复同步。 */
7259
+ pathsFor(sessionId, languageId) {
7260
+ const docs = sessionDocs.get(sessionId);
7261
+ if (!docs) return [];
7262
+ return [...docs].filter((key) => key.includes("|" + languageId + "|")).map((key) => key.slice(key.lastIndexOf("|") + 1));
6739
7263
  }
6740
7264
  };
6741
7265
  }
@@ -6768,6 +7292,39 @@ function createLspRpc(deps) {
6768
7292
  ...location,
6769
7293
  root
6770
7294
  }));
7295
+ /** 当前 provider 检测结论 → 设置页 idle 状态(不启动 server)。 */
7296
+ const detectedStatus = (languageId, root) => {
7297
+ const spec = resolveProviderSpec(ctx, pluginConfig, languageId);
7298
+ return {
7299
+ languageId,
7300
+ source: spec.kind,
7301
+ phase: "idle",
7302
+ reason: spec.ready ? void 0 : spec.reason,
7303
+ version: spec.version,
7304
+ providerName: spec.providerName,
7305
+ root
7306
+ };
7307
+ };
7308
+ /**
7309
+ * 清发现缓存并重置旧 server(保留 tracker 文档记录);
7310
+ * 下一次模型同步会按最新配置重新 acquire,已打开文档无需重开。
7311
+ * 作者 ddj 2026年09月02号
7312
+ * @param languageId 语言 id
7313
+ * @param sessionId 会话语境(缺省重置全部工作区该语言)
7314
+ * @returns 检测后的 idle 状态列表
7315
+ */
7316
+ const redetectLanguage = async (languageId, sessionId) => {
7317
+ clearProviderCache();
7318
+ if (sessionId) {
7319
+ const sc = await rootOf(sessionId);
7320
+ if ("err" in sc) return [];
7321
+ manager.reset(sc.root, languageId);
7322
+ return [detectedStatus(languageId, sc.root)];
7323
+ }
7324
+ const roots = [...new Set(manager.statusAll().filter((s) => s.languageId === languageId).map((s) => s.root))];
7325
+ manager.resetLanguage(languageId);
7326
+ return roots.length ? roots.map((root) => detectedStatus(languageId, root)) : [detectedStatus(languageId)];
7327
+ };
6771
7328
  const disposeSession = (sessionId) => {
6772
7329
  for (const rl of tracker.closeSession(sessionId)) {
6773
7330
  const sep = rl.lastIndexOf("|");
@@ -6825,13 +7382,15 @@ function createLspRpc(deps) {
6825
7382
  error: "设置服务不可用"
6826
7383
  };
6827
7384
  await settings.update(LSP_SETTINGS_NS, { [lang]: target });
7385
+ const servers = await redetectLanguage(lang);
6828
7386
  const stored = (settings.describe?.({ redactSecrets: true })?.find((item) => item.ns === LSP_SETTINGS_NS))?.value;
6829
7387
  return {
6830
7388
  ok: true,
6831
7389
  config: {
6832
7390
  ...configFromPlugin(pluginConfig),
6833
7391
  ...stored
6834
- }
7392
+ },
7393
+ servers
6835
7394
  };
6836
7395
  } catch (error) {
6837
7396
  return {
@@ -7006,6 +7565,51 @@ function createLspRpc(deps) {
7006
7565
  };
7007
7566
  }
7008
7567
  },
7568
+ "edrv.lsp.semanticTokens": async (args) => {
7569
+ const lang = langOfPath(args.path);
7570
+ if (!lang) return {
7571
+ ok: true,
7572
+ tokens: void 0
7573
+ };
7574
+ const sc = await rootOf(args.sessionId);
7575
+ if ("err" in sc) return {
7576
+ ok: false,
7577
+ error: sc.err
7578
+ };
7579
+ try {
7580
+ const server = serverOf(sc.root, lang);
7581
+ if (!server) return {
7582
+ ok: true,
7583
+ tokens: void 0
7584
+ };
7585
+ return {
7586
+ ok: true,
7587
+ tokens: await server.semanticTokens(args.path) ?? void 0
7588
+ };
7589
+ } catch (error) {
7590
+ return {
7591
+ ok: false,
7592
+ error: "LSP 语义分色查询失败:" + String(error)
7593
+ };
7594
+ }
7595
+ },
7596
+ "edrv.lsp.redetect": async (args) => {
7597
+ if (!LSP_LANGUAGES.includes(args.languageId)) return {
7598
+ ok: false,
7599
+ error: "不支持的语言:" + args.languageId
7600
+ };
7601
+ try {
7602
+ return {
7603
+ ok: true,
7604
+ servers: await redetectLanguage(args.languageId)
7605
+ };
7606
+ } catch (error) {
7607
+ return {
7608
+ ok: false,
7609
+ error: "重新检测失败:" + String(error)
7610
+ };
7611
+ }
7612
+ },
7009
7613
  "edrv.lsp.ext.list": async () => ({
7010
7614
  ok: true,
7011
7615
  extensions: listInstalled()
@@ -7032,6 +7636,7 @@ function createLspRpc(deps) {
7032
7636
  ok: false,
7033
7637
  error: "缺少安装源(vsixPath 或 namespace+name)"
7034
7638
  };
7639
+ clearProviderCache();
7035
7640
  return {
7036
7641
  ok: true,
7037
7642
  extension: info
@@ -7044,7 +7649,9 @@ function createLspRpc(deps) {
7044
7649
  }
7045
7650
  },
7046
7651
  "edrv.lsp.ext.uninstall": async (args) => {
7047
- return uninstall(args.id) ? { ok: true } : {
7652
+ const ok = uninstall(args.id);
7653
+ if (ok) clearProviderCache();
7654
+ return ok ? { ok: true } : {
7048
7655
  ok: false,
7049
7656
  error: "扩展未安装:" + args.id
7050
7657
  };
@@ -7063,6 +7670,7 @@ function createLspRpc(deps) {
7063
7670
  error: "扩展未安装:" + args.id
7064
7671
  };
7065
7672
  }
7673
+ clearProviderCache();
7066
7674
  return {
7067
7675
  ok: true,
7068
7676
  extension: updated,
@@ -7113,7 +7721,10 @@ function createLspRpc(deps) {
7113
7721
  * @returns 是否成功安装
7114
7722
  */
7115
7723
  async function installLspSettingsSection(ctx, entry) {
7116
- const deps = await loadSettingsDeps();
7724
+ let deps = null;
7725
+ try {
7726
+ deps = await loadSettingsDeps();
7727
+ } catch {}
7117
7728
  if (!deps) return false;
7118
7729
  try {
7119
7730
  const z = deps.z;
@@ -7124,11 +7735,11 @@ async function installLspSettingsSection(ctx, entry) {
7124
7735
  path: z.string()
7125
7736
  }).default({});
7126
7737
  const schema = z.object(langShape).default({});
7127
- deps.installSettingsSection(ctx, LSP_SETTINGS_NS, schema, entry, {
7738
+ const strategy = await runSettingsInstall(ctx, LSP_SETTINGS_NS, schema, entry, {
7128
7739
  setSource: () => {},
7129
7740
  onChange: () => {}
7130
7741
  });
7131
- return true;
7742
+ return strategy === "legacy" || strategy === "service";
7132
7743
  } catch (error) {
7133
7744
  console.error("[dsh-vscode-mode] LSP 设置 section 安装失败:" + String(error));
7134
7745
  return false;
@@ -7214,7 +7825,7 @@ async function logCompatSummary(ctx, warnings) {
7214
7825
  for (const warning of warnings) report.warnings.push(warning);
7215
7826
  const logger = ctx.logger;
7216
7827
  if (!logger?.info) return;
7217
- const head = "[dsh-vscode-mode] 兼容性:" + report.external.length + " 项外部适配 / " + report.guards.length + " 项护栏";
7828
+ const head = "[dsh-vscode-mode] 兼容性:" + (report.dshVersion ? "DSH " + report.dshVersion : "DSH 未探测") + " · " + report.external.length + " 项外部适配 / " + report.guards.length + " 项护栏 / " + (report.adapters?.length ?? 0) + " 项版本适配";
7218
7829
  if (report.warnings.length) logger.warn?.(head + ",警告 " + report.warnings.length + " 条:" + report.warnings.join(";"));
7219
7830
  else logger.info?.(head + ",无警告");
7220
7831
  } catch (error) {