Skip to content

feat(mouse): #304 PR 4f — mouse_links module wiring#367

Merged
fentas merged 2 commits into
masterfrom
feat/304-pr4f-mouse-links-module
May 29, 2026
Merged

feat(mouse): #304 PR 4f — mouse_links module wiring#367
fentas merged 2 commits into
masterfrom
feat/304-pr4f-mouse-links-module

Conversation

@fentas
Copy link
Copy Markdown
Owner

@fentas fentas commented May 29, 2026

Summary

Wires the path detector from PR 4e (#366) into a full module that turns mouse clicks on file paths in terminal output into editor launches.

Pipeline

  1. onOutput — captures terminal output into a per-process ring of rows (default 256 × 1024 bytes). SGR + OSC sequences are stripped; \n / \r / BS are honoured.
  2. onMouseClick — left-press only. Maps screen (row, col) → captured row via a monotonic write counter, then runs path_detect.find from PR 4e. On hit, formats a POSIX-shell-safe injection command.
  3. pollShellInput — surfaces \x15<editor> +LINE 'path'\n to pty.master. The shell receives it as if the user typed it; the leading Ctrl+U (\x15) clears any half-typed prompt first.

Command format

mouse_links/inject.zig single-quotes the path with the standard '\'' escape — paths can contain *, ?, ;, $, backtick, even embedded newlines without breaking the shell parser.

`+LINE` is the lowest-common-denominator jump syntax: vim, nvim, vi, emacs, nano all honour it. Column is dropped (no portable convention across editors).

Editor resolution

`Config.editor` → `$EDITOR` → `$VISUAL` → silent no-op.

Click → row mapping

Streaming-line model: each \\n increments current_row. Visible window = current_row - term_rows + 1 .. current_row. Reserved bottom rows (statusbar) are excluded from the clickable area. When ctx.terminal_rows == null (non-TTY) clicks pass through.

TUIs in the alt-screen are not affected — atty's mouse intercept (PR 4c) is already gated on !shell_owns_input, so vim/htop see their own clicks normally.

Click coalescing

A second click while an injection is still queued is silently passthrough — prevents click-spam from overwriting a pending command before the proxy's next tick drains it.

Wiring

  • New Mouse type re-export in config_resolver.zig + root.zig so user configs can spell \pub const mouse: atty.Mouse = .{ .enabled = true }``
  • New module exported as \atty.modules.mouse_links``
  • Opt-in example added to src/config.def.zig

Roadmap (#304)

Test plan

  • `zig build test -Dtarget=x86_64-linux-gnu` — 1050/1050 pass (+25 mouse_links integration tests, +11 inject unit tests)
  • `zig fmt --check src/ build.zig`
  • `zig build -Dtarget=x86_64-linux-gnu -Doptimize=ReleaseSafe` — 10.7 MB binary
  • Manual smoke (deferred — needs Ghostty session with PR 4f-built atty)

🤖 Generated with Claude Code

Wires the path detector from PR 4e (#366) into a full module:

- onOutput captures terminal output into a per-process ring of
  rows (SGR + OSC stripped; CR / BS / \n line discipline)
- onMouseClick maps screen (row,col) → captured row →
  path_detect.find → POSIX-shell-safe injection command
- pollShellInput surfaces `\x15<editor> +LINE 'path'\n` to
  pty.master so the shell runs the editor via normal readline

Command formatting (`mouse_links/inject.zig`) single-quotes the
path with `'\''` escape — handles `*`, `?`, `;`, `$`, backtick,
embedded newlines. Editor resolution checks `Config.editor`,
`$EDITOR`, `$VISUAL`; falls back to silent no-op if none set.

Click → row mapping uses a monotonic write counter; visible
window = `current_row - term_rows + 1 .. current_row`. Streaming-
line model only — TUIs in alt-screen bypass the intercept
(shell owns input → atty's mouse intercept is gated off).
Statusbar reserve rows are excluded from clickable area.

A second click is silently passthrough while a prior injection
is still queued — prevents click-spam from overwriting the
pending command.

25 integration tests + 11 inject tests = 36 new tests.
- 1050/1050 unit tests pass
- ReleaseSafe build verified

Wires `atty.Mouse` re-export through resolver + root so user
configs can write `pub const mouse: atty.Mouse = .{ .enabled = true }`.

URL detection + atty-guard security gate lands in PR 4g.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

…sserts

Subagent flagged a real bug in the output-ring high-water-mark
accounting: a shorter rewrite after CR (\r) would leave stale tail
bytes from the previous content. Path detection then sees garbage
past the new write.

Fix: `line_lens[idx] = new_col` unconditionally on every printable
write (overwrite semantics replaces high-water-mark). Lock the new
behaviour with a paired test that fails with the old code —
`wrongabcdef\rsrc/y.zig\n` must produce `'src/y.zig'`, not
`'src/y.zigef'`.

Also:
- Comptime asserts `ring_rows > 0` and `row_bytes > 0` — prevents
  divide-by-zero panic if a user copies the example without
  uncommenting the defaults.
- WHY-only comment on the `inject_len = 0` reset in pollShellInput.
- Trim the file header — narrative WHAT was redundant with the
  hook names; keep only the WHY (streaming-line model + $EDITOR
  trust posture).

1051/1051 tests pass (+1 new for the bug repro).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@fentas fentas merged commit 9fef863 into master May 29, 2026
4 of 5 checks passed
@fentas fentas deleted the feat/304-pr4f-mouse-links-module branch May 29, 2026 23:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants