march-cli 0.1.14 → 0.1.15
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/package.json +1 -1
- package/src/agent/command-exec-tool.mjs +53 -10
- package/src/agent/runner.mjs +6 -7
- package/src/agent/runtime/remote-runner-client.mjs +1 -1
- package/src/agent/runtime/runner-ipc-target.mjs +2 -3
- package/src/agent/tools.mjs +2 -2
- package/src/cli/session/pi-session-switch-command.mjs +2 -2
- package/src/lsp/client.mjs +59 -6
- package/src/lsp/managed-node-server.mjs +5 -0
- package/src/lsp/server-definitions.mjs +188 -0
- package/src/lsp/servers.mjs +11 -134
package/package.json
CHANGED
|
@@ -6,6 +6,8 @@ import { toolText } from "./tool-result.mjs";
|
|
|
6
6
|
import { stripAnsi } from "../text/ansi.mjs";
|
|
7
7
|
|
|
8
8
|
const OUTPUT_LIMIT = 64 * 1024;
|
|
9
|
+
const DEFAULT_COMMAND_TIMEOUT_SECONDS = 60;
|
|
10
|
+
const COMMAND_KILL_GRACE_MS = 1000;
|
|
9
11
|
|
|
10
12
|
export function createCommandExecTool({ cwd }) {
|
|
11
13
|
return defineTool({
|
|
@@ -15,13 +17,13 @@ export function createCommandExecTool({ cwd }) {
|
|
|
15
17
|
parameters: Type.Object({
|
|
16
18
|
command: Type.String({ description: "Command to execute" }),
|
|
17
19
|
shell: Type.Optional(Type.String({ description: "auto (default), bash, or powershell" })),
|
|
18
|
-
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds; default 60" })),
|
|
20
|
+
timeout: Type.Optional(Type.Number({ description: "Timeout in seconds; default 60", default: DEFAULT_COMMAND_TIMEOUT_SECONDS })),
|
|
19
21
|
}),
|
|
20
|
-
execute: async (_toolCallId, params) => executeCommand({ cwd, ...params }),
|
|
22
|
+
execute: async (_toolCallId, params, signal) => executeCommand({ cwd, signal, ...params }),
|
|
21
23
|
});
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
export async function executeCommand({ cwd, command, shell = "auto", timeout =
|
|
26
|
+
export async function executeCommand({ cwd, command, shell = "auto", timeout = DEFAULT_COMMAND_TIMEOUT_SECONDS, signal, spawnImpl = spawn, killProcessTreeImpl = killProcessTree, forceSettleMs = COMMAND_KILL_GRACE_MS }) {
|
|
25
27
|
let resolved;
|
|
26
28
|
try {
|
|
27
29
|
resolved = resolveCommandShell(shell);
|
|
@@ -29,14 +31,18 @@ export async function executeCommand({ cwd, command, shell = "auto", timeout = 6
|
|
|
29
31
|
return toolText(`Error: ${err.message}`, { error: true });
|
|
30
32
|
}
|
|
31
33
|
|
|
32
|
-
const
|
|
34
|
+
const timeoutSeconds = Math.max(0.001, Number(timeout) || DEFAULT_COMMAND_TIMEOUT_SECONDS);
|
|
35
|
+
const timeoutMs = timeoutSeconds * 1000;
|
|
33
36
|
const result = await spawnCommand(spawnImpl, resolved.bin, [...resolved.args, String(command ?? "")], {
|
|
34
37
|
cwd,
|
|
35
38
|
timeoutMs,
|
|
39
|
+
signal,
|
|
36
40
|
windowsHide: true,
|
|
41
|
+
killProcessTreeImpl,
|
|
42
|
+
forceSettleMs,
|
|
37
43
|
});
|
|
38
44
|
if (result.error) {
|
|
39
|
-
const detail = result.timedOut ? ` (timed out after ${
|
|
45
|
+
const detail = result.timedOut ? ` (timed out after ${timeoutSeconds}s)` : "";
|
|
40
46
|
return toolText(`Error: ${result.error.message}${detail}`, { error: true });
|
|
41
47
|
}
|
|
42
48
|
const stdout = stripAnsi(result.stdout ?? "");
|
|
@@ -57,12 +63,23 @@ function spawnCommand(spawnImpl, bin, args, options) {
|
|
|
57
63
|
let stderr = "";
|
|
58
64
|
let settled = false;
|
|
59
65
|
let timedOut = false;
|
|
60
|
-
|
|
66
|
+
let aborted = false;
|
|
67
|
+
let forceTimer = null;
|
|
68
|
+
const child = spawnImpl(bin, args, {
|
|
69
|
+
cwd: options.cwd,
|
|
70
|
+
windowsHide: options.windowsHide,
|
|
71
|
+
detached: process.platform !== "win32",
|
|
72
|
+
});
|
|
61
73
|
const timer = setTimeout(() => {
|
|
62
74
|
timedOut = true;
|
|
63
|
-
|
|
75
|
+
terminateChild(new Error("Command timed out"));
|
|
64
76
|
}, options.timeoutMs);
|
|
65
|
-
|
|
77
|
+
const onAbort = () => {
|
|
78
|
+
aborted = true;
|
|
79
|
+
terminateChild(new Error("Command aborted"));
|
|
80
|
+
};
|
|
81
|
+
if (options.signal?.aborted) queueMicrotask(onAbort);
|
|
82
|
+
else options.signal?.addEventListener?.("abort", onAbort, { once: true });
|
|
66
83
|
|
|
67
84
|
child.stdout?.setEncoding?.("utf8");
|
|
68
85
|
child.stderr?.setEncoding?.("utf8");
|
|
@@ -71,16 +88,42 @@ function spawnCommand(spawnImpl, bin, args, options) {
|
|
|
71
88
|
child.once?.("error", (error) => finish({ error }));
|
|
72
89
|
child.once?.("close", (status, signal) => finish({ status, signal }));
|
|
73
90
|
|
|
91
|
+
function terminateChild(error) {
|
|
92
|
+
if (settled) return;
|
|
93
|
+
options.killProcessTreeImpl?.(child);
|
|
94
|
+
forceTimer ??= setTimeout(() => finish({ error }), options.forceSettleMs);
|
|
95
|
+
}
|
|
96
|
+
|
|
74
97
|
function finish(result) {
|
|
75
98
|
if (settled) return;
|
|
76
99
|
settled = true;
|
|
77
100
|
clearTimeout(timer);
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
clearTimeout(forceTimer);
|
|
102
|
+
options.signal?.removeEventListener?.("abort", onAbort);
|
|
103
|
+
const error = result.error ?? (timedOut ? Object.assign(new Error("Command timed out"), { code: "ETIMEDOUT" }) : null) ?? (aborted ? Object.assign(new Error("Command aborted"), { code: "ABORT_ERR" }) : null);
|
|
104
|
+
resolve({ ...result, error, stdout, stderr, timedOut, aborted });
|
|
80
105
|
}
|
|
81
106
|
});
|
|
82
107
|
}
|
|
83
108
|
|
|
109
|
+
function killProcessTree(child, platform = process.platform) {
|
|
110
|
+
if (!child?.pid) {
|
|
111
|
+
child?.kill?.("SIGTERM");
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (platform === "win32") {
|
|
115
|
+
try {
|
|
116
|
+
spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], { windowsHide: true, stdio: "ignore", timeout: 5000 });
|
|
117
|
+
return;
|
|
118
|
+
} catch {}
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
process.kill(-child.pid, "SIGTERM");
|
|
122
|
+
return;
|
|
123
|
+
} catch {}
|
|
124
|
+
child.kill?.("SIGTERM");
|
|
125
|
+
}
|
|
126
|
+
|
|
84
127
|
function appendLimited(current, chunk) {
|
|
85
128
|
const next = current + String(chunk ?? "");
|
|
86
129
|
return next.length <= OUTPUT_LIMIT ? next : next.slice(-OUTPUT_LIMIT);
|
package/src/agent/runner.mjs
CHANGED
|
@@ -52,14 +52,11 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
52
52
|
const engine = new ContextEngine({ cwd, modelId, provider, namespace, memoryRoot, profilePaths, shellRuntime, lspService, injections: mcpInjections, maxTurns, trimBatch });
|
|
53
53
|
const resolvedSessionManager = resolveRunnerSessionManager(cwd, sessionManager);
|
|
54
54
|
const sessionBinding = createSessionBinding(null);
|
|
55
|
-
let currentModelCallKind = "model", currentTurnId = null;
|
|
56
|
-
let currentPromptForContext = "";
|
|
55
|
+
let currentModelCallKind = "model", currentTurnId = null, currentPromptForContext = "";
|
|
57
56
|
let currentTurnContextMode = "rebuild";
|
|
58
57
|
let nextTurnContextMode = "rebuild";
|
|
59
58
|
let pendingMidTurnRecallHints = [];
|
|
60
|
-
let lastNotificationResult = null;
|
|
61
|
-
let runtimeHost = null;
|
|
62
|
-
let lifecycleAdapter = null;
|
|
59
|
+
let lastNotificationResult = null, runtimeHost = null, lifecycleAdapter = null;
|
|
63
60
|
let _currentFastEntry = null;
|
|
64
61
|
if (useRuntimeHost) {
|
|
65
62
|
runtimeHost = await createRunnerRuntimeHost({
|
|
@@ -227,10 +224,12 @@ export async function createRunner({ cwd, modelId = null, provider = null, provi
|
|
|
227
224
|
getExtensionDiagnostics() { return runtimeHost?.getDiagnostics?.() ?? []; },
|
|
228
225
|
getExtensionLifecycleState() { return lifecycleAdapter.getState(); },
|
|
229
226
|
getLspStatus() { return lspService.snapshot(); },
|
|
230
|
-
async switchPiSession(sessionPath) {
|
|
227
|
+
async switchPiSession(sessionPath, restoreState = null) {
|
|
231
228
|
if (!runtimeHost) throw new Error("pi runtime host is not enabled");
|
|
232
229
|
nextTurnContextMode = "rebuild";
|
|
233
|
-
|
|
230
|
+
const result = await runtimeHost.switchSession(sessionPath);
|
|
231
|
+
if (!result?.cancelled && restoreState) engine.restoreSession(restoreState, null, { replace: true });
|
|
232
|
+
return result;
|
|
234
233
|
},
|
|
235
234
|
cycleThinkingLevel() {
|
|
236
235
|
const level = sessionBinding.get().cycleThinkingLevel();
|
|
@@ -30,7 +30,7 @@ export function createRemoteRunnerClient(peer, { initialState = null } = {}) {
|
|
|
30
30
|
getExtensionDiagnostics: () => state?.extensionDiagnostics ?? [],
|
|
31
31
|
getExtensionLifecycleState: () => state?.extensionLifecycleState ?? null,
|
|
32
32
|
getLspStatus: () => state?.lspStatus ?? null,
|
|
33
|
-
async switchPiSession(sessionPath) { return applyResultWithState(await peer.call("switchPiSession", sessionPath)); },
|
|
33
|
+
async switchPiSession(sessionPath, restoreState = null) { return applyResultWithState(await peer.call("switchPiSession", sessionPath, restoreState)); },
|
|
34
34
|
async cycleThinkingLevel() { return applyResultWithState(await peer.call("cycleThinkingLevel")); },
|
|
35
35
|
getThinkingLevel: () => state?.engine?.thinkingLevel ?? null,
|
|
36
36
|
async setThinkingLevel(level) { return applyResultWithState(await peer.call("setThinkingLevel", level)); },
|
|
@@ -67,8 +67,8 @@ export function createRunnerIpcTarget({ createRunnerImpl, runnerOptions = {} } =
|
|
|
67
67
|
getLspStatus() {
|
|
68
68
|
return getRunner().getLspStatus();
|
|
69
69
|
},
|
|
70
|
-
async switchPiSession(sessionPath) {
|
|
71
|
-
const result = await getRunner().switchPiSession(sessionPath);
|
|
70
|
+
async switchPiSession(sessionPath, restoreState = null) {
|
|
71
|
+
const result = await getRunner().switchPiSession(sessionPath, restoreState);
|
|
72
72
|
return { result, state: getRunnerState(runner) };
|
|
73
73
|
},
|
|
74
74
|
cycleThinkingLevel() {
|
|
@@ -123,4 +123,3 @@ export function getRunnerState(runner) {
|
|
|
123
123
|
extensionLifecycleState: runner.getExtensionLifecycleState?.() ?? null,
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
|
-
|
package/src/agent/tools.mjs
CHANGED
|
@@ -40,7 +40,7 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shel
|
|
|
40
40
|
return tools.map((tool) => {
|
|
41
41
|
const execute = tool.execute;
|
|
42
42
|
if (!execute) return tool;
|
|
43
|
-
const wrapped = async (toolCallId, params) => {
|
|
43
|
+
const wrapped = async (toolCallId, params, signal, onUpdate) => {
|
|
44
44
|
const decision = await permissionController.requestApproval(
|
|
45
45
|
tool.name,
|
|
46
46
|
params,
|
|
@@ -51,7 +51,7 @@ export function createMarchCustomTools({ cwd, engine, ui, memoryTools = [], shel
|
|
|
51
51
|
if (decision.behavior === "deny") {
|
|
52
52
|
return toolText(`Permission denied: ${decision.message}`, { error: true, permissionDenied: true });
|
|
53
53
|
}
|
|
54
|
-
return execute(toolCallId, params);
|
|
54
|
+
return execute(toolCallId, params, signal, onUpdate);
|
|
55
55
|
};
|
|
56
56
|
return { ...tool, execute: wrapped };
|
|
57
57
|
});
|
|
@@ -26,13 +26,13 @@ export async function resumePiSessionById(id, { runner, sessions, projectMarchDi
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
let result;
|
|
29
|
+
const restoreState = toContextSessionState(sidecar.state);
|
|
29
30
|
try {
|
|
30
|
-
result = await runner.switchPiSession(session.path);
|
|
31
|
+
result = await runner.switchPiSession(session.path, restoreState);
|
|
31
32
|
} catch (err) {
|
|
32
33
|
return [`Error: failed to switch pi session ${session.id}: ${err.message}`];
|
|
33
34
|
}
|
|
34
35
|
if (result?.cancelled) return [`Resume pi session cancelled: ${session.id}`];
|
|
35
|
-
runner.engine.restoreSession(toContextSessionState(sidecar.state), null, { replace: true });
|
|
36
36
|
return [`Resumed pi session: ${session.id}`];
|
|
37
37
|
}
|
|
38
38
|
|
package/src/lsp/client.mjs
CHANGED
|
@@ -1,23 +1,76 @@
|
|
|
1
1
|
import { pathToFileURL } from "node:url";
|
|
2
2
|
import { spawnCommand } from "../platform/spawn-command.mjs";
|
|
3
|
-
import { extname } from "node:path";
|
|
3
|
+
import { basename, extname } from "node:path";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
|
|
6
6
|
const INITIALIZE_TIMEOUT_MS = 15000;
|
|
7
7
|
const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2;
|
|
8
8
|
|
|
9
9
|
const LANGUAGE_IDS = {
|
|
10
|
+
".astro": "astro",
|
|
11
|
+
".bash": "shellscript",
|
|
12
|
+
".c": "c",
|
|
13
|
+
".c++": "cpp",
|
|
14
|
+
".cc": "cpp",
|
|
15
|
+
".cjs": "javascript",
|
|
16
|
+
".cpp": "cpp",
|
|
17
|
+
".css": "css",
|
|
18
|
+
".cts": "typescript",
|
|
19
|
+
".cxx": "cpp",
|
|
20
|
+
".dart": "dart",
|
|
21
|
+
".dockerfile": "dockerfile",
|
|
22
|
+
".go": "go",
|
|
23
|
+
".h": "c",
|
|
24
|
+
".h++": "cpp",
|
|
25
|
+
".hh": "cpp",
|
|
26
|
+
".hpp": "cpp",
|
|
27
|
+
".htm": "html",
|
|
28
|
+
".html": "html",
|
|
29
|
+
".hxx": "cpp",
|
|
10
30
|
".js": "javascript",
|
|
31
|
+
".json": "json",
|
|
32
|
+
".jsonc": "jsonc",
|
|
11
33
|
".jsx": "javascriptreact",
|
|
12
|
-
".
|
|
13
|
-
".
|
|
34
|
+
".ksh": "shellscript",
|
|
35
|
+
".less": "less",
|
|
36
|
+
".lua": "lua",
|
|
37
|
+
".markdown": "markdown",
|
|
38
|
+
".md": "markdown",
|
|
39
|
+
".mdx": "mdx",
|
|
14
40
|
".mjs": "javascript",
|
|
15
|
-
".cjs": "javascript",
|
|
16
41
|
".mts": "typescript",
|
|
17
|
-
".
|
|
42
|
+
".php": "php",
|
|
43
|
+
".prisma": "prisma",
|
|
44
|
+
".py": "python",
|
|
45
|
+
".pyi": "python",
|
|
46
|
+
".rs": "rust",
|
|
47
|
+
".sass": "sass",
|
|
48
|
+
".scss": "scss",
|
|
49
|
+
".sh": "shellscript",
|
|
50
|
+
".svelte": "svelte",
|
|
51
|
+
".tf": "terraform",
|
|
52
|
+
".tfvars": "terraform-vars",
|
|
53
|
+
".toml": "toml",
|
|
54
|
+
".ts": "typescript",
|
|
55
|
+
".tsx": "typescriptreact",
|
|
18
56
|
".vue": "vue",
|
|
57
|
+
".yaml": "yaml",
|
|
58
|
+
".yml": "yaml",
|
|
59
|
+
".zig": "zig",
|
|
60
|
+
".zon": "zig",
|
|
61
|
+
".zsh": "shellscript",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const FILENAME_LANGUAGE_IDS = {
|
|
65
|
+
containerfile: "dockerfile",
|
|
66
|
+
dockerfile: "dockerfile",
|
|
19
67
|
};
|
|
20
68
|
|
|
69
|
+
export function languageIdForPath(path) {
|
|
70
|
+
const name = basename(path).toLowerCase();
|
|
71
|
+
return FILENAME_LANGUAGE_IDS[name] ?? LANGUAGE_IDS[extname(path).toLowerCase()] ?? "plaintext";
|
|
72
|
+
}
|
|
73
|
+
|
|
21
74
|
export class LspClient {
|
|
22
75
|
constructor({ serverId, command, args = [], cwd, initialization = {}, store }) {
|
|
23
76
|
this.serverId = serverId;
|
|
@@ -94,7 +147,7 @@ export class LspClient {
|
|
|
94
147
|
this.#notify("textDocument/didOpen", {
|
|
95
148
|
textDocument: {
|
|
96
149
|
uri,
|
|
97
|
-
languageId:
|
|
150
|
+
languageId: languageIdForPath(path),
|
|
98
151
|
version: 0,
|
|
99
152
|
text,
|
|
100
153
|
},
|
|
@@ -8,6 +8,11 @@ const DEFAULT_CACHE_ROOT = join(homedir(), ".march", "lsp", "node");
|
|
|
8
8
|
const MANAGED_PACKAGES = {
|
|
9
9
|
"typescript-language-server": ["typescript-language-server", "typescript"],
|
|
10
10
|
"vue-language-server": ["@vue/language-server", "typescript"],
|
|
11
|
+
"pyright-langserver": ["pyright"],
|
|
12
|
+
"vscode-json-language-server": ["vscode-langservers-extracted"],
|
|
13
|
+
"vscode-html-language-server": ["vscode-langservers-extracted"],
|
|
14
|
+
"vscode-css-language-server": ["vscode-langservers-extracted"],
|
|
15
|
+
"docker-langserver": ["dockerfile-language-server-nodejs"],
|
|
11
16
|
};
|
|
12
17
|
|
|
13
18
|
const installs = new Map();
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const NODE_ROOT_MARKERS = ["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock", "package.json"];
|
|
2
|
+
|
|
3
|
+
export function createLspServerDefinitions({ resolveTypeScriptProjectRoot, resolveTypeScriptSdk, resolveTypeScriptServer }) {
|
|
4
|
+
return [
|
|
5
|
+
{
|
|
6
|
+
id: "vue",
|
|
7
|
+
extensions: [".vue"],
|
|
8
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
9
|
+
command: ["vue-language-server"],
|
|
10
|
+
managedCommand: "vue-language-server",
|
|
11
|
+
args: ["--stdio"],
|
|
12
|
+
initialization: ({ root, workspaceRoot }) => {
|
|
13
|
+
const tsdk = resolveTypeScriptSdk({ root, workspaceRoot });
|
|
14
|
+
return tsdk ? { typescript: { tsdk } } : null;
|
|
15
|
+
},
|
|
16
|
+
managedTypeScript: true,
|
|
17
|
+
missingInitialization: "missing project typescript SDK",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "typescript",
|
|
21
|
+
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
|
22
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
23
|
+
projectRoot: resolveTypeScriptProjectRoot,
|
|
24
|
+
command: ["typescript-language-server"],
|
|
25
|
+
managedCommand: "typescript-language-server",
|
|
26
|
+
args: ["--stdio"],
|
|
27
|
+
initialization: ({ root, workspaceRoot }) => {
|
|
28
|
+
const tsserver = resolveTypeScriptServer({ root, workspaceRoot });
|
|
29
|
+
return tsserver ? { tsserver: { path: tsserver } } : null;
|
|
30
|
+
},
|
|
31
|
+
managedTypeScript: true,
|
|
32
|
+
missingInitialization: "missing project typescript/tsserver.js",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "python",
|
|
36
|
+
extensions: [".py", ".pyi"],
|
|
37
|
+
rootMarkers: ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"],
|
|
38
|
+
command: ["pyright-langserver"],
|
|
39
|
+
managedCommand: "pyright-langserver",
|
|
40
|
+
args: ["--stdio"],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "go",
|
|
44
|
+
extensions: [".go"],
|
|
45
|
+
rootMarkers: ["go.work", "go.mod", "go.sum"],
|
|
46
|
+
command: ["gopls"],
|
|
47
|
+
args: [],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "rust",
|
|
51
|
+
extensions: [".rs"],
|
|
52
|
+
rootMarkers: ["Cargo.toml", "Cargo.lock"],
|
|
53
|
+
command: ["rust-analyzer"],
|
|
54
|
+
args: [],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "clangd",
|
|
58
|
+
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
|
59
|
+
rootMarkers: ["compile_commands.json", "compile_flags.txt", ".clangd"],
|
|
60
|
+
command: ["clangd"],
|
|
61
|
+
args: ["--background-index", "--clang-tidy"],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "svelte",
|
|
65
|
+
extensions: [".svelte"],
|
|
66
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
67
|
+
command: ["svelteserver", "svelte-language-server"],
|
|
68
|
+
args: ["--stdio"],
|
|
69
|
+
initialization: () => ({}),
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "astro",
|
|
73
|
+
extensions: [".astro"],
|
|
74
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
75
|
+
command: ["astro-ls", "@astrojs/language-server"],
|
|
76
|
+
args: ["--stdio"],
|
|
77
|
+
initialization: ({ root, workspaceRoot }) => {
|
|
78
|
+
const tsdk = resolveTypeScriptSdk({ root, workspaceRoot });
|
|
79
|
+
return tsdk ? { typescript: { tsdk } } : null;
|
|
80
|
+
},
|
|
81
|
+
missingInitialization: "missing project typescript SDK",
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
id: "json",
|
|
85
|
+
extensions: [".json", ".jsonc"],
|
|
86
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
87
|
+
command: ["vscode-json-language-server"],
|
|
88
|
+
managedCommand: "vscode-json-language-server",
|
|
89
|
+
args: ["--stdio"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "html",
|
|
93
|
+
extensions: [".html", ".htm"],
|
|
94
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
95
|
+
command: ["vscode-html-language-server"],
|
|
96
|
+
managedCommand: "vscode-html-language-server",
|
|
97
|
+
args: ["--stdio"],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "css",
|
|
101
|
+
extensions: [".css", ".scss", ".sass", ".less"],
|
|
102
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
103
|
+
command: ["vscode-css-language-server"],
|
|
104
|
+
managedCommand: "vscode-css-language-server",
|
|
105
|
+
args: ["--stdio"],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "yaml",
|
|
109
|
+
extensions: [".yaml", ".yml"],
|
|
110
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
111
|
+
command: ["yaml-language-server"],
|
|
112
|
+
args: ["--stdio"],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
id: "bash",
|
|
116
|
+
extensions: [".sh", ".bash", ".zsh", ".ksh"],
|
|
117
|
+
rootMarkers: [],
|
|
118
|
+
command: ["bash-language-server"],
|
|
119
|
+
args: ["start"],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: "lua",
|
|
123
|
+
extensions: [".lua"],
|
|
124
|
+
rootMarkers: [".luarc.json", ".luarc.jsonc", ".luacheckrc", ".stylua.toml", "stylua.toml", "selene.toml", "selene.yml"],
|
|
125
|
+
command: ["lua-language-server"],
|
|
126
|
+
args: [],
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: "zig",
|
|
130
|
+
extensions: [".zig", ".zon"],
|
|
131
|
+
rootMarkers: ["build.zig"],
|
|
132
|
+
command: ["zls"],
|
|
133
|
+
args: [],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "dart",
|
|
137
|
+
extensions: [".dart"],
|
|
138
|
+
rootMarkers: ["pubspec.yaml", "analysis_options.yaml"],
|
|
139
|
+
command: ["dart"],
|
|
140
|
+
args: ["language-server", "--lsp"],
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: "php",
|
|
144
|
+
extensions: [".php"],
|
|
145
|
+
rootMarkers: ["composer.json", "composer.lock", ".php-version"],
|
|
146
|
+
command: ["intelephense"],
|
|
147
|
+
args: ["--stdio"],
|
|
148
|
+
initialization: () => ({ telemetry: { enabled: false } }),
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: "dockerfile",
|
|
152
|
+
extensions: [".dockerfile"],
|
|
153
|
+
filenames: ["dockerfile", "containerfile"],
|
|
154
|
+
rootMarkers: ["Dockerfile", "docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"],
|
|
155
|
+
command: ["docker-langserver"],
|
|
156
|
+
managedCommand: "docker-langserver",
|
|
157
|
+
args: ["--stdio"],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "markdown",
|
|
161
|
+
extensions: [".md", ".mdx", ".markdown"],
|
|
162
|
+
rootMarkers: NODE_ROOT_MARKERS,
|
|
163
|
+
command: ["marksman"],
|
|
164
|
+
args: ["server"],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: "toml",
|
|
168
|
+
extensions: [".toml"],
|
|
169
|
+
rootMarkers: ["Cargo.toml", "pyproject.toml", "taplo.toml", ".taplo.toml"],
|
|
170
|
+
command: ["taplo"],
|
|
171
|
+
args: ["lsp", "stdio"],
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
id: "terraform",
|
|
175
|
+
extensions: [".tf", ".tfvars"],
|
|
176
|
+
rootMarkers: [".terraform", ".terraform.lock.hcl", "terraform.tfstate"],
|
|
177
|
+
command: ["terraform-ls"],
|
|
178
|
+
args: ["serve"],
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "prisma",
|
|
182
|
+
extensions: [".prisma"],
|
|
183
|
+
rootMarkers: ["schema.prisma", "prisma/schema.prisma", "prisma"],
|
|
184
|
+
command: ["prisma"],
|
|
185
|
+
args: ["language-server"],
|
|
186
|
+
},
|
|
187
|
+
];
|
|
188
|
+
}
|
package/src/lsp/servers.mjs
CHANGED
|
@@ -1,140 +1,11 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { ensureManagedNodeCommand, ensureManagedTypeScript, findManagedTypeScriptSdk, findManagedTypeScriptServer } from "./managed-node-server.mjs";
|
|
5
|
+
import { createLspServerDefinitions } from "./server-definitions.mjs";
|
|
5
6
|
import { resolveTypeScriptProjectRoot } from "./typescript-project-resolver.mjs";
|
|
6
7
|
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const LSP_SERVERS = [
|
|
10
|
-
{
|
|
11
|
-
id: "vue",
|
|
12
|
-
extensions: [".vue"],
|
|
13
|
-
rootMarkers: NODE_ROOT_MARKERS,
|
|
14
|
-
command: ["vue-language-server"],
|
|
15
|
-
managedCommand: "vue-language-server",
|
|
16
|
-
args: ["--stdio"],
|
|
17
|
-
initialization: ({ root, workspaceRoot }) => {
|
|
18
|
-
const tsdk = resolveTypeScriptSdk({ root, workspaceRoot });
|
|
19
|
-
return tsdk ? { typescript: { tsdk } } : null;
|
|
20
|
-
},
|
|
21
|
-
managedTypeScript: true,
|
|
22
|
-
missingInitialization: "missing project typescript SDK",
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
id: "typescript",
|
|
26
|
-
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
|
|
27
|
-
rootMarkers: NODE_ROOT_MARKERS,
|
|
28
|
-
projectRoot: resolveTypeScriptProjectRoot,
|
|
29
|
-
command: ["typescript-language-server"],
|
|
30
|
-
managedCommand: "typescript-language-server",
|
|
31
|
-
args: ["--stdio"],
|
|
32
|
-
initialization: ({ root, workspaceRoot }) => {
|
|
33
|
-
const tsserver = resolveTypeScriptServer({ root, workspaceRoot });
|
|
34
|
-
return tsserver ? { tsserver: { path: tsserver } } : null;
|
|
35
|
-
},
|
|
36
|
-
managedTypeScript: true,
|
|
37
|
-
missingInitialization: "missing project typescript/tsserver.js",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: "python",
|
|
41
|
-
extensions: [".py", ".pyi"],
|
|
42
|
-
rootMarkers: ["pyproject.toml", "setup.py", "setup.cfg", "requirements.txt", "Pipfile", "pyrightconfig.json"],
|
|
43
|
-
command: ["pyright-langserver"],
|
|
44
|
-
args: ["--stdio"],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: "go",
|
|
48
|
-
extensions: [".go"],
|
|
49
|
-
rootMarkers: ["go.work", "go.mod", "go.sum"],
|
|
50
|
-
command: ["gopls"],
|
|
51
|
-
args: [],
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
id: "rust",
|
|
55
|
-
extensions: [".rs"],
|
|
56
|
-
rootMarkers: ["Cargo.toml", "Cargo.lock"],
|
|
57
|
-
command: ["rust-analyzer"],
|
|
58
|
-
args: [],
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: "clangd",
|
|
62
|
-
extensions: [".c", ".cpp", ".cc", ".cxx", ".c++", ".h", ".hpp", ".hh", ".hxx", ".h++"],
|
|
63
|
-
rootMarkers: ["compile_commands.json", "compile_flags.txt", ".clangd"],
|
|
64
|
-
command: ["clangd"],
|
|
65
|
-
args: ["--background-index", "--clang-tidy"],
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: "svelte",
|
|
69
|
-
extensions: [".svelte"],
|
|
70
|
-
rootMarkers: NODE_ROOT_MARKERS,
|
|
71
|
-
command: ["svelteserver", "svelte-language-server"],
|
|
72
|
-
args: ["--stdio"],
|
|
73
|
-
initialization: () => ({}),
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
id: "astro",
|
|
77
|
-
extensions: [".astro"],
|
|
78
|
-
rootMarkers: NODE_ROOT_MARKERS,
|
|
79
|
-
command: ["astro-ls", "@astrojs/language-server"],
|
|
80
|
-
args: ["--stdio"],
|
|
81
|
-
initialization: ({ root, workspaceRoot }) => {
|
|
82
|
-
const tsdk = resolveTypeScriptSdk({ root, workspaceRoot });
|
|
83
|
-
return tsdk ? { typescript: { tsdk } } : null;
|
|
84
|
-
},
|
|
85
|
-
missingInitialization: "missing project typescript SDK",
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
id: "yaml",
|
|
89
|
-
extensions: [".yaml", ".yml"],
|
|
90
|
-
rootMarkers: NODE_ROOT_MARKERS,
|
|
91
|
-
command: ["yaml-language-server"],
|
|
92
|
-
args: ["--stdio"],
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
id: "bash",
|
|
96
|
-
extensions: [".sh", ".bash", ".zsh", ".ksh"],
|
|
97
|
-
rootMarkers: [],
|
|
98
|
-
command: ["bash-language-server"],
|
|
99
|
-
args: ["start"],
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: "lua",
|
|
103
|
-
extensions: [".lua"],
|
|
104
|
-
rootMarkers: [".luarc.json", ".luarc.jsonc", ".luacheckrc", ".stylua.toml", "stylua.toml", "selene.toml", "selene.yml"],
|
|
105
|
-
command: ["lua-language-server"],
|
|
106
|
-
args: [],
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
id: "zig",
|
|
110
|
-
extensions: [".zig", ".zon"],
|
|
111
|
-
rootMarkers: ["build.zig"],
|
|
112
|
-
command: ["zls"],
|
|
113
|
-
args: [],
|
|
114
|
-
},
|
|
115
|
-
{
|
|
116
|
-
id: "dart",
|
|
117
|
-
extensions: [".dart"],
|
|
118
|
-
rootMarkers: ["pubspec.yaml", "analysis_options.yaml"],
|
|
119
|
-
command: ["dart"],
|
|
120
|
-
args: ["language-server", "--lsp"],
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
id: "php",
|
|
124
|
-
extensions: [".php"],
|
|
125
|
-
rootMarkers: ["composer.json", "composer.lock", ".php-version"],
|
|
126
|
-
command: ["intelephense"],
|
|
127
|
-
args: ["--stdio"],
|
|
128
|
-
initialization: () => ({ telemetry: { enabled: false } }),
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "prisma",
|
|
132
|
-
extensions: [".prisma"],
|
|
133
|
-
rootMarkers: ["schema.prisma", "prisma/schema.prisma", "prisma"],
|
|
134
|
-
command: ["prisma"],
|
|
135
|
-
args: ["language-server"],
|
|
136
|
-
},
|
|
137
|
-
];
|
|
8
|
+
const LSP_SERVERS = createLspServerDefinitions({ resolveTypeScriptProjectRoot, resolveTypeScriptSdk, resolveTypeScriptServer });
|
|
138
9
|
|
|
139
10
|
export async function resolveLspServer({ filePath, workspaceRoot, onEvent = null } = {}) {
|
|
140
11
|
const result = await resolveLspServerStatus({ filePath, workspaceRoot, onEvent });
|
|
@@ -143,7 +14,7 @@ export async function resolveLspServer({ filePath, workspaceRoot, onEvent = null
|
|
|
143
14
|
|
|
144
15
|
export async function resolveLspServerStatus({ filePath, workspaceRoot, onEvent = null } = {}) {
|
|
145
16
|
const ext = extensionOf(filePath);
|
|
146
|
-
const def = LSP_SERVERS.find((server) => server
|
|
17
|
+
const def = LSP_SERVERS.find((server) => matchesServer(server, filePath, ext));
|
|
147
18
|
if (!def) return { status: "unsupported", extension: ext };
|
|
148
19
|
|
|
149
20
|
const root = resolveServerRoot(def, { filePath, workspaceRoot });
|
|
@@ -165,7 +36,13 @@ export async function resolveLspServerStatus({ filePath, workspaceRoot, onEvent
|
|
|
165
36
|
}
|
|
166
37
|
|
|
167
38
|
export function listLspServerDefinitions() {
|
|
168
|
-
return LSP_SERVERS.map(({ id, extensions, rootMarkers, command, args }) => ({ id, extensions, rootMarkers, command, args }));
|
|
39
|
+
return LSP_SERVERS.map(({ id, extensions, filenames, rootMarkers, command, args }) => ({ id, extensions, filenames, rootMarkers, command, args }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function matchesServer(server, filePath, ext) {
|
|
43
|
+
if (server.extensions.includes(ext)) return true;
|
|
44
|
+
const name = basename(filePath).toLowerCase();
|
|
45
|
+
return server.filenames?.includes(name) ?? false;
|
|
169
46
|
}
|
|
170
47
|
|
|
171
48
|
async function resolveCommand(def, { root, workspaceRoot, onEvent }) {
|