73 lines
2.3 KiB
JavaScript
73 lines
2.3 KiB
JavaScript
import ansiEscapes from 'ansi-escapes';
|
|
import cliCursor from 'cli-cursor';
|
|
import stringWidth from 'string-width';
|
|
|
|
const create = (stream, { showCursor = false } = {}) => {
|
|
let previousLineCount = 0;
|
|
let previousOutput = '';
|
|
let hasHiddenCursor = false;
|
|
|
|
const renderWithClearedLineEnds = (output) => {
|
|
// On some terminals, writing to the last column leaves the cursor in a
|
|
// deferred-wrap state where CSI K (Erase in Line) erases the character
|
|
// at the final column instead of being a no-op. Skip the erase for
|
|
// lines that already fill the terminal width — there is nothing beyond
|
|
// them to clean up.
|
|
const cols = stream.columns || 80;
|
|
const lines = output.split('\n');
|
|
return lines.map((line) => {
|
|
if (stringWidth(line) >= cols) return line;
|
|
return line + ansiEscapes.eraseEndLine;
|
|
}).join('\n');
|
|
};
|
|
|
|
const render = (str) => {
|
|
if (!showCursor && !hasHiddenCursor) {
|
|
cliCursor.hide();
|
|
hasHiddenCursor = true;
|
|
}
|
|
|
|
const output = str + '\n';
|
|
if (output === previousOutput) {
|
|
return;
|
|
}
|
|
|
|
// Keep existing line-count semantics used by Ink's bundled log-update.
|
|
const nextLineCount = output.split('\n').length;
|
|
|
|
// Avoid eraseLines() pre-clear flashes by repainting in place:
|
|
// move to start of previous frame, rewrite each line while erasing EOL,
|
|
// then clear any trailing old lines if the frame got shorter.
|
|
if (previousLineCount > 1) {
|
|
stream.write(ansiEscapes.cursorUp(previousLineCount - 1));
|
|
}
|
|
stream.write(renderWithClearedLineEnds(output));
|
|
if (nextLineCount < previousLineCount) {
|
|
stream.write(ansiEscapes.eraseDown);
|
|
}
|
|
|
|
previousOutput = output;
|
|
previousLineCount = nextLineCount;
|
|
};
|
|
|
|
render.clear = () => {
|
|
stream.write(ansiEscapes.eraseLines(previousLineCount));
|
|
previousOutput = '';
|
|
previousLineCount = 0;
|
|
};
|
|
|
|
render.done = () => {
|
|
previousOutput = '';
|
|
previousLineCount = 0;
|
|
if (!showCursor) {
|
|
cliCursor.show();
|
|
hasHiddenCursor = false;
|
|
}
|
|
};
|
|
|
|
return render;
|
|
};
|
|
|
|
const logUpdate = { create };
|
|
export default logUpdate;
|