diff --git a/package-lock.json b/package-lock.json index b1df6f3..55d873a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "net": "^1.0.2", "prismjs": "^1.17.1", "terminate": "^2.5.0", - "vscode-languageclient": "^7.0.0", + "vscode-languageclient": "^9.0.1", "vscode-oniguruma": "^2.0.1", "vscode-textmate": "^9.0.0", "ws": "^8.17.1", @@ -2410,6 +2410,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2905,7 +2906,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true }, "node_modules/console-control-strings": { "version": "1.1.0", @@ -5027,6 +5029,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6590,24 +6593,43 @@ } }, "node_modules/vscode-jsonrpc": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz", - "integrity": "sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "engines": { - "node": ">=8.0.0 || >=10.0.0" + "node": ">=14.0.0" } }, "node_modules/vscode-languageclient": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz", - "integrity": "sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-9.0.1.tgz", + "integrity": "sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==", "dependencies": { - "minimatch": "^3.0.4", - "semver": "^7.3.4", - "vscode-languageserver-protocol": "3.16.0" + "minimatch": "^5.1.0", + "semver": "^7.3.7", + "vscode-languageserver-protocol": "3.17.5" }, "engines": { - "vscode": "^1.52.0" + "vscode": "^1.82.0" + } + }, + "node_modules/vscode-languageclient/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/vscode-languageclient/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, "node_modules/vscode-languageclient/node_modules/semver": { @@ -6625,18 +6647,18 @@ } }, "node_modules/vscode-languageserver-protocol": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz", - "integrity": "sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==", + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "dependencies": { - "vscode-jsonrpc": "6.0.0", - "vscode-languageserver-types": "3.16.0" + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" } }, "node_modules/vscode-languageserver-types": { - "version": "3.16.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz", - "integrity": "sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==" + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" }, "node_modules/vscode-oniguruma": { "version": "2.0.1", diff --git a/package.json b/package.json index 5cf779b..dcba4e5 100644 --- a/package.json +++ b/package.json @@ -914,7 +914,7 @@ "net": "^1.0.2", "prismjs": "^1.17.1", "terminate": "^2.5.0", - "vscode-languageclient": "^7.0.0", + "vscode-languageclient": "^9.0.1", "vscode-oniguruma": "^2.0.1", "vscode-textmate": "^9.0.0", "ws": "^8.17.1", diff --git a/src/lsp/ClientConnectionManager.ts b/src/lsp/ClientConnectionManager.ts index 27e0ddd..9803544 100644 --- a/src/lsp/ClientConnectionManager.ts +++ b/src/lsp/ClientConnectionManager.ts @@ -25,6 +25,7 @@ enum ManagerStatus { DISCONNECTED = 4, CONNECTED = 5, RETRYING = 6, + WRONG_WORKSPACE = 7, } export class ClientConnectionManager { @@ -211,6 +212,9 @@ export class ClientConnectionManager { case ManagerStatus.RETRYING: this.show_retrying_prompt(); break; + case ManagerStatus.WRONG_WORKSPACE: + this.retry_connect_client(); + break; } } @@ -253,6 +257,10 @@ export class ClientConnectionManager { tooltip += `\n${this.connectedVersion}`; } break; + case ManagerStatus.WRONG_WORKSPACE: + text = "$(x) Wrong Project"; + tooltip = "Disconnected from the GDScript language server."; + break; } this.statusWidget.text = text; this.statusWidget.tooltip = tooltip; @@ -269,7 +277,7 @@ export class ClientConnectionManager { set_context("connectedToLSP", true); this.status = ManagerStatus.CONNECTED; if (this.client.needsStart()) { - this.context.subscriptions.push(this.client.start()); + this.client.start().then(() => log.info("LSP Client started")); } break; case ClientStatus.DISCONNECTED: @@ -285,6 +293,10 @@ export class ClientConnectionManager { } this.retry = true; break; + case ClientStatus.REJECTED: + this.status = ManagerStatus.WRONG_WORKSPACE; + this.retry = false; + break; default: break; } diff --git a/src/lsp/GDScriptLanguageClient.ts b/src/lsp/GDScriptLanguageClient.ts index 31a09d7..457f39f 100644 --- a/src/lsp/GDScriptLanguageClient.ts +++ b/src/lsp/GDScriptLanguageClient.ts @@ -1,4 +1,5 @@ import EventEmitter from "node:events"; +import * as path from "node:path"; import * as vscode from "vscode"; import { LanguageClient, @@ -10,7 +11,7 @@ import { } from "vscode-languageclient/node"; import { globals } from "../extension"; -import { createLogger, get_configuration } from "../utils"; +import { createLogger, get_configuration, get_project_dir } from "../utils"; import { MessageIO } from "./MessageIO"; const log = createLogger("lsp.client", { output: "Godot LSP" }); @@ -19,6 +20,7 @@ export enum ClientStatus { PENDING = 0, DISCONNECTED = 1, CONNECTED = 2, + REJECTED = 3, } export enum TargetLSP { @@ -29,7 +31,7 @@ export enum TargetLSP { export type Target = { host: string; port: number; - type: TargetLSP; + type: TargetLSP; }; type HoverResult = { @@ -55,6 +57,13 @@ type HoverResponseMesssage = { result: HoverResult; }; +type ChangeWorkspaceNotification = { + method: string; + params: { + path: string; + }; +}; + export default class GDScriptLanguageClient extends LanguageClient { public io: MessageIO = new MessageIO(); @@ -63,6 +72,8 @@ export default class GDScriptLanguageClient extends LanguageClient { public port = -1; public lastPortTried = -1; public sentMessages = new Map(); + private initMessage: RequestMessage; + private rejected = false; events = new EventEmitter(); @@ -85,9 +96,6 @@ export default class GDScriptLanguageClient extends LanguageClient { { scheme: "file", language: "gdscript" }, { scheme: "untitled", language: "gdscript" }, ], - synchronize: { - fileEvents: vscode.workspace.createFileSystemWatcher("**/*.gd"), - }, }; super("GDScriptLanguageClient", serverOptions, clientOptions); @@ -100,6 +108,7 @@ export default class GDScriptLanguageClient extends LanguageClient { } connect(target: TargetLSP = TargetLSP.EDITOR) { + this.rejected = false; this.target = target; this.status = ClientStatus.PENDING; @@ -122,15 +131,38 @@ export default class GDScriptLanguageClient extends LanguageClient { this.io.connect(host, port); } + async send_request(method: string, params) { + try { + return this.sendRequest(method, params); + } catch { + log.warn("sending request failed!"); + } + } + private request_filter(message: RequestMessage) { + if (this.rejected) { + if (message.method === "shutdown") { + return message; + } + return false; + } this.sentMessages.set(message.id, message); + if (!this.initMessage && message.method === "initialize") { + this.initMessage = message; + } // discard outgoing messages that we know aren't supported - if (message.method === "didChangeWatchedFiles") { - return; + // if (message.method === "textDocument/didSave") { + // return false; + // } + // if (message.method === "textDocument/willSaveWaitUntil") { + // return false; + // } + if (message.method === "workspace/didChangeWatchedFiles") { + return false; } if (message.method === "workspace/symbol") { - return; + return false; } return message; @@ -165,9 +197,19 @@ export default class GDScriptLanguageClient extends LanguageClient { return message; } + private async check_workspace(message: ChangeWorkspaceNotification) { + const server_path = path.normalize(message.params.path); + const client_path = path.normalize(await get_project_dir()); + if (server_path !== client_path) { + log.warn("Connected LSP is a different workspace"); + this.io.socket.resetAndDestroy(); + this.rejected = true; + } + } + private notification_filter(message: NotificationMessage) { if (message.method === "gdscript_client/changeWorkspace") { - // + this.check_workspace(message as ChangeWorkspaceNotification); } if (message.method === "gdscript/capabilities") { globals.docsProvider.register_capabilities(message); @@ -194,9 +236,8 @@ export default class GDScriptLanguageClient extends LanguageClient { textDocument: { uri: uri.toString() }, position: { line: position.line, character: position.character }, }; - const response: HoverResult = await this.sendRequest("textDocument/hover", params); - - return this.parse_hover_result(response); + const response = await this.send_request("textDocument/hover", params); + return this.parse_hover_result(response as HoverResult); } private parse_hover_result(message: HoverResult) { @@ -233,9 +274,17 @@ export default class GDScriptLanguageClient extends LanguageClient { const host = get_configuration("lsp.serverHost"); log.info(`connected to LSP at ${host}:${this.lastPortTried}`); + + if (this.initMessage) { + this.send_request(this.initMessage.method, this.initMessage.params); + } } private on_disconnected() { + if (this.rejected) { + this.status = ClientStatus.REJECTED; + return; + } if (this.target === TargetLSP.EDITOR) { const host = get_configuration("lsp.serverHost"); let port = get_configuration("lsp.serverPort"); diff --git a/src/lsp/MessageIO.ts b/src/lsp/MessageIO.ts index 6a04b42..d575b85 100644 --- a/src/lsp/MessageIO.ts +++ b/src/lsp/MessageIO.ts @@ -22,9 +22,9 @@ export class MessageIO extends EventEmitter { reader = new MessageIOReader(this); writer = new MessageIOWriter(this); - requestFilter: (msg: RequestMessage) => RequestMessage = (msg) => msg; - responseFilter: (msg: ResponseMessage) => ResponseMessage = (msg) => msg; - notificationFilter: (msg: NotificationMessage) => NotificationMessage = (msg) => msg; + requestFilter: (msg: RequestMessage) => RequestMessage | false = (msg) => msg; + responseFilter: (msg: ResponseMessage) => ResponseMessage | false = (msg) => msg; + notificationFilter: (msg: NotificationMessage) => NotificationMessage | false = (msg) => msg; socket: Socket = null; messageCache: string[] = []; @@ -100,7 +100,7 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea } const json = JSON.parse(msg); // allow message to be modified - let modified: ResponseMessage | NotificationMessage; + let modified: ResponseMessage | NotificationMessage | false; if ("id" in json) { modified = this.io.responseFilter(json); } else if ("method" in json) { @@ -109,7 +109,7 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea log.warn("rx [unhandled]:", json); } - if (!modified) { + if (modified === false) { log.debug("rx [discarded]:", json); return; } @@ -128,7 +128,7 @@ export class MessageIOWriter extends AbstractMessageWriter implements MessageWri async write(msg: RequestMessage) { const modified = this.io.requestFilter(msg); - if (!modified) { + if (modified === false) { log.debug("tx [discarded]:", msg); return; } diff --git a/src/providers/documentation.ts b/src/providers/documentation.ts index e7653ea..bc259ce 100644 --- a/src/providers/documentation.ts +++ b/src/providers/documentation.ts @@ -103,7 +103,7 @@ export class GDDocumentationProvider implements CustomReadonlyEditorProvider { symbol_name: className, }; - const response = await globals.lsp.client.sendRequest("textDocument/nativeSymbol", params); + const response = await globals.lsp.client.send_request("textDocument/nativeSymbol", params); symbol = response as GodotNativeSymbol; symbol.class_info = this.classInfo.get(symbol.name); diff --git a/src/providers/inlay_hints.ts b/src/providers/inlay_hints.ts index 3300b33..f9afdb6 100644 --- a/src/providers/inlay_hints.ts +++ b/src/providers/inlay_hints.ts @@ -27,7 +27,7 @@ function fromDetail(detail: string): string { } async function addByHover(document: TextDocument, hoverPosition: vscode.Position, start: vscode.Position): Promise { - const response = await globals.lsp.client.sendRequest("textDocument/hover", { + const response = await globals.lsp.client.send_request("textDocument/hover", { textDocument: { uri: document.uri.toString() }, position: { line: hoverPosition.line, @@ -65,10 +65,12 @@ export class GDInlayHintsProvider implements InlayHintsProvider { if (!get_configuration("inlayHints.gdscript", true)) { return hints; } + + if (!globals.lsp.client.isRunning()) { + return hints; + } - await globals.lsp.client.onReady(); - - const symbolsRequest = await globals.lsp.client.sendRequest("textDocument/documentSymbol", { + const symbolsRequest = await globals.lsp.client.send_request("textDocument/documentSymbol", { textDocument: { uri: document.uri.toString() }, }) as unknown[];