diff --git a/CHANGELOG.md b/CHANGELOG.md index 365d41df729..78f661815fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai ### Changes +- TUI/dependencies: remove direct `cli-highlight` usage from the OpenClaw TUI code-block renderer, keeping themed code coloring without the extra root dependency. Thanks @vincentkoc. - Plugins/activation: expose activation plan reasons and a richer plan API so callers can inspect why a plugin was selected while preserving existing id-list activation behavior. (#70943) Thanks @vincentkoc. - Plugins/source metadata: expose normalized install-source facts on provider and channel catalogs so onboarding can explain npm pinning, integrity state, and local availability before runtime loads. (#70951) Thanks @vincentkoc. - Plugins/catalog: pin the official external WeCom channel source to an exact npm release plus dist integrity, with a guard that official external sources stay integrity-pinned. (#70997) Thanks @vincentkoc. diff --git a/package.json b/package.json index 13f6dee469c..eadfe71cb8f 100644 --- a/package.json +++ b/package.json @@ -1594,7 +1594,6 @@ "ajv": "^8.18.0", "chalk": "^5.6.2", "chokidar": "^5.0.0", - "cli-highlight": "^2.1.11", "commander": "^14.0.3", "croner": "^10.0.1", "dotenv": "^17.4.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d91301feb5..39a1143c614 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,9 +84,6 @@ importers: chokidar: specifier: ^5.0.0 version: 5.0.0 - cli-highlight: - specifier: ^2.1.11 - version: 2.1.11 commander: specifier: ^14.0.3 version: 14.0.3 diff --git a/scripts/lib/dependency-ownership.json b/scripts/lib/dependency-ownership.json index 285970d1abd..fda625ed3c6 100644 --- a/scripts/lib/dependency-ownership.json +++ b/scripts/lib/dependency-ownership.json @@ -76,11 +76,6 @@ "class": "core-runtime", "risk": ["filesystem-watch"] }, - "cli-highlight": { - "owner": "capability:tui", - "class": "default-runtime-initially", - "risk": ["syntax-highlighting", "large-transitive-cone"] - }, "commander": { "owner": "core:cli", "class": "core-runtime", diff --git a/src/tui/theme/syntax-theme.ts b/src/tui/theme/syntax-theme.ts deleted file mode 100644 index d0aea2d5a9c..00000000000 --- a/src/tui/theme/syntax-theme.ts +++ /dev/null @@ -1,100 +0,0 @@ -import chalk from "chalk"; - -type HighlightTheme = Record string>; - -/** - * Syntax highlighting theme for code blocks. - * Uses chalk functions to style different token types. - */ -export function createSyntaxTheme( - fallback: (text: string) => string, - light = false, -): HighlightTheme { - if (light) { - return { - keyword: chalk.hex("#AF00DB"), - built_in: chalk.hex("#267F99"), - type: chalk.hex("#267F99"), - literal: chalk.hex("#0000FF"), - number: chalk.hex("#098658"), - string: chalk.hex("#A31515"), - regexp: chalk.hex("#811F3F"), - symbol: chalk.hex("#098658"), - class: chalk.hex("#267F99"), - function: chalk.hex("#795E26"), - title: chalk.hex("#795E26"), - params: chalk.hex("#001080"), - comment: chalk.hex("#008000"), - doctag: chalk.hex("#008000"), - meta: chalk.hex("#001080"), - "meta-keyword": chalk.hex("#AF00DB"), - "meta-string": chalk.hex("#A31515"), - section: chalk.hex("#795E26"), - tag: chalk.hex("#800000"), - name: chalk.hex("#001080"), - attr: chalk.hex("#C50000"), - attribute: chalk.hex("#C50000"), - variable: chalk.hex("#001080"), - bullet: chalk.hex("#795E26"), - code: chalk.hex("#A31515"), - emphasis: chalk.italic, - strong: chalk.bold, - formula: chalk.hex("#AF00DB"), - link: chalk.hex("#267F99"), - quote: chalk.hex("#008000"), - addition: chalk.hex("#098658"), - deletion: chalk.hex("#A31515"), - "selector-tag": chalk.hex("#800000"), - "selector-id": chalk.hex("#800000"), - "selector-class": chalk.hex("#800000"), - "selector-attr": chalk.hex("#800000"), - "selector-pseudo": chalk.hex("#800000"), - "template-tag": chalk.hex("#AF00DB"), - "template-variable": chalk.hex("#001080"), - default: fallback, - }; - } - - return { - keyword: chalk.hex("#C586C0"), // purple - if, const, function, etc. - built_in: chalk.hex("#4EC9B0"), // teal - console, Math, etc. - type: chalk.hex("#4EC9B0"), // teal - types - literal: chalk.hex("#569CD6"), // blue - true, false, null - number: chalk.hex("#B5CEA8"), // green - numbers - string: chalk.hex("#CE9178"), // orange - strings - regexp: chalk.hex("#D16969"), // red - regex - symbol: chalk.hex("#B5CEA8"), // green - symbols - class: chalk.hex("#4EC9B0"), // teal - class names - function: chalk.hex("#DCDCAA"), // yellow - function names - title: chalk.hex("#DCDCAA"), // yellow - titles/names - params: chalk.hex("#9CDCFE"), // light blue - parameters - comment: chalk.hex("#6A9955"), // green - comments - doctag: chalk.hex("#608B4E"), // darker green - jsdoc tags - meta: chalk.hex("#9CDCFE"), // light blue - meta/preprocessor - "meta-keyword": chalk.hex("#C586C0"), // purple - "meta-string": chalk.hex("#CE9178"), // orange - section: chalk.hex("#DCDCAA"), // yellow - sections - tag: chalk.hex("#569CD6"), // blue - HTML/XML tags - name: chalk.hex("#9CDCFE"), // light blue - tag names - attr: chalk.hex("#9CDCFE"), // light blue - attributes - attribute: chalk.hex("#9CDCFE"), // light blue - attributes - variable: chalk.hex("#9CDCFE"), // light blue - variables - bullet: chalk.hex("#D7BA7D"), // gold - list bullets in markdown - code: chalk.hex("#CE9178"), // orange - inline code - emphasis: chalk.italic, // italic - strong: chalk.bold, // bold - formula: chalk.hex("#C586C0"), // purple - math - link: chalk.hex("#4EC9B0"), // teal - links - quote: chalk.hex("#6A9955"), // green - quotes - addition: chalk.hex("#B5CEA8"), // green - diff additions - deletion: chalk.hex("#F44747"), // red - diff deletions - "selector-tag": chalk.hex("#D7BA7D"), // gold - CSS selectors - "selector-id": chalk.hex("#D7BA7D"), // gold - "selector-class": chalk.hex("#D7BA7D"), // gold - "selector-attr": chalk.hex("#D7BA7D"), // gold - "selector-pseudo": chalk.hex("#D7BA7D"), // gold - "template-tag": chalk.hex("#C586C0"), // purple - "template-variable": chalk.hex("#9CDCFE"), // light blue - default: fallback, // fallback to code color - }; -} diff --git a/src/tui/theme/theme.test.ts b/src/tui/theme/theme.test.ts index c5e3d9f7458..2c0aed489f1 100644 --- a/src/tui/theme/theme.test.ts +++ b/src/tui/theme/theme.test.ts @@ -1,11 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; - -const cliHighlightMocks = vi.hoisted(() => ({ - highlight: vi.fn((code: string) => code), - supportsLanguage: vi.fn((_lang: string) => true), -})); - -vi.mock("cli-highlight", () => cliHighlightMocks); +import { afterEach, describe, expect, it, vi } from "vitest"; const { markdownTheme, searchableSelectListTheme, selectListTheme, theme } = await import("./theme.js"); @@ -34,41 +27,14 @@ function contrastRatio(foreground: string, background: string): number { describe("markdownTheme", () => { describe("highlightCode", () => { - beforeEach(() => { - cliHighlightMocks.highlight.mockClear(); - cliHighlightMocks.supportsLanguage.mockClear(); - cliHighlightMocks.highlight.mockImplementation((code: string) => code); - cliHighlightMocks.supportsLanguage.mockReturnValue(true); - }); - - it("passes supported language through to the highlighter", () => { - markdownTheme.highlightCode!("const x = 42;", "javascript"); - expect(cliHighlightMocks.supportsLanguage).toHaveBeenCalledWith("javascript"); - expect(cliHighlightMocks.highlight).toHaveBeenCalledWith( - "const x = 42;", - expect.objectContaining({ language: "javascript" }), - ); - }); - - it("falls back to auto-detect for unknown language and preserves lines", () => { - cliHighlightMocks.supportsLanguage.mockReturnValue(false); - cliHighlightMocks.highlight.mockImplementation((code: string) => `${code}\nline-2`); + it("renders code blocks with the theme code color and preserves lines", () => { const result = markdownTheme.highlightCode!(`echo "hello"`, "not-a-real-language"); - expect(cliHighlightMocks.highlight).toHaveBeenCalledWith( - `echo "hello"`, - expect.objectContaining({ language: undefined }), - ); expect(stripAnsi(result[0] ?? "")).toContain("echo"); - expect(stripAnsi(result[1] ?? "")).toBe("line-2"); }); - it("returns plain highlighted lines when highlighting throws", () => { - cliHighlightMocks.highlight.mockImplementation(() => { - throw new Error("boom"); - }); - const result = markdownTheme.highlightCode!("echo hello", "javascript"); - expect(result).toHaveLength(1); - expect(stripAnsi(result[0] ?? "")).toBe("echo hello"); + it("preserves multi-line code blocks", () => { + const result = markdownTheme.highlightCode!("line-1\nline-2", "javascript"); + expect(result.map((line) => stripAnsi(line))).toEqual(["line-1", "line-2"]); }); }); }); diff --git a/src/tui/theme/theme.ts b/src/tui/theme/theme.ts index 910c8437018..31b72506810 100644 --- a/src/tui/theme/theme.ts +++ b/src/tui/theme/theme.ts @@ -5,10 +5,8 @@ import type { SettingsListTheme, } from "@mariozechner/pi-tui"; import chalk from "chalk"; -import { highlight, supportsLanguage } from "cli-highlight"; import { normalizeOptionalLowercaseString } from "../../shared/string-coerce.js"; import type { SearchableSelectListTheme } from "../components/searchable-select-list.js"; -import { createSyntaxTheme } from "./syntax-theme.js"; const DARK_TEXT = "#E8E3D5"; const LIGHT_TEXT = "#1E1E1E"; @@ -132,27 +130,12 @@ export const palette = lightMode ? lightPalette : darkPalette; const fg = (hex: string) => (text: string) => chalk.hex(hex)(text); const bg = (hex: string) => (text: string) => chalk.bgHex(hex)(text); -const syntaxTheme = createSyntaxTheme(fg(palette.code), lightMode); - /** - * Highlight code with syntax coloring. + * Render code blocks with the theme code color without pulling a parser into the base TUI path. * Returns an array of lines with ANSI escape codes. */ -function highlightCode(code: string, lang?: string): string[] { - try { - // Auto-detect can be slow for very large blocks; prefer explicit language when available. - // Check if language is supported, fall back to auto-detect - const language = lang && supportsLanguage(lang) ? lang : undefined; - const highlighted = highlight(code, { - language, - theme: syntaxTheme, - ignoreIllegals: true, - }); - return highlighted.split("\n"); - } catch { - // If highlighting fails, return plain code - return code.split("\n").map((line) => fg(palette.code)(line)); - } +function highlightCode(code: string): string[] { + return code.split("\n").map((line) => fg(palette.code)(line)); } export const theme = { diff --git a/src/types/cli-highlight.d.ts b/src/types/cli-highlight.d.ts deleted file mode 100644 index ab4ad92fb21..00000000000 --- a/src/types/cli-highlight.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -declare module "cli-highlight" { - export type HighlightOptions = { - language?: string; - theme?: unknown; - ignoreIllegals?: boolean; - }; - - export function highlight(code: string, options?: HighlightOptions): string; - export function supportsLanguage(language: string): boolean; -}