diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..061e0a3 --- /dev/null +++ b/TODO.md @@ -0,0 +1,98 @@ +# TODO + +## Smooth resize with very large scrollback + +**Symptom:** with a large `scrollback-limit` (e.g. 1 GiB) and a pane holding a +lot of history, resizing the window or adding a split freezes the UI for a +moment. + +### Why it freezes + +1. **Every resize rewrites the whole scrollback.** Any column change (window + drag, new split) runs `PageList::resize_cols` → `reflow_pages` + (`crates/ghostty-terminal/src/page_list.rs`), which rewrites every page cell + by cell into new pages. With ~1 GiB of history that is 100M+ cells and + roughly 2 GiB of memory traffic: a single synchronous O(scrollback) job. +2. **It holds the terminal lock the whole time.** `Termio::resize` + (`crates/ghostty-termio/src/termio.rs`) runs the reflow inside + `lock_terminal()`, so: + - the renderer thread can't build a frame and that pane stops updating; + - the GTK main thread blocks whenever it touches that terminal. Adding a + split changes focus, and `focus_callback` + (`crates/ghostty-core/src/surface/mod.rs`) does + `self.lock_terminal().flags.focused = …`. Mouse motion, the mouse cursor + shape and key handling also take the lock. So the whole window freezes, + not just the pane. +3. **A drag means many full reflows in a row.** Resizes are only coalesced + for 25 ms (`COALESCE_MIN_MS`, `crates/ghostty-termio/src/thread.rs`). Each + coalesced step reflows everything, so during a drag the IO thread holds the + lock almost all the time. +4. **The inner loop does extra work.** `reflow_row` and `remap_cell` scan + every tracked pin for every cell, which is O(cells × pins). Every cell also + goes through `write_cell`, even on plain short lines that would fit + unchanged. + +Upstream Zig Ghostty has the same design (including the per-cell pin scan in +`reflowRow`), so this was inherited faithfully, not introduced by the rewrite. + +**Key point:** making the reflow faster helps but can't get to "smooth at all +times". Even at memory bandwidth, rewriting ~1 GiB takes 100 ms or more, far +beyond a 16 ms frame. To truly never stall, the full reflow must not happen +under the lock in one go. + +### Options, from cheapest to most thorough + +- [ ] **0. Resize benchmark** in `ghostty-bench` (fill N GiB, resize the + columns) to get real numbers before and after each change. + +- [ ] **A. Speed up the reflow** (low risk, contained in `page_list.rs`) + - [ ] Skip the pin scan for pages with no tracked pins (nearly all + scrollback), or pre-sort pins per page. This removes the O(cells × pins) + factor. + - [ ] Fast path for rows that fit at the new width and aren't wrapped: copy + the whole row at once (cells plus style/grapheme/hyperlink refs) instead + of cell by cell. + - [ ] Page-level fast path: when shrinking, a page with no wrapped rows + whose content already fits keeps its storage and just gets a smaller + `size.cols`, with no copy. + - Likely several times faster, but only the benchmark will tell. Still + won't be frame-time at 1 GiB. + +- [ ] **B. Stop the GTK main thread from blocking on the terminal lock** + (moderate, independent of A) + - [ ] Hot main-thread paths shouldn't need the lock: the focus flag via an + IO message or atomic; the mouse-reporting mode and mouse shape cached in + atomics. + - Result: the window, other panes, split creation and dragging stay smooth. + Only the pane being reflowed briefly shows its last frame. + - Probably the biggest perceived improvement for the effort. + +- [ ] **C. Defer the history reflow until the drag settles** (moderate) + - During a drag, reflow only the active screen. Reflow history once the + size has been stable for about 100–200 ms. + - Avoids repeated full reflows mid-drag; one stall at the end remains unless + combined with D. + +- [ ] **D. Incremental, chunked reflow** (the real fix, biggest change) + - Reflow from the newest content toward the oldest, a few milliseconds of + work per lock hold, releasing the lock in between so the renderer and pty + keep running. + - Requires the page list to allow pages of different widths temporarily, so + the renderer, selection, search and scrollbar must cope with "not yet + reflowed" rows. If another resize arrives mid-way, the job just switches + to the new target width. + - Variant: only reflow the last N rows eagerly and do older history in the + background, or when it's scrolled into view. + +- [ ] **E. Config escape hatch** (trivial) + - A setting such as "reflow only the last N MB/rows of scrollback" and leave + older history unreflowed. Useful as a stopgap but changes behaviour. + +### Recommended order + +1. **B + A first.** They're mostly contained, low risk, and remove the + whole-window freeze plus most of the per-pane stall. +2. **Then C** for live drags. +3. **D only if the remaining single-pane hitch still matters.** It's the only + option that fully guarantees smoothness, but it's a real design change and + diverges from upstream.