Ghostty libghostty-vt 实战:使用 Grid Reference API 逐格遍历终端网格
【免费下载链接】ghostty👻 Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.项目地址: https://gitcode.com/GitHub_Trending/gh/ghostty
本文围绕仓库示例c-vt-grid-traverse展开,讲解如何用 Ghostty 的 C 库libghostty-vt创建一个终端、写入含样式的内容,再通过 grid reference(网格引用)API 逐格遍历网格,检查每个单元格的码点、行的换行状态(wrap)以及单元格样式。读完后你将掌握GhosttyPoint坐标系统、GhosttyGridRef的获取与读取、单元格/行/样式的查询方法,以及这套 API 的生命周期规则与性能边界,可直接用于构建自定义的网格检查、搜索、快照或渲染前置逻辑。
示例概览:它在验证什么
示例说明文档 指出,该示例演示了ghostty-vt终端与网格引用 API 的完整组合:
- 创建一个小型终端(10 列 × 3 行);
- 通过 VT 字节流写入三行内容,其中第三行带加粗样式(
ESC[1mBold); - 遍历整个网格,逐格解析出码点、行 wrap 状态和单元格样式并打印。
示例同时说明了构建方式:示例程序用build.zig和 Zig 构建系统编译,这样可以直接依赖 Ghostty 源码树并复用其构建逻辑;但 Ghostty 本身会产出一个标准 C 库,任何 C 工具链(CMake、GCC 等)都可以使用,参见仓库中example/c-vt-cmake等姊妹示例。
按 example/README.md 的统一约定,运行方式为:
cd example/c-vt-grid-traverse zig build run构建方式:通过 Zig 构建系统链接 ghostty-vt
该示例并没有把 C 代码交给 CMake,而是用 build.zig 定义了一个独立的 Zig 构建工程。关键逻辑有三步:
// 1. 把 src/ 下的 main.c 作为 C 源文件加入构建 exe_mod.addCSourceFiles(.{ .root = b.path("src"), .files = &.{"main.c"}, }); // 2. 用 lazyDependency 引入 ghostty 依赖,链接其产出的 ghostty-vt 库 if (b.lazyDependency("ghostty", .{ // Setting simd to false will force a pure static build that // doesn't even require libc, but it has a significant performance // penalty. If your embedding app requires libc anyway, you should // always keep simd enabled. // .simd = false, })) |dep| { exe_mod.linkLibrary(dep.artifact("ghostty-vt")); } // 3. 产出可执行文件 c_vt_grid_traverse 并挂到 run step const exe = b.addExecutable(.{ .name = "c_vt_grid_traverse", .root_module = exe_mod, });几点值得注意:
lazyDependency("ghostty", ...)表示只有真正需要时才会拉取 ghostty 依赖;- 注释中提醒:若显式设置
.simd = false,会得到不依赖 libc 的纯静态构建,但有明显性能代价;如果你的宿主程序本来就需要 libc,应保持 SIMD 开启; - 可执行文件名用下划线(
c_vt_grid_traverse)而不是连字符,这是 example/AGENTS.md 中示例目录的统一约定。
完整源码解析:从创建终端到逐格遍历
下面结合 src/main.c 逐段讲解。该文件的主体代码被//! [grid-ref-traverse]标记包裹,这是 Ghostty 的 Doxygen snippet 约定:头文件 grid_ref.h 通过@snippet c-vt-grid-traverse/src/main.c grid-ref-traverse直接引用这段代码,避免在头文件中重复粘贴示例代码。
1. 创建终端并写入内容
GhosttyTerminal terminal; GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 10, 3); assert(result == GHOSTTY_SUCCESS); const char *text = "Hello!\r\n" // Row 0: H e l l o ! "World\r\n" // Row 1: W o r l d "\033[1mBold"; // Row 2: B o l d (bold style) ghostty_terminal_vt_write(terminal, (const uint8_t *)text, strlen(text));ghostty_terminal_new的第一个参数是可选的自定义分配器,传NULL即使用默认分配器;后两个参数分别是列数 10 和行数 3。写入的内容故意设计得"有看点":前两行是普通文本且短于 10 列(用于区分"有文字"与"空白"的格子),第三行带\033[1m(SGR bold),用于验证样式读取。
注意 terminal.h 中声明的ghostty_terminal_vt_write是无返回值函数,字节流由内部解析器直接消费并更新网格状态。
2. 获取网格尺寸
uint16_t cols, rows; ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_COLS, &cols); ghostty_terminal_get(terminal, GHOSTTY_TERMINAL_DATA_ROWS, &rows);ghostty_terminal_get是终端查询接口,GHOSTTY_TERMINAL_DATA_COLS/GHOSTTY_TERMINAL_DATA_ROWS两个枚举值定义在 terminal.h 中,分别返回当前终端的列数与行数。遍历循环以这两个值作为边界,即使后续改动了ghostty_terminal_new的尺寸参数,遍历逻辑也无需修改。
3. 把坐标解析为 Grid Ref
遍历的每个格子都通过"点"(GhosttyPoint)解析得到网格引用:
GhosttyGridRef ref = GHOSTTY_INIT_SIZED(GhosttyGridRef); GhosttyPoint pt = { .tag = GHOSTTY_POINT_TAG_ACTIVE, .value = { .coordinate = { .x = col, .y = row } }, }; result = ghostty_terminal_grid_ref(terminal, pt, &ref); assert(result == GHOSTTY_SUCCESS);这里涉及两个关键类型,定义在 point.h:
GhosttyPoint是一个带标签的联合体,tag字段决定坐标系,value.coordinate提供 x/y 坐标(x 为 0 起始列,y 为 0 起始行);GhosttyPointTag有四种取值:GHOSTTY_POINT_TAG_ACTIVE:光标可移动的活跃区域(本示例使用);GHOSTTY_POINT_TAG_VIEWPORT:当前可见视口(随滚动变化);GHOSTTY_POINT_TAG_SCREEN:含 scrollback 的完整屏幕;GHOSTTY_POINT_TAG_HISTORY:仅 scrollback 历史区域。
GHOSTTY_INIT_SIZED(GhosttyGridRef)宏用于初始化带size字段的"sized struct"——GhosttyGridRef结构体在 grid_ref.h 中形如:
typedef struct { size_t size; void *node; // 指向底层网格节点的内部指针 uint16_t x; uint16_t y; } GhosttyGridRef;即一个 grid ref 本质是"已解析的网格节点指针 + 坐标"。
4. 从 Grid Ref 读取单元格、行和样式
// 读取单元格 GhosttyCell cell; result = ghostty_grid_ref_cell(&ref, &cell); assert(result == GHOSTTY_SUCCESS); // 判断该格是否有文字 bool has_text = false; ghostty_cell_get(cell, GHOSTTY_CELL_DATA_HAS_TEXT, &has_text); if (has_text) { uint32_t codepoint = 0; ghostty_cell_get(cell, GHOSTTY_CELL_DATA_CODEPOINT, &codepoint); printf("%c", (char)codepoint); } else { printf("."); }单元格数据通过ghostty_cell_get以"数据键"方式逐项取出:GHOSTTY_CELL_DATA_HAS_TEXT先判断该格是否含有文本,GHOSTTY_CELL_DATA_CODEPOINT取出主码点;没有文本的格子打印.,这样输出中"空白格"与文本格一目了然。
行级状态则通过ghostty_grid_ref_row拿到GhosttyRow,再查询 wrap 状态:
GhosttyRow grid_row; ghostty_grid_ref_row(&ref, &grid_row); bool wrap = false; ghostty_row_get(grid_row, GHOSTTY_ROW_DATA_WRAP, &wrap); printf(" (wrap=%s", wrap ? "true" : "false");GHOSTTY_ROW_DATA_WRAP记录该行的自动换行标记(终端在行尾换行时会在行上留下 wrap 标志,供后续 reflow/选择使用)。
样式读取:
GhosttyStyle style = GHOSTTY_INIT_SIZED(GhosttyStyle); ghostty_grid_ref_style(&ref, &style); printf(", bold=%s)\n", style.bold ? "true" : "false");对第三行,style.bold会被 SGR 序列\033[1m置为 true,从而打印出bold=true,验证了样式信息确实随单元格可查。
最后调用ghostty_terminal_free(terminal)释放终端实例。整个流程是:new → vt_write → get 尺寸 → 逐格 grid_ref → cell/row/style 查询 → free。
Grid Ref API 全景与生命周期规则
示例用到的只是 grid_ref.h 中的三个函数,完整的 untracked grid ref API 共有五个:
| 函数 | 作用 |
|---|---|
ghostty_grid_ref_cell | 取该位置的GhosttyCell |
ghostty_grid_ref_row | 取该位置的GhosttyRow |
ghostty_grid_ref_graphemes | 取完整字素簇码点(主码点 + 组合码点),缓冲区不足时返回GHOSTTY_OUT_OF_SPACE并回填所需长度 |
ghostty_grid_ref_hyperlink_uri | 取该单元格超链接 URI,同样支持两阶段缓冲区探测 |
ghostty_grid_ref_style | 取该单元格的GhosttyStyle |
头文件中还有两条对集成方非常重要的约束,值得在移植示例时一并记住:
- 生命周期:untracked grid ref 是"快照",不需要释放,但只在下次终端变更操作(包括
free)之前有效。拿到后应立即读取并缓存数值,不能跨帧持有; - 性能边界:头文件明确警告 grid reference API 不是为渲染循环设计的,"不适合维持大尺寸屏幕渲染所需的帧率",大流量渲染应使用 render state API。grid ref 更适合本示例这类"检查/查询"场景,以及搜索、选择、书签等低频读取路径。
此外还存在与 untracked 相对的tracked(跟踪)grid ref:ghostty_terminal_grid_ref_track创建的引用会随滚动、scrollback 修剪、resize/reflow 等操作自动跟随其单元格移动,适用于选择区域、搜索状态等长生命周期锚点;它需要调用方用ghostty_tracked_grid_ref_free释放,且每次终端变更都会带来额外的簿记开销。姊妹示例 c-vt-grid-ref-tracked 演示了跟踪引用在滚动时的跟随、失值检测与set重定位用法,可与本示例对照阅读。
适用前提与 API 稳定性说明
- 构建前提:按 example/README.md 的说法,
zig build run需要在示例目录内执行;构建脚本通过lazyDependency自动拉取 ghostty 依赖,无需手动配置头文件路径。 - API 稳定性:vt.h 顶部明确标注
libghostty-vt是不完整的 work-in-progress API,"尚未稳定、肯定会变化"。因此本文中的函数签名、枚举名(如GHOSTTY_POINT_TAG_ACTIVE、GHOSTTY_CELL_DATA_CODEPOINT)以当前仓库源码为准,升级 ghostty 版本时应对照 include/ghostty/vt/ 下的头文件复核。 - 与 CMake 构建的关系:README 强调用 Zig 构建只是"复用构建逻辑、直接依赖源码树"的便利手段,Ghostty 产出的是标准 C 库,生产环境完全可以改用任意 C 工具链链接,本示例的源码本身不依赖 Zig。
小结
这个 70 行左右的 C 示例完整覆盖了用 grid ref 读取网格状态的典型路径:ghostty_terminal_new建终端、ghostty_terminal_vt_write喂 VT 流、ghostty_terminal_get取尺寸、ghostty_terminal_grid_ref把GhosttyPoint解析为GhosttyGridRef,再经ghostty_grid_ref_cell/ghostty_grid_ref_row/ghostty_grid_ref_style分别读取码点、wrap 状态与样式。配合 grid_ref.h 中关于快照生命周期与渲染帧率边界的说明,以及 c-vt-grid-ref-tracked 中 tracked 变体的对照,开发者可以在 Ghostty 的 C 库之上实现自定义的网格检查、内容提取与锚点跟踪逻辑。
【免费下载链接】ghostty👻 Ghostty is a fast, feature-rich, and cross-platform terminal emulator that uses platform-native UI and GPU acceleration.项目地址: https://gitcode.com/GitHub_Trending/gh/ghostty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考