Skip to content

Commit

Permalink
Improve LSP connection behavior (fixes Godot3/4 port issue) (#511)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
DaelonSuzuka authored Oct 29, 2023
1 parent 55fd896 commit 55617fd
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 88 deletions.
101 changes: 63 additions & 38 deletions src/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
100 changes: 63 additions & 37 deletions src/lsp/ClientConnectionManager.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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;

Expand All @@ -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));
Expand All @@ -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() {
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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:
Expand All @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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();
Expand Down
Loading

0 comments on commit 55617fd

Please sign in to comment.