fix(coding-agent): keep syntax colors on multiline tokens (#10356)

* fix(coding-agent): keep syntax colors on multiline tokens

- format each line of a highlighted span separately
- add regression test for python docstrings
- fixes #10143

* fix(coding-agent): use text color for string interpolations

- map highlight.js `subst` scope to text color
This commit is contained in:
Ramiz Wachtler 2026-10-05 10:35:43 +02:00 • committed by GitHub
parent 997d31f286
commit b9ab918c62
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 2 deletions

View file

@ -2,6 +2,10 @@
## [Unreleased]
### Fixed
- Fixed syntax highlighting losing colors after the first line of multiline strings and comments in fenced code blocks ([#10143](https://github.com/earendil-works/pi/issues/10143))
## [1.0.3] - 2026-10-05
### New Features

View file

@ -966,6 +966,7 @@ function buildCliHighlightTheme(t: Theme): CliHighlightTheme {
number: (s: string) => t.fg("syntaxNumber", s),
regexp: (s: string) => t.fg("syntaxString", s),
string: (s: string) => t.fg("syntaxString", s),
subst: (s: string) => t.fg("text", s),
comment: (s: string) => t.fg("syntaxComment", s),
doctag: (s: string) => t.fg("syntaxComment", s),
meta: (s: string) => t.fg("muted", s),

View file

@ -155,7 +155,12 @@ export function renderHighlightedHtml(html: string, theme: HighlightTheme = {}):
return;
}
const formatter = getActiveFormatter(scopes, theme);
output += formatter ? formatter(textBuffer) : textBuffer;
output += formatter
? textBuffer
.split("\n")
.map((line) => (line ? formatter(line) : line))
.join("\n")
: textBuffer;
textBuffer = "";
};

View file

@ -1,6 +1,6 @@
import { resetCapabilitiesCache, setCapabilities } from "@earendil-works/pi-tui";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { highlightCode, initTheme, theme } from "../src/modes/interactive/theme/theme.ts";
import { getMarkdownTheme, highlightCode, initTheme, theme } from "../src/modes/interactive/theme/theme.ts";
import {
highlight,
loadAllHighlightLanguages,
@ -96,6 +96,23 @@ describe("theme syntax highlighting", () => {
resetCapabilitiesCache();
});
// #10143
it("colors each line of Python docstrings independently", () => {
const code = '"""\nline one\n\nline two\n"""\nafter';
const ansi = theme.getFgAnsi("syntaxString");
const expected = [
`${ansi}"""\x1b[39m`,
`${ansi}line one\x1b[39m`,
"",
`${ansi}line two\x1b[39m`,
`${ansi}"""\x1b[39m`,
"after",
];
expect(highlightCode(code, "python")).toEqual(expected);
expect(getMarkdownTheme().highlightCode?.(code, "python")).toEqual(expected);
});
it("colors diff additions and deletions in fenced diff blocks", () => {
const lines = highlightCode("-old\n+new\n", "diff");