Skip to content

Commit 03606fd

Browse files
authored
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
1 parent f4ae73c commit 03606fd

File tree

7 files changed

+131
-46
lines changed

7 files changed

+131
-46
lines changed

package-lock.json

Lines changed: 43 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,7 +914,7 @@
914914
"net": "^1.0.2",
915915
"prismjs": "^1.17.1",
916916
"terminate": "^2.5.0",
917-
"vscode-languageclient": "^7.0.0",
917+
"vscode-languageclient": "^9.0.1",
918918
"vscode-oniguruma": "^2.0.1",
919919
"vscode-textmate": "^9.0.0",
920920
"ws": "^8.17.1",

src/lsp/ClientConnectionManager.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ enum ManagerStatus {
2525
DISCONNECTED = 4,
2626
CONNECTED = 5,
2727
RETRYING = 6,
28+
WRONG_WORKSPACE = 7,
2829
}
2930

3031
export class ClientConnectionManager {
@@ -211,6 +212,9 @@ export class ClientConnectionManager {
211212
case ManagerStatus.RETRYING:
212213
this.show_retrying_prompt();
213214
break;
215+
case ManagerStatus.WRONG_WORKSPACE:
216+
this.retry_connect_client();
217+
break;
214218
}
215219
}
216220

@@ -253,6 +257,10 @@ export class ClientConnectionManager {
253257
tooltip += `\n${this.connectedVersion}`;
254258
}
255259
break;
260+
case ManagerStatus.WRONG_WORKSPACE:
261+
text = "$(x) Wrong Project";
262+
tooltip = "Disconnected from the GDScript language server.";
263+
break;
256264
}
257265
this.statusWidget.text = text;
258266
this.statusWidget.tooltip = tooltip;
@@ -269,7 +277,7 @@ export class ClientConnectionManager {
269277
set_context("connectedToLSP", true);
270278
this.status = ManagerStatus.CONNECTED;
271279
if (this.client.needsStart()) {
272-
this.context.subscriptions.push(this.client.start());
280+
this.client.start().then(() => log.info("LSP Client started"));
273281
}
274282
break;
275283
case ClientStatus.DISCONNECTED:
@@ -285,6 +293,10 @@ export class ClientConnectionManager {
285293
}
286294
this.retry = true;
287295
break;
296+
case ClientStatus.REJECTED:
297+
this.status = ManagerStatus.WRONG_WORKSPACE;
298+
this.retry = false;
299+
break;
288300
default:
289301
break;
290302
}

src/lsp/GDScriptLanguageClient.ts

Lines changed: 61 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import EventEmitter from "node:events";
2+
import * as path from "node:path";
23
import * as vscode from "vscode";
34
import {
45
LanguageClient,
@@ -10,7 +11,7 @@ import {
1011
} from "vscode-languageclient/node";
1112

1213
import { globals } from "../extension";
13-
import { createLogger, get_configuration } from "../utils";
14+
import { createLogger, get_configuration, get_project_dir } from "../utils";
1415
import { MessageIO } from "./MessageIO";
1516

1617
const log = createLogger("lsp.client", { output: "Godot LSP" });
@@ -19,6 +20,7 @@ export enum ClientStatus {
1920
PENDING = 0,
2021
DISCONNECTED = 1,
2122
CONNECTED = 2,
23+
REJECTED = 3,
2224
}
2325

2426
export enum TargetLSP {
@@ -29,7 +31,7 @@ export enum TargetLSP {
2931
export type Target = {
3032
host: string;
3133
port: number;
32-
type: TargetLSP;
34+
type: TargetLSP;
3335
};
3436

3537
type HoverResult = {
@@ -55,6 +57,13 @@ type HoverResponseMesssage = {
5557
result: HoverResult;
5658
};
5759

60+
type ChangeWorkspaceNotification = {
61+
method: string;
62+
params: {
63+
path: string;
64+
};
65+
};
66+
5867
export default class GDScriptLanguageClient extends LanguageClient {
5968
public io: MessageIO = new MessageIO();
6069

@@ -63,6 +72,8 @@ export default class GDScriptLanguageClient extends LanguageClient {
6372
public port = -1;
6473
public lastPortTried = -1;
6574
public sentMessages = new Map();
75+
private initMessage: RequestMessage;
76+
private rejected = false;
6677

6778
events = new EventEmitter();
6879

@@ -85,9 +96,6 @@ export default class GDScriptLanguageClient extends LanguageClient {
8596
{ scheme: "file", language: "gdscript" },
8697
{ scheme: "untitled", language: "gdscript" },
8798
],
88-
synchronize: {
89-
fileEvents: vscode.workspace.createFileSystemWatcher("**/*.gd"),
90-
},
9199
};
92100

93101
super("GDScriptLanguageClient", serverOptions, clientOptions);
@@ -100,6 +108,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
100108
}
101109

102110
connect(target: TargetLSP = TargetLSP.EDITOR) {
111+
this.rejected = false;
103112
this.target = target;
104113
this.status = ClientStatus.PENDING;
105114

@@ -122,15 +131,38 @@ export default class GDScriptLanguageClient extends LanguageClient {
122131
this.io.connect(host, port);
123132
}
124133

134+
async send_request(method: string, params) {
135+
try {
136+
return this.sendRequest(method, params);
137+
} catch {
138+
log.warn("sending request failed!");
139+
}
140+
}
141+
125142
private request_filter(message: RequestMessage) {
143+
if (this.rejected) {
144+
if (message.method === "shutdown") {
145+
return message;
146+
}
147+
return false;
148+
}
126149
this.sentMessages.set(message.id, message);
127150

151+
if (!this.initMessage && message.method === "initialize") {
152+
this.initMessage = message;
153+
}
128154
// discard outgoing messages that we know aren't supported
129-
if (message.method === "didChangeWatchedFiles") {
130-
return;
155+
// if (message.method === "textDocument/didSave") {
156+
// return false;
157+
// }
158+
// if (message.method === "textDocument/willSaveWaitUntil") {
159+
// return false;
160+
// }
161+
if (message.method === "workspace/didChangeWatchedFiles") {
162+
return false;
131163
}
132164
if (message.method === "workspace/symbol") {
133-
return;
165+
return false;
134166
}
135167

136168
return message;
@@ -165,9 +197,19 @@ export default class GDScriptLanguageClient extends LanguageClient {
165197
return message;
166198
}
167199

200+
private async check_workspace(message: ChangeWorkspaceNotification) {
201+
const server_path = path.normalize(message.params.path);
202+
const client_path = path.normalize(await get_project_dir());
203+
if (server_path !== client_path) {
204+
log.warn("Connected LSP is a different workspace");
205+
this.io.socket.resetAndDestroy();
206+
this.rejected = true;
207+
}
208+
}
209+
168210
private notification_filter(message: NotificationMessage) {
169211
if (message.method === "gdscript_client/changeWorkspace") {
170-
//
212+
this.check_workspace(message as ChangeWorkspaceNotification);
171213
}
172214
if (message.method === "gdscript/capabilities") {
173215
globals.docsProvider.register_capabilities(message);
@@ -194,9 +236,8 @@ export default class GDScriptLanguageClient extends LanguageClient {
194236
textDocument: { uri: uri.toString() },
195237
position: { line: position.line, character: position.character },
196238
};
197-
const response: HoverResult = await this.sendRequest("textDocument/hover", params);
198-
199-
return this.parse_hover_result(response);
239+
const response = await this.send_request("textDocument/hover", params);
240+
return this.parse_hover_result(response as HoverResult);
200241
}
201242

202243
private parse_hover_result(message: HoverResult) {
@@ -233,9 +274,17 @@ export default class GDScriptLanguageClient extends LanguageClient {
233274

234275
const host = get_configuration("lsp.serverHost");
235276
log.info(`connected to LSP at ${host}:${this.lastPortTried}`);
277+
278+
if (this.initMessage) {
279+
this.send_request(this.initMessage.method, this.initMessage.params);
280+
}
236281
}
237282

238283
private on_disconnected() {
284+
if (this.rejected) {
285+
this.status = ClientStatus.REJECTED;
286+
return;
287+
}
239288
if (this.target === TargetLSP.EDITOR) {
240289
const host = get_configuration("lsp.serverHost");
241290
let port = get_configuration("lsp.serverPort");

src/lsp/MessageIO.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ export class MessageIO extends EventEmitter {
2222
reader = new MessageIOReader(this);
2323
writer = new MessageIOWriter(this);
2424

25-
requestFilter: (msg: RequestMessage) => RequestMessage = (msg) => msg;
26-
responseFilter: (msg: ResponseMessage) => ResponseMessage = (msg) => msg;
27-
notificationFilter: (msg: NotificationMessage) => NotificationMessage = (msg) => msg;
25+
requestFilter: (msg: RequestMessage) => RequestMessage | false = (msg) => msg;
26+
responseFilter: (msg: ResponseMessage) => ResponseMessage | false = (msg) => msg;
27+
notificationFilter: (msg: NotificationMessage) => NotificationMessage | false = (msg) => msg;
2828

2929
socket: Socket = null;
3030
messageCache: string[] = [];
@@ -100,7 +100,7 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea
100100
}
101101
const json = JSON.parse(msg);
102102
// allow message to be modified
103-
let modified: ResponseMessage | NotificationMessage;
103+
let modified: ResponseMessage | NotificationMessage | false;
104104
if ("id" in json) {
105105
modified = this.io.responseFilter(json);
106106
} else if ("method" in json) {
@@ -109,7 +109,7 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea
109109
log.warn("rx [unhandled]:", json);
110110
}
111111

112-
if (!modified) {
112+
if (modified === false) {
113113
log.debug("rx [discarded]:", json);
114114
return;
115115
}
@@ -128,7 +128,7 @@ export class MessageIOWriter extends AbstractMessageWriter implements MessageWri
128128

129129
async write(msg: RequestMessage) {
130130
const modified = this.io.requestFilter(msg);
131-
if (!modified) {
131+
if (modified === false) {
132132
log.debug("tx [discarded]:", msg);
133133
return;
134134
}

src/providers/documentation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export class GDDocumentationProvider implements CustomReadonlyEditorProvider {
103103
symbol_name: className,
104104
};
105105

106-
const response = await globals.lsp.client.sendRequest("textDocument/nativeSymbol", params);
106+
const response = await globals.lsp.client.send_request("textDocument/nativeSymbol", params);
107107

108108
symbol = response as GodotNativeSymbol;
109109
symbol.class_info = this.classInfo.get(symbol.name);

0 commit comments

Comments
 (0)