search/: sliding-window matcher over pages, active-area, page list, viewport and screen searches, and the background search thread (std thread + crossbeam-channel mailbox, 24 ms refresh, terminal behind Arc<Mutex<_>>). Searches take &mut Screen explicitly; ScreenSearch::deinit must be called to untrack pins. The sliding window uses a VecDeque instead of CircBuf (same results; two layout-only assertions dropped). tmux/: layout parsing, output formatting and the control-mode viewer state machine, reusing ghostty_vt::tmux::control for parsing. The viewer is not wired into termio yet; steps are in the notes. The renderer must set terminal.flags.search_viewport_dirty for the search thread to re-search the viewport. Test Plan: - cargo test -p ghostty-terminal --lib search (54; 51/53 Zig tests, 2 allocator-failure tests skipped) - cargo test -p ghostty-terminal --lib tmux (96; all Zig tests)
5.3 KiB
5.3 KiB
ghostty-terminal: search/* and tmux/* (agent T5)
Ported
| Zig | Rust | tests (ported/Zig) |
|---|---|---|
| search.zig | search/mod.rs |
- |
| search/sliding_window.zig | search/sliding_window.rs |
25/25 |
| search/active.zig | search/active.rs |
2/2 |
| search/pagelist.zig | search/pagelist.rs |
7/7 |
| search/viewport.zig | search/viewport.rs |
4/4 |
| search/screen.zig | search/screen.rs |
11/13 |
| search/Thread.zig | search/thread.rs |
1/1 (unnamed test {} -> search_thread_basic) + 1 extra (search_thread_select_and_state) |
| tmux.zig | tmux/mod.rs (re-exports ghostty_vt::tmux::control) |
- |
| tmux/layout.zig | tmux/layout.rs |
43 (24 layout + 19 checksum) |
| tmux/output.zig | tmux/output.rs |
37 |
| tmux/viewer.zig | tmux/viewer.rs |
8/8 |
Skipped (Zig-only): the two reloadActive partial history cleanup on ... error tests in search/screen.zig
(tripwire allocator-failure injection). output.zig comptime-format variants are covered by the runtime format() tests.
Search design / deviations
- Uses
crate::highlight::{Flattened, Untracked, Tracked, Chunk}andcrate::formatter::{PageFormatter, Options}(plain +unwrapwith a point map) from T4.NodeIdcarries the node,Chunk.serialis the node serial. - Nothing stores
*Screen/*PageList. Everything touching tracked pins takes the screen explicitly:ScreenSearch::new(&mut Screen, needle),.deinit(&mut Screen)(must be called to untrack pins),.feed(&mut Screen),.reload_active(&mut Screen),.select(&mut Screen, Select),.search_all(&mut Screen).tick(),matches(),selected_match(),matches_len()need no screen.PageListSearch::{new,feed,deinit}take&mut PageList.ActiveSearch::update(&PageList) -> Option<NodeId>,ViewportSearch::update(&PageList) -> bool(infallible).SlidingWindow::append(&PageList, NodeId) -> usize. SlidingWindowusesVecDeque(made contiguous for searching) instead ofCircBuf+ overlap buffer; results are identical. The Zig assertions about circular-buffer slice layout in the "across circular buffer boundary" tests are dropped (behaviour kept). Row counts are cached per page atappendtime. Needle must be non-empty (Zig has the same underflow).next()returns an ownedFlattened, so Zig'sclone()dance is unnecessary.ScreenSearchcachesHistorySearch.start_node(Zig readsstart_pin.nodelock-free intick); refreshed infeed/reload_active. Screen identity (screen_id, address) replaces Zig's*Screenpointer comparison for "screen was reinitialized" in the thread.Thread: libxev replaced by crossbeam-channel: bounded(64) mailbox + control channel (wakeup/stop) + 24 ms deadline refresh timer. API:Thread::new(Options),.handle() -> Handle {send,try_send,wakeup,stop},.thread_main()orThread::spawn(opts) -> (Handle, JoinHandle).Options { terminal: Arc<Mutex<Terminal>>, event_cb: Option<Arc<dyn Fn(Event)+Send+Sync>> };Message::{ChangeNeedle(Vec<u8>), Select(Select)}(ZigMessageData(u8,255)->Vec<u8>);Eventcarries owned data (ViewportMatches(Vec<Flattened>)). The search is torn down under the lock (pins untracked) when the thread exits or the needle changes. Dependency added:crossbeam-channel.- Terminal hook already present:
Terminal.flags.search_viewport_dirty(the thread reads/clears it). cargo fmt -p ghostty-terminalwas run for the whole crate (not just my files).
tmux design / deviations
ghostty_vt::tmux::controlis reused (tmux::control, plusControlParser/ControlNotificationaliases).- output.rs: comptime
FormatStruct->Variableenum +FormatValues(.bool()/.usize()/.str());Variable::parsekeeps Zig'sInvalidCharacter/Overflow/FormatErrordistinction;parse_format_structfolds intoParseError::FormatError. - layout.rs:
Layoutowns children (Vec),ParseError::{SyntaxError, ChecksumMismatch}. - viewer.rs:
Viewer::new(),next(Input) -> Vec<Action>(owned; no arena);Action::{Command(Vec<u8>), Windows(Vec<..> clone), Exit}; publicsession_id,tmux_version,windows,panes(insertion-orderedPanesMap).sync_layoutscreates terminals before committing; any command-processing failure goes defunct like Zig.Notification::Entermust NOT be fed to the viewer (unreachable, as Zig). Unknown extraNotificationvariants are ignored.
TODO(integration) - tmux viewer wiring (nothing in terminal.rs is required)
- In the DCS state kept by
ghostty_vt::dcs/stream, on DCS1000p(Notification::Enter) createViewer::new()(do not pass Enter to it). - Feed every later
ghostty_vt::tmux::control::Parsernotification viaviewer.next(Input::Tmux(n)). - Execute returned actions in termio:
Command(bytes)-> write to pty;Windows(w)-> diff in apprt;Exit-> drop viewer, leave control mode. - Pane contents are in
viewer.panes.get(id).terminal; renderer/apprt render from there.
TODO(integration) - search wiring
- Core owns the search thread: keep
Handle; on search-start/changed text sendMessage::ChangeNeedle, navigation sendsMessage::Select. Events arrive on the search thread viaevent_cb(forward to the apprt main loop). The renderer must setterminal.flags.search_viewport_dirty = truewhen it finds viewport/active dirty (as Zig), so the thread re-searches.