Chroma 语法高亮器解析:纯 Go 实现的通用语法高亮库及其在 Loki 生态中的应用

发布时间:2026/9/12 17:54:43
Chroma 语法高亮器解析:纯 Go 实现的通用语法高亮库及其在 Loki 生态中的应用 Chroma 语法高亮器解析纯 Go 实现的通用语法高亮库及其在 Loki 生态中的应用【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki本文系统讲解随当前仓库Grafana Loki一起 vendored 的 Chroma v2——一个基于 Pygments 理念、以纯 Go 编写的通用语法高亮库覆盖其词法分析器Lexer、格式化器Formatter与配色风格Style三大核心模型、Go 库 API、HTML 格式化选项与命令行工具并结合仓库源码说明它在 Loki 工具链如lokitool中的真实落地方式。读完本文你将能够独立使用 Chroma 将任意源码或结构化文本高亮为 HTML、ANSI 终端彩色文本等格式并能在自己的 Go 项目中复刻 Loki 的高亮输出方案。Chroma 是什么Chroma 是一个用纯 Go 编写的通用语法高亮器它接收源代码及其他结构化文本并将其转换为语法高亮的 HTML、ANSI 彩色文本等输出。Chroma 在很大程度上基于 Pygments 的设计思想构建并且内置了将 Pygments 的词法分析器lexers和样式styles转换为 Chroma 格式的翻译工具。在当前仓库中Chroma v2 以依赖形式被 vendor 在 vendor/github.com/alecthomas/chroma/v2 目录下作为 Loki 项目内部工具链的语法高亮引擎使用详见下文Chroma 在 Loki 仓库中的实际使用一节。Chroma 的核心抽象与 Pygments 一一对应由三个相互协作的概念组成词法分析器Lexers将源文本转换为 Token 流样式Styles定义 Token 类型如何映射为颜色格式化器Formatters将 Token 与样式转换为最终的格式化输出。这三个概念在仓库中各自有独立包lexers、formatters、styles。每个包内部都持有一个全局Registry变量保存所有已注册的实现同时提供辅助函数用于按名称查找词法分析器、按文件名匹配、格式化输出等操作。在所有场景中如果无法确定某个 lexer、formatter 或 style对应 API 会返回nil此时可以回退到各包中的Fallback值——它提供了合理的默认实现。支持的编程语言Chroma 内置了数量庞大的词法分析器覆盖主流及小众编程语言、配置文件格式、标记语言与 DSL。下表按语言名称首字母分组列出以当前 vendor 目录中的版本为准前缀语言AABAP, ABNF, ActionScript, ActionScript 3, Ada, Agda, AL, Alloy, AMPL, Angular2, ANTLR, ApacheConf, APL, AppleScript, ArangoDB AQL, Arduino, ArmAsm, Arturo, ATL, AutoHotkey, AutoIt, AwkBBallerina, Bash, Bash Session, Batchfile, Beef, BibTeX, Bicep, BlitzBasic, BNF, BQN, BrainfuckCC, C#, C, C3, Caddyfile, Caddyfile Directives, Capn Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Chapel, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Core, Crystal, CSS, CSV, CUE, CythonDD, Dart, Dax, Desktop file, Devicetree, Diff, Django/Jinja, dns, Docker, DTD, DylanEEBNF, Elixir, Elm, EmacsLisp, ERB, ErlangFFactor, Fennel, Fish, Forth, Fortran, FortranFixed, FSharpGGAS, GDScript, GDScript3, Gemtext, Genshi, Genshi HTML, Genshi Text, Gettext, Gherkin, Gleam, GLSL, Gnuplot, Go, Go HTML Template, Go Template, Go Text Template, GraphQL, Groff, GroovyHHandlebars, Hare, Haskell, Haxe, HCL, Hexdump, HLB, HLSL, HolyC, HTML, HTTP, HyIIdris, Igor, INI, Io, ISCdhcpdJJ, Janet, Java, JavaScript, JSON, JSONata, Jsonnet, Julia, JungleKKakoune, KDL, KotlinLLateralus, Lean4, Lighttpd configuration file, LilyPond, LLVM, lox, Lua, LuauMMakefile, Mako, markdown, Markless, Mason, Materialize SQL dialect, Mathematica, Matlab, MCFunction, Meson, Metal, microcad, MiniZinc, MLIR, Modelica, Modula-2, Mojo, MonkeyC, MoonBit, MoonScript, MorrowindScript, Myghty, MySQLNNASM, Natural, NDISASM, Newspeak, Nginx configuration file, Nim, Nix, NSIS, NuOObjective-C, ObjectPascal, OCaml, Octave, Odin, OnesEnterprise, OpenEdge ABL, OpenSCAD, Org ModePPacmanConf, Perl, PHP, PHTML, Pig, PkgConfig, PL/pgSQL, plaintext, Plutus Core, Pony, PostgreSQL SQL dialect, PostScript, POVRay, PowerQuery, PowerShell, Prolog, Promela, PromQL, properties, Protocol Buffer, Protocol Buffer Text Format, PRQL, PSL, Puppet, Python, Python 2QQBasic, QMLRR, Racket, Ragel, Raku, react, ReasonML, reg, Rego, reStructuredText, Rexx, RGBDS Assembly, Ring, RPGLE, RPMSpec, Ruby, RustSSAS, Sass, Scala, scdoc, Scheme, Scilab, SCSS, Sed, Sieve, Smali, Smalltalk, Smarty, SNBT, Snobol, Solidity, SourcePawn, Spade, SPARQL, SQL, SquidConf, Standard ML, stas, Stylus, Svelte, Swift, SYSTEMD, systemverilogTTableGen, Tal, TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData, TypstUucodeVV, V shell, Vala, VB.net, verilog, VHDL, VHS, VimL, vueWWDTE, WebAssembly Text Format, WebGPU Shading Language, WebVTT, WhileyXXML, XorgYYAML, YANGZZ80 Assembly, Zed, Zig上表可能滞后于实际实现最权威的列表可以通过命令行工具输出chroma --list。值得关注的是表中包含多个与可观测性领域高度相关的语言PromQLPrometheus 查询语言Loki 的 LogQL 姊妹语言、YAML几乎所有配置文件以及Go Template / Go Text Template等。这些正是 Loki 工具链在输出告警规则、配置文件时实际使用的高亮目标见下文。Chroma 在 Loki 仓库中的实际使用Chroma 并非仅作为理论上可用的依赖存在于本仓库而是真实服务于 Loki 的命令行工具链。在 pkg/tool/printer/printer.go 中Loki 的lokitool使用github.com/alecthomas/chroma/v2/quick包为各类 YAML/JSON/模板输出添加终端彩色高亮import github.com/alecthomas/chroma/v2/quick err : quick.Highlight(os.Stdout, config, yaml, terminal, swapoff)从源码结构看Printer提供以下高亮输出场景PrintAlertmanagerConfig以yaml词法器高亮 Alertmanager 配置并以go-text-template高亮其中的模板文件PrintRuleGroups/PrintRuleGroup将规则组 YAML 编码后以yaml高亮输出PrintRuleSet在json/yaml两种输出格式下分别以对应词法器高亮。上述调用统一使用terminal格式化器与swapoff风格——这正是quick.Highlight一行式 API 的典型用法无需手动组装 lexer、formatter、style全部交给 quick 包尽力而为地解析。同时Printer通过disableColor标志在关闭彩色输出时直接回退到普通fmt.Println体现了高亮是增强、可降级的工程实践。使用 Chroma Go 库Chroma 当前是第 2 个大版本导入路径为import github.com/alecthomas/chroma/v2快速开始一行完成高亮quick包提供了一个免配置的便捷函数Highlight可以零成本地把一段源码直接格式化输出到任意io.Writererr : quick.Highlight(os.Stdout, someSourceCode, go, html, monokai)结合 vendor/github.com/alecthomas/chroma/v2/quick/quick.go 的实现可以看到它的尽力而为逻辑先用lexers.Get(lexer)按名称查找词法分析器找不到则用lexers.Analyse(source)根据内容探测再找不到则回退到lexers.Fallback对词法分析器调用chroma.Coalesce做合并优化分别用formatters.Get与styles.Get解析格式化器和风格失败时回退到各自Fallback最后执行l.Tokenise(nil, source)生成 Token 迭代器交给f.Format(w, s, it)完成输出。也就是说即使你传空的 lexer/formatter/style 名称quick.Highlight也会以最佳努力给出可用的高亮结果。识别语言三种方式要高亮代码首先需要确定这段代码是什么语言。Chroma 提供三种主要途径返回nil表示无法识别1. 根据文件名探测语言lexer : lexers.Match(foo.go)2. 显式指定语言的 Chroma 语法 ID完整列表可通过lexers.Names()获取lexer : lexers.Get(go)3. 根据内容分析语言lexer : lexers.Analyse(package main\n\nfunc main()\n{\n}\n)无论哪种方式识别失败都会返回nil此时建议回退到默认值if lexer nil { lexer lexers.Fallback }另外某些词法分析器输出的 Token 可能极其零碎chatty。可以用 coalescing lexer 把连续的相同 Token 类型合并为单个 Token从而得到更干净的输出lexer chroma.Coalesce(lexer)格式化输出组装 Style 与 Formatter语言确定后需要挑选一个 formatter 和一个 style主题style : styles.Get(swapoff) if style nil { style styles.Fallback } formatter : formatters.Get(html) if formatter nil { formatter formatters.Fallback }然后对源文本执行 Token 化获得 Token 迭代器contents, err : ioutil.ReadAll(r) iterator, err : lexer.Tokenise(nil, string(contents))最后把迭代器中的 Token 交给 formatter 渲染err : formatter.Format(w, style, iterator)HTML 格式化器的高级选项默认情况下html注册的格式化器生成内嵌 CSS 的独立standaloneHTML 文档若要获得更高的灵活性应使用 formatters/html 包。可以通过构造选项定制输出行为Standalone()——生成内嵌 CSS 的独立 HTMLWithClasses()——使用 CSS 类名而非内联 style 属性ClassPrefix(prefix)——为生成的每个 CSS 类添加前缀TabWidth(width)——设置渲染时的 Tab 宽度字符数WithLineNumbers()——渲染行号用LineNumbers样式修饰WithLinkableLineNumbers()——使行号可链接且链接指向自身HighlightLines(ranges)——高亮指定范围内的行用LineHighlight样式修饰LineNumbersInTable()——用表格而非 span 来布局行号与代码。当启用WithClasses()后可通过WriteCSS导出对应 CSSformatter : html.New(html.WithClasses(true)) err : formatter.WriteCSS(w, style)从 vendor/github.com/alecthomas/chroma/v2/formatters/api.go 可以看到注册表内名为html的默认格式化器其实正是html.New(html.Standalone(true), html.WithClasses(true))的产物——即独立 HTML CSS 类组合这解释了为什么默认输出自带样式。深入三大组件Lexers词法分析器Chroma 的词法分析器实现方式与 Pygments 高度一致。若需了解如何实现 lexer可参考 Pygments 的 lexer 开发文档Chroma 的大多数概念与之直接对应更直观的做法是直接阅读仓库中已有的 lexer 实现作为真实范例。在多数情况下lexer 可以直接通过自带的 Python 3 脚本pygments2chroma_xml.py从 Pygments 自动转换而来转换命令形如uv run --script _tools/pygments2chroma_xml.py \ pygments.lexers.jvm.KotlinLexer \ lexers/embedded/kotlin.xml即输入 Pygments 的 lexer 全限定类名输出 Chroma 的 XML 定义文件。这也是 Chroma 能迅速覆盖大量语言的原因——绝大多数 lexer 由 Pygments 生态自动移植。Chroma 目前对 lexer 的定义策略是除需要自定义逻辑的场景外所有 lexer 都用 XML 定义见 vendor/github.com/alecthomas/chroma/v2/lexers/README.md。Formatters格式化器Chroma 支持以下输出形式HTML 输出用于 Web 页面渲染终端输出支持 8 色、256 色以及 true-colour真彩三种级别对应terminal、terminal256、terminal16m等格式化器源码位于 vendor/github.com/alecthomas/chroma/v2/formatters其中 tty_indexed.go 与 tty_truecolour.go 分别实现索引色与真彩输出noop格式化器仅输出 Token 文本本身不做任何修饰tokens格式化器输出原始 Token 流是调试 lexer 的利器svg格式化器可输出 SVG 图形格式源码在 vendor/github.com/alecthomas/chroma/v2/formatters/svg。Fallback格式化器是noop见 api.go保证即使找不到格式化器也能原样输出文本。Styles配色风格Chroma 的样式以 XML 定义其条目语法与 Pygments 相同全部 Pygments 风格均已通过_tools/style.py脚本转换为 Chroma 格式。样式名称不区分大小写例如monokai与Monokai被视为同一样式。当前仓库 vendor/github.com/alecthomas/chroma/v2/styles 目录中内置了 70 套风格涵盖经典与流行主题例如monokai、monokailight、dracula、solarized-dark、solarized-light、github、github-dark、nord、onedark、tokyonight-*系列、catppuccin-*系列、gruvbox、xcode、swapoff等。使用风格时有两个关键机制需要理解1.BackgroundToken 提供默认样式Backgroundtoken 类型通过定义前景色与背景色为未显式指定的 Token 提供默认样式。例如下面这条 XML 给所有未定义的 Token 名设置默认前景色#f8f8f2并让高亮代码块的背景色为#000000entry typeBackground style#f8f8f2 bg:#000000/2. Token 类型具有层级继承样式文件中的 Token 类型是分层的。例如当CommentSpecial未定义时Chroma 会使用Comment的样式。因此当多个注释类 Token 使用同一颜色时只需定义Comment一次再单独覆盖颜色不同的那一个即可大幅精简样式文件。命令行接口Chroma 附带了一个命令行工具chroma可用于直接对文件进行语法高亮输出作为less(1)的着色预处理器配合LESSOPEN环境变量使用生成支持语言的权威清单chroma --list。--fail标志用于抑制输出并以退出码 1 返回方便在 chroma 无法为给定文件解析出词法分析器时回退到其他预处理器。例如export LESSOPEN| p() { chroma --fail $1 || cat $1; }; p %s把其中的cat换成你偏好的回退预处理器即可。当可执行文件被以.lessfilter名称调用时--fail标志会在底层自动开启以便与 Debian 及其衍生发行版自带的 lesspipe 集成用于其用户自定义过滤器机制。对于该场景只需把chroma可执行文件软链接到~/.lessfilter即可。测试词法分析器如果修改了某个 lexer 并想本地验证效果可以进入cmd/chromad目录启动本地调试服务go run . --csrf-keysecurekey启动后会打印一个链接在浏览器中打开它即可在 Playground 上使用本地修改进行测试。若要运行词法分析器测试在项目根目录执行go test ./lexers根据 vendor/github.com/alecthomas/chroma/v2/lexers/README.md 的说明lexer 测试的机制是把testdata/name.actual中的已知输入喂给对应解析器然后校验输出是否与name.expected一致同一个 parser 也可以通过testdata/name/目录存放多个*.actual输入来执行多组测试。新增或更新 lexer 时官方要求补充对应测试。当新增测试数据文件*.actual后需要重新生成全部测试期望文件使用RECORD环境变量RECORDtrue go test ./lexers该命令先设置RECORDtrue再运行测试该环境变量告知 Chroma 输出测试数据跑完后即可移除或重置该变量。Windows 用户在标准命令提示符与 PowerShell 下需分两步执行先用set RECORDtruecmd或$env:RECORD truePowerShell设置环境变量再单独运行go test ./lexers。与 Pygments 相比还缺什么Chroma 虽然移植了大量 Pygments 能力但官方文档也坦承存在如下差距仍有不少 lexer 未移植原因包括Pygments 中复杂语言的 lexer 往往包含大量自定义代码来处理特殊语法例如 Raku 在正则表达式内嵌套代码的能力转换需要大量时间与精力同时为控制移植成本最初只转换了作者本人听说过的语言欢迎提交 pull request 补充Pygments 部分更偏门的功能被有意省略以保持实现简洁内容探测Analyse支持较弱虽然 Chroma API 支持基于内容的语言检测但目前只有极少数语言实现了这一能力官方有计划引入统计算法器但尚未落地。对使用者而言这意味着以文件名匹配lexers.Match或显式指定 IDlexers.Get的方式通常最可靠而lexers.Analyse仅在少数语言上有效——Loki 工具链之所以总是显式传入yaml、json、go-text-template等词法器名称正是对这一限制的工程化规避。小结Chroma v2 为 Go 生态提供了一套完整、纯原生、无需 CGO 依赖的语法高亮方案以 Pygments 为蓝本的 Lexer/Style/Formatter 三元模型、内置数百种语言与数十套主题、quick一行式 API、可高度定制的 HTML 输出以及可作为 less 预处理器的 CLI。在本仓库中它已实际服务于 Loki 的lokitool输出高亮是仓库内可复用的通用基础组件的典型代表。无论是构建自己的代码展示工具、终端日志着色器还是为 CLI 增加配置预览能力Chroma 都能以极小的接入成本提供专业的语法着色体验。【免费下载链接】lokiLike Prometheus, but for logs.项目地址: https://gitcode.com/GitHub_Trending/lok/loki创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考