在 C 中使用 ghostty-vt 搜索 API:解析 libghostty 的终端 Search 示例
2026/9/8 22:01:06 网站建设 项目流程

在 C 中使用 ghostty-vt 搜索 API:解析 libghostty 的终端 Search 示例

【免费下载链接】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

本文以 Ghostty 仓库内example/c-vt-search示例为主线,讲解如何用纯 C 语言调用ghostty-vt(Ghostty 的 VT 终端 C 库)实现类似 find bar 的搜索能力:向终端写入内容、检索匹配、在匹配之间前后跳转、读取视口(viewport)匹配用于绘制高亮。读完本文,你将掌握GhosttySearch对象的完整生命周期、tick/feed/run三种搜索驱动方式、匹配与选区的关系,以及后台线程驱动搜索的线程安全模型。

示例定位:一个可运行的终端搜索程序

example/c-vt-search是 Ghostty 仓库example/目录下一批 "语言绑定 / 嵌入示例" 中的一个。它展示的是 Ghostty 通过 include/ghostty/vt.h 与 include/ghostty/vt/(内含search.hselection.hterminal.hgrid_ref.hpoint.h等约 30 个 C 头文件)对外暴露的标准 C ABI。与同目录的其他示例(如c-vtc-vt-grid-ref-trackedc-vt-snapshotc-vt-sgr)一样,其命名c-vt-search表明这是专门演示搜索 API 的 C 语言最小样例。

示例的核心流程与真实终端中用户按Ctrl+F打开查找栏后发生的事情一一对应:

  1. 创建终端并灌入模拟文本;
  2. 新建GhosttySearch并设置搜索词(needle);
  3. 驱动搜索直到完成;
  4. 按方向键在匹配之间循环跳转并自动滚动视口;
  5. 周期性同步终端变化并读取视口匹配,供宿主程序绘制高亮矩形。

快速运行

example/c-vt-search/目录下直接运行即可看到输出:

zig build run

程序会打印匹配总数、每次跳转的选中序号(如1 of 3),以及每个位于可见行内匹配的行列区间(即高亮矩形位置)。

为什么用 Zig 构建 C 程序

示例的构建脚本使用 Zig(要求minimum_zig_version = "0.15.1",见 build.zig.zon)来编译src/main.c,原因在 README.md 中说得很清楚:复用 Ghostty 自身的构建逻辑,直接以源码树为依赖,避免重复配置;同时 Ghostty 本身发布的是标准 C 库,build.zig只是一种便捷的取用方式,任何 C 工具链都可以使用它。

build.zig 的关键点:

  • 通过addCSourceFilessrc/main.c编入可执行模块c_vt_search
  • b.lazyDependency("ghostty", ...)懒加载依赖——只有在真正构建时才引入 Ghostty,避免无用下载;
  • exe_mod.linkLibrary(dep.artifact("ghostty-vt"))链接名为ghostty-vt的产物;
  • 注释里还提示了一个性能权衡:若把.simd = false打开会得到完全不依赖 libc 的纯静态构建,但有显著性能损失;只要宿主程序本身需要 libc,就应该保持 simd 开启

依赖声明在 build.zig.zon:示例用{ .path = "../../" }指回仓库根目录,以保证示例始终与本仓库的源码一起被测试;注释中给出了更贴近真实项目的 URL 依赖写法(指向某次 commit 的 tar 包并带上 hash),供外部项目参考。

示例源码分步解析

下面按 src/main.c 的执行顺序拆解每个环节。它只包含一个约 110 行的main()函数,并用 Doxygen 标签//! [search-main]标记了整段代码——这段代码被搜索头文件 search.h 以@snippet c-vt-search/src/main.c search-main方式直接内嵌到 API 文档中,本身就是官方推荐的 "最小可用调用序列"。

1. 创建终端并写入待搜索内容

GhosttyTerminal terminal; GhosttyResult result = ghostty_terminal_new(NULL, &terminal, 80, 24); assert(result == GHOSTTY_SUCCESS);

以 80 列、24 行的尺寸创建终端(第一个参数NULL表示使用默认分配器)。随后把 5 行模拟编译输出的文本(含\r\n)用ghostty_terminal_vt_write逐条写入,内容刻意包含小写的error(模块 B 的错误信息)与大写的ERROR(grep 命令中的关键词),用于演示大小写匹配行为。

2. 创建搜索并设置搜索词

GhosttySearch search; result = ghostty_search_new(NULL, &search, terminal); ... GhosttyString needle = { (const uint8_t *)"error", 5 }; result = ghostty_search_set(search, GHOSTTY_SEARCH_OPT_NEEDLE, &needle);

ghostty_search_new创建一个绑定到指定终端的搜索对象。创建是廉价的,不会立刻读取终端内容,但会向终端注册自身,使双方可以在任意顺序下被释放(见下文 "生命周期")。新建的搜索处于空闲态(idle),直到设置搜索词后才开始工作。

搜索词的匹配规则(search.hGHOSTTY_SEARCH_OPT_NEEDLE注释):除 ASCII 字母按大小写不敏感比较外,其余字节精确匹配(byte-exact)。因此示例里搜索"error"同时命中了行内小写errorgrep -n ERROR中的大写ERROR

针对交互式查找栏的两个细节很实用:

  • 重复提交不重启:如果新设置的搜索词与当前搜索词相等(按同样规则比较),已有结果被保留,find bar 可以放心地反复重设;
  • 修改即重建:更改搜索词会从零重启搜索并丢弃全部结果;置空(NULL 或空字符串)则清除搜索词、回到空闲态。

3. 驱动搜索:tick / feed / run

搜索大段回滚(scrollback)是耗时的,因此 API 把工作拆成调用方驱动的小步,让调用方能直接控制性能。头文件定义了三种驱动原语:

  • ghostty_search_tick():在搜索已拷贝的数据上做有界的推进,完全不触碰终端,因而可以从别的线程安全调用;
  • ghostty_search_feed():读取终端以拷贝新数据并拾取终端变化——feed 是搜索感知终端变化的唯一途径,所以搜索使用期间要周期性调用,即使状态已经 COMPLETE;
  • ghostty_search_run():阻塞式便利函数,先至少 feed 一次,再循环 tick 直到搜索追上终端。

示例是非交互的一次性搜索,所以直接用:

result = ghostty_search_run(search);

对应的状态机在GhosttySearchStatus枚举中定义(search.h):

状态含义
GHOSTTY_SEARCH_STATUS_RUNNINGtick 无需终端访问即可继续推进
GHOSTTY_SEARCH_STATUS_FEED_REQUIRED被阻塞,等待ghostty_search_feed();刚设置搜索词后也是此状态
GHOSTTY_SEARCH_STATUS_COMPLETE已追上截至最后一次 feed 的终端状态;绝不意味永久结束,后续终端写入需再次 feed;无搜索词时也报告 COMPLETE

交互式嵌入方(如真实终端应用)不应使用阻塞的run,而应把tick/feed交织进自身事件循环——这正对应 search.h "Threading" 一节描述的线程模型,也与仓库中真实渲染器/查找栏的驱动方式一致(见下文源码级原理)。

4. 读取匹配总数

size_t total = 0; result = ghostty_search_get(search, GHOSTTY_SEARCH_DATA_TOTAL_MATCHES, &total); printf("%zu matches for \"error\"\n", total);

ghostty_search_get用统一的(data, value)二元组读取各类数据。所有读取反映的是"截至最近一次 feed 的活动屏(active screen)"状态。示例输出3 matches for "error",并以此实现 find bar 的 "1 of 3" 文本。

5. 前后跳转匹配并跟随选中项

find bar 里按下回车应跳转到下一处匹配。示例用一个while循环模拟连续按回车,直到绕回第一个匹配:

while (true) { result = ghostty_search_set(search, GHOSTTY_SEARCH_OPT_SELECT_NEXT, NULL); if (result != GHOSTTY_SUCCESS) break; ... if (idx + 1 == total) break; }

方向语义GHOSTTY_SEARCH_OPT_SELECT_NEXT/_PREV注释):

  • SELECT_NEXT更旧内容移动:从屏幕底部向上进入历史,这正是"从当前提示符向上游查找"的直觉方向;越过最旧匹配后回绕(wrap)。
  • SELECT_PREV反向,向更新内容移动,越过最新匹配后回绕。
  • 选择操作会先追上终端(catches up),因此相对 feed 在任意时刻调用都是安全的;
  • 当没有匹配时返回GHOSTTY_NO_VALUE(示例靠它作为终止条件之一)。

选中后示例用ghostty_search_get_multi一次读两个字段——选中序号和选中匹配选区:

size_t idx = 0; GhosttySelection match = GHOSTTY_INIT_SIZED(GhosttySelection); const GhosttySearchData keys[] = { GHOSTTY_SEARCH_DATA_SELECTED_INDEX, GHOSTTY_SEARCH_DATA_SELECTED_MATCH, }; void *values[] = { &idx, &match }; result = ghostty_search_get_multi(search, 2, keys, values, NULL); printf("selected %zu of %zu\n", idx + 1, total);

这里揭示了两个 find bar 实现要点:

  • 序号方向SELECTED_INDEX索引的是"新→旧"排序(0 为最新匹配),所以 "k of n" 文本要渲染为index + 1
  • 批量读取ghostty_search_get_multi比连续多次get高效;若其中某个读取失败,返回该错误并把失败 key 的序号写入out_written(此前 keys 已写入),因此缓冲区类型的 key 应放在标量 key 之后(标量 key 之后)。若需同时读取缓冲区型字段,应把缓冲区 key 排在后面,避免GHOSTTY_OUT_OF_SPACE中断整批读取。

6. 读取视口匹配并绘制高亮

find bar 打开期间,宿主每一帧都应 feed 一次以追上终端变化,然后读取视口匹配来绘制高亮:

result = ghostty_search_feed(search); GhosttySelection viewport_storage[64]; GhosttySelectionBuffer viewport = { .ptr = viewport_storage, .cap = 64 }; result = ghostty_search_get(search, GHOSTTY_SEARCH_DATA_VIEWPORT_MATCHES, &viewport);

要点:

  • GHOSTTY_SEARCH_DATA_VIEWPORT_MATCHES在 feed 期间计算并缓存,反映的是截至上次 feed 的视口;缓冲区不足时返回GHOSTTY_OUT_OF_SPACE并在len中给出所需容量;也可置ptr=NULL,cap=0先查询容量;
  • 按页(page)粒度产生匹配,因此列表可能包含与视口共享同一页、但位于可见行之外的匹配——Ghostty 自身渲染器行为相同;
  • 所以示例对每个匹配用ghostty_terminal_point_from_grid_ref(terminal, &sel.start/end, GHOSTTY_POINT_TAG_VIEWPORT, ...)把网格引用(grid ref)换算成视口坐标,然后跳过任何转换失败或start.y/end.y >= 24(超过可见行数)的匹配;
  • 转换成功的匹配,宿主在此处绘制从startend的高亮矩形(示例用printf打印行列区间代替)。

这个"把 grid ref 转成视口坐标再做裁剪"的做法正是渲染器绘制多行/滚动区高亮的通用套路,可配合 grid_ref.h、point.h 与GHOSTTY_POINT_TAG_VIEWPORT使用。

7. 释放资源

ghostty_search_free(search); ghostty_terminal_free(terminal); return 0;

生命周期规则(search.h "Lifetime" 一节):搜索"借用"创建它的终端、从不释放终端;一个终端可同时被任意多个搜索、以及 formatter、render state 等其他读取者共享。搜索与终端可以以任意顺序释放

  • 先释放搜索:会释放它保存在终端内的跟踪状态;
  • 先释放终端:搜索会检测到这一点,需要终端的调用返回GHOSTTY_INVALID_VALUE,读取返回它最后看到的值,ghostty_search_free只释放搜索自有内存;
  • 搜索不能被重新绑定到别的终端,要搜索另一个终端只能新建搜索对象。

匹配即选区:与选择 API 的互通

搜索头文件用了一整节强调设计核心:每个匹配都以GhosttySelection快照返回rectangle为 false),因此现有选区 API 全部可复用:

  • ghostty_terminal_selection_format_buf()复制匹配文本;
  • ghostty_terminal_point_from_grid_ref()+GHOSTTY_POINT_TAG_VIEWPORT定位高亮矩形;
  • ghostty_terminal_selection_contains()做命中测试;
  • ghostty_terminal_set()+GHOSTTY_TERMINAL_OPT_SELECTION把某处匹配设为终端当前选区。

快照时效:返回的匹配遵循标准快照生命周期——仅在下一次修改终端的操作(ghostty_terminal_vt_write、resize、reset、free)之前有效。正确用法是 feed 之后读取、用完即弃、不要缓存;唯一例外是选中匹配,其内部会在终端变化中持续跟踪准确,因此要跟随当前匹配,应在每次 feed 后重新读取GHOSTTY_SEARCH_DATA_SELECTED_MATCH

源码级原理:搜索引擎与线程模型

示例调用的每个 C 函数都有对应实现依据:

  • C ABI 层:ghostty-search_*系列函数及其枚举定义集中在 search.h,由GhosttySearch/GhosttySearchData/GhosttySearchOption/GhosttySearchStatus描述完整状态机;
  • 引擎层:终端搜索的实际实现在 src/terminal/search/ 目录,从源码结构看,其内部按职责拆分为多个模块,包括负责屏幕级匹配、回滚滑动窗口搜索、按页匹配列表与视口匹配缓存、以及后台搜索线程驱动的组件(sliding_windowactivepagelistscreenviewportThread等模块均在此目录下)。

头文件承诺的能力恰好能映射到这些模块的设计意图:

  • 搜索结果与实时屏幕同步、且在主屏与备用屏(alternate screen)间存续:进入/退出全屏应用(如 vim)不会重启回滚搜索——备用屏激活时,下个 feed 会把计数、匹配与选中切换到该屏结果;主屏结果(含已完成的历史搜索)被保留并在切回时恢复;
  • 抗 resize、reflow、reset 与回滚裁剪:搜索内部持续协调跟踪屏与实时屏,裁剪被回滚淘汰失效的结果,并从滚出/重排中恢复;
  • 线程模型(search.h "Threading"):库自身不创建线程;同一搜索对象上的调用不可并发。触碰终端的函数(ghostty_search_newfeedrun、涉及 needle/select 的setfree)必须与对该终端的其他访问串行化;而tickgetget_multi只访问搜索自有内存,可在另一线程修改终端的同时安全调用。这就是 Ghostty 把搜索放到后台线程的机制:tick 随意跑,只在 feed 时短暂持有终端锁。

在真实嵌入项目中如何使用

把示例替换成自己的嵌入方案,只需注意几点:

  1. 链接ghostty-vt产物即可拿到标准 C ABI,头文件在 include/ghostty/vt.h(module map 定义在 include/module.modulemap);示例用zig build只是图省事;
  2. 交互式宿主:把tick/feed织入事件循环,feed 持锁时间要短(单次 feed 本身只做有界工作);需后台搜索时按上文线程模型拆分线程职责;
  3. 参考本示例的完整注解即可串联其余能力——如用ghostty_search_run做一次性同步搜索,或用GHOSTTY_SEARCH_OPT_SELECT_SCROLL把视口滚动策略改为GHOSTTY_SEARCH_SCROLL_NONE(默认是GHOSTTY_SEARCH_SCROLL_IF_NEEDED,即仅当匹配不可见时才滚动)。

小结

example/c-vt-search虽然只有百行左右,却完整演示了ghostty-vt搜索 API 的全部关键概念:GhosttySearch对象生命周期与"借用终端、可任意序释放"的语义、needle 的字节精确 + ASCII 字母大小写不敏感匹配、tick/feed/run三级驱动与FEED_REQUIRED/COMPLETE状态机的含义、匹配"新→旧"排序与SELECT_NEXT/PREV回绕跳转、匹配即选区(可直接复用文本提取与命中测试 API),以及"feed 后按页读取视口匹配并换算视口坐标做高亮裁剪"的渲染范式。把这段代码与 search.h 的注释、src/terminal/search/ 的引擎实现对照阅读,即可在任意 C/C++ 宿主中复刻出与 Ghostty 自身一致的查找体验。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询