feat(coding-agent): defer uncommon syntax grammars

This commit is contained in:
Armin Ronacher 2026-08-22 22:50:12 +02:00
parent 39d869f02a
commit cec3a91c02
6 changed files with 162 additions and 21 deletions

View file

@ -8,6 +8,7 @@
### Changed
- Changed syntax highlighting to initialize only twenty common languages eagerly and defer the remaining grammars until after the initial TUI render, reducing CLI startup time.
- Changed the Node.js CLI and RPC entrypoints to load a bundled runtime, reducing startup filesystem reads while keeping the public library and legacy module paths on the modular runtime for normal dependency identity.
- Changed session sharing to render clickable terminal links and Radius shares to display only the artifact's canonical URL.

View file

@ -111,6 +111,7 @@ import { openBrowser } from "../../utils/open-browser.ts";
import { getCwdRelativePath } from "../../utils/paths.ts";
import { getPiUserAgent } from "../../utils/pi-user-agent.ts";
import { killTrackedDetachedChildren } from "../../utils/shell.ts";
import { loadAllHighlightLanguages } from "../../utils/syntax-highlight.ts";
import { ensureTool, type ToolStatus } from "../../utils/tools-manager.ts";
import { checkForNewPiVersion, type LatestPiRelease } from "../../utils/version-check.ts";
import { ArminComponent } from "./components/armin.ts";
@ -1051,6 +1052,14 @@ export class InteractiveMode {
// Initialize available provider count for footer display
await this.updateAvailableProviderCount();
// Flush the completed startup state before loading the remaining syntax grammars.
this.ui.renderNow();
void loadAllHighlightLanguages().then(() => {
if (!this.isInitialized) return;
this.ui.invalidate();
this.ui.requestRender();
});
}
/**

View file

@ -1,19 +0,0 @@
declare module "highlight.js/lib/index.js" {
interface HighlightResult {
value: string;
}
interface HighlightOptions {
language: string;
ignoreIllegals?: boolean;
}
interface HighlightJs {
highlight(code: string, options: HighlightOptions): HighlightResult;
highlightAuto(code: string, languageSubset?: string[]): HighlightResult;
getLanguage(name: string): unknown;
}
const hljs: HighlightJs;
export default hljs;
}

View file

@ -0,0 +1,36 @@
interface HighlightJsResult {
value: string;
}
interface HighlightJsOptions {
language: string;
ignoreIllegals?: boolean;
}
interface HighlightJsLanguageDefinition {
readonly name?: string;
}
type HighlightJsLanguageFactory = (hljs: HighlightJsApi) => HighlightJsLanguageDefinition;
interface HighlightJsApi {
highlight(code: string, options: HighlightJsOptions): HighlightJsResult;
highlightAuto(code: string, languageSubset?: string[]): HighlightJsResult;
getLanguage(name: string): HighlightJsLanguageDefinition | undefined;
registerLanguage(name: string, language: HighlightJsLanguageFactory): void;
}
declare module "highlight.js/lib/core.js" {
const hljs: HighlightJsApi;
export default hljs;
}
declare module "highlight.js/lib/index.js" {
const hljs: HighlightJsApi;
export default hljs;
}
declare module "highlight.js/lib/languages/*.js" {
const language: HighlightJsLanguageFactory;
export default language;
}

View file

@ -1,6 +1,83 @@
import hljs from "highlight.js/lib/index.js";
import { createRequire } from "node:module";
import hljs from "highlight.js/lib/core.js";
import bash from "highlight.js/lib/languages/bash.js";
import c from "highlight.js/lib/languages/c.js";
import cpp from "highlight.js/lib/languages/cpp.js";
import csharp from "highlight.js/lib/languages/csharp.js";
import dart from "highlight.js/lib/languages/dart.js";
import go from "highlight.js/lib/languages/go.js";
import groovy from "highlight.js/lib/languages/groovy.js";
import java from "highlight.js/lib/languages/java.js";
import javascript from "highlight.js/lib/languages/javascript.js";
import kotlin from "highlight.js/lib/languages/kotlin.js";
import lua from "highlight.js/lib/languages/lua.js";
import nix from "highlight.js/lib/languages/nix.js";
import perl from "highlight.js/lib/languages/perl.js";
import php from "highlight.js/lib/languages/php.js";
import python from "highlight.js/lib/languages/python.js";
import ruby from "highlight.js/lib/languages/ruby.js";
import rust from "highlight.js/lib/languages/rust.js";
import scala from "highlight.js/lib/languages/scala.js";
import swift from "highlight.js/lib/languages/swift.js";
import typescript from "highlight.js/lib/languages/typescript.js";
import { decodeHtmlEntityAt } from "./html.ts";
const moduleRequire = createRequire(import.meta.url);
declare const PI_BUNDLED_NODE: boolean;
declare const Bun: unknown;
declare const require: (specifier: string) => unknown;
const eagerLanguages = {
python,
java,
go,
javascript,
cpp,
typescript,
php,
ruby,
c,
csharp,
nix,
bash,
rust,
scala,
kotlin,
swift,
dart,
groovy,
perl,
lua,
};
for (const [name, language] of Object.entries(eagerLanguages)) {
hljs.registerLanguage(name, language);
}
let allLanguagesPromise: Promise<void> | undefined;
export function loadAllHighlightLanguages(): Promise<void> {
if (!allLanguagesPromise) {
allLanguagesPromise = new Promise((resolve) => {
setImmediate(() => {
try {
// Static require lets esbuild and Bun include this deferred module in
// standalone bundles. Source and unbundled Node runtimes need createRequire.
if ((typeof PI_BUNDLED_NODE !== "undefined" && PI_BUNDLED_NODE) || typeof Bun !== "undefined") {
require("highlight.js/lib/index.js");
} else {
moduleRequire("highlight.js/lib/index.js");
}
} catch {
// Eager languages and plaintext fallback remain available.
}
resolve();
});
});
}
return allLanguagesPromise;
}
export type HighlightFormatter = (text: string) => string;
export type HighlightTheme = Partial<Record<string, HighlightFormatter>>;

View file

@ -1,9 +1,46 @@
import { resetCapabilitiesCache, setCapabilities } from "@earendil-works/pi-tui";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { highlightCode, initTheme } from "../src/modes/interactive/theme/theme.ts";
import { highlight, renderHighlightedHtml, supportsLanguage } from "../src/utils/syntax-highlight.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('<span class="hljs-keyword">const</span> value', {
keyword: (text) => `[keyword:${text}]`,