Skip to content

Make file paths in backticks clickable in agent panel#57303

Merged
MartinYe1234 merged 13 commits into
mainfrom
link-backticked-file-paths
May 21, 2026
Merged

Make file paths in backticks clickable in agent panel#57303
MartinYe1234 merged 13 commits into
mainfrom
link-backticked-file-paths

Conversation

@MartinYe1234

@MartinYe1234 MartinYe1234 commented May 20, 2026

Copy link
Copy Markdown
Contributor

When the agent mentions a file path inside backticks (e.g. `src/main.rs` or `src/main.rs:42`), the rendered code span now becomes a clickable link in the agent panel. Clicking opens the referenced file in the workspace, jumping to the right line and column when present.

How it works

  • Shared path resolution. Extracted OpenTarget and the workspace/worktree resolution logic out of terminal_view::terminal_path_like_target into a new workspace::path_link module so both the terminal and the agent panel can use the same code. Includes a sanitize_path_text helper ported from the terminal's URL/punctuation handling. Pure refactor — terminal behavior is unchanged.
  • markdown crate hook. Added MarkdownElement::on_code_span_link(callback). When the callback returns Some(url) for a given code span's contents, the existing push_link machinery wires up cmd-hover, hit testing, and the existing on_url_click callback. When it returns None, the code span renders as before. The hook is opt-in, so markdown stays workspace-agnostic.
  • Agent panel wiring. render_agent_markdown constructs an AgentCodeSpanResolver that snapshots the project's visible worktree entries plus their file extensions. try_resolve does a cheap synchronous heuristic check (path must contain //\ or end in an extension present in the workspace, can't be a URL, can't be all digits, etc.) and then looks the candidate up in the per-worktree HashSet<Arc<RelPath>>. On a hit it returns a MentionUri::File or MentionUri::Selection URI, which the existing thread_view::open_link already knows how to open at the right line.

Edge cases handled

  • Code spans inside fenced code blocks stay plain (gated on builder.code_block_stack.is_empty(), matching how regular markdown links behave).
  • Trailing prose punctuation (`src/main.rs.`) is stripped before lookup.
  • Identifiers like `String`, `await`, `npm run dev` stay plain — they don't pass the path-like heuristic.
  • Cross-platform path separators handled via the per-worktree PathStyle.

Tests

  • crates/markdown — unit test asserting code spans become links when the callback returns Some, and stay plain when it doesn't.
  • crates/agent_ui — unit test for AgentCodeSpanResolver::try_resolve covering hits with and without a :line suffix, misses, identifiers, and trailing punctuation.
  • Existing terminal_view tests cover the moved resolution code (unchanged behavior).

Notes

  • There's currently a temporary log::info! in AgentCodeSpanResolver::try_resolve that reports per-call worktree-walk timing and a cumulative total. Kept in for now to verify the feature isn't being called excessively during streaming renders. Can be removed before merge.
  • Resolution is sync-only against worktree entries; absolute paths outside the workspace are not resolved (would require an async re-render path).

Closes AI-277

Release Notes:

  • Made file paths in backticks clickable in the agent panel; clicking opens the referenced file at the given line when present.

Inline code spans the agent emits that resolve to a worktree file (e.g. `src/main.rs` or `src/main.rs:42`) now render as clickable links that open the referenced file at the given line.

Path resolution is shared with the terminal's path-hyperlink logic via a new workspace::path_link module, extracted from terminal_view::terminal_path_like_target. The markdown crate gained an on_code_span_link hook so consumers can opt into turning code spans into links without baking workspace knowledge into the crate. The agent panel wires this up via an AgentCodeSpanResolver that snapshots worktree files at render time and synchronously resolves candidate paths.
@MartinYe1234 MartinYe1234 self-assigned this May 20, 2026
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label May 20, 2026
@zed-community-bot zed-community-bot Bot added the staff Pull requests authored by a current member of Zed staff label May 20, 2026
MartinYe1234 and others added 12 commits May 20, 2026 12:47
Previously `AgentCodeSpanResolver` was rebuilt on every render, snapshotting all worktree files each time and re-resolving every code span on every layout pass. Scrolling a thread with code spans triggered tens of these calls per second.

Make the resolver an Arc-wrapped, cloneable handle owned by ConversationView and cloned into ThreadView. The worktree snapshot is now built once and refreshed only when project::Event::Worktree{Added,Removed,UpdatedEntries} fires; resolutions are memoized in an lru::LruCache capped at 2048 entries so memory cannot grow unboundedly across long sessions. Memory is freed when the ConversationView is dropped, which happens on thread switch.
Reject obviously-not-a-path text before the cache lookup instead of after, so prose like 'String', 'await', or regex snippets are filtered out without any cache or snapshot work. Also tighten is_path_like to reject text containing '|' (regex alternation, never valid in a path) and text shorter than 3 bytes (single- and two-char identifiers). The cache miss path now reuses the already-acquired snapshot and trimmed text instead of recomputing them.
Match worktree::Snapshot's storage so the agent's code-span path resolver shares the same handle Worktree::abs_path() returns, rather than cloning into a PathBuf. The duplicated absolutize logic now mirrors worktree::Snapshot::absolutize exactly, so any future improvement there carries over.
@MartinYe1234 MartinYe1234 marked this pull request as ready for review May 21, 2026 21:09
@MartinYe1234 MartinYe1234 added this pull request to the merge queue May 21, 2026
Merged via the queue into main with commit f78f6da May 21, 2026
32 checks passed
@MartinYe1234 MartinYe1234 deleted the link-backticked-file-paths branch May 21, 2026 22:07
TomPlanche pushed a commit to TomPlanche/zed that referenced this pull request Jun 2, 2026
…#57303)

When the agent mentions a file path inside `backticks` (e.g. ``
`src/main.rs` `` or `` `src/main.rs:42` ``), the rendered code span now
becomes a clickable link in the agent panel. Clicking opens the
referenced file in the workspace, jumping to the right line and column
when present.

## How it works

- **Shared path resolution.** Extracted `OpenTarget` and the
workspace/worktree resolution logic out of
`terminal_view::terminal_path_like_target` into a new
`workspace::path_link` module so both the terminal and the agent panel
can use the same code. Includes a `sanitize_path_text` helper ported
from the terminal's URL/punctuation handling. Pure refactor — terminal
behavior is unchanged.
- **`markdown` crate hook.** Added
`MarkdownElement::on_code_span_link(callback)`. When the callback
returns `Some(url)` for a given code span's contents, the existing
`push_link` machinery wires up cmd-hover, hit testing, and the existing
`on_url_click` callback. When it returns `None`, the code span renders
as before. The hook is opt-in, so `markdown` stays workspace-agnostic.
- **Agent panel wiring.** `render_agent_markdown` constructs an
`AgentCodeSpanResolver` that snapshots the project's visible worktree
entries plus their file extensions. `try_resolve` does a cheap
synchronous heuristic check (path must contain `/`/`\` or end in an
extension present in the workspace, can't be a URL, can't be all digits,
etc.) and then looks the candidate up in the per-worktree
`HashSet<Arc<RelPath>>`. On a hit it returns a `MentionUri::File` or
`MentionUri::Selection` URI, which the existing `thread_view::open_link`
already knows how to open at the right line.

## Edge cases handled

- Code spans inside fenced code blocks stay plain (gated on
`builder.code_block_stack.is_empty()`, matching how regular markdown
links behave).
- Trailing prose punctuation (`` `src/main.rs.` ``) is stripped before
lookup.
- Identifiers like `` `String` ``, `` `await` ``, `` `npm run dev` ``
stay plain — they don't pass the path-like heuristic.
- Cross-platform path separators handled via the per-worktree
`PathStyle`.

## Tests

- `crates/markdown` — unit test asserting code spans become links when
the callback returns `Some`, and stay plain when it doesn't.
- `crates/agent_ui` — unit test for `AgentCodeSpanResolver::try_resolve`
covering hits with and without a `:line` suffix, misses, identifiers,
and trailing punctuation.
- Existing `terminal_view` tests cover the moved resolution code
(unchanged behavior).

## Notes

- There's currently a temporary `log::info!` in
`AgentCodeSpanResolver::try_resolve` that reports per-call worktree-walk
timing and a cumulative total. Kept in for now to verify the feature
isn't being called excessively during streaming renders. Can be removed
before merge.
- Resolution is sync-only against worktree entries; absolute paths
outside the workspace are not resolved (would require an async re-render
path).

Closes AI-277

Release Notes:

- Made file paths in `backticks` clickable in the agent panel; clicking
opens the referenced file at the given line when present.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants