feat(terminal): clickable file references open the editor at the line - #173
Conversation
A second VTE match regex (FILE_PATTERN) underlines path-shaped text — absolute, ~/, or relative with at least one slash, with Claude Code's optional :line[:col] suffix. The regex only nominates candidates: at Ctrl+click, resolve_file_reference checks the filesystem (agent cwd first, then the tab's project root) and an unresolvable candidate falls through to the terminal unclaimed, so over-matched prose costs nothing. Resolved references open per kind: images in the lightbox, files inside the project in the tab's editor at the referenced line (the win.open-in-editor action now carries line/col, presents a popped-out pane, and re-places the cursor in already-open pages), everything else in the default app. file: URIs and OSC 8 file: hyperlinks route through the same path, so a reference behaves identically however the CLI emitted it. Cursor restore now scrolls via scroll_to_mark — on a freshly loaded buffer scroll_to_iter ran before line-height validation and silently stayed at the top. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dpjh2MJkFJNTjUbbZm1tSN
|
@claude review |
|
Claude finished @ghackett's task in 3m 52s —— View job Review: clickable file references (#173)
SummaryClean design overall: pushing filesystem validation to click-time (instead of trying to make the VTE hover-match itself accurate) is the right call, and Bug: column is off by one when restoring cursor position
def _on_open_in_editor(self, _action, param: GLib.Variant) -> None:
path, line, col = param.unpack()
self._open_in_editor(path, [line - 1, col] if line > 0 else None)The it.set_line_offset(max(0, min(int(offset), it.get_chars_in_line())))Since the status bar displays Fix: convert self._open_in_editor(path, [line - 1, max(0, col - 1)] if line > 0 else None)No existing test exercises this path end-to-end ( Minor notes
Branch: |
… fully Live-testing feedback on the clickable-references branch: - `collins/` (single segment, trailing slash) never matched — the relative alternative demanded a character after the slash. Its tail is optional now; the absolute alternative keeps a mandatory tail so a lone `/` in prose never links to the filesystem root. - Opening a large file at a deep line landed hundreds of lines short: scroll_to_mark's pending scroll is applied against estimated line heights on a freshly loaded buffer. The scroll is re-issued at PRIORITY_LOW, after the view's (higher-priority) validation idles have drained and heights are exact. Verified headlessly: a fresh open of a 1000-line file at :602 now shows line 602 (was stuck at ~44). Also verified: VTE matches references across its own soft wraps in full; references the CLI hard-wraps (real newline + indent) are two separate strings on screen and stay inert by design. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dpjh2MJkFJNTjUbbZm1tSN
|
Follow-up from live testing:
🤖 Generated with Claude Code |
Implements
~/specs/collins/clickable-file-references.md: the file references Claude Code prints constantly —collins/terminal.py:152,/abs/path/foo.py,~/x/y.md— were inert unless the CLI wrapped them infile://or OSC 8. Now they hover-underline and Ctrl+click opens them in the tab's own editor pane at the referenced line.How it works
FILE_PATTERN(linkpatterns.py, GTK-free likeURL_PATTERN): matches absolute paths,~/paths, and relative paths containing at least one/, with an optional:line[:col]suffix. Same ending discipline as URLs, so(collins/foo.py).andcollins/foo.py:12.shed the trailing punctuation. Bare filenames and paths with spaces are out of scope per the spec. A negative lookbehind keeps the grammar out of URLs entirely —https://a.b/cnever produces a file match.resolve_file_referencestrips the suffix, expands~, and tries the agent's current cwd (worktree-aware) then the tab's project root. If nothing exists on disk, the click falls through to the terminal unclaimed — over-matched prose (a/b, dates) costs the user nothing. (The hover underline can still show on prose; VTE has no per-match validation hook.)win.open-in-editornow carries(path, line, col), presents a popped-out editor window, and re-places the cursor when the file is already open); directories, outside-project files, and no-editor tabs fall back to the default app.file:URIs and OSC 8file:hyperlinks route through this same path, so a reference behaves identically however the CLI emitted it.scroll_to_mark—scroll_to_iteron a freshly loaded buffer runs before line-height validation and silently left the view at the top, which this feature made visible (cursor at line 42, view at line 1).Verification
resolve_file_referencecoverage (roots order,~, suffix vs literal-colon filenames, directories, misses). 51 tests pass.re.check_match_atmatched exactly the three staged references on the terminal grid (relative +:line:col, relative +:line, absolute), the gate rejecteda/b, and the editor landed at models.py 42:16 including the jump-within-already-open-file case.Ctrl+clicked
app/models.py:42:15in the panel — editor opens at line 42, col 16 (status bar):Notes for review
.claude/worktrees/<name>sits under the project root (is_insideaccepts it), and the agent-cwd root is tried first.🤖 Generated with Claude Code
https://claude.ai/code/session_01Dpjh2MJkFJNTjUbbZm1tSN