Ghostty 源码拆解

配置与输入:文本配置如何变成 Action

拆解配置加载、条件配置、keybind 解析和跨平台输入边界

配置与输入:文本配置如何变成 Action

Ghostty 的配置系统并不是启动时读一次 key-value 就结束。它要处理默认文件、用户文件、CLI 覆盖、条件配置、主题、keybind、动态更新、C API 和 WASM。输入系统也不是“收到字符就写 PTY”,而是先判断键盘/鼠标是否被 Ghostty 自己的 binding 或终端模式消费。

配置的四个阶段

可以把 src/config/ 看成一条管道:

Config.init
  → load default files
  → load explicit / recursive files
  → load CLI args
  → apply conditional state
  → finalize / validate / diagnostics
  → derive per-surface runtime config

src/config/Config.zig 聚合字段和解析器;file_load.zigio.zigconditional.zigformatter.zigpath.zig 等把 IO、条件表达式、格式化和路径语义分开。src/config/CApi.zig 再把同一套对象变成 ghostty_config_* API,避免 Swift 或其他宿主各写一套配置解释器。

为什么有 DerivedConfig

TermioSurface、renderer 不长期持有一个共享 Config*,而是把自己需要的字段复制/派生到 arena 管理的 DerivedConfig。这样做牺牲少量复制,换取两个不变量:

  • 配置热更新时,旧 surface 不会突然读取到已经释放或半更新的字符串;
  • 每个线程拥有稳定的、只读倾向的配置快照,减少锁和生命周期耦合。

例如 Termio.DerivedConfig.init 会计算 256 色 palette、复制 enquiry-response、保存 clipboard 权限、cursor、foreground/background 和 conditional state。它是“配置文本”到“终端运行时语义”的转换点。

条件配置与热更新

配置可以受系统 light/dark、焦点或其他环境状态影响。App.updateConfig 先把配置消息发给已有 surface,再尝试用 config_conditional_state 应用 app 级条件状态,最后通知 runtime。Surface 自己维护 surface 级 conditional state。

这不是单纯的全量替换:源码明确区分 app-level 和 surface-level config,并允许应用失败时保留旧 config 继续运行。正确的阅读方式是把配置更新看作一个可失败的事务边界:输入是一份新配置,输出是各 surface 的可用派生快照和 runtime action。

keybind 解析

src/input/Binding.zig 把人类可读的 binding 语法解析为结构化 trigger/action:修饰键、物理/逻辑 key、按下/释放/repeat、参数化 action、key table 都会在这里归一化。src/input/key_mods.zig 专门处理 modifier 组合和平台侧差异。

action 的参数不直接塞进字符串执行,而是先解析成类型安全的 union/enum,再在 SurfaceApp 上执行。这样可以让 split, resize_split, write_screen_file, goto_tab 等动作拥有不同参数类型,并能在配置加载时尽早报错。

输入决策树

native key event
  → Surface.key
  → focus / readonly / app focus checks
  → binding match?
       ├─ yes: perform action; mark consumed
       └─ no
           → terminal keyboard mode encoding
           → enqueue write request
           → pty

鼠标路径类似,但要额外判断 terminal 的 mouse reporting mode、selection gesture、链接 hover、scrollback 和 clipboard。InputEffect 明确区分 ignoredconsumedclosed,尤其 closed 之后 surface 指针可能已不安全,调用方必须立即退出当前事件处理。

设计取舍

  • 解析时失败:配置和 binding 尽量在 load/finalize 阶段产生 diagnostics,避免运行到热路径才发现拼写错误。
  • 输入在 core 判断:平台只提供原始事件和 callback,是否消费由 core 统一决定,防止 macOS/GTK 的快捷键行为漂移。
  • 写入异步化:事件处理不直接阻塞写 PTY,转为 mailbox 请求交给 writer thread。
  • 只读不是禁用所有操作Surface.readonly 只阻止 PTY 输入,选择、滚动、复制等 terminal-level 操作仍然可用。

On this page