Repository navigation
feat: expose GHOSTSNP terminal snapshots - #2
Merged
Merged
Conversation
leizhiyuan
force-pushed
the
feat/ghostsnp-snapshots
branch
from
October 30, 2026 09:42
10866f8 to
c54e7c7
Compare
nekohasekai
force-pushed
the
feat/ghostsnp-snapshots
branch
from
October 30, 2026 14:17
c54e7c7 to
abbab74
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Wires up libghostty-vt's terminal snapshot support (the
GHOSTSNPrecord stream) so asession can be persisted and resumed. 把 libghostty-vt 的终端快照(
GHOSTSNP记录流)接出来,让会话可以持久化并恢复。
English
What
A snapshot carries cell contents, scrollback, colors, palette, modes, title, working
directory and the unfinished escape sequence the parser is inside. A restored session
keeps rendering and accepting input where the saved one stopped, and can be snapshotted
again.
The pinned ghostty already ships
include/ghostty/vt/snapshot.hand allghostty_snapshot_*symbols, so this needs no submodule bump. The implementation only usesentry points that exist in that revision, so it also builds against current
ghostty-org/ghosttymain.Changes
GhosttyVt:nativeSnapshotEncode,nativeSnapshotDecode,nativeSizeGhosttyTerminalSession:saveSnapshot(),Companion.restore(), plus a privateconstructor that takes snapshot bytes
nativeCreateintocreateAuxResources/attachTerminalHandlers/readXTVersion/applyTerminfoName, so a decoded terminalgets the same wiring
GhosttySnapshotTest: round trip and invalid input coverageDesign notes
nativeCreate(
GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES, 8 MiB). Encoding a terminal whose VTparser is mid-sequence requires the replayable suffix to have been retained before the
input was written, so it cannot be turned on lazily at save time. Tracking has no cost
while the parser is at ground and the buffer only grows while a single sequence is in
flight;
saveSnapshot()returns null if a suffix ever exceeds the cap, instead of writinga snapshot that cannot be resumed.
colors, cursor defaults and screen contents come from the snapshot, so
Options.columns/rows/maxScrollbackLines/defaultCursorStyle/defaultCursorBlinkareignored by
restore(); onlyreportedVersionandterminfoNameapply, because theydescribe the host terminal. Documented on the factory.
GHOSTTY_SNAPSHOT_DECODER_OPT_RETAIN_CONTINUATION; changing a live cap can invalidate theretained suffix, so
nativeSnapshotDecodedeliberately skips it.restore()seedscolumns,rows,title,workingDirectoryandbackgroundColorfrom the decoded terminal, and returns null for data that is empty, too short, truncated
before FINISH, or fails CRC.
Verification
:library:compileDebugKotlin,:library:compileDebugAndroidTestKotlin: pass:library:connectedDebugAndroidTeston an arm64-v8a API 36 emulator: 7 tests, 0 failures,including
GhosttySnapshotTest:roundTripRestoresContentsAndContinuation— OSC title, OSC 7 pwd, colored text, 40lines of scrollback and an unfinished
CSI 31; asserts equal size, title, pwd,screenText()andtranscriptText(), feeds the rest of the sequence to both terminalsand compares again, then snapshots and restores a second time
restoreRejectsInvalidSnapshot— empty, non-snapshot bytes, truncated FINISH and asingle flipped byte all return null
Stream a pane from Rex instead of polling its screen leizhiyuan/rexpocket#1
Caveats
restored session has the same text and state without images. Noted in the KDoc; it will
follow a future ghostty bump.
should keep the snapshot and the library version paired.
中文
背景
libghostty-vt早就能把终端整体序列化成GHOSTSNP记录流并还原,但这个封装一直没接,所以会话无法持久化。这个 PR 补上编解码两端。
快照内容包含单元格内容、scrollback、颜色、调色板、模式、标题、工作目录,以及 parser
当前停在半截的转义序列。恢复出来的会话可以直接继续渲染、继续喂 PTY 输入,而且能再次
打快照。
子模块 pin 的 ghostty 版本已经带
include/ghostty/vt/snapshot.h和全套ghostty_snapshot_*符号,因此不需要升子模块;实现只用了该版本已有的入口,所以对当前ghostty-org/ghosttymain 也编得过。改动
GhosttyVt:新增nativeSnapshotEncode、nativeSnapshotDecode、nativeSizeGhosttyTerminalSession:新增saveSnapshot()、Companion.restore(),构造器改为主/次分离,次构造器接收快照字节
nativeCreate里的资源分配和 option 注册拆成createAuxResources/attachTerminalHandlers/readXTVersion/applyTerminfoName,让解码出来的终端复用同一套接线
GhosttySnapshotTest:往返与非法输入用例设计说明
nativeCreate里就打开(
GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES,8 MiB)。当 parser 停在半截序列时想编码,可重放的字节后缀必须在写入那批输入之前就开始保留,存盘时再开是来不及的。parser 处于
ground 时这项没有任何开销,缓冲也只在单条序列未结束时增长;万一后缀超过上限,
saveSnapshot()返回 null,而不是写一个恢复不了的快照。来自快照,所以
restore()忽略Options.columns/rows/maxScrollbackLines/defaultCursorStyle/defaultCursorBlink;只有reportedVersion和terminfoName生效,因为它们描述的是宿主终端而不是被保存的状态。GHOSTTY_SNAPSHOT_DECODER_OPT_RETAIN_CONTINUATION设好;改动一个正在生效的上限可能让已保留的后缀失效,所以
nativeSnapshotDecode故意跳过这一项。restore()会从解码出的终端回填columns、rows、title、workingDirectory、backgroundColor;对空数据、长度不足、FINISH 之前截断、CRC 校验失败的数据一律返回 null。验证
:library:compileDebugKotlin、:library:compileDebugAndroidTestKotlin:通过:library:connectedDebugAndroidTest(arm64-v8a / API 36 模拟器):7 个用例 0 失败,其中GhosttySnapshotTest:roundTripRestoresContentsAndContinuation:OSC 标题、OSC 7 工作目录、彩色文本、40 行scrollback,外加半截
CSI 31;断言尺寸、标题、pwd、screenText()、transcriptText()完全一致,再给两边分别喂上序列剩余部分后比较一次,最后对恢复出的终端再存快照并二次恢复
restoreRejectsInvalidSnapshot:空字节、非快照字节、截断 FINISH、翻转一个字节,都必须返回 null
注意事项
KDoc 里已注明,等以后升 ghostty 会自然带上。
管理。