diff --git a/src/client.ts b/src/client.ts index d909db3f..fcc42833 100644 --- a/src/client.ts +++ b/src/client.ts @@ -14,9 +14,10 @@ import { MessageSignature, } from "vscode-languageclient/node"; -import { LOG_CHANNEL, LSP_NAME, ClientInterface } from "./common"; +import { LSP_NAME, ClientInterface } from "./common"; import { Telemetry, RequestEvent } from "./telemetry"; import { Ruby } from "./ruby"; +import { WorkspaceChannel } from "./workspaceChannel"; type EnabledFeatures = Record; @@ -89,6 +90,7 @@ function getLspExecutables( function collectClientOptions( configuration: vscode.WorkspaceConfiguration, workspaceFolder: vscode.WorkspaceFolder, + outputChannel: WorkspaceChannel, ): LanguageClientOptions { const pullOn: "change" | "save" | "both" = configuration.get("pullDiagnosticsOn")!; @@ -107,7 +109,7 @@ function collectClientOptions( ], workspaceFolder, diagnosticCollectionName: LSP_NAME, - outputChannel: LOG_CHANNEL, + outputChannel, revealOutputChannelOn: RevealOutputChannelOn.Never, diagnosticPullOptions, initializationOptions: { @@ -139,6 +141,7 @@ export default class Client extends LanguageClient implements ClientInterface { ruby: Ruby, createTestItems: (response: CodeLens[]) => void, workspaceFolder: vscode.WorkspaceFolder, + outputChannel: WorkspaceChannel, ) { super( LSP_NAME, @@ -146,6 +149,7 @@ export default class Client extends LanguageClient implements ClientInterface { collectClientOptions( vscode.workspace.getConfiguration("rubyLsp"), workspaceFolder, + outputChannel, ), ); diff --git a/src/ruby.ts b/src/ruby.ts index cfa2b004..d36654b5 100644 --- a/src/ruby.ts +++ b/src/ruby.ts @@ -3,7 +3,8 @@ import fs from "fs/promises"; import * as vscode from "vscode"; -import { asyncExec, pathExists, LOG_CHANNEL, RubyInterface } from "./common"; +import { asyncExec, pathExists, RubyInterface } from "./common"; +import { WorkspaceChannel } from "./workspaceChannel"; export enum VersionManager { Asdf = "asdf", @@ -29,13 +30,16 @@ export class Ruby implements RubyInterface { private readonly context: vscode.ExtensionContext; private readonly customBundleGemfile?: string; private readonly cwd: string; + private readonly outputChannel: WorkspaceChannel; constructor( context: vscode.ExtensionContext, workingFolder: vscode.WorkspaceFolder, + outputChannel: WorkspaceChannel, ) { this.context = context; this.workingFolderPath = workingFolder.uri.fsPath; + this.outputChannel = outputChannel; const customBundleGemfile: string = vscode.workspace .getConfiguration("rubyLsp") @@ -78,7 +82,9 @@ export class Ruby implements RubyInterface { // If the version manager is auto, discover the actual manager before trying to activate anything if (this.versionManager === VersionManager.Auto) { await this.discoverVersionManager(); - LOG_CHANNEL.info(`Discovered version manager ${this.versionManager}`); + this.outputChannel.info( + `Discovered version manager ${this.versionManager}`, + ); } try { @@ -184,7 +190,7 @@ export class Ruby implements RubyInterface { command += "'"; } - LOG_CHANNEL.info( + this.outputChannel.info( `Trying to activate Ruby environment with command: ${command} inside directory: ${this.cwd}`, ); @@ -322,7 +328,7 @@ export class Ruby implements RubyInterface { command += "'"; } - LOG_CHANNEL.info( + this.outputChannel.info( `Checking if ${tool} is available on the path with command: ${command}`, ); diff --git a/src/test/suite/client.test.ts b/src/test/suite/client.test.ts index e2894d59..f285a3d4 100644 --- a/src/test/suite/client.test.ts +++ b/src/test/suite/client.test.ts @@ -10,7 +10,8 @@ import { State } from "vscode-languageclient/node"; import { Ruby, VersionManager } from "../../ruby"; import { Telemetry, TelemetryApi, TelemetryEvent } from "../../telemetry"; import Client from "../../client"; -import { asyncExec } from "../../common"; +import { LOG_CHANNEL, asyncExec } from "../../common"; +import { WorkspaceChannel } from "../../workspaceChannel"; class FakeApi implements TelemetryApi { public sentEvents: TelemetryEvent[]; @@ -66,8 +67,9 @@ suite("Client", () => { update: (_name: string, _value: any) => Promise.resolve(), }, } as unknown as vscode.ExtensionContext; + const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL); - const ruby = new Ruby(context, workspaceFolder); + const ruby = new Ruby(context, workspaceFolder, outputChannel); await ruby.activateRuby(); await asyncExec("gem install ruby-lsp", { @@ -82,6 +84,7 @@ suite("Client", () => { ruby, () => {}, workspaceFolder, + outputChannel, ); try { diff --git a/src/test/suite/ruby.test.ts b/src/test/suite/ruby.test.ts index 5de90527..e231f93d 100644 --- a/src/test/suite/ruby.test.ts +++ b/src/test/suite/ruby.test.ts @@ -6,6 +6,8 @@ import * as os from "os"; import * as vscode from "vscode"; import { Ruby, VersionManager } from "../../ruby"; +import { WorkspaceChannel } from "../../workspaceChannel"; +import { LOG_CHANNEL } from "../../common"; suite("Ruby environment activation", () => { let ruby: Ruby; @@ -22,10 +24,15 @@ suite("Ruby environment activation", () => { const context = { extensionMode: vscode.ExtensionMode.Test, } as vscode.ExtensionContext; - - ruby = new Ruby(context, { - uri: { fsPath: tmpPath }, - } as vscode.WorkspaceFolder); + const outputChannel = new WorkspaceChannel("fake", LOG_CHANNEL); + + ruby = new Ruby( + context, + { + uri: { fsPath: tmpPath }, + } as vscode.WorkspaceFolder, + outputChannel, + ); await ruby.activateRuby( // eslint-disable-next-line no-process-env process.env.CI ? VersionManager.None : VersionManager.Chruby, diff --git a/src/test/suite/workspaceChannel.test.ts b/src/test/suite/workspaceChannel.test.ts new file mode 100644 index 00000000..ea8d8829 --- /dev/null +++ b/src/test/suite/workspaceChannel.test.ts @@ -0,0 +1,27 @@ +import * as assert from "assert"; + +import * as vscode from "vscode"; + +import { WorkspaceChannel } from "../../workspaceChannel"; + +class FakeChannel { + public readonly messages: string[] = []; + + info(message: string) { + this.messages.push(message); + } +} + +suite("Workspace channel", () => { + test("prepends name as a prefix", () => { + const fakeChannel = new FakeChannel(); + const channel = new WorkspaceChannel( + "test", + fakeChannel as unknown as vscode.LogOutputChannel, + ); + + channel.info("hello!"); + assert.strictEqual(fakeChannel.messages.length, 1); + assert.strictEqual(fakeChannel.messages[0], "(test) hello!"); + }); +}); diff --git a/src/workspace.ts b/src/workspace.ts index 2bff825f..c8a3cd1a 100644 --- a/src/workspace.ts +++ b/src/workspace.ts @@ -12,6 +12,7 @@ import { WorkspaceInterface, STATUS_EMITTER, } from "./common"; +import { WorkspaceChannel } from "./workspaceChannel"; export class Workspace implements WorkspaceInterface { public lspClient?: Client; @@ -20,6 +21,7 @@ export class Workspace implements WorkspaceInterface { public readonly workspaceFolder: vscode.WorkspaceFolder; private readonly context: vscode.ExtensionContext; private readonly telemetry: Telemetry; + private readonly outputChannel: WorkspaceChannel; private needsRestart = false; #rebaseInProgress = false; #error = false; @@ -32,8 +34,12 @@ export class Workspace implements WorkspaceInterface { ) { this.context = context; this.workspaceFolder = workspaceFolder; + this.outputChannel = new WorkspaceChannel( + workspaceFolder.name, + LOG_CHANNEL, + ); this.telemetry = telemetry; - this.ruby = new Ruby(context, workspaceFolder); + this.ruby = new Ruby(context, workspaceFolder, this.outputChannel); this.createTestItems = createTestItems; this.registerRestarts(context); @@ -87,6 +93,7 @@ export class Workspace implements WorkspaceInterface { this.ruby, this.createTestItems, this.workspaceFolder, + this.outputChannel, ); try { @@ -102,7 +109,7 @@ export class Workspace implements WorkspaceInterface { } } catch (error: any) { this.error = true; - LOG_CHANNEL.error(`Error starting the server: ${error.message}`); + this.outputChannel.error(`Error starting the server: ${error.message}`); } } @@ -144,7 +151,7 @@ export class Workspace implements WorkspaceInterface { } } catch (error: any) { this.error = true; - LOG_CHANNEL.error(`Error restarting the server: ${error.message}`); + this.outputChannel.error(`Error restarting the server: ${error.message}`); } } @@ -199,7 +206,9 @@ export class Workspace implements WorkspaceInterface { this.context.workspaceState.update("rubyLsp.lastGemUpdate", Date.now()); } catch (error) { // If we fail to update the global installation of `ruby-lsp`, we don't want to prevent the server from starting - LOG_CHANNEL.error(`Failed to update global ruby-lsp gem: ${error}`); + this.outputChannel.error( + `Failed to update global ruby-lsp gem: ${error}`, + ); } } } diff --git a/src/workspaceChannel.ts b/src/workspaceChannel.ts new file mode 100644 index 00000000..3d85d4a3 --- /dev/null +++ b/src/workspaceChannel.ts @@ -0,0 +1,75 @@ +import * as vscode from "vscode"; + +export class WorkspaceChannel implements vscode.LogOutputChannel { + public readonly onDidChangeLogLevel: vscode.Event; + private readonly actualChannel: vscode.LogOutputChannel; + private readonly prefix: string; + + constructor(workspaceName: string, actualChannel: vscode.LogOutputChannel) { + this.prefix = `(${workspaceName})`; + this.actualChannel = actualChannel; + this.onDidChangeLogLevel = this.actualChannel.onDidChangeLogLevel; + } + + get name(): string { + return this.actualChannel.name; + } + + get logLevel(): vscode.LogLevel { + return this.actualChannel.logLevel; + } + + trace(message: string, ...args: any[]): void { + this.actualChannel.trace(`${this.prefix} ${message}`, ...args); + } + + debug(message: string, ...args: any[]): void { + this.actualChannel.debug(`${this.prefix} ${message}`, ...args); + } + + info(message: string, ...args: any[]): void { + this.actualChannel.info(`${this.prefix} ${message}`, ...args); + } + + warn(message: string, ...args: any[]): void { + this.actualChannel.warn(`${this.prefix} ${message}`, ...args); + } + + error(error: string | Error, ...args: any[]): void { + this.actualChannel.error(`${this.prefix} ${error}`, ...args); + } + + append(value: string): void { + this.actualChannel.append(`${this.prefix} ${value}`); + } + + appendLine(value: string): void { + this.actualChannel.appendLine(`${this.prefix} ${value}`); + } + + replace(value: string): void { + this.actualChannel.replace(`${this.prefix} ${value}`); + } + + clear(): void { + this.actualChannel.clear(); + } + + show(preserveFocus?: boolean | undefined): void; + show( + column?: vscode.ViewColumn | undefined, + preserveFocus?: boolean | undefined, + ): void; + + show(_column?: unknown, preserveFocus?: boolean | undefined): void { + this.actualChannel.show(preserveFocus); + } + + hide(): void { + this.actualChannel.hide(); + } + + dispose(): void { + this.actualChannel.dispose(); + } +}