Skip to content

Commit

Permalink
Speed up generation of output (#564)
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Jul 16, 2023
1 parent f3f5720 commit 9ff4d20
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 9 deletions.
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -43,6 +43,7 @@
"text"
],
"dependencies": {
"@alcalzone/ansi-tokenize": "^0.1.1",
"ansi-escapes": "^6.0.0",
"auto-bind": "^5.0.1",
"chalk": "^5.2.0",
Expand Down
58 changes: 49 additions & 9 deletions src/output.ts
@@ -1,6 +1,12 @@
import sliceAnsi from 'slice-ansi';
import stringWidth from 'string-width';
import widestLine from 'widest-line';
import {
type StyledChar,
styledCharsFromTokens,
styledCharsToString,
tokenize
} from '@alcalzone/ansi-tokenize';
import {type OutputTransformer} from './render-node-to-output.js';

/**
Expand Down Expand Up @@ -92,10 +98,21 @@ export default class Output {

get(): {output: string; height: number} {
// Initialize output array with a specific set of rows, so that margin/padding at the bottom is preserved
const output: string[] = [];
const output: StyledChar[][] = [];

for (let y = 0; y < this.height; y++) {
output.push(' '.repeat(this.width));
const row: StyledChar[] = [];

for (let x = 0; x < this.width; x++) {
row.push({
type: 'char',
value: ' ',
fullWidth: false,
styles: []
});
}

output.push(row);
}

const clips: Clip[] = [];
Expand Down Expand Up @@ -178,23 +195,46 @@ export default class Output {
continue;
}

const width = stringWidth(line);

for (const transformer of transformers) {
line = transformer(line);
}

output[y + offsetY] =
sliceAnsi(currentLine, 0, x) +
line +
sliceAnsi(currentLine, x + width);
const characters = styledCharsFromTokens(tokenize(line));
let offsetX = x;

for (const character of characters) {
currentLine[offsetX] = character;

// Some characters take up more than one column. In that case, the following
// pixels need to be cleared to avoid printing extra characters
const isWideCharacter =
character.fullWidth || character.value.length > 1;

if (isWideCharacter) {
currentLine[offsetX + 1] = {
type: 'char',
value: '',
fullWidth: false,
styles: character.styles
};
}

offsetX += isWideCharacter ? 2 : 1;
}

offsetY++;
}
}
}

const generatedOutput = output.map(line => line.trimEnd()).join('\n');
const generatedOutput = output
.map(line => {
// See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
const lineWithoutEmptyItems = line.filter(item => item !== undefined);

return styledCharsToString(lineWithoutEmptyItems).trimEnd();
})
.join('\n');

return {
output: generatedOutput,
Expand Down
23 changes: 23 additions & 0 deletions test/overflow.tsx
Expand Up @@ -501,3 +501,26 @@ test('nested overflow', t => {

t.is(output, 'AA\nBB\nXXXX\nYYYY\n');
});

// See https://github.com/vadimdemedes/ink/pull/564#issuecomment-1637022742
test('out of bounds writes do not crash', t => {
const output = renderToString(
<Box width={12} height={10} borderStyle="round" />,
{columns: 10}
);

const expected = boxen('', {
width: 12,
height: 10,
borderStyle: 'round'
})
.split('\n')
.map((line, index) => {
return index === 0 || index === 9
? line
: `${line.slice(0, 10)}${line[11] ?? ''}`;
})
.join('\n');

t.is(output, expected);
});

0 comments on commit 9ff4d20

Please sign in to comment.