dsh-vscode-mode 0.1.35 → 0.1.42
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 +1117 -156
- package/lib/client.js.map +1 -1
- package/lib/index.js +447 -62
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- 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/paths.ts +2 -0
- package/src/client/state/workspaceFoldCache.ts +95 -0
- package/src/client/styles/mcp.css +35 -12
- package/src/client/ui/EditorView.ts +21 -2
- package/src/client/ui/KeybindingsPanel.ts +8 -5
- package/src/client/ui/LspSettings.ts +53 -15
- package/src/client/ui/McpSettings.ts +110 -49
- package/src/client/ui/PerfSettings.ts +145 -34
- 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/paths.ts +7 -0
- package/src/rpc.ts +4 -1
- package/src/shared/lsp.ts +24 -0
- package/src/shared/rpc.ts +6 -2
package/lib/index.js
CHANGED
|
@@ -485,6 +485,14 @@ function pluginCacheRoot(home = dshHome()) {
|
|
|
485
485
|
function workspaceCacheDir(workspaceId, home = dshHome()) {
|
|
486
486
|
return join(pluginCacheRoot(home), "workspace", workspaceId);
|
|
487
487
|
}
|
|
488
|
+
/** 用户级缓存目录(跨工作区共享;当前无实例,预留供全局偏好类缓存使用)。 */
|
|
489
|
+
function userCacheDir(home = dshHome()) {
|
|
490
|
+
return join(pluginCacheRoot(home), "user");
|
|
491
|
+
}
|
|
492
|
+
/** LSP provider 发现结果缓存文件(用户级:user/lsp-specs.v<schema>.json,跨会话/跨重启共享)。 */
|
|
493
|
+
function lspSpecCacheFile(home = dshHome(), schema = 4) {
|
|
494
|
+
return join(userCacheDir(home), "lsp-specs.v" + schema + ".json");
|
|
495
|
+
}
|
|
488
496
|
/** 插件日志根(~/.dsh/dsh-vscode-mode/logs,用户级)。 */
|
|
489
497
|
function pluginLogRoot(home = dshHome()) {
|
|
490
498
|
return join(home, PLUGIN_ID, "logs");
|
|
@@ -4378,7 +4386,8 @@ function buildHandlers(ctx, registry, searcher = newSearcher(ctx), contentSearch
|
|
|
4378
4386
|
const info = await fs.stat(target);
|
|
4379
4387
|
if (!info || info.type !== "file") return {
|
|
4380
4388
|
ok: false,
|
|
4381
|
-
error: "文件不存在"
|
|
4389
|
+
error: "文件不存在",
|
|
4390
|
+
resolvedPath: fs.processPath(target)
|
|
4382
4391
|
};
|
|
4383
4392
|
if ((info.size ?? 0) > 8388608) return {
|
|
4384
4393
|
ok: false,
|
|
@@ -5453,6 +5462,50 @@ function disposeAllServers() {
|
|
|
5453
5462
|
running.clear();
|
|
5454
5463
|
}
|
|
5455
5464
|
//#endregion
|
|
5465
|
+
//#region src/shared/lsp.ts
|
|
5466
|
+
/** LSP semantic token 标准类型(host 归一化后,client 与 Monaco 共用)。 */
|
|
5467
|
+
const LSP_SEMANTIC_TOKEN_TYPES = [
|
|
5468
|
+
"namespace",
|
|
5469
|
+
"type",
|
|
5470
|
+
"class",
|
|
5471
|
+
"enum",
|
|
5472
|
+
"interface",
|
|
5473
|
+
"struct",
|
|
5474
|
+
"typeParameter",
|
|
5475
|
+
"parameter",
|
|
5476
|
+
"variable",
|
|
5477
|
+
"property",
|
|
5478
|
+
"enumMember",
|
|
5479
|
+
"event",
|
|
5480
|
+
"function",
|
|
5481
|
+
"method",
|
|
5482
|
+
"macro",
|
|
5483
|
+
"label",
|
|
5484
|
+
"comment",
|
|
5485
|
+
"string",
|
|
5486
|
+
"keyword",
|
|
5487
|
+
"number",
|
|
5488
|
+
"regexp",
|
|
5489
|
+
"operator",
|
|
5490
|
+
"decorator"
|
|
5491
|
+
];
|
|
5492
|
+
/** LSP semantic token 标准修饰符(host 归一化后,client 与 Monaco 共用)。 */
|
|
5493
|
+
const LSP_SEMANTIC_TOKEN_MODIFIERS = [
|
|
5494
|
+
"declaration",
|
|
5495
|
+
"definition",
|
|
5496
|
+
"readonly",
|
|
5497
|
+
"static",
|
|
5498
|
+
"deprecated",
|
|
5499
|
+
"abstract",
|
|
5500
|
+
"async",
|
|
5501
|
+
"modification",
|
|
5502
|
+
"documentation",
|
|
5503
|
+
"defaultLibrary",
|
|
5504
|
+
"global",
|
|
5505
|
+
"local",
|
|
5506
|
+
"mutable"
|
|
5507
|
+
];
|
|
5508
|
+
//#endregion
|
|
5456
5509
|
//#region src/lsp/client.ts
|
|
5457
5510
|
/**
|
|
5458
5511
|
* dsh-vscode-mode host — LSP 客户端(JSON-RPC over stdio)。
|
|
@@ -5484,7 +5537,10 @@ function createLspClient(transport, events = {}) {
|
|
|
5484
5537
|
references: false,
|
|
5485
5538
|
documentSymbol: false,
|
|
5486
5539
|
workspaceSymbol: false,
|
|
5487
|
-
hover: false
|
|
5540
|
+
hover: false,
|
|
5541
|
+
semanticTokens: false,
|
|
5542
|
+
semanticTokenTypes: [],
|
|
5543
|
+
semanticTokenModifiers: []
|
|
5488
5544
|
};
|
|
5489
5545
|
const log = (line) => events.onLog?.(line);
|
|
5490
5546
|
createFrameParser();
|
|
@@ -5614,16 +5670,30 @@ function createLspClient(transport, events = {}) {
|
|
|
5614
5670
|
definition: { dynamicRegistration: false },
|
|
5615
5671
|
references: { dynamicRegistration: false },
|
|
5616
5672
|
documentSymbol: { dynamicRegistration: false },
|
|
5617
|
-
hover: { dynamicRegistration: false }
|
|
5673
|
+
hover: { dynamicRegistration: false },
|
|
5674
|
+
semanticTokens: {
|
|
5675
|
+
dynamicRegistration: false,
|
|
5676
|
+
requests: {
|
|
5677
|
+
range: false,
|
|
5678
|
+
full: { delta: false }
|
|
5679
|
+
},
|
|
5680
|
+
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
5681
|
+
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS],
|
|
5682
|
+
formats: ["relative"]
|
|
5683
|
+
}
|
|
5618
5684
|
}
|
|
5619
5685
|
}
|
|
5620
5686
|
}))?.capabilities ?? {};
|
|
5687
|
+
const legend = caps.semanticTokensProvider?.legend;
|
|
5621
5688
|
serverCapabilities = {
|
|
5622
5689
|
definition: Boolean(caps.definitionProvider),
|
|
5623
5690
|
references: Boolean(caps.referencesProvider),
|
|
5624
5691
|
documentSymbol: Boolean(caps.documentSymbolProvider),
|
|
5625
5692
|
workspaceSymbol: Boolean(caps.workspaceSymbolProvider),
|
|
5626
|
-
hover: Boolean(caps.hoverProvider)
|
|
5693
|
+
hover: Boolean(caps.hoverProvider),
|
|
5694
|
+
semanticTokens: Boolean(caps.semanticTokensProvider),
|
|
5695
|
+
semanticTokenTypes: Array.isArray(legend?.tokenTypes) ? legend.tokenTypes.filter((item) => typeof item === "string") : [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
5696
|
+
semanticTokenModifiers: Array.isArray(legend?.tokenModifiers) ? legend.tokenModifiers.filter((item) => typeof item === "string") : [...LSP_SEMANTIC_TOKEN_MODIFIERS]
|
|
5627
5697
|
};
|
|
5628
5698
|
client.notify("initialized", {});
|
|
5629
5699
|
events.onReady?.(serverCapabilities);
|
|
@@ -5697,7 +5767,10 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
5697
5767
|
references: false,
|
|
5698
5768
|
documentSymbol: false,
|
|
5699
5769
|
workspaceSymbol: false,
|
|
5700
|
-
hover: false
|
|
5770
|
+
hover: false,
|
|
5771
|
+
semanticTokens: false,
|
|
5772
|
+
semanticTokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
5773
|
+
semanticTokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
|
|
5701
5774
|
};
|
|
5702
5775
|
let disposed = false;
|
|
5703
5776
|
let readyWait = Promise.resolve(false);
|
|
@@ -5708,9 +5781,15 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
5708
5781
|
source: spec.kind,
|
|
5709
5782
|
phase: "idle",
|
|
5710
5783
|
reason: spec.ready ? void 0 : spec.reason,
|
|
5711
|
-
root
|
|
5784
|
+
root,
|
|
5785
|
+
version: spec.version,
|
|
5786
|
+
providerName: spec.providerName ?? (spec.version ? "语言服务器扩展" : void 0)
|
|
5712
5787
|
};
|
|
5713
5788
|
const log = (line) => logger?.("[" + languageId + "@" + root + "] " + line);
|
|
5789
|
+
const providerInfo = {
|
|
5790
|
+
version: spec.version,
|
|
5791
|
+
providerName: spec.providerName ?? (spec.version ? "EmmyLua" : void 0)
|
|
5792
|
+
};
|
|
5714
5793
|
const setPhase = (next, reason) => {
|
|
5715
5794
|
phase = next;
|
|
5716
5795
|
lastStatus = {
|
|
@@ -5719,6 +5798,7 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
5719
5798
|
phase: next,
|
|
5720
5799
|
reason,
|
|
5721
5800
|
root,
|
|
5801
|
+
...providerInfo,
|
|
5722
5802
|
...progressFields()
|
|
5723
5803
|
};
|
|
5724
5804
|
server.onStateChange?.(lastStatus);
|
|
@@ -5913,6 +5993,17 @@ function createLspServer(spec, root, languageId, logger) {
|
|
|
5913
5993
|
if (!result || !result.contents) return null;
|
|
5914
5994
|
return { contents: stringifyHoverContents(result.contents) };
|
|
5915
5995
|
},
|
|
5996
|
+
/** 查询全文 semantic tokens,并将服务器 legend 归一到插件固定 legend。 */
|
|
5997
|
+
async semanticTokens(path) {
|
|
5998
|
+
const doc = docs.get(path);
|
|
5999
|
+
if (!doc || !await readyWait || !capabilities.semanticTokens) return null;
|
|
6000
|
+
const result = await ensureClient().request("textDocument/semanticTokens/full", { textDocument: { uri: doc.uri } });
|
|
6001
|
+
if (!result || !Array.isArray(result.data)) return null;
|
|
6002
|
+
return {
|
|
6003
|
+
data: normalizeSemanticData(result.data, capabilities.semanticTokenTypes ?? [], capabilities.semanticTokenModifiers ?? []),
|
|
6004
|
+
resultId: typeof result.resultId === "string" ? result.resultId : void 0
|
|
6005
|
+
};
|
|
6006
|
+
},
|
|
5916
6007
|
async dispose() {
|
|
5917
6008
|
disposed = true;
|
|
5918
6009
|
resolveReady?.(false);
|
|
@@ -6021,6 +6112,39 @@ function toInt(value) {
|
|
|
6021
6112
|
const n = Number(value);
|
|
6022
6113
|
return Number.isFinite(n) ? Math.max(0, Math.floor(n)) : 0;
|
|
6023
6114
|
}
|
|
6115
|
+
/** 把服务器 legend 下的 5 元组流转换为插件固定 legend 下的 5 元组流。 */
|
|
6116
|
+
function normalizeSemanticData(data, types, modifiers) {
|
|
6117
|
+
const out = [];
|
|
6118
|
+
let previousLine = 0;
|
|
6119
|
+
let previousCharacter = 0;
|
|
6120
|
+
let outputLine = 0;
|
|
6121
|
+
let outputCharacter = 0;
|
|
6122
|
+
for (let i = 0; i + 4 < data.length; i += 5) {
|
|
6123
|
+
const deltaLine = toInt(data[i]);
|
|
6124
|
+
const deltaCharacter = toInt(data[i + 1]);
|
|
6125
|
+
const length = toInt(data[i + 2]);
|
|
6126
|
+
const sourceType = types[toInt(data[i + 3])] ?? "variable";
|
|
6127
|
+
const sourceModifiers = toInt(data[i + 4]);
|
|
6128
|
+
const line = previousLine + deltaLine;
|
|
6129
|
+
const character = deltaLine === 0 ? previousCharacter + deltaCharacter : deltaCharacter;
|
|
6130
|
+
const typeIndex = Math.max(0, LSP_SEMANTIC_TOKEN_TYPES.indexOf(sourceType));
|
|
6131
|
+
let modifierBits = 0;
|
|
6132
|
+
for (let bit = 0; bit < modifiers.length; bit++) {
|
|
6133
|
+
if ((sourceModifiers & 1 << bit) === 0) continue;
|
|
6134
|
+
const modifier = modifiers[bit];
|
|
6135
|
+
const targetBit = LSP_SEMANTIC_TOKEN_MODIFIERS.indexOf(modifier);
|
|
6136
|
+
if (targetBit >= 0) modifierBits |= 1 << targetBit;
|
|
6137
|
+
}
|
|
6138
|
+
const outputDeltaLine = line - outputLine;
|
|
6139
|
+
const outputDeltaCharacter = outputDeltaLine === 0 ? character - outputCharacter : character;
|
|
6140
|
+
out.push(outputDeltaLine, outputDeltaCharacter, length, typeIndex, modifierBits);
|
|
6141
|
+
previousLine = line;
|
|
6142
|
+
previousCharacter = character;
|
|
6143
|
+
outputLine = line;
|
|
6144
|
+
outputCharacter = character;
|
|
6145
|
+
}
|
|
6146
|
+
return out;
|
|
6147
|
+
}
|
|
6024
6148
|
//#endregion
|
|
6025
6149
|
//#region src/lsp/manager.ts
|
|
6026
6150
|
/**
|
|
@@ -6079,6 +6203,13 @@ function createLspManager(logger) {
|
|
|
6079
6203
|
releaseRoot(root) {
|
|
6080
6204
|
for (const entry of [...entries.values()]) if (entry.server.root === root) dropEntry(entry);
|
|
6081
6205
|
},
|
|
6206
|
+
reset(root, languageId) {
|
|
6207
|
+
const entry = entries.get(keyOf(root, languageId));
|
|
6208
|
+
if (entry) dropEntry(entry);
|
|
6209
|
+
},
|
|
6210
|
+
resetLanguage(languageId) {
|
|
6211
|
+
for (const entry of [...entries.values()]) if (entry.server.languageId === languageId) dropEntry(entry);
|
|
6212
|
+
},
|
|
6082
6213
|
statusAll() {
|
|
6083
6214
|
return [...entries.values()].map((entry) => entry.server.status());
|
|
6084
6215
|
},
|
|
@@ -6388,7 +6519,9 @@ function extensionProviderFor(languageId) {
|
|
|
6388
6519
|
kind: "extension",
|
|
6389
6520
|
argv: p.argv,
|
|
6390
6521
|
cwd: p.cwd,
|
|
6391
|
-
ready: true
|
|
6522
|
+
ready: true,
|
|
6523
|
+
version: p.version,
|
|
6524
|
+
providerName: p.version ? "EmmyLua" : "语言服务器扩展"
|
|
6392
6525
|
};
|
|
6393
6526
|
}
|
|
6394
6527
|
/** PATH 中查找可执行文件(不含扩展名校验,交由 spawn 报错)。 */
|
|
@@ -6416,6 +6549,25 @@ function findDotnet() {
|
|
|
6416
6549
|
for (const c of ["C:/Program Files/dotnet/dotnet.exe", "C:/Program Files (x86)/dotnet/dotnet.exe"]) if (existsSync(c)) return c;
|
|
6417
6550
|
return null;
|
|
6418
6551
|
}
|
|
6552
|
+
/** EmmyLua 自动发现候选(tangzx.emmylua-* / server/emmylua_ls)。 */
|
|
6553
|
+
function candidateEmmyLua(home = dshHome()) {
|
|
6554
|
+
const candidates = [];
|
|
6555
|
+
for (const dir of vscodeExtensionsDirs(home)) for (const extDir of listMatchingDirs(dir, "tangzx.emmylua-")) {
|
|
6556
|
+
const server = join(extDir, "server", "emmylua_ls" + exeSuffix());
|
|
6557
|
+
if (!existsSync(server)) continue;
|
|
6558
|
+
let version;
|
|
6559
|
+
try {
|
|
6560
|
+
const manifest = JSON.parse(readFileSync(join(extDir, "package.json"), "utf8"));
|
|
6561
|
+
if (typeof manifest.version === "string") version = manifest.version;
|
|
6562
|
+
} catch (error) {}
|
|
6563
|
+
candidates.push({
|
|
6564
|
+
path: server,
|
|
6565
|
+
version
|
|
6566
|
+
});
|
|
6567
|
+
}
|
|
6568
|
+
candidates.sort((a, b) => (b.version ?? "").localeCompare(a.version ?? "", void 0, { numeric: true }) || a.path.localeCompare(b.path));
|
|
6569
|
+
return candidates[0] ?? null;
|
|
6570
|
+
}
|
|
6419
6571
|
/** LuaLS 自动发现候选(VSCode 扩展目录 + extmgr 目录下 sumneko.lua-* 的 server/bin 可执行)。 */
|
|
6420
6572
|
function candidateLuaServers(home = dshHome()) {
|
|
6421
6573
|
const plat = platformServerDir();
|
|
@@ -6497,30 +6649,41 @@ function resolveLuaProvider(manual, home = dshHome()) {
|
|
|
6497
6649
|
cwd: manualResolved.cwd,
|
|
6498
6650
|
ready: true
|
|
6499
6651
|
};
|
|
6500
|
-
|
|
6501
|
-
|
|
6502
|
-
|
|
6503
|
-
|
|
6504
|
-
|
|
6505
|
-
|
|
6506
|
-
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6513
|
-
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6652
|
+
return resolveCached("lua", manual, home, (h) => {
|
|
6653
|
+
const ext = extensionProviderFor("lua");
|
|
6654
|
+
if (ext) return ext;
|
|
6655
|
+
const emmy = candidateEmmyLua(h);
|
|
6656
|
+
if (emmy) return {
|
|
6657
|
+
...base,
|
|
6658
|
+
kind: "extension",
|
|
6659
|
+
argv: [emmy.path],
|
|
6660
|
+
ready: true,
|
|
6661
|
+
version: emmy.version,
|
|
6662
|
+
providerName: "EmmyLua"
|
|
6663
|
+
};
|
|
6664
|
+
const candidates = candidateLuaServers(h);
|
|
6665
|
+
if (candidates.length) return {
|
|
6666
|
+
...base,
|
|
6667
|
+
kind: "discover",
|
|
6668
|
+
argv: [candidates[0]],
|
|
6669
|
+
cwd: void 0,
|
|
6670
|
+
ready: true
|
|
6671
|
+
};
|
|
6672
|
+
const inPath = findInPath("lua-language-server" + exeSuffix());
|
|
6673
|
+
if (inPath) return {
|
|
6674
|
+
...base,
|
|
6675
|
+
kind: "discover",
|
|
6676
|
+
argv: [inPath],
|
|
6677
|
+
ready: true
|
|
6678
|
+
};
|
|
6679
|
+
return {
|
|
6680
|
+
...base,
|
|
6681
|
+
kind: "none",
|
|
6682
|
+
argv: [],
|
|
6683
|
+
ready: false,
|
|
6684
|
+
reason: manualResolved.reason ?? "未发现 lua-language-server(可安装 LuaLS 或手动指定路径)"
|
|
6685
|
+
};
|
|
6686
|
+
});
|
|
6524
6687
|
}
|
|
6525
6688
|
/**
|
|
6526
6689
|
* 解析 C# provider(优先级:手动 > 扩展源 > 自动发现;需 dotnet + Roslyn dll;或 OmniSharp)。
|
|
@@ -6539,40 +6702,163 @@ function resolveCSharpProvider(manual, home = dshHome()) {
|
|
|
6539
6702
|
cwd: manualResolved.cwd,
|
|
6540
6703
|
ready: true
|
|
6541
6704
|
};
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6550
|
-
|
|
6551
|
-
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6705
|
+
return resolveCached("csharp", manual, home, (h) => {
|
|
6706
|
+
const ext = extensionProviderFor("csharp");
|
|
6707
|
+
if (ext) return ext;
|
|
6708
|
+
const discovered = candidateCSharpServers(h);
|
|
6709
|
+
if (discovered.roslynDll && discovered.dotnet) return {
|
|
6710
|
+
...base,
|
|
6711
|
+
kind: "discover",
|
|
6712
|
+
argv: [
|
|
6713
|
+
discovered.dotnet,
|
|
6714
|
+
discovered.roslynDll,
|
|
6715
|
+
"--stdio"
|
|
6716
|
+
],
|
|
6717
|
+
ready: true
|
|
6718
|
+
};
|
|
6719
|
+
if (discovered.omnisharpExe) return {
|
|
6720
|
+
...base,
|
|
6721
|
+
kind: "discover",
|
|
6722
|
+
argv: [discovered.omnisharpExe, "--stdio"],
|
|
6723
|
+
ready: true
|
|
6724
|
+
};
|
|
6725
|
+
const reasons = [];
|
|
6726
|
+
if (!discovered.dotnet) reasons.push("未找到 dotnet");
|
|
6727
|
+
if (!discovered.roslynDll && !discovered.omnisharpExe) reasons.push("未发现 ms-dotnettools.csharp 扩展");
|
|
6728
|
+
return {
|
|
6729
|
+
...base,
|
|
6730
|
+
kind: "none",
|
|
6731
|
+
argv: [],
|
|
6732
|
+
ready: false,
|
|
6733
|
+
reason: reasons.join(";") + "(可手动指定 Roslyn/OmniSharp 入口)"
|
|
6734
|
+
};
|
|
6735
|
+
});
|
|
6571
6736
|
}
|
|
6572
6737
|
const PROVIDER_RESOLVERS = {
|
|
6573
6738
|
lua: resolveLuaProvider,
|
|
6574
6739
|
csharp: resolveCSharpProvider
|
|
6575
6740
|
};
|
|
6741
|
+
/** 进程级缓存(home|languageId → 条目),同一 DSH home 的不同会话共享。 */
|
|
6742
|
+
const specCache = /* @__PURE__ */ new Map();
|
|
6743
|
+
/** 磁盘缓存是否已载入本进程(home → loaded),不同 profile 不串缓存。 */
|
|
6744
|
+
const diskLoaded = /* @__PURE__ */ new Set();
|
|
6745
|
+
/** 自动发现成功结果的复用时长;过期后才检查目录指纹。 */
|
|
6746
|
+
const SPEC_CACHE_TTL_MS = 18e5;
|
|
6747
|
+
/** 未发现结果的短缓存时长,避免缺失服务器时反复扫描 PATH。 */
|
|
6748
|
+
const SPEC_NEGATIVE_CACHE_MS = 6e4;
|
|
6749
|
+
/** 生成带 home 的缓存键,避免不同 profile 的 provider 结果串用。 */
|
|
6750
|
+
function cacheEntryKey(home, languageId) {
|
|
6751
|
+
return home + "|" + languageId;
|
|
6752
|
+
}
|
|
6753
|
+
/**
|
|
6754
|
+
* 构造缓存 key:语言 + 手动配置(manual 结果不缓存,故只参与"是否走缓存"判定)。
|
|
6755
|
+
* @author ddj 2026年08月28号
|
|
6756
|
+
* @param languageId 语言 id
|
|
6757
|
+
* @param manual 手动配置
|
|
6758
|
+
* @returns 缓存 key(manual 有效时返回空串表示跳过缓存)
|
|
6759
|
+
*/
|
|
6760
|
+
function cacheKeyOf(languageId, manual) {
|
|
6761
|
+
const pathValue = manual?.path?.trim() ?? "";
|
|
6762
|
+
const commandValue = manual?.command?.trim() ?? "";
|
|
6763
|
+
if (pathValue || commandValue) return "";
|
|
6764
|
+
return languageId;
|
|
6765
|
+
}
|
|
6766
|
+
/**
|
|
6767
|
+
* 环境指纹:扩展目录清单 + 平台。目录增删/换平台即失效(防缓存陈旧指向已删扩展)。
|
|
6768
|
+
* @author ddj 2026年08月28号
|
|
6769
|
+
* @param home DSH home
|
|
6770
|
+
* @returns 指纹串
|
|
6771
|
+
*/
|
|
6772
|
+
function fingerprintOf(home) {
|
|
6773
|
+
const marks = vscodeExtensionsDirs(home).map((dir) => {
|
|
6774
|
+
try {
|
|
6775
|
+
return readdirSync(dir).sort().join(",");
|
|
6776
|
+
} catch (error) {
|
|
6777
|
+
return "?";
|
|
6778
|
+
}
|
|
6779
|
+
});
|
|
6780
|
+
return process.platform + "|" + marks.join("|");
|
|
6781
|
+
}
|
|
6782
|
+
/**
|
|
6783
|
+
* 读取磁盘缓存(幂等:每进程只载一次;schema 文件名已隔离版本)。
|
|
6784
|
+
* @author ddj 2026年08月28号
|
|
6785
|
+
* @param home DSH home
|
|
6786
|
+
*/
|
|
6787
|
+
function loadDiskCache(home) {
|
|
6788
|
+
if (diskLoaded.has(home)) return;
|
|
6789
|
+
diskLoaded.add(home);
|
|
6790
|
+
const file = lspSpecCacheFile(home);
|
|
6791
|
+
try {
|
|
6792
|
+
const raw = JSON.parse(readFileSync(file, "utf8"));
|
|
6793
|
+
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);
|
|
6794
|
+
} catch (error) {}
|
|
6795
|
+
}
|
|
6796
|
+
/**
|
|
6797
|
+
* 写回磁盘缓存(best-effort;写失败不影响本次解析结果)。
|
|
6798
|
+
* @author ddj 2026年08月28号
|
|
6799
|
+
* @param home DSH home
|
|
6800
|
+
*/
|
|
6801
|
+
function saveDiskCache(home) {
|
|
6802
|
+
const file = lspSpecCacheFile(home);
|
|
6803
|
+
try {
|
|
6804
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
6805
|
+
const out = {};
|
|
6806
|
+
const prefix = home + "|";
|
|
6807
|
+
for (const [key, entry] of specCache) if (key.startsWith(prefix)) out[key.slice(prefix.length)] = entry;
|
|
6808
|
+
writeFileSync(file, JSON.stringify(out), "utf8");
|
|
6809
|
+
} catch (error) {}
|
|
6810
|
+
}
|
|
6811
|
+
/**
|
|
6812
|
+
* 带缓存执行 provider 解析(自动发现路径专用)。
|
|
6813
|
+
* @author ddj 2026年08月28号
|
|
6814
|
+
* @param languageId 语言 id
|
|
6815
|
+
* @param manual 手动配置
|
|
6816
|
+
* @param home DSH home
|
|
6817
|
+
* @param resolve 真解析函数(未命中时调用)
|
|
6818
|
+
* @returns provider 规格
|
|
6819
|
+
*/
|
|
6820
|
+
function resolveCached(languageId, manual, home, resolve) {
|
|
6821
|
+
const key = cacheKeyOf(languageId, manual);
|
|
6822
|
+
if (!key) return resolve(home);
|
|
6823
|
+
loadDiskCache(home);
|
|
6824
|
+
const fullKey = cacheEntryKey(home, key);
|
|
6825
|
+
const hit = specCache.get(fullKey);
|
|
6826
|
+
if (hit && isCacheFresh(hit)) return hit.spec;
|
|
6827
|
+
const fingerprint = fingerprintOf(home);
|
|
6828
|
+
if (hit && hit.fingerprint === fingerprint) {
|
|
6829
|
+
hit.at = Date.now();
|
|
6830
|
+
return hit.spec;
|
|
6831
|
+
}
|
|
6832
|
+
const spec = resolve(home);
|
|
6833
|
+
specCache.set(fullKey, {
|
|
6834
|
+
spec,
|
|
6835
|
+
fingerprint,
|
|
6836
|
+
at: Date.now()
|
|
6837
|
+
});
|
|
6838
|
+
saveDiskCache(home);
|
|
6839
|
+
return spec;
|
|
6840
|
+
}
|
|
6841
|
+
/** 判断缓存条目是否仍可直接复用,不触发扩展目录扫描。 */
|
|
6842
|
+
function isCacheFresh(entry) {
|
|
6843
|
+
const age = Date.now() - entry.at;
|
|
6844
|
+
const ttl = entry.spec.kind === "none" ? SPEC_NEGATIVE_CACHE_MS : SPEC_CACHE_TTL_MS;
|
|
6845
|
+
if (age < 0 || age >= ttl) return false;
|
|
6846
|
+
const executable = entry.spec.argv[0];
|
|
6847
|
+
return !entry.spec.ready || !executable || existsSync(executable);
|
|
6848
|
+
}
|
|
6849
|
+
/**
|
|
6850
|
+
* 清空 provider 发现缓存(扩展安装/卸载、配置变更后调用;进程级 + 删磁盘文件)。
|
|
6851
|
+
* @author ddj 2026年08月28号
|
|
6852
|
+
* @param home DSH home(缺省真实)
|
|
6853
|
+
*/
|
|
6854
|
+
function clearProviderCache(home = dshHome()) {
|
|
6855
|
+
const prefix = home + "|";
|
|
6856
|
+
for (const key of specCache.keys()) if (key.startsWith(prefix)) specCache.delete(key);
|
|
6857
|
+
diskLoaded.delete(home);
|
|
6858
|
+
try {
|
|
6859
|
+
rmSync(lspSpecCacheFile(home), { force: true });
|
|
6860
|
+
} catch (error) {}
|
|
6861
|
+
}
|
|
6576
6862
|
//#endregion
|
|
6577
6863
|
//#region src/lsp/config.ts
|
|
6578
6864
|
const LSP_SETTINGS_NS = "dsh-vscode-mode.languageServers";
|
|
@@ -6736,6 +7022,21 @@ function createDocTracker() {
|
|
|
6736
7022
|
}
|
|
6737
7023
|
sessionDocs.delete(sessionId);
|
|
6738
7024
|
return [...toRelease];
|
|
7025
|
+
},
|
|
7026
|
+
/** 清除指定语言的文档归属,让当前客户端重新同步时重新 acquire。 */
|
|
7027
|
+
resetLanguage(languageId) {
|
|
7028
|
+
for (const [docKey, owners] of openDocs) {
|
|
7029
|
+
if (!docKey.includes("|" + languageId + "|")) continue;
|
|
7030
|
+
for (const sessionId of owners) forgetDoc(sessionId, docKey);
|
|
7031
|
+
openDocs.delete(docKey);
|
|
7032
|
+
}
|
|
7033
|
+
for (const key of [...rootLangRefs.keys()]) if (key.endsWith("|" + languageId)) rootLangRefs.delete(key);
|
|
7034
|
+
},
|
|
7035
|
+
/** 返回指定会话已打开的文档路径,用于重检测后恢复同步。 */
|
|
7036
|
+
pathsFor(sessionId, languageId) {
|
|
7037
|
+
const docs = sessionDocs.get(sessionId);
|
|
7038
|
+
if (!docs) return [];
|
|
7039
|
+
return [...docs].filter((key) => key.includes("|" + languageId + "|")).map((key) => key.slice(key.lastIndexOf("|") + 1));
|
|
6739
7040
|
}
|
|
6740
7041
|
};
|
|
6741
7042
|
}
|
|
@@ -6768,6 +7069,39 @@ function createLspRpc(deps) {
|
|
|
6768
7069
|
...location,
|
|
6769
7070
|
root
|
|
6770
7071
|
}));
|
|
7072
|
+
/** 当前 provider 检测结论 → 设置页 idle 状态(不启动 server)。 */
|
|
7073
|
+
const detectedStatus = (languageId, root) => {
|
|
7074
|
+
const spec = resolveProviderSpec(ctx, pluginConfig, languageId);
|
|
7075
|
+
return {
|
|
7076
|
+
languageId,
|
|
7077
|
+
source: spec.kind,
|
|
7078
|
+
phase: "idle",
|
|
7079
|
+
reason: spec.ready ? void 0 : spec.reason,
|
|
7080
|
+
version: spec.version,
|
|
7081
|
+
providerName: spec.providerName,
|
|
7082
|
+
root
|
|
7083
|
+
};
|
|
7084
|
+
};
|
|
7085
|
+
/**
|
|
7086
|
+
* 清发现缓存并重置旧 server(保留 tracker 文档记录);
|
|
7087
|
+
* 下一次模型同步会按最新配置重新 acquire,已打开文档无需重开。
|
|
7088
|
+
* 作者 ddj 2026年09月02号
|
|
7089
|
+
* @param languageId 语言 id
|
|
7090
|
+
* @param sessionId 会话语境(缺省重置全部工作区该语言)
|
|
7091
|
+
* @returns 检测后的 idle 状态列表
|
|
7092
|
+
*/
|
|
7093
|
+
const redetectLanguage = async (languageId, sessionId) => {
|
|
7094
|
+
clearProviderCache();
|
|
7095
|
+
if (sessionId) {
|
|
7096
|
+
const sc = await rootOf(sessionId);
|
|
7097
|
+
if ("err" in sc) return [];
|
|
7098
|
+
manager.reset(sc.root, languageId);
|
|
7099
|
+
return [detectedStatus(languageId, sc.root)];
|
|
7100
|
+
}
|
|
7101
|
+
const roots = [...new Set(manager.statusAll().filter((s) => s.languageId === languageId).map((s) => s.root))];
|
|
7102
|
+
manager.resetLanguage(languageId);
|
|
7103
|
+
return roots.length ? roots.map((root) => detectedStatus(languageId, root)) : [detectedStatus(languageId)];
|
|
7104
|
+
};
|
|
6771
7105
|
const disposeSession = (sessionId) => {
|
|
6772
7106
|
for (const rl of tracker.closeSession(sessionId)) {
|
|
6773
7107
|
const sep = rl.lastIndexOf("|");
|
|
@@ -6825,13 +7159,15 @@ function createLspRpc(deps) {
|
|
|
6825
7159
|
error: "设置服务不可用"
|
|
6826
7160
|
};
|
|
6827
7161
|
await settings.update(LSP_SETTINGS_NS, { [lang]: target });
|
|
7162
|
+
const servers = await redetectLanguage(lang);
|
|
6828
7163
|
const stored = (settings.describe?.({ redactSecrets: true })?.find((item) => item.ns === LSP_SETTINGS_NS))?.value;
|
|
6829
7164
|
return {
|
|
6830
7165
|
ok: true,
|
|
6831
7166
|
config: {
|
|
6832
7167
|
...configFromPlugin(pluginConfig),
|
|
6833
7168
|
...stored
|
|
6834
|
-
}
|
|
7169
|
+
},
|
|
7170
|
+
servers
|
|
6835
7171
|
};
|
|
6836
7172
|
} catch (error) {
|
|
6837
7173
|
return {
|
|
@@ -7006,6 +7342,51 @@ function createLspRpc(deps) {
|
|
|
7006
7342
|
};
|
|
7007
7343
|
}
|
|
7008
7344
|
},
|
|
7345
|
+
"edrv.lsp.semanticTokens": async (args) => {
|
|
7346
|
+
const lang = langOfPath(args.path);
|
|
7347
|
+
if (!lang) return {
|
|
7348
|
+
ok: true,
|
|
7349
|
+
tokens: void 0
|
|
7350
|
+
};
|
|
7351
|
+
const sc = await rootOf(args.sessionId);
|
|
7352
|
+
if ("err" in sc) return {
|
|
7353
|
+
ok: false,
|
|
7354
|
+
error: sc.err
|
|
7355
|
+
};
|
|
7356
|
+
try {
|
|
7357
|
+
const server = serverOf(sc.root, lang);
|
|
7358
|
+
if (!server) return {
|
|
7359
|
+
ok: true,
|
|
7360
|
+
tokens: void 0
|
|
7361
|
+
};
|
|
7362
|
+
return {
|
|
7363
|
+
ok: true,
|
|
7364
|
+
tokens: await server.semanticTokens(args.path) ?? void 0
|
|
7365
|
+
};
|
|
7366
|
+
} catch (error) {
|
|
7367
|
+
return {
|
|
7368
|
+
ok: false,
|
|
7369
|
+
error: "LSP 语义分色查询失败:" + String(error)
|
|
7370
|
+
};
|
|
7371
|
+
}
|
|
7372
|
+
},
|
|
7373
|
+
"edrv.lsp.redetect": async (args) => {
|
|
7374
|
+
if (!LSP_LANGUAGES.includes(args.languageId)) return {
|
|
7375
|
+
ok: false,
|
|
7376
|
+
error: "不支持的语言:" + args.languageId
|
|
7377
|
+
};
|
|
7378
|
+
try {
|
|
7379
|
+
return {
|
|
7380
|
+
ok: true,
|
|
7381
|
+
servers: await redetectLanguage(args.languageId)
|
|
7382
|
+
};
|
|
7383
|
+
} catch (error) {
|
|
7384
|
+
return {
|
|
7385
|
+
ok: false,
|
|
7386
|
+
error: "重新检测失败:" + String(error)
|
|
7387
|
+
};
|
|
7388
|
+
}
|
|
7389
|
+
},
|
|
7009
7390
|
"edrv.lsp.ext.list": async () => ({
|
|
7010
7391
|
ok: true,
|
|
7011
7392
|
extensions: listInstalled()
|
|
@@ -7032,6 +7413,7 @@ function createLspRpc(deps) {
|
|
|
7032
7413
|
ok: false,
|
|
7033
7414
|
error: "缺少安装源(vsixPath 或 namespace+name)"
|
|
7034
7415
|
};
|
|
7416
|
+
clearProviderCache();
|
|
7035
7417
|
return {
|
|
7036
7418
|
ok: true,
|
|
7037
7419
|
extension: info
|
|
@@ -7044,7 +7426,9 @@ function createLspRpc(deps) {
|
|
|
7044
7426
|
}
|
|
7045
7427
|
},
|
|
7046
7428
|
"edrv.lsp.ext.uninstall": async (args) => {
|
|
7047
|
-
|
|
7429
|
+
const ok = uninstall(args.id);
|
|
7430
|
+
if (ok) clearProviderCache();
|
|
7431
|
+
return ok ? { ok: true } : {
|
|
7048
7432
|
ok: false,
|
|
7049
7433
|
error: "扩展未安装:" + args.id
|
|
7050
7434
|
};
|
|
@@ -7063,6 +7447,7 @@ function createLspRpc(deps) {
|
|
|
7063
7447
|
error: "扩展未安装:" + args.id
|
|
7064
7448
|
};
|
|
7065
7449
|
}
|
|
7450
|
+
clearProviderCache();
|
|
7066
7451
|
return {
|
|
7067
7452
|
ok: true,
|
|
7068
7453
|
extension: updated,
|