fix-deviant(links): bound hover lookup and renderer regex work

Follow-up testing showed that the mailbox limit did not fix the base64
hang: moving the pointer stalled the app, and opening a tab could hang it.
Mouse link lookup copied the entire wrapped line before checking modifier
eligibility, while renderer link searches had no whole-search work budget.

Filter eligible links before traversing scrollback. Add bounded logical
line selection and skip automatic regex lookup on lines exceeding 65,536
grid cells. Keep explicit line selection and OSC8 handling unrestricted.
Skip oversized lines instead of truncating URLs into different targets.

Apply the existing terminal StringMap retry budget to renderer searches
and handle search errors without panicking. The regression triggered a
retry-limit panic through the old onig search API and now returns normally,
allowing subsequent link patterns to run.

Document the lookup limits and correct the earlier mailbox fix entry to
record that it did not resolve the reported hang on its own.

Test Plan:
- 1,513 unit/integration tests and one doctest pass across core, renderer,
  and terminal with Mesa software OpenGL; one existing doctest is ignored.
- Release application build, formatting, and all-target Clippy pass.
- GTK handles 400 pointer moves, including Ctrl hover, during continuous
  base64 output, opens/closes a tab, handles Ctrl+C, prints a marker, and
  exits cleanly. Normal URL hover still works after a hard newline.

Reference: docs/rewrite/DEVIANT_FIXES.md (entry 41)
This commit is contained in:
ddidderr committed 2026-10-15 00:02:47 +02:00
1 parent 4c31d27888
commit 9010117d08
9 files changed
+153 -24

No files matched your search

+7
View File
@@ -132,6 +132,13 @@ GTK invokes `glib-compile-resources` directly so generated XML/UI files in
the runtime again if work remains. Continuous renderer notifications must
yield to GTK input, timers, and drawing instead of extending one tick
indefinitely.
Interactive link lookup checks modifier eligibility before scanning text.
`Surface::link_at_pin` uses `Screen::select_line_bounded` to skip logical
lines spanning more than 65,536 grid cells, bounding synchronous scrollback
traversal and string-map allocation while holding the terminal mutex.
Explicit text selection remains unrestricted. Renderer link regex searches
use the same 100,000-step whole-search retry budget as terminal `StringMap`
and treat regex failures as no further matches instead of panicking.
9. **Config:** same file format, same keys, same defaults, same CLI flags,
same docs text (used by `+show-config --docs` and `--help`).
10. **Tests:** every Zig `test "..."` block in a ported file is ported to a
+2 -1
View File
@@ -65,7 +65,8 @@ when this file was written.
| 37 | Termio: subprocess cleanup | `termio/Exec.zig:1188-1224` (`getpgid`) | Loops forever while `getpgid(child) == our pgid`, i.e. also when the child died before `setsid` and is an unreaped zombie (its pgid stays ours), hanging the surface close. | Still waits without a cap (no 5 s limit) while the child is alive, but stops (no SIGHUP, nothing to kill) once `waitid(WNOWAIT)` shows the child exited. | `b9ad595` fix-deviant(termio) | `getpgid_gives_up_on_exited_child_without_setsid`, `getpgid_waits_for_late_setsid` (`ghostty-termio/src/exec.rs`) |
| 38 | Config (Windows): absolute theme paths | `config/Config.zig:9788-9799` (`Theme.parseCLI`) | Any value containing `,`, `=` or `:` is parsed as a `light:..,dark:..` pair, and a Windows absolute path (`C:\\themes\\x`, `\\\\?\\C:\\...`) always contains a colon, so `theme = <absolute path>` is rejected on Windows (found by running the config tests under Wine). | On Windows a value that starts like an absolute Windows path (drive letter + `:` + separator, or `\\\\`) is a single theme; the pair syntax is unchanged (quote backslashes). Other platforms are unchanged. | `97a262d` fix-deviant(config) | `windows_absolute_path_detection`, `theme_windows_absolute_path_and_pair_with_paths` (`ghostty-config/src/types_b.rs`), `theme_loading` under Wine |
| 39 | Termio (Windows): bare program names | `Command.zig:261,347-358` (`startWindows`, `CreateProcessW(application_w, ...)`) with `termio/Exec.zig:1004` (`.path = self.args[0]`) | `lpApplicationName` is never searched on `PATH` (a partial name only completes against the current directory), so a `direct:` command / `-e` argv[0] such as `cmd.exe` fails with `FileNotFound`; only the default shell works (it is wrapped as `%WINDIR%\\System32\\cmd.exe /C ...`). | A name without drive/separator is looked up on `PATH` (`.exe` implied) before `CreateProcessW`; failures keep the original name (same error as Zig). | `4d7ad7c` fix-deviant(termio) | `resolve_application_searches_path_for_bare_names`, `bare_program_name_starts_via_path_search` (`ghostty-termio/src/command_windows.rs`), `echo_output_reaches_terminal_and_exit_is_reported` (`tests/exec_windows.rs`, Wine) |
| 40 | App: mailbox starvation | `App.zig:238-266` (`drainMailbox`) | Drains until the mailbox is empty. A producer that keeps refilling it can keep one app tick running indefinitely, preventing the runtime from dispatching input and timers. | Handles at most one mailbox capacity (64 messages) per tick, then wakes the runtime if messages remain. This bounds queue processing without dropping or reordering messages. The regression uses finite callback-driven refills; the reported full freeze with continuous base64 is not consistently reproduced in headless GTK. | this commit (`fix-deviant(core)`) | `app::tests::mailbox_tick_yields_and_rearms_when_producer_keeps_refilling` (`ghostty-core/src/app.rs`) |
| 40 | App: mailbox starvation | `App.zig:238-266` (`drainMailbox`) | Drains until the mailbox is empty. A producer that keeps refilling it can keep one app tick running indefinitely, preventing the runtime from dispatching input and timers. | Handles at most one mailbox capacity (64 messages) per tick, then wakes the runtime if messages remain. This bounds queue processing without dropping or reordering messages. The regression uses finite callback-driven refills. Follow-up user testing confirmed this alone does not fix the base64 hang; see entry 41 for the mouse/link path. | `4c31d27` | `app::tests::mailbox_tick_yields_and_rearms_when_producer_keeps_refilling` (`ghostty-core/src/app.rs`) |
| 41 | Mouse/link lookup under unbroken output | `Surface.zig:4431-4475` (`linkAtPin`), `renderer/link.zig:98-106` (regex search) | Mouse lookup selects and copies the entire soft-wrapped logical line before checking modifier eligibility, so newline-free output can make mouse motion process all scrollback under the terminal mutex. Renderer regex searches have no explicit whole-search retry budget. | Check eligible links first; skip logical lines exceeding 65,536 grid cells using bounded row traversal. Limit renderer regex searches to 100,000 retry steps, and handle errors without panicking (the old Rust search API panicked on retry errors). Explicit line selection stays unrestricted; oversized logical lines are not URL targets. | this commit (`fix-deviant(links)`) | `screen_select_line_bounded_*`, `long_unwrapped_output_allows_mouse_motion_and_new_tab_shortcut`, `render_cell_map_limits_backtracking_and_continues_with_other_links` |
## Upstream quirks deliberately kept (not fixed)