fix(tui): cleared stale streaming prefix caches

Cleared frozen-prefix render state after full-lex renders cannot establish a current appendable prefix, preventing truncated transient text from reusing spacer lines produced with a former tail.

Added regression coverage for truncating back to a previously frozen prefix.

Fixes #3975
This commit is contained in:
roboomp
2026-07-01 05:31:53 +02:00
committed by can1357
parent a36213a780
commit 3822a83b46
2 changed files with 23 additions and 3 deletions
+11 -3
View File
@@ -890,15 +890,16 @@ export class Markdown implements Component {
) {
const tailTokens = markdownParser.lexer(text.slice(prefix.length));
const tokens = [...prefixTokens, ...tailTokens];
this.#freezeStablePrefix(text, tokens);
this.#freezeStablePrefix(text, tokens, { preserveExisting: true });
return tokens;
}
const tokens = markdownParser.lexer(text);
if (canStream) {
this.#freezeStablePrefix(text, tokens);
this.#freezeStablePrefix(text, tokens, { preserveExisting: false });
} else {
this.#streamPrefixText = undefined;
this.#streamPrefixTokens = undefined;
this.#streamPrefixLineCache = undefined;
}
return tokens;
}
@@ -908,7 +909,7 @@ export class Markdown implements Component {
// render re-lexes only the unfrozen tail. Caller guarantees no CR / no
// reference definitions, so each token's `raw` is a verbatim slice of `text`
// and the summed offsets address `text` exactly.
#freezeStablePrefix(text: string, tokens: Token[]): void {
#freezeStablePrefix(text: string, tokens: Token[], opts: { preserveExisting: boolean }): void {
let pos = 0;
let frozenEnd = 0;
let frozenCount = 0;
@@ -937,8 +938,15 @@ export class Markdown implements Component {
if (next !== 0x20 /* space */ && next !== 0x0a /* \n */) {
this.#streamPrefixText = text.slice(0, frozenEnd);
this.#streamPrefixTokens = tokens.slice(0, frozenCount);
return;
}
}
if (!opts.preserveExisting) {
this.#streamPrefixText = undefined;
this.#streamPrefixTokens = undefined;
this.#streamPrefixLineCache = undefined;
}
}
render(width: number): readonly string[] {
@@ -78,4 +78,16 @@ describe("Markdown streaming prefix render cache", () => {
expect(codeBlockBorderCalls).toBe(0);
expect(streamingLines).toEqual(renderCold(thirdText, theme));
});
it("drops cached prefix lines after truncating to a previously frozen prefix", () => {
const prefix = "---\n\n";
const md = new Markdown(`${prefix}body`, 0, 0, defaultMarkdownTheme);
md.transientRenderCache = true;
md.render(WIDTH);
md.setText(prefix);
const streamingLines = md.render(WIDTH);
expect(streamingLines).toEqual(renderCold(prefix, defaultMarkdownTheme));
});
});