@4onstudios/iris-agent 0.1.0

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 (216) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +690 -0
  3. package/dist/api/acp/acpServer.d.ts +23 -0
  4. package/dist/api/acp/acpServer.js +814 -0
  5. package/dist/api/acp/index.d.ts +2 -0
  6. package/dist/api/acp/index.js +2 -0
  7. package/dist/api/acp/irisClient.d.ts +58 -0
  8. package/dist/api/acp/irisClient.js +214 -0
  9. package/dist/api/agent.d.ts +14 -0
  10. package/dist/api/agent.js +4171 -0
  11. package/dist/api/backendService.d.ts +4 -0
  12. package/dist/api/backendService.js +14 -0
  13. package/dist/api/core/agent/host/AgentContract.d.ts +82 -0
  14. package/dist/api/core/agent/host/AgentContract.js +1 -0
  15. package/dist/api/core/agent/host/AgentRegistry.d.ts +24 -0
  16. package/dist/api/core/agent/host/AgentRegistry.js +125 -0
  17. package/dist/api/core/agent/host/defaultRegistry.d.ts +15 -0
  18. package/dist/api/core/agent/host/defaultRegistry.js +46 -0
  19. package/dist/api/core/agent/host/externalAgentLifecycle.d.ts +65 -0
  20. package/dist/api/core/agent/host/externalAgentLifecycle.js +117 -0
  21. package/dist/api/core/agent/host/hostSessionManager.d.ts +111 -0
  22. package/dist/api/core/agent/host/hostSessionManager.js +352 -0
  23. package/dist/api/core/agent/host/index.d.ts +5 -0
  24. package/dist/api/core/agent/host/index.js +5 -0
  25. package/dist/api/core/agent/index.d.ts +87 -0
  26. package/dist/api/core/agent/index.js +1024 -0
  27. package/dist/api/core/agent/publicContracts.d.ts +35 -0
  28. package/dist/api/core/agent/publicContracts.js +103 -0
  29. package/dist/api/core/agent/tools/applyDiff.d.ts +51 -0
  30. package/dist/api/core/agent/tools/applyDiff.js +131 -0
  31. package/dist/api/core/agent/tools/backgroundTasks.d.ts +240 -0
  32. package/dist/api/core/agent/tools/backgroundTasks.js +313 -0
  33. package/dist/api/core/agent/tools/createDirectory.d.ts +43 -0
  34. package/dist/api/core/agent/tools/createDirectory.js +77 -0
  35. package/dist/api/core/agent/tools/deleteFile.d.ts +37 -0
  36. package/dist/api/core/agent/tools/deleteFile.js +66 -0
  37. package/dist/api/core/agent/tools/editFile.d.ts +73 -0
  38. package/dist/api/core/agent/tools/editFile.js +173 -0
  39. package/dist/api/core/agent/tools/executeCommand.d.ts +59 -0
  40. package/dist/api/core/agent/tools/executeCommand.js +250 -0
  41. package/dist/api/core/agent/tools/fileContent.d.ts +54 -0
  42. package/dist/api/core/agent/tools/fileContent.js +227 -0
  43. package/dist/api/core/agent/tools/findDefinition.d.ts +72 -0
  44. package/dist/api/core/agent/tools/findDefinition.js +109 -0
  45. package/dist/api/core/agent/tools/findReferences.d.ts +86 -0
  46. package/dist/api/core/agent/tools/findReferences.js +135 -0
  47. package/dist/api/core/agent/tools/formatDocument.d.ts +67 -0
  48. package/dist/api/core/agent/tools/formatDocument.js +90 -0
  49. package/dist/api/core/agent/tools/getCodeActions.d.ts +77 -0
  50. package/dist/api/core/agent/tools/getCodeActions.js +97 -0
  51. package/dist/api/core/agent/tools/getCodeCompletion.d.ts +77 -0
  52. package/dist/api/core/agent/tools/getCodeCompletion.js +174 -0
  53. package/dist/api/core/agent/tools/getCodeContext.d.ts +14 -0
  54. package/dist/api/core/agent/tools/getCodeContext.js +282 -0
  55. package/dist/api/core/agent/tools/getSignatureHelp.d.ts +61 -0
  56. package/dist/api/core/agent/tools/getSignatureHelp.js +85 -0
  57. package/dist/api/core/agent/tools/getSymbols.d.ts +50 -0
  58. package/dist/api/core/agent/tools/getSymbols.js +164 -0
  59. package/dist/api/core/agent/tools/getSymbolsLSP.d.ts +58 -0
  60. package/dist/api/core/agent/tools/getSymbolsLSP.js +147 -0
  61. package/dist/api/core/agent/tools/getTypeInfo.d.ts +70 -0
  62. package/dist/api/core/agent/tools/getTypeInfo.js +84 -0
  63. package/dist/api/core/agent/tools/getWorkspaceInfo.d.ts +80 -0
  64. package/dist/api/core/agent/tools/getWorkspaceInfo.js +281 -0
  65. package/dist/api/core/agent/tools/getWorkspaceSymbols.d.ts +53 -0
  66. package/dist/api/core/agent/tools/getWorkspaceSymbols.js +97 -0
  67. package/dist/api/core/agent/tools/grepSearch.d.ts +100 -0
  68. package/dist/api/core/agent/tools/grepSearch.js +211 -0
  69. package/dist/api/core/agent/tools/languageModelToolsIntegration.d.ts +13 -0
  70. package/dist/api/core/agent/tools/languageModelToolsIntegration.js +22 -0
  71. package/dist/api/core/agent/tools/listDirectory.d.ts +66 -0
  72. package/dist/api/core/agent/tools/listDirectory.js +161 -0
  73. package/dist/api/core/agent/tools/mcpTools.d.ts +48 -0
  74. package/dist/api/core/agent/tools/mcpTools.js +442 -0
  75. package/dist/api/core/agent/tools/queryKnowledgeGraph.d.ts +120 -0
  76. package/dist/api/core/agent/tools/queryKnowledgeGraph.js +306 -0
  77. package/dist/api/core/agent/tools/readFile.d.ts +92 -0
  78. package/dist/api/core/agent/tools/readFile.js +337 -0
  79. package/dist/api/core/agent/tools/renameFile.d.ts +49 -0
  80. package/dist/api/core/agent/tools/renameFile.js +86 -0
  81. package/dist/api/core/agent/tools/renameSymbol.d.ts +70 -0
  82. package/dist/api/core/agent/tools/renameSymbol.js +106 -0
  83. package/dist/api/core/agent/tools/runTerminalCommand.d.ts +72 -0
  84. package/dist/api/core/agent/tools/runTerminalCommand.js +69 -0
  85. package/dist/api/core/agent/tools/searchFiles.d.ts +82 -0
  86. package/dist/api/core/agent/tools/searchFiles.js +280 -0
  87. package/dist/api/core/agent/tools/terminalAutoApprove.d.ts +3 -0
  88. package/dist/api/core/agent/tools/terminalAutoApprove.js +676 -0
  89. package/dist/api/core/agent/tools/treeTraversal.d.ts +60 -0
  90. package/dist/api/core/agent/tools/treeTraversal.js +217 -0
  91. package/dist/api/core/agent/tools/webSearch.d.ts +37 -0
  92. package/dist/api/core/agent/tools/webSearch.js +80 -0
  93. package/dist/api/core/agent/tools/writeFile.d.ts +59 -0
  94. package/dist/api/core/agent/tools/writeFile.js +97 -0
  95. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.d.ts +12 -0
  96. package/dist/api/core/agent/utils/capturedWorkspaceMutationBridge.js +67 -0
  97. package/dist/api/core/agent/utils/diffUtils.d.ts +14 -0
  98. package/dist/api/core/agent/utils/diffUtils.js +39 -0
  99. package/dist/api/core/agent/utils/environmentSnapshot.d.ts +42 -0
  100. package/dist/api/core/agent/utils/environmentSnapshot.js +213 -0
  101. package/dist/api/core/agent/utils/errorRecovery.d.ts +49 -0
  102. package/dist/api/core/agent/utils/errorRecovery.js +229 -0
  103. package/dist/api/core/agent/utils/multimodalTokenLimiter.d.ts +8 -0
  104. package/dist/api/core/agent/utils/multimodalTokenLimiter.js +238 -0
  105. package/dist/api/core/agent/utils/openRouterModelSettings.d.ts +12 -0
  106. package/dist/api/core/agent/utils/openRouterModelSettings.js +34 -0
  107. package/dist/api/core/agent/utils/pathRecovery.d.ts +1 -0
  108. package/dist/api/core/agent/utils/pathRecovery.js +29 -0
  109. package/dist/api/core/agent/utils/repoMapIndex.d.ts +3 -0
  110. package/dist/api/core/agent/utils/repoMapIndex.js +197 -0
  111. package/dist/api/core/agent/utils/skillsDiscovery.d.ts +10 -0
  112. package/dist/api/core/agent/utils/skillsDiscovery.js +50 -0
  113. package/dist/api/core/agent/utils/toolCallBudget.d.ts +9 -0
  114. package/dist/api/core/agent/utils/toolCallBudget.js +55 -0
  115. package/dist/api/core/agent/utils/toolLifecycle.d.ts +30 -0
  116. package/dist/api/core/agent/utils/toolLifecycle.js +361 -0
  117. package/dist/api/core/agent/utils/toolResultSafetyProcessor.d.ts +12 -0
  118. package/dist/api/core/agent/utils/toolResultSafetyProcessor.js +57 -0
  119. package/dist/api/core/agent/utils/workspaceMutationBridge.d.ts +13 -0
  120. package/dist/api/core/agent/utils/workspaceMutationBridge.js +149 -0
  121. package/dist/api/core/agent/utils/workspacePathGuard.d.ts +7 -0
  122. package/dist/api/core/agent/utils/workspacePathGuard.js +79 -0
  123. package/dist/api/core/containers/chat/toolResultSerialization.d.ts +8 -0
  124. package/dist/api/core/containers/chat/toolResultSerialization.js +71 -0
  125. package/dist/api/core/library/BrowserManager.d.ts +11 -0
  126. package/dist/api/core/library/BrowserManager.js +66 -0
  127. package/dist/api/core/library/desktopWorkspace.d.ts +1 -0
  128. package/dist/api/core/library/desktopWorkspace.js +3 -0
  129. package/dist/api/core/library/extensionManager.d.ts +10 -0
  130. package/dist/api/core/library/extensionManager.js +27 -0
  131. package/dist/api/core/library/knowledgeGraph.d.ts +268 -0
  132. package/dist/api/core/library/knowledgeGraph.js +989 -0
  133. package/dist/api/core/library/languageModelTools.d.ts +141 -0
  134. package/dist/api/core/library/languageModelTools.js +207 -0
  135. package/dist/api/core/library/localRuntime.d.ts +23 -0
  136. package/dist/api/core/library/localRuntime.js +144 -0
  137. package/dist/api/core/library/lsp/coreLsp.d.ts +367 -0
  138. package/dist/api/core/library/lsp/coreLsp.js +2076 -0
  139. package/dist/api/core/library/lsp/protocol.d.ts +25 -0
  140. package/dist/api/core/library/lsp/protocol.js +1 -0
  141. package/dist/api/core/library/lsp/serverManager.d.ts +37 -0
  142. package/dist/api/core/library/lsp/serverManager.js +427 -0
  143. package/dist/api/core/library/mcpServerProvider.d.ts +69 -0
  144. package/dist/api/core/library/mcpServerProvider.js +152 -0
  145. package/dist/api/core/library/mcpSettings.d.ts +20 -0
  146. package/dist/api/core/library/mcpSettings.js +131 -0
  147. package/dist/api/core/library/patternMatcher.d.ts +66 -0
  148. package/dist/api/core/library/patternMatcher.js +284 -0
  149. package/dist/api/core/library/regexEscape.d.ts +4 -0
  150. package/dist/api/core/library/regexEscape.js +4 -0
  151. package/dist/api/core/library/runtimeEventBus.d.ts +27 -0
  152. package/dist/api/core/library/runtimeEventBus.js +78 -0
  153. package/dist/api/core/library/safetyMiddleware.d.ts +98 -0
  154. package/dist/api/core/library/safetyMiddleware.js +215 -0
  155. package/dist/api/core/library/tauri.d.ts +71 -0
  156. package/dist/api/core/library/tauri.js +222 -0
  157. package/dist/api/core/library/tauriImport.d.ts +5 -0
  158. package/dist/api/core/library/tauriImport.js +7 -0
  159. package/dist/api/core/library/terminalAutoApproveSettings.d.ts +32 -0
  160. package/dist/api/core/library/terminalAutoApproveSettings.js +529 -0
  161. package/dist/api/core/library/workspaceIdentity.d.ts +10 -0
  162. package/dist/api/core/library/workspaceIdentity.js +49 -0
  163. package/dist/api/core/library/workspaceSummary.d.ts +19 -0
  164. package/dist/api/core/library/workspaceSummary.js +35 -0
  165. package/dist/api/core/skills/agent-customization/SKILL.md +27 -0
  166. package/dist/api/core/skills/bug-fix/SKILL.md +21 -0
  167. package/dist/api/core/skills/create-pr/SKILL.md +32 -0
  168. package/dist/api/core/skills/dev-server/SKILL.md +20 -0
  169. package/dist/api/core/skills/fix-suggestions/SKILL.md +21 -0
  170. package/dist/api/core/skills/github-search/SKILL.md +21 -0
  171. package/dist/api/core/skills/github-summary/SKILL.md +25 -0
  172. package/dist/api/core/skills/integration-tests/SKILL.md +29 -0
  173. package/dist/api/core/skills/pr-comments/SKILL.md +23 -0
  174. package/dist/api/core/skills/project-setup/SKILL.md +20 -0
  175. package/dist/api/core/skills/search-integration/SKILL.md +19 -0
  176. package/dist/api/core/skills/search-results/SKILL.md +19 -0
  177. package/dist/api/core/skills/typescript-upgrade/SKILL.md +21 -0
  178. package/dist/api/data/runStore.d.ts +46 -0
  179. package/dist/api/data/runStore.js +244 -0
  180. package/dist/api/helpers/agentUtils.d.ts +11 -0
  181. package/dist/api/helpers/agentUtils.js +23 -0
  182. package/dist/api/helpers/modelTokenLimits.d.ts +2 -0
  183. package/dist/api/helpers/modelTokenLimits.js +158 -0
  184. package/dist/api/helpers/observationalMemory.d.ts +10 -0
  185. package/dist/api/helpers/observationalMemory.js +36 -0
  186. package/dist/api/helpers/promptBudget.d.ts +30 -0
  187. package/dist/api/helpers/promptBudget.js +216 -0
  188. package/dist/api/helpers/resolveImageMessageParts.d.ts +9 -0
  189. package/dist/api/helpers/resolveImageMessageParts.js +140 -0
  190. package/dist/api/helpers/slashCommands.d.ts +31 -0
  191. package/dist/api/helpers/slashCommands.js +129 -0
  192. package/dist/api/helpers/tokenUsage.d.ts +19 -0
  193. package/dist/api/helpers/tokenUsage.js +81 -0
  194. package/dist/api/routes/fileRoutes.d.ts +2 -0
  195. package/dist/api/routes/fileRoutes.js +108 -0
  196. package/dist/api/routes/lspDocumentRoutes.d.ts +4 -0
  197. package/dist/api/routes/lspDocumentRoutes.js +84 -0
  198. package/dist/api/routes/lspHierarchyRoutes.d.ts +4 -0
  199. package/dist/api/routes/lspHierarchyRoutes.js +122 -0
  200. package/dist/api/routes/lspPositionRoutes.d.ts +4 -0
  201. package/dist/api/routes/lspPositionRoutes.js +263 -0
  202. package/dist/api/routes/lspQueryRoutes.d.ts +4 -0
  203. package/dist/api/routes/lspQueryRoutes.js +679 -0
  204. package/dist/api/routes/lspResolveRoutes.d.ts +4 -0
  205. package/dist/api/routes/lspResolveRoutes.js +143 -0
  206. package/dist/api/routes/lspSemanticDocumentRoutes.d.ts +4 -0
  207. package/dist/api/routes/lspSemanticDocumentRoutes.js +98 -0
  208. package/dist/api/routes/semanticRoutes.d.ts +2 -0
  209. package/dist/api/routes/semanticRoutes.js +126 -0
  210. package/dist/cli.d.ts +8 -0
  211. package/dist/cli.js +160 -0
  212. package/dist/index.d.ts +1 -0
  213. package/dist/index.js +1 -0
  214. package/dist/server.d.ts +2 -0
  215. package/dist/server.js +38 -0
  216. package/package.json +100 -0
@@ -0,0 +1,2076 @@
1
+ /**
2
+ * Core LSP manager for language intelligence.
3
+ * Connects to language servers via JSON-RPC 2.0 and delegates server startup
4
+ * to the shared server manager.
5
+ */
6
+ import { spawn } from "child_process";
7
+ import { EventEmitter } from "events";
8
+ import fs from "fs/promises";
9
+ import path from "path";
10
+ import { pathToFileURL } from "url";
11
+ import { serverManager } from "./serverManager.js";
12
+ class LSPClient extends EventEmitter {
13
+ process;
14
+ languageId;
15
+ rootPath;
16
+ messageId;
17
+ pendingRequests;
18
+ buffer;
19
+ initialized;
20
+ semanticLegend;
21
+ textDocumentSyncKind;
22
+ constructor(serverProcess, languageId, rootPath = null) {
23
+ super();
24
+ this.process = serverProcess;
25
+ this.languageId = languageId;
26
+ this.rootPath = rootPath;
27
+ this.messageId = 0;
28
+ this.pendingRequests = new Map();
29
+ this.buffer = Buffer.alloc(0);
30
+ this.initialized = false;
31
+ this.semanticLegend = null;
32
+ this.textDocumentSyncKind = 1;
33
+ this.setupProcessHandlers();
34
+ }
35
+ setupProcessHandlers() {
36
+ this.process.stdout.on("data", (data) => {
37
+ this.buffer = Buffer.concat([this.buffer, data]);
38
+ this.processBuffer();
39
+ });
40
+ this.process.stderr.on("data", (data) => {
41
+ console.error(`LSP ${this.languageId} stderr:`, data.toString());
42
+ });
43
+ this.process.on("exit", (code) => {
44
+ console.log(`LSP ${this.languageId} exited with code ${code}`);
45
+ this.rejectAllPendingRequests(new Error(`LSP ${this.languageId} exited`));
46
+ this.emit("exit", code);
47
+ });
48
+ this.process.on("error", (error) => {
49
+ console.error(`LSP ${this.languageId} process error:`, error);
50
+ this.rejectAllPendingRequests(error);
51
+ // Only emit the reserved EventEmitter "error" event when a listener exists,
52
+ // otherwise Node treats it as an unhandled exception and crashes the process.
53
+ if (this.listenerCount("error") > 0) {
54
+ this.emit("error", error);
55
+ }
56
+ });
57
+ }
58
+ rejectAllPendingRequests(error) {
59
+ for (const [id, handlers] of this.pendingRequests.entries()) {
60
+ this.pendingRequests.delete(id);
61
+ handlers.reject(error);
62
+ }
63
+ }
64
+ processBuffer() {
65
+ const headerDelimiter = Buffer.from("\r\n\r\n");
66
+ for (;;) {
67
+ const headerEnd = this.buffer.indexOf(headerDelimiter);
68
+ if (headerEnd === -1)
69
+ break;
70
+ const headers = this.buffer.subarray(0, headerEnd).toString("utf8");
71
+ const contentLengthMatch = headers.match(/Content-Length:\s*(\d+)/i);
72
+ if (!contentLengthMatch) {
73
+ console.error("No Content-Length header found");
74
+ this.buffer = this.buffer.subarray(headerEnd + headerDelimiter.length);
75
+ continue;
76
+ }
77
+ const contentLength = Number.parseInt(contentLengthMatch[1] || "0", 10);
78
+ const messageStart = headerEnd + headerDelimiter.length;
79
+ if (this.buffer.length < messageStart + contentLength) {
80
+ break;
81
+ }
82
+ const messageBytes = this.buffer.subarray(messageStart, messageStart + contentLength);
83
+ const messageStr = messageBytes.toString("utf8");
84
+ this.buffer = this.buffer.subarray(messageStart + contentLength);
85
+ try {
86
+ const message = JSON.parse(messageStr);
87
+ this.handleMessage(message);
88
+ }
89
+ catch (error) {
90
+ console.error("Failed to parse LSP message:", error);
91
+ }
92
+ }
93
+ }
94
+ handleMessage(message) {
95
+ if (message.id !== undefined && this.pendingRequests.has(message.id)) {
96
+ const handlers = this.pendingRequests.get(message.id);
97
+ if (!handlers) {
98
+ return;
99
+ }
100
+ const { resolve, reject } = handlers;
101
+ this.pendingRequests.delete(message.id);
102
+ if (message.error) {
103
+ reject(new Error(message.error.message || "LSP request failed"));
104
+ }
105
+ else {
106
+ resolve(message.result);
107
+ }
108
+ }
109
+ else if (message.method && message.id !== undefined) {
110
+ this.handleServerRequest(message);
111
+ }
112
+ else if (message.method) {
113
+ this.emit("notification", message);
114
+ }
115
+ }
116
+ writeMessage(payload) {
117
+ const messageStr = JSON.stringify(payload);
118
+ const messageBuffer = Buffer.from(messageStr, "utf8");
119
+ const headers = Buffer.from(`Content-Length: ${messageBuffer.byteLength}\r\n\r\n`, "utf8");
120
+ this.process.stdin.write(Buffer.concat([headers, messageBuffer]));
121
+ }
122
+ sendResponse(id, result) {
123
+ this.writeMessage({
124
+ jsonrpc: "2.0",
125
+ id,
126
+ result,
127
+ });
128
+ }
129
+ sendErrorResponse(id, code, message) {
130
+ this.writeMessage({
131
+ jsonrpc: "2.0",
132
+ id,
133
+ error: {
134
+ code,
135
+ message,
136
+ },
137
+ });
138
+ }
139
+ handleServerRequest(message) {
140
+ const method = message.method;
141
+ const id = message.id;
142
+ if (id === undefined || !method) {
143
+ return;
144
+ }
145
+ // Keep compatibility with common server requests even if the editor does not
146
+ // consume their results yet.
147
+ if (method === "workspace/applyEdit") {
148
+ this.sendResponse(id, { applied: false });
149
+ return;
150
+ }
151
+ if (method === "window/workDoneProgress/create") {
152
+ this.sendResponse(id, null);
153
+ return;
154
+ }
155
+ if (method === "workspace/configuration") {
156
+ const items = Array.isArray(message.params?.items)
157
+ ? message.params?.items
158
+ : [];
159
+ this.sendResponse(id, items.map(() => ({})));
160
+ return;
161
+ }
162
+ if (method === "client/registerCapability" || method === "client/unregisterCapability") {
163
+ this.sendResponse(id, null);
164
+ return;
165
+ }
166
+ if (method === "window/showMessageRequest") {
167
+ const actions = Array.isArray(message.params?.actions)
168
+ ? message.params?.actions
169
+ : [];
170
+ this.sendResponse(id, actions.find((action) => typeof action.title === "string") || null);
171
+ return;
172
+ }
173
+ if (method === "workspace/workspaceFolders") {
174
+ const rootUri = this.rootPath ? pathToFileURL(this.rootPath).toString() : undefined;
175
+ this.sendResponse(id, rootUri ? [{ uri: rootUri, name: "workspace" }] : []);
176
+ return;
177
+ }
178
+ if (method === "window/showDocument") {
179
+ this.sendResponse(id, { success: false });
180
+ return;
181
+ }
182
+ if (method === "workspace/diagnostic/refresh" ||
183
+ method === "workspace/codeLens/refresh" ||
184
+ method === "workspace/inlayHint/refresh" ||
185
+ method === "workspace/inlineValue/refresh" ||
186
+ method === "workspace/semanticTokens/refresh") {
187
+ this.sendResponse(id, null);
188
+ return;
189
+ }
190
+ this.sendErrorResponse(id, -32601, `Method not found: ${method}`);
191
+ }
192
+ sendRequest(method, params) {
193
+ return new Promise((resolve, reject) => {
194
+ const id = ++this.messageId;
195
+ this.writeMessage({
196
+ jsonrpc: "2.0",
197
+ id,
198
+ method,
199
+ params,
200
+ });
201
+ this.pendingRequests.set(id, { resolve, reject });
202
+ setTimeout(() => {
203
+ if (this.pendingRequests.has(id)) {
204
+ this.pendingRequests.delete(id);
205
+ reject(new Error("LSP request timeout"));
206
+ }
207
+ }, 30000);
208
+ });
209
+ }
210
+ sendNotification(method, params) {
211
+ this.writeMessage({
212
+ jsonrpc: "2.0",
213
+ method,
214
+ params,
215
+ });
216
+ }
217
+ async initialize(rootUri, capabilities = {}) {
218
+ const result = await this.sendRequest("initialize", {
219
+ processId: process.pid,
220
+ rootUri,
221
+ capabilities: {
222
+ window: { workDoneProgress: true },
223
+ textDocument: {
224
+ hover: { contentFormat: ["plaintext", "markdown"] },
225
+ completion: { completionItem: { snippetSupport: true } },
226
+ definition: { linkSupport: true },
227
+ references: {},
228
+ documentSymbol: {},
229
+ codeAction: {
230
+ codeActionLiteralSupport: {
231
+ codeActionKind: {
232
+ valueSet: ["quickfix", "refactor", "source"],
233
+ },
234
+ },
235
+ },
236
+ rename: { prepareSupport: true },
237
+ formatting: {},
238
+ rangeFormatting: {},
239
+ onTypeFormatting: {},
240
+ declaration: { linkSupport: true },
241
+ typeDefinition: { linkSupport: true },
242
+ implementation: { linkSupport: true },
243
+ documentHighlight: {},
244
+ moniker: {},
245
+ foldingRange: {},
246
+ selectionRange: {},
247
+ documentLink: {},
248
+ linkedEditingRange: {},
249
+ colorProvider: {},
250
+ callHierarchy: { dynamicRegistration: false },
251
+ typeHierarchy: { dynamicRegistration: false },
252
+ inlayHint: { dynamicRegistration: false },
253
+ inlineValue: { dynamicRegistration: false },
254
+ signatureHelp: {
255
+ signatureInformation: {
256
+ documentationFormat: ["plaintext", "markdown"],
257
+ },
258
+ },
259
+ semanticTokens: {
260
+ dynamicRegistration: false,
261
+ requests: { full: true, range: true, delta: true },
262
+ tokenTypes: [
263
+ "namespace",
264
+ "type",
265
+ "class",
266
+ "enum",
267
+ "interface",
268
+ "struct",
269
+ "typeParameter",
270
+ "parameter",
271
+ "variable",
272
+ "property",
273
+ "enumMember",
274
+ "event",
275
+ "function",
276
+ "method",
277
+ "macro",
278
+ "keyword",
279
+ "modifier",
280
+ "comment",
281
+ "string",
282
+ "number",
283
+ "regexp",
284
+ "operator",
285
+ "decorator",
286
+ ],
287
+ tokenModifiers: [
288
+ "declaration",
289
+ "definition",
290
+ "readonly",
291
+ "static",
292
+ "deprecated",
293
+ "abstract",
294
+ "async",
295
+ "modification",
296
+ "documentation",
297
+ "defaultLibrary",
298
+ "local",
299
+ ],
300
+ formats: ["relative"],
301
+ overlappingTokenSupport: false,
302
+ multilineTokenSupport: false,
303
+ },
304
+ ...capabilities,
305
+ },
306
+ workspace: {
307
+ configuration: true,
308
+ symbol: {},
309
+ executeCommand: {},
310
+ didChangeConfiguration: { dynamicRegistration: true },
311
+ didChangeWatchedFiles: { dynamicRegistration: true },
312
+ fileOperations: {
313
+ willCreate: true,
314
+ didCreate: true,
315
+ willRename: true,
316
+ didRename: true,
317
+ willDelete: true,
318
+ didDelete: true,
319
+ },
320
+ },
321
+ },
322
+ });
323
+ const capabilitiesResult = result;
324
+ const semanticProvider = capabilitiesResult?.capabilities?.semanticTokensProvider;
325
+ if (semanticProvider?.legend?.tokenTypes && semanticProvider?.legend?.tokenModifiers) {
326
+ this.semanticLegend = {
327
+ tokenTypes: semanticProvider.legend.tokenTypes,
328
+ tokenModifiers: semanticProvider.legend.tokenModifiers,
329
+ };
330
+ }
331
+ const textDocumentSync = capabilitiesResult?.capabilities?.textDocumentSync;
332
+ if (typeof textDocumentSync === "number") {
333
+ this.textDocumentSyncKind = textDocumentSync === 2 ? 2 : 1;
334
+ }
335
+ else if (typeof textDocumentSync?.change === "number") {
336
+ this.textDocumentSyncKind = textDocumentSync.change === 2 ? 2 : 1;
337
+ }
338
+ this.sendNotification("initialized", {});
339
+ this.initialized = true;
340
+ return result;
341
+ }
342
+ async openDocument(uri, languageId, version, text) {
343
+ this.sendNotification("textDocument/didOpen", {
344
+ textDocument: { uri, languageId, version, text },
345
+ });
346
+ }
347
+ async closeDocument(uri) {
348
+ this.sendNotification("textDocument/didClose", {
349
+ textDocument: { uri },
350
+ });
351
+ }
352
+ async changeDocument(uri, version, text, range) {
353
+ const contentChanges = this.textDocumentSyncKind === 2 && range
354
+ ? [{ range, text }]
355
+ : [{ text }];
356
+ this.sendNotification("textDocument/didChange", {
357
+ textDocument: { uri, version },
358
+ contentChanges,
359
+ });
360
+ }
361
+ async saveDocument(uri, text) {
362
+ this.sendNotification("textDocument/didSave", {
363
+ textDocument: { uri },
364
+ ...(typeof text === "string" ? { text } : {}),
365
+ });
366
+ }
367
+ willSaveDocument(uri, reason = 1) {
368
+ this.sendNotification("textDocument/willSave", {
369
+ textDocument: { uri },
370
+ reason,
371
+ });
372
+ }
373
+ async didChangeWorkspaceFolders(added, removed) {
374
+ this.sendNotification("workspace/didChangeWorkspaceFolders", {
375
+ event: { added, removed },
376
+ });
377
+ }
378
+ async didChangeConfiguration(settings) {
379
+ this.sendNotification("workspace/didChangeConfiguration", {
380
+ settings,
381
+ });
382
+ }
383
+ async didChangeWatchedFiles(changes) {
384
+ this.sendNotification("workspace/didChangeWatchedFiles", {
385
+ changes,
386
+ });
387
+ }
388
+ async willCreateFiles(files) {
389
+ return this.sendRequest("workspace/willCreateFiles", {
390
+ files,
391
+ });
392
+ }
393
+ async didCreateFiles(files) {
394
+ this.sendNotification("workspace/didCreateFiles", {
395
+ files,
396
+ });
397
+ }
398
+ async willRenameFiles(files) {
399
+ return this.sendRequest("workspace/willRenameFiles", {
400
+ files,
401
+ });
402
+ }
403
+ async didRenameFiles(files) {
404
+ this.sendNotification("workspace/didRenameFiles", {
405
+ files,
406
+ });
407
+ }
408
+ async willDeleteFiles(files) {
409
+ return this.sendRequest("workspace/willDeleteFiles", {
410
+ files,
411
+ });
412
+ }
413
+ async didDeleteFiles(files) {
414
+ this.sendNotification("workspace/didDeleteFiles", {
415
+ files,
416
+ });
417
+ }
418
+ async getHover(uri, position) {
419
+ return this.sendRequest("textDocument/hover", {
420
+ textDocument: { uri },
421
+ position,
422
+ });
423
+ }
424
+ async getDefinition(uri, position) {
425
+ return this.sendRequest("textDocument/definition", {
426
+ textDocument: { uri },
427
+ position,
428
+ });
429
+ }
430
+ async getDeclaration(uri, position) {
431
+ return this.sendRequest("textDocument/declaration", {
432
+ textDocument: { uri },
433
+ position,
434
+ });
435
+ }
436
+ async getTypeDefinition(uri, position) {
437
+ return this.sendRequest("textDocument/typeDefinition", {
438
+ textDocument: { uri },
439
+ position,
440
+ });
441
+ }
442
+ async getImplementation(uri, position) {
443
+ return this.sendRequest("textDocument/implementation", {
444
+ textDocument: { uri },
445
+ position,
446
+ });
447
+ }
448
+ async getReferences(uri, position, includeDeclaration = true) {
449
+ return this.sendRequest("textDocument/references", {
450
+ textDocument: { uri },
451
+ position,
452
+ context: { includeDeclaration },
453
+ });
454
+ }
455
+ async getDocumentHighlight(uri, position) {
456
+ return this.sendRequest("textDocument/documentHighlight", {
457
+ textDocument: { uri },
458
+ position,
459
+ });
460
+ }
461
+ async getMoniker(uri, position) {
462
+ return this.sendRequest("textDocument/moniker", {
463
+ textDocument: { uri },
464
+ position,
465
+ });
466
+ }
467
+ async getCompletion(uri, position) {
468
+ return this.sendRequest("textDocument/completion", {
469
+ textDocument: { uri },
470
+ position,
471
+ });
472
+ }
473
+ async resolveCompletionItem(item) {
474
+ return this.sendRequest("completionItem/resolve", item);
475
+ }
476
+ async getDocumentSymbols(uri) {
477
+ return this.sendRequest("textDocument/documentSymbol", {
478
+ textDocument: { uri },
479
+ });
480
+ }
481
+ async getFoldingRange(uri) {
482
+ return this.sendRequest("textDocument/foldingRange", {
483
+ textDocument: { uri },
484
+ });
485
+ }
486
+ async getSelectionRange(uri, positions) {
487
+ return this.sendRequest("textDocument/selectionRange", {
488
+ textDocument: { uri },
489
+ positions,
490
+ });
491
+ }
492
+ async getDocumentLinks(uri) {
493
+ return this.sendRequest("textDocument/documentLink", {
494
+ textDocument: { uri },
495
+ });
496
+ }
497
+ async resolveDocumentLink(link) {
498
+ return this.sendRequest("documentLink/resolve", link);
499
+ }
500
+ async getCodeLens(uri) {
501
+ return this.sendRequest("textDocument/codeLens", {
502
+ textDocument: { uri },
503
+ });
504
+ }
505
+ async resolveCodeLens(codeLens) {
506
+ return this.sendRequest("codeLens/resolve", codeLens);
507
+ }
508
+ async getLinkedEditingRange(uri, position) {
509
+ return this.sendRequest("textDocument/linkedEditingRange", {
510
+ textDocument: { uri },
511
+ position,
512
+ });
513
+ }
514
+ async getDocumentColors(uri) {
515
+ return this.sendRequest("textDocument/documentColor", {
516
+ textDocument: { uri },
517
+ });
518
+ }
519
+ async getColorPresentations(uri, color, range) {
520
+ return this.sendRequest("textDocument/colorPresentation", {
521
+ textDocument: { uri },
522
+ color,
523
+ range,
524
+ });
525
+ }
526
+ async prepareCallHierarchy(uri, position) {
527
+ return this.sendRequest("textDocument/prepareCallHierarchy", {
528
+ textDocument: { uri },
529
+ position,
530
+ });
531
+ }
532
+ async prepareTypeHierarchy(uri, position) {
533
+ return this.sendRequest("textDocument/prepareTypeHierarchy", {
534
+ textDocument: { uri },
535
+ position,
536
+ });
537
+ }
538
+ async getIncomingCalls(item) {
539
+ return this.sendRequest("callHierarchy/incomingCalls", {
540
+ item,
541
+ });
542
+ }
543
+ async getOutgoingCalls(item) {
544
+ return this.sendRequest("callHierarchy/outgoingCalls", {
545
+ item,
546
+ });
547
+ }
548
+ async getTypeHierarchySupertypes(item) {
549
+ return this.sendRequest("typeHierarchy/supertypes", {
550
+ item,
551
+ });
552
+ }
553
+ async getTypeHierarchySubtypes(item) {
554
+ return this.sendRequest("typeHierarchy/subtypes", {
555
+ item,
556
+ });
557
+ }
558
+ async getInlayHints(uri, range) {
559
+ return this.sendRequest("textDocument/inlayHint", {
560
+ textDocument: { uri },
561
+ range,
562
+ });
563
+ }
564
+ async getInlineValues(uri, range, context = {}) {
565
+ return this.sendRequest("textDocument/inlineValue", {
566
+ textDocument: { uri },
567
+ range,
568
+ context,
569
+ });
570
+ }
571
+ async getInlineCompletion(uri, position, context = {}) {
572
+ return this.sendRequest("textDocument/inlineCompletion", {
573
+ textDocument: { uri },
574
+ position,
575
+ context,
576
+ });
577
+ }
578
+ async resolveInlineCompletionItem(item) {
579
+ return this.sendRequest("inlineCompletionItem/resolve", item);
580
+ }
581
+ async getSemanticTokens(uri) {
582
+ return this.sendRequest("textDocument/semanticTokens/full", {
583
+ textDocument: { uri },
584
+ });
585
+ }
586
+ async getSemanticTokensRange(uri, range) {
587
+ return this.sendRequest("textDocument/semanticTokens/range", {
588
+ textDocument: { uri },
589
+ range,
590
+ });
591
+ }
592
+ async getSemanticTokensDelta(uri, previousResultId) {
593
+ return this.sendRequest("textDocument/semanticTokens/full/delta", {
594
+ textDocument: { uri },
595
+ previousResultId,
596
+ });
597
+ }
598
+ async getCodeActions(uri, range, context = {}) {
599
+ return this.sendRequest("textDocument/codeAction", {
600
+ textDocument: { uri },
601
+ range,
602
+ context,
603
+ });
604
+ }
605
+ async resolveCodeAction(action) {
606
+ return this.sendRequest("codeAction/resolve", action);
607
+ }
608
+ async rename(uri, position, newName) {
609
+ return this.sendRequest("textDocument/rename", {
610
+ textDocument: { uri },
611
+ position,
612
+ newName,
613
+ });
614
+ }
615
+ async prepareRename(uri, position) {
616
+ return this.sendRequest("textDocument/prepareRename", {
617
+ textDocument: { uri },
618
+ position,
619
+ });
620
+ }
621
+ async formatting(uri, options) {
622
+ return this.sendRequest("textDocument/formatting", {
623
+ textDocument: { uri },
624
+ options,
625
+ });
626
+ }
627
+ async rangeFormatting(uri, range, options) {
628
+ return this.sendRequest("textDocument/rangeFormatting", {
629
+ textDocument: { uri },
630
+ range,
631
+ options,
632
+ });
633
+ }
634
+ async onTypeFormatting(uri, position, ch, options) {
635
+ return this.sendRequest("textDocument/onTypeFormatting", {
636
+ textDocument: { uri },
637
+ position,
638
+ ch,
639
+ options,
640
+ });
641
+ }
642
+ async workspaceSymbol(query) {
643
+ return this.sendRequest("workspace/symbol", {
644
+ query,
645
+ });
646
+ }
647
+ async resolveWorkspaceSymbol(symbol) {
648
+ return this.sendRequest("workspaceSymbol/resolve", symbol);
649
+ }
650
+ async resolveInlayHint(hint) {
651
+ return this.sendRequest("inlayHint/resolve", hint);
652
+ }
653
+ async willSaveWaitUntil(uri, reason = 1) {
654
+ return this.sendRequest("textDocument/willSaveWaitUntil", {
655
+ textDocument: { uri },
656
+ reason,
657
+ });
658
+ }
659
+ async executeCommand(command, argumentsList = []) {
660
+ return this.sendRequest("workspace/executeCommand", {
661
+ command,
662
+ arguments: argumentsList,
663
+ });
664
+ }
665
+ async signatureHelp(uri, position) {
666
+ return this.sendRequest("textDocument/signatureHelp", {
667
+ textDocument: { uri },
668
+ position,
669
+ });
670
+ }
671
+ async shutdown() {
672
+ await this.sendRequest("shutdown", {});
673
+ this.sendNotification("exit", {});
674
+ }
675
+ kill() {
676
+ this.process.kill();
677
+ }
678
+ }
679
+ class CoreLsp extends EventEmitter {
680
+ servers;
681
+ // In-flight spawns, keyed by languageId. Prevents concurrent callers (e.g. the
682
+ // semantic-tokens route firing while a file is opened) from each starting a
683
+ // separate language server during cold start.
684
+ pendingSpawns;
685
+ // Languages whose server failed to start (missing binary, bad config). Cleared
686
+ // on workspace-root change so a newly installed server is picked up.
687
+ unavailableLanguages;
688
+ // Incremented on every workspace-root change. A spawn captures the generation
689
+ // it started under; if that no longer matches when it completes, the spawn
690
+ // belongs to a stale root and its client must be discarded rather than
691
+ // registered — clearing `pendingSpawns` alone cannot cancel work already
692
+ // in flight.
693
+ spawnGeneration;
694
+ rootPath;
695
+ documentVersions;
696
+ openDocumentLanguages;
697
+ lastDocumentText;
698
+ diagnosticsByUri;
699
+ documentDiagnosticResultIds;
700
+ workspaceDiagnosticResultIds;
701
+ semanticTokenSnapshots;
702
+ constructor() {
703
+ super();
704
+ this.servers = new Map();
705
+ this.pendingSpawns = new Map();
706
+ this.unavailableLanguages = new Set();
707
+ this.spawnGeneration = 0;
708
+ this.rootPath = null;
709
+ this.documentVersions = new Map();
710
+ this.openDocumentLanguages = new Map();
711
+ this.lastDocumentText = new Map();
712
+ this.diagnosticsByUri = new Map();
713
+ this.documentDiagnosticResultIds = new Map();
714
+ this.workspaceDiagnosticResultIds = new Map();
715
+ this.semanticTokenSnapshots = new Map();
716
+ }
717
+ connect(rootPath) {
718
+ const nextRootPath = rootPath ? path.resolve(rootPath) : null;
719
+ const currentRootPath = this.rootPath ? path.resolve(this.rootPath) : null;
720
+ if (currentRootPath && currentRootPath !== nextRootPath) {
721
+ this.resetServersForRootChange();
722
+ }
723
+ this.rootPath = nextRootPath;
724
+ serverManager.setWorkspaceRoot(nextRootPath);
725
+ }
726
+ setRootPath(rootPath) {
727
+ this.connect(rootPath);
728
+ }
729
+ resetServersForRootChange() {
730
+ // Invalidate any spawn already in flight for the previous root. Clearing
731
+ // `pendingSpawns` only drops our handle on those promises; they keep
732
+ // running and would otherwise register a server against the old root.
733
+ this.spawnGeneration += 1;
734
+ this.servers.forEach((client) => {
735
+ try {
736
+ client.kill();
737
+ }
738
+ catch {
739
+ // Ignore process cleanup failures while switching workspaces.
740
+ }
741
+ });
742
+ this.servers.clear();
743
+ this.pendingSpawns.clear();
744
+ this.unavailableLanguages.clear();
745
+ this.documentVersions.clear();
746
+ this.openDocumentLanguages.clear();
747
+ this.lastDocumentText.clear();
748
+ this.diagnosticsByUri.clear();
749
+ this.documentDiagnosticResultIds.clear();
750
+ this.workspaceDiagnosticResultIds.clear();
751
+ this.semanticTokenSnapshots.clear();
752
+ }
753
+ getLanguageId(filePath) {
754
+ const ext = path.extname(filePath).toLowerCase();
755
+ const languageMap = {
756
+ ".js": "javascript",
757
+ ".jsx": "javascriptreact",
758
+ ".ts": "typescript",
759
+ ".tsx": "typescriptreact",
760
+ ".py": "python",
761
+ ".java": "java",
762
+ ".cpp": "cpp",
763
+ ".c": "c",
764
+ ".go": "go",
765
+ ".rs": "rust",
766
+ ".gd": "gdscript",
767
+ ".gdshader": "gdscript",
768
+ ".rb": "ruby",
769
+ ".php": "php",
770
+ };
771
+ return languageMap[ext] || null;
772
+ }
773
+ async spawnServer(languageId) {
774
+ if (this.servers.has(languageId)) {
775
+ return this.servers.get(languageId) ?? null;
776
+ }
777
+ // A previous spawn already failed for this language (e.g. the language
778
+ // server binary is not installed in this workspace). Retrying cannot
779
+ // succeed until the workspace root changes, and without this guard every
780
+ // request re-attempts the spawn and floods stderr.
781
+ if (this.unavailableLanguages.has(languageId)) {
782
+ return null;
783
+ }
784
+ const pending = this.pendingSpawns.get(languageId);
785
+ if (pending) {
786
+ return pending;
787
+ }
788
+ const generation = this.spawnGeneration;
789
+ const spawnPromise = this.spawnServerUncached(languageId, generation)
790
+ .then((client) => {
791
+ // The workspace root changed while this spawn was in flight, so the
792
+ // result belongs to a root we no longer track. Tear it down instead of
793
+ // recording it against the current workspace.
794
+ if (generation !== this.spawnGeneration) {
795
+ if (client) {
796
+ try {
797
+ client.kill();
798
+ }
799
+ catch {
800
+ // Ignore cleanup failures for an already-discarded client.
801
+ }
802
+ }
803
+ return null;
804
+ }
805
+ if (!client) {
806
+ this.unavailableLanguages.add(languageId);
807
+ }
808
+ return client;
809
+ })
810
+ .catch((error) => {
811
+ if (generation === this.spawnGeneration) {
812
+ this.unavailableLanguages.add(languageId);
813
+ }
814
+ throw error;
815
+ })
816
+ .finally(() => {
817
+ // Only retract the entry we installed; a newer generation may have
818
+ // already stored its own pending spawn under this language.
819
+ if (this.pendingSpawns.get(languageId) === spawnPromise) {
820
+ this.pendingSpawns.delete(languageId);
821
+ }
822
+ });
823
+ this.pendingSpawns.set(languageId, spawnPromise);
824
+ return spawnPromise;
825
+ }
826
+ isLanguageUnavailable(languageId) {
827
+ return this.unavailableLanguages.has(languageId);
828
+ }
829
+ async spawnServerUncached(languageId, generation) {
830
+ try {
831
+ const launchSpec = await serverManager.resolveLaunchSpec(languageId);
832
+ if (!launchSpec) {
833
+ console.warn(`No LSP server configured for language: ${languageId}`);
834
+ return null;
835
+ }
836
+ // `resolveLaunchSpec` awaits, so the root may have changed already. Bail
837
+ // out before spawning a process that would target the stale workspace.
838
+ if (generation !== this.spawnGeneration) {
839
+ return null;
840
+ }
841
+ const serverProcess = spawn(launchSpec.command, launchSpec.args, {
842
+ cwd: launchSpec.cwd || this.rootPath || process.cwd(),
843
+ stdio: ["pipe", "pipe", "pipe"],
844
+ });
845
+ const client = new LSPClient(serverProcess, languageId, this.rootPath || process.cwd());
846
+ const rootUri = pathToFileURL(this.rootPath || process.cwd()).toString();
847
+ await client.initialize(rootUri);
848
+ await serverManager.recordServerStarted(languageId, launchSpec.sha256);
849
+ // Initialization and bookkeeping are the long poles here; re-check
850
+ // immediately before publishing so a root change during either await
851
+ // cannot leave a server registered against the previous workspace.
852
+ if (generation !== this.spawnGeneration) {
853
+ try {
854
+ client.kill();
855
+ }
856
+ catch {
857
+ // Ignore cleanup failures for a client we are discarding.
858
+ }
859
+ return null;
860
+ }
861
+ client.on("notification", (message) => {
862
+ if (message.method !== "textDocument/publishDiagnostics") {
863
+ return;
864
+ }
865
+ const payload = message.params;
866
+ if (!payload?.uri) {
867
+ return;
868
+ }
869
+ const diagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
870
+ this.diagnosticsByUri.set(payload.uri, diagnostics);
871
+ this.emit("diagnostics", {
872
+ languageId,
873
+ uri: payload.uri,
874
+ diagnostics,
875
+ });
876
+ });
877
+ this.servers.set(languageId, client);
878
+ client.on("exit", () => {
879
+ void serverManager.recordServerStopped(languageId, launchSpec.sha256);
880
+ });
881
+ return client;
882
+ }
883
+ catch (error) {
884
+ const err = error;
885
+ console.error(`Failed to start LSP server for ${languageId}:`, err.message);
886
+ return null;
887
+ }
888
+ }
889
+ async getClientForFile(filePath) {
890
+ const languageId = this.getLanguageId(filePath);
891
+ if (!languageId) {
892
+ return null;
893
+ }
894
+ let client = this.servers.get(languageId) ?? null;
895
+ if (!client) {
896
+ client = await this.spawnServer(languageId);
897
+ }
898
+ return client;
899
+ }
900
+ async getClientForLanguage(languageId) {
901
+ let client = this.servers.get(languageId) ?? null;
902
+ if (!client) {
903
+ client = await this.spawnServer(languageId);
904
+ }
905
+ return client;
906
+ }
907
+ async ensureDocumentSynced(filePath, text) {
908
+ const languageId = this.getLanguageId(filePath);
909
+ if (!languageId) {
910
+ return null;
911
+ }
912
+ const client = await this.getClientForLanguage(languageId);
913
+ if (!client || !client.initialized) {
914
+ return null;
915
+ }
916
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
917
+ let currentText = text;
918
+ if (typeof currentText !== "string") {
919
+ try {
920
+ currentText = await fs.readFile(filePath, "utf8");
921
+ }
922
+ catch {
923
+ return null;
924
+ }
925
+ }
926
+ const currentVersion = this.documentVersions.get(uri) ?? 0;
927
+ const previousText = this.lastDocumentText.get(uri);
928
+ if (currentVersion === 0) {
929
+ await client.openDocument(uri, languageId, 1, currentText);
930
+ this.documentVersions.set(uri, 1);
931
+ this.openDocumentLanguages.set(uri, languageId);
932
+ this.lastDocumentText.set(uri, currentText);
933
+ return { client, uri, languageId };
934
+ }
935
+ if (previousText !== currentText) {
936
+ const nextVersion = currentVersion + 1;
937
+ await client.changeDocument(uri, nextVersion, currentText);
938
+ this.documentVersions.set(uri, nextVersion);
939
+ this.lastDocumentText.set(uri, currentText);
940
+ }
941
+ return { client, uri, languageId };
942
+ }
943
+ async openDocument(filePath, text) {
944
+ const languageId = this.getLanguageId(filePath);
945
+ if (!languageId) {
946
+ return false;
947
+ }
948
+ const client = await this.getClientForLanguage(languageId);
949
+ if (!client || !client.initialized) {
950
+ return false;
951
+ }
952
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
953
+ let docText = text;
954
+ if (typeof docText !== "string") {
955
+ docText = await fs.readFile(filePath, "utf8");
956
+ }
957
+ await client.openDocument(uri, languageId, 1, docText);
958
+ this.documentVersions.set(uri, 1);
959
+ this.openDocumentLanguages.set(uri, languageId);
960
+ this.lastDocumentText.set(uri, docText);
961
+ return true;
962
+ }
963
+ async changeDocument(filePath, text) {
964
+ const synced = await this.ensureDocumentSynced(filePath);
965
+ if (!synced) {
966
+ return false;
967
+ }
968
+ const currentVersion = this.documentVersions.get(synced.uri) ?? 1;
969
+ const nextVersion = currentVersion + 1;
970
+ await synced.client.changeDocument(synced.uri, nextVersion, text);
971
+ this.documentVersions.set(synced.uri, nextVersion);
972
+ this.lastDocumentText.set(synced.uri, text);
973
+ return true;
974
+ }
975
+ async saveDocument(filePath) {
976
+ const synced = await this.ensureDocumentSynced(filePath);
977
+ if (!synced) {
978
+ return false;
979
+ }
980
+ const text = this.lastDocumentText.get(synced.uri);
981
+ await synced.client.saveDocument(synced.uri, text);
982
+ return true;
983
+ }
984
+ async closeDocument(filePath) {
985
+ const languageId = this.getLanguageId(filePath);
986
+ if (!languageId) {
987
+ return false;
988
+ }
989
+ const client = await this.getClientForLanguage(languageId);
990
+ if (!client || !client.initialized) {
991
+ return false;
992
+ }
993
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
994
+ await client.closeDocument(uri);
995
+ this.documentVersions.delete(uri);
996
+ this.openDocumentLanguages.delete(uri);
997
+ this.lastDocumentText.delete(uri);
998
+ this.diagnosticsByUri.delete(uri);
999
+ return true;
1000
+ }
1001
+ async didChangeConfiguration(settings) {
1002
+ const calls = Array.from(this.servers.values()).map((client) => client.didChangeConfiguration(settings));
1003
+ await Promise.all(calls);
1004
+ }
1005
+ willSaveDocument(filePath, reason = 1) {
1006
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
1007
+ for (const client of this.servers.values()) {
1008
+ if (client.initialized && this.openDocumentLanguages.has(uri)) {
1009
+ client.willSaveDocument(uri, reason);
1010
+ }
1011
+ }
1012
+ }
1013
+ async didChangeWorkspaceFolders(added, removed) {
1014
+ const calls = Array.from(this.servers.values()).map((client) => client.didChangeWorkspaceFolders(added, removed));
1015
+ await Promise.all(calls);
1016
+ }
1017
+ async didChangeWatchedFiles(changes) {
1018
+ const byLanguage = new Map();
1019
+ for (const change of changes) {
1020
+ const languageId = this.getLanguageId(change.filePath);
1021
+ if (!languageId) {
1022
+ continue;
1023
+ }
1024
+ if (!byLanguage.has(languageId)) {
1025
+ byLanguage.set(languageId, []);
1026
+ }
1027
+ byLanguage.get(languageId)?.push({
1028
+ uri: pathToFileURL(path.resolve(change.filePath)).toString(),
1029
+ type: change.type,
1030
+ });
1031
+ }
1032
+ const calls = [];
1033
+ for (const [languageId, languageChanges] of byLanguage.entries()) {
1034
+ const client = await this.getClientForLanguage(languageId);
1035
+ if (!client || !client.initialized || languageChanges.length === 0) {
1036
+ continue;
1037
+ }
1038
+ calls.push(client.didChangeWatchedFiles(languageChanges));
1039
+ }
1040
+ await Promise.all(calls);
1041
+ }
1042
+ async willCreateFiles(filePaths) {
1043
+ const byLanguage = new Map();
1044
+ for (const filePath of filePaths) {
1045
+ const languageId = this.getLanguageId(filePath);
1046
+ if (!languageId) {
1047
+ continue;
1048
+ }
1049
+ if (!byLanguage.has(languageId)) {
1050
+ byLanguage.set(languageId, []);
1051
+ }
1052
+ byLanguage.get(languageId)?.push({
1053
+ uri: pathToFileURL(path.resolve(filePath)).toString(),
1054
+ });
1055
+ }
1056
+ const results = [];
1057
+ for (const [languageId, files] of byLanguage.entries()) {
1058
+ const client = await this.getClientForLanguage(languageId);
1059
+ if (!client || !client.initialized || files.length === 0) {
1060
+ continue;
1061
+ }
1062
+ results.push(await client.willCreateFiles(files));
1063
+ }
1064
+ return results;
1065
+ }
1066
+ async didCreateFiles(filePaths) {
1067
+ const byLanguage = new Map();
1068
+ for (const filePath of filePaths) {
1069
+ const languageId = this.getLanguageId(filePath);
1070
+ if (!languageId) {
1071
+ continue;
1072
+ }
1073
+ if (!byLanguage.has(languageId)) {
1074
+ byLanguage.set(languageId, []);
1075
+ }
1076
+ byLanguage.get(languageId)?.push({
1077
+ uri: pathToFileURL(path.resolve(filePath)).toString(),
1078
+ });
1079
+ }
1080
+ const calls = [];
1081
+ for (const [languageId, files] of byLanguage.entries()) {
1082
+ const client = await this.getClientForLanguage(languageId);
1083
+ if (!client || !client.initialized || files.length === 0) {
1084
+ continue;
1085
+ }
1086
+ calls.push(client.didCreateFiles(files));
1087
+ }
1088
+ await Promise.all(calls);
1089
+ }
1090
+ async willRenameFiles(changes) {
1091
+ const byLanguage = new Map();
1092
+ for (const change of changes) {
1093
+ const languageId = this.getLanguageId(change.newFilePath) || this.getLanguageId(change.oldFilePath);
1094
+ if (!languageId) {
1095
+ continue;
1096
+ }
1097
+ if (!byLanguage.has(languageId)) {
1098
+ byLanguage.set(languageId, []);
1099
+ }
1100
+ byLanguage.get(languageId)?.push({
1101
+ oldUri: pathToFileURL(path.resolve(change.oldFilePath)).toString(),
1102
+ newUri: pathToFileURL(path.resolve(change.newFilePath)).toString(),
1103
+ });
1104
+ }
1105
+ const results = [];
1106
+ for (const [languageId, files] of byLanguage.entries()) {
1107
+ const client = await this.getClientForLanguage(languageId);
1108
+ if (!client || !client.initialized || files.length === 0) {
1109
+ continue;
1110
+ }
1111
+ results.push(await client.willRenameFiles(files));
1112
+ }
1113
+ return results;
1114
+ }
1115
+ async didRenameFiles(changes) {
1116
+ const byLanguage = new Map();
1117
+ for (const change of changes) {
1118
+ const languageId = this.getLanguageId(change.newFilePath) || this.getLanguageId(change.oldFilePath);
1119
+ if (!languageId) {
1120
+ continue;
1121
+ }
1122
+ if (!byLanguage.has(languageId)) {
1123
+ byLanguage.set(languageId, []);
1124
+ }
1125
+ byLanguage.get(languageId)?.push({
1126
+ oldUri: pathToFileURL(path.resolve(change.oldFilePath)).toString(),
1127
+ newUri: pathToFileURL(path.resolve(change.newFilePath)).toString(),
1128
+ });
1129
+ }
1130
+ const calls = [];
1131
+ for (const [languageId, files] of byLanguage.entries()) {
1132
+ const client = await this.getClientForLanguage(languageId);
1133
+ if (!client || !client.initialized || files.length === 0) {
1134
+ continue;
1135
+ }
1136
+ calls.push(client.didRenameFiles(files));
1137
+ }
1138
+ await Promise.all(calls);
1139
+ }
1140
+ async willDeleteFiles(filePaths) {
1141
+ const byLanguage = new Map();
1142
+ for (const filePath of filePaths) {
1143
+ const languageId = this.getLanguageId(filePath);
1144
+ if (!languageId) {
1145
+ continue;
1146
+ }
1147
+ if (!byLanguage.has(languageId)) {
1148
+ byLanguage.set(languageId, []);
1149
+ }
1150
+ byLanguage.get(languageId)?.push({
1151
+ uri: pathToFileURL(path.resolve(filePath)).toString(),
1152
+ });
1153
+ }
1154
+ const results = [];
1155
+ for (const [languageId, files] of byLanguage.entries()) {
1156
+ const client = await this.getClientForLanguage(languageId);
1157
+ if (!client || !client.initialized || files.length === 0) {
1158
+ continue;
1159
+ }
1160
+ results.push(await client.willDeleteFiles(files));
1161
+ }
1162
+ return results;
1163
+ }
1164
+ async didDeleteFiles(filePaths) {
1165
+ const byLanguage = new Map();
1166
+ for (const filePath of filePaths) {
1167
+ const languageId = this.getLanguageId(filePath);
1168
+ if (!languageId) {
1169
+ continue;
1170
+ }
1171
+ if (!byLanguage.has(languageId)) {
1172
+ byLanguage.set(languageId, []);
1173
+ }
1174
+ byLanguage.get(languageId)?.push({
1175
+ uri: pathToFileURL(path.resolve(filePath)).toString(),
1176
+ });
1177
+ }
1178
+ const calls = [];
1179
+ for (const [languageId, files] of byLanguage.entries()) {
1180
+ const client = await this.getClientForLanguage(languageId);
1181
+ if (!client || !client.initialized || files.length === 0) {
1182
+ continue;
1183
+ }
1184
+ calls.push(client.didDeleteFiles(files));
1185
+ }
1186
+ await Promise.all(calls);
1187
+ }
1188
+ onDiagnostics(listener) {
1189
+ this.on("diagnostics", listener);
1190
+ return () => {
1191
+ this.off("diagnostics", listener);
1192
+ };
1193
+ }
1194
+ getDiagnosticsForFile(filePath) {
1195
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
1196
+ return this.diagnosticsByUri.get(uri) ?? [];
1197
+ }
1198
+ async getPullDiagnosticsForFile(filePath) {
1199
+ const synced = await this.ensureDocumentSynced(filePath);
1200
+ if (!synced) {
1201
+ return null;
1202
+ }
1203
+ const previousResultId = this.documentDiagnosticResultIds.get(synced.uri);
1204
+ try {
1205
+ const result = await synced.client.sendRequest("textDocument/diagnostic", {
1206
+ textDocument: { uri: synced.uri },
1207
+ previousResultId,
1208
+ });
1209
+ if (!result || (result.kind !== "full" && result.kind !== "unchanged")) {
1210
+ return null;
1211
+ }
1212
+ if (typeof result.resultId === "string") {
1213
+ this.documentDiagnosticResultIds.set(synced.uri, result.resultId);
1214
+ }
1215
+ if (Array.isArray(result.items)) {
1216
+ this.diagnosticsByUri.set(synced.uri, result.items);
1217
+ }
1218
+ return {
1219
+ kind: result.kind || "full",
1220
+ resultId: typeof result.resultId === "string" ? result.resultId : null,
1221
+ items: Array.isArray(result.items) ? result.items : [],
1222
+ };
1223
+ }
1224
+ catch (error) {
1225
+ console.error("LSP pull diagnostics (document) failed:", error.message);
1226
+ return null;
1227
+ }
1228
+ }
1229
+ async getWorkspacePullDiagnostics(languageId) {
1230
+ const resolvedLanguageId = typeof languageId === "string" && languageId.trim().length > 0
1231
+ ? languageId
1232
+ : this.openDocumentLanguages.values().next().value;
1233
+ if (!resolvedLanguageId) {
1234
+ return null;
1235
+ }
1236
+ const client = await this.getClientForLanguage(resolvedLanguageId);
1237
+ if (!client || !client.initialized) {
1238
+ return null;
1239
+ }
1240
+ const previousResultIds = Array.from(this.workspaceDiagnosticResultIds.entries()).map(([uri, resultId]) => ({ uri, value: resultId }));
1241
+ try {
1242
+ const result = await client.sendRequest("workspace/diagnostic", {
1243
+ previousResultIds,
1244
+ });
1245
+ if (!result || !Array.isArray(result.items)) {
1246
+ return null;
1247
+ }
1248
+ result.items.forEach((item) => {
1249
+ if (typeof item?.uri !== "string") {
1250
+ return;
1251
+ }
1252
+ if (typeof item.resultId === "string") {
1253
+ this.workspaceDiagnosticResultIds.set(item.uri, item.resultId);
1254
+ }
1255
+ if (Array.isArray(item.items)) {
1256
+ this.diagnosticsByUri.set(item.uri, item.items);
1257
+ }
1258
+ });
1259
+ return result;
1260
+ }
1261
+ catch (error) {
1262
+ console.error("LSP pull diagnostics (workspace) failed:", error.message);
1263
+ return null;
1264
+ }
1265
+ }
1266
+ toPosition(line, character) {
1267
+ return {
1268
+ line: line - 1,
1269
+ character: character - 1,
1270
+ };
1271
+ }
1272
+ async getHover(filePath, line, character) {
1273
+ const synced = await this.ensureDocumentSynced(filePath);
1274
+ if (!synced) {
1275
+ return null;
1276
+ }
1277
+ try {
1278
+ return await synced.client.getHover(synced.uri, this.toPosition(line, character));
1279
+ }
1280
+ catch (error) {
1281
+ console.error("LSP hover failed:", error.message);
1282
+ return null;
1283
+ }
1284
+ }
1285
+ async getDefinition(filePath, line, character, text) {
1286
+ const synced = await this.ensureDocumentSynced(filePath, text);
1287
+ if (!synced) {
1288
+ return null;
1289
+ }
1290
+ try {
1291
+ return await synced.client.getDefinition(synced.uri, this.toPosition(line, character));
1292
+ }
1293
+ catch (error) {
1294
+ console.error("LSP definition failed:", error.message);
1295
+ return null;
1296
+ }
1297
+ }
1298
+ async getDeclaration(filePath, line, character, text) {
1299
+ const synced = await this.ensureDocumentSynced(filePath, text);
1300
+ if (!synced) {
1301
+ return null;
1302
+ }
1303
+ try {
1304
+ return await synced.client.getDeclaration(synced.uri, this.toPosition(line, character));
1305
+ }
1306
+ catch (error) {
1307
+ const err = error;
1308
+ const message = err.message || "";
1309
+ if (/Unhandled method|Method not found|not supported/i.test(message)) {
1310
+ try {
1311
+ const fallbackDefinition = await synced.client.getDefinition(synced.uri, this.toPosition(line, character));
1312
+ if (fallbackDefinition) {
1313
+ return fallbackDefinition;
1314
+ }
1315
+ return await synced.client.getTypeDefinition(synced.uri, this.toPosition(line, character));
1316
+ }
1317
+ catch (fallbackError) {
1318
+ console.error("LSP declaration fallback failed:", fallbackError.message);
1319
+ return null;
1320
+ }
1321
+ }
1322
+ console.error("LSP declaration failed:", message);
1323
+ return null;
1324
+ }
1325
+ }
1326
+ async getTypeDefinition(filePath, line, character) {
1327
+ const synced = await this.ensureDocumentSynced(filePath);
1328
+ if (!synced) {
1329
+ return null;
1330
+ }
1331
+ try {
1332
+ return await synced.client.getTypeDefinition(synced.uri, this.toPosition(line, character));
1333
+ }
1334
+ catch (error) {
1335
+ console.error("LSP type definition failed:", error.message);
1336
+ return null;
1337
+ }
1338
+ }
1339
+ async getImplementation(filePath, line, character) {
1340
+ const synced = await this.ensureDocumentSynced(filePath);
1341
+ if (!synced) {
1342
+ return null;
1343
+ }
1344
+ try {
1345
+ return await synced.client.getImplementation(synced.uri, this.toPosition(line, character));
1346
+ }
1347
+ catch (error) {
1348
+ console.error("LSP implementation failed:", error.message);
1349
+ return null;
1350
+ }
1351
+ }
1352
+ async getReferences(filePath, line, character, includeDeclaration = true) {
1353
+ const synced = await this.ensureDocumentSynced(filePath);
1354
+ if (!synced) {
1355
+ return null;
1356
+ }
1357
+ try {
1358
+ return await synced.client.getReferences(synced.uri, this.toPosition(line, character), includeDeclaration);
1359
+ }
1360
+ catch (error) {
1361
+ console.error("LSP references failed:", error.message);
1362
+ return null;
1363
+ }
1364
+ }
1365
+ async getDocumentHighlight(filePath, line, character) {
1366
+ const synced = await this.ensureDocumentSynced(filePath);
1367
+ if (!synced) {
1368
+ return null;
1369
+ }
1370
+ try {
1371
+ return await synced.client.getDocumentHighlight(synced.uri, this.toPosition(line, character));
1372
+ }
1373
+ catch (error) {
1374
+ console.error("LSP document highlight failed:", error.message);
1375
+ return null;
1376
+ }
1377
+ }
1378
+ async getMoniker(filePath, line, character) {
1379
+ const synced = await this.ensureDocumentSynced(filePath);
1380
+ if (!synced) {
1381
+ return null;
1382
+ }
1383
+ try {
1384
+ return await synced.client.getMoniker(synced.uri, this.toPosition(line, character));
1385
+ }
1386
+ catch (error) {
1387
+ console.error("LSP moniker failed:", error.message);
1388
+ return null;
1389
+ }
1390
+ }
1391
+ async getDocumentSymbols(filePath) {
1392
+ const synced = await this.ensureDocumentSynced(filePath);
1393
+ if (!synced) {
1394
+ return null;
1395
+ }
1396
+ try {
1397
+ return await synced.client.getDocumentSymbols(synced.uri);
1398
+ }
1399
+ catch (error) {
1400
+ console.error("LSP document symbols failed:", error.message);
1401
+ return null;
1402
+ }
1403
+ }
1404
+ async getCodeLens(filePath) {
1405
+ const synced = await this.ensureDocumentSynced(filePath);
1406
+ if (!synced) {
1407
+ return null;
1408
+ }
1409
+ try {
1410
+ return await synced.client.getCodeLens(synced.uri);
1411
+ }
1412
+ catch (error) {
1413
+ console.error("LSP code lens failed:", error.message);
1414
+ return null;
1415
+ }
1416
+ }
1417
+ async getFoldingRange(filePath) {
1418
+ const synced = await this.ensureDocumentSynced(filePath);
1419
+ if (!synced) {
1420
+ return null;
1421
+ }
1422
+ try {
1423
+ return await synced.client.getFoldingRange(synced.uri);
1424
+ }
1425
+ catch (error) {
1426
+ console.error("LSP folding range failed:", error.message);
1427
+ return null;
1428
+ }
1429
+ }
1430
+ async getSelectionRange(filePath, line, character) {
1431
+ const synced = await this.ensureDocumentSynced(filePath);
1432
+ if (!synced) {
1433
+ return null;
1434
+ }
1435
+ try {
1436
+ return await synced.client.getSelectionRange(synced.uri, [this.toPosition(line, character)]);
1437
+ }
1438
+ catch (error) {
1439
+ console.error("LSP selection range failed:", error.message);
1440
+ return null;
1441
+ }
1442
+ }
1443
+ async getDocumentLinks(filePath) {
1444
+ const synced = await this.ensureDocumentSynced(filePath);
1445
+ if (!synced) {
1446
+ return null;
1447
+ }
1448
+ try {
1449
+ return await synced.client.getDocumentLinks(synced.uri);
1450
+ }
1451
+ catch (error) {
1452
+ console.error("LSP document links failed:", error.message);
1453
+ return null;
1454
+ }
1455
+ }
1456
+ async getLinkedEditingRange(filePath, line, character) {
1457
+ const synced = await this.ensureDocumentSynced(filePath);
1458
+ if (!synced) {
1459
+ return null;
1460
+ }
1461
+ try {
1462
+ return await synced.client.getLinkedEditingRange(synced.uri, this.toPosition(line, character));
1463
+ }
1464
+ catch (error) {
1465
+ console.error("LSP linked editing range failed:", error.message);
1466
+ return null;
1467
+ }
1468
+ }
1469
+ async getDocumentColors(filePath) {
1470
+ const synced = await this.ensureDocumentSynced(filePath);
1471
+ if (!synced) {
1472
+ return null;
1473
+ }
1474
+ try {
1475
+ return await synced.client.getDocumentColors(synced.uri);
1476
+ }
1477
+ catch (error) {
1478
+ console.error("LSP document colors failed:", error.message);
1479
+ return null;
1480
+ }
1481
+ }
1482
+ async getColorPresentations(filePath, color, startLine, startCharacter, endLine, endCharacter) {
1483
+ const synced = await this.ensureDocumentSynced(filePath);
1484
+ if (!synced) {
1485
+ return null;
1486
+ }
1487
+ try {
1488
+ return await synced.client.getColorPresentations(synced.uri, color, {
1489
+ start: this.toPosition(startLine, startCharacter),
1490
+ end: this.toPosition(endLine, endCharacter),
1491
+ });
1492
+ }
1493
+ catch (error) {
1494
+ console.error("LSP color presentations failed:", error.message);
1495
+ return null;
1496
+ }
1497
+ }
1498
+ async prepareCallHierarchy(filePath, line, character) {
1499
+ const synced = await this.ensureDocumentSynced(filePath);
1500
+ if (!synced) {
1501
+ return null;
1502
+ }
1503
+ try {
1504
+ return await synced.client.prepareCallHierarchy(synced.uri, this.toPosition(line, character));
1505
+ }
1506
+ catch (error) {
1507
+ console.error("LSP call hierarchy prepare failed:", error.message);
1508
+ return null;
1509
+ }
1510
+ }
1511
+ async prepareTypeHierarchy(filePath, line, character) {
1512
+ const synced = await this.ensureDocumentSynced(filePath);
1513
+ if (!synced) {
1514
+ return null;
1515
+ }
1516
+ try {
1517
+ return await synced.client.prepareTypeHierarchy(synced.uri, this.toPosition(line, character));
1518
+ }
1519
+ catch (error) {
1520
+ console.error("LSP type hierarchy prepare failed:", error.message);
1521
+ return null;
1522
+ }
1523
+ }
1524
+ async getIncomingCalls(item) {
1525
+ const uri = typeof item?.uri === "string" ? item.uri : "";
1526
+ const languageId = uri.startsWith("file://")
1527
+ ? this.getLanguageId(uri.replace("file://", ""))
1528
+ : null;
1529
+ if (!languageId) {
1530
+ return null;
1531
+ }
1532
+ const client = await this.getClientForLanguage(languageId);
1533
+ if (!client || !client.initialized) {
1534
+ return null;
1535
+ }
1536
+ try {
1537
+ return await client.getIncomingCalls(item);
1538
+ }
1539
+ catch (error) {
1540
+ console.error("LSP incoming calls failed:", error.message);
1541
+ return null;
1542
+ }
1543
+ }
1544
+ async getOutgoingCalls(item) {
1545
+ const uri = typeof item?.uri === "string" ? item.uri : "";
1546
+ const languageId = uri.startsWith("file://")
1547
+ ? this.getLanguageId(uri.replace("file://", ""))
1548
+ : null;
1549
+ if (!languageId) {
1550
+ return null;
1551
+ }
1552
+ const client = await this.getClientForLanguage(languageId);
1553
+ if (!client || !client.initialized) {
1554
+ return null;
1555
+ }
1556
+ try {
1557
+ return await client.getOutgoingCalls(item);
1558
+ }
1559
+ catch (error) {
1560
+ console.error("LSP outgoing calls failed:", error.message);
1561
+ return null;
1562
+ }
1563
+ }
1564
+ async getTypeHierarchySupertypes(item) {
1565
+ const uri = typeof item?.uri === "string" ? item.uri : "";
1566
+ const languageId = uri.startsWith("file://")
1567
+ ? this.getLanguageId(uri.replace("file://", ""))
1568
+ : null;
1569
+ if (!languageId) {
1570
+ return null;
1571
+ }
1572
+ const client = await this.getClientForLanguage(languageId);
1573
+ if (!client || !client.initialized) {
1574
+ return null;
1575
+ }
1576
+ try {
1577
+ return await client.getTypeHierarchySupertypes(item);
1578
+ }
1579
+ catch (error) {
1580
+ console.error("LSP type hierarchy supertypes failed:", error.message);
1581
+ return null;
1582
+ }
1583
+ }
1584
+ async getTypeHierarchySubtypes(item) {
1585
+ const uri = typeof item?.uri === "string" ? item.uri : "";
1586
+ const languageId = uri.startsWith("file://")
1587
+ ? this.getLanguageId(uri.replace("file://", ""))
1588
+ : null;
1589
+ if (!languageId) {
1590
+ return null;
1591
+ }
1592
+ const client = await this.getClientForLanguage(languageId);
1593
+ if (!client || !client.initialized) {
1594
+ return null;
1595
+ }
1596
+ try {
1597
+ return await client.getTypeHierarchySubtypes(item);
1598
+ }
1599
+ catch (error) {
1600
+ console.error("LSP type hierarchy subtypes failed:", error.message);
1601
+ return null;
1602
+ }
1603
+ }
1604
+ async getInlayHints(filePath, startLine, startCharacter, endLine, endCharacter) {
1605
+ const synced = await this.ensureDocumentSynced(filePath);
1606
+ if (!synced) {
1607
+ return null;
1608
+ }
1609
+ try {
1610
+ return await synced.client.getInlayHints(synced.uri, {
1611
+ start: this.toPosition(startLine, startCharacter),
1612
+ end: this.toPosition(endLine, endCharacter),
1613
+ });
1614
+ }
1615
+ catch (error) {
1616
+ console.error("LSP inlay hint failed:", error.message);
1617
+ return null;
1618
+ }
1619
+ }
1620
+ async getInlineValues(filePath, range, context = {}) {
1621
+ const synced = await this.ensureDocumentSynced(filePath);
1622
+ if (!synced) {
1623
+ return null;
1624
+ }
1625
+ try {
1626
+ return await synced.client.getInlineValues(synced.uri, range, context);
1627
+ }
1628
+ catch (error) {
1629
+ console.error("LSP inline value failed:", error.message);
1630
+ return null;
1631
+ }
1632
+ }
1633
+ async getInlineCompletion(filePath, line, character, context = {}) {
1634
+ const synced = await this.ensureDocumentSynced(filePath);
1635
+ if (!synced) {
1636
+ return null;
1637
+ }
1638
+ try {
1639
+ return await synced.client.getInlineCompletion(synced.uri, this.toPosition(line, character), context);
1640
+ }
1641
+ catch (error) {
1642
+ console.error("LSP inline completion failed:", error.message);
1643
+ return null;
1644
+ }
1645
+ }
1646
+ async resolveInlineCompletionItem(item, languageId, filePath) {
1647
+ const client = await this.getClientForResolve(languageId, filePath);
1648
+ if (!client) {
1649
+ return null;
1650
+ }
1651
+ try {
1652
+ return await client.resolveInlineCompletionItem(item);
1653
+ }
1654
+ catch (error) {
1655
+ console.error("LSP inline completion resolve failed:", error.message);
1656
+ return null;
1657
+ }
1658
+ }
1659
+ async getCompletion(filePath, line, character) {
1660
+ const synced = await this.ensureDocumentSynced(filePath);
1661
+ if (!synced) {
1662
+ return null;
1663
+ }
1664
+ try {
1665
+ return await synced.client.getCompletion(synced.uri, this.toPosition(line, character));
1666
+ }
1667
+ catch (error) {
1668
+ console.error("LSP completion failed:", error.message);
1669
+ return null;
1670
+ }
1671
+ }
1672
+ async resolveCompletionItem(item, languageId, filePath) {
1673
+ const client = await this.getClientForResolve(languageId, filePath);
1674
+ if (!client) {
1675
+ return null;
1676
+ }
1677
+ try {
1678
+ return await client.resolveCompletionItem(item);
1679
+ }
1680
+ catch (error) {
1681
+ console.error("LSP completion resolve failed:", error.message);
1682
+ return null;
1683
+ }
1684
+ }
1685
+ async resolveCodeAction(action, languageId, filePath) {
1686
+ const client = await this.getClientForResolve(languageId, filePath);
1687
+ if (!client) {
1688
+ return null;
1689
+ }
1690
+ try {
1691
+ return await client.resolveCodeAction(action);
1692
+ }
1693
+ catch (error) {
1694
+ console.error("LSP code action resolve failed:", error.message);
1695
+ return null;
1696
+ }
1697
+ }
1698
+ async resolveCodeLens(codeLens, languageId, filePath) {
1699
+ const client = await this.getClientForResolve(languageId, filePath);
1700
+ if (!client) {
1701
+ return null;
1702
+ }
1703
+ try {
1704
+ return await client.resolveCodeLens(codeLens);
1705
+ }
1706
+ catch (error) {
1707
+ console.error("LSP code lens resolve failed:", error.message);
1708
+ return null;
1709
+ }
1710
+ }
1711
+ async resolveDocumentLink(link, languageId, filePath) {
1712
+ const client = await this.getClientForResolve(languageId, filePath);
1713
+ if (!client) {
1714
+ return null;
1715
+ }
1716
+ try {
1717
+ return await client.resolveDocumentLink(link);
1718
+ }
1719
+ catch (error) {
1720
+ console.error("LSP document link resolve failed:", error.message);
1721
+ return null;
1722
+ }
1723
+ }
1724
+ async resolveWorkspaceSymbol(symbol, languageId) {
1725
+ const client = await this.getClientForResolve(languageId);
1726
+ if (!client) {
1727
+ return null;
1728
+ }
1729
+ try {
1730
+ return await client.resolveWorkspaceSymbol(symbol);
1731
+ }
1732
+ catch (error) {
1733
+ console.error("LSP workspace symbol resolve failed:", error.message);
1734
+ return null;
1735
+ }
1736
+ }
1737
+ async resolveInlayHint(hint, languageId, filePath) {
1738
+ const client = await this.getClientForResolve(languageId, filePath);
1739
+ if (!client) {
1740
+ return null;
1741
+ }
1742
+ try {
1743
+ return await client.resolveInlayHint(hint);
1744
+ }
1745
+ catch (error) {
1746
+ console.error("LSP inlay hint resolve failed:", error.message);
1747
+ return null;
1748
+ }
1749
+ }
1750
+ async getSemanticTokensForCode(filePath, languageId, code) {
1751
+ const client = await this.getClientForLanguage(languageId);
1752
+ if (!client || !client.initialized || !client.semanticLegend) {
1753
+ return null;
1754
+ }
1755
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
1756
+ try {
1757
+ await client.openDocument(uri, languageId, 1, code);
1758
+ const result = (await client.getSemanticTokens(uri));
1759
+ return {
1760
+ data: Array.isArray(result?.data) ? result.data : [],
1761
+ legend: client.semanticLegend,
1762
+ languageId,
1763
+ };
1764
+ }
1765
+ catch (error) {
1766
+ console.warn("LSP semantic tokens failed:", error.message);
1767
+ return null;
1768
+ }
1769
+ finally {
1770
+ try {
1771
+ await client.closeDocument(uri);
1772
+ }
1773
+ catch {
1774
+ // Ignore close errors for transient semantic-token requests.
1775
+ }
1776
+ }
1777
+ }
1778
+ async getSemanticTokensRange(filePath, startLine, startCharacter, endLine, endCharacter) {
1779
+ const synced = await this.ensureDocumentSynced(filePath);
1780
+ if (!synced || !synced.client.semanticLegend) {
1781
+ return null;
1782
+ }
1783
+ try {
1784
+ const result = (await synced.client.getSemanticTokensRange(synced.uri, {
1785
+ start: this.toPosition(startLine, startCharacter),
1786
+ end: this.toPosition(endLine, endCharacter),
1787
+ }));
1788
+ return {
1789
+ data: Array.isArray(result?.data) ? result.data : [],
1790
+ legend: synced.client.semanticLegend,
1791
+ languageId: synced.languageId,
1792
+ };
1793
+ }
1794
+ catch (error) {
1795
+ console.warn("LSP semantic tokens range failed:", error.message);
1796
+ return null;
1797
+ }
1798
+ }
1799
+ async getSemanticTokensFull(filePath) {
1800
+ const synced = await this.ensureDocumentSynced(filePath);
1801
+ if (!synced || !synced.client.semanticLegend) {
1802
+ return null;
1803
+ }
1804
+ try {
1805
+ const result = (await synced.client.getSemanticTokens(synced.uri));
1806
+ const fullData = Array.isArray(result?.data) ? result.data : [];
1807
+ const resultId = typeof result?.resultId === "string" ? result.resultId : "0";
1808
+ this.semanticTokenSnapshots.set(synced.uri, { data: fullData, resultId, languageId: synced.languageId });
1809
+ return {
1810
+ data: fullData,
1811
+ resultId,
1812
+ legend: synced.client.semanticLegend,
1813
+ languageId: synced.languageId,
1814
+ };
1815
+ }
1816
+ catch (error) {
1817
+ console.warn("LSP semantic tokens full failed:", error.message);
1818
+ return null;
1819
+ }
1820
+ }
1821
+ async getSemanticTokensDocumentDelta(filePath, previousResultId) {
1822
+ const synced = await this.ensureDocumentSynced(filePath);
1823
+ if (!synced || !synced.client.semanticLegend) {
1824
+ return null;
1825
+ }
1826
+ try {
1827
+ const priorSnapshot = this.semanticTokenSnapshots.get(synced.uri);
1828
+ const effectivePreviousResultId = previousResultId || priorSnapshot?.resultId || "0";
1829
+ const result = effectivePreviousResultId === "0"
1830
+ ? (await synced.client.getSemanticTokens(synced.uri))
1831
+ : (await synced.client.getSemanticTokensDelta(synced.uri, effectivePreviousResultId));
1832
+ const fullData = Array.isArray(result?.data)
1833
+ ? result?.data
1834
+ : [];
1835
+ if (Array.isArray(result?.edits)) {
1836
+ const deltaResult = result;
1837
+ const nextResultId = typeof deltaResult.resultId === "string" ? deltaResult.resultId : effectivePreviousResultId;
1838
+ this.semanticTokenSnapshots.set(synced.uri, {
1839
+ data: fullData,
1840
+ resultId: nextResultId,
1841
+ languageId: synced.languageId,
1842
+ });
1843
+ return {
1844
+ data: fullData,
1845
+ resultId: nextResultId,
1846
+ edits: deltaResult.edits || [],
1847
+ legend: synced.client.semanticLegend,
1848
+ languageId: synced.languageId,
1849
+ };
1850
+ }
1851
+ const nextResultId = typeof result?.resultId === "string"
1852
+ ? result.resultId
1853
+ : effectivePreviousResultId;
1854
+ this.semanticTokenSnapshots.set(synced.uri, {
1855
+ data: fullData,
1856
+ resultId: nextResultId,
1857
+ languageId: synced.languageId,
1858
+ });
1859
+ return {
1860
+ data: fullData,
1861
+ resultId: nextResultId,
1862
+ edits: [],
1863
+ legend: synced.client.semanticLegend,
1864
+ languageId: synced.languageId,
1865
+ };
1866
+ }
1867
+ catch (error) {
1868
+ console.warn("LSP semantic tokens delta failed:", error.message);
1869
+ return null;
1870
+ }
1871
+ }
1872
+ async getSemanticTokensDelta(filePath, languageId, code, previousResultId) {
1873
+ const client = await this.getClientForLanguage(languageId);
1874
+ if (!client || !client.initialized || !client.semanticLegend) {
1875
+ return null;
1876
+ }
1877
+ const uri = pathToFileURL(path.resolve(filePath)).toString();
1878
+ try {
1879
+ await client.openDocument(uri, languageId, 1, code);
1880
+ const priorSnapshot = this.semanticTokenSnapshots.get(uri);
1881
+ const effectivePreviousResultId = previousResultId || priorSnapshot?.resultId || "0";
1882
+ const result = effectivePreviousResultId === "0"
1883
+ ? (await client.getSemanticTokens(uri))
1884
+ : (await client.getSemanticTokensDelta(uri, effectivePreviousResultId));
1885
+ const fullData = Array.isArray(result?.data)
1886
+ ? result?.data
1887
+ : [];
1888
+ if (Array.isArray(result?.edits)) {
1889
+ const deltaResult = result;
1890
+ const nextResultId = typeof deltaResult.resultId === "string" ? deltaResult.resultId : effectivePreviousResultId;
1891
+ this.semanticTokenSnapshots.set(uri, { data: fullData, resultId: nextResultId, languageId });
1892
+ return {
1893
+ data: fullData,
1894
+ resultId: nextResultId,
1895
+ edits: deltaResult.edits || [],
1896
+ legend: client.semanticLegend,
1897
+ languageId,
1898
+ };
1899
+ }
1900
+ const nextResultId = typeof result?.resultId === "string"
1901
+ ? result.resultId
1902
+ : effectivePreviousResultId;
1903
+ this.semanticTokenSnapshots.set(uri, { data: fullData, resultId: nextResultId, languageId });
1904
+ return {
1905
+ data: fullData,
1906
+ resultId: nextResultId,
1907
+ edits: [],
1908
+ legend: client.semanticLegend,
1909
+ languageId,
1910
+ };
1911
+ }
1912
+ catch (error) {
1913
+ console.warn("LSP semantic tokens delta failed:", error.message);
1914
+ return null;
1915
+ }
1916
+ finally {
1917
+ try {
1918
+ await client.closeDocument(uri);
1919
+ }
1920
+ catch {
1921
+ // Ignore close errors for transient semantic-token requests.
1922
+ }
1923
+ }
1924
+ }
1925
+ async getCodeActions(filePath, startLine, startCharacter, endLine, endCharacter, context = {}) {
1926
+ const synced = await this.ensureDocumentSynced(filePath);
1927
+ if (!synced) {
1928
+ return null;
1929
+ }
1930
+ return synced.client.getCodeActions(synced.uri, {
1931
+ start: this.toPosition(startLine, startCharacter),
1932
+ end: this.toPosition(endLine, endCharacter),
1933
+ }, context);
1934
+ }
1935
+ async renameSymbol(filePath, line, character, newName) {
1936
+ const synced = await this.ensureDocumentSynced(filePath);
1937
+ if (!synced) {
1938
+ return null;
1939
+ }
1940
+ return synced.client.rename(synced.uri, this.toPosition(line, character), newName);
1941
+ }
1942
+ async prepareRename(filePath, line, character) {
1943
+ const synced = await this.ensureDocumentSynced(filePath);
1944
+ if (!synced) {
1945
+ return null;
1946
+ }
1947
+ return synced.client.prepareRename(synced.uri, this.toPosition(line, character));
1948
+ }
1949
+ async formatDocument(filePath, options) {
1950
+ const synced = await this.ensureDocumentSynced(filePath);
1951
+ if (!synced) {
1952
+ return null;
1953
+ }
1954
+ return synced.client.formatting(synced.uri, options);
1955
+ }
1956
+ async formatRange(filePath, startLine, startCharacter, endLine, endCharacter, options) {
1957
+ const synced = await this.ensureDocumentSynced(filePath);
1958
+ if (!synced) {
1959
+ return null;
1960
+ }
1961
+ return synced.client.rangeFormatting(synced.uri, {
1962
+ start: this.toPosition(startLine, startCharacter),
1963
+ end: this.toPosition(endLine, endCharacter),
1964
+ }, options);
1965
+ }
1966
+ async formatOnType(filePath, line, character, ch, options) {
1967
+ const synced = await this.ensureDocumentSynced(filePath);
1968
+ if (!synced) {
1969
+ return null;
1970
+ }
1971
+ return synced.client.onTypeFormatting(synced.uri, this.toPosition(line, character), ch, options);
1972
+ }
1973
+ async getWorkspaceSymbols(query, languageId) {
1974
+ if (languageId) {
1975
+ const client = await this.getClientForLanguage(languageId);
1976
+ if (!client || !client.initialized) {
1977
+ return null;
1978
+ }
1979
+ return client.workspaceSymbol(query);
1980
+ }
1981
+ const clients = Array.from(this.servers.values());
1982
+ const results = await Promise.all(clients.map((client) => client.workspaceSymbol(query)));
1983
+ return results.flatMap((result) => (Array.isArray(result) ? result : [result]));
1984
+ }
1985
+ async getSignatureHelp(filePath, line, character) {
1986
+ const synced = await this.ensureDocumentSynced(filePath);
1987
+ if (!synced) {
1988
+ return null;
1989
+ }
1990
+ return synced.client.signatureHelp(synced.uri, this.toPosition(line, character));
1991
+ }
1992
+ async executeWorkspaceCommand(command, args = [], languageId, filePath) {
1993
+ const client = await this.getClientForResolve(languageId, filePath);
1994
+ if (!client) {
1995
+ return null;
1996
+ }
1997
+ try {
1998
+ return await client.executeCommand(command, args);
1999
+ }
2000
+ catch (error) {
2001
+ console.error("LSP workspace execute command failed:", error.message);
2002
+ return null;
2003
+ }
2004
+ }
2005
+ async willSaveWaitUntil(filePath, reason = 1) {
2006
+ const synced = await this.ensureDocumentSynced(filePath);
2007
+ if (!synced) {
2008
+ return null;
2009
+ }
2010
+ try {
2011
+ return await synced.client.willSaveWaitUntil(synced.uri, reason);
2012
+ }
2013
+ catch (error) {
2014
+ console.error("LSP willSaveWaitUntil failed:", error.message);
2015
+ return null;
2016
+ }
2017
+ }
2018
+ async getClientForResolve(languageId, filePath) {
2019
+ let resolvedLanguageId = languageId;
2020
+ if (!resolvedLanguageId && filePath) {
2021
+ resolvedLanguageId = this.getLanguageId(filePath) || undefined;
2022
+ }
2023
+ if (!resolvedLanguageId) {
2024
+ return null;
2025
+ }
2026
+ const client = await this.getClientForLanguage(resolvedLanguageId);
2027
+ if (!client || !client.initialized) {
2028
+ return null;
2029
+ }
2030
+ return client;
2031
+ }
2032
+ async shutdown() {
2033
+ const openDocs = Array.from(this.openDocumentLanguages.keys());
2034
+ const closePromises = openDocs.map(async (uri) => {
2035
+ const languageId = this.openDocumentLanguages.get(uri);
2036
+ if (!languageId) {
2037
+ return;
2038
+ }
2039
+ const client = this.servers.get(languageId);
2040
+ if (!client) {
2041
+ return;
2042
+ }
2043
+ try {
2044
+ await client.closeDocument(uri);
2045
+ }
2046
+ catch {
2047
+ // Ignore close errors during shutdown.
2048
+ }
2049
+ });
2050
+ await Promise.all(closePromises);
2051
+ const shutdownPromises = Array.from(this.servers.values()).map((client) => client.shutdown().catch((err) => console.error("Shutdown error:", err)));
2052
+ await Promise.all(shutdownPromises);
2053
+ this.servers.forEach((client) => {
2054
+ try {
2055
+ client.kill();
2056
+ }
2057
+ catch {
2058
+ // Ignore
2059
+ }
2060
+ });
2061
+ this.servers.clear();
2062
+ // Invalidate in-flight spawns for the same reason as a root change: a
2063
+ // spawn completing after shutdown would otherwise register a live server.
2064
+ this.spawnGeneration += 1;
2065
+ this.pendingSpawns.clear();
2066
+ this.unavailableLanguages.clear();
2067
+ this.documentVersions.clear();
2068
+ this.openDocumentLanguages.clear();
2069
+ this.lastDocumentText.clear();
2070
+ this.diagnosticsByUri.clear();
2071
+ this.documentDiagnosticResultIds.clear();
2072
+ this.workspaceDiagnosticResultIds.clear();
2073
+ }
2074
+ }
2075
+ export const coreLsp = new CoreLsp();
2076
+ export default CoreLsp;