
在 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-vtGhostty 的 VT 终端 C 库实现类似 find bar 的搜索能力向终端写入内容、检索匹配、在匹配之间前后跳转、读取视口viewport匹配用于绘制高亮。读完本文你将掌握GhosttySearch对象的完整生命周期、tick/feed/run三种搜索驱动方式、匹配与选区的关系以及后台线程驱动搜索的线程安全模型。示例定位一个可运行的终端搜索程序example/c-vt-search是 Ghostty 仓库example/目录下一批 语言绑定 / 嵌入示例 中的一个。它展示的是 Ghostty 通过 include/ghostty/vt.h 与 include/ghostty/vt/内含search.h、selection.h、terminal.h、grid_ref.h、point.h等约 30 个 C 头文件对外暴露的标准 C ABI。与同目录的其他示例如c-vt、c-vt-grid-ref-tracked、c-vt-snapshot、c-vt-sgr一样其命名c-vt-search表明这是专门演示搜索 API 的 C 语言最小样例。示例的核心流程与真实终端中用户按CtrlF打开查找栏后发生的事情一一对应创建终端并灌入模拟文本新建GhosttySearch并设置搜索词needle驱动搜索直到完成按方向键在匹配之间循环跳转并自动滚动视口周期性同步终端变化并读取视口匹配供宿主程序绘制高亮矩形。快速运行在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 的关键点通过addCSourceFiles把src/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 的错误信息与大写的ERRORgrep 命令中的关键词用于演示大小写匹配行为。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同时命中了行内小写error与grep -n ERROR中的大写ERROR。针对交互式查找栏的两个细节很实用重复提交不重启如果新设置的搜索词与当前搜索词相等按同样规则比较已有结果被保留find bar 可以放心地反复重设修改即重建更改搜索词会从零重启搜索并丢弃全部结果置空NULL 或空字符串则清除搜索词、回到空闲态。3. 驱动搜索tick / feed / run搜索大段回滚scrollback是耗时的因此 API 把工作拆成调用方驱动的小步让调用方能直接控制性能。头文件定义了三种驱动原语ghostty_search_tick()在搜索已拷贝的数据上做有界的推进完全不触碰终端因而可以从别的线程安全调用ghostty_search_feed()读取终端以拷贝新数据并拾取终端变化——feed 是搜索感知终端变化的唯一途径所以搜索使用期间要周期性调用即使状态已经 COMPLETEghostty_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中给出所需容量也可置ptrNULL,cap0先查询容量按页page粒度产生匹配因此列表可能包含与视口共享同一页、但位于可见行之外的匹配——Ghostty 自身渲染器行为相同所以示例对每个匹配用ghostty_terminal_point_from_grid_ref(terminal, sel.start/end, GHOSTTY_POINT_TAG_VIEWPORT, ...)把网格引用grid ref换算成视口坐标然后跳过任何转换失败或start.y/end.y 24超过可见行数的匹配转换成功的匹配宿主在此处绘制从start到end的高亮矩形示例用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_window、active、pagelist、screen、viewport、Thread等模块均在此目录下。头文件承诺的能力恰好能映射到这些模块的设计意图搜索结果与实时屏幕同步、且在主屏与备用屏alternate screen间存续进入/退出全屏应用如 vim不会重启回滚搜索——备用屏激活时下个 feed 会把计数、匹配与选中切换到该屏结果主屏结果含已完成的历史搜索被保留并在切回时恢复抗 resize、reflow、reset 与回滚裁剪搜索内部持续协调跟踪屏与实时屏裁剪被回滚淘汰失效的结果并从滚出/重排中恢复线程模型search.h Threading库自身不创建线程同一搜索对象上的调用不可并发。触碰终端的函数ghostty_search_new、feed、run、涉及 needle/select 的set、free必须与对该终端的其他访问串行化而tick、get、get_multi只访问搜索自有内存可在另一线程修改终端的同时安全调用。这就是 Ghostty 把搜索放到后台线程的机制tick 随意跑只在 feed 时短暂持有终端锁。在真实嵌入项目中如何使用把示例替换成自己的嵌入方案只需注意几点链接ghostty-vt产物即可拿到标准 C ABI头文件在 include/ghostty/vt.hmodule map 定义在 include/module.modulemap示例用zig build只是图省事交互式宿主把tick/feed织入事件循环feed 持锁时间要短单次 feed 本身只做有界工作需后台搜索时按上文线程模型拆分线程职责参考本示例的完整注解即可串联其余能力——如用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),仅供参考