import { resetCapabilitiesCache, setCapabilities } from "@earendil-works/pi-tui"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { 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("renders highlighted spans with the provided theme", () => { const rendered = renderHighlightedHtml('const 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('AA'); }); it("inherits parent formatting for unmapped nested scopes", () => { const interpolation = "$" + "{x}"; const rendered = renderHighlightedHtml( `a${interpolation}b`, { string: (text) => `[string:${text}]`, }, ); expect(rendered).toBe(`[string:a][string:${interpolation}][string:b]`); }); it("keeps parent formatting across unscoped nested spans", () => { const rendered = renderHighlightedHtml('abc', { 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("
", "html")[0]).toContain(`${theme.getFgAnsi("syntaxKeyword")}div\x1b[39m`); }); });