Skip to content

WIP: Implement unified palette with all 5 modes#53676

Draft
kathbigra wants to merge 14 commits intozed-industries:mainfrom
kathbigra:unified-palette-feature
Draft

WIP: Implement unified palette with all 5 modes#53676
kathbigra wants to merge 14 commits intozed-industries:mainfrom
kathbigra:unified-palette-feature

Conversation

@kathbigra
Copy link
Copy Markdown

@kathbigra kathbigra commented Apr 11, 2026

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content is consistent with the UI/UX checklist
  • Tests cover the new/changed behavior (39 tests passing)
  • Performance impact has been considered and is acceptable

Closes #15896

Release Notes:

  • Added unified palette that combines file finder, command palette, go-to-line, project symbols, and outline into a single interface with mode switching via prefixes (>, #, @, :)

Overview

This PR implements a unified command palette that consolidates 5 separate pickers (File Finder, Command Palette, Go To Line, Project Symbols, Outline) into a single modal with prefix-based mode switching.

Implementation Journey

Initial Approach

Started by attempting to reuse existing picker delegates, but encountered fundamental blockers:

  • Existing delegates are tightly coupled to their specific picker types via Context<Picker<SpecificDelegate>>
  • Private constructors and internal state made composition impossible
  • Sub-picker approach failed due to type system constraints

Final Solution

Built a new unified delegate from scratch with:

  • Single UnifiedPaletteDelegate handling all 5 modes
  • Mode detection from query prefixes
  • Async search with proper cancellation
  • Shared match storage with mode-specific rendering

Features Implemented

Core Modes

File Finder (default)

  • Opens with Cmd+P
  • Fuzzy search with match highlighting
  • File history (shows 10 most recent on empty query)
  • Path parsing: file.rs:42:10 navigates to line/column
  • File type icons
  • Enter to open, Cmd+Enter for split pane

Command Palette (> prefix)

  • Fuzzy search through available commands
  • Keyboard shortcuts displayed
  • Match highlighting shows why commands matched

Go To Line (: prefix)

  • Type :42 to jump to line 42
  • Works with active editor

Project Symbols (# prefix)

  • LSP-based symbol search across project
  • Two-line display: symbol name + file path with line number
  • Path truncation for long paths (middle elision)
  • Opens file and navigates to symbol

Outline (@ prefix)

  • Searches symbols in current file
  • Fuzzy matching with match highlighting
  • Live preview: navigates as you arrow through results
  • Single-line display format

Advanced Features

  • Async search with cancellation: Prevents stale results from slow searches
  • File history persistence: Stored in Workspace, survives palette close/reopen
  • Race condition prevention: Cancel flags in all async search paths
  • Split pane support: Cmd+Enter opens in adjacent pane
  • Mode switching: Seamless transition between modes, cancels pending searches

Issues Discovered & Fixed

P0 Critical Issues (Post-Implementation)

1. History Persistence Bug

  • Problem: file_history stored in delegate, wiped on every palette open
  • Root cause: New delegate created each time in register()
  • Fix: Moved file_history to Workspace with accessor methods
  • Tests: 4 tests (empty query, tracking, limit, ordering)

2. Missing Cancel Flags in Symbols/Outline

  • Problem: Async tasks could update stale/dismissed pickers
  • Root cause: Only file/command searches had cancellation logic
  • Fix: Added cancel_flag to search_project_symbols() and search_outline()
  • Tests: 4 tests (rapid typing, mode switching, symbols, outline)

3. Outline Match Highlighting Missing

  • Problem: Outline showed syntax highlighting but not fuzzy match positions
  • Root cause: Discarded match positions from fuzzy search results
  • Fix: Store positions in SymbolMatch, use HighlightedLabel for rendering
  • Tests: Visual verification (existing tests cover functionality)

P1 UX Issues

4. Path Overflow

  • Problem: Long file paths overflow UI, filename invisible
  • Fix: Middle elision for paths > 60 chars (start...end)

Compromises & Deferred Work

Deferred to Future PRs

  1. Symbol kind icons (ƒ, C, etc.)

    • Reason: No existing icon mapping in codebase
    • Would require design decision on icon set
  2. Command descriptions ("Editor: Save File")

    • Reason: Requires extracting metadata from action registry
    • Non-trivial implementation, low priority
  3. Selection reset optimization

    • Reason: Complex logic, low user impact
    • Current behavior is standard for pickers

Technical Decisions

Why not reuse existing pickers?

  • Type system constraints made composition impossible
  • Delegates expect specific Context<Picker<TheirDelegate>> types
  • Private constructors prevented instantiation
  • Building from scratch gave better control and cleaner code

Why store history in Workspace?

  • Delegate is recreated on every palette open
  • Workspace persists for entire session
  • Natural location for cross-session state

Why cancel flags AND search IDs?

  • Search IDs prevent out-of-order results
  • Cancel flags stop unnecessary work early
  • Both needed for robust async handling

Code Quality

Structure

  • Refactored rendering: Split 110-line render_match into 5 focused functions (10-35 lines each)
  • Proper error handling: No unwrap(), all errors propagated with ? or .log_err()
  • Async safety: Cancel flags prevent race conditions
  • Type safety: Strong typing throughout, no Any types

Test Coverage

39 tests passing covering:

  • All 5 modes (basic functionality)
  • Mode switching and cancellation (4 tests)
  • File history persistence (4 tests)
  • Match highlighting (2 tests)
  • Edge cases (no editor, empty queries, special characters)
  • Split pane navigation
  • Path parsing with line/column

Performance

  • Async search prevents UI blocking
  • Cancel flags stop unnecessary work
  • Fuzzy matching limited to 100 results per mode
  • File history limited to 10 items

Files Modified

  • crates/workspace/src/workspace.rs - Added file_history field and accessor methods
  • crates/unified_palette/unified_palette.rs - Complete implementation (960 lines)
  • crates/unified_palette/unified_palette_tests.rs - Test suite (1200+ lines)
  • crates/unified_palette/Cargo.toml - Dependencies
  • crates/zed/src/main.rs - Module initialization

Migration Notes

  • Existing Cmd+P keybinding now opens unified palette
  • All original functionality preserved
  • No breaking changes to user workflows

- Create new unified_palette crate with dependencies
- Implement single Picker with UnifiedPaletteDelegate
- Add PaletteMode enum for 5 modes (FileFinder, CommandPalette, ProjectSymbols, Outline, GoToLine)
- Add helper functions for prefix detection and mode availability
- Implement basic traits: Render, Focusable, ModalView, EventEmitter
- Add tests for prefix detection and mode availability (2/2 passing)
- Lazy-load sub-pickers for performance (Option 1 approach)

This commit includes:
- crates/unified_palette/ (new crate)
- Cargo.toml (workspace member added)
- Cargo.lock (dependency updates)
- Update UnifiedPaletteDelegate to store sub-picker entities
- Add lazy-loading placeholders for FileFinder, CommandPalette, ProjectSymbols
- Implement mode detection and switching in update_matches()
- Add TODOs for Phase 2 (forwarding queries to sub-pickers)
Phase 1: Structure
- Add UnifiedPaletteDelegate with mode enum (FileFinder, CommandPalette, ProjectSymbols, Outline, GoToLine)
- Store match data instead of sub-pickers (simpler approach)
- Implement PickerDelegate trait methods

Phase 2: Search Logic
- Implement simple file search with substring matching
- Add mode detection from query prefixes (>, #, @, :)
- Strip prefix before searching
- Mode switches automatically when prefix typed

Phase 3: Workspace Integration
- Add init() and register() functions
- Wire to workspace via ToggleFileFinder action
- Add to zed's Cargo.toml and main.rs
- Opens with cmd-p (same as FileFinder)

Current functionality:
- Type "file" → searches files in project
- Type ">cmd" → switches to CommandPalette mode (empty)
- Type "#sym" → switches to ProjectSymbols mode (empty)
- Backspace prefix → returns to FileFinder mode

Known limitations:
- Simple substring search (no fuzzy matching yet)
- CommandPalette and ProjectSymbols modes not implemented
- No file opening on Enter (Phase 4)
- Made FileFinder::new() and picker field public (temporary)

Tests: 2/2 passing
… modes

Implements a unified command palette that combines multiple search modes
into a single interface with automatic mode switching based on query prefix.

Implemented modes:
- File Finder (default): Search and open files with substring matching
- Command Palette (>): Search and execute commands
- Go To Line (:): Jump to specific line numbers

Architecture:
- UnifiedPalette modal with Picker<UnifiedPaletteDelegate>
- Match enum for different result types (File, Command, Line)
- WeakEntity pattern for proper modal lifecycle management
- Comprehensive logging for debugging

Not yet implemented:
- Project Symbols (#) and Outline (@) modes (require LSP integration)
- Fuzzy matching (currently substring matching only)
- File history
- Placeholder text updates on mode change

Release Notes:

- Added unified command palette with file finder, command search, and go-to-line functionality
…popup width

Fixes three critical issues found during testing:

1. Escape key now properly dismisses the modal
   - Added subscription to picker's DismissEvent
   - Forward dismiss events from picker to UnifiedPalette

2. Empty queries no longer dismiss modal on Enter
   - Track raw query in delegate
   - Prevent confirmation when query is just a prefix (>, :, #, @)
   - Stay open to allow user to continue typing

3. Popup width increased to show full command text
   - Added min_w(rems(34.)) to prevent text cutoff
   - Commands now fully visible in CommandPalette mode

All core functionality now working correctly:
- File finder, command palette, and go-to-line modes
- Mode switching, keyboard navigation, and modal dismissal

Release Notes:

- Fixed unified palette escape key, empty query handling, and display width
…modes

Adds a unified command palette that combines multiple search modes into a
single interface with automatic mode switching based on query prefix.

Fully implemented modes:
- File Finder (default): Search and open files
- Command Palette (>): Search and execute commands
- Go To Line (:): Jump to specific line numbers

Partially implemented modes (infrastructure only):
- Project Symbols (#): Mode switching works, search pending
- Outline (@): Mode switching works, search pending

Features:
- Automatic mode detection from query prefix
- Escape key dismisses modal
- Empty query handling (doesn't dismiss on Enter)
- Adequate popup width for command text
- Comprehensive logging for debugging

Technical details:
- Uses Picker with UnifiedPaletteDelegate
- Match enum for different result types
- WeakEntity pattern for modal lifecycle
- Subscription to picker dismiss events

Follow-up work needed:
- Implement Project Symbols search and navigation
- Implement Outline search and navigation
- Add fuzzy matching (currently substring only)
- Add file history

Release Notes:

- Added unified command palette with file finder, command search, and go-to-line modes (Project Symbols and Outline modes coming soon)
Add support for searching and navigating to project symbols using the `#` prefix
in the unified palette. Symbols are fetched from LSP and displayed with a
two-line layout showing the symbol name and file path with line number.

- Add SymbolMatch type to store LSP symbol data
- Implement async search_project_symbols() that queries project.symbols()
- Add symbol navigation that opens files and jumps to symbol location
- Support both normal (Enter) and split pane (Cmd+Enter) navigation
- Render symbols with two-line layout matching original project symbols UI
- Display file path and line number on second line

Phase 1 (Symbol Search) and Phase 2 (Symbol Navigation) complete.
Add full outline mode support to unified palette, completing all 5 modes.
Outline symbols are displayed with syntax highlighting and live preview
navigation, matching the original Cmd+Shift+O behavior.

- Add outline mode search that extracts symbols from current file
- Implement live preview: arrow keys navigate to symbols without Enter
- Add syntax highlighting for outline symbols (colored function names)
- Use single-line format for outline (no file path/line number)
- Preserve highlight ranges from outline items for proper rendering
- Fix confirmation to work with empty queries when matches exist
- Update testing guide with comprehensive outline and project symbols tests

Phase 3 (Outline Mode) complete. All 5 modes now fully functional:
- File Finder (default)
- Command Palette (>)
- Go To Line (:)
- Project Symbols (#)
- Outline (@)
@cla-bot cla-bot bot added the cla-signed The user has signed the Contributor License Agreement label Apr 11, 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 Apr 11, 2026
Implements Phase 5 (P1) and Phase 6 (P2) improvements based on code review:

Phase 5 - Performance & Search (P1 - Blocking):
- Add asynchronous file and command search with background execution
- Implement search cancellation using Arc<AtomicBool> pattern
- Add fuzzy matching for files (fuzzy_nucleo) and commands (fuzzy)
- Prevent stale search results with search ID tracking
- Cancel searches on mode switching

Phase 6 - UX Improvements (P2 - Before Merge):
- Support path parsing with line/column (e.g., "file.rs:42:10")
- Display keyboard shortcuts next to commands in command palette
- Respect PreviewTabsSettings for file preview behavior
- Add 7 new test cases for Phase 5 & 6 features

Test Coverage:
- 29 tests total (22 original + 7 new)
- Tests cover fuzzy matching, search cancellation, path parsing
- All tests passing

Release Notes:

- Improved unified palette performance with async search and fuzzy matching
- Added support for jumping to specific lines/columns (e.g., "file.rs:42:10")
- Command palette now shows keyboard shortcuts
Implements Phase 7 (P3 - Optional) visual improvements:

Phase 7.1 - Placeholder Polish:
- Verified placeholder text updates correctly on mode switch
- Returns mode-specific text ("Go to file...", "Execute a command...", etc.)

Phase 7.2 - Icon Rendering:
- Add file_icons dependency
- Display file type icons in file finder results
- Icons show for .rs, .js, .ts, and other file types
- Uses FileIcons::get_icon() with start_slot in ListItem

Test Coverage:
- All 29 tests passing
- File icons render correctly without breaking existing functionality

Note: Phase 7.3 (label highlighting) and 7.4 (refactoring) deferred
as optional improvements for future PRs.

Release Notes:

- File finder now displays file type icons for better visual recognition
… history

Addresses critical P0 blockers and high-priority P1 improvements identified
in code review.

## P0 Fixes (Blocking Merge)

### Race Conditions in Symbols/Outline
- Add search_id tracking to search_project_symbols() and search_outline()
- Prevent stale LSP results from overwriting fresh queries
- Increment search_id even on early returns (empty query, no editor)

### Match Highlighting
- Add match_positions field to FileMatch and CommandMatch
- Extract positions from fuzzy_nucleo and fuzzy::match_strings
- Render with ui::HighlightedLabel to show why items matched
- Users can now see highlighted characters (e.g., "fpl" → **f**ile_**p**a**l**ette.rs)

### File History (Recent Files)
- Add file_history: Vec<ProjectPath> to delegate
- Track opened files in confirm() (most recent first, limit 10)
- Show history on empty query (Cmd+P → Enter workflow)
- Fixes regression where empty query showed nothing

## P1 Fixes (High Priority)

### Outline Fuzzy Matching
- Replace substring matching with fuzzy::match_strings()
- Matches existing @ picker behavior
- Provides feature parity with original outline picker

### Split Pane Test
- Add test_split_pane_secondary_action to verify Cmd+Enter
- Tests secondary action (confirm with secondary=true)

## Testing
- Added 10 new tests (39 total, all passing)
  - 2 race condition tests
  - 2 match highlighting tests
  - 4 file history tests
  - 1 outline fuzzy matching test
  - 1 split pane test

## Dependencies
- Added fuzzy::StringMatchCandidate import for outline fuzzy matching

Fixes critical bugs that made symbols/outline unreliable and restores
expected file finder behavior (recent files on empty query).
Implements a unified command palette that combines file finder, command palette,
project symbols, outline, and go-to-line into a single modal with prefix-based
mode switching.

Features:
- Mode switching via prefixes: >, #, @, : (default: file finder)
- Fuzzy search with match highlighting across all modes
- File history persistence in workspace
- Async search with cancellation to prevent stale results
- Path parsing with line/column support (file.rs:42:10)
- Split pane support (Cmd+Enter)
- Live preview for outline mode
- Path truncation for long paths (middle elision)
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 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.

VSCode style unified palettes (global search)

1 participant