Skip to content

Replace LSP-based symbol search with tree-sitter symbol index - #61950

Open
gaojunran wants to merge 10 commits into
zed-industries:mainfrom
gaojunran:tree-sitter-symbol-index
Open

Replace LSP-based symbol search with tree-sitter symbol index#61950
gaojunran wants to merge 10 commits into
zed-industries:mainfrom
gaojunran:tree-sitter-symbol-index

Conversation

@gaojunran

@gaojunran gaojunran commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Replace LSP-based symbol search (workspace/symbol) with a tree-sitter symbol index that provides client-side fuzzy matching using fuzzy_nucleo. This enables camelCase initialism queries (e.g. ibfoinitBookForOfficial), works without any LSP running, and indexes all languages in a single pass.

Discussion: #61880

Follow-Up: Use the same tree-sitter index way in agent ui @symbol search.

Showcase

(For manually test you can use https://github.com/gaojunran/zed/actions/runs/30794496633):

This PR: Tree-sitter version (quicker index, can index before actually opening a file, can fuzzy search, has grammar highlight on candidates, just like outline panel)

2026-07-31.17.32.26.mov

Current version (index speed depends on LSP and is always slower, cannot index before opening a file, no grammar highlights on candidates, must fully match, cannot search in many markup languages)

2026-07-31.13.26.28.mov

Solution

New crate crates/symbol_index/ — Core index with no dependency on project or worktree crates. SymbolIndex holds a flat Vec<IndexedSymbol> with Arc<str> fields. extract_symbols() parses source text with tree-sitter and runs the outline.scm query. IndexSnapshot provides Arc<[...]> for lock-free background search. Snapshot rebuild is deferred via a dirty flag — O(n) per search, not per batch.

crates/project/src/symbol_index_manager.rs — GPUI entity managing the indexing lifecycle. Initial scan processes files in parallel (buffer_unordered(num_cpus * 2)), pre-filters by language, groups by extension to load each grammar once. Incremental updates via WorktreeStore events use the same strategy. Batch operations use single HashSet retain passes.

crates/project_symbols/src/project_symbols.rs — Rewritten picker delegate. Search runs on in-memory IndexSnapshot with no LSP round-trips. Cancel flag prevents stale results. Labels are syntax-highlighted: context in keyword color, name colored by SymbolKind. Fuzzy match positions are offset to align with display text.

crates/picker/ + crates/picker_preview/PreviewSource::Path gains position: Option<Point> for symbol highlighting. New PreviewUpdate::from_path_with_position() constructor; from_path() kept for backward compatibility. picker_preview derives MatchLocation from position, extending to end-of-line.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior (See below)
  • Performance impact has been considered and is acceptable (Note that this improves performance than before)

Tests

  • 6 unit tests in symbol_index (extraction, initialism matching, add/remove, sorted results, empty query, concurrent mutation)
  • 2 integration tests in project_symbols (tree-sitter search, struct/enum/function matching)
  • picker and picker_preview tests pass
  • Clippy clean (--no-deps --lib)
  • Manual test: symbol search on a large project (10K+ files)
  • Manual test: can update after file save or git pull

Release Notes:

  • Added tree-sitter based symbol search with client-side fuzzy matching, enabling camelCase initialism queries and instant results without waiting for LSP indexing.

Introduce a new symbol_index crate that uses tree-sitter outline queries
to extract and fuzzy-search symbols, replacing the LSP-based project
symbols picker.

symbol_index crate:
- SymbolIndex with extract_symbols via tree-sitter outline queries
- IndexSnapshot for zero-copy concurrent search
- fuzzy_nucleo-based search with cancellation support
- Batch update API for efficient bulk indexing

SymbolIndexManager (project crate):
- GPUI entity integrated into Project via lazy init
- Subscribes to WorktreeStore events for incremental updates
- Batch indexing with progress tracking
- Fs trait for testability (FakeFs support)

project_symbols picker:
- Replaced LSP symbol search with symbol_index snapshot search
- Search cancellation guards against stale results overwriting
- Byte-accurate cursor positioning via Point (not PointUtf16)
- IndexedSymbol gains name_range for syntax highlight runs
- render_match colors context as keyword and name by SymbolKind
- PreviewSource::Path carries optional position for symbol highlighting
- picker_preview derives MatchLocation from position after buffer loads
…ions

- Lazy snapshot rebuild via dirty flag instead of per-batch O(n) clone (C1)
- Parallel file processing with buffer_unordered for 8x faster indexing (C2)
- Batch file removal with single retain pass (M1)
- Eliminate triple string storage: Arc<str> name + context, compute display on demand (M2)
- Single HashSet retain in update_files_batch instead of per-file O(n) (M3)
- Pre-filter files by language before collecting into file list (M4)
- Group files by extension and load each language grammar once (M5)
@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jul 30, 2026
@zed-community-bot zed-community-bot Bot added the first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions label Jul 30, 2026
@context captures appearing after @name in source (e.g. "(" and ")"
in TypeScript function_declarations) were collected unordered and joined
with spaces, producing "async ( ) mmdata" instead of "async function
mmdata". Now extract context text from source between the first capture
and the name node, preserving original token order.
@MrSubidubi MrSubidubi added the area:outline Feedback for outline view, symbols, etc label Jul 31, 2026
Mirror the initial scan strategy in on_updated_entries: group changed
files by extension, load each language grammar once, then process with
buffer_unordered. Avoids per-file language loading on bulk changes like
git checkout.
@gaojunran
gaojunran marked this pull request as ready for review July 31, 2026 09:36
gaojunran and others added 4 commits July 31, 2026 21:07
Replace context-only extraction with full source range from first to
last relevant capture (@context, @name, @OPEN, @close). Display text
now includes tokens after @name (e.g., "()" in TypeScript functions),
producing "async function mmdata()" instead of "async function mmdata".

Data model change: IndexedSymbol.context -> display_text + name_range,
where name_range tracks the name's byte position within display_text.
Take raw source range directly and normalize whitespace as a whole,
instead of splitting into before/after parts and rejoining with
explicit spaces. This preserves original spacing like "name()" instead
of "name ()".
Collect (byte_range, is_name) for each @context and @name capture,
then build display text by concatenating captured text in order. A
space is inserted only when there's a source gap between adjacent
captures. This preserves "mmdata()" (no gap) and "async function"
(gap), while excluding uncaptured tokens like "{" in destructuring.

@OPEN and @close captures are excluded from display text, matching
outline panel's next_outline_item behavior.
@gaojunran

Copy link
Copy Markdown
Contributor Author

@maxbrunsfeld Any idea for this? This is a follow up for #45719 which you previously reviewed! Sorry for the noise :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:outline Feedback for outline view, symbols, etc cla-signed The user has signed the Contributor License Agreement first contribution the author's first pull request to Zed. NOTE: the label application is automated via github actions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants