feat(terminal): stitch hard-wrapped file references at click time - #174
Conversation
When the CLI hard-wraps a long path (a real newline plus continuation indent), the screen holds two separate fragments and no regex can match across them. Clicking either fragment now stitches: geometry-gated (the fragment must touch its row's edge — the signature of a wrapped token), joined with the adjacent token of neighbour rows (chaining across whole-row middle fragments, 2 up / 3 down), re-matched against FILE_PATTERN, and existence-checked like any other candidate. Stitching runs before direct resolution: the leading fragment of a wrapped path is often an existing directory prefix, and the stitched whole is the truer reading. Verified live: both fragments of a hard-wrapped absolute path open the full file (the head fragment alone resolves to .claude/worktrees — the ordering hazard), a mid-row bogus candidate stays inert, and the y-to-row math holds under pixel scrolling with a scrolled-back buffer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dpjh2MJkFJNTjUbbZm1tSN
4584ae6 to
ea6cebc
Compare
|
@claude review |
|
Claude finished @ghackett's task in 4m 24s —— View job Claude finished the review
|
|
Addressed the review's three observations in the stacked follow-up #175 (which rewrites
On the pass-count verification: CI runs the suite (the local sandbox has no pytest); the current count on the follow-up branch is 28 linkpatterns tests, all green. |
…able (#175) Stacked on #174. A live testing pass there found that clicks on wrapped file references did nothing — and the failing half was the *continuation* fragment, the one holding the file name, i.e. exactly where you'd naturally click. Verified against a live headless terminal: with PR 174 as-is, only head-fragment clicks ever worked in Claude Code's real output format. ## The two causes - **Slashless continuations were never candidates.** `FILE_PATTERN` deliberately requires a slash, and a wrap point usually falls inside the basename, so the continuation row shows `o.py:7)` — no VTE match under the pointer, and the click handler returned before the stitcher was consulted. Now, when *nothing* under the pointer matches, the raw whitespace-delimited token at the clicked cell stands in as the stitch candidate (`token_at_column`, unit-tested). The stitcher's geometry gates plus the existence check keep Ctrl+clicks on ordinary prose inert — verified live for row-edge prose tokens and empty areas. - **Leading junk killed the join.** The emitter wraps whatever surrounds the path along with it, so fragments arrive glued to prefixes — `⏺ Read(/a/b/c` is Claude Code's own tool-call format, and the row-above token it contributes is `Read(/a/b/fo`, not `/a/b/fo`. The anchored `match` could only shed *trailing* punctuation, so every up-stitch through that format failed even when the continuation did contain a slash. Joins are now matched with a search over the joined string, with span guards requiring the hit to overlap the clicked fragment itself — one mechanism that sheds junk whether it sits in a contributed token or in a token-derived candidate, and that still refuses to resolve text the click never touched. ## Verification - 5 new unit tests: up-stitch through a `Read(`-glued head, a slashless token candidate keeping its `:7` suffix, a glued-prefix token candidate, the span guard (a junk-leading candidate must not resolve the row above's path), and `token_at_column` edge cases. 64 pass total. - Live headless run (real VTE, 40 rows of scrollback, pixel scrolling, Claude-style `⏺ Read(...)`/`⏺ Update(...)` wrapped output): all 8 click scenarios pass — both fragments of a wrapped reference resolve with the line suffix, a head token glued to `Update(` resolves, plain unwrapped references still resolve, and prose tokens at row edges plus empty areas stay inert. Before the fix the same probe failed 4 of 8. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01AXEJjBkzLLfVtVk6HhzFEn --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

Stacked on #173. Live testing there showed the one annoying gap: when the CLI hard-wraps a long path in its output, the screen genuinely contains two strings separated by a newline and indent, so the reference was inert however you clicked it.
How it works
Clicking a fragment that fails (or shouldn't win) direct resolution now tries stitching before giving up:
FILE_PATTERN(shedding trailing punctuation, keeping a:line[:col]suffix that landed on the continuation row) and must exist on disk. A stitch that guesses wrong opens nothing.…/.claude/worktrees), and without the reordering the click opens the file manager there instead of the file the reference actually names. The stitcher returns fast for unwrapped references, so ordinary clicks don't pay for it.The stitcher is pure and GTK-free (
resolve_wrapped_referencein linkpatterns.py, unit-tested); terminal.py only converts the click's y into a buffer row (pixel- and row-unit scrolling both handled, ±1 row slop for VTE's inner border) and feeds neighbour row texts.Verification
🤖 Generated with Claude Code
https://claude.ai/code/session_01Dpjh2MJkFJNTjUbbZm1tSN