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
This commit is contained in:
David Kincaid
2025-03-10 04:49:05 -04:00
committed by GitHub
parent f4ae73c9a0
commit 03606fdb3a
7 changed files with 131 additions and 46 deletions

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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[];