diff --git a/src/commands.ts b/src/commands.ts index aff5943..36cc8cb 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -10,7 +10,6 @@ import { Disposable, window, workspace, - OutputChannel, SourceControlResourceState, SourceControlResourceGroup, TextDocumentShowOptions, @@ -18,6 +17,7 @@ import { Selection, ExtensionContext, SourceControl, + LogOutputChannel, } from 'vscode'; import { LineChange, revertChanges } from './revert'; import * as path from 'path'; @@ -182,7 +182,7 @@ export class CommandCenter { constructor( private readonly executable: FossilExecutable, private readonly model: Model, - private readonly outputChannel: OutputChannel, + private readonly outputChannel: LogOutputChannel, context: ExtensionContext ) { this.previewManager = new FossilPreviewManager(context, executable); @@ -1398,7 +1398,7 @@ export class CommandCenter { } } } else { - this.outputChannel.appendLine( + this.outputChannel.error( "couldn't create wiki entity - no active preview" ); } diff --git a/src/fossilExecutable.ts b/src/fossilExecutable.ts index 31a63ff..61d7f6a 100644 --- a/src/fossilExecutable.ts +++ b/src/fossilExecutable.ts @@ -6,7 +6,7 @@ import type { } from './openedRepository'; import * as path from 'path'; import * as fs from 'fs/promises'; -import { window, OutputChannel, ProgressLocation } from 'vscode'; +import { window, LogOutputChannel, ProgressLocation } from 'vscode'; import * as cp from 'child_process'; import { dispose, IDisposable, toDisposable } from './util'; import * as interaction from './interaction'; @@ -129,7 +129,7 @@ export function toString(this: ExecFailure): string { export class FossilExecutable { private fossilPath!: FossilExecutablePath; public version!: FossilVersion; - constructor(private readonly outputChannel: OutputChannel) {} + constructor(public readonly outputChannel: LogOutputChannel) {} setInfo(info: FossilExecutableInfo) { this.fossilPath = info.path; @@ -362,7 +362,7 @@ export class FossilExecutable { })(); if (options.logErrors !== false && result.stderr) { - this.log(`${result.stderr}\n`); + this.outputChannel.error(result.stderr); } const failure: ExecFailure = { ...result, @@ -385,8 +385,8 @@ export class FossilExecutable { return result as ExecSuccess; } - public log(output: string): void { - this.outputChannel.appendLine(output); + private log(output: string): void { + this.outputChannel.info(output); } private logArgs(args: FossilArgs, reason: string, info: string): void { if (args[0] == 'clone') { diff --git a/src/fossilFinder.ts b/src/fossilFinder.ts index 90b4278..da3c061 100644 --- a/src/fossilFinder.ts +++ b/src/fossilFinder.ts @@ -6,6 +6,7 @@ import type { FossilVersion, } from './fossilExecutable'; import { localize } from './main'; +import { LogOutputChannel } from 'vscode'; export type UnvalidatedFossilExecutablePath = Distinct< string, @@ -38,7 +39,7 @@ function getVersion( export async function findFossil( hint: UnvalidatedFossilExecutablePath, - logLine: (test: string) => void + outputChannel: LogOutputChannel ): Promise { for (const [path, isHint] of [ [hint, 1], @@ -49,11 +50,15 @@ export async function findFossil( try { stdout = await getVersion(path); } catch (e: unknown) { - logLine( - isHint - ? `\`fossil.path\` '${path}' is unavailable (${e}). Will try 'fossil' as the path` - : `'${path}' is unavailable (${e}). Fossil extension commands will be disabled` - ); + if (isHint) { + outputChannel.warn( + `\`fossil.path\` '${path}' is unavailable (${e}). Will try 'fossil' as the path` + ); + } else { + outputChannel.error( + `'${path}' is unavailable (${e}). Fossil extension commands will be disabled` + ); + } continue; } @@ -64,12 +69,12 @@ export async function findFossil( .split('.') .map(s => parseInt(s)) as FossilVersion; } else { - logLine( + outputChannel.error( `Failed to parse fossil version from output: '${stdout}'` ); } - logLine( + outputChannel.info( localize( 'using fossil', 'Using fossil {0} from {1}', diff --git a/src/main.ts b/src/main.ts index 03985a2..1e28200 100644 --- a/src/main.ts +++ b/src/main.ts @@ -23,11 +23,11 @@ async function init(context: ExtensionContext): Promise { new Disposable(() => Disposable.from(...disposables).dispose()) ); - const outputChannel = window.createOutputChannel('Fossil'); + const outputChannel = window.createOutputChannel('Fossil', { log: true }); disposables.push(outputChannel); const fossilHist = typedConfig.path; - const fossilInfo = await findFossil(fossilHist, outputChannel.appendLine); + const fossilInfo = await findFossil(fossilHist, outputChannel); const executable = new FossilExecutable(outputChannel); const model = new Model(executable, fossilHist); diff --git a/src/model.ts b/src/model.ts index 93701bd..8d8eb3b 100644 --- a/src/model.ts +++ b/src/model.ts @@ -175,10 +175,9 @@ export class Model implements Disposable { const fossilHint = typedConfig.path; if (this.lastUsedHist != fossilHint) { this.lastUsedHist = fossilHint; - return findFossil( - fossilHint, - this.executable.log.bind(this.executable) - ).then(this.foundExecutable.bind(this)); + return findFossil(fossilHint, this.executable.outputChannel).then( + this.foundExecutable.bind(this) + ); } } diff --git a/src/test/suite/setup.test.ts b/src/test/suite/setup.test.ts index e22e5c0..ceb0da3 100644 --- a/src/test/suite/setup.test.ts +++ b/src/test/suite/setup.test.ts @@ -1,4 +1,11 @@ -import { window, Uri, workspace, ExtensionContext, commands } from 'vscode'; +import { + window, + Uri, + workspace, + ExtensionContext, + commands, + LogOutputChannel, +} from 'vscode'; import { SinonStubT, cleanRoot, @@ -35,26 +42,28 @@ function ExecutableSuite(this: Suite): void { after(resetIgnoreMissingFossilWarning); test('Invalid executable path', async () => { - const appendLine = sinon.stub(); + const outputChanel = { + info: sinon.stub(), + warn: sinon.stub(), + }; await fossilFinder.findFossil( 'non_existing_fossil' as UnvalidatedFossilExecutablePath, - appendLine + outputChanel as unknown as LogOutputChannel ); - sinon.assert.calledTwice(appendLine); - sinon.assert.calledWithExactly( - appendLine.firstCall, + sinon.assert.calledOnceWithExactly( + outputChanel.warn, "`fossil.path` 'non_existing_fossil' is unavailable " + '(Error: spawn non_existing_fossil ENOENT). ' + "Will try 'fossil' as the path" ); sinon.assert.calledWithMatch( - appendLine.secondCall, + outputChanel.info, /^Using fossil \d.\d+ from fossil$/ ); }).timeout(2000); test('Execution error is caught and shown', async () => { - const appendLine = sinon.stub(); + const outputChanel = { error: sinon.stub() }; const childProcess = { on: sinon .stub() @@ -73,10 +82,10 @@ function ExecutableSuite(this: Suite): void { ); await fossilFinder.findFossil( '' as UnvalidatedFossilExecutablePath, - appendLine + outputChanel as unknown as LogOutputChannel ); sinon.assert.calledOnceWithExactly( - appendLine, + outputChanel.error, "'fossil' is unavailable (Error: mocked error). " + 'Fossil extension commands will be disabled' );