mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
The `read` and `write` tool views find a file's language from its
extension with `getLanguageFromPath()`. That function's extension table
had no entries for Dart, Nix, or Groovy. Their highlight.js grammars are
loaded at startup, but the lookup returned `undefined`, so these files
were shown as plain uncolored text.
This change adds `dart → dart`, `nix → nix`, `groovy → groovy`, and
`gradle → groovy` to the table in
`packages/coding-agent/src/modes/interactive/theme/theme.ts`.
HTML export keeps its own separate extension map and a vendored
highlight bundle without a Dart grammar. This change does not touch
either.
## Testing
- Added a test to `test/syntax-highlight.test.ts` that checks the new
extension mappings. It fails without the fix and passes with it.
- `node ../../node_modules/vitest/dist/cli.js --run
test/syntax-highlight.test.ts` passes.
- Before, `getLanguageFromPath("a.dart")` (and `.nix`, `.groovy`,
`build.gradle`) returned `undefined` from the built dist. After, they
return `dart`, `nix`, `groovy`, and `groovy`.
- `npm run check` reports 6 type errors in `packages/ai/test` (model ID
types). Main without this change has the same 6 errors.
Fixes #11643
Requested by @badlogic
Co-authored-by: triagent <triagent@localhost>
144 lines
4.4 KiB
TypeScript
144 lines
4.4 KiB
TypeScript
import { resetCapabilitiesCache, setCapabilities } from "@earendil-works/pi-tui";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import {
|
|
getLanguageFromPath,
|
|
getMarkdownTheme,
|
|
highlightCode,
|
|
initTheme,
|
|
theme,
|
|
} from "../src/modes/interactive/theme/theme.ts";
|
|
import {
|
|
highlight,
|
|
loadAllHighlightLanguages,
|
|
renderHighlightedHtml,
|
|
supportsLanguage,
|
|
} from "../src/utils/syntax-highlight.ts";
|
|
|
|
const eagerLanguages = [
|
|
"python",
|
|
"java",
|
|
"go",
|
|
"javascript",
|
|
"cpp",
|
|
"typescript",
|
|
"php",
|
|
"ruby",
|
|
"c",
|
|
"csharp",
|
|
"nix",
|
|
"bash",
|
|
"rust",
|
|
"scala",
|
|
"kotlin",
|
|
"swift",
|
|
"dart",
|
|
"groovy",
|
|
"perl",
|
|
"lua",
|
|
];
|
|
const eagerLanguagesLoadedAtStartup = eagerLanguages.every(supportsLanguage);
|
|
const uncommonLanguageLoadedAtStartup = supportsLanguage("ada");
|
|
|
|
describe("syntax highlight renderer", () => {
|
|
it("loads the twenty most common languages at startup and defers the rest", async () => {
|
|
expect(eagerLanguagesLoadedAtStartup).toBe(true);
|
|
expect(uncommonLanguageLoadedAtStartup).toBe(false);
|
|
await loadAllHighlightLanguages();
|
|
expect(supportsLanguage("ada")).toBe(true);
|
|
});
|
|
|
|
it("maps file extensions of eagerly loaded languages to their language", () => {
|
|
expect(getLanguageFromPath("lib/main.dart")).toBe("dart");
|
|
expect(getLanguageFromPath("flake.nix")).toBe("nix");
|
|
expect(getLanguageFromPath("script.groovy")).toBe("groovy");
|
|
expect(getLanguageFromPath("build.gradle")).toBe("groovy");
|
|
expect(getLanguageFromPath("src/Main.kt")).toBe("kotlin");
|
|
});
|
|
|
|
it("renders highlighted spans with the provided theme", () => {
|
|
const rendered = renderHighlightedHtml('<span class="hljs-keyword">const</span> value', {
|
|
keyword: (text) => `[keyword:${text}]`,
|
|
});
|
|
expect(rendered).toBe("[keyword:const] value");
|
|
});
|
|
|
|
it("decodes HTML entities emitted by highlight.js", () => {
|
|
const rendered = renderHighlightedHtml("<tag attr="value">&#x41;A</tag>");
|
|
expect(rendered).toBe('<tag attr="value">AA</tag>');
|
|
});
|
|
|
|
it("inherits parent formatting for unmapped nested scopes", () => {
|
|
const interpolation = "$" + "{x}";
|
|
const rendered = renderHighlightedHtml(
|
|
`<span class="hljs-string">a<span class="hljs-subst">${interpolation}</span>b</span>`,
|
|
{
|
|
string: (text) => `[string:${text}]`,
|
|
},
|
|
);
|
|
expect(rendered).toBe(`[string:a][string:${interpolation}][string:b]`);
|
|
});
|
|
|
|
it("keeps parent formatting across unscoped nested spans", () => {
|
|
const rendered = renderHighlightedHtml('<span class="hljs-string">a<span class="language-xml">b</span>c</span>', {
|
|
string: (text) => `[string:${text}]`,
|
|
});
|
|
expect(rendered).toBe("[string:a][string:b][string:c]");
|
|
});
|
|
|
|
it("highlights code through highlight.js", () => {
|
|
expect(supportsLanguage("typescript")).toBe(true);
|
|
const rendered = highlight("const value = 1", {
|
|
language: "typescript",
|
|
ignoreIllegals: true,
|
|
theme: {
|
|
keyword: (text) => `[keyword:${text}]`,
|
|
number: (text) => `[number:${text}]`,
|
|
},
|
|
});
|
|
expect(rendered).toContain("[keyword:const]");
|
|
expect(rendered).toContain("[number:1]");
|
|
});
|
|
});
|
|
|
|
describe("theme syntax highlighting", () => {
|
|
beforeEach(() => {
|
|
setCapabilities({ images: null, trueColor: true, hyperlinks: false });
|
|
initTheme("dark");
|
|
});
|
|
|
|
afterEach(() => {
|
|
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");
|
|
|
|
expect(lines[0]).toBe(`${theme.getFgAnsi("toolDiffRemoved")}-old\x1b[39m`);
|
|
expect(lines[1]).toBe(`${theme.getFgAnsi("toolDiffAdded")}+new\x1b[39m`);
|
|
});
|
|
|
|
it("keeps cli-highlight default styled scopes mapped to theme styles", () => {
|
|
expect(highlightCode("const re = /foo+/gi;", "javascript")[0]).toContain(
|
|
`${theme.getFgAnsi("syntaxString")}/foo+/gi\x1b[39m`,
|
|
);
|
|
expect(highlightCode("@decorator", "python")[0]).toBe(`${theme.getFgAnsi("muted")}@decorator\x1b[39m`);
|
|
expect(highlightCode("<div></div>", "html")[0]).toContain(`${theme.getFgAnsi("syntaxKeyword")}div\x1b[39m`);
|
|
});
|
|
});
|