Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/utils/__tests__/renderer-separator-collapse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,39 @@ describe('renderer separator collapse around empty widgets', () => {
expect(out).toBe('A:C');
});

it.each([
{ label: 'two spaces', characters: [' ', ' '], expected: 'A B' },
{ label: 'two pipes', characters: ['|', '|'], expected: 'A | | B' },
{ label: 'a space then a pipe', characters: [' ', '|'], expected: 'A | B' },
{ label: 'a glyph between spaces', characters: [' ', '•', ' '], expected: 'A • B' }
])('keeps separators placed next to each other between visible widgets: $label', ({ characters, expected }) => {
const separators = characters.map((character, i): WidgetItem => ({ id: `sep-${i}`, type: 'separator', character }));
const widgets = [T('a'), ...separators, T('b')];
const out = stripSgrCodes(render(widgets, { 0: 'A', [widgets.length - 1]: 'B' }));

expect(out).toBe(expected);
});

it('keeps a run of separators with a visible one as the boundary around an empty widget', () => {
const space1: WidgetItem = { id: 'space1', type: 'separator', character: ' ' };
const bullet: WidgetItem = { id: 'bullet', type: 'separator', character: '•' };
const space2: WidgetItem = { id: 'space2', type: 'separator', character: ' ' };
const pipe: WidgetItem = { id: 'pipe', type: 'separator', character: '|' };
const widgets = [T('a'), space1, bullet, space2, T('b'), pipe, T('c')];
const out = stripSgrCodes(render(widgets, { 0: 'A', 4: '', 6: 'C' }));
expect(out).toBe('A • C');
});

it('replaces a run of spacing before an empty widget with the whole run after it', () => {
const space1: WidgetItem = { id: 'space1', type: 'separator', character: ' ' };
const space2: WidgetItem = { id: 'space2', type: 'separator', character: ' ' };
const space3: WidgetItem = { id: 'space3', type: 'separator', character: ' ' };
const pipe: WidgetItem = { id: 'pipe', type: 'separator', character: '|' };
const widgets = [T('a'), space1, space2, T('b'), space3, pipe, T('c')];
const out = stripSgrCodes(render(widgets, { 0: 'A', 3: '', 6: 'C' }));
expect(out).toBe('A | C');
});

it('collapses multiple consecutive empty widgets into a single separator', () => {
const widgets = [T('a'), SEP, T('b'), SEP, T('c'), SEP, T('d')];
// both 'b' and 'c' render empty
Expand Down
13 changes: 12 additions & 1 deletion src/utils/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1102,14 +1102,22 @@ export function renderStatusLine(
// and the preceding visible content. A visible separator owns that
// boundary, while spacing-only separators can be replaced by a
// more meaningful separator that follows the empty widget.
// Separators placed next to each other form one run and all
// render; only a run on the far side of an empty widget can
// own or give up the boundary.
let contentBeforeIndex: number | null = null;
let replacesSpacingSeparator = false;
let followsSeparatorInRun = false;
let crossedEmptyWidget = false;
for (let j = i - 1; j >= 0; j--) {
const prevWidget = widgets[j];
if (!prevWidget)
continue;
if (prevWidget.type === 'separator') {
if (!crossedEmptyWidget) {
followsSeparatorInRun = true;
continue;
}
if (!isSpacingSeparator(prevWidget, settings.defaultSeparator))
break;
replacesSpacingSeparator = true;
Expand All @@ -1128,7 +1136,10 @@ export function renderStatusLine(
if (contentBeforeIndex === null)
continue;

if (replacesSpacingSeparator) {
// Only the first separator of a run removes the spacing it
// replaces; the rest of the run would remove that run's own
// separators.
if (replacesSpacingSeparator && !followsSeparatorInRun) {
while (isSpacingSeparator(elements[elements.length - 1]?.widget, settings.defaultSeparator)) {
elements.pop();
}
Expand Down
Loading