mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Suppress "workspace/symbol" not found error (#723)
* Discard outgoing "workspace/symbol" LSP messages
This commit is contained in:
@@ -17,13 +17,13 @@ import { subProcess, killSubProcesses } from "../utils/subspawn";
|
||||
const log = createLogger("lsp.manager", { output: "Godot LSP" });
|
||||
|
||||
enum ManagerStatus {
|
||||
INITIALIZING,
|
||||
INITIALIZING_LSP,
|
||||
PENDING,
|
||||
PENDING_LSP,
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
RETRYING,
|
||||
INITIALIZING = 0,
|
||||
INITIALIZING_LSP = 1,
|
||||
PENDING = 2,
|
||||
PENDING_LSP = 3,
|
||||
DISCONNECTED = 4,
|
||||
CONNECTED = 5,
|
||||
RETRYING = 6,
|
||||
}
|
||||
|
||||
export class ClientConnectionManager {
|
||||
@@ -126,16 +126,18 @@ export class ClientConnectionManager {
|
||||
|
||||
if (result.version[2] < minimumVersion) {
|
||||
const message = `Cannot launch headless LSP: Headless LSP mode is only available on v${targetVersion} or newer, but the specified Godot executable is v${result.version}.`;
|
||||
vscode.window.showErrorMessage(message, "Select Godot executable", "Open Settings", "Disable Headless LSP", "Ignore").then(item => {
|
||||
if (item === "Select Godot executable") {
|
||||
select_godot_executable(settingName);
|
||||
} else if (item === "Open Settings") {
|
||||
vscode.commands.executeCommand("workbench.action.openSettings", settingName);
|
||||
} else if (item === "Disable Headless LSP") {
|
||||
set_configuration("lsp.headless", false);
|
||||
prompt_for_reload();
|
||||
}
|
||||
});
|
||||
vscode.window
|
||||
.showErrorMessage(message, "Select Godot executable", "Open Settings", "Disable Headless LSP", "Ignore")
|
||||
.then((item) => {
|
||||
if (item === "Select Godot executable") {
|
||||
select_godot_executable(settingName);
|
||||
} else if (item === "Open Settings") {
|
||||
vscode.commands.executeCommand("workbench.action.openSettings", settingName);
|
||||
} else if (item === "Disable Headless LSP") {
|
||||
set_configuration("lsp.headless", false);
|
||||
prompt_for_reload();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -197,7 +199,7 @@ export class ClientConnectionManager {
|
||||
if (this.target === TargetLSP.HEADLESS) {
|
||||
options = ["Restart LSP", ...options];
|
||||
}
|
||||
vscode.window.showInformationMessage(message, ...options).then(item => {
|
||||
vscode.window.showInformationMessage(message, ...options).then((item) => {
|
||||
if (item === "Restart LSP") {
|
||||
this.connect_to_language_server();
|
||||
}
|
||||
@@ -324,7 +326,7 @@ export class ClientConnectionManager {
|
||||
options = ["Open workspace with Godot Editor", ...options];
|
||||
}
|
||||
|
||||
vscode.window.showErrorMessage(message, ...options).then(item => {
|
||||
vscode.window.showErrorMessage(message, ...options).then((item) => {
|
||||
if (item === "Retry") {
|
||||
this.connect_to_language_server();
|
||||
}
|
||||
|
||||
@@ -1,27 +1,40 @@
|
||||
import * as vscode from "vscode";
|
||||
import { LanguageClient, NotificationMessage, RequestMessage, ResponseMessage } from "vscode-languageclient/node";
|
||||
import { EventEmitter } from "events";
|
||||
import {
|
||||
LanguageClient,
|
||||
type NotificationMessage,
|
||||
type RequestMessage,
|
||||
type ResponseMessage,
|
||||
} from "vscode-languageclient/node";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { get_configuration, createLogger } from "../utils";
|
||||
import { Message, MessageIO, MessageIOReader, MessageIOWriter, TCPMessageIO, WebSocketMessageIO } from "./MessageIO";
|
||||
import {
|
||||
type Message,
|
||||
type MessageIO,
|
||||
MessageIOReader,
|
||||
MessageIOWriter,
|
||||
TCPMessageIO,
|
||||
WebSocketMessageIO,
|
||||
} from "./MessageIO";
|
||||
import { globals } from "../extension";
|
||||
|
||||
const log = createLogger("lsp.client", { output: "Godot LSP" });
|
||||
|
||||
export enum ClientStatus {
|
||||
PENDING,
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
PENDING = 0,
|
||||
DISCONNECTED = 1,
|
||||
CONNECTED = 2,
|
||||
}
|
||||
|
||||
export enum TargetLSP {
|
||||
HEADLESS,
|
||||
EDITOR,
|
||||
HEADLESS = 0,
|
||||
EDITOR = 1,
|
||||
}
|
||||
|
||||
const CUSTOM_MESSAGE = "gdscript_client/";
|
||||
|
||||
export default class GDScriptLanguageClient extends LanguageClient {
|
||||
public readonly io: MessageIO = (get_configuration("lsp.serverProtocol") === "ws") ? new WebSocketMessageIO() : new TCPMessageIO();
|
||||
public readonly io: MessageIO =
|
||||
get_configuration("lsp.serverProtocol") === "ws" ? new WebSocketMessageIO() : new TCPMessageIO();
|
||||
|
||||
private _status_changed_callbacks: ((v: ClientStatus) => void)[] = [];
|
||||
private _initialize_request: Message = null;
|
||||
@@ -35,10 +48,14 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
public lastSymbolHovered = "";
|
||||
|
||||
private _started = false;
|
||||
public get started(): boolean { return this._started; }
|
||||
public get started(): boolean {
|
||||
return this._started;
|
||||
}
|
||||
|
||||
private _status: ClientStatus;
|
||||
public get status(): ClientStatus { return this._status; }
|
||||
public get status(): ClientStatus {
|
||||
return this._status;
|
||||
}
|
||||
public set status(v: ClientStatus) {
|
||||
if (this._status !== v) {
|
||||
this._status = v;
|
||||
@@ -72,7 +89,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
// Notify the server about file changes to '.gd files contain in the workspace
|
||||
fileEvents: vscode.workspace.createFileSystemWatcher("**/*.gd"),
|
||||
},
|
||||
}
|
||||
},
|
||||
);
|
||||
this.status = ClientStatus.PENDING;
|
||||
this.io.on("disconnected", this.on_disconnected.bind(this));
|
||||
@@ -149,7 +166,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
let value: string = message.result["contents"]?.value;
|
||||
if (value) {
|
||||
// this is a dirty hack to fix language server sending us prerendered
|
||||
// markdown but not correctly stripping leading #'s, leading to
|
||||
// markdown but not correctly stripping leading #'s, leading to
|
||||
// docstrings being displayed as titles
|
||||
value = value.replace(/\n[#]+/g, "\n");
|
||||
|
||||
@@ -216,7 +233,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
this.io.writer.write(this._initialize_request);
|
||||
}
|
||||
this.status = ClientStatus.CONNECTED;
|
||||
|
||||
|
||||
const host = get_configuration("lsp.serverHost");
|
||||
log.info(`connected to LSP at ${host}:${this.lastPortTried}`);
|
||||
}
|
||||
@@ -260,12 +277,12 @@ class MessageHandler extends EventEmitter {
|
||||
|
||||
on_message(message: any) {
|
||||
// FIXME: Hot fix VSCode 1.42 hover position
|
||||
if (message && message.result && message.result.range && message.result.contents) {
|
||||
if (message?.result?.range && message.result.contents) {
|
||||
message.result.range = undefined;
|
||||
}
|
||||
|
||||
// What does this do?
|
||||
if (message && message.method && (message.method as string).startsWith(CUSTOM_MESSAGE)) {
|
||||
if (message?.method && (message.method as string).startsWith(CUSTOM_MESSAGE)) {
|
||||
const method = (message.method as string).substring(CUSTOM_MESSAGE.length, message.method.length);
|
||||
if (this[method]) {
|
||||
const ret = this[method](message.params);
|
||||
|
||||
@@ -4,34 +4,33 @@
|
||||
* ------------------------------------------------------------------------------------------ */
|
||||
|
||||
const DefaultSize: number = 8192;
|
||||
const CR: number = Buffer.from('\r', 'ascii')[0];
|
||||
const LF: number = Buffer.from('\n', 'ascii')[0];
|
||||
const CRLF: string = '\r\n';
|
||||
const CR: number = Buffer.from("\r", "ascii")[0];
|
||||
const LF: number = Buffer.from("\n", "ascii")[0];
|
||||
const CRLF: string = "\r\n";
|
||||
|
||||
export default class MessageBuffer {
|
||||
|
||||
private encoding: BufferEncoding;
|
||||
private index: number;
|
||||
private buffer: Buffer;
|
||||
|
||||
constructor(encoding: string = 'utf8') {
|
||||
constructor(encoding = "utf8") {
|
||||
this.encoding = encoding as BufferEncoding;
|
||||
this.index = 0;
|
||||
this.buffer = Buffer.allocUnsafe(DefaultSize);
|
||||
}
|
||||
|
||||
public append(chunk: Buffer | String): void {
|
||||
var toAppend: Buffer = <Buffer>chunk;
|
||||
if (typeof (chunk) === 'string') {
|
||||
var str = <string>chunk;
|
||||
var bufferLen = Buffer.byteLength(str, this.encoding);
|
||||
public append(chunk: Buffer | string): void {
|
||||
let toAppend: Buffer = <Buffer>chunk;
|
||||
if (typeof chunk === "string") {
|
||||
const str = <string>chunk;
|
||||
const bufferLen = Buffer.byteLength(str, this.encoding);
|
||||
toAppend = Buffer.allocUnsafe(bufferLen);
|
||||
toAppend.write(str, 0, bufferLen, this.encoding);
|
||||
}
|
||||
if (this.buffer.length - this.index >= toAppend.length) {
|
||||
toAppend.copy(this.buffer, this.index, 0, toAppend.length);
|
||||
} else {
|
||||
var newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
|
||||
const newSize = (Math.ceil((this.index + toAppend.length) / DefaultSize) + 1) * DefaultSize;
|
||||
if (this.index === 0) {
|
||||
this.buffer = Buffer.allocUnsafe(newSize);
|
||||
toAppend.copy(this.buffer, 0, 0, toAppend.length);
|
||||
@@ -42,10 +41,16 @@ export default class MessageBuffer {
|
||||
this.index += toAppend.length;
|
||||
}
|
||||
|
||||
public tryReadHeaders(): { [key: string]: string; } | undefined {
|
||||
let result: { [key: string]: string; } | undefined = undefined;
|
||||
public tryReadHeaders(): { [key: string]: string } | undefined {
|
||||
let result: { [key: string]: string } | undefined = undefined;
|
||||
let current = 0;
|
||||
while (current + 3 < this.index && (this.buffer[current] !== CR || this.buffer[current + 1] !== LF || this.buffer[current + 2] !== CR || this.buffer[current + 3] !== LF)) {
|
||||
while (
|
||||
current + 3 < this.index &&
|
||||
(this.buffer[current] !== CR ||
|
||||
this.buffer[current + 1] !== LF ||
|
||||
this.buffer[current + 2] !== CR ||
|
||||
this.buffer[current + 3] !== LF)
|
||||
) {
|
||||
current++;
|
||||
}
|
||||
// No header / body separator found (e.g CRLFCRLF)
|
||||
@@ -53,18 +58,18 @@ export default class MessageBuffer {
|
||||
return result;
|
||||
}
|
||||
result = Object.create(null);
|
||||
let headers = this.buffer.toString('ascii', 0, current).split(CRLF);
|
||||
headers.forEach((header) => {
|
||||
let index: number = header.indexOf(':');
|
||||
const headers = this.buffer.toString("ascii", 0, current).split(CRLF);
|
||||
for (const header of headers) {
|
||||
const index: number = header.indexOf(":");
|
||||
if (index === -1) {
|
||||
throw new Error('Message header must separate key and value using :');
|
||||
throw new Error("Message header must separate key and value using :");
|
||||
}
|
||||
let key = header.substr(0, index);
|
||||
let value = header.substr(index + 1).trim();
|
||||
const key = header.substr(0, index);
|
||||
const value = header.substr(index + 1).trim();
|
||||
result![key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
let nextStart = current + 4;
|
||||
const nextStart = current + 4;
|
||||
this.buffer = this.buffer.slice(nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
@@ -74,8 +79,8 @@ export default class MessageBuffer {
|
||||
if (this.index < length) {
|
||||
return null;
|
||||
}
|
||||
let result = this.buffer.toString(this.encoding, 0, length);
|
||||
let nextStart = length;
|
||||
const result = this.buffer.toString(this.encoding, 0, length);
|
||||
const nextStart = length;
|
||||
this.buffer.copy(this.buffer, 0, nextStart);
|
||||
this.index = this.index - nextStart;
|
||||
return result;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import {
|
||||
AbstractMessageReader,
|
||||
MessageReader,
|
||||
DataCallback,
|
||||
Disposable,
|
||||
RequestMessage,
|
||||
ResponseMessage,
|
||||
NotificationMessage,
|
||||
type MessageReader,
|
||||
type DataCallback,
|
||||
type Disposable,
|
||||
type RequestMessage,
|
||||
type ResponseMessage,
|
||||
type NotificationMessage,
|
||||
AbstractMessageWriter,
|
||||
MessageWriter
|
||||
type MessageWriter,
|
||||
} from "vscode-jsonrpc";
|
||||
import { EventEmitter } from "events";
|
||||
import { WebSocket, Data } from "ws";
|
||||
import { EventEmitter } from "node:events";
|
||||
import { WebSocket, type Data } from "ws";
|
||||
import { Socket } from "net";
|
||||
import MessageBuffer from "./MessageBuffer";
|
||||
import { createLogger } from "../utils";
|
||||
@@ -45,7 +45,6 @@ export class MessageIO extends EventEmitter {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WebSocketMessageIO extends MessageIO {
|
||||
private socket: WebSocket = null;
|
||||
|
||||
@@ -59,7 +58,10 @@ export class WebSocketMessageIO extends MessageIO {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.socket = null;
|
||||
const ws = new WebSocket(`ws://${host}:${port}`);
|
||||
ws.on("open", () => { this.on_connected(ws); resolve(); });
|
||||
ws.on("open", () => {
|
||||
this.on_connected(ws);
|
||||
resolve();
|
||||
});
|
||||
ws.on("message", this.on_message.bind(this));
|
||||
ws.on("error", this.on_disconnected.bind(this));
|
||||
ws.on("close", this.on_disconnected.bind(this));
|
||||
@@ -91,7 +93,10 @@ export class TCPMessageIO extends MessageIO {
|
||||
this.socket = null;
|
||||
const socket = new Socket();
|
||||
socket.connect(port, host);
|
||||
socket.on("connect", () => { this.on_connected(socket); resolve(); });
|
||||
socket.on("connect", () => {
|
||||
this.on_connected(socket);
|
||||
resolve();
|
||||
});
|
||||
socket.on("data", this.on_message.bind(this));
|
||||
socket.on("end", this.on_disconnected.bind(this));
|
||||
socket.on("close", this.on_disconnected.bind(this));
|
||||
@@ -162,15 +167,15 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea
|
||||
if (!contentLength) {
|
||||
throw new Error("Header must provide a Content-Length property.");
|
||||
}
|
||||
const length = parseInt(contentLength);
|
||||
if (isNaN(length)) {
|
||||
const length = Number.parseInt(contentLength);
|
||||
if (Number.isNaN(length)) {
|
||||
throw new Error("Content-Length value must be a number.");
|
||||
}
|
||||
this.nextMessageLength = length;
|
||||
// Take the encoding form the header. For compatibility
|
||||
// treat both utf-8 and utf8 as node utf8
|
||||
}
|
||||
var msg = this.buffer.tryReadContent(this.nextMessageLength);
|
||||
const msg = this.buffer.tryReadContent(this.nextMessageLength);
|
||||
if (msg === null) {
|
||||
/** We haven't received the full message yet. */
|
||||
this.setPartialMessageTimer();
|
||||
@@ -179,7 +184,7 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea
|
||||
this.clearPartialMessageTimer();
|
||||
this.nextMessageLength = -1;
|
||||
this.messageToken++;
|
||||
var json = JSON.parse(msg);
|
||||
const json = JSON.parse(msg);
|
||||
|
||||
log.debug("rx:", json);
|
||||
this.callback(json);
|
||||
@@ -200,13 +205,18 @@ export class MessageIOReader extends AbstractMessageReader implements MessageRea
|
||||
if (this._partialMessageTimeout <= 0) {
|
||||
return;
|
||||
}
|
||||
this.partialMessageTimer = setTimeout((token, timeout) => {
|
||||
this.partialMessageTimer = undefined;
|
||||
if (token === this.messageToken) {
|
||||
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
|
||||
this.setPartialMessageTimer();
|
||||
}
|
||||
}, this._partialMessageTimeout, this.messageToken, this._partialMessageTimeout);
|
||||
this.partialMessageTimer = setTimeout(
|
||||
(token, timeout) => {
|
||||
this.partialMessageTimer = undefined;
|
||||
if (token === this.messageToken) {
|
||||
this.firePartialMessage({ messageToken: token, waitingTime: timeout });
|
||||
this.setPartialMessageTimer();
|
||||
}
|
||||
},
|
||||
this._partialMessageTimeout,
|
||||
this.messageToken,
|
||||
this._partialMessageTimeout,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,21 +237,20 @@ export class MessageIOWriter extends AbstractMessageWriter implements MessageWri
|
||||
this.io.on("close", () => this.fireClose());
|
||||
}
|
||||
|
||||
public end(): void {
|
||||
|
||||
}
|
||||
public end(): void {}
|
||||
|
||||
public write(msg: Message): Promise<void> {
|
||||
// discard outgoing messages that we know aren't supported
|
||||
if ((msg as RequestMessage).method === "didChangeWatchedFiles") {
|
||||
return;
|
||||
}
|
||||
if ((msg as RequestMessage).method === "workspace/symbol") {
|
||||
return;
|
||||
}
|
||||
const json = JSON.stringify(msg);
|
||||
const contentLength = Buffer.byteLength(json, this.encoding);
|
||||
|
||||
const headers: string[] = [
|
||||
ContentLength, contentLength.toString(), CRLF,
|
||||
CRLF
|
||||
];
|
||||
const headers: string[] = [ContentLength, contentLength.toString(), CRLF, CRLF];
|
||||
try {
|
||||
// callback
|
||||
this.io.on_send_message(msg);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DocumentSymbol, Range, SymbolKind } from "vscode-languageclient";
|
||||
import type { DocumentSymbol, Range, SymbolKind } from "vscode-languageclient";
|
||||
|
||||
export interface NativeSymbolInspectParams {
|
||||
native_class: string;
|
||||
|
||||
Reference in New Issue
Block a user