# 主题与终端适配 · 在五花八门的终端里都好看

> 从 truecolor 到 16 色、从 iTerm 到 ConHost、从跟随系统明暗到 OSC 11 探测——让配色在任意终端里都不崩，是一大堆不起眼的适配。这一章看 grok 的语义主题、颜色能力探测与降级、终端品牌识别。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/theming-terminal
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 主题与终端适配 · 在五花八门的终端里都好看

一个终端产品要面对的现实：用户的终端千差万别——truecolor 还是 16 色、深色还是浅色、iTerm 还是 Windows ConHost、裸终端还是 tmux+SSH。让配色在**任意**终端里都不崩，是一大堆不起眼的适配(都在 `xai-grok-pager-render`)。

## 语义主题

`Theme` 是约 60 个**语义**颜色字段：`accent_user`/`accent_assistant`/`accent_thinking`/`accent_tool`、`bg_base`、`diff_delete_bg`/`diff_insert_bg`……设计原则是**全部走语义字段**，模块注释直说别处不许硬编码颜色(代码块的语法高亮另走内置的 `.tmTheme`)。`ThemeKind` 有 GrokNight(默认)、GrokDay、TokyoNight、RosePineMoon、OscuraMidnight，其中几个 `requires_truecolor()` 的主题在非 truecolor 终端会被自动降级成 GrokNight。还有一个灰度中的 `Terminal` 主题：背景全用 `Reset`，让终端自己的底色透出来，不依赖明暗探测，要打开 `features.terminal_theme` 才看得到。

## 颜色能力探测与降级

`Theme::current()` 是热点访问器(缓存的是主题种类和颜色等级，`Theme` 本身按需构造)，干这几件事：探测颜色等级 → Windows 上先做对比度增强(ConHost 的 gamma 会让深色 RGB 挤成一团；增强只对 RGB 生效，所以必须排在量化之前)→ 量化颜色 → 在 16 色/老 ConHost 上做 ANSI-16 chrome 覆盖(否则深色 RGB 全塌成黑)。

`ColorLevel` 分 `None / Basic / Ansi256 / TrueColor`，探测(`detect`)很讲究：`NO_COLOR` → None；否则用 `supports-color` crate；**非 TTY → TrueColor**(TUI 安全默认)；若探到 < TrueColor 但终端品牌是 iTerm2、Ghostty、Kitty、WezTerm、Alacritty、Rio、Warp、VS Code、Windows Terminal、Foot 之一(原生 Windows 也算)→ **升级到 TrueColor**——因为 tmux/SSH 常把 `COLORTERM` 剥掉，靠品牌名把它补回来。量化时 `Rgb → 最近 256 色 / 最近 16 色`(平方欧氏距离)。

## 跟随系统明暗

`Auto` 主题要跟随系统深浅色，探测是一条四级链(前一级没结果才走下一级)：① `dark_light` crate 读桌面 API；② 环境变量 `GROK_APPEARANCE` / `LC_GROK_APPEARANCE`(后者能搭 SSH 的 `LC_*` 顺风车，`grok wrap` 会自动写入本地的明暗)；③ 启动期**向终端发 `OSC 11` 查询背景色**，读回复、按 ITU-R BT.709 亮度阈值判明暗；④ 最后才看 `COLORFGBG`。OSC 11 查询必须在 crossterm 抢占 stdin **之前**跑，自己临时管 termios；在 tmux 里还会改用 DCS 包装的查询再试一次，结果缓存下来供之后的轮询复用。

## 终端品牌识别

`TerminalContext` 认约 20 个终端品牌(`TerminalName` 是 19 个具名品牌加 `Unknown`，VS Code、Cursor、Windsurf、Zed、JetBrains 这些 IDE 的集成终端都算品牌)+ 多路复用器(tmux/screen/Zellij 等)+ 是否 SSH + 内嵌编辑器(Neovim/Vim/Emacs 里的 `:terminal`)。这些驱动一堆能力判断：键盘协议(Kitty keyboard)、超链接(OSC 8)支持、图形协议、是否会带外重绘 pane。识别靠环境变量，外加少量探测：只有在 tmux 里才会跑 `tmux -V`、`tmux show-option` 这类子进程；XTVERSION、DA2 则是直接发给终端的带内查询。

## pager.toml

外观配置是两层：`Raw*Config`(从 `~/.grok/pager.toml` 反序列化，字段自带文档)→ 运行时 `AppearanceConfig`。它在启动时读一次，改完重启生效——代码里的 watcher 只剩静态模式，注释里「开发模式热重载」的说法已跟不上实现；`/settings` 里改的部分外观项则会实时生效并回写这个文件。它控制 alt-screen 模式、动画、prompt、scrollback 布局等；主题、时间戳这类开关则在 `config.toml` 的 `[ui]` 里(比如 `ui.theme`，也可以用 `/theme` 切换)。排查配色问题时可以跑 `grok doctor`(会话里是 `/doctor`，旧名 `/terminal-setup` 仍是它的别名)，看颜色等级和当前终端能用哪些主题。

## 和《从 LLM 到 Coding Agent》对照

教学实现根本不管终端差异——ANSI 转义直接吐。grok 的增量是**能力门控的降级**贯穿始终：颜色 truecolor→256→16→无、主题按能力钳制、按品牌反推 truecolor、多级探测明暗、约 20 个品牌识别。这些代码不产生任何「功能」，却决定产品在**你的**终端里是好看还是花屏。下一章看更炫也更硬核的：在终端里显示图片、以及让复制穿透 SSH。

---

上一篇：[diff 与工具调用可视化](https://daiw.org/manual/grok-build/diff-visualization) · 下一篇：[PTY 包装与内联媒体](https://daiw.org/manual/grok-build/pty-wrap-media)
