apex-code 0.0.1-alpha.4 → 0.0.1-alpha.6
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/CHANGELOG.md +78 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -1
- package/dist/cli.js.map +1 -1
- package/dist/core/agent-session-runtime.d.ts.map +1 -1
- package/dist/core/agent-session-runtime.js +2 -2
- package/dist/core/agent-session-runtime.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +20 -1
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +91 -1
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +17 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +28 -4
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/lsp/client.d.ts +76 -0
- package/dist/core/lsp/client.d.ts.map +1 -0
- package/dist/core/lsp/client.js +469 -0
- package/dist/core/lsp/client.js.map +1 -0
- package/dist/core/lsp/diagnostics.d.ts +47 -0
- package/dist/core/lsp/diagnostics.d.ts.map +1 -0
- package/dist/core/lsp/diagnostics.js +224 -0
- package/dist/core/lsp/diagnostics.js.map +1 -0
- package/dist/core/lsp/pool.d.ts +48 -0
- package/dist/core/lsp/pool.d.ts.map +1 -0
- package/dist/core/lsp/pool.js +150 -0
- package/dist/core/lsp/pool.js.map +1 -0
- package/dist/core/lsp/registry.d.ts +49 -0
- package/dist/core/lsp/registry.d.ts.map +1 -0
- package/dist/core/lsp/registry.js +215 -0
- package/dist/core/lsp/registry.js.map +1 -0
- package/dist/core/lsp/status.d.ts +23 -0
- package/dist/core/lsp/status.d.ts.map +1 -0
- package/dist/core/lsp/status.js +35 -0
- package/dist/core/lsp/status.js.map +1 -0
- package/dist/core/lsp/sync.d.ts +13 -0
- package/dist/core/lsp/sync.d.ts.map +1 -0
- package/dist/core/lsp/sync.js +71 -0
- package/dist/core/lsp/sync.js.map +1 -0
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +15 -0
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +43 -0
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +70 -3
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +1 -0
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sdk.d.ts +4 -0
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +10 -1
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager.d.ts +3 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +3 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts +19 -7
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +77 -19
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +1 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +3 -3
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/contract.d.ts +29 -2
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/diagnostics.d.ts +39 -0
- package/dist/core/tools/diagnostics.d.ts.map +1 -0
- package/dist/core/tools/diagnostics.js +62 -0
- package/dist/core/tools/diagnostics.js.map +1 -0
- package/dist/core/tools/edit.d.ts +10 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +17 -8
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/index.d.ts +20 -3
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +18 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +62 -0
- package/dist/core/tools/lsp.d.ts.map +1 -0
- package/dist/core/tools/lsp.js +216 -0
- package/dist/core/tools/lsp.js.map +1 -0
- package/dist/core/tools/skill-search.d.ts +32 -0
- package/dist/core/tools/skill-search.d.ts.map +1 -0
- package/dist/core/tools/skill-search.js +56 -0
- package/dist/core/tools/skill-search.js.map +1 -0
- package/dist/core/tools/write.d.ts +10 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +16 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +49 -1
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +235 -1
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.d.ts.map +1 -1
- package/dist/modes/interactive/components/first-time-setup.js +2 -1
- package/dist/modes/interactive/components/first-time-setup.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +1 -0
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +1 -0
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +19 -6
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +172 -89
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/splash-header.d.ts +41 -0
- package/dist/modes/interactive/components/splash-header.d.ts.map +1 -0
- package/dist/modes/interactive/components/splash-header.js +145 -0
- package/dist/modes/interactive/components/splash-header.js.map +1 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +19 -15
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/apex.json +94 -0
- package/dist/modes/interactive/theme/light.json +1 -1
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +6 -1
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +2 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/themes/apex-logo.d.ts +21 -0
- package/dist/themes/apex-logo.d.ts.map +1 -0
- package/dist/themes/apex-logo.js +31 -0
- package/dist/themes/apex-logo.js.map +1 -0
- package/dist/utils/changelog.d.ts +7 -2
- package/dist/utils/changelog.d.ts.map +1 -1
- package/dist/utils/changelog.js +82 -13
- package/dist/utils/changelog.js.map +1 -1
- package/docs/skills.md +54 -4
- package/npm-shrinkwrap.json +5 -5
- package/package.json +2 -2
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { LspClient } from "./client.js";
|
|
2
|
+
import { selectLspServerForPath, } from "./registry.js";
|
|
3
|
+
const MAX_ATTEMPTS = 3;
|
|
4
|
+
function entryKey(serverId, root) {
|
|
5
|
+
return `${serverId}\0${root}`;
|
|
6
|
+
}
|
|
7
|
+
function normalizeError(error) {
|
|
8
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
9
|
+
}
|
|
10
|
+
/** Owns and bounds language-server clients for one resolved workspace registry. */
|
|
11
|
+
export class LspPool {
|
|
12
|
+
registry;
|
|
13
|
+
options;
|
|
14
|
+
entries = new Map();
|
|
15
|
+
startPromise;
|
|
16
|
+
disposePromise;
|
|
17
|
+
closing = false;
|
|
18
|
+
constructor(registry, options = {}) {
|
|
19
|
+
this.registry = registry;
|
|
20
|
+
this.options = options;
|
|
21
|
+
}
|
|
22
|
+
/** Eagerly initialize the workspace-root process for every configured server. */
|
|
23
|
+
start() {
|
|
24
|
+
if (this.closing)
|
|
25
|
+
return Promise.reject(new Error("LSP pool is disposed."));
|
|
26
|
+
this.startPromise ??= Promise.all(this.registry.servers.map((server) => this.ensureClient(this.getOrCreateEntry(server, this.registry.workspace)))).then(() => undefined);
|
|
27
|
+
return this.startPromise;
|
|
28
|
+
}
|
|
29
|
+
/** Select a file's server/root and return its ready shared client. */
|
|
30
|
+
async acquire(path) {
|
|
31
|
+
if (this.closing)
|
|
32
|
+
throw new Error("LSP pool is disposed.");
|
|
33
|
+
const selection = selectLspServerForPath(this.registry, path);
|
|
34
|
+
if (!selection)
|
|
35
|
+
return undefined;
|
|
36
|
+
const client = await this.ensureClient(this.getOrCreateEntry(selection.server, selection.root));
|
|
37
|
+
return { client, selection };
|
|
38
|
+
}
|
|
39
|
+
status() {
|
|
40
|
+
return [...this.entries.values()]
|
|
41
|
+
.map((entry) => this.toStatus(entry))
|
|
42
|
+
.sort((left, right) => left.serverId === right.serverId
|
|
43
|
+
? left.root.localeCompare(right.root)
|
|
44
|
+
: left.serverId.localeCompare(right.serverId));
|
|
45
|
+
}
|
|
46
|
+
dispose() {
|
|
47
|
+
this.disposePromise ??= this.disposeEntries();
|
|
48
|
+
return this.disposePromise;
|
|
49
|
+
}
|
|
50
|
+
getOrCreateEntry(server, root) {
|
|
51
|
+
const key = entryKey(server.id, root);
|
|
52
|
+
let entry = this.entries.get(key);
|
|
53
|
+
if (!entry) {
|
|
54
|
+
entry = { server, root, attempts: 0, state: "degraded" };
|
|
55
|
+
this.entries.set(key, entry);
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
58
|
+
}
|
|
59
|
+
ensureClient(entry) {
|
|
60
|
+
if (entry.state === "disabled") {
|
|
61
|
+
return Promise.reject(new Error(`LSP server ${entry.server.id} at ${entry.root} is disabled after ${entry.attempts} attempts: ${entry.reason}`));
|
|
62
|
+
}
|
|
63
|
+
if (entry.state === "closed" || this.closing)
|
|
64
|
+
return Promise.reject(new Error("LSP pool is disposed."));
|
|
65
|
+
if (entry.state === "ready" && entry.client)
|
|
66
|
+
return Promise.resolve(entry.client);
|
|
67
|
+
if (entry.startPromise)
|
|
68
|
+
return entry.startPromise;
|
|
69
|
+
if (entry.attempts >= MAX_ATTEMPTS) {
|
|
70
|
+
this.disable(entry, entry.reason ?? "attempt limit reached");
|
|
71
|
+
return this.ensureClient(entry);
|
|
72
|
+
}
|
|
73
|
+
entry.attempts += 1;
|
|
74
|
+
entry.state = "starting";
|
|
75
|
+
entry.reason = undefined;
|
|
76
|
+
this.emitStatus(entry);
|
|
77
|
+
const client = new LspClient({
|
|
78
|
+
command: entry.server.command,
|
|
79
|
+
args: entry.server.args,
|
|
80
|
+
cwd: entry.root,
|
|
81
|
+
env: this.options.env,
|
|
82
|
+
initializationTimeoutMs: entry.server.initializationTimeoutMs,
|
|
83
|
+
requestTimeoutMs: entry.server.requestTimeoutMs,
|
|
84
|
+
});
|
|
85
|
+
entry.client = client;
|
|
86
|
+
entry.unsubscribeFailure = client.onFailure((error) => this.onClientFailure(entry, client, error));
|
|
87
|
+
entry.startPromise = client
|
|
88
|
+
.start()
|
|
89
|
+
.then(() => {
|
|
90
|
+
if (this.closing)
|
|
91
|
+
throw new Error("LSP pool is disposed.");
|
|
92
|
+
entry.state = "ready";
|
|
93
|
+
this.emitStatus(entry);
|
|
94
|
+
return client;
|
|
95
|
+
})
|
|
96
|
+
.catch((error) => {
|
|
97
|
+
this.onClientFailure(entry, client, normalizeError(error));
|
|
98
|
+
throw error;
|
|
99
|
+
})
|
|
100
|
+
.finally(() => {
|
|
101
|
+
entry.startPromise = undefined;
|
|
102
|
+
});
|
|
103
|
+
return entry.startPromise;
|
|
104
|
+
}
|
|
105
|
+
onClientFailure(entry, client, error) {
|
|
106
|
+
if (entry.client !== client || entry.state === "closed" || this.closing)
|
|
107
|
+
return;
|
|
108
|
+
entry.unsubscribeFailure?.();
|
|
109
|
+
entry.unsubscribeFailure = undefined;
|
|
110
|
+
entry.client = undefined;
|
|
111
|
+
entry.reason = error.message;
|
|
112
|
+
if (entry.attempts >= MAX_ATTEMPTS) {
|
|
113
|
+
this.disable(entry, error.message);
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
entry.state = "degraded";
|
|
117
|
+
this.emitStatus(entry);
|
|
118
|
+
}
|
|
119
|
+
void client.dispose();
|
|
120
|
+
}
|
|
121
|
+
disable(entry, reason) {
|
|
122
|
+
entry.state = "disabled";
|
|
123
|
+
entry.reason = reason;
|
|
124
|
+
this.emitStatus(entry);
|
|
125
|
+
}
|
|
126
|
+
toStatus(entry) {
|
|
127
|
+
return {
|
|
128
|
+
serverId: entry.server.id,
|
|
129
|
+
root: entry.root,
|
|
130
|
+
state: entry.state,
|
|
131
|
+
attempts: entry.attempts,
|
|
132
|
+
...(entry.reason === undefined ? {} : { reason: entry.reason }),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
emitStatus(entry) {
|
|
136
|
+
this.options.onStatusChange?.(this.toStatus(entry));
|
|
137
|
+
}
|
|
138
|
+
async disposeEntries() {
|
|
139
|
+
this.closing = true;
|
|
140
|
+
await Promise.allSettled([...this.entries.values()].map(async (entry) => {
|
|
141
|
+
entry.unsubscribeFailure?.();
|
|
142
|
+
entry.unsubscribeFailure = undefined;
|
|
143
|
+
await entry.client?.dispose();
|
|
144
|
+
entry.client = undefined;
|
|
145
|
+
entry.state = "closed";
|
|
146
|
+
this.emitStatus(entry);
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=pool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.js","sourceRoot":"","sources":["../../../src/core/lsp/pool.ts"],"names":[],"mappings":"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","sourcesContent":["import { LspClient } from \"./client.ts\";\nimport {\n\ttype ResolvedLspRegistry,\n\ttype ResolvedLspServer,\n\ttype SelectedLspServer,\n\tselectLspServerForPath,\n} from \"./registry.ts\";\n\nconst MAX_ATTEMPTS = 3;\n\ntype PoolEntryState = \"starting\" | \"ready\" | \"degraded\" | \"disabled\" | \"closed\";\n\ninterface PoolEntry {\n\treadonly server: ResolvedLspServer;\n\treadonly root: string;\n\tattempts: number;\n\tstate: PoolEntryState;\n\treason?: string;\n\tclient?: LspClient;\n\tstartPromise?: Promise<LspClient>;\n\tunsubscribeFailure?: () => void;\n}\n\nexport interface LspPoolOptions {\n\treadonly env?: NodeJS.ProcessEnv;\n\t/**\n\t * Invoked synchronously on every server-root state transition (starting, ready,\n\t * degraded, disabled, closed) so a caller can build a chronological status history\n\t * across the pool's whole lifetime, not just a single snapshot (LSP.2).\n\t */\n\treadonly onStatusChange?: (status: LspPoolStatus) => void;\n}\n\nexport interface LspPoolConnection {\n\treadonly client: LspClient;\n\treadonly selection: SelectedLspServer;\n}\n\nexport interface LspPoolStatus {\n\treadonly serverId: string;\n\treadonly root: string;\n\treadonly state: PoolEntryState;\n\treadonly attempts: number;\n\treadonly reason?: string;\n}\n\nfunction entryKey(serverId: string, root: string): string {\n\treturn `${serverId}\\0${root}`;\n}\n\nfunction normalizeError(error: unknown): Error {\n\treturn error instanceof Error ? error : new Error(String(error));\n}\n\n/** Owns and bounds language-server clients for one resolved workspace registry. */\nexport class LspPool {\n\tprivate readonly registry: ResolvedLspRegistry;\n\tprivate readonly options: LspPoolOptions;\n\tprivate readonly entries = new Map<string, PoolEntry>();\n\tprivate startPromise: Promise<void> | undefined;\n\tprivate disposePromise: Promise<void> | undefined;\n\tprivate closing = false;\n\n\tconstructor(registry: ResolvedLspRegistry, options: LspPoolOptions = {}) {\n\t\tthis.registry = registry;\n\t\tthis.options = options;\n\t}\n\n\t/** Eagerly initialize the workspace-root process for every configured server. */\n\tstart(): Promise<void> {\n\t\tif (this.closing) return Promise.reject(new Error(\"LSP pool is disposed.\"));\n\t\tthis.startPromise ??= Promise.all(\n\t\t\tthis.registry.servers.map((server) =>\n\t\t\t\tthis.ensureClient(this.getOrCreateEntry(server, this.registry.workspace)),\n\t\t\t),\n\t\t).then(() => undefined);\n\t\treturn this.startPromise;\n\t}\n\n\t/** Select a file's server/root and return its ready shared client. */\n\tasync acquire(path: string): Promise<LspPoolConnection | undefined> {\n\t\tif (this.closing) throw new Error(\"LSP pool is disposed.\");\n\t\tconst selection = selectLspServerForPath(this.registry, path);\n\t\tif (!selection) return undefined;\n\t\tconst client = await this.ensureClient(this.getOrCreateEntry(selection.server, selection.root));\n\t\treturn { client, selection };\n\t}\n\n\tstatus(): readonly LspPoolStatus[] {\n\t\treturn [...this.entries.values()]\n\t\t\t.map((entry) => this.toStatus(entry))\n\t\t\t.sort((left, right) =>\n\t\t\t\tleft.serverId === right.serverId\n\t\t\t\t\t? left.root.localeCompare(right.root)\n\t\t\t\t\t: left.serverId.localeCompare(right.serverId),\n\t\t\t);\n\t}\n\n\tdispose(): Promise<void> {\n\t\tthis.disposePromise ??= this.disposeEntries();\n\t\treturn this.disposePromise;\n\t}\n\n\tprivate getOrCreateEntry(server: ResolvedLspServer, root: string): PoolEntry {\n\t\tconst key = entryKey(server.id, root);\n\t\tlet entry = this.entries.get(key);\n\t\tif (!entry) {\n\t\t\tentry = { server, root, attempts: 0, state: \"degraded\" };\n\t\t\tthis.entries.set(key, entry);\n\t\t}\n\t\treturn entry;\n\t}\n\n\tprivate ensureClient(entry: PoolEntry): Promise<LspClient> {\n\t\tif (entry.state === \"disabled\") {\n\t\t\treturn Promise.reject(\n\t\t\t\tnew Error(\n\t\t\t\t\t`LSP server ${entry.server.id} at ${entry.root} is disabled after ${entry.attempts} attempts: ${entry.reason}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (entry.state === \"closed\" || this.closing) return Promise.reject(new Error(\"LSP pool is disposed.\"));\n\t\tif (entry.state === \"ready\" && entry.client) return Promise.resolve(entry.client);\n\t\tif (entry.startPromise) return entry.startPromise;\n\t\tif (entry.attempts >= MAX_ATTEMPTS) {\n\t\t\tthis.disable(entry, entry.reason ?? \"attempt limit reached\");\n\t\t\treturn this.ensureClient(entry);\n\t\t}\n\n\t\tentry.attempts += 1;\n\t\tentry.state = \"starting\";\n\t\tentry.reason = undefined;\n\t\tthis.emitStatus(entry);\n\t\tconst client = new LspClient({\n\t\t\tcommand: entry.server.command,\n\t\t\targs: entry.server.args,\n\t\t\tcwd: entry.root,\n\t\t\tenv: this.options.env,\n\t\t\tinitializationTimeoutMs: entry.server.initializationTimeoutMs,\n\t\t\trequestTimeoutMs: entry.server.requestTimeoutMs,\n\t\t});\n\t\tentry.client = client;\n\t\tentry.unsubscribeFailure = client.onFailure((error) => this.onClientFailure(entry, client, error));\n\t\tentry.startPromise = client\n\t\t\t.start()\n\t\t\t.then(() => {\n\t\t\t\tif (this.closing) throw new Error(\"LSP pool is disposed.\");\n\t\t\t\tentry.state = \"ready\";\n\t\t\t\tthis.emitStatus(entry);\n\t\t\t\treturn client;\n\t\t\t})\n\t\t\t.catch((error) => {\n\t\t\t\tthis.onClientFailure(entry, client, normalizeError(error));\n\t\t\t\tthrow error;\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tentry.startPromise = undefined;\n\t\t\t});\n\t\treturn entry.startPromise;\n\t}\n\n\tprivate onClientFailure(entry: PoolEntry, client: LspClient, error: Error): void {\n\t\tif (entry.client !== client || entry.state === \"closed\" || this.closing) return;\n\t\tentry.unsubscribeFailure?.();\n\t\tentry.unsubscribeFailure = undefined;\n\t\tentry.client = undefined;\n\t\tentry.reason = error.message;\n\t\tif (entry.attempts >= MAX_ATTEMPTS) {\n\t\t\tthis.disable(entry, error.message);\n\t\t} else {\n\t\t\tentry.state = \"degraded\";\n\t\t\tthis.emitStatus(entry);\n\t\t}\n\t\tvoid client.dispose();\n\t}\n\n\tprivate disable(entry: PoolEntry, reason: string): void {\n\t\tentry.state = \"disabled\";\n\t\tentry.reason = reason;\n\t\tthis.emitStatus(entry);\n\t}\n\n\tprivate toStatus(entry: PoolEntry): LspPoolStatus {\n\t\treturn {\n\t\t\tserverId: entry.server.id,\n\t\t\troot: entry.root,\n\t\t\tstate: entry.state,\n\t\t\tattempts: entry.attempts,\n\t\t\t...(entry.reason === undefined ? {} : { reason: entry.reason }),\n\t\t};\n\t}\n\n\tprivate emitStatus(entry: PoolEntry): void {\n\t\tthis.options.onStatusChange?.(this.toStatus(entry));\n\t}\n\n\tprivate async disposeEntries(): Promise<void> {\n\t\tthis.closing = true;\n\t\tawait Promise.allSettled(\n\t\t\t[...this.entries.values()].map(async (entry) => {\n\t\t\t\tentry.unsubscribeFailure?.();\n\t\t\t\tentry.unsubscribeFailure = undefined;\n\t\t\t\tawait entry.client?.dispose();\n\t\t\t\tentry.client = undefined;\n\t\t\t\tentry.state = \"closed\";\n\t\t\t\tthis.emitStatus(entry);\n\t\t\t}),\n\t\t);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export interface LspLanguageSettings {
|
|
2
|
+
readonly languageId: string;
|
|
3
|
+
readonly extensions?: readonly string[];
|
|
4
|
+
readonly filenames?: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
export interface LspServerSettings {
|
|
7
|
+
readonly enabled?: boolean;
|
|
8
|
+
readonly command: string;
|
|
9
|
+
readonly args?: readonly string[];
|
|
10
|
+
readonly languages: readonly LspLanguageSettings[];
|
|
11
|
+
readonly rootMarkers?: readonly string[];
|
|
12
|
+
readonly initializationTimeoutMs?: number;
|
|
13
|
+
readonly requestTimeoutMs?: number;
|
|
14
|
+
readonly diagnosticsTimeoutMs?: number;
|
|
15
|
+
}
|
|
16
|
+
export type LspSettings = Readonly<Record<string, LspServerSettings>>;
|
|
17
|
+
export interface ResolvedLspLanguage {
|
|
18
|
+
readonly languageId: string;
|
|
19
|
+
readonly extensions: readonly string[];
|
|
20
|
+
readonly filenames: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
export interface ResolvedLspServer {
|
|
23
|
+
readonly id: string;
|
|
24
|
+
readonly command: string;
|
|
25
|
+
readonly args: readonly string[];
|
|
26
|
+
readonly languages: readonly ResolvedLspLanguage[];
|
|
27
|
+
readonly rootMarkers: readonly string[];
|
|
28
|
+
readonly initializationTimeoutMs: number;
|
|
29
|
+
readonly requestTimeoutMs: number;
|
|
30
|
+
readonly diagnosticsTimeoutMs: number;
|
|
31
|
+
}
|
|
32
|
+
export interface ResolvedLspRegistry {
|
|
33
|
+
readonly workspace: string;
|
|
34
|
+
readonly servers: readonly ResolvedLspServer[];
|
|
35
|
+
readonly platform: NodeJS.Platform;
|
|
36
|
+
}
|
|
37
|
+
export interface SelectedLspServer {
|
|
38
|
+
readonly server: ResolvedLspServer;
|
|
39
|
+
readonly languageId: string;
|
|
40
|
+
readonly path: string;
|
|
41
|
+
readonly root: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function resolveLspRegistry(settings: LspSettings | undefined, options: {
|
|
44
|
+
workspace: string;
|
|
45
|
+
env?: NodeJS.ProcessEnv;
|
|
46
|
+
platform?: NodeJS.Platform;
|
|
47
|
+
}): ResolvedLspRegistry;
|
|
48
|
+
export declare function selectLspServerForPath(registry: ResolvedLspRegistry, inputPath: string): SelectedLspServer | undefined;
|
|
49
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/registry.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACvC;AAED,MAAM,MAAM,WAAW,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAEtE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,SAAS,EAAE,SAAS,mBAAmB,EAAE,CAAC;IACnD,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AA0FD,wBAAgB,kBAAkB,CACjC,QAAQ,EAAE,WAAW,GAAG,SAAS,EACjC,OAAO,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;CAAE,GACjF,mBAAmB,CA0ErB;AA6BD,wBAAgB,sBAAsB,CACrC,QAAQ,EAAE,mBAAmB,EAC7B,SAAS,EAAE,MAAM,GACf,iBAAiB,GAAG,SAAS,CAoB/B","sourcesContent":["import { accessSync, constants, existsSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\n\nexport interface LspLanguageSettings {\n\treadonly languageId: string;\n\treadonly extensions?: readonly string[];\n\treadonly filenames?: readonly string[];\n}\n\nexport interface LspServerSettings {\n\treadonly enabled?: boolean;\n\treadonly command: string;\n\treadonly args?: readonly string[];\n\treadonly languages: readonly LspLanguageSettings[];\n\treadonly rootMarkers?: readonly string[];\n\treadonly initializationTimeoutMs?: number;\n\treadonly requestTimeoutMs?: number;\n\treadonly diagnosticsTimeoutMs?: number;\n}\n\nexport type LspSettings = Readonly<Record<string, LspServerSettings>>;\n\nexport interface ResolvedLspLanguage {\n\treadonly languageId: string;\n\treadonly extensions: readonly string[];\n\treadonly filenames: readonly string[];\n}\n\nexport interface ResolvedLspServer {\n\treadonly id: string;\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly languages: readonly ResolvedLspLanguage[];\n\treadonly rootMarkers: readonly string[];\n\treadonly initializationTimeoutMs: number;\n\treadonly requestTimeoutMs: number;\n\treadonly diagnosticsTimeoutMs: number;\n}\n\nexport interface ResolvedLspRegistry {\n\treadonly workspace: string;\n\treadonly servers: readonly ResolvedLspServer[];\n\treadonly platform: NodeJS.Platform;\n}\n\nexport interface SelectedLspServer {\n\treadonly server: ResolvedLspServer;\n\treadonly languageId: string;\n\treadonly path: string;\n\treadonly root: string;\n}\n\nconst SERVER_KEYS = new Set([\n\t\"enabled\",\n\t\"command\",\n\t\"args\",\n\t\"languages\",\n\t\"rootMarkers\",\n\t\"initializationTimeoutMs\",\n\t\"requestTimeoutMs\",\n\t\"diagnosticsTimeoutMs\",\n]);\nconst LANGUAGE_KEYS = new Set([\"languageId\", \"extensions\", \"filenames\"]);\n\nfunction canonical(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn resolve(path);\n\t}\n}\n\nfunction compareKey(value: string, platform: NodeJS.Platform): string {\n\treturn platform === \"win32\" ? value.toLowerCase() : value;\n}\n\nfunction isContained(workspace: string, candidate: string, platform: NodeJS.Platform): boolean {\n\tconst rel = relative(compareKey(workspace, platform), compareKey(candidate, platform));\n\treturn rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\nfunction validateStringArray(value: unknown, label: string, options?: { allowEmpty?: boolean }): readonly string[] {\n\tif (!Array.isArray(value) || (!options?.allowEmpty && value.length === 0)) {\n\t\tthrow new Error(`${label} must be ${options?.allowEmpty ? \"an\" : \"a non-empty\"} array of strings.`);\n\t}\n\tif (value.some((item) => typeof item !== \"string\" || item.length === 0)) {\n\t\tthrow new Error(`${label} must contain only non-empty strings.`);\n\t}\n\treturn value;\n}\n\nfunction validateTimeout(value: unknown, fallback: number, label: string): number {\n\tif (value === undefined) return fallback;\n\tif (!Number.isSafeInteger(value) || (value as number) < 100 || (value as number) > 120_000) {\n\t\tthrow new Error(`${label} must be a safe integer from 100 to 120000.`);\n\t}\n\treturn value as number;\n}\n\nfunction isExecutableFile(path: string, platform: NodeJS.Platform): boolean {\n\ttry {\n\t\tif (!statSync(path).isFile()) return false;\n\t\tif (platform !== \"win32\") accessSync(path, constants.X_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction resolveExecutable(command: string, env: NodeJS.ProcessEnv, platform: NodeJS.Platform): string {\n\tif (command.length === 0) throw new Error(\"LSP command must be non-empty.\");\n\tconst hasSeparator = command.includes(\"/\") || command.includes(\"\\\\\");\n\tif (hasSeparator && !isAbsolute(command)) {\n\t\tthrow new Error(`LSP command ${JSON.stringify(command)} must be a bare executable name or an absolute path.`);\n\t}\n\tif (isAbsolute(command)) {\n\t\tconst candidate = canonical(command);\n\t\tif (!isExecutableFile(candidate, platform)) throw new Error(`LSP executable is not runnable: ${candidate}`);\n\t\treturn candidate;\n\t}\n\n\tconst pathValue = env.PATH ?? env.Path ?? env.path ?? \"\";\n\tconst extensions = platform === \"win32\" ? (env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\").split(\";\").filter(Boolean) : [\"\"];\n\tconst commandHasExtension = platform === \"win32\" && extname(command) !== \"\";\n\tfor (const directory of pathValue.split(delimiter).filter(Boolean)) {\n\t\tconst candidates =\n\t\t\tplatform === \"win32\" && !commandHasExtension\n\t\t\t\t? extensions.map((extension) => join(directory, `${command}${extension}`))\n\t\t\t\t: [join(directory, command)];\n\t\tfor (const candidate of candidates) if (isExecutableFile(candidate, platform)) return canonical(candidate);\n\t}\n\tthrow new Error(`LSP executable ${JSON.stringify(command)} was not found in PATH: ${pathValue || \"<empty>\"}`);\n}\n\nfunction validateUnknownKeys(value: object, allowed: ReadonlySet<string>, label: string): void {\n\tconst unknown = Object.keys(value).filter((key) => !allowed.has(key));\n\tif (unknown.length > 0)\n\t\tthrow new Error(`${label} contains unknown key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`);\n}\n\nexport function resolveLspRegistry(\n\tsettings: LspSettings | undefined,\n\toptions: { workspace: string; env?: NodeJS.ProcessEnv; platform?: NodeJS.Platform },\n): ResolvedLspRegistry {\n\tconst workspace = canonical(options.workspace);\n\tif (!statSync(workspace).isDirectory()) throw new Error(`LSP workspace is not a directory: ${workspace}`);\n\tconst platform = options.platform ?? process.platform;\n\tconst env = options.env ?? process.env;\n\tconst servers: ResolvedLspServer[] = [];\n\tconst claimedMatchers = new Map<string, string>();\n\n\tfor (const [id, raw] of Object.entries(settings ?? {})) {\n\t\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) throw new Error(`LSP server ${id} must be an object.`);\n\t\tvalidateUnknownKeys(raw, SERVER_KEYS, `LSP server ${id}`);\n\t\tif (raw.enabled === false) continue;\n\t\tif (typeof raw.command !== \"string\") throw new Error(`LSP server ${id}.command must be a string.`);\n\t\tconst args =\n\t\t\traw.args === undefined ? [] : validateStringArray(raw.args, `LSP server ${id}.args`, { allowEmpty: true });\n\t\tif (!Array.isArray(raw.languages) || raw.languages.length === 0) {\n\t\t\tthrow new Error(`LSP server ${id}.languages must be a non-empty array.`);\n\t\t}\n\t\tconst languages = raw.languages.map((language, index): ResolvedLspLanguage => {\n\t\t\tif (!language || typeof language !== \"object\" || Array.isArray(language)) {\n\t\t\t\tthrow new Error(`LSP server ${id}.languages[${index}] must be an object.`);\n\t\t\t}\n\t\t\tvalidateUnknownKeys(language, LANGUAGE_KEYS, `LSP server ${id}.languages[${index}]`);\n\t\t\tif (typeof language.languageId !== \"string\" || language.languageId.length === 0) {\n\t\t\t\tthrow new Error(`LSP server ${id}.languages[${index}].languageId must be non-empty.`);\n\t\t\t}\n\t\t\tconst extensions =\n\t\t\t\tlanguage.extensions === undefined\n\t\t\t\t\t? []\n\t\t\t\t\t: validateStringArray(language.extensions, `LSP server ${id}.languages[${index}].extensions`, {\n\t\t\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\t\t});\n\t\t\tconst filenames =\n\t\t\t\tlanguage.filenames === undefined\n\t\t\t\t\t? []\n\t\t\t\t\t: validateStringArray(language.filenames, `LSP server ${id}.languages[${index}].filenames`, {\n\t\t\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\t\t});\n\t\t\tif (extensions.length === 0 && filenames.length === 0)\n\t\t\t\tthrow new Error(`LSP language ${language.languageId} has no matchers.`);\n\t\t\tfor (const extension of extensions) {\n\t\t\t\tif (!extension.startsWith(\".\"))\n\t\t\t\t\tthrow new Error(`LSP extension ${JSON.stringify(extension)} must start with \".\".`);\n\t\t\t\tconst key = `extension:${compareKey(extension, platform)}`;\n\t\t\t\tconst owner = claimedMatchers.get(key);\n\t\t\t\tif (owner) throw new Error(`LSP ambiguous matcher ${extension}: claimed by ${owner} and ${id}.`);\n\t\t\t\tclaimedMatchers.set(key, id);\n\t\t\t}\n\t\t\tfor (const filename of filenames) {\n\t\t\t\tconst key = `filename:${compareKey(filename, platform)}`;\n\t\t\t\tconst owner = claimedMatchers.get(key);\n\t\t\t\tif (owner) throw new Error(`LSP ambiguous matcher ${filename}: claimed by ${owner} and ${id}.`);\n\t\t\t\tclaimedMatchers.set(key, id);\n\t\t\t}\n\t\t\treturn { languageId: language.languageId, extensions, filenames };\n\t\t});\n\t\tconst rootMarkers =\n\t\t\traw.rootMarkers === undefined\n\t\t\t\t? []\n\t\t\t\t: validateStringArray(raw.rootMarkers, `LSP server ${id}.rootMarkers`, { allowEmpty: true });\n\t\tif (rootMarkers.some((marker) => basename(marker) !== marker))\n\t\t\tthrow new Error(`LSP root markers must be basenames.`);\n\t\tservers.push({\n\t\t\tid,\n\t\t\tcommand: resolveExecutable(raw.command, env, platform),\n\t\t\targs,\n\t\t\tlanguages,\n\t\t\trootMarkers,\n\t\t\tinitializationTimeoutMs: validateTimeout(raw.initializationTimeoutMs, 10_000, `${id}.initializationTimeoutMs`),\n\t\t\trequestTimeoutMs: validateTimeout(raw.requestTimeoutMs, 10_000, `${id}.requestTimeoutMs`),\n\t\t\tdiagnosticsTimeoutMs: validateTimeout(raw.diagnosticsTimeoutMs, 2_000, `${id}.diagnosticsTimeoutMs`),\n\t\t});\n\t}\n\treturn { workspace, servers, platform };\n}\n\nfunction matchLanguage(\n\tregistry: ResolvedLspRegistry,\n\tpath: string,\n): { server: ResolvedLspServer; languageId: string } | undefined {\n\tconst filename = compareKey(basename(path), registry.platform);\n\tconst exact: Array<{ server: ResolvedLspServer; languageId: string }> = [];\n\tconst suffixes: Array<{ server: ResolvedLspServer; languageId: string; length: number }> = [];\n\tfor (const server of registry.servers) {\n\t\tfor (const language of server.languages) {\n\t\t\tif (language.filenames.some((candidate) => compareKey(candidate, registry.platform) === filename)) {\n\t\t\t\texact.push({ server, languageId: language.languageId });\n\t\t\t}\n\t\t\tfor (const extension of language.extensions) {\n\t\t\t\tconst normalized = compareKey(extension, registry.platform);\n\t\t\t\tif (filename.endsWith(normalized))\n\t\t\t\t\tsuffixes.push({ server, languageId: language.languageId, length: normalized.length });\n\t\t\t}\n\t\t}\n\t}\n\tif (exact.length > 1) throw new Error(`LSP path has ambiguous exact filename matches: ${path}`);\n\tif (exact[0]) return exact[0];\n\tconst max = suffixes.reduce((value, match) => Math.max(value, match.length), -1);\n\tconst longest = suffixes.filter((match) => match.length === max);\n\tif (longest.length > 1) throw new Error(`LSP path has ambiguous extension matches: ${path}`);\n\treturn longest[0];\n}\n\nexport function selectLspServerForPath(\n\tregistry: ResolvedLspRegistry,\n\tinputPath: string,\n): SelectedLspServer | undefined {\n\tif (!existsSync(inputPath) || !statSync(inputPath).isFile())\n\t\tthrow new Error(`LSP path is not an existing file: ${inputPath}`);\n\tconst path = canonical(inputPath);\n\tif (!isContained(registry.workspace, path, registry.platform))\n\t\tthrow new Error(`Path is outside the LSP workspace: ${path}`);\n\tconst match = matchLanguage(registry, path);\n\tif (!match) return undefined;\n\tlet root = dirname(path);\n\twhile (true) {\n\t\tif (match.server.rootMarkers.some((marker) => existsSync(join(root, marker)))) break;\n\t\tif (compareKey(root, registry.platform) === compareKey(registry.workspace, registry.platform)) break;\n\t\tconst parent = dirname(root);\n\t\tif (parent === root || !isContained(registry.workspace, parent, registry.platform)) {\n\t\t\troot = registry.workspace;\n\t\t\tbreak;\n\t\t}\n\t\troot = parent;\n\t}\n\treturn { server: match.server, languageId: match.languageId, path, root: canonical(root) };\n}\n"]}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync, realpathSync, statSync } from "node:fs";
|
|
2
|
+
import { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
const SERVER_KEYS = new Set([
|
|
4
|
+
"enabled",
|
|
5
|
+
"command",
|
|
6
|
+
"args",
|
|
7
|
+
"languages",
|
|
8
|
+
"rootMarkers",
|
|
9
|
+
"initializationTimeoutMs",
|
|
10
|
+
"requestTimeoutMs",
|
|
11
|
+
"diagnosticsTimeoutMs",
|
|
12
|
+
]);
|
|
13
|
+
const LANGUAGE_KEYS = new Set(["languageId", "extensions", "filenames"]);
|
|
14
|
+
function canonical(path) {
|
|
15
|
+
try {
|
|
16
|
+
return realpathSync(path);
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return resolve(path);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function compareKey(value, platform) {
|
|
23
|
+
return platform === "win32" ? value.toLowerCase() : value;
|
|
24
|
+
}
|
|
25
|
+
function isContained(workspace, candidate, platform) {
|
|
26
|
+
const rel = relative(compareKey(workspace, platform), compareKey(candidate, platform));
|
|
27
|
+
return rel === "" || (!rel.startsWith(`..${sep}`) && rel !== ".." && !isAbsolute(rel));
|
|
28
|
+
}
|
|
29
|
+
function validateStringArray(value, label, options) {
|
|
30
|
+
if (!Array.isArray(value) || (!options?.allowEmpty && value.length === 0)) {
|
|
31
|
+
throw new Error(`${label} must be ${options?.allowEmpty ? "an" : "a non-empty"} array of strings.`);
|
|
32
|
+
}
|
|
33
|
+
if (value.some((item) => typeof item !== "string" || item.length === 0)) {
|
|
34
|
+
throw new Error(`${label} must contain only non-empty strings.`);
|
|
35
|
+
}
|
|
36
|
+
return value;
|
|
37
|
+
}
|
|
38
|
+
function validateTimeout(value, fallback, label) {
|
|
39
|
+
if (value === undefined)
|
|
40
|
+
return fallback;
|
|
41
|
+
if (!Number.isSafeInteger(value) || value < 100 || value > 120_000) {
|
|
42
|
+
throw new Error(`${label} must be a safe integer from 100 to 120000.`);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
function isExecutableFile(path, platform) {
|
|
47
|
+
try {
|
|
48
|
+
if (!statSync(path).isFile())
|
|
49
|
+
return false;
|
|
50
|
+
if (platform !== "win32")
|
|
51
|
+
accessSync(path, constants.X_OK);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function resolveExecutable(command, env, platform) {
|
|
59
|
+
if (command.length === 0)
|
|
60
|
+
throw new Error("LSP command must be non-empty.");
|
|
61
|
+
const hasSeparator = command.includes("/") || command.includes("\\");
|
|
62
|
+
if (hasSeparator && !isAbsolute(command)) {
|
|
63
|
+
throw new Error(`LSP command ${JSON.stringify(command)} must be a bare executable name or an absolute path.`);
|
|
64
|
+
}
|
|
65
|
+
if (isAbsolute(command)) {
|
|
66
|
+
const candidate = canonical(command);
|
|
67
|
+
if (!isExecutableFile(candidate, platform))
|
|
68
|
+
throw new Error(`LSP executable is not runnable: ${candidate}`);
|
|
69
|
+
return candidate;
|
|
70
|
+
}
|
|
71
|
+
const pathValue = env.PATH ?? env.Path ?? env.path ?? "";
|
|
72
|
+
const extensions = platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
|
|
73
|
+
const commandHasExtension = platform === "win32" && extname(command) !== "";
|
|
74
|
+
for (const directory of pathValue.split(delimiter).filter(Boolean)) {
|
|
75
|
+
const candidates = platform === "win32" && !commandHasExtension
|
|
76
|
+
? extensions.map((extension) => join(directory, `${command}${extension}`))
|
|
77
|
+
: [join(directory, command)];
|
|
78
|
+
for (const candidate of candidates)
|
|
79
|
+
if (isExecutableFile(candidate, platform))
|
|
80
|
+
return canonical(candidate);
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`LSP executable ${JSON.stringify(command)} was not found in PATH: ${pathValue || "<empty>"}`);
|
|
83
|
+
}
|
|
84
|
+
function validateUnknownKeys(value, allowed, label) {
|
|
85
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
86
|
+
if (unknown.length > 0)
|
|
87
|
+
throw new Error(`${label} contains unknown key${unknown.length === 1 ? "" : "s"}: ${unknown.join(", ")}`);
|
|
88
|
+
}
|
|
89
|
+
export function resolveLspRegistry(settings, options) {
|
|
90
|
+
const workspace = canonical(options.workspace);
|
|
91
|
+
if (!statSync(workspace).isDirectory())
|
|
92
|
+
throw new Error(`LSP workspace is not a directory: ${workspace}`);
|
|
93
|
+
const platform = options.platform ?? process.platform;
|
|
94
|
+
const env = options.env ?? process.env;
|
|
95
|
+
const servers = [];
|
|
96
|
+
const claimedMatchers = new Map();
|
|
97
|
+
for (const [id, raw] of Object.entries(settings ?? {})) {
|
|
98
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
99
|
+
throw new Error(`LSP server ${id} must be an object.`);
|
|
100
|
+
validateUnknownKeys(raw, SERVER_KEYS, `LSP server ${id}`);
|
|
101
|
+
if (raw.enabled === false)
|
|
102
|
+
continue;
|
|
103
|
+
if (typeof raw.command !== "string")
|
|
104
|
+
throw new Error(`LSP server ${id}.command must be a string.`);
|
|
105
|
+
const args = raw.args === undefined ? [] : validateStringArray(raw.args, `LSP server ${id}.args`, { allowEmpty: true });
|
|
106
|
+
if (!Array.isArray(raw.languages) || raw.languages.length === 0) {
|
|
107
|
+
throw new Error(`LSP server ${id}.languages must be a non-empty array.`);
|
|
108
|
+
}
|
|
109
|
+
const languages = raw.languages.map((language, index) => {
|
|
110
|
+
if (!language || typeof language !== "object" || Array.isArray(language)) {
|
|
111
|
+
throw new Error(`LSP server ${id}.languages[${index}] must be an object.`);
|
|
112
|
+
}
|
|
113
|
+
validateUnknownKeys(language, LANGUAGE_KEYS, `LSP server ${id}.languages[${index}]`);
|
|
114
|
+
if (typeof language.languageId !== "string" || language.languageId.length === 0) {
|
|
115
|
+
throw new Error(`LSP server ${id}.languages[${index}].languageId must be non-empty.`);
|
|
116
|
+
}
|
|
117
|
+
const extensions = language.extensions === undefined
|
|
118
|
+
? []
|
|
119
|
+
: validateStringArray(language.extensions, `LSP server ${id}.languages[${index}].extensions`, {
|
|
120
|
+
allowEmpty: true,
|
|
121
|
+
});
|
|
122
|
+
const filenames = language.filenames === undefined
|
|
123
|
+
? []
|
|
124
|
+
: validateStringArray(language.filenames, `LSP server ${id}.languages[${index}].filenames`, {
|
|
125
|
+
allowEmpty: true,
|
|
126
|
+
});
|
|
127
|
+
if (extensions.length === 0 && filenames.length === 0)
|
|
128
|
+
throw new Error(`LSP language ${language.languageId} has no matchers.`);
|
|
129
|
+
for (const extension of extensions) {
|
|
130
|
+
if (!extension.startsWith("."))
|
|
131
|
+
throw new Error(`LSP extension ${JSON.stringify(extension)} must start with ".".`);
|
|
132
|
+
const key = `extension:${compareKey(extension, platform)}`;
|
|
133
|
+
const owner = claimedMatchers.get(key);
|
|
134
|
+
if (owner)
|
|
135
|
+
throw new Error(`LSP ambiguous matcher ${extension}: claimed by ${owner} and ${id}.`);
|
|
136
|
+
claimedMatchers.set(key, id);
|
|
137
|
+
}
|
|
138
|
+
for (const filename of filenames) {
|
|
139
|
+
const key = `filename:${compareKey(filename, platform)}`;
|
|
140
|
+
const owner = claimedMatchers.get(key);
|
|
141
|
+
if (owner)
|
|
142
|
+
throw new Error(`LSP ambiguous matcher ${filename}: claimed by ${owner} and ${id}.`);
|
|
143
|
+
claimedMatchers.set(key, id);
|
|
144
|
+
}
|
|
145
|
+
return { languageId: language.languageId, extensions, filenames };
|
|
146
|
+
});
|
|
147
|
+
const rootMarkers = raw.rootMarkers === undefined
|
|
148
|
+
? []
|
|
149
|
+
: validateStringArray(raw.rootMarkers, `LSP server ${id}.rootMarkers`, { allowEmpty: true });
|
|
150
|
+
if (rootMarkers.some((marker) => basename(marker) !== marker))
|
|
151
|
+
throw new Error(`LSP root markers must be basenames.`);
|
|
152
|
+
servers.push({
|
|
153
|
+
id,
|
|
154
|
+
command: resolveExecutable(raw.command, env, platform),
|
|
155
|
+
args,
|
|
156
|
+
languages,
|
|
157
|
+
rootMarkers,
|
|
158
|
+
initializationTimeoutMs: validateTimeout(raw.initializationTimeoutMs, 10_000, `${id}.initializationTimeoutMs`),
|
|
159
|
+
requestTimeoutMs: validateTimeout(raw.requestTimeoutMs, 10_000, `${id}.requestTimeoutMs`),
|
|
160
|
+
diagnosticsTimeoutMs: validateTimeout(raw.diagnosticsTimeoutMs, 2_000, `${id}.diagnosticsTimeoutMs`),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
return { workspace, servers, platform };
|
|
164
|
+
}
|
|
165
|
+
function matchLanguage(registry, path) {
|
|
166
|
+
const filename = compareKey(basename(path), registry.platform);
|
|
167
|
+
const exact = [];
|
|
168
|
+
const suffixes = [];
|
|
169
|
+
for (const server of registry.servers) {
|
|
170
|
+
for (const language of server.languages) {
|
|
171
|
+
if (language.filenames.some((candidate) => compareKey(candidate, registry.platform) === filename)) {
|
|
172
|
+
exact.push({ server, languageId: language.languageId });
|
|
173
|
+
}
|
|
174
|
+
for (const extension of language.extensions) {
|
|
175
|
+
const normalized = compareKey(extension, registry.platform);
|
|
176
|
+
if (filename.endsWith(normalized))
|
|
177
|
+
suffixes.push({ server, languageId: language.languageId, length: normalized.length });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if (exact.length > 1)
|
|
182
|
+
throw new Error(`LSP path has ambiguous exact filename matches: ${path}`);
|
|
183
|
+
if (exact[0])
|
|
184
|
+
return exact[0];
|
|
185
|
+
const max = suffixes.reduce((value, match) => Math.max(value, match.length), -1);
|
|
186
|
+
const longest = suffixes.filter((match) => match.length === max);
|
|
187
|
+
if (longest.length > 1)
|
|
188
|
+
throw new Error(`LSP path has ambiguous extension matches: ${path}`);
|
|
189
|
+
return longest[0];
|
|
190
|
+
}
|
|
191
|
+
export function selectLspServerForPath(registry, inputPath) {
|
|
192
|
+
if (!existsSync(inputPath) || !statSync(inputPath).isFile())
|
|
193
|
+
throw new Error(`LSP path is not an existing file: ${inputPath}`);
|
|
194
|
+
const path = canonical(inputPath);
|
|
195
|
+
if (!isContained(registry.workspace, path, registry.platform))
|
|
196
|
+
throw new Error(`Path is outside the LSP workspace: ${path}`);
|
|
197
|
+
const match = matchLanguage(registry, path);
|
|
198
|
+
if (!match)
|
|
199
|
+
return undefined;
|
|
200
|
+
let root = dirname(path);
|
|
201
|
+
while (true) {
|
|
202
|
+
if (match.server.rootMarkers.some((marker) => existsSync(join(root, marker))))
|
|
203
|
+
break;
|
|
204
|
+
if (compareKey(root, registry.platform) === compareKey(registry.workspace, registry.platform))
|
|
205
|
+
break;
|
|
206
|
+
const parent = dirname(root);
|
|
207
|
+
if (parent === root || !isContained(registry.workspace, parent, registry.platform)) {
|
|
208
|
+
root = registry.workspace;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
root = parent;
|
|
212
|
+
}
|
|
213
|
+
return { server: match.server, languageId: match.languageId, path, root: canonical(root) };
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/core/lsp/registry.ts"],"names":[],"mappings":"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","sourcesContent":["import { accessSync, constants, existsSync, realpathSync, statSync } from \"node:fs\";\nimport { basename, delimiter, dirname, extname, isAbsolute, join, relative, resolve, sep } from \"node:path\";\n\nexport interface LspLanguageSettings {\n\treadonly languageId: string;\n\treadonly extensions?: readonly string[];\n\treadonly filenames?: readonly string[];\n}\n\nexport interface LspServerSettings {\n\treadonly enabled?: boolean;\n\treadonly command: string;\n\treadonly args?: readonly string[];\n\treadonly languages: readonly LspLanguageSettings[];\n\treadonly rootMarkers?: readonly string[];\n\treadonly initializationTimeoutMs?: number;\n\treadonly requestTimeoutMs?: number;\n\treadonly diagnosticsTimeoutMs?: number;\n}\n\nexport type LspSettings = Readonly<Record<string, LspServerSettings>>;\n\nexport interface ResolvedLspLanguage {\n\treadonly languageId: string;\n\treadonly extensions: readonly string[];\n\treadonly filenames: readonly string[];\n}\n\nexport interface ResolvedLspServer {\n\treadonly id: string;\n\treadonly command: string;\n\treadonly args: readonly string[];\n\treadonly languages: readonly ResolvedLspLanguage[];\n\treadonly rootMarkers: readonly string[];\n\treadonly initializationTimeoutMs: number;\n\treadonly requestTimeoutMs: number;\n\treadonly diagnosticsTimeoutMs: number;\n}\n\nexport interface ResolvedLspRegistry {\n\treadonly workspace: string;\n\treadonly servers: readonly ResolvedLspServer[];\n\treadonly platform: NodeJS.Platform;\n}\n\nexport interface SelectedLspServer {\n\treadonly server: ResolvedLspServer;\n\treadonly languageId: string;\n\treadonly path: string;\n\treadonly root: string;\n}\n\nconst SERVER_KEYS = new Set([\n\t\"enabled\",\n\t\"command\",\n\t\"args\",\n\t\"languages\",\n\t\"rootMarkers\",\n\t\"initializationTimeoutMs\",\n\t\"requestTimeoutMs\",\n\t\"diagnosticsTimeoutMs\",\n]);\nconst LANGUAGE_KEYS = new Set([\"languageId\", \"extensions\", \"filenames\"]);\n\nfunction canonical(path: string): string {\n\ttry {\n\t\treturn realpathSync(path);\n\t} catch {\n\t\treturn resolve(path);\n\t}\n}\n\nfunction compareKey(value: string, platform: NodeJS.Platform): string {\n\treturn platform === \"win32\" ? value.toLowerCase() : value;\n}\n\nfunction isContained(workspace: string, candidate: string, platform: NodeJS.Platform): boolean {\n\tconst rel = relative(compareKey(workspace, platform), compareKey(candidate, platform));\n\treturn rel === \"\" || (!rel.startsWith(`..${sep}`) && rel !== \"..\" && !isAbsolute(rel));\n}\n\nfunction validateStringArray(value: unknown, label: string, options?: { allowEmpty?: boolean }): readonly string[] {\n\tif (!Array.isArray(value) || (!options?.allowEmpty && value.length === 0)) {\n\t\tthrow new Error(`${label} must be ${options?.allowEmpty ? \"an\" : \"a non-empty\"} array of strings.`);\n\t}\n\tif (value.some((item) => typeof item !== \"string\" || item.length === 0)) {\n\t\tthrow new Error(`${label} must contain only non-empty strings.`);\n\t}\n\treturn value;\n}\n\nfunction validateTimeout(value: unknown, fallback: number, label: string): number {\n\tif (value === undefined) return fallback;\n\tif (!Number.isSafeInteger(value) || (value as number) < 100 || (value as number) > 120_000) {\n\t\tthrow new Error(`${label} must be a safe integer from 100 to 120000.`);\n\t}\n\treturn value as number;\n}\n\nfunction isExecutableFile(path: string, platform: NodeJS.Platform): boolean {\n\ttry {\n\t\tif (!statSync(path).isFile()) return false;\n\t\tif (platform !== \"win32\") accessSync(path, constants.X_OK);\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction resolveExecutable(command: string, env: NodeJS.ProcessEnv, platform: NodeJS.Platform): string {\n\tif (command.length === 0) throw new Error(\"LSP command must be non-empty.\");\n\tconst hasSeparator = command.includes(\"/\") || command.includes(\"\\\\\");\n\tif (hasSeparator && !isAbsolute(command)) {\n\t\tthrow new Error(`LSP command ${JSON.stringify(command)} must be a bare executable name or an absolute path.`);\n\t}\n\tif (isAbsolute(command)) {\n\t\tconst candidate = canonical(command);\n\t\tif (!isExecutableFile(candidate, platform)) throw new Error(`LSP executable is not runnable: ${candidate}`);\n\t\treturn candidate;\n\t}\n\n\tconst pathValue = env.PATH ?? env.Path ?? env.path ?? \"\";\n\tconst extensions = platform === \"win32\" ? (env.PATHEXT ?? \".COM;.EXE;.BAT;.CMD\").split(\";\").filter(Boolean) : [\"\"];\n\tconst commandHasExtension = platform === \"win32\" && extname(command) !== \"\";\n\tfor (const directory of pathValue.split(delimiter).filter(Boolean)) {\n\t\tconst candidates =\n\t\t\tplatform === \"win32\" && !commandHasExtension\n\t\t\t\t? extensions.map((extension) => join(directory, `${command}${extension}`))\n\t\t\t\t: [join(directory, command)];\n\t\tfor (const candidate of candidates) if (isExecutableFile(candidate, platform)) return canonical(candidate);\n\t}\n\tthrow new Error(`LSP executable ${JSON.stringify(command)} was not found in PATH: ${pathValue || \"<empty>\"}`);\n}\n\nfunction validateUnknownKeys(value: object, allowed: ReadonlySet<string>, label: string): void {\n\tconst unknown = Object.keys(value).filter((key) => !allowed.has(key));\n\tif (unknown.length > 0)\n\t\tthrow new Error(`${label} contains unknown key${unknown.length === 1 ? \"\" : \"s\"}: ${unknown.join(\", \")}`);\n}\n\nexport function resolveLspRegistry(\n\tsettings: LspSettings | undefined,\n\toptions: { workspace: string; env?: NodeJS.ProcessEnv; platform?: NodeJS.Platform },\n): ResolvedLspRegistry {\n\tconst workspace = canonical(options.workspace);\n\tif (!statSync(workspace).isDirectory()) throw new Error(`LSP workspace is not a directory: ${workspace}`);\n\tconst platform = options.platform ?? process.platform;\n\tconst env = options.env ?? process.env;\n\tconst servers: ResolvedLspServer[] = [];\n\tconst claimedMatchers = new Map<string, string>();\n\n\tfor (const [id, raw] of Object.entries(settings ?? {})) {\n\t\tif (!raw || typeof raw !== \"object\" || Array.isArray(raw)) throw new Error(`LSP server ${id} must be an object.`);\n\t\tvalidateUnknownKeys(raw, SERVER_KEYS, `LSP server ${id}`);\n\t\tif (raw.enabled === false) continue;\n\t\tif (typeof raw.command !== \"string\") throw new Error(`LSP server ${id}.command must be a string.`);\n\t\tconst args =\n\t\t\traw.args === undefined ? [] : validateStringArray(raw.args, `LSP server ${id}.args`, { allowEmpty: true });\n\t\tif (!Array.isArray(raw.languages) || raw.languages.length === 0) {\n\t\t\tthrow new Error(`LSP server ${id}.languages must be a non-empty array.`);\n\t\t}\n\t\tconst languages = raw.languages.map((language, index): ResolvedLspLanguage => {\n\t\t\tif (!language || typeof language !== \"object\" || Array.isArray(language)) {\n\t\t\t\tthrow new Error(`LSP server ${id}.languages[${index}] must be an object.`);\n\t\t\t}\n\t\t\tvalidateUnknownKeys(language, LANGUAGE_KEYS, `LSP server ${id}.languages[${index}]`);\n\t\t\tif (typeof language.languageId !== \"string\" || language.languageId.length === 0) {\n\t\t\t\tthrow new Error(`LSP server ${id}.languages[${index}].languageId must be non-empty.`);\n\t\t\t}\n\t\t\tconst extensions =\n\t\t\t\tlanguage.extensions === undefined\n\t\t\t\t\t? []\n\t\t\t\t\t: validateStringArray(language.extensions, `LSP server ${id}.languages[${index}].extensions`, {\n\t\t\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\t\t});\n\t\t\tconst filenames =\n\t\t\t\tlanguage.filenames === undefined\n\t\t\t\t\t? []\n\t\t\t\t\t: validateStringArray(language.filenames, `LSP server ${id}.languages[${index}].filenames`, {\n\t\t\t\t\t\t\tallowEmpty: true,\n\t\t\t\t\t\t});\n\t\t\tif (extensions.length === 0 && filenames.length === 0)\n\t\t\t\tthrow new Error(`LSP language ${language.languageId} has no matchers.`);\n\t\t\tfor (const extension of extensions) {\n\t\t\t\tif (!extension.startsWith(\".\"))\n\t\t\t\t\tthrow new Error(`LSP extension ${JSON.stringify(extension)} must start with \".\".`);\n\t\t\t\tconst key = `extension:${compareKey(extension, platform)}`;\n\t\t\t\tconst owner = claimedMatchers.get(key);\n\t\t\t\tif (owner) throw new Error(`LSP ambiguous matcher ${extension}: claimed by ${owner} and ${id}.`);\n\t\t\t\tclaimedMatchers.set(key, id);\n\t\t\t}\n\t\t\tfor (const filename of filenames) {\n\t\t\t\tconst key = `filename:${compareKey(filename, platform)}`;\n\t\t\t\tconst owner = claimedMatchers.get(key);\n\t\t\t\tif (owner) throw new Error(`LSP ambiguous matcher ${filename}: claimed by ${owner} and ${id}.`);\n\t\t\t\tclaimedMatchers.set(key, id);\n\t\t\t}\n\t\t\treturn { languageId: language.languageId, extensions, filenames };\n\t\t});\n\t\tconst rootMarkers =\n\t\t\traw.rootMarkers === undefined\n\t\t\t\t? []\n\t\t\t\t: validateStringArray(raw.rootMarkers, `LSP server ${id}.rootMarkers`, { allowEmpty: true });\n\t\tif (rootMarkers.some((marker) => basename(marker) !== marker))\n\t\t\tthrow new Error(`LSP root markers must be basenames.`);\n\t\tservers.push({\n\t\t\tid,\n\t\t\tcommand: resolveExecutable(raw.command, env, platform),\n\t\t\targs,\n\t\t\tlanguages,\n\t\t\trootMarkers,\n\t\t\tinitializationTimeoutMs: validateTimeout(raw.initializationTimeoutMs, 10_000, `${id}.initializationTimeoutMs`),\n\t\t\trequestTimeoutMs: validateTimeout(raw.requestTimeoutMs, 10_000, `${id}.requestTimeoutMs`),\n\t\t\tdiagnosticsTimeoutMs: validateTimeout(raw.diagnosticsTimeoutMs, 2_000, `${id}.diagnosticsTimeoutMs`),\n\t\t});\n\t}\n\treturn { workspace, servers, platform };\n}\n\nfunction matchLanguage(\n\tregistry: ResolvedLspRegistry,\n\tpath: string,\n): { server: ResolvedLspServer; languageId: string } | undefined {\n\tconst filename = compareKey(basename(path), registry.platform);\n\tconst exact: Array<{ server: ResolvedLspServer; languageId: string }> = [];\n\tconst suffixes: Array<{ server: ResolvedLspServer; languageId: string; length: number }> = [];\n\tfor (const server of registry.servers) {\n\t\tfor (const language of server.languages) {\n\t\t\tif (language.filenames.some((candidate) => compareKey(candidate, registry.platform) === filename)) {\n\t\t\t\texact.push({ server, languageId: language.languageId });\n\t\t\t}\n\t\t\tfor (const extension of language.extensions) {\n\t\t\t\tconst normalized = compareKey(extension, registry.platform);\n\t\t\t\tif (filename.endsWith(normalized))\n\t\t\t\t\tsuffixes.push({ server, languageId: language.languageId, length: normalized.length });\n\t\t\t}\n\t\t}\n\t}\n\tif (exact.length > 1) throw new Error(`LSP path has ambiguous exact filename matches: ${path}`);\n\tif (exact[0]) return exact[0];\n\tconst max = suffixes.reduce((value, match) => Math.max(value, match.length), -1);\n\tconst longest = suffixes.filter((match) => match.length === max);\n\tif (longest.length > 1) throw new Error(`LSP path has ambiguous extension matches: ${path}`);\n\treturn longest[0];\n}\n\nexport function selectLspServerForPath(\n\tregistry: ResolvedLspRegistry,\n\tinputPath: string,\n): SelectedLspServer | undefined {\n\tif (!existsSync(inputPath) || !statSync(inputPath).isFile())\n\t\tthrow new Error(`LSP path is not an existing file: ${inputPath}`);\n\tconst path = canonical(inputPath);\n\tif (!isContained(registry.workspace, path, registry.platform))\n\t\tthrow new Error(`Path is outside the LSP workspace: ${path}`);\n\tconst match = matchLanguage(registry, path);\n\tif (!match) return undefined;\n\tlet root = dirname(path);\n\twhile (true) {\n\t\tif (match.server.rootMarkers.some((marker) => existsSync(join(root, marker)))) break;\n\t\tif (compareKey(root, registry.platform) === compareKey(registry.workspace, registry.platform)) break;\n\t\tconst parent = dirname(root);\n\t\tif (parent === root || !isContained(registry.workspace, parent, registry.platform)) {\n\t\t\troot = registry.workspace;\n\t\t\tbreak;\n\t\t}\n\t\troot = parent;\n\t}\n\treturn { server: match.server, languageId: match.languageId, path, root: canonical(root) };\n}\n"]}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type LspServerState = "resolved" | "starting" | "ready" | "degraded" | "disabled" | "closing" | "closed";
|
|
2
|
+
export interface LspStatus {
|
|
3
|
+
readonly serverId: string;
|
|
4
|
+
readonly root: string;
|
|
5
|
+
readonly state: LspServerState;
|
|
6
|
+
readonly attempt: number;
|
|
7
|
+
readonly reason?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare class LspStatusStore {
|
|
10
|
+
private readonly capacity;
|
|
11
|
+
private readonly statuses;
|
|
12
|
+
private readonly current;
|
|
13
|
+
private _totalCount;
|
|
14
|
+
constructor(options?: {
|
|
15
|
+
capacity?: number;
|
|
16
|
+
});
|
|
17
|
+
get totalCount(): number;
|
|
18
|
+
record(status: LspStatus): void;
|
|
19
|
+
latest(serverId: string, root: string): LspStatus | undefined;
|
|
20
|
+
list(): readonly LspStatus[];
|
|
21
|
+
private key;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/status.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAEhH,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,cAAc,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,cAAc;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgC;IACxD,OAAO,CAAC,WAAW,CAAK;IAExB,YAAY,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,EAM1C;IAED,IAAI,UAAU,IAAI,MAAM,CAEvB;IAED,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAM9B;IAED,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAG5D;IAED,IAAI,IAAI,SAAS,SAAS,EAAE,CAE3B;IAED,OAAO,CAAC,GAAG;CAGX","sourcesContent":["export type LspServerState = \"resolved\" | \"starting\" | \"ready\" | \"degraded\" | \"disabled\" | \"closing\" | \"closed\";\n\nexport interface LspStatus {\n\treadonly serverId: string;\n\treadonly root: string;\n\treadonly state: LspServerState;\n\treadonly attempt: number;\n\treadonly reason?: string;\n}\n\nexport class LspStatusStore {\n\tprivate readonly capacity: number;\n\tprivate readonly statuses: LspStatus[] = [];\n\tprivate readonly current = new Map<string, LspStatus>();\n\tprivate _totalCount = 0;\n\n\tconstructor(options?: { capacity?: number }) {\n\t\tconst capacity = options?.capacity ?? 100;\n\t\tif (!Number.isSafeInteger(capacity) || capacity < 1) {\n\t\t\tthrow new Error(\"LSP status capacity must be a positive safe integer.\");\n\t\t}\n\t\tthis.capacity = capacity;\n\t}\n\n\tget totalCount(): number {\n\t\treturn this._totalCount;\n\t}\n\n\trecord(status: LspStatus): void {\n\t\tthis._totalCount += 1;\n\t\tconst snapshot = { ...status };\n\t\tthis.statuses.push(snapshot);\n\t\tthis.current.set(this.key(status.serverId, status.root), snapshot);\n\t\tif (this.statuses.length > this.capacity) this.statuses.splice(0, this.statuses.length - this.capacity);\n\t}\n\n\tlatest(serverId: string, root: string): LspStatus | undefined {\n\t\tconst status = this.current.get(this.key(serverId, root));\n\t\treturn status ? { ...status } : undefined;\n\t}\n\n\tlist(): readonly LspStatus[] {\n\t\treturn this.statuses.map((status) => ({ ...status }));\n\t}\n\n\tprivate key(serverId: string, root: string): string {\n\t\treturn `${serverId}\\0${root}`;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class LspStatusStore {
|
|
2
|
+
capacity;
|
|
3
|
+
statuses = [];
|
|
4
|
+
current = new Map();
|
|
5
|
+
_totalCount = 0;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
const capacity = options?.capacity ?? 100;
|
|
8
|
+
if (!Number.isSafeInteger(capacity) || capacity < 1) {
|
|
9
|
+
throw new Error("LSP status capacity must be a positive safe integer.");
|
|
10
|
+
}
|
|
11
|
+
this.capacity = capacity;
|
|
12
|
+
}
|
|
13
|
+
get totalCount() {
|
|
14
|
+
return this._totalCount;
|
|
15
|
+
}
|
|
16
|
+
record(status) {
|
|
17
|
+
this._totalCount += 1;
|
|
18
|
+
const snapshot = { ...status };
|
|
19
|
+
this.statuses.push(snapshot);
|
|
20
|
+
this.current.set(this.key(status.serverId, status.root), snapshot);
|
|
21
|
+
if (this.statuses.length > this.capacity)
|
|
22
|
+
this.statuses.splice(0, this.statuses.length - this.capacity);
|
|
23
|
+
}
|
|
24
|
+
latest(serverId, root) {
|
|
25
|
+
const status = this.current.get(this.key(serverId, root));
|
|
26
|
+
return status ? { ...status } : undefined;
|
|
27
|
+
}
|
|
28
|
+
list() {
|
|
29
|
+
return this.statuses.map((status) => ({ ...status }));
|
|
30
|
+
}
|
|
31
|
+
key(serverId, root) {
|
|
32
|
+
return `${serverId}\0${root}`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/core/lsp/status.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,cAAc;IACT,QAAQ,CAAS;IACjB,QAAQ,GAAgB,EAAE,CAAC;IAC3B,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAChD,WAAW,GAAG,CAAC,CAAC;IAExB,YAAY,OAA+B,EAAE;QAC5C,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,GAAG,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QACzE,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAAA,CACzB;IAED,IAAI,UAAU,GAAW;QACxB,OAAO,IAAI,CAAC,WAAW,CAAC;IAAA,CACxB;IAED,MAAM,CAAC,MAAiB,EAAQ;QAC/B,IAAI,CAAC,WAAW,IAAI,CAAC,CAAC;QACtB,MAAM,QAAQ,GAAG,EAAE,GAAG,MAAM,EAAE,CAAC;QAC/B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;QACnE,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;YAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC;IAAA,CACxG;IAED,MAAM,CAAC,QAAgB,EAAE,IAAY,EAAyB;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,OAAO,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;IAAA,CAC1C;IAED,IAAI,GAAyB;QAC5B,OAAO,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC;IAAA,CACtD;IAEO,GAAG,CAAC,QAAgB,EAAE,IAAY,EAAU;QACnD,OAAO,GAAG,QAAQ,KAAK,IAAI,EAAE,CAAC;IAAA,CAC9B;CACD","sourcesContent":["export type LspServerState = \"resolved\" | \"starting\" | \"ready\" | \"degraded\" | \"disabled\" | \"closing\" | \"closed\";\n\nexport interface LspStatus {\n\treadonly serverId: string;\n\treadonly root: string;\n\treadonly state: LspServerState;\n\treadonly attempt: number;\n\treadonly reason?: string;\n}\n\nexport class LspStatusStore {\n\tprivate readonly capacity: number;\n\tprivate readonly statuses: LspStatus[] = [];\n\tprivate readonly current = new Map<string, LspStatus>();\n\tprivate _totalCount = 0;\n\n\tconstructor(options?: { capacity?: number }) {\n\t\tconst capacity = options?.capacity ?? 100;\n\t\tif (!Number.isSafeInteger(capacity) || capacity < 1) {\n\t\t\tthrow new Error(\"LSP status capacity must be a positive safe integer.\");\n\t\t}\n\t\tthis.capacity = capacity;\n\t}\n\n\tget totalCount(): number {\n\t\treturn this._totalCount;\n\t}\n\n\trecord(status: LspStatus): void {\n\t\tthis._totalCount += 1;\n\t\tconst snapshot = { ...status };\n\t\tthis.statuses.push(snapshot);\n\t\tthis.current.set(this.key(status.serverId, status.root), snapshot);\n\t\tif (this.statuses.length > this.capacity) this.statuses.splice(0, this.statuses.length - this.capacity);\n\t}\n\n\tlatest(serverId: string, root: string): LspStatus | undefined {\n\t\tconst status = this.current.get(this.key(serverId, root));\n\t\treturn status ? { ...status } : undefined;\n\t}\n\n\tlist(): readonly LspStatus[] {\n\t\treturn this.statuses.map((status) => ({ ...status }));\n\t}\n\n\tprivate key(serverId: string, root: string): string {\n\t\treturn `${serverId}\\0${root}`;\n\t}\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LspClient } from "./client.ts";
|
|
2
|
+
export interface SynchronizedDocument {
|
|
3
|
+
readonly uri: string;
|
|
4
|
+
readonly version: number;
|
|
5
|
+
}
|
|
6
|
+
/** Keeps versioned, whole-document disk snapshots open for a pool lifetime. */
|
|
7
|
+
export declare class LspDocumentSync {
|
|
8
|
+
private readonly documents;
|
|
9
|
+
synchronize(client: LspClient, path: string, languageId: string): SynchronizedDocument | undefined;
|
|
10
|
+
dispose(): void;
|
|
11
|
+
private notifySaved;
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/sync.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAuB,MAAM,aAAa,CAAC;AAElE,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AAeD,+EAA+E;AAC/E,qBAAa,eAAe;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAE7D,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,oBAAoB,GAAG,SAAS,CAqCjG;IAED,OAAO,IAAI,IAAI,CAUd;IAED,OAAO,CAAC,WAAW;CAOnB","sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { pathToFileURL } from \"node:url\";\nimport type { LspClient, LspTextDocumentSync } from \"./client.ts\";\n\nexport interface SynchronizedDocument {\n\treadonly uri: string;\n\treadonly version: number;\n}\n\ninterface OpenDocument {\n\treadonly client: LspClient;\n\treadonly languageId: string;\n\treadonly uri: string;\n\tversion: number;\n\ttext: string;\n}\n\nfunction positionAtEnd(text: string): { line: number; character: number } {\n\tconst lines = text.split(\"\\n\");\n\treturn { line: lines.length - 1, character: lines[lines.length - 1]?.length ?? 0 };\n}\n\n/** Keeps versioned, whole-document disk snapshots open for a pool lifetime. */\nexport class LspDocumentSync {\n\tprivate readonly documents = new Map<string, OpenDocument>();\n\n\tsynchronize(client: LspClient, path: string, languageId: string): SynchronizedDocument | undefined {\n\t\tconst capabilities = client.textDocumentSync;\n\t\tif (capabilities.kind === \"none\") return undefined;\n\t\tconst uri = pathToFileURL(path).href;\n\t\tconst text = readFileSync(path, \"utf8\");\n\t\tconst current = this.documents.get(uri);\n\t\tif (!current) {\n\t\t\tconst document: OpenDocument = { client, languageId, text, uri, version: 1 };\n\t\t\tthis.documents.set(uri, document);\n\t\t\tclient.notify(\"textDocument/didOpen\", {\n\t\t\t\ttextDocument: { uri, languageId, version: document.version, text },\n\t\t\t});\n\t\t\tthis.notifySaved(document, capabilities);\n\t\t\treturn document;\n\t\t}\n\t\tif (current.client !== client || current.languageId !== languageId) {\n\t\t\tthrow new Error(`LSP document ${uri} changed server or language while open.`);\n\t\t}\n\t\tif (!Number.isSafeInteger(current.version + 1)) throw new Error(`LSP document version exhausted for ${uri}.`);\n\t\tconst nextVersion = current.version + 1;\n\t\tconst contentChanges =\n\t\t\tcapabilities.kind === \"full\"\n\t\t\t\t? [{ text }]\n\t\t\t\t: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\trange: { start: { line: 0, character: 0 }, end: positionAtEnd(current.text) },\n\t\t\t\t\t\t\ttext,\n\t\t\t\t\t\t},\n\t\t\t\t\t];\n\t\tcurrent.version = nextVersion;\n\t\tcurrent.text = text;\n\t\tclient.notify(\"textDocument/didChange\", {\n\t\t\ttextDocument: { uri, version: nextVersion },\n\t\t\tcontentChanges,\n\t\t});\n\t\tthis.notifySaved(current, capabilities);\n\t\treturn current;\n\t}\n\n\tdispose(): void {\n\t\tfor (const document of this.documents.values()) {\n\t\t\tif (!document.client.textDocumentSync.openClose) continue;\n\t\t\ttry {\n\t\t\t\tdocument.client.notify(\"textDocument/didClose\", { textDocument: { uri: document.uri } });\n\t\t\t} catch {\n\t\t\t\t// A failed server cannot accept teardown notifications.\n\t\t\t}\n\t\t}\n\t\tthis.documents.clear();\n\t}\n\n\tprivate notifySaved(document: OpenDocument, capabilities: LspTextDocumentSync): void {\n\t\tif (!capabilities.save) return;\n\t\tdocument.client.notify(\"textDocument/didSave\", {\n\t\t\ttextDocument: { uri: document.uri },\n\t\t\t...(capabilities.saveIncludeText ? { text: document.text } : {}),\n\t\t});\n\t}\n}\n"]}
|