Răsfoiți Sursa

Various LSP Client Improvements (#816)

* Fix outgoing LSP messages not actually being discarded
* Resend init message if reconnecting (fixes #818)
* Added wrong project disconnect feature
* Update vscode-languageclient from ^7.0.0 to ^9.0.1
David Kincaid 5 luni în urmă
părinte
comite
03606fdb3a

+ 43 - 21
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",

+ 1 - 1
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",

+ 13 - 1
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;
 		}

+ 61 - 12
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");

+ 6 - 6
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;
 		}

+ 1 - 1
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);

+ 6 - 4
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<InlayHint | undefined> {
-	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[];