TUI 定制与主题
当前 TUI 不是一个“只能改颜色的黑盒”,但它也还不是插件化 UI 平台。
最准确的理解是:
- 使用层已经有一套可直接落地的 theme / config 机制
- Java 层还保留了 renderer / runtime / factory 级扩展点
- 但这些扩展仍然是代码注入式定制,不是前端插件市场
1. 先看 TUI 装配链
当前 TUI 相关装配大致会走这条链:
CodeCommand -> CodingCliSessionRunner
-> TuiConfigManager.load(...)
-> TuiConfigManager.resolveTheme(...)
-> DefaultCodingCliTuiFactory.create(...)
-> TuiSessionView
-> AppendOnlyTuiRuntime 或 AnsiTuiRuntime
这里最重要的是:
- theme、config、renderer、runtime 是分层装配的
- 不是一个
TUI对象自己把所有事情都做完
这也是为什么你能分别改:
- 配置
- 主题
- 渲染器
- runtime 行为
2. 当前可直接使用的定制点
最直接可用的是两层:
- theme
tui.json
2.1 CLI 启动参数里的 theme override
--theme <name>
这个值来自 CodeCommandOptionsParser,随后被 TuiConfigManager.load(overrideTheme) 读入。
一个很关键的细节是:
--theme只是本次启动的 in-memory override- 它不会自动落盘到
tui.json
2.2 会话内 /theme
当前 /theme 命令支持:
/theme
/theme <name>
当你执行 /theme <name> 时,CodingCliSessionRunner.applyTheme(...) 会调用:
TuiConfigManager.switchTheme(themeName)
而这个方法会:
- 校验主题是否存在
- 读取当前配置
- 把
config.theme切到新主题 - 保存到 workspace
tui.json
所以 /theme 是持久化切换,不是一次性预览。
3. 主题查找顺序不是拍脑袋决定的
TuiConfigManager.resolveTheme(name) 当前查找顺序非常明确:
<workspace>/.ai4j/themes/<name>.json~/.ai4j/themes/<name>.json- 内置资源
/io/github/lnyocly/ai4j/tui/themes/<name>.json - 如果还找不到且不是
default,回退到default - 最后再兜底成代码生成的默认主题
所以当前主题优先级是:
- workspace 自定义
- home 自定义
- built-in
- hardcoded fallback
这意味着团队完全可以:
- 在仓库里放一份 repo-specific theme
而不影响用户 home 目录里的其他 TUI 使用场景。
4. 配置文件本身有两层,但不是字段级 merge
当前支持的配置文件位置是:
<workspace>/.ai4j/tui.json~/.ai4j/tui.json
很多人会下意识以为:
- home 配置提供默认值
- workspace 配置只覆盖个别字段
但当前 TuiConfigManager.merge(base, override) 不是字段级 merge。
它的行为更接近:
- 如果 workspace config 存在,就直接用 workspace config
- 否则才回退到 home config
所以当前语义是:
- 文件级 override
而不是:
- 字段级层叠合并
这点非常关键,因为它决定了你不能假设:
- home 里设置了
showFooter=false - workspace 只写了
theme=ocean - 最后一定会保留
showFooter=false
当前实现不保证这种字段级继承。
5. tui.json 当前真正控制哪些行为
TuiConfig 当前核心字段包括:
themedenseModeshowTimestampsshowFootermaxEventsuseAlternateScreen
同时 TuiConfigManager.normalize(config) 会保证:
- 空 theme 回退成
default maxEvents <= 0时回退成10
所以这里也不是“任意值原样放行”,而是有最小归一化规则。
6. built-in themes 只是起点,不是全部
当前 TuiConfigManager 内置的主题名字是:
defaultamberoceanmatrixgithub-darkgithub-light
但 listThemeNames() 会把三类来源合并:
- built-in names
- home 目录自定义 theme
- workspace 目录自定义 theme
这也是为什么 /theme 列出来的不一定只有内置主题。
它会把用户和仓库自定义的主题一并列出来。
7. 主题文件里哪些字段真的重要
TuiTheme 会被 TuiConfigManager.normalize(theme, fallbackName) 补齐大量默认值。
当前重点字段包括:
brandaccentsuccesswarningdangertextmutedpanelBorderpanelTitlebadgeForegroundcodeBackgroundcodeBordercodeTextcodeKeywordcodeStringcodeCommentcodeNumber
这说明主题不只是“主色 + 辅色”。
它已经覆盖了:
- transcript
- panel
- badge
- code block
- syntax highlight
如果你只改一两个字段,其他字段会继续沿用 normalize 后的默认值。
8. DefaultCodingCliTuiFactory 真正决定了什么
这是当前最值得直接读的开发层入口。
它在 create(...) 里做四件事:
- 读取
TuiConfig - 解析
TuiTheme - 构造
TuiSessionView作为 renderer - 根据 terminal 和
useAlternateScreen选择 runtime
也就是说,它不只是“生产一个 TUI 对象”,而是在决定:
- 用什么配置
- 用什么主题
- 用什么 renderer
- 用什么 runtime backend
9. 为什么会在 AppendOnlyTuiRuntime 和 AnsiTuiRuntime 之间分叉
当前 runtime 选择规则是:
- 如果
useAlternateScreen=false且 terminal 是JlineTerminalIO- 使用
AppendOnlyTuiRuntime
- 使用
- 否则
- 使用
AnsiTuiRuntime
- 使用
这意味着 useAlternateScreen 不是一个纯视觉偏好。
它会直接影响 runtime backend 的选择。
工程上可以这样理解:
AppendOnlyTuiRuntime
更接近:
- 追加式终端输出
- 对 JLINE 终端更友好
AnsiTuiRuntime
更接近:
- 带 renderer 的完整屏幕刷新模型
- alternate screen 或非 JLINE 终端的统一后备路径
所以不要把 useAlternateScreen 理解成“只改一下终端清屏方式”。
10. /theme 切换时,真正更新了哪些对象
CodingCliSessionRunner.applyTheme(...) 当前会:
- 切
TuiConfigManager.switchTheme(...) - 重新
resolveTheme(...) - 如果是
JlineShellTerminalIO,更新 shell terminal 的 theme styler - 如果当前有 TUI renderer,也调用
tuiRenderer.updateTheme(config, theme) - 刷新当前会话输出提示
这说明 theme 切换不是“下次打开 TUI 再生效”。
它会即时影响:
- shell transcript 样式
- renderer 使用的主题
- 后续 TUI 呈现
11. 更深一层的扩展点分别适合什么
改配色、品牌风格、代码高亮颜色
优先改:
TuiTheme
改显示密度、时间戳、footer、事件数
优先改:
TuiConfig
改布局、状态栏结构、消息板块呈现
优先改:
TuiRenderer- 当前默认实现是
TuiSessionView
改屏幕刷新模式、alternate screen 策略、运行时交互壳
优先改:
TuiRuntime- 或更上一层的
CodingCliTuiFactory
这四层不要混改,否则最后很难判断某个行为到底是配置、主题、渲染器还是 runtime 导致的。
12. 当前边界是什么
当前 TUI 的扩展边界可以直接概括成一句话:
- 开箱可配置,但还不是插件生态
也就是说:
- theme 和
tui.json已经是正式使用层能力 CodingCliTuiFactory、TuiRenderer、TuiRuntime是开发层扩展点- 还没有做成“用户下载一个 UI 插件包就能热插拔”的系统
所以如果你要做深度定制,当前预期仍然应该是:
- 在 Java 层接入自定义实现
13. 最容易踩坑的 5 个点
13.1 以为 home 和 workspace tui.json 会字段级 merge
当前不是,workspace 文件存在时更接近整体覆盖。
13.2 以为 --theme 会自动落盘
当前只是启动时 override,不会保存。
13.3 以为 useAlternateScreen 只是观感开关
它会直接改变 runtime backend 选择。
13.4 只改 theme,却期待布局也变
布局属于 renderer 层,不属于 theme 层。
13.5 只改 renderer,却忽略 terminal backend 差异
AppendOnlyTuiRuntime 和 AnsiTuiRuntime 的交互模型并不完全相同。
14. 这页最该记住的结论
- 当前 TUI 定制分成 config、theme、renderer、runtime 四层
- theme 查找顺序是 workspace > home > built-in > default fallback
tui.json当前更接近文件级 override,不是字段级 merge--theme是一次性 override,/theme才会持久化写回 workspace configuseAlternateScreen会影响 runtime backend,而不只是视觉模式