From 45138283cfaa713099a1d2d0e06c9046d2022ac1 Mon Sep 17 00:00:00 2001 From: Simon Boudrias Date: Sun, 4 Aug 2024 18:15:41 -0400 Subject: [PATCH] Fix(@inquirer/core) Improve rendering performance (#1498) Fixes #1497 Related to #1407 --- packages/core/core.test.mts | 4 +- packages/core/src/lib/create-prompt.mts | 7 +-- packages/core/src/lib/screen-manager.mts | 71 +++++++++--------------- 3 files changed, 29 insertions(+), 53 deletions(-) diff --git a/packages/core/core.test.mts b/packages/core/core.test.mts index 56b787550..c8f72cdf4 100644 --- a/packages/core/core.test.mts +++ b/packages/core/core.test.mts @@ -465,7 +465,9 @@ describe('createPrompt()', () => { events.keypress('enter'); await expect(answer).resolves.toEqual('done'); - expect(getScreen({ raw: true })).toEqual(ansiEscapes.eraseLines(1)); + expect(getScreen({ raw: true })).toEqual( + ansiEscapes.eraseLines(1) + ansiEscapes.cursorShow, + ); }); it('clear timeout when force closing', { timeout: 1000 }, async () => { diff --git a/packages/core/src/lib/create-prompt.mts b/packages/core/src/lib/create-prompt.mts index 7f66a9922..7637b5f75 100644 --- a/packages/core/src/lib/create-prompt.mts +++ b/packages/core/src/lib/create-prompt.mts @@ -54,12 +54,7 @@ export function createPrompt(view: ViewFunction) { function onExit() { hooksCleanup(); - if (context?.clearPromptOnDone) { - screen.clean(); - } else { - screen.clearContent(); - } - screen.done(); + screen.done({ clearContent: Boolean(context?.clearPromptOnDone) }); removeExitListener(); rl.input.removeListener('keypress', checkCursorPos); diff --git a/packages/core/src/lib/screen-manager.mts b/packages/core/src/lib/screen-manager.mts index ded3dc428..a09c84cc4 100644 --- a/packages/core/src/lib/screen-manager.mts +++ b/packages/core/src/lib/screen-manager.mts @@ -7,6 +7,10 @@ import type { InquirerReadline } from '@inquirer/type'; const height = (content: string): number => content.split('\n').length; const lastLine = (content: string): string => content.split('\n').pop() ?? ''; +function cursorDown(n: number): string { + return n > 0 ? ansiEscapes.cursorDown(n) : ''; +} + export default class ScreenManager { // These variables are keeping information to allow correct prompt re-rendering private height: number = 0; @@ -18,11 +22,14 @@ export default class ScreenManager { this.cursorPos = rl.getCursorPos(); } - render(content: string, bottomContent: string = '') { - /** - * Write message to screen and setPrompt to control backspace - */ + write(content: string) { + this.rl.output.unmute(); + this.rl.output.write(content); + this.rl.output.mute(); + } + render(content: string, bottomContent: string = '') { + // Write message to screen and setPrompt to control backspace const promptLine = lastLine(content); const rawPromptLine = stripAnsi(promptLine); @@ -69,63 +76,35 @@ export default class ScreenManager { // Return cursor to the initial left offset. output += ansiEscapes.cursorTo(this.cursorPos.cols); - this.clean(); - this.rl.output.unmute(); - /** - * Set up state for next re-rendering + * Render and store state for future re-rendering */ + this.write( + cursorDown(this.extraLinesUnderPrompt) + + ansiEscapes.eraseLines(this.height) + + output, + ); + this.extraLinesUnderPrompt = bottomContentHeight; this.height = height(output); - - this.rl.output.write(output); - this.rl.output.mute(); } checkCursorPos() { const cursorPos = this.rl.getCursorPos(); if (cursorPos.cols !== this.cursorPos.cols) { - this.rl.output.unmute(); - this.rl.output.write(ansiEscapes.cursorTo(cursorPos.cols)); - this.rl.output.mute(); + this.write(ansiEscapes.cursorTo(cursorPos.cols)); this.cursorPos = cursorPos; } } - clean() { - this.rl.output.unmute(); - this.rl.output.write( - [ - this.extraLinesUnderPrompt > 0 - ? ansiEscapes.cursorDown(this.extraLinesUnderPrompt) - : '', - ansiEscapes.eraseLines(this.height), - ].join(''), - ); + done({ clearContent }: { clearContent: boolean }) { + this.rl.setPrompt(''); - this.extraLinesUnderPrompt = 0; - this.rl.output.mute(); - } + let output = cursorDown(this.extraLinesUnderPrompt); + output += clearContent ? ansiEscapes.eraseLines(this.height) : '\n'; + output += ansiEscapes.cursorShow; + this.write(output); - clearContent() { - this.rl.output.unmute(); - // Reset the cursor at the end of the previously displayed content - this.rl.output.write( - [ - this.extraLinesUnderPrompt > 0 - ? ansiEscapes.cursorDown(this.extraLinesUnderPrompt) - : '', - '\n', - ].join(''), - ); - this.rl.output.mute(); - } - - done() { - this.rl.setPrompt(''); - this.rl.output.unmute(); - this.rl.output.write(ansiEscapes.cursorShow); - this.rl.output.end(); this.rl.close(); } }