diff --git a/src/logger.spec.ts b/src/logger.spec.ts index d27df0e9..1b2cbf09 100644 --- a/src/logger.spec.ts +++ b/src/logger.spec.ts @@ -37,13 +37,29 @@ describe('#log()', () => { expect(values[1]).toEqual({ command: undefined, text: 'bar\nfoobaz\n' }); }); - it('does not emit prefix if last call did not finish with a LF', () => { + it('does not emit prefix if previous call from same command did not finish with a LF', () => { const { logger, spy } = createLogger({}); - logger.log('foo', 'bar'); - logger.log('foo', 'baz'); + const command = new FakeCommand(); + logger.log('foo', 'bar', command); + logger.log('foo', 'baz', command); expect(spy.getValuesLength()).toBe(3); - expect(spy.getLastValue()).toEqual({ command: undefined, text: 'baz' }); + expect(spy.getLastValue()).toEqual({ command, text: 'baz' }); + }); + + it('emits LF and prefix if previous call is from different command and did not finish with a LF', () => { + const { logger, spy } = createLogger({}); + const command1 = new FakeCommand(); + logger.log('foo', 'bar', command1); + + const command2 = new FakeCommand(); + logger.log('foo', 'baz', command2); + + const values = spy.getValues(); + expect(values).toHaveLength(5); + expect(values).toContainEqual({ command: command1, text: '\n' }); + expect(values).toContainEqual({ command: command2, text: 'foo' }); + expect(values).toContainEqual({ command: command2, text: 'baz' }); }); it('does not emit prefix nor handle text if logger is in raw mode', () => { @@ -267,6 +283,19 @@ describe('#logCommandEvent()', () => { cmd, ); }); + + it('prepends a LF if previous command write did not end with a LF', () => { + const { logger } = createLogger({}); + const cmd = new FakeCommand('', undefined, 1); + logger.logCommandText('text', cmd); + logger.logCommandEvent('event', cmd); + + expect(logger.log).toHaveBeenCalledWith( + chalk.reset('[1]') + ' ', + '\n' + chalk.reset('event') + '\n', + cmd, + ); + }); }); describe('#logTable()', () => { diff --git a/src/logger.ts b/src/logger.ts index 131596ef..0568ebf6 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -20,10 +20,10 @@ export class Logger { private prefixLength = 0; /** - * Last character emitted. + * Last character emitted, and from which command. * If `undefined`, then nothing has been logged yet. */ - private lastChar?: string; + private lastWrite?: { command: Command | undefined; char: string }; /** * Observable that emits when there's been output logged. @@ -160,7 +160,14 @@ export class Logger { return; } - this.logCommandText(chalk.reset(text) + '\n', command); + // Last write was from this command, but it didn't end with a line feed. + // Prepend one, otherwise the event's text will be concatenated to that write. + // A line feed is otherwise inserted anyway. + let prefix = ''; + if (this.lastWrite?.command === command && this.lastWrite.char !== '\n') { + prefix = '\n'; + } + this.logCommandText(prefix + chalk.reset(text) + '\n', command); } logCommandText(text: string, command: Command) { @@ -254,24 +261,22 @@ export class Logger { // #70 - replace some ANSI code that would impact clearing lines text = text.replace(/\u2026/g, '...'); - const lines = text.split('\n').map((line, index, lines) => { - // First line will write prefix only if we finished the last write with a LF. - // Last line won't write prefix because it should be empty. - if (index === 0 || index === lines.length - 1) { - return line; - } - return prefix + line; - }); + // This write's interrupting another command, emit a line feed to start clean. + if (this.lastWrite && this.lastWrite.command !== command && this.lastWrite.char !== '\n') { + this.emit(this.lastWrite.command, '\n'); + } - if (!this.lastChar || this.lastChar === '\n') { + // Clean lines should emit a prefix + if (!this.lastWrite || this.lastWrite.char === '\n') { this.emit(command, prefix); } - this.lastChar = text[text.length - 1]; - this.emit(command, lines.join('\n')); + const textToWrite = text.replace(/\n(.)/g, `\n${prefix}$1`); + this.emit(command, textToWrite); } emit(command: Command | undefined, text: string) { + this.lastWrite = { command, char: text[text.length - 1] }; this.output.next({ command, text }); } }