mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2025-12-31 13:48:24 +03:00
Improve LSP connection behavior (fixes Godot3/4 port issue) (#511)
* Add port auto-fallback when attempting to connect to open editor's LSP * Improve status widget tooltips * Fix issue with configuration changes requiring a reload * Upgraded logger utility
This commit is contained in:
101
src/logger.ts
101
src/logger.ts
@@ -44,64 +44,89 @@ export class Logger {
|
||||
}
|
||||
}
|
||||
|
||||
export class Logger2 {
|
||||
protected tag: string = "";
|
||||
protected level: string = "";
|
||||
protected time: boolean = false;
|
||||
export enum LOG_LEVEL {
|
||||
SILENT,
|
||||
ERROR,
|
||||
WARNING,
|
||||
INFO,
|
||||
DEBUG,
|
||||
}
|
||||
|
||||
constructor(tag: string) {
|
||||
this.tag = tag;
|
||||
const LOG_LEVEL_NAMES = [
|
||||
"SILENT",
|
||||
"ERROR",
|
||||
"WARN ",
|
||||
"INFO ",
|
||||
"DEBUG",
|
||||
]
|
||||
|
||||
const RESET = "\u001b[0m"
|
||||
|
||||
const LOG_COLORS = [
|
||||
RESET, // SILENT, normal
|
||||
"\u001b[1;31m", // ERROR, red
|
||||
"\u001b[1;33m", // WARNING, yellow
|
||||
"\u001b[1;36m", // INFO, cyan
|
||||
"\u001b[1;32m", // DEBUG, green
|
||||
]
|
||||
|
||||
export class Logger2 {
|
||||
private show_tag: boolean = true;
|
||||
private show_time: boolean;
|
||||
private show_label: boolean;
|
||||
private show_level: boolean = false;
|
||||
|
||||
constructor(
|
||||
private tag: string,
|
||||
private level: LOG_LEVEL = LOG_LEVEL.DEBUG,
|
||||
{ time = false, label = false }: { time?: boolean, label?: boolean } = {},
|
||||
) {
|
||||
this.show_time = time;
|
||||
this.show_label = label;
|
||||
}
|
||||
|
||||
log(...messages) {
|
||||
let line = "[godotTools]";
|
||||
if (this.time) {
|
||||
line += `[${new Date().toISOString()}]`;
|
||||
private log(level: LOG_LEVEL, ...messages) {
|
||||
let prefix = "";
|
||||
if (this.show_label) {
|
||||
prefix += "[godotTools]";
|
||||
}
|
||||
if (this.level) {
|
||||
line += `[${this.level}]`;
|
||||
this.level = "";
|
||||
if (this.show_time) {
|
||||
prefix += `[${new Date().toISOString()}]`;
|
||||
}
|
||||
if (this.tag) {
|
||||
line += `[${this.tag}]`;
|
||||
if (this.show_level) {
|
||||
prefix += "[" + LOG_COLORS[level] + LOG_LEVEL_NAMES[level] + RESET + "]";
|
||||
}
|
||||
if (line) {
|
||||
line += " ";
|
||||
if (this.show_tag) {
|
||||
prefix += "[" + LOG_COLORS[level] + this.tag + RESET + "]";
|
||||
}
|
||||
|
||||
for (let index = 0; index < messages.length; index++) {
|
||||
line += messages[index];
|
||||
if (index < messages.length) {
|
||||
line += " ";
|
||||
} else {
|
||||
line += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
console.log(line);
|
||||
console.log(prefix, ...messages);
|
||||
}
|
||||
|
||||
info(...messages) {
|
||||
this.level = "INFO";
|
||||
this.log(messages);
|
||||
if (LOG_LEVEL.INFO <= this.level) {
|
||||
this.log(LOG_LEVEL.INFO, ...messages);
|
||||
}
|
||||
}
|
||||
debug(...messages) {
|
||||
this.level = "DEBUG";
|
||||
this.log(messages);
|
||||
if (LOG_LEVEL.DEBUG <= this.level) {
|
||||
this.log(LOG_LEVEL.DEBUG, ...messages);
|
||||
}
|
||||
}
|
||||
warn(...messages) {
|
||||
this.level = "WARNING";
|
||||
this.log(messages);
|
||||
if (LOG_LEVEL.WARNING <= this.level) {
|
||||
this.log(LOG_LEVEL.WARNING, ...messages);
|
||||
}
|
||||
}
|
||||
error(...messages) {
|
||||
this.level = "ERROR";
|
||||
this.log(messages);
|
||||
if (LOG_LEVEL.ERROR <= this.level) {
|
||||
this.log(LOG_LEVEL.ERROR, ...messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function createLogger(tag) {
|
||||
return new Logger2(tag);
|
||||
export function createLogger(tag, level: LOG_LEVEL = LOG_LEVEL.DEBUG) {
|
||||
return new Logger2(tag, level);
|
||||
}
|
||||
|
||||
const logger = new Logger("godot-tools", true);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as vscode from "vscode";
|
||||
import * as fs from "fs";
|
||||
import GDScriptLanguageClient, { ClientStatus } from "./GDScriptLanguageClient";
|
||||
import GDScriptLanguageClient, { ClientStatus, TargetLSP } from "./GDScriptLanguageClient";
|
||||
import {
|
||||
get_configuration,
|
||||
get_free_port,
|
||||
@@ -30,11 +30,14 @@ export class ClientConnectionManager {
|
||||
private context: vscode.ExtensionContext;
|
||||
public client: GDScriptLanguageClient = null;
|
||||
|
||||
private reconnection_attempts = 0;
|
||||
private reconnectionAttempts = 0;
|
||||
|
||||
private target: TargetLSP = TargetLSP.EDITOR;
|
||||
private status: ManagerStatus = ManagerStatus.INITIALIZING;
|
||||
private statusWidget: vscode.StatusBarItem = null;
|
||||
|
||||
private connectedVersion: string = "";
|
||||
|
||||
constructor(p_context: vscode.ExtensionContext) {
|
||||
this.context = p_context;
|
||||
|
||||
@@ -46,9 +49,11 @@ export class ClientConnectionManager {
|
||||
}, get_configuration("lsp.autoReconnect.cooldown"));
|
||||
|
||||
register_command("startLanguageServer", () => {
|
||||
// TODO: this might leave the manager in a wierd state
|
||||
this.start_language_server();
|
||||
this.reconnection_attempts = 0;
|
||||
this.client.connect_to_server();
|
||||
this.reconnectionAttempts = 0;
|
||||
this.target = TargetLSP.HEADLESS;
|
||||
this.client.connect_to_server(this.target);
|
||||
});
|
||||
register_command("stopLanguageServer", this.stop_language_server.bind(this));
|
||||
register_command("checkStatus", this.on_status_item_click.bind(this));
|
||||
@@ -65,13 +70,16 @@ export class ClientConnectionManager {
|
||||
|
||||
private async connect_to_language_server() {
|
||||
this.client.port = -1;
|
||||
this.target = TargetLSP.EDITOR;
|
||||
this.connectedVersion = undefined;
|
||||
|
||||
if (get_configuration("lsp.headless")) {
|
||||
this.target = TargetLSP.HEADLESS;
|
||||
await this.start_language_server();
|
||||
}
|
||||
|
||||
this.reconnection_attempts = 0;
|
||||
this.client.connect_to_server();
|
||||
this.reconnectionAttempts = 0;
|
||||
this.client.connect_to_server(this.target);
|
||||
}
|
||||
|
||||
private stop_language_server() {
|
||||
@@ -112,7 +120,7 @@ export class ClientConnectionManager {
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.connectedVersion = output;
|
||||
if (match[1] !== projectVersion[0]) {
|
||||
const message = `Cannot launch headless LSP: The current project uses Godot v${projectVersion}, but the specified Godot executable is version ${match[0]}`;
|
||||
vscode.window.showErrorMessage(message, "Select Godot executable", "Ignore").then(item => {
|
||||
@@ -207,7 +215,7 @@ export class ClientConnectionManager {
|
||||
}
|
||||
|
||||
private on_status_item_click() {
|
||||
const lsp_target = this.get_lsp_connection_string();
|
||||
const lspTarget = this.get_lsp_connection_string();
|
||||
// TODO: fill these out with the ACTIONS a user could perform in each state
|
||||
switch (this.status) {
|
||||
case ManagerStatus.INITIALIZING:
|
||||
@@ -217,11 +225,21 @@ export class ClientConnectionManager {
|
||||
// vscode.window.showInformationMessage("Initializing LSP");
|
||||
break;
|
||||
case ManagerStatus.PENDING:
|
||||
// vscode.window.showInformationMessage(`Connecting to the GDScript language server at ${lsp_target}`);
|
||||
// vscode.window.showInformationMessage(`Connecting to the GDScript language server at ${lspTarget}`);
|
||||
break;
|
||||
case ManagerStatus.CONNECTED:
|
||||
// vscode.window.showInformationMessage("Connected to the GDScript language server.");
|
||||
case ManagerStatus.CONNECTED: {
|
||||
const message = `Connected to the GDScript language server at ${lspTarget}.`;
|
||||
vscode.window.showInformationMessage(
|
||||
message,
|
||||
"Restart LSP",
|
||||
"Ok"
|
||||
).then(item => {
|
||||
if (item === "Restart LSP") {
|
||||
this.connect_to_language_server();
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case ManagerStatus.DISCONNECTED:
|
||||
this.retry_connect_client();
|
||||
break;
|
||||
@@ -231,39 +249,47 @@ export class ClientConnectionManager {
|
||||
}
|
||||
|
||||
private update_status_widget() {
|
||||
const lsp_target = this.get_lsp_connection_string();
|
||||
const lspTarget = this.get_lsp_connection_string();
|
||||
const maxAttempts = get_configuration("lsp.autoReconnect.attempts")
|
||||
let text = "";
|
||||
let tooltip = "";
|
||||
switch (this.status) {
|
||||
case ManagerStatus.INITIALIZING:
|
||||
// this.statusWidget.text = `INITIALIZING`;
|
||||
this.statusWidget.text = `$(sync~spin) Initializing`;
|
||||
this.statusWidget.tooltip = `Initializing extension...`;
|
||||
text = `$(sync~spin) Initializing`;
|
||||
tooltip = `Initializing extension...`;
|
||||
break;
|
||||
case ManagerStatus.INITIALIZING_LSP:
|
||||
// this.statusWidget.text = `INITIALIZING_LSP ` + this.reconnection_attempts;
|
||||
this.statusWidget.text = `$(sync~spin) Initializing LSP`;
|
||||
this.statusWidget.tooltip = `Connecting to headless GDScript language server at ${lsp_target}`;
|
||||
text = `$(sync~spin) Initializing LSP ${this.reconnectionAttempts}/${maxAttempts}`;
|
||||
tooltip = `Connecting to headless GDScript language server.\n${lspTarget}`;
|
||||
if (this.connectedVersion) {
|
||||
tooltip += `\n${this.connectedVersion}`;
|
||||
}
|
||||
break;
|
||||
case ManagerStatus.PENDING:
|
||||
// this.statusWidget.text = `PENDING`;
|
||||
this.statusWidget.text = `$(sync~spin) Connecting`;
|
||||
this.statusWidget.tooltip = `Connecting to the GDScript language server at ${lsp_target}`;
|
||||
text = `$(sync~spin) Connecting`;
|
||||
tooltip = `Connecting to the GDScript language server at ${lspTarget}`;
|
||||
break;
|
||||
case ManagerStatus.CONNECTED:
|
||||
// this.statusWidget.text = `CONNECTED`;
|
||||
this.statusWidget.text = `$(check) Connected`;
|
||||
this.statusWidget.tooltip = `Connected to the GDScript language server.`;
|
||||
text = `$(check) Connected`;
|
||||
tooltip = `Connected to the GDScript language server.\n${lspTarget}`;
|
||||
if (this.connectedVersion) {
|
||||
tooltip += `\n${this.connectedVersion}`;
|
||||
}
|
||||
break;
|
||||
case ManagerStatus.DISCONNECTED:
|
||||
// this.statusWidget.text = `DISCONNECTED`;
|
||||
this.statusWidget.text = `$(x) Disconnected`;
|
||||
this.statusWidget.tooltip = `Disconnected from the GDScript language server.`;
|
||||
text = `$(x) Disconnected`;
|
||||
tooltip = `Disconnected from the GDScript language server.`;
|
||||
break;
|
||||
case ManagerStatus.RETRYING:
|
||||
// this.statusWidget.text = `RETRYING ` + this.reconnection_attempts;
|
||||
this.statusWidget.text = `$(sync~spin) Connecting ` + this.reconnection_attempts;
|
||||
this.statusWidget.tooltip = `Connecting to the GDScript language server at ${lsp_target}`;
|
||||
text = `$(sync~spin) Connecting ${this.reconnectionAttempts}/${maxAttempts}`;
|
||||
tooltip = `Connecting to the GDScript language server.\n${lspTarget}`;
|
||||
if (this.connectedVersion) {
|
||||
tooltip += `\n${this.connectedVersion}`;
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.statusWidget.text = text;
|
||||
this.statusWidget.tooltip = tooltip;
|
||||
}
|
||||
|
||||
private on_client_status_changed(status: ClientStatus) {
|
||||
@@ -307,11 +333,11 @@ export class ClientConnectionManager {
|
||||
}
|
||||
|
||||
private retry_connect_client() {
|
||||
const auto_retry = get_configuration("lsp.autoReconnect.enabled");
|
||||
const max_attempts = get_configuration("lsp.autoReconnect.attempts");
|
||||
if (auto_retry && this.reconnection_attempts <= max_attempts - 1) {
|
||||
this.reconnection_attempts++;
|
||||
this.client.connect_to_server();
|
||||
const autoRetry = get_configuration("lsp.autoReconnect.enabled");
|
||||
const maxAttempts = get_configuration("lsp.autoReconnect.attempts");
|
||||
if (autoRetry && this.reconnectionAttempts <= maxAttempts - 1) {
|
||||
this.reconnectionAttempts++;
|
||||
this.client.connect_to_server(this.target);
|
||||
this.retry = true;
|
||||
return;
|
||||
}
|
||||
@@ -320,8 +346,8 @@ export class ClientConnectionManager {
|
||||
this.status = ManagerStatus.DISCONNECTED;
|
||||
this.update_status_widget();
|
||||
|
||||
const lsp_target = this.get_lsp_connection_string();
|
||||
let message = `Couldn't connect to the GDScript language server at ${lsp_target}. Is the Godot editor or language server running?`;
|
||||
const lspTarget = this.get_lsp_connection_string();
|
||||
let message = `Couldn't connect to the GDScript language server at ${lspTarget}. Is the Godot editor or language server running?`;
|
||||
vscode.window.showErrorMessage(message, "Retry", "Ignore").then(item => {
|
||||
if (item == "Retry") {
|
||||
this.connect_to_language_server();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EventEmitter } from "events";
|
||||
import * as vscode from 'vscode';
|
||||
import { LanguageClient, RequestMessage, ResponseMessage } from "vscode-languageclient/node";
|
||||
import { LanguageClient, RequestMessage, ResponseMessage, integer } from "vscode-languageclient/node";
|
||||
import { createLogger } from "../logger";
|
||||
import { get_configuration, set_context } from "../utils";
|
||||
import { Message, MessageIO, MessageIOReader, MessageIOWriter, TCPMessageIO, WebSocketMessageIO } from "./MessageIO";
|
||||
import NativeDocumentManager from './NativeDocumentManager';
|
||||
import { NativeDocumentManager } from './NativeDocumentManager';
|
||||
|
||||
const log = createLogger("lsp.client");
|
||||
|
||||
@@ -13,10 +13,15 @@ export enum ClientStatus {
|
||||
DISCONNECTED,
|
||||
CONNECTED,
|
||||
}
|
||||
|
||||
export enum TargetLSP {
|
||||
HEADLESS,
|
||||
EDITOR,
|
||||
}
|
||||
|
||||
const CUSTOM_MESSAGE = "gdscrip_client/";
|
||||
|
||||
export default class GDScriptLanguageClient extends LanguageClient {
|
||||
|
||||
public readonly io: MessageIO = (get_configuration("lsp.serverProtocol") == "ws") ? new WebSocketMessageIO() : new TCPMessageIO();
|
||||
|
||||
private context: vscode.ExtensionContext;
|
||||
@@ -27,7 +32,10 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
private message_handler: MessageHandler = null;
|
||||
private native_doc_manager: NativeDocumentManager = null;
|
||||
|
||||
public target: TargetLSP = TargetLSP.EDITOR;
|
||||
|
||||
public port: number = -1;
|
||||
public lastPortTried: number = -1;
|
||||
public sentMessages = new Map();
|
||||
public lastSymbolHovered: string = "";
|
||||
|
||||
@@ -83,14 +91,26 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
this.native_doc_manager = new NativeDocumentManager(this.io);
|
||||
}
|
||||
|
||||
connect_to_server() {
|
||||
connect_to_server(target: TargetLSP = TargetLSP.EDITOR) {
|
||||
this.target = target;
|
||||
this.status = ClientStatus.PENDING;
|
||||
const host = get_configuration("lsp.serverHost");
|
||||
|
||||
let port = get_configuration("lsp.serverPort");
|
||||
if (this.port !== -1) {
|
||||
port = this.port;
|
||||
}
|
||||
log.info(`attempting to connect to LSP at port ${port}`);
|
||||
|
||||
if (this.target == TargetLSP.EDITOR) {
|
||||
if (port === 6005 || port === 6008) {
|
||||
port = 6005;
|
||||
}
|
||||
}
|
||||
|
||||
this.lastPortTried = port;
|
||||
|
||||
const host = get_configuration("lsp.serverHost");
|
||||
log.info(`attempting to connect to LSP at ${host}:${port}`);
|
||||
|
||||
this.io.connect_to_language_server(host, port);
|
||||
}
|
||||
|
||||
@@ -100,7 +120,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
}
|
||||
|
||||
private on_send_message(message: RequestMessage) {
|
||||
log.debug("tx: " + JSON.stringify(message));
|
||||
log.debug("tx:", message);
|
||||
|
||||
this.sentMessages.set(message.id, message.method);
|
||||
|
||||
@@ -111,7 +131,7 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
|
||||
private on_message(message: ResponseMessage) {
|
||||
const msgString = JSON.stringify(message);
|
||||
log.debug("rx: " + msgString);
|
||||
log.debug("rx:", message);
|
||||
|
||||
// This is a dirty hack to fix the language server sending us
|
||||
// invalid file URIs
|
||||
@@ -178,6 +198,21 @@ export default class GDScriptLanguageClient extends LanguageClient {
|
||||
}
|
||||
|
||||
private on_disconnected() {
|
||||
if (this.target == TargetLSP.EDITOR) {
|
||||
const host = get_configuration("lsp.serverHost");
|
||||
let port = get_configuration("lsp.serverPort");
|
||||
|
||||
if (port === 6005 || port === 6008) {
|
||||
if (this.lastPortTried === 6005) {
|
||||
port = 6008;
|
||||
log.info(`attempting to connect to LSP at ${host}:${port}`);
|
||||
|
||||
this.lastPortTried = port;
|
||||
this.io.connect_to_language_server(host, port);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.status = ClientStatus.DISCONNECTED;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const enum WebViewMessageType {
|
||||
INSPECT_NATIVE_SYMBOL = "INSPECT_NATIVE_SYMBOL",
|
||||
}
|
||||
|
||||
export default class NativeDocumentManager extends EventEmitter {
|
||||
export class NativeDocumentManager extends EventEmitter {
|
||||
private io: MessageIO = null;
|
||||
private native_classes: { [key: string]: GodotNativeClassInfo } = {};
|
||||
|
||||
|
||||
@@ -5,10 +5,8 @@ import { AddressInfo, createServer } from "net";
|
||||
|
||||
const EXTENSION_PREFIX = "godotTools";
|
||||
|
||||
const config = vscode.workspace.getConfiguration(EXTENSION_PREFIX);
|
||||
|
||||
export function get_configuration(name: string, default_value?: any) {
|
||||
let config_value = config.get(name, null);
|
||||
let config_value = vscode.workspace.getConfiguration(EXTENSION_PREFIX).get(name, null);
|
||||
if (default_value && config_value === null) {
|
||||
return default_value;
|
||||
}
|
||||
@@ -16,7 +14,7 @@ export function get_configuration(name: string, default_value?: any) {
|
||||
}
|
||||
|
||||
export function set_configuration(name: string, value: any) {
|
||||
return config.update(name, value);
|
||||
return vscode.workspace.getConfiguration(EXTENSION_PREFIX).update(name, value);
|
||||
}
|
||||
|
||||
export function is_debug_mode(): boolean {
|
||||
|
||||
Reference in New Issue
Block a user