dsh-vscode-mode 0.1.50 → 0.1.52

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
@@ -7537,7 +7537,13 @@ function candidateEmmyLua(home = dshHome()) {
7537
7537
  candidates.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.path.localeCompare(b.path));
7538
7538
  return candidates[0] ?? null;
7539
7539
  }
7540
- /** LuaLS 自动发现候选(VSCode 扩展目录 + extmgr 目录下 sumneko.lua-* 的 server/bin 可执行)。 */
7540
+ /**
7541
+ * LuaLS 自动发现候选(VSCode 扩展目录 + extmgr 目录下 sumneko.lua-* 的 server/bin 可执行)。
7542
+ * 多版本并存时按清单版本降序、路径升序排序(与 EmmyLua/DotRush 发现的排序策略一致)。
7543
+ * @author ddj 2026年09月03号
7544
+ * @param home DSH home(缺省真实;测试可注入临时目录)
7545
+ * @returns 排序后的候选列表(首个为建议选用者)
7546
+ */
7541
7547
  function candidateLuaServers(home = dshHome()) {
7542
7548
  const plat = platformServerDir();
7543
7549
  const suffix = exeSuffix();
@@ -7546,40 +7552,62 @@ function candidateLuaServers(home = dshHome()) {
7546
7552
  const binDir = join(extDir, "server", "bin");
7547
7553
  const binName = "lua-language-server" + suffix;
7548
7554
  const bin = existsSync(join(binDir, binName)) ? join(binDir, binName) : join(binDir, plat, binName);
7549
- if (existsSync(bin) && !out.includes(bin)) out.push(bin);
7555
+ if (existsSync(bin) && !out.some((c) => c.path === bin)) out.push({
7556
+ path: bin,
7557
+ version: manifestVersionOf(extDir)
7558
+ });
7550
7559
  }
7551
- return out;
7560
+ return out.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.path.localeCompare(b.path));
7552
7561
  }
7553
7562
  /**
7554
7563
  * C# 自动发现候选:ms-dotnettools.csharp 的 Roslyn dll(需 dotnet)/ OmniSharp 可执行,
7555
7564
  * 以及 DotRush 扩展自带服务器(extension/bin/LanguageServer/DotRush.dll,需对应 .NET 运行时)。
7565
+ * 同类多版本并存时取清单版本最高者(与 EmmyLua/DotRush 发现的排序策略一致)。
7556
7566
  * @author ddj 2026年09月03号
7557
7567
  * @param home DSH home(缺省真实;测试可注入临时目录)
7558
7568
  * @returns 发现结果
7559
7569
  */
7560
7570
  function candidateCSharpServers(home = dshHome()) {
7561
7571
  const out = {};
7572
+ /** 命中收集 → 清单版本降序取最高(平级按路径稳定排序)。 */
7573
+ const bestOf = (hits) => hits.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.entry.localeCompare(b.entry))[0] ?? null;
7574
+ const roslynHits = [];
7575
+ const omniHits = [];
7562
7576
  const dotrushHits = [];
7563
7577
  for (const dir of vscodeExtensionsDirs(home)) {
7564
7578
  for (const extDir of listMatchingDirs(dir, "ms-dotnettools.csharp-")) {
7579
+ const version = manifestVersionOf(extDir);
7565
7580
  const roslyn = join(extDir, ".roslyn", "Microsoft.CodeAnalysis.LanguageServer.dll");
7566
- if (!out.roslynDll && existsSync(roslyn)) out.roslynDll = roslyn;
7581
+ if (existsSync(roslyn)) roslynHits.push({
7582
+ entry: roslyn,
7583
+ version
7584
+ });
7567
7585
  const omni = join(extDir, ".omnisharp", "OmniSharp" + exeSuffix());
7568
- if (!out.omnisharpExe && existsSync(omni)) out.omnisharpExe = omni;
7586
+ if (existsSync(omni)) omniHits.push({
7587
+ entry: omni,
7588
+ version
7589
+ });
7569
7590
  }
7570
7591
  for (const extDir of listMatchingDirs(dir, "nromanov.dotrush-")) {
7571
7592
  const dll = join(extDir, "extension", "bin", "LanguageServer", "DotRush.dll");
7572
7593
  if (!existsSync(dll)) continue;
7573
7594
  dotrushHits.push({
7574
- dll,
7595
+ entry: dll,
7575
7596
  version: manifestVersionOf(extDir)
7576
7597
  });
7577
7598
  }
7578
7599
  }
7579
- const best = dotrushHits.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.dll.localeCompare(b.dll))[0];
7580
- if (best) {
7581
- out.dotrushDll = best.dll;
7582
- out.dotrushVersion = best.version;
7600
+ const roslynBest = bestOf(roslynHits);
7601
+ if (roslynBest) {
7602
+ out.roslynDll = roslynBest.entry;
7603
+ out.roslynVersion = roslynBest.version;
7604
+ }
7605
+ const omniBest = bestOf(omniHits);
7606
+ if (omniBest) out.omnisharpExe = omniBest.entry;
7607
+ const dotrushBest = bestOf(dotrushHits);
7608
+ if (dotrushBest) {
7609
+ out.dotrushDll = dotrushBest.entry;
7610
+ out.dotrushVersion = dotrushBest.version;
7583
7611
  }
7584
7612
  const dotnet = findDotnet();
7585
7613
  if (dotnet) out.dotnet = dotnet;
@@ -7683,13 +7711,15 @@ function resolveLuaProvider(manual, home = dshHome()) {
7683
7711
  version: emmy.version,
7684
7712
  providerName: "EmmyLua"
7685
7713
  };
7686
- const candidates = candidateLuaServers(h);
7687
- if (candidates.length) return {
7714
+ const best = candidateLuaServers(h)[0];
7715
+ if (best) return {
7688
7716
  ...base,
7689
7717
  kind: "discover",
7690
- argv: [candidates[0]],
7718
+ argv: [best.path],
7691
7719
  cwd: void 0,
7692
- ready: true
7720
+ ready: true,
7721
+ version: best.version,
7722
+ providerName: "LuaLS"
7693
7723
  };
7694
7724
  const inPath = findInPath("lua-language-server" + exeSuffix());
7695
7725
  if (inPath) return {
@@ -7785,6 +7815,67 @@ const PROVIDER_RESOLVERS = {
7785
7815
  lua: resolveLuaProvider,
7786
7816
  csharp: resolveCSharpProvider
7787
7817
  };
7818
+ /**
7819
+ * 汇总某语言当前检测到的候选服务器(每类服务器一条、取该类最高版本;设置页展示用)。
7820
+ * chosen 以候选路径命中 spec.argv 判定;未选用(kind=none/manual 不匹配)时不标记。
7821
+ * @author ddj 2026年09月03号
7822
+ * @param languageId 语言 id
7823
+ * @param spec 已解析的 provider 规格
7824
+ * @param home DSH home(缺省真实;测试可注入临时目录)
7825
+ * @returns 候选列表(不支持的语言/检测异常返回空)
7826
+ */
7827
+ function candidatesFor(languageId, spec, home = dshHome()) {
7828
+ /** 候选路径命中 spec.argv 时补 chosen 标记。 */
7829
+ const markChosen = (candidate) => spec.ready && candidate.path && spec.argv.includes(candidate.path) ? {
7830
+ ...candidate,
7831
+ chosen: true
7832
+ } : candidate;
7833
+ try {
7834
+ if (languageId === "lua") {
7835
+ const out = [];
7836
+ const emmy = candidateEmmyLua(home);
7837
+ if (emmy) out.push(markChosen({
7838
+ name: "EmmyLua",
7839
+ version: emmy.version,
7840
+ path: emmy.path
7841
+ }));
7842
+ const lua = candidateLuaServers(home)[0];
7843
+ if (lua) out.push(markChosen({
7844
+ name: "LuaLS(sumneko.lua)",
7845
+ version: lua.version,
7846
+ path: lua.path
7847
+ }));
7848
+ const inPath = findInPath("lua-language-server" + exeSuffix());
7849
+ if (inPath) out.push(markChosen({
7850
+ name: "lua-language-server(PATH)",
7851
+ path: inPath
7852
+ }));
7853
+ return out;
7854
+ }
7855
+ if (languageId === "csharp") {
7856
+ const out = [];
7857
+ const found = candidateCSharpServers(home);
7858
+ if (found.roslynDll) out.push(markChosen({
7859
+ name: "Roslyn(ms-dotnettools.csharp)",
7860
+ version: found.roslynVersion,
7861
+ path: found.roslynDll
7862
+ }));
7863
+ if (found.dotrushDll) out.push(markChosen({
7864
+ name: "DotRush",
7865
+ version: found.dotrushVersion,
7866
+ path: found.dotrushDll
7867
+ }));
7868
+ if (found.omnisharpExe) out.push(markChosen({
7869
+ name: "OmniSharp",
7870
+ path: found.omnisharpExe
7871
+ }));
7872
+ return out;
7873
+ }
7874
+ return [];
7875
+ } catch (error) {
7876
+ return [];
7877
+ }
7878
+ }
7788
7879
  /** 进程级缓存(home|languageId → 条目),同一 DSH home 的不同会话共享。 */
7789
7880
  const specCache = /* @__PURE__ */ new Map();
7790
7881
  /** 磁盘缓存是否已载入本进程(home → loaded),不同 profile 不串缓存。 */
@@ -8098,8 +8189,16 @@ async function extractTo(buf, dir) {
8098
8189
  }
8099
8190
  //#endregion
8100
8191
  //#region src/lsp/config.ts
8101
- const LSP_SETTINGS_NS = "dsh-vscode-mode.languageServers";
8192
+ /**
8193
+ * dsh-vscode-mode host — LSP 配置读取与 provider 解析装配。
8194
+ * 配置来源优先级:运行时覆盖(设置页保存,重启失效)> 插件组合配置 > 默认。
8195
+ * 原 settings section(ns dsh-vscode-mode.languageServers)命名空间不合法
8196
+ * (DSH settings 仅允许 /^[a-z][a-z0-9-]*$/,点号命名空间永久不可注册),
8197
+ * 已降级为配置值(插件组合配置 + 会话内运行时覆盖),不再尝试 settings 安装。
8198
+ * 作者 ddj 2026-08-27 / 2026-09-02
8199
+ */
8102
8200
  const LSP_LANGUAGES = ["lua", "csharp"];
8201
+ /** 清洗单语言配置:仅保留合法字段并剥离 undefined(稀疏覆盖语义,清空字段即回落组合配置)。 */
8103
8202
  function sanitizeLang(raw) {
8104
8203
  if (!raw || typeof raw !== "object") return void 0;
8105
8204
  const obj = raw;
@@ -8120,22 +8219,7 @@ function configFromPlugin(config) {
8120
8219
  }
8121
8220
  return out;
8122
8221
  }
8123
- /** 从 settings 描述符读取(无 settings 服务/无该 section → {})。 */
8124
- function configFromSettings(ctx) {
8125
- try {
8126
- const value = (ctx.get("settings")?.describe?.({ redactSecrets: true })?.find((item) => item.ns === LSP_SETTINGS_NS))?.value;
8127
- if (!value || typeof value !== "object") return {};
8128
- const out = {};
8129
- for (const lang of LSP_LANGUAGES) {
8130
- const v = sanitizeLang(value[lang]);
8131
- if (v) out[lang] = v;
8132
- }
8133
- return out;
8134
- } catch (error) {
8135
- return {};
8136
- }
8137
- }
8138
- /** 合并配置:settings 覆盖插件配置。 */
8222
+ /** 合并配置:上层(运行时覆盖/settings)覆盖插件组合配置。 */
8139
8223
  function mergeConfig(a, b) {
8140
8224
  const out = { ...a };
8141
8225
  for (const lang of LSP_LANGUAGES) {
@@ -8150,14 +8234,14 @@ function mergeConfig(a, b) {
8150
8234
  return out;
8151
8235
  }
8152
8236
  /**
8153
- * 解析某语言的 provider spec(合并配置后)。
8154
- * @author ddj 2026年08月27号
8155
- * @param ctx DSH 上下文
8237
+ * 解析某语言的 provider spec(插件组合配置 + 运行时覆盖层合并后)。
8238
+ * @author ddj 2026年08月27号 / 2026年09月02号
8156
8239
  * @param pluginConfig 插件组合配置
8157
8240
  * @param languageId 语言 id
8241
+ * @param override 运行时覆盖层(设置页保存值,重启失效;缺省为空)
8158
8242
  * @returns provider 规格
8159
8243
  */
8160
- function resolveProviderSpec(ctx, pluginConfig, languageId) {
8244
+ function resolveProviderSpec(pluginConfig, languageId, override) {
8161
8245
  const resolver = PROVIDER_RESOLVERS[languageId];
8162
8246
  if (!resolver) return {
8163
8247
  languageId,
@@ -8166,7 +8250,7 @@ function resolveProviderSpec(ctx, pluginConfig, languageId) {
8166
8250
  ready: false,
8167
8251
  reason: "不支持的语言:" + languageId
8168
8252
  };
8169
- const lang = mergeConfig(configFromPlugin(pluginConfig), configFromSettings(ctx))[languageId];
8253
+ const lang = mergeConfig(configFromPlugin(pluginConfig), override ?? {})[languageId];
8170
8254
  if (lang && lang.enabled === false) return {
8171
8255
  languageId,
8172
8256
  kind: "none",
@@ -8364,6 +8448,8 @@ function createDocTracker() {
8364
8448
  function createLspRpc(deps) {
8365
8449
  const { ctx, pluginConfig, manager } = deps;
8366
8450
  const tracker = createDocTracker();
8451
+ /** 运行时覆盖层:设置页保存值(内存态,重启回落组合配置值;每 apply 新建,插件重载自动复位)。 */
8452
+ const runtimeConfig = {};
8367
8453
  onRuntimeProvisioned(() => {
8368
8454
  clearProviderCache();
8369
8455
  manager.resetLanguage("csharp");
@@ -8382,16 +8468,17 @@ function createLspRpc(deps) {
8382
8468
  return { err: String(error) };
8383
8469
  }
8384
8470
  };
8385
- const acquireServer = (root, lang) => manager.acquire(root, lang, (languageId) => resolveProviderSpec(ctx, pluginConfig, languageId));
8471
+ const acquireServer = (root, lang) => manager.acquire(root, lang, (languageId) => resolveProviderSpec(pluginConfig, languageId, runtimeConfig));
8386
8472
  const serverOf = (root, lang) => manager.peek(root, lang);
8387
8473
  const rootLocations = (locations, root) => locations.map((location) => ({
8388
8474
  ...location,
8389
8475
  root
8390
8476
  }));
8391
- /** 当前 provider 检测结论 → 设置页 idle 状态(不启动 server);附带未满足的环境需求。 */
8477
+ /** 当前 provider 检测结论 → 设置页 idle 状态(不启动 server);附带未满足的环境需求与候选服务器。 */
8392
8478
  const detectedStatus = (languageId, root) => {
8393
- const spec = resolveProviderSpec(ctx, pluginConfig, languageId);
8479
+ const spec = resolveProviderSpec(pluginConfig, languageId, runtimeConfig);
8394
8480
  const missingEnv = envRequirementsFor(languageId, dshHome());
8481
+ const candidates = candidatesFor(languageId, spec);
8395
8482
  return {
8396
8483
  languageId,
8397
8484
  source: spec.kind,
@@ -8400,7 +8487,8 @@ function createLspRpc(deps) {
8400
8487
  version: spec.version,
8401
8488
  providerName: spec.providerName,
8402
8489
  root,
8403
- missingEnv: missingEnv.length ? missingEnv : void 0
8490
+ missingEnv: missingEnv.length ? missingEnv : void 0,
8491
+ candidates: candidates.length ? candidates : void 0
8404
8492
  };
8405
8493
  };
8406
8494
  /**
@@ -8446,48 +8534,34 @@ function createLspRpc(deps) {
8446
8534
  servers: manager.statusAll().filter((status) => status.root === sc.root)
8447
8535
  };
8448
8536
  },
8449
- "edrv.lsp.configGet": async () => {
8450
- const settings = configFromSettings(ctx);
8451
- const plugin = configFromPlugin(pluginConfig);
8452
- const merged = { ...plugin };
8453
- for (const lang of Object.keys(settings)) merged[lang] = {
8454
- ...plugin[lang],
8455
- ...settings[lang]
8537
+ "edrv.lsp.detect": async () => {
8538
+ return {
8539
+ ok: true,
8540
+ servers: LSP_LANGUAGES.map((lang) => detectedStatus(lang))
8456
8541
  };
8542
+ },
8543
+ "edrv.lsp.configGet": async () => {
8457
8544
  return {
8458
8545
  ok: true,
8459
- config: merged
8546
+ config: mergeConfig(configFromPlugin(pluginConfig), runtimeConfig)
8460
8547
  };
8461
8548
  },
8462
8549
  "edrv.lsp.configUpdate": async (args) => {
8463
8550
  const lang = args.languageId;
8464
- if (!lang || !resolveProviderSpec(ctx, pluginConfig, lang).languageId) return {
8551
+ if (!lang || !LSP_LANGUAGES.includes(lang)) return {
8465
8552
  ok: false,
8466
8553
  error: "不支持的语言:" + lang
8467
8554
  };
8468
- const current = configFromSettings(ctx);
8469
- const target = {
8470
- ...current,
8471
- [lang]: { ...current[lang] ?? {} }
8472
- }[lang];
8555
+ const target = { ...runtimeConfig[lang] ?? {} };
8473
8556
  if (args.enabled !== void 0) target.enabled = args.enabled;
8474
8557
  if (args.command !== void 0) target.command = args.command || void 0;
8475
8558
  if (args.path !== void 0) target.path = args.path || void 0;
8559
+ runtimeConfig[lang] = sanitizeLang(target) ?? {};
8476
8560
  try {
8477
- const settings = ctx.get("settings");
8478
- if (!settings?.update) return {
8479
- ok: false,
8480
- error: "设置服务不可用"
8481
- };
8482
- await settings.update(LSP_SETTINGS_NS, { [lang]: target });
8483
8561
  const servers = await redetectLanguage(lang);
8484
- const stored = (settings.describe?.({ redactSecrets: true })?.find((item) => item.ns === LSP_SETTINGS_NS))?.value;
8485
8562
  return {
8486
8563
  ok: true,
8487
- config: {
8488
- ...configFromPlugin(pluginConfig),
8489
- ...stored
8490
- },
8564
+ config: mergeConfig(configFromPlugin(pluginConfig), runtimeConfig),
8491
8565
  servers
8492
8566
  };
8493
8567
  } catch (error) {
@@ -8827,40 +8901,6 @@ function createLspRpc(deps) {
8827
8901
  };
8828
8902
  }
8829
8903
  //#endregion
8830
- //#region src/lsp/settings.ts
8831
- /**
8832
- * 安装 LSP 设置 section。
8833
- * @author ddj 2026年08月27号
8834
- * @param ctx DSH 上下文
8835
- * @param entry 初始配置
8836
- * @returns 是否成功安装
8837
- */
8838
- async function installLspSettingsSection(ctx, entry) {
8839
- let deps = null;
8840
- try {
8841
- deps = await loadSettingsDeps();
8842
- } catch {}
8843
- if (!deps) return false;
8844
- try {
8845
- const z = deps.z;
8846
- const langShape = {};
8847
- for (const lang of LSP_LANGUAGES) langShape[lang] = z.object({
8848
- enabled: z.boolean(),
8849
- command: z.string(),
8850
- path: z.string()
8851
- }).default({});
8852
- const schema = z.object(langShape).default({});
8853
- const strategy = await runSettingsInstall(ctx, LSP_SETTINGS_NS, schema, entry, {
8854
- setSource: () => {},
8855
- onChange: () => {}
8856
- });
8857
- return strategy === "legacy" || strategy === "service";
8858
- } catch (error) {
8859
- console.error("[dsh-vscode-mode] LSP 设置 section 安装失败:" + String(error));
8860
- return false;
8861
- }
8862
- }
8863
- //#endregion
8864
8904
  //#region src/index.ts
8865
8905
  /**
8866
8906
  * @dsh-external 生态 → dsh-vscode-mode:DSH 上的类 VSCode 编码体验(Host 半入口)。
@@ -8895,7 +8935,7 @@ function apply(ctx, config) {
8895
8935
  /** 兼容性警告收集(route 护栏等写入,启动日志一并输出)。 */
8896
8936
  const warnings = [];
8897
8937
  setupOpenSettings(ctx, config, () => {});
8898
- installLspSettingsSection(ctx, {});
8938
+ /** LSP 配置为配置值模式:插件组合配置 + 会话内运行时覆盖(原 settings section 命名空间不合法,见 lsp/config.ts)。 */
8899
8939
  /** 规则注入 section(~/.dsh/rules 与 <工作区>/.dsh/rules;旧版 DSH 无 systemPrompt 时静默降级)。 */
8900
8940
  const rulesInstalled = installRulesSection(ctx);
8901
8941
  if (!rulesInstalled) ctx.logger?.warn?.("[" + name + "] 未检测到 systemPrompt 服务,规则仅可管理不注入");