march-cli 0.1.24 → 0.1.26

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.
Files changed (234) hide show
  1. package/bin/march.mjs +13 -13
  2. package/package.json +49 -49
  3. package/src/agent/command-exec-tool.mjs +172 -172
  4. package/src/agent/context-stats-tool.mjs +57 -57
  5. package/src/agent/editing/diff-apply.mjs +28 -28
  6. package/src/agent/editing/diff-format.mjs +57 -57
  7. package/src/agent/editing/lsp-report.mjs +69 -69
  8. package/src/agent/file-edit-tool.mjs +250 -250
  9. package/src/agent/file-tools/read-file-tool.mjs +112 -112
  10. package/src/agent/file-tools/read-image-tool.mjs +76 -76
  11. package/src/agent/model-payload-dumper.mjs +208 -208
  12. package/src/agent/pi-session/pi-session-sidecar-failure.mjs +10 -10
  13. package/src/agent/provider/payload-messages.mjs +138 -138
  14. package/src/agent/runner/codex-large-context-guard.mjs +87 -87
  15. package/src/agent/runner/codex-transport-compression.mjs +180 -180
  16. package/src/agent/runner/codex-transport-debug.mjs +113 -113
  17. package/src/agent/runner/codex-websocket-event-debug.mjs +130 -130
  18. package/src/agent/runner/fast-model.mjs +36 -36
  19. package/src/agent/runner/runner-cleanup.mjs +12 -12
  20. package/src/agent/runner/runner-init.mjs +15 -15
  21. package/src/agent/runner/runner-session-state.mjs +40 -40
  22. package/src/agent/runner/runner-utils.mjs +24 -24
  23. package/src/agent/runner.mjs +299 -299
  24. package/src/agent/runtime/ipc/ipc-peer.mjs +99 -99
  25. package/src/agent/runtime/ipc/process-ipc-transport.mjs +16 -16
  26. package/src/agent/runtime/remote-runner-client.mjs +73 -73
  27. package/src/agent/runtime/remote-ui-client.mjs +20 -20
  28. package/src/agent/runtime/runner-ipc-target.mjs +125 -125
  29. package/src/agent/runtime/runner-process-client.mjs +47 -47
  30. package/src/agent/runtime/runner-process-entry.mjs +11 -11
  31. package/src/agent/runtime/runner-process-factory.mjs +111 -108
  32. package/src/agent/runtime/runner-runtime-host.mjs +79 -79
  33. package/src/agent/runtime/runtime-factory.mjs +42 -42
  34. package/src/agent/runtime/runtime-host.mjs +34 -34
  35. package/src/agent/runtime/ui-event-bridge.mjs +95 -95
  36. package/src/agent/screen-tools/list-windows-tool.mjs +39 -39
  37. package/src/agent/screen-tools/screen-tool.mjs +49 -49
  38. package/src/agent/screen-tools/windows-screen.mjs +133 -133
  39. package/src/agent/session/session-auto-name.mjs +41 -41
  40. package/src/agent/session/session-binding.mjs +12 -12
  41. package/src/agent/session/session-options.mjs +47 -47
  42. package/src/agent/tool-names.mjs +1 -1
  43. package/src/agent/tool-result.mjs +3 -3
  44. package/src/agent/tool-summary.mjs +112 -112
  45. package/src/agent/tools.mjs +58 -58
  46. package/src/agent/turn/turn-events.mjs +111 -111
  47. package/src/agent/turn/turn-logging.mjs +30 -30
  48. package/src/agent/turn/turn-runner.mjs +196 -196
  49. package/src/agent/vision-capability.mjs +14 -14
  50. package/src/auth/login-command.mjs +90 -90
  51. package/src/auth/storage.mjs +34 -34
  52. package/src/cli/args.mjs +96 -79
  53. package/src/cli/commands/copy-command.mjs +87 -87
  54. package/src/cli/commands/export-command.mjs +206 -206
  55. package/src/cli/commands/extensions-command.mjs +53 -53
  56. package/src/cli/commands/help-command.mjs +7 -7
  57. package/src/cli/commands/model-command.mjs +141 -141
  58. package/src/cli/commands/paste-image-command.mjs +43 -43
  59. package/src/cli/commands/provider-command.mjs +59 -59
  60. package/src/cli/commands/status-command.mjs +196 -194
  61. package/src/cli/commands/thinking-command.mjs +87 -87
  62. package/src/cli/fallback-ui.mjs +156 -156
  63. package/src/cli/input/attachment-tokens.mjs +20 -20
  64. package/src/cli/input/autocomplete.mjs +74 -74
  65. package/src/cli/input/external-editor.mjs +39 -39
  66. package/src/cli/input/file-search/index.mjs +160 -160
  67. package/src/cli/input/history-store.mjs +35 -35
  68. package/src/cli/input/image-clipboard.mjs +55 -55
  69. package/src/cli/input/keybinding-dispatch.mjs +76 -76
  70. package/src/cli/input/keybindings.mjs +96 -96
  71. package/src/cli/input/mode-state.mjs +43 -43
  72. package/src/cli/input/prompt-templates.mjs +84 -84
  73. package/src/cli/input/select-with-keyboard.mjs +86 -86
  74. package/src/cli/permissions.mjs +103 -103
  75. package/src/cli/repl-commands.mjs +86 -86
  76. package/src/cli/repl-loop.mjs +183 -183
  77. package/src/cli/selector-list.mjs +21 -21
  78. package/src/cli/session/pi-session-switch-command.mjs +41 -41
  79. package/src/cli/session/session-command.mjs +23 -23
  80. package/src/cli/session/session-list-command.mjs +68 -68
  81. package/src/cli/session/session-name-command.mjs +26 -26
  82. package/src/cli/session/session-source-command.mjs +89 -89
  83. package/src/cli/session/session-switch-command.mjs +1 -1
  84. package/src/cli/shell/shell-command.mjs +55 -55
  85. package/src/cli/shell/shell-drawer-controls.mjs +33 -33
  86. package/src/cli/shell/shell-drawer.mjs +192 -192
  87. package/src/cli/shell/shell-split-layout.mjs +70 -70
  88. package/src/cli/slash-commands.mjs +192 -192
  89. package/src/cli/startup/create-runtime-runner.mjs +61 -61
  90. package/src/cli/startup/runtime-close.mjs +23 -23
  91. package/src/cli/startup/startup-banner.mjs +71 -71
  92. package/src/cli/startup/startup-session.mjs +51 -51
  93. package/src/cli/status-line-updater.mjs +75 -75
  94. package/src/cli/tool-output.mjs +9 -9
  95. package/src/cli/tui/editor/external-editor-runner.mjs +24 -24
  96. package/src/cli/tui/input/mouse-selection-controller.mjs +91 -91
  97. package/src/cli/tui/input/mouse-tracking.mjs +20 -20
  98. package/src/cli/tui/layout/main-pane-layout.mjs +47 -47
  99. package/src/cli/tui/layout/safe-render-boundary.mjs +46 -46
  100. package/src/cli/tui/markdown-renderer.mjs +285 -285
  101. package/src/cli/tui/output/scroll-state.mjs +79 -79
  102. package/src/cli/tui/output/text-line-renderer.mjs +50 -50
  103. package/src/cli/tui/output/tool-card-renderer.mjs +59 -59
  104. package/src/cli/tui/output/visible-lines.mjs +8 -8
  105. package/src/cli/tui/output-buffer.mjs +293 -293
  106. package/src/cli/tui/permission-request-ui.mjs +18 -18
  107. package/src/cli/tui/recall-rendering.mjs +28 -25
  108. package/src/cli/tui/render/render-scheduler.mjs +26 -26
  109. package/src/cli/tui/render/stream-delta-buffer.mjs +46 -46
  110. package/src/cli/tui/select/editor-select-list.mjs +111 -111
  111. package/src/cli/tui/selection-screen.mjs +269 -269
  112. package/src/cli/tui/status/retry-status.mjs +72 -72
  113. package/src/cli/tui/status/spinner-status.mjs +42 -42
  114. package/src/cli/tui/status/status-bar.mjs +225 -225
  115. package/src/cli/tui/syntax/highlighting.mjs +260 -260
  116. package/src/cli/tui/syntax/languages.mjs +91 -91
  117. package/src/cli/tui/syntax/tree-sitter/bash.highlights.scm +261 -261
  118. package/src/cli/tui/syntax/tree-sitter/c.highlights.scm +341 -341
  119. package/src/cli/tui/syntax/tree-sitter/cpp.highlights.scm +268 -268
  120. package/src/cli/tui/syntax/tree-sitter/csharp.highlights.scm +577 -577
  121. package/src/cli/tui/syntax/tree-sitter/css.highlights.scm +109 -109
  122. package/src/cli/tui/syntax/tree-sitter/diff.highlights.scm +49 -49
  123. package/src/cli/tui/syntax/tree-sitter/go.highlights.scm +254 -254
  124. package/src/cli/tui/syntax/tree-sitter/html.highlights.scm +13 -13
  125. package/src/cli/tui/syntax/tree-sitter/java.highlights.scm +330 -330
  126. package/src/cli/tui/syntax/tree-sitter/json.highlights.scm +38 -38
  127. package/src/cli/tui/syntax/tree-sitter/php.highlights.scm +203 -203
  128. package/src/cli/tui/syntax/tree-sitter/python.highlights.scm +137 -137
  129. package/src/cli/tui/syntax/tree-sitter/ruby.highlights.scm +309 -309
  130. package/src/cli/tui/syntax/tree-sitter/rust.highlights.scm +531 -531
  131. package/src/cli/tui/syntax/tree-sitter/toml.highlights.scm +39 -39
  132. package/src/cli/tui/syntax/tree-sitter/tsx.highlights.scm +35 -35
  133. package/src/cli/tui/syntax/tree-sitter/typescript.highlights.scm +35 -35
  134. package/src/cli/tui/syntax/tree-sitter/yaml.highlights.scm +99 -99
  135. package/src/cli/tui/tool-rendering.mjs +87 -87
  136. package/src/cli/tui/tui-diff-rendering.mjs +157 -157
  137. package/src/cli/tui/tui-handlers.mjs +111 -111
  138. package/src/cli/tui/tui-input-controller.mjs +61 -61
  139. package/src/cli/tui/ui-theme.mjs +157 -157
  140. package/src/cli/ui.mjs +297 -297
  141. package/src/config/config-json.mjs +108 -84
  142. package/src/config/dotenv.mjs +20 -20
  143. package/src/config/features.mjs +75 -75
  144. package/src/config/loader.mjs +156 -143
  145. package/src/config/settings-command.mjs +97 -97
  146. package/src/context/engine.mjs +199 -198
  147. package/src/context/injections.mjs +26 -26
  148. package/src/context/profiles.mjs +39 -39
  149. package/src/context/project-context.mjs +20 -20
  150. package/src/context/session-status.mjs +25 -17
  151. package/src/context/shell-layers.mjs +23 -23
  152. package/src/context/system-core/base.md +51 -50
  153. package/src/context/system-core/prompts/deepseek-v4-pro.md +3 -3
  154. package/src/context/system-core/prompts/default.md +3 -3
  155. package/src/context/system-core.mjs +35 -35
  156. package/src/debug/logger.mjs +141 -141
  157. package/src/debug/model-context-dumper.mjs +52 -52
  158. package/src/extensions/discovery.mjs +40 -40
  159. package/src/extensions/lifecycle-adapter.mjs +210 -210
  160. package/src/extensions/lifecycle-manifest.mjs +69 -69
  161. package/src/image-gen/index.mjs +7 -7
  162. package/src/image-gen/provider.mjs +231 -231
  163. package/src/image-gen/tool.mjs +84 -84
  164. package/src/lsp/client.mjs +257 -257
  165. package/src/lsp/diagnostic-store.mjs +42 -42
  166. package/src/lsp/diagnostics-format.mjs +72 -72
  167. package/src/lsp/managed-node-server.mjs +99 -99
  168. package/src/lsp/path-match.mjs +10 -10
  169. package/src/lsp/server-definitions.mjs +188 -188
  170. package/src/lsp/servers.mjs +165 -165
  171. package/src/lsp/service.mjs +110 -110
  172. package/src/lsp/status-message.mjs +9 -9
  173. package/src/lsp/typescript-project-resolver.mjs +186 -186
  174. package/src/main.mjs +294 -299
  175. package/src/mcp/client.mjs +195 -195
  176. package/src/mcp/config.mjs +130 -130
  177. package/src/mcp/index.mjs +48 -48
  178. package/src/mcp/tools.mjs +98 -98
  179. package/src/memory/command.mjs +120 -0
  180. package/src/memory/markdown/markdown-delete.mjs +23 -23
  181. package/src/memory/markdown/markdown-format.mjs +128 -128
  182. package/src/memory/markdown/markdown-recall.mjs +28 -28
  183. package/src/memory/markdown/ripgrep.mjs +16 -16
  184. package/src/memory/markdown/sqlite-index.mjs +87 -87
  185. package/src/memory/markdown-store.mjs +272 -286
  186. package/src/memory/markdown-tools.mjs +174 -103
  187. package/src/memory/remote/client.mjs +68 -0
  188. package/src/memory/remote/config.mjs +52 -0
  189. package/src/memory/remote/server.mjs +99 -0
  190. package/src/memory/search.mjs +183 -0
  191. package/src/network/environment.mjs +131 -131
  192. package/src/notification/desktop-notifier.mjs +262 -262
  193. package/src/platform/open-file.mjs +28 -28
  194. package/src/platform/spawn-command.mjs +27 -27
  195. package/src/provider/accept-command.mjs +89 -89
  196. package/src/provider/command.mjs +21 -21
  197. package/src/provider/config-command.mjs +129 -129
  198. package/src/provider/custom-provider.mjs +113 -113
  199. package/src/provider/hosted-tools.mjs +111 -111
  200. package/src/provider/presets.mjs +72 -72
  201. package/src/provider/share-command.mjs +79 -79
  202. package/src/provider/share-payload.mjs +52 -52
  203. package/src/session/attachment-display.mjs +16 -16
  204. package/src/session/attachment-references.mjs +65 -65
  205. package/src/session/attachments.mjs +140 -140
  206. package/src/session/persist.mjs +1 -1
  207. package/src/session/pi-manager.mjs +34 -34
  208. package/src/session/session-utils.mjs +16 -16
  209. package/src/session/sidecar-sync.mjs +19 -19
  210. package/src/session/sidecar.mjs +69 -69
  211. package/src/session/transcript.mjs +83 -83
  212. package/src/session/tree.mjs +42 -42
  213. package/src/shell/cli-runtime.mjs +11 -11
  214. package/src/shell/hints.mjs +12 -12
  215. package/src/shell/node-pty-adapter.mjs +81 -81
  216. package/src/shell/runtime-state.mjs +126 -126
  217. package/src/shell/runtime.mjs +252 -252
  218. package/src/shell/screen-buffer.mjs +136 -136
  219. package/src/shell/tool-read.mjs +74 -74
  220. package/src/shell/tools.mjs +299 -299
  221. package/src/supergrok/actions/image-generate.mjs +60 -60
  222. package/src/supergrok/actions/search.mjs +78 -78
  223. package/src/supergrok/auth.mjs +36 -36
  224. package/src/supergrok/constants.mjs +18 -18
  225. package/src/supergrok/oauth-provider.mjs +278 -278
  226. package/src/supergrok/provider.mjs +35 -35
  227. package/src/supergrok/response.mjs +76 -76
  228. package/src/supergrok/tool.mjs +61 -61
  229. package/src/text/ansi.mjs +3 -3
  230. package/src/web/config-command.mjs +43 -43
  231. package/src/web/fetch.mjs +78 -78
  232. package/src/web/presets.mjs +16 -16
  233. package/src/web/search.mjs +83 -83
  234. package/src/web/tools.mjs +107 -107
@@ -1,257 +1,257 @@
1
- import { pathToFileURL } from "node:url";
2
- import { spawnCommand } from "../platform/spawn-command.mjs";
3
- import { basename, extname } from "node:path";
4
- import { readFileSync } from "node:fs";
5
-
6
- const INITIALIZE_TIMEOUT_MS = 15000;
7
- const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2;
8
-
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",
30
- ".js": "javascript",
31
- ".json": "json",
32
- ".jsonc": "jsonc",
33
- ".jsx": "javascriptreact",
34
- ".ksh": "shellscript",
35
- ".less": "less",
36
- ".lua": "lua",
37
- ".markdown": "markdown",
38
- ".md": "markdown",
39
- ".mdx": "mdx",
40
- ".mjs": "javascript",
41
- ".mts": "typescript",
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",
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",
67
- };
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
-
74
- export class LspClient {
75
- constructor({ serverId, command, args = [], cwd, initialization = {}, store }) {
76
- this.serverId = serverId;
77
- this.command = command;
78
- this.args = args;
79
- this.cwd = cwd;
80
- this.initialization = initialization;
81
- this.store = store;
82
- this.status = "starting";
83
- this.process = null;
84
- this.buffer = Buffer.alloc(0);
85
- this.nextId = 1;
86
- this.pending = new Map();
87
- this.documents = new Map();
88
- this.syncKind = null;
89
- }
90
-
91
- async start() {
92
- this.process = spawnCommand(this.command, this.args, {
93
- cwd: this.cwd,
94
- env: process.env,
95
- stdio: ["pipe", "pipe", "pipe"],
96
- windowsHide: true,
97
- });
98
- this.process.stdout.on("data", (chunk) => this.#onData(chunk));
99
- this.process.on("exit", () => {
100
- this.status = "failed";
101
- for (const pending of this.pending.values()) pending.reject(new Error("LSP exited"));
102
- this.pending.clear();
103
- });
104
-
105
- const initialized = await withTimeout(this.#request("initialize", {
106
- processId: process.pid,
107
- rootUri: pathToFileURL(this.cwd).href,
108
- workspaceFolders: [{ name: "workspace", uri: pathToFileURL(this.cwd).href }],
109
- initializationOptions: this.initialization,
110
- capabilities: {
111
- workspace: {
112
- configuration: true,
113
- workspaceFolders: true,
114
- },
115
- textDocument: {
116
- synchronization: {
117
- didOpen: true,
118
- didChange: true,
119
- },
120
- publishDiagnostics: {},
121
- },
122
- },
123
- }), INITIALIZE_TIMEOUT_MS);
124
- this.syncKind = getSyncKind(initialized?.capabilities);
125
- this.#notify("initialized", {});
126
- this.status = "ready";
127
- }
128
-
129
- touchFile(path) {
130
- if (this.status === "failed") return;
131
- const text = readFileSync(path, "utf8");
132
- const uri = pathToFileURL(path).href;
133
- const existing = this.documents.get(path);
134
- this.status = "busy";
135
- if (existing) {
136
- const version = existing.version + 1;
137
- this.documents.set(path, { version, text });
138
- this.#notify("textDocument/didChange", {
139
- textDocument: { uri, version },
140
- contentChanges: this.syncKind === TEXT_DOCUMENT_SYNC_INCREMENTAL
141
- ? [{ range: { start: { line: 0, character: 0 }, end: endPosition(existing.text) }, text }]
142
- : [{ text }],
143
- });
144
- return;
145
- }
146
- this.documents.set(path, { version: 0, text });
147
- this.#notify("textDocument/didOpen", {
148
- textDocument: {
149
- uri,
150
- languageId: languageIdForPath(path),
151
- version: 0,
152
- text,
153
- },
154
- });
155
- }
156
-
157
- async shutdown() {
158
- if (!this.process) return;
159
- try { this.#notify("shutdown", null); } catch {}
160
- this.process.kill();
161
- }
162
-
163
- #onData(chunk) {
164
- this.buffer = Buffer.concat([this.buffer, chunk]);
165
- for (;;) {
166
- const headerEnd = this.buffer.indexOf("\r\n\r\n");
167
- if (headerEnd === -1) return;
168
- const header = this.buffer.slice(0, headerEnd).toString("utf8");
169
- const match = header.match(/Content-Length: (\d+)/i);
170
- if (!match) {
171
- this.buffer = this.buffer.slice(headerEnd + 4);
172
- continue;
173
- }
174
- const length = Number(match[1]);
175
- const messageStart = headerEnd + 4;
176
- const messageEnd = messageStart + length;
177
- if (this.buffer.length < messageEnd) return;
178
- const raw = this.buffer.slice(messageStart, messageEnd).toString("utf8");
179
- this.buffer = this.buffer.slice(messageEnd);
180
- try {
181
- this.#handleMessage(JSON.parse(raw));
182
- } catch {}
183
- }
184
- }
185
-
186
- #handleMessage(message) {
187
- if (message.id !== undefined && this.pending.has(message.id)) {
188
- const pending = this.pending.get(message.id);
189
- this.pending.delete(message.id);
190
- if (message.error) pending.reject(new Error(message.error.message ?? "LSP request failed"));
191
- else pending.resolve(message.result);
192
- return;
193
- }
194
-
195
- if (message.method === "textDocument/publishDiagnostics") {
196
- this.store.replaceFile({
197
- serverId: this.serverId,
198
- uri: message.params?.uri,
199
- diagnostics: message.params?.diagnostics ?? [],
200
- });
201
- this.status = "idle";
202
- return;
203
- }
204
-
205
- if (message.id !== undefined && message.method) {
206
- this.#send({ jsonrpc: "2.0", id: message.id, result: this.#requestResult(message.method) });
207
- }
208
- }
209
-
210
- #requestResult(method) {
211
- if (method === "workspace/configuration") return [];
212
- if (method === "workspace/workspaceFolders") return [{ name: "workspace", uri: pathToFileURL(this.cwd).href }];
213
- if (method === "window/workDoneProgress/create") return null;
214
- if (method === "client/registerCapability") return null;
215
- if (method === "client/unregisterCapability") return null;
216
- return null;
217
- }
218
-
219
- #request(method, params) {
220
- const id = this.nextId++;
221
- const promise = new Promise((resolve, reject) => {
222
- this.pending.set(id, { resolve, reject });
223
- });
224
- this.#send({ jsonrpc: "2.0", id, method, params });
225
- return promise;
226
- }
227
-
228
- #notify(method, params) {
229
- this.#send({ jsonrpc: "2.0", method, params });
230
- }
231
-
232
- #send(message) {
233
- const body = JSON.stringify(message);
234
- this.process.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
235
- }
236
- }
237
-
238
- function withTimeout(promise, ms) {
239
- let timer;
240
- return Promise.race([
241
- promise,
242
- new Promise((_, reject) => {
243
- timer = setTimeout(() => reject(new Error("LSP initialize timed out")), ms);
244
- }),
245
- ]).finally(() => clearTimeout(timer));
246
- }
247
-
248
- function getSyncKind(capabilities) {
249
- const sync = capabilities?.textDocumentSync;
250
- if (typeof sync === "number") return sync;
251
- return sync?.change ?? null;
252
- }
253
-
254
- function endPosition(text) {
255
- const lines = text.split(/\r\n|\r|\n/);
256
- return { line: lines.length - 1, character: lines.at(-1)?.length ?? 0 };
257
- }
1
+ import { pathToFileURL } from "node:url";
2
+ import { spawnCommand } from "../platform/spawn-command.mjs";
3
+ import { basename, extname } from "node:path";
4
+ import { readFileSync } from "node:fs";
5
+
6
+ const INITIALIZE_TIMEOUT_MS = 15000;
7
+ const TEXT_DOCUMENT_SYNC_INCREMENTAL = 2;
8
+
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",
30
+ ".js": "javascript",
31
+ ".json": "json",
32
+ ".jsonc": "jsonc",
33
+ ".jsx": "javascriptreact",
34
+ ".ksh": "shellscript",
35
+ ".less": "less",
36
+ ".lua": "lua",
37
+ ".markdown": "markdown",
38
+ ".md": "markdown",
39
+ ".mdx": "mdx",
40
+ ".mjs": "javascript",
41
+ ".mts": "typescript",
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",
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",
67
+ };
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
+
74
+ export class LspClient {
75
+ constructor({ serverId, command, args = [], cwd, initialization = {}, store }) {
76
+ this.serverId = serverId;
77
+ this.command = command;
78
+ this.args = args;
79
+ this.cwd = cwd;
80
+ this.initialization = initialization;
81
+ this.store = store;
82
+ this.status = "starting";
83
+ this.process = null;
84
+ this.buffer = Buffer.alloc(0);
85
+ this.nextId = 1;
86
+ this.pending = new Map();
87
+ this.documents = new Map();
88
+ this.syncKind = null;
89
+ }
90
+
91
+ async start() {
92
+ this.process = spawnCommand(this.command, this.args, {
93
+ cwd: this.cwd,
94
+ env: process.env,
95
+ stdio: ["pipe", "pipe", "pipe"],
96
+ windowsHide: true,
97
+ });
98
+ this.process.stdout.on("data", (chunk) => this.#onData(chunk));
99
+ this.process.on("exit", () => {
100
+ this.status = "failed";
101
+ for (const pending of this.pending.values()) pending.reject(new Error("LSP exited"));
102
+ this.pending.clear();
103
+ });
104
+
105
+ const initialized = await withTimeout(this.#request("initialize", {
106
+ processId: process.pid,
107
+ rootUri: pathToFileURL(this.cwd).href,
108
+ workspaceFolders: [{ name: "workspace", uri: pathToFileURL(this.cwd).href }],
109
+ initializationOptions: this.initialization,
110
+ capabilities: {
111
+ workspace: {
112
+ configuration: true,
113
+ workspaceFolders: true,
114
+ },
115
+ textDocument: {
116
+ synchronization: {
117
+ didOpen: true,
118
+ didChange: true,
119
+ },
120
+ publishDiagnostics: {},
121
+ },
122
+ },
123
+ }), INITIALIZE_TIMEOUT_MS);
124
+ this.syncKind = getSyncKind(initialized?.capabilities);
125
+ this.#notify("initialized", {});
126
+ this.status = "ready";
127
+ }
128
+
129
+ touchFile(path) {
130
+ if (this.status === "failed") return;
131
+ const text = readFileSync(path, "utf8");
132
+ const uri = pathToFileURL(path).href;
133
+ const existing = this.documents.get(path);
134
+ this.status = "busy";
135
+ if (existing) {
136
+ const version = existing.version + 1;
137
+ this.documents.set(path, { version, text });
138
+ this.#notify("textDocument/didChange", {
139
+ textDocument: { uri, version },
140
+ contentChanges: this.syncKind === TEXT_DOCUMENT_SYNC_INCREMENTAL
141
+ ? [{ range: { start: { line: 0, character: 0 }, end: endPosition(existing.text) }, text }]
142
+ : [{ text }],
143
+ });
144
+ return;
145
+ }
146
+ this.documents.set(path, { version: 0, text });
147
+ this.#notify("textDocument/didOpen", {
148
+ textDocument: {
149
+ uri,
150
+ languageId: languageIdForPath(path),
151
+ version: 0,
152
+ text,
153
+ },
154
+ });
155
+ }
156
+
157
+ async shutdown() {
158
+ if (!this.process) return;
159
+ try { this.#notify("shutdown", null); } catch {}
160
+ this.process.kill();
161
+ }
162
+
163
+ #onData(chunk) {
164
+ this.buffer = Buffer.concat([this.buffer, chunk]);
165
+ for (;;) {
166
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
167
+ if (headerEnd === -1) return;
168
+ const header = this.buffer.slice(0, headerEnd).toString("utf8");
169
+ const match = header.match(/Content-Length: (\d+)/i);
170
+ if (!match) {
171
+ this.buffer = this.buffer.slice(headerEnd + 4);
172
+ continue;
173
+ }
174
+ const length = Number(match[1]);
175
+ const messageStart = headerEnd + 4;
176
+ const messageEnd = messageStart + length;
177
+ if (this.buffer.length < messageEnd) return;
178
+ const raw = this.buffer.slice(messageStart, messageEnd).toString("utf8");
179
+ this.buffer = this.buffer.slice(messageEnd);
180
+ try {
181
+ this.#handleMessage(JSON.parse(raw));
182
+ } catch {}
183
+ }
184
+ }
185
+
186
+ #handleMessage(message) {
187
+ if (message.id !== undefined && this.pending.has(message.id)) {
188
+ const pending = this.pending.get(message.id);
189
+ this.pending.delete(message.id);
190
+ if (message.error) pending.reject(new Error(message.error.message ?? "LSP request failed"));
191
+ else pending.resolve(message.result);
192
+ return;
193
+ }
194
+
195
+ if (message.method === "textDocument/publishDiagnostics") {
196
+ this.store.replaceFile({
197
+ serverId: this.serverId,
198
+ uri: message.params?.uri,
199
+ diagnostics: message.params?.diagnostics ?? [],
200
+ });
201
+ this.status = "idle";
202
+ return;
203
+ }
204
+
205
+ if (message.id !== undefined && message.method) {
206
+ this.#send({ jsonrpc: "2.0", id: message.id, result: this.#requestResult(message.method) });
207
+ }
208
+ }
209
+
210
+ #requestResult(method) {
211
+ if (method === "workspace/configuration") return [];
212
+ if (method === "workspace/workspaceFolders") return [{ name: "workspace", uri: pathToFileURL(this.cwd).href }];
213
+ if (method === "window/workDoneProgress/create") return null;
214
+ if (method === "client/registerCapability") return null;
215
+ if (method === "client/unregisterCapability") return null;
216
+ return null;
217
+ }
218
+
219
+ #request(method, params) {
220
+ const id = this.nextId++;
221
+ const promise = new Promise((resolve, reject) => {
222
+ this.pending.set(id, { resolve, reject });
223
+ });
224
+ this.#send({ jsonrpc: "2.0", id, method, params });
225
+ return promise;
226
+ }
227
+
228
+ #notify(method, params) {
229
+ this.#send({ jsonrpc: "2.0", method, params });
230
+ }
231
+
232
+ #send(message) {
233
+ const body = JSON.stringify(message);
234
+ this.process.stdin.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
235
+ }
236
+ }
237
+
238
+ function withTimeout(promise, ms) {
239
+ let timer;
240
+ return Promise.race([
241
+ promise,
242
+ new Promise((_, reject) => {
243
+ timer = setTimeout(() => reject(new Error("LSP initialize timed out")), ms);
244
+ }),
245
+ ]).finally(() => clearTimeout(timer));
246
+ }
247
+
248
+ function getSyncKind(capabilities) {
249
+ const sync = capabilities?.textDocumentSync;
250
+ if (typeof sync === "number") return sync;
251
+ return sync?.change ?? null;
252
+ }
253
+
254
+ function endPosition(text) {
255
+ const lines = text.split(/\r\n|\r|\n/);
256
+ return { line: lines.length - 1, character: lines.at(-1)?.length ?? 0 };
257
+ }
@@ -1,42 +1,42 @@
1
- import { fileURLToPath } from "node:url";
2
- import { lspPathKey } from "./path-match.mjs";
3
-
4
- export class LspDiagnosticStore {
5
- constructor() {
6
- this.byPath = new Map();
7
- }
8
-
9
- replaceFile({ serverId, uri, diagnostics = [] }) {
10
- const path = uriToPath(uri);
11
- if (!path) return;
12
- const normalized = diagnostics.map((diagnostic) => ({
13
- ...diagnostic,
14
- serverId,
15
- path,
16
- }));
17
- this.byPath.set(lspPathKey(path), {
18
- path,
19
- updatedAt: Date.now(),
20
- diagnostics: normalized,
21
- });
22
- }
23
-
24
- snapshot() {
25
- const diagnostics = [];
26
- const files = [];
27
- for (const entry of this.byPath.values()) {
28
- diagnostics.push(...entry.diagnostics);
29
- files.push({ path: entry.path, updatedAt: entry.updatedAt, diagnostics: entry.diagnostics.length });
30
- }
31
- return { diagnostics, files };
32
- }
33
- }
34
-
35
- function uriToPath(uri) {
36
- if (typeof uri !== "string" || !uri.startsWith("file://")) return null;
37
- try {
38
- return fileURLToPath(uri);
39
- } catch {
40
- return null;
41
- }
42
- }
1
+ import { fileURLToPath } from "node:url";
2
+ import { lspPathKey } from "./path-match.mjs";
3
+
4
+ export class LspDiagnosticStore {
5
+ constructor() {
6
+ this.byPath = new Map();
7
+ }
8
+
9
+ replaceFile({ serverId, uri, diagnostics = [] }) {
10
+ const path = uriToPath(uri);
11
+ if (!path) return;
12
+ const normalized = diagnostics.map((diagnostic) => ({
13
+ ...diagnostic,
14
+ serverId,
15
+ path,
16
+ }));
17
+ this.byPath.set(lspPathKey(path), {
18
+ path,
19
+ updatedAt: Date.now(),
20
+ diagnostics: normalized,
21
+ });
22
+ }
23
+
24
+ snapshot() {
25
+ const diagnostics = [];
26
+ const files = [];
27
+ for (const entry of this.byPath.values()) {
28
+ diagnostics.push(...entry.diagnostics);
29
+ files.push({ path: entry.path, updatedAt: entry.updatedAt, diagnostics: entry.diagnostics.length });
30
+ }
31
+ return { diagnostics, files };
32
+ }
33
+ }
34
+
35
+ function uriToPath(uri) {
36
+ if (typeof uri !== "string" || !uri.startsWith("file://")) return null;
37
+ try {
38
+ return fileURLToPath(uri);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }