diff --git a/lib/ruby_lsp/server.rb b/lib/ruby_lsp/server.rb index 8bac238f91..a3cc57eb06 100644 --- a/lib/ruby_lsp/server.rb +++ b/lib/ruby_lsp/server.rb @@ -76,6 +76,16 @@ def process_message(message) text_document_show_syntax_tree(message) when "rubyLsp/workspace/dependencies" workspace_dependencies(message) + when "rubyLsp/workspace/getAddons" + send_message( + Result.new( + id: message[:id], + response: + Addon.addons.map do |addon| + { name: addon.name, errored: addon.error? } + end, + ), + ) when "$/cancelRequest" @mutex.synchronize { @cancelled_requests << message[:params][:id] } end @@ -177,6 +187,9 @@ def run_initialize(message) definition_provider: enabled_features["definition"], workspace_symbol_provider: enabled_features["workspaceSymbol"] && !@global_state.typechecker, signature_help_provider: signature_help_provider, + experimental: { + addon_detection: true, + }, ), serverInfo: { name: "Ruby LSP", diff --git a/test/server_test.rb b/test/server_test.rb index 4d1e768538..3cce378932 100644 --- a/test/server_test.rb +++ b/test/server_test.rb @@ -27,8 +27,8 @@ def test_initialize_enabled_features_with_array hash = JSON.parse(@server.pop_response.response.to_json) capabilities = hash["capabilities"] - # TextSynchronization + encodings + semanticHighlighting - assert_equal(3, capabilities.length) + # TextSynchronization + encodings + semanticHighlighting + experimental + assert_equal(4, capabilities.length) assert_includes(capabilities, "semanticTokensProvider") end diff --git a/vscode/src/client.ts b/vscode/src/client.ts index 74fd91ceb0..b1a33e896a 100644 --- a/vscode/src/client.ts +++ b/vscode/src/client.ts @@ -15,7 +15,7 @@ import { DocumentSelector, } from "vscode-languageclient/node"; -import { LSP_NAME, ClientInterface } from "./common"; +import { LSP_NAME, ClientInterface, Addon } from "./common"; import { Telemetry, RequestEvent } from "./telemetry"; import { Ruby } from "./ruby"; import { WorkspaceChannel } from "./workspaceChannel"; @@ -167,11 +167,13 @@ function collectClientOptions( export default class Client extends LanguageClient implements ClientInterface { public readonly ruby: Ruby; public serverVersion?: string; + public addons?: Addon[]; private readonly workingDirectory: string; private readonly telemetry: Telemetry; private readonly createTestItems: (response: CodeLens[]) => void; private readonly baseFolder; private requestId = 0; + private readonly workspaceOutputChannel: WorkspaceChannel; #context: vscode.ExtensionContext; #formatter: string; @@ -197,6 +199,8 @@ export default class Client extends LanguageClient implements ClientInterface { ), ); + this.workspaceOutputChannel = outputChannel; + // Middleware are part of client options, but because they must reference `this`, we cannot make it a part of the // `super` call (TypeScript does not allow accessing `this` before invoking `super`) this.registerMiddleware(); @@ -210,9 +214,22 @@ export default class Client extends LanguageClient implements ClientInterface { this.#formatter = ""; } - afterStart() { + async afterStart() { this.#formatter = this.initializeResult?.formatter; this.serverVersion = this.initializeResult?.serverInfo?.version; + await this.fetchAddons(); + } + + async fetchAddons() { + if (this.initializeResult?.capabilities.experimental?.addon_detection) { + try { + this.addons = await this.sendRequest("rubyLsp/workspace/getAddons", {}); + } catch (error: any) { + this.workspaceOutputChannel.error( + `Error while fetching addons: ${error.data.errorMessage}`, + ); + } + } } get formatter(): string { diff --git a/vscode/src/common.ts b/vscode/src/common.ts index bb125a2096..46f63d7a40 100644 --- a/vscode/src/common.ts +++ b/vscode/src/common.ts @@ -26,9 +26,14 @@ export interface RubyInterface { rubyVersion?: string; } +export interface Addon { + name: string; +} + export interface ClientInterface { state: State; formatter: string; + addons?: Addon[]; serverVersion?: string; sendRequest( method: string, diff --git a/vscode/src/status.ts b/vscode/src/status.ts index 773399a3bd..fae4ae4f5d 100644 --- a/vscode/src/status.ts +++ b/vscode/src/status.ts @@ -178,6 +178,28 @@ export class FormatterStatus extends StatusItem { } } +export class AddonsStatus extends StatusItem { + constructor() { + super("addons"); + + this.item.name = "Ruby LSP Addons"; + this.item.text = "Fetching addon information"; + } + + refresh(workspace: WorkspaceInterface): void { + if (workspace.lspClient) { + if (workspace.lspClient.addons === undefined) { + this.item.text = + "Addons: requires server to be v0.17.4 or higher to display this field"; + } else if (workspace.lspClient.addons.length === 0) { + this.item.text = "Addons: none"; + } else { + this.item.text = `Addons: ${workspace.lspClient.addons.map((addon) => addon.name).join(", ")}`; + } + } + } +} + export class StatusItems { private readonly items: StatusItem[] = []; @@ -188,6 +210,7 @@ export class StatusItems { new ExperimentalFeaturesStatus(), new FeaturesStatus(), new FormatterStatus(), + new AddonsStatus(), ]; STATUS_EMITTER.event((workspace) => { diff --git a/vscode/src/test/suite/status.test.ts b/vscode/src/test/suite/status.test.ts index a15270a391..907c825411 100644 --- a/vscode/src/test/suite/status.test.ts +++ b/vscode/src/test/suite/status.test.ts @@ -35,6 +35,7 @@ suite("StatusItems", () => { workspace = { ruby, lspClient: { + addons: [], state: State.Running, formatter: "none", serverVersion: "1.0.0", @@ -72,6 +73,7 @@ suite("StatusItems", () => { ruby, lspClient: { state: State.Running, + addons: [], formatter: "none", serverVersion: "1.0.0", sendRequest: () => Promise.resolve([] as T), @@ -129,6 +131,7 @@ suite("StatusItems", () => { workspace = { ruby, lspClient: { + addons: [], state: State.Running, formatter, serverVersion: "1.0.0", @@ -157,6 +160,7 @@ suite("StatusItems", () => { workspace = { ruby, lspClient: { + addons: [], state: State.Running, formatter: "none", serverVersion: "1.0.0", @@ -244,6 +248,7 @@ suite("StatusItems", () => { workspace = { ruby, lspClient: { + addons: [], state: State.Running, formatter: "auto", serverVersion: "1.0.0", diff --git a/vscode/src/workspace.ts b/vscode/src/workspace.ts index a6a0267bc7..54d0033469 100644 --- a/vscode/src/workspace.ts +++ b/vscode/src/workspace.ts @@ -111,7 +111,7 @@ export class Workspace implements WorkspaceInterface { try { STATUS_EMITTER.fire(this); await this.lspClient.start(); - this.lspClient.afterStart(); + await this.lspClient.afterStart(); STATUS_EMITTER.fire(this); // If something triggered a restart while we were still booting, then now we need to perform the restart since the