Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display addons status in the control panel #2180

Merged
merged 1 commit into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/ruby_lsp/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ def process_message(message)
text_document_show_syntax_tree(message)
when "rubyLsp/workspace/dependencies"
workspace_dependencies(message)
when "rubyLsp/workspace/addons"
send_message(
st0012 marked this conversation as resolved.
Show resolved Hide resolved
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
Expand Down Expand Up @@ -104,7 +114,7 @@ def load_addons
),
)

$stderr.puts(errored_addons.map(&:errors_details).join("\n\n"))
$stderr.puts(errored_addons.map(&:errors_details).join("\n\n")) unless @test_mode
end
end

Expand Down Expand Up @@ -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",
Expand Down
50 changes: 48 additions & 2 deletions test/server_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -426,6 +426,27 @@ def test_changed_file_only_indexes_ruby
})
end

def test_workspace_addons
create_test_addons
@server.load_addons

@server.process_message({ id: 1, method: "rubyLsp/workspace/addons" })

addon_error_notification = @server.pop_response
assert_equal("window/showMessage", addon_error_notification.method)
assert_equal("Error loading addons:\n\nBar:\n boom\n", addon_error_notification.params.message)
addons_info = @server.pop_response.response

assert_equal("Foo", addons_info[0][:name])
refute(addons_info[0][:errored])

assert_equal("Bar", addons_info[1][:name])
assert(addons_info[1][:errored])
ensure
RubyLsp::Addon.addons.clear
RubyLsp::Addon.addon_classes.clear
end

private

def with_uninstalled_rubocop(&block)
Expand All @@ -452,4 +473,29 @@ def unload_rubocop_runner
rescue NameError
# Depending on which tests have run prior to this one, the classes may or may not be defined
end

def create_test_addons
Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue); end

def name
"Foo"
end

def deactivate; end
end

Class.new(RubyLsp::Addon) do
def activate(global_state, outgoing_queue)
# simulates failed addon activation
raise "boom"
end

def name
"Bar"
end

def deactivate; end
end
end
end
21 changes: 19 additions & 2 deletions vscode/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
Expand All @@ -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();
Expand All @@ -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/addons", {});
} catch (error: any) {
this.workspaceOutputChannel.error(
`Error while fetching addons: ${error.data.errorMessage}`,
);
}
}
}

get formatter(): string {
Expand Down
6 changes: 6 additions & 0 deletions vscode/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,15 @@ export interface RubyInterface {
rubyVersion?: string;
}

export interface Addon {
name: string;
errored: boolean;
}

export interface ClientInterface {
state: State;
formatter: string;
addons?: Addon[];
serverVersion?: string;
sendRequest<T>(
method: string,
Expand Down
27 changes: 27 additions & 0 deletions vscode/src/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,32 @@ 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) {
return;
}
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 {
const addonNames = workspace.lspClient.addons.map((addon) =>
addon.errored ? `${addon.name} (errored)` : `${addon.name}`,
);
this.item.text = `Addons: ${addonNames.join(", ")}`;
}
}
}

export class StatusItems {
private readonly items: StatusItem[] = [];

Expand All @@ -188,6 +214,7 @@ export class StatusItems {
new ExperimentalFeaturesStatus(),
new FeaturesStatus(),
new FormatterStatus(),
new AddonsStatus(),
];

STATUS_EMITTER.event((workspace) => {
Expand Down
53 changes: 53 additions & 0 deletions vscode/src/test/suite/status.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
StatusItem,
FeaturesStatus,
FormatterStatus,
AddonsStatus,
} from "../../status";
import { Command, WorkspaceInterface } from "../../common";

Expand All @@ -35,6 +36,7 @@ suite("StatusItems", () => {
workspace = {
ruby,
lspClient: {
addons: [],
state: State.Running,
formatter: "none",
serverVersion: "1.0.0",
Expand Down Expand Up @@ -72,6 +74,7 @@ suite("StatusItems", () => {
ruby,
lspClient: {
state: State.Running,
addons: [],
formatter: "none",
serverVersion: "1.0.0",
sendRequest: <T>() => Promise.resolve([] as T),
Expand Down Expand Up @@ -129,6 +132,7 @@ suite("StatusItems", () => {
workspace = {
ruby,
lspClient: {
addons: [],
state: State.Running,
formatter,
serverVersion: "1.0.0",
Expand Down Expand Up @@ -157,6 +161,7 @@ suite("StatusItems", () => {
workspace = {
ruby,
lspClient: {
addons: [],
state: State.Running,
formatter: "none",
serverVersion: "1.0.0",
Expand Down Expand Up @@ -244,6 +249,7 @@ suite("StatusItems", () => {
workspace = {
ruby,
lspClient: {
addons: [],
state: State.Running,
formatter: "auto",
serverVersion: "1.0.0",
Expand All @@ -262,4 +268,51 @@ suite("StatusItems", () => {
assert.strictEqual(status.item.command.command, Command.FormatterHelp);
});
});

suite("AddonsStatus", () => {
beforeEach(() => {
ruby = {} as Ruby;
workspace = {
ruby,
lspClient: {
addons: undefined,
state: State.Running,
formatter: "auto",
serverVersion: "1.0.0",
sendRequest: <T>() => Promise.resolve([] as T),
},
error: false,
};
status = new AddonsStatus();
status.refresh(workspace);
});

test("Status displays the server requirement info when addons is undefined", () => {
workspace.lspClient!.addons = undefined;
status.refresh(workspace);

assert.strictEqual(
status.item.text,
"Addons: requires server to be v0.17.4 or higher to display this field",
);
});

test("Status displays no addons when addons is an empty array", () => {
workspace.lspClient!.addons = [];
status.refresh(workspace);

assert.strictEqual(status.item.text, "Addons: none");
});

test("Status displays addon names and errored status", () => {
workspace.lspClient!.addons = [
{ name: "foo", errored: false },
{ name: "bar", errored: true },
];

status.refresh(workspace);

assert.strictEqual(status.item.text, "Addons: foo, bar (errored)");
});
});
});
2 changes: 1 addition & 1 deletion vscode/src/workspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading