Skip to content

feat: add Quick Open file search (Cmd+Shift+O) to command palette#5982

Open
tctony wants to merge 1 commit into
manaflow-ai:mainfrom
tctony:feat_quick_open
Open

feat: add Quick Open file search (Cmd+Shift+O) to command palette#5982
tctony wants to merge 1 commit into
manaflow-ai:mainfrom
tctony:feat_quick_open

Conversation

@tctony

@tctony tctony commented Jun 12, 2026

Copy link
Copy Markdown

Summary

  • What changed?: impl feature quick open file like Go To File (Cmd+P) in vscode.

    • Open quick open file in command palette via shortcut Cmd+Shift+O).
    • @abc/xyz to search files within the workspace with cross-directory fuzzy search.
    • Supports path-based directory browsing with querys start with @/, @~, @./.
    • Open selected file or directory using default app just like Cmd+click on some file path
    • Handle exe script (open with default text editor) and binary (show in finder) properly
  • Why?: Users need fast opening and review files that just edited by coding agent!

Testing

  • 38 unit tests covering scope detection, query extraction, path resolution, matching term generation, and directory skipping
  • './scripts/reload.sh --tag quick-open-file --launch'
    Manual testing: workspace root listing, home directory browsing, cross-directory fuzzy search with cancellation, symlink handling, file open safety (scripts → text editor, binaries → Finder**

Demo Video

cmux-quick-open-file.mov

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed (docs/quick-open-file.md)
  • I requested bot reviews after my latest commit
  • All code review bot comments are resolved
  • All human review comments are resolved

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

Release Notes

New Features

  • Added Quick Open file search feature with Cmd+Shift+O keyboard shortcut for rapid file discovery across your workspace
  • Integrated file search into command palette using @ prefix, enabling cross-directory searching and directory navigation
  • File opening supports multiple actions: open in default editor, reveal in Finder, or open as text

Documentation

  • Added comprehensive Quick Open feature documentation

Tests

  • Added extensive test coverage for file search functionality

@vercel

vercel Bot commented Jun 12, 2026

Copy link
Copy Markdown

@tctony is attempting to deploy a commit to the Manaflow Team on Vercel.

A member of the Team first needs to authorize it.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Quick Open file-search mode to the command palette, triggered by Cmd+Shift+O and the @ query prefix. Introduces path-mode (single-directory listing) and cross-directory BFS fuzzy search, a new CommandPaletteQuickOpenFileSearch engine, shortcut/menu entry wiring, localization strings for en/ja/zh-Hans, 47 unit tests, and a full behavioral specification document.

Changes

Quick Open File Search

Layer / File(s) Summary
Shortcut action definitions and default bindings
Packages/CmuxSettings/.../ShortcutAction.swift, Packages/CmuxSettings/.../ShortcutAction+Defaults.swift, Sources/KeyboardShortcutSettings.swift
Adds quickOpenFile enum case to ShortcutAction and KeyboardShortcutSettings.Action, assigns workspace group membership and localized labels, and moves the ⌘⇧O default binding from reopenPreviousSession to quickOpenFile.
Command palette package contracts
Packages/CmuxCommandPalette/.../CommandPaletteListScope.swift, Packages/CmuxCommandPalette/.../CommandPaletteRequestKind.swift, Packages/CmuxCommandPalette/.../QuickOpen/CommandPaletteQuickOpenFileOpenAction.swift, Packages/CmuxCommandPalette/.../QuickOpen/CommandPaletteQuickOpenScoredFile.swift, Packages/CmuxCommandPalette/Tests/.../CommandPaletteRequestKindTests.swift
Adds fileSearch case to CommandPaletteListScope and CommandPaletteRequestKind (with notificationName and marksPending wiring), introduces CommandPaletteQuickOpenFileOpenAction (open/reveal/textEditor), CommandPaletteQuickOpenScoredFile data struct, and asserts the legacy notification name in tests.
Shortcut routing, notification constant, menu entry, and ContentView scope wiring
Sources/TabManager.swift, Sources/cmuxApp.swift, Sources/AppDelegate.swift, Sources/ContentView.swift
Declares commandPaletteFileSearchRequested notification constant, adds the "Quick Open…" File menu button, wires three AppDelegate shortcut routing paths, and connects notification handling, @-scope detection, dedup-state reset, and UI placeholder/empty-state strings in ContentView.
Core search engine and ContentView bridge
Packages/CmuxCommandPalette/.../QuickOpen/CommandPaletteQuickOpenFileSearch.swift, Sources/CommandPaletteQuickOpenFileSearch.swift
Implements CommandPaletteQuickOpenFileSearch with query parsing, fingerprinting, path resolution, file classification, cross-directory BFS with bounded TaskGroup, fuzzy scoring, top-K heap, relative path derivation, and isTextFile detection. The ContentView extension bridges all calls and handles main-actor NSWorkspace open/reveal/textEditor dispatch.
Query normalization, path-mode listing, and ContentView search entry handling
Sources/ContentView.swift
Strips @ prefix and normalizes the matching query, resolves path-mode vs cross-directory mode, generates synchronous path-mode directory listing commands with . dot entry and @-query rewriting for navigation, and wires workspace-root resolution.
Search refresh orchestration, dedup, and async result materialization
Sources/ContentView.swift
Updates corpus refresh for the file-search scope, implements cross-directory dedup fingerprint guard, derives nucleoQuery from the matching-term extractor, filters score-0 results, enforces file-search result limits, runs a cancellable background task for cross-directory search that materializes palette commands on the main actor, and selects the correct search fingerprint.
Localized strings
Resources/Localizable.xcstrings
Adds localization keys for command-palette kind labels (directory, file, open in Finder), file-search placeholder and empty-state text, and Quick Open menu/shortcut labels across en, ja, and zh-Hans.
Unit tests and feature specification docs
Packages/CmuxCommandPalette/Tests/.../CommandPaletteQuickOpenFileSearchTests.swift, docs/quick-open-file.md
Adds 47 XCTest cases covering scope selection, query resolution, path resolution/formatting, matching-term normalization, cross-directory fuzzy slash semantics, symlink-cycle dedup, relative-path output, and directory skipping; populates docs/quick-open-file.md with the full behavioral specification.
Project wiring, scheme bumps, and lockfile
cmux.xcodeproj/project.pbxproj, Examples/SampleSidebarExtensionApp/.../*.xcscheme, ios/cmuxPackage/Package.resolved
Adds CommandPaletteQuickOpenFileSearch.swift to the Xcode build phase and source group, bumps sample app scheme versions from 1.7 to 1.8, and updates the iOS SwiftPM lockfile pins.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant AppDelegate
  participant NotificationCenter
  participant ContentView
  participant CommandPaletteQuickOpenFileSearch
  participant NSWorkspace

  rect rgba(100, 149, 237, 0.5)
    note over User, AppDelegate: Trigger
    User->>AppDelegate: Cmd+Shift+O keystroke
    AppDelegate->>NotificationCenter: post commandPaletteFileSearchRequested<br/>(markPending: true)
  end

  rect rgba(144, 238, 144, 0.5)
    note over NotificationCenter, ContentView: Palette open
    NotificationCenter->>ContentView: commandPaletteFileSearchRequested
    ContentView->>ContentView: openCommandPaletteFileSearch()<br/>→ handleCommandPaletteListRequest(scope: .fileSearch)<br/>→ set query to "@"
  end

  rect rgba(255, 200, 100, 0.5)
    note over ContentView, CommandPaletteQuickOpenFileSearch: Search execution
    ContentView->>CommandPaletteQuickOpenFileSearch: resolve(matchingQuery, workspaceRoot)
    CommandPaletteQuickOpenFileSearch-->>ContentView: ResolvedPath (currentDir, searchTerm, isPathMode)
    alt Path mode
      ContentView->>CommandPaletteQuickOpenFileSearch: listFiles(inDirectory:, maxCount:)
      CommandPaletteQuickOpenFileSearch-->>ContentView: [URL] directory listing
    else Cross-directory mode
      ContentView->>CommandPaletteQuickOpenFileSearch: searchCrossDirectory(query, rootDir)
      CommandPaletteQuickOpenFileSearch-->>ContentView: [ScoredFile] ranked
    end
  end

  rect rgba(220, 130, 220, 0.5)
    note over ContentView, NSWorkspace: File open
    ContentView->>CommandPaletteQuickOpenFileSearch: openAction(for: url)
    CommandPaletteQuickOpenFileSearch-->>ContentView: QuickOpenFileOpenAction
    ContentView->>NSWorkspace: open / reveal / openWith textEditor
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • manaflow-ai/cmux#4445: Both PRs modify Sources/AppDelegate.swift shortcut-chord routing and arming logic in handleCustomShortcut; this PR extends that routing to add the .quickOpenFile action via the same chord/command-palette path.
  • manaflow-ai/cmux#6029: This PR extends CommandPaletteListScope (adding fileSearch) and CommandPaletteRequestKind in the same package files that PR #6029 introduced or owns, making them directly connected at the enum/contract level.

Suggested reviewers

  • lawrencecchen

🐇 A hop, a skip, a @ away,
Type a name and files display!
Fuzzy BFS through every tree,
⌘⇧O sets the search free!
Directories spin, top-K ranks soar,
Quick Open file search — who could ask for more? ✨


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (6 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Expensive Synchronous Load ❌ Error The PR adds expensive synchronous file I/O on the main interactive thread. Trace: onChange (main) → scheduleCommandPaletteResultsRefresh → refreshCommandPaletteSearchCorpus → commandPaletteFileSear... Move synchronous directory listing to a Task.detached background operation like cross-directory search does, or cache results off-main. Avoid blocking command palette updates on file I/O.
Cmux Algorithmic Complexity ❌ Error Two algorithmic complexity violations in production code: (1) Line 446 in CommandPaletteQuickOpenFileSearch.swift calls heap.sort on every insertion in hot searchCrossDirectoryBranch path; (2) Line... Precompute directory flags before sorting at line 209; replace repeated full-heap sorting at line 446 with min-heap sift-down after appending (only sort once when limit reached).
Cmux Swift @Concurrent ❌ Error Two nonisolated async functions in Sources/CommandPaletteQuickOpenFileSearch.swift (quickOpenFileOpenAction and searchCrossDirectory) are missing @concurrent annotations despite being file-I/O-heav... Add @concurrent to both nonisolated async functions: quickOpenFileOpenAction (line 65) and searchCrossDirectory (line 103) to allow safe actor-leaving for file I/O work.
Cmux Swift File And Package Boundaries ❌ Error PR adds 272 lines to ContentView.swift (already 16827 lines, over 800-line threshold), exceeding the 250-line limit for oversized files. File grew by net +254 lines, not the required >200-line shri... Refactor to add high-level orchestration helpers to CmuxCommandPalette (dedup + result generation logic), then invoke those from ContentView with single-line calls. This approach would reduce ContentView by ~150+ lines while preserving t...
Cmux Full Internationalization ❌ Error 7 new string catalog entries added to Resources/Localizable.xcstrings lack translations for 17 of the 20 supported locales (ar, bs, da, de, es, fr, it, km, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Han... Add missing locale translations for: shortcut.quickOpenFile.label, menu.file.quickOpenFile, commandPalette.search.fileSearchEmpty, commandPalette.search.fileSearchPlaceholder, commandPalette.kind.directory, commandPalette.kind.file, and...
Cmux Architecture Rethink ❌ Error PR introduces fileSearchDedupFingerprint static mutable flag for inter-instance coordination between ContentView instances without synchronization, creating a side-channel that violates architect... Move fileSearchDedupFingerprint into a MainActor-isolated singleton registry like SidebarWorkspaceDragRegistry, or redesign to have a single owner coordinate searches across instances.
Docstring Coverage ⚠️ Warning Docstring coverage is 24.81% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (14 passed)
Check name Status Explanation
Title check ✅ Passed The PR title accurately summarizes the main feature addition: implementing Quick Open file search with Cmd+Shift+O in the command palette.
Description check ✅ Passed The PR description covers the required sections including Summary (what changed and why), Testing (unit tests and manual testing approach), and Demo Video. Documentation and checklist items are also addressed.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR follows Swift 6 actor isolation best practices: all new types (3 enums/structs) explicitly marked Sendable, no mutable reference types added, filesystem operations marked nonisolated, NSWorkspac...
Cmux Swift Blocking Runtime ✅ Passed PR introduces no blocking or timing-based synchronization in quick-open file search code; uses async/await with withTaskGroup and Task.isCancelled for proper background search coordination.
Cmux Cache Substitution Correctness ✅ Passed File search cache structures are transient @State variables, never persisted. No cache substitution in persistence/history/undo/snapshot paths.
Cmux No Hacky Sleeps ✅ Passed PR contains only Swift source files, Xcode project configs, and documentation. The "no hacky sleeps" check applies exclusively to TypeScript, JavaScript, shell, and non-Swift build/runtime scripts,...
Cmux Swift Concurrency ✅ Passed PR uses modern Swift concurrency (async/await, TaskGroup, @MainActor, stored Task with cancellation) instead of legacy patterns; no DispatchQueue.global(), Combine state, or problematic fire-and-fo...
Cmux Swift Logging ✅ Passed All new production code in the quick-open feature (124+ lines across 4 new files) contains zero logging violations; no print/debugPrint/dump/NSLog found.
Cmux User-Facing Error Privacy ✅ Passed All user-facing strings in the Quick Open file search feature are generic and safe: "Quick Open…", "Search files by name", "No files found", "File", "Directory". No vendor names, credentials, token...
Cmux Swiftui State Layout ✅ Passed PR introduces new types as plain Sendable structs/enums without ObservableObject/@published; state modifications occur in .onChange lifecycle callbacks and task completions, not render-time body mu...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds Quick Open file search integrated into existing command palette—no new NSWindow, NSPanel, NSWindowController, or SwiftUI Window/WindowGroup created; feature routes Cmd+Shift+O shortcut via...
Cmux Source Artifacts ✅ Passed All 21 changed files comply with the source control artifacts rule: Swift source/test code, Xcode configs, localization catalogs, documentation, and SwiftPM lock file are all intentional product/bu...
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements a Quick Open file search (Cmd+Shift+O) for the command palette, activated via the @ prefix. It supports two modes: path-mode for directory browsing (@/, @~, @./) and cross-directory fuzzy search with BFS across the workspace tree.

  • Core logic is correctly extracted into a new CommandPaletteQuickOpenFileSearch SwiftPM package type (473 lines), with the previous Task.detached + DispatchQueue.concurrentPerform + NSLock pattern replaced by Task(priority:) + withTaskGroup, and the fileSearchDedupFingerprint static var now only accessed from @MainActor context.
  • ios/cmuxPackage/Package.resolved was accidentally replaced wholesale — format downgraded from v3 to v2, 11 unrelated packages added (posthog-ios, sentry-cocoa, sparkle, xcodeproj, swift-syntax, etc.), and swift-asn1 downgraded from 1.7.0 to 1.6.0; this needs to be reverted before merge.
  • Localization covers only en/ja/zh-Hans for all new string keys; 16 of the 19 locales already supported by Localizable.xcstrings (zh-Hant, ko, de, es, fr, it, da, pl, ru, bs, ar, nb, pt-BR, th, tr, uk) are missing translations.

Confidence Score: 3/5

Not safe to merge as-is: the iOS Package.resolved was accidentally replaced with a completely different lockfile that adds 11 unrelated SDKs, and all new user-facing strings are missing translations for 16 of 19 supported locales.

Two issues must be resolved before merging. The iOS lockfile replacement is a concrete build-graph corruption: it adds posthog-ios, sentry-cocoa, sparkle, and other large SDKs that have no connection to this feature, and downgrades swift-asn1 from 1.7.0 to 1.6.0. The localization gap means Quick Open UI text will appear in English for users of 16 supported languages the moment this ships.

ios/cmuxPackage/Package.resolved (needs full revert to pre-PR state) and Resources/Localizable.xcstrings (needs translations for zh-Hant, ko, de, es, fr, it, da, pl, ru, bs, ar, nb, pt-BR, th, tr, uk).

Important Files Changed

Filename Overview
Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenFileSearch.swift New 473-line file implementing file search helpers (BFS, fuzzy scoring, path resolution, isTextFile). Logic is well-structured; uses withTaskGroup for concurrency.
Sources/CommandPaletteQuickOpenFileSearch.swift New 124-line ContentView extension bridging package helpers to app-layer wrappers. openFileInDefaultEditor correctly wraps openAction(for:) in a Task.
Sources/ContentView.swift +302 lines adding Quick Open integration. Previous Task.detached/DispatchQueue.concurrentPerform pattern replaced with Task(priority:)/withTaskGroup; fileSearchDedupFingerprint static var now only accessed from @mainactor context.
ios/cmuxPackage/Package.resolved Lockfile accidentally replaced — format downgraded from v3 to v2, originHash removed, 11 unrelated packages added and swift-asn1 downgraded from 1.7.0 to 1.6.0.
Resources/Localizable.xcstrings Adds ~161 lines of new string catalog entries for Quick Open UI, but only en/ja/zh-Hans translations are provided; 16 of the 19 supported locales are missing translations for all new keys.
Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction.swift Adds quickOpenFile case with correct String(localized:defaultValue:). Shortcut default for reopenPreviousSession moved to nil to free Cmd+Shift+O.
Sources/AppDelegate.swift Adds quickOpenFile shortcut handling in two places, following the exact same pattern as commandPalette and goToWorkspace.
Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteQuickOpenFileSearchTests.swift 38 unit tests covering scope detection, query extraction, path resolution, matching terms, and directory-skip logic.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["Cmd+Shift+O / Menu"] --> B["NSNotification: commandPaletteFileSearchRequested"]
    B --> C["ContentView.openCommandPaletteFileSearch()"]
    C --> D["handleCommandPaletteListRequest(scope: .fileSearch)"]
    D --> E["commandPaletteQuery = '@'"]
    E --> F["commandPaletteListScope(for: query) == .fileSearch"]
    F --> G["commandPaletteFileSearchResolve()"]
    G --> H{isPathMode?}
    H -->|"@/, @~, @./"| I["commandPaletteFileSearchPathEntries()\nSync: FileManager.contentsOfDirectory\n@MainActor"]
    H -->|"@query"| J["Cross-directory BFS\nTask(priority: .userInitiated)\nwithTaskGroup"]
    J --> K["searchCrossDirectoryBranch()\nBFS + fuzzyMatch\nnonisolated async"]
    K --> L["Top-30 scored results\nMainActor.run → update UI"]
    I --> M["CommandPaletteCommand list\nNucleo fuzzy scoring"]
    M --> N["User selects file"]
    L --> N
    N --> O["openFileInDefaultEditor(url)\nTask → openAction(for:)\nisTextFile / UTType check"]
    O --> P{Action}
    P -->|script| Q["Open in text editor"]
    P -->|binary| R["Reveal in Finder"]
    P -->|regular| S["NSWorkspace.shared.open()"]
Loading

Reviews (8): Last reviewed commit: "feat: add Quick Open file search to comm..." | Re-trigger Greptile

Comment thread Sources/ContentView.swift Outdated
Comment on lines +5630 to +5636
let sq = Self.commandPaletteFileSearchMatchingTerm(capturedMatchingQuery)
let rootDir = capturedFSRoot ?? NSHomeDirectory()
Self.fileSearchGeneration &+= 1
let gen = Self.fileSearchGeneration
let scored = Self.searchCrossDirectory(query: sq, rootDir: rootDir, shouldCancel: { Self.fileSearchGeneration != gen })
await MainActor.run {
guard commandPaletteSearchRequestID == requestID,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Data race on fileSearchGeneration static var

fileSearchGeneration is a private static var on a @MainActor-bound View struct. It is written (&+= 1) and read inside a Task.detached block, which inherits no actor isolation and runs on the cooperative thread pool. The shouldCancel closure that captures it is then passed into DispatchQueue.concurrentPerform, where multiple concurrent threads read the same value without any synchronization. In Swift 6 strict-concurrency mode, accessing a @MainActor-isolated static from a non-isolated detached task is a data-isolation violation, and at runtime it is a plain data race.

Rule Used: Flag new or materially worsened Swift 6 actor isol... (source)

case .newBrowserWorkspace:
return String(localized: "shortcut.newBrowserWorkspace.label", defaultValue: "New Browser Workspace")
case .openFolder: return "Open Folder"
case .quickOpenFile: return "Quick Open…"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The label for quickOpenFile is a hardcoded string literal instead of routing through String(localized:defaultValue:) like the surrounding cases. The string catalog (shortcut.quickOpenFile.label) with zh-Hans translation exists in Localizable.xcstrings but is never referenced by this call site, so the label will always appear in English.

Suggested change
case .quickOpenFile: return "Quick Open…"
case .quickOpenFile: return String(localized: "shortcut.quickOpenFile.label", defaultValue: "Quick Open…")

Rule Used: Flag production user-facing text that is not fully... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread Sources/ContentView.swift Outdated
Comment on lines +6168 to +6174
let sorted = contents.prefix(maxCount).sorted { a, b in
let aIsDir = (try? a.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
let bIsDir = (try? b.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
if aIsDir != bIsDir { return aIsDir }
return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
}
return Array(sorted)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 contents.prefix(maxCount) is applied before the sort, so for directories with more than 1 000 entries the function takes an arbitrary slice of up to 1 000 URLs (in filesystem-enumeration order) and only then sorts them alphabetically. Entries whose names start later in the alphabet can be silently excluded even if they should appear in the sorted top-1 000. Sorting the full set first, then capping, gives deterministic, alphabetically-correct results.

Suggested change
let sorted = contents.prefix(maxCount).sorted { a, b in
let aIsDir = (try? a.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
let bIsDir = (try? b.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
if aIsDir != bIsDir { return aIsDir }
return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
}
return Array(sorted)
let sorted = contents.sorted { a, b in
let aIsDir = (try? a.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
let bIsDir = (try? b.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
if aIsDir != bIsDir { return aIsDir }
return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
}
return Array(sorted.prefix(maxCount))

Rule Used: Flag production code that adds nested full-collect... (source)

Comment thread Sources/ContentView.swift Outdated
Comment on lines +6212 to +6220
enumerator.skipDescendants()
continue
}
results.append(url)
}
return results
}

static func shouldSkipDirectoryForQuickOpen(_ name: String) -> Bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Blocking synchronous file I/O on the main actor

isTextFile(at:) opens a FileHandle and reads up to 4 KB synchronously. It is called from openFileInDefaultEditor, which — as a static func on a @MainActor View — runs on the main thread when the user activates a result. On a slow or network-mounted filesystem, this blocks the main actor and stalls the UI. The read should be performed asynchronously (e.g., via Task { await ... }) before the open call, consistent with the cmux rule against expensive synchronous disk loads on interactive paths.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

Comment thread Sources/ContentView.swift Outdated
Comment on lines +5557 to +5570
) {
let matches = CommandPaletteSearchOrchestrator.resolvedSearchMatches(
searchIndex: searchIndex,
searchCorpus: searchCorpus,
searchCorpusByID: searchCorpusByID,
query: matchingQuery,
query: nucleoQuery,
usageHistory: usageHistory,
queryIsEmpty: queryIsEmpty,
historyTimestamp: historyTimestamp,
additionalScoreBoost: additionalScoreBoost
additionalScoreBoost: additionalScoreBoost,
resultLimit: scope == .fileSearch ? 30 : nil
)
#if DEBUG
if scope == .fileSearch {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Dead #if DEBUG blocks with no-op loop bodies

There are two #if DEBUG blocks that iterate over search matches but do nothing with the captured value (let entry = searchCorpusByID[m.commandID] is unused, and the second block has an entirely empty if body). These appear to be unfinished debug tracing that was accidentally left in. They should either be completed with a proper cmuxDebugLog call or removed to avoid compiler warnings and reader confusion.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/CommandPaletteQuickOpenFileSearchTests.swift`:
- Around line 13-466: The test file CommandPaletteQuickOpenFileSearchTests.swift
(containing the QuickOpenFileSearchScopeTests XCTestCase and related tests like
testCommandsScopeWithPrefix, testFileSearchScopeWithPrefix, etc.) is incorrectly
added to the cmuxUITests target; move it into the cmuxTests unit-test target by
updating the Xcode project: add or correct the PBXFileReference for
CommandPaletteQuickOpenFileSearchTests.swift under the project, and ensure it is
listed in the PBXSourcesBuildPhase of the cmuxTests target (remove it from
cmuxUITests sources if present) so the tests compile and run as part of
cmuxTests.

In `@docs/quick-open-file.md`:
- Around line 241-265: The test count in the header is wrong: reconcile the
"单元测试(45 个)" summary with the actual listed tests in
QuickOpenFileSearchScopeTests by either adding the two missing entries in the
"Query 提取" subsection and the one missing entry in "Nucleo 搜索词提取" (include their
test names like the other bullets) or change the header count to match the
current total (e.g., 42); update the section title and any other summary counts
to keep them consistent with the enumerated test names (refer to test names such
as testCommandsScopeWithPrefix, testFileSearchScopeWithPrefix,
testFileSearchMatchingQueryEmpty, testMatchingTermEmpty to locate the related
lists).
- Around line 187-193: The dedup fingerprint currently uses
commandPaletteFileSearchDedupFingerprint(query:isCrossDirectory:) which for
cross-directory searches falls back to
commandPaletteFileSearchFingerprint(query:) and omits
resolvedFileSearchWorkspaceRoot (from
tabManager.selectedWorkspace?.currentDirectory), causing identical queries in
different workspaces to be deduplicated incorrectly; update
commandPaletteFileSearchDedupFingerprint to include the workspace root/current
directory (and the isCrossDirectory flag if not already) when computing
fileSearchDedupFingerprint so that searches from different windows/workspaces
produce distinct fingerprints and do not return early in the
BFS/searchCrossDirectory flow.

In `@Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction.swift`:
- Line 285: The new hard-coded user-facing label for the ShortcutAction enum
case quickOpenFile bypasses localization; replace the literal "Quick Open…" with
a localized string using String(localized: "shortcut.action.quickOpenFile",
defaultValue: "Quick Open…") (or similar key) so the UI uses the strings
catalog; update the return in the ShortcutAction .quickOpenFile branch and add
the corresponding key/value to the Localizable strings files.

In `@Resources/Localizable.xcstrings`:
- Around line 58560-58610: Add Japanese ("ja") localization blocks for each new
key so they match the structure used by "en" and "zh-Hans"; specifically add a
"ja" -> "stringUnit" -> {"state":"translated","value": "<Japanese translation>"}
entry for commandPalette.kind.directory, commandPalette.kind.file,
commandPalette.kind.openInFinder, commandPalette.search.fileSearchEmpty,
commandPalette.search.fileSearchPlaceholder, menu.file.quickOpenFile, and
shortcut.quickOpenFile.label (and repeat the same for the other ranges noted).
Use the English value as the source string and supply the correct Japanese
translation for the "value" field, preserving the exact JSON/xcstrings structure
and "state":"translated" flag.

In `@Sources/ContentView.swift`:
- Around line 5197-5224: In commandPaletteFileSearchMatchingTerm(_:), the
trimmed.hasPrefix("./") branch currently returns term unconditionally which
causes workspace-relative paths that resolve to directories (e.g. @./Sources) to
be treated as fuzzy search terms instead of browsing into that directory; change
the logic in the trimmed.hasPrefix("./") branch to mirror the ~ and
absolute-path handling: expand the ./ path against the workspace/current
directory (reuse the resolver’s remainder/current-dir result or use
FileManager.default.currentDirectoryPath + String(trimmed.dropFirst(2))), check
FileManager.default.fileExists(atPath:isDirectory:) and if it is a directory
return "" (so the UI will browse) otherwise return term. Ensure you reference
commandPaletteFileSearchMatchingTerm and the trimmed.hasPrefix("./") branch when
making the change.
- Around line 5877-6400: The ContentView.swift file contains the entire Quick
Open engine (filesystem traversal, scoring, path resolution, and file-type
dispatch) which should be extracted into a dedicated module file; move the pure
helper functions, types, and constants into a new Swift source (e.g.
QuickOpenEngine.swift) and leave only the palette state/wiring in ContentView.
Specifically, extract: ScoredFile, fastQuit* constants, searchCrossDirectory,
fileSearchCrossDirectoryFuzzyScore, fileSearchCrossDirectoryFuzzyMatch,
quickOpenRelativePath, insertTopK, shouldSkipDirectoryForQuickOpen,
listFilesRecursively, listFilesInDirectory, resolveSymlinkTarget, isSymlink,
isDirectory, isTextFile, openFileInDefaultEditor,
commandPaletteFileSearchPathForDirectory, commandPaletteFileSearchDotEntry,
resolveLongestExistingDirectory, commandPaletteFileSearchResolve,
commandPaletteFileSearchPathEntries, commandPaletteFileSearchFingerprint and
dedupFingerprint; make them internal/public as needed, add required imports
(Foundation/AppKit/UniformTypeIdentifiers), update ContentView to call into the
new file for these symbols, and run tests to ensure access levels and symbol
names remain identical to avoid breaking references.
- Around line 5503-5509: The early-return dedup uses
Self.fileSearchDedupFingerprint and never clears it when leaving cross-directory
path mode; update the logic around
Self.commandPaletteFileSearchDedupFingerprint(effectiveQuery:isCrossDirectory:)
so that if fsIsCrossDirectory is false (we left cross-directory mode) you clear
or reset Self.fileSearchDedupFingerprint before comparing/assigning the new fp
(or detect a mode change and nil the token), ensuring old cross-directory
fingerprints do not short-circuit subsequent non-cross-directory queries.
- Around line 5931-5938: The click handler flips to fuzzy cross-directory mode
when matchingQuery is empty because usePrefix is set to !matchingQuery.isEmpty;
to keep initial @ folder clicks in path mode, always enable the path prefix when
drilling into a directory. Update the action in ContentView so usePrefix is true
(or computed to be true when coming from the initial @ listing) before calling
Self.commandPaletteFileSearchPathForDirectory(url, rootDir: rootDir,
usePathPrefix: usePrefix) and assigning commandPaletteQuery using
Self.commandPaletteFileSearchPrefix + newPath; this preserves path-mode parsing
in commandPaletteFileSearchResolve.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b2e09f48-94da-4c57-a94f-deaf021ee6d4

📥 Commits

Reviewing files that changed from the base of the PR and between aeb8847 and 75feeb7.

📒 Files selected for processing (16)
  • Examples/SampleSidebarExtensionApp/SampleSidebarExtensionApp.xcodeproj/xcshareddata/xcschemes/CMUXExtKitSampleSidebarApp.xcscheme
  • Examples/SampleSidebarExtensionApp/SampleSidebarExtensionApp.xcodeproj/xcshareddata/xcschemes/SampleSidebarExtensionApp.xcscheme
  • Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction+Defaults.swift
  • Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction.swift
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/CommandPalette/CommandPaletteSearchOrchestrator.swift
  • Sources/ContentView.swift
  • Sources/KeyboardShortcutSettings.swift
  • Sources/TabManager.swift
  • Sources/cmuxApp.swift
  • cmux.xcodeproj/project.pbxproj
  • cmux.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
  • cmuxTests/CommandPaletteQuickOpenFileSearchTests.swift
  • docs/quick-open-file.md
  • ios/cmuxPackage/Package.resolved

Comment thread cmuxTests/CommandPaletteQuickOpenFileSearchTests.swift Outdated
Comment thread docs/quick-open-file.md Outdated
Comment thread docs/quick-open-file.md Outdated
Comment thread Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction.swift Outdated
Comment thread Resources/Localizable.xcstrings
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift
Comment thread Sources/ContentView.swift Outdated
Comment thread Sources/ContentView.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/quick-open-file.md (1)

80-103: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Label this pseudo-code fence.

This bare fenced block will keep markdownlint MD040 failing. Add an explicit language tag (for example, text) here; prose/table examples can stay as-is.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/quick-open-file.md` around lines 80 - 103, The fenced pseudo-code block
is unlabeled which triggers markdownlint MD040; update the opening fence for the
block that begins with the BFS pseudocode (the triple backticks before "调用任务:
BFS(root, depth=0) ...") to include an explicit language tag (for example change
``` to ```text) so the block is treated as plain text/pseudocode and MD040 is
satisfied.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/quick-open-file.md`:
- Line 258: Update the example in testFileSearchMatchingQueryEmpty to make the
trailing-space case explicit: instead of the ambiguous backtick span showing `@
`, spell the space visibly (e.g., `@␠` or `@·`) so the pair reads `@`, `@␠` →
`""`, which avoids the MD038 lint warning and clearly distinguishes empty vs
space.

In `@Sources/CommandPaletteQuickOpenFileSearch.swift`:
- Around line 343-350: The BFS can loop on symlinked directories; before
enqueueing a directory in the walk (the branch around isDir and queue.append),
detect and avoid symlink cycles by resolving and deduplicating real paths: when
isDir is true, compute a canonicalPath via url.resolvingSymlinksInPath().path
(or by reading .isSymbolicLinkKey and resolving) and maintain a Set<String>
visitedRealPaths; if canonicalPath is already in visitedRealPaths skip
enqueueing, otherwise insert canonicalPath and append the directory (or its
resolved URL) to queue; this prevents re-entering symlinked subtrees while
preserving existing checks like shouldSkipDirectoryForQuickOpen,
fileSearchCrossDirectoryFuzzyScore, insertTopK and use of localHeap.
- Around line 295-305: The current withTaskGroup launches one
searchCrossDirectoryBranch task per entry in topDirs, which can create unbounded
concurrent BFS scans; change this to a bounded worker pool or chunked submission
so at most N directory searches run concurrently (e.g., create a fixed number of
worker tasks that pull from topDirs or iterate topDirs in chunks) and have each
worker call searchCrossDirectoryBranch(query:rootDir:rootDirectory:threshold:),
forwarding results into the same aggregation; ensure cancellation still
propagates to the workers and choose a reasonable concurrency limit constant (or
configurable parameter) instead of spawning a task for every top-level
directory.
- Around line 9-39: The helper commandPaletteFileSearchMatchingTerm incorrectly
uses FileManager.default.currentDirectoryPath for "./" checks; change it to use
the workspace root used by commandPaletteFileSearchResolve so "./foo" is
resolved workspace-relative. Concretely: update
commandPaletteFileSearchMatchingTerm to accept (or obtain) the same
workspaceRoot string used by commandPaletteFileSearchResolve and replace
FileManager.default.currentDirectoryPath with that workspaceRoot when computing
expanded for the "./" branch; keep the rest of the directory-exists logic the
same so workspace paths like "./Sources" are classified correctly.

---

Outside diff comments:
In `@docs/quick-open-file.md`:
- Around line 80-103: The fenced pseudo-code block is unlabeled which triggers
markdownlint MD040; update the opening fence for the block that begins with the
BFS pseudocode (the triple backticks before "调用任务: BFS(root, depth=0) ...") to
include an explicit language tag (for example change ``` to ```text) so the
block is treated as plain text/pseudocode and MD040 is satisfied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 56eed6ea-86f4-4bff-84a8-8ee2a75ab9ea

📥 Commits

Reviewing files that changed from the base of the PR and between 75feeb7 and 1782f8d.

📒 Files selected for processing (7)
  • Packages/CmuxSettings/Sources/CmuxSettings/Values/ShortcutAction.swift
  • Resources/Localizable.xcstrings
  • Sources/CommandPaletteQuickOpenFileSearch.swift
  • Sources/ContentView.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/CommandPaletteQuickOpenFileSearchTests.swift
  • docs/quick-open-file.md

Comment thread docs/quick-open-file.md Outdated
Comment thread Sources/CommandPaletteQuickOpenFileSearch.swift Outdated
Comment thread Sources/CommandPaletteQuickOpenFileSearch.swift Outdated
Comment thread Sources/CommandPaletteQuickOpenFileSearch.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Sources/CommandPaletteQuickOpenFileSearch.swift (2)

249-255: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Precompute directory metadata before sorting.

The comparator fetches .isDirectoryKey on every comparison. On large folders this becomes many repeated filesystem lookups in an interactive path. Compute isDirectory once per entry, then sort the cached tuples.

🔧 Suggested fix
-        let sorted = contents.sorted { a, b in
-            let aIsDir = (try? a.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
-            let bIsDir = (try? b.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
-            if aIsDir != bIsDir { return aIsDir }
-            return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
-        }
-        return Array(sorted.prefix(maxCount))
+        let entries = contents.map { url in
+            (
+                url: url,
+                isDirectory: (try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false
+            )
+        }
+        let sorted = entries.sorted { a, b in
+            if a.isDirectory != b.isDirectory { return a.isDirectory }
+            return a.url.lastPathComponent.localizedCaseInsensitiveCompare(b.url.lastPathComponent) == .orderedAscending
+        }
+        return Array(sorted.prefix(maxCount).map(\.url))

As per coding guidelines, production code over scalable user data should avoid repeated sort/filter/map work in hot paths and other unbounded hot-path overhead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CommandPaletteQuickOpenFileSearch.swift` around lines 249 - 255, The
comparator in the sorting closure repeatedly performs filesystem lookups for
.isDirectoryKey (inside the sorted closure over contents), causing many
redundant I/O ops; fix by precomputing the directory flag once per entry (e.g.,
map contents to an array of tuples (url, isDirectory) using try?
url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false), perform
the sort on that cached tuple array (comparing the cached isDirectory and
lastPathComponent), then return the first maxCount URLs from the sorted tuples;
update the code around the sorted = contents.sorted { … } block to implement
this precompute-and-sort approach using the existing variable names (contents,
sorted, maxCount).

Source: Coding guidelines


158-197: ⚠️ Potential issue | 🟠 Major

Fix directory handling in quick-open + avoid repeated resourceValues during directory sort

  • quickOpenFileOpenAction(for:) evaluates script/binary heuristics before any directory check, so a directory named like Scripts.py can be misrouted to .textEditor instead of .open. Add an early isDirectory guard.

    🔧 Suggested fix
     nonisolated static func quickOpenFileOpenAction(for url: URL) async -> QuickOpenFileOpenAction {
         let resolvedURL = url.resolvingSymlinksInPath()
  •  if isDirectory(resolvedURL) {
    
  •      return .open(resolvedURL)
    
  •  }
     let utType = UTType(filenameExtension: resolvedURL.pathExtension)
     let resourceValues = try? resolvedURL.resourceValues(
         forKeys: [.isExecutableKey, .isSymbolicLinkKey]
     )
    
    </details>
    
  • listFilesInDirectory(_:) sorts with a comparator that calls resourceValues(forKeys: [.isDirectoryKey]) for every comparison; precompute isDirectory once per URL before sorting to avoid repeated metadata work in the sort comparator.
  • Add a regression test asserting that directories (including those with script-like extensions) produce .open.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CommandPaletteQuickOpenFileSearch.swift` around lines 158 - 197,
quickOpenFileOpenAction(for:) currently applies script/binary heuristics before
checking for directories, so files like "Scripts.py" that are actually
directories can be misclassified; update quickOpenFileOpenAction(for:) to
immediately resolve resourceValues(forKeys: [.isDirectoryKey]) and return
.open(resolvedURL) if isDirectory is true (do this before the
isScript/isKnownBinary checks), and avoid re-calling resourceValues later. In
listFilesInDirectory(_:) stop calling resourceValues(forKeys: [.isDirectoryKey])
inside the sort comparator; instead map the URL list to a temporary array of
(url, isDirectory: Bool) by fetching resourceValues once per URL, sort that
array using the precomputed isDirectory flags, then extract sorted URLs. Add a
regression test asserting directories (including names with script-like
extensions) are returned as .open by quickOpenFileOpenAction(for:).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@Sources/CommandPaletteQuickOpenFileSearch.swift`:
- Around line 249-255: The comparator in the sorting closure repeatedly performs
filesystem lookups for .isDirectoryKey (inside the sorted closure over
contents), causing many redundant I/O ops; fix by precomputing the directory
flag once per entry (e.g., map contents to an array of tuples (url, isDirectory)
using try? url.resourceValues(forKeys: [.isDirectoryKey]).isDirectory ?? false),
perform the sort on that cached tuple array (comparing the cached isDirectory
and lastPathComponent), then return the first maxCount URLs from the sorted
tuples; update the code around the sorted = contents.sorted { … } block to
implement this precompute-and-sort approach using the existing variable names
(contents, sorted, maxCount).
- Around line 158-197: quickOpenFileOpenAction(for:) currently applies
script/binary heuristics before checking for directories, so files like
"Scripts.py" that are actually directories can be misclassified; update
quickOpenFileOpenAction(for:) to immediately resolve resourceValues(forKeys:
[.isDirectoryKey]) and return .open(resolvedURL) if isDirectory is true (do this
before the isScript/isKnownBinary checks), and avoid re-calling resourceValues
later. In listFilesInDirectory(_:) stop calling resourceValues(forKeys:
[.isDirectoryKey]) inside the sort comparator; instead map the URL list to a
temporary array of (url, isDirectory: Bool) by fetching resourceValues once per
URL, sort that array using the precomputed isDirectory flags, then extract
sorted URLs. Add a regression test asserting directories (including names with
script-like extensions) are returned as .open by quickOpenFileOpenAction(for:).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: efef77bc-203f-472c-8d95-2779159e919f

📥 Commits

Reviewing files that changed from the base of the PR and between 1782f8d and d94f0f9.

📒 Files selected for processing (4)
  • Sources/CommandPaletteQuickOpenFileSearch.swift
  • Sources/ContentView.swift
  • cmuxTests/CommandPaletteQuickOpenFileSearchTests.swift
  • docs/quick-open-file.md

@tctony tctony force-pushed the feat_quick_open branch from d94f0f9 to 1e1b48f Compare June 14, 2026 08:40
Comment thread Sources/ContentView.swift
Comment on lines 5570 to +5620
return CommandPaletteSwitcherFingerprintContext.fingerprint(windowContexts: fingerprintContexts)
}

// MARK: - File Search (Quick Open)

private func commandPaletteFileSearchFingerprint() -> Int {
Self.commandPaletteFileSearchFingerprint(query: commandPaletteQuery)
}

/// Path mode: single-directory listing. Called directly from
/// refreshCommandPaletteSearchCorpus.
private func commandPaletteFileSearchPathEntries(
matchingQuery: String, currentDir: String
) -> [CommandPaletteCommand] {
guard let rootDir = resolvedFileSearchWorkspaceRoot, !rootDir.isEmpty else { return [] }
let resolvedDir = currentDir
let fileManager = FileManager.default
guard fileManager.fileExists(atPath: resolvedDir) else { return [] }

var entries: [CommandPaletteCommand] = []
if resolvedDir != rootDir {
entries.append(Self.commandPaletteFileSearchDotEntry(currentDir: resolvedDir))
}
let nucleoTerm = Self.commandPaletteFileSearchMatchingTerm(
matchingQuery,
workspaceRoot: rootDir
)
let fileURLs = Self.listFilesInDirectory(resolvedDir, maxCount: 1000)
.filter { nucleoTerm.isEmpty ? !$0.lastPathComponent.hasPrefix(".") : true }
for url in fileURLs {
let isDir = Self.isDirectory(url)
let name = url.lastPathComponent
entries.append(CommandPaletteCommand(
id: "file.quickopen.\(url.absoluteString.hashValue)",
rank: isDir ? 0 : 1,
title: name, subtitle: "",
shortcutHint: nil,
kindLabel: isDir
? String(localized: "commandPalette.kind.directory", defaultValue: "Directory")
: String(localized: "commandPalette.kind.file", defaultValue: "File"),
keywords: [],
dismissOnRun: !isDir,
action: {
if isDir {
let newPath = Self.commandPaletteFileSearchPathForDirectory(
url, rootDir: rootDir, usePathPrefix: true
)
self.commandPaletteQuery = Self.commandPaletteFileSearchPrefix + newPath
} else {
Self.openFileInDefaultEditor(url)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Synchronous directory listing on @MainActor on every keystroke

commandPaletteFileSearchPathEntries is a regular instance method on ContentView (implicitly @MainActor), and calls Self.listFilesInDirectory(resolvedDir, maxCount: 1000) synchronously. listFilesInDirectory issues a FileManager.contentsOfDirectory syscall followed by up to 1000 resourceValues calls and a sort — all on the main thread. This runs on every keystroke change in path mode. On a network-mounted volume or a slow disk, a single keystroke can stall the main run loop for hundreds of milliseconds.

Similarly, commandPaletteFileSearchResolve — called two-to-four times per keystroke — invokes resolveLongestExistingDirectory, which walks path components with synchronous fileExists(atPath:isDirectory:) calls on the main actor.

The path-listing work should be moved into the existing Task(priority: .userInitiated) search task and deliver results via a MainActor.run callback, consistent with how the cross-directory BFS path is already handled.

Rule Used: Flag new blocking or timing-based synchronization ... (source)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteQuickOpenFileSearchTests.swift (1)

331-334: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid process-global CWD mutation in tests (parallel flake risk).

Line 331-334 changes the process working directory globally. That can race with other tests and make failures nondeterministic. Use workspaceRoot: in matchingTerm instead so the test is self-contained.

Suggested fix
-        let previousDirectory = FileManager.default.currentDirectoryPath
-        XCTAssertTrue(FileManager.default.changeCurrentDirectoryPath(root.path))
-        defer { FileManager.default.changeCurrentDirectoryPath(previousDirectory) }
-
         let dotTestDirectory = root.appendingPathComponent(".test", isDirectory: true)
         let dotGitPath = root.appendingPathComponent(".git", isDirectory: true)
         try FileManager.default.createDirectory(at: dotTestDirectory, withIntermediateDirectories: true)
         try FileManager.default.createDirectory(at: dotGitPath, withIntermediateDirectories: true)

-        XCTAssertEqual(CommandPaletteQuickOpenFileSearch.matchingTerm("./.test"), "")
-        XCTAssertEqual(CommandPaletteQuickOpenFileSearch.matchingTerm("./.git"), "")
+        XCTAssertEqual(
+            CommandPaletteQuickOpenFileSearch.matchingTerm("./.test", workspaceRoot: root.path),
+            ""
+        )
+        XCTAssertEqual(
+            CommandPaletteQuickOpenFileSearch.matchingTerm("./.git", workspaceRoot: root.path),
+            ""
+        )

         try FileManager.default.removeItem(at: dotGitPath)
         try "gitdir: ../.git/worktrees/example\n".write(to: dotGitPath, atomically: true, encoding: .utf8)
-        XCTAssertEqual(CommandPaletteQuickOpenFileSearch.matchingTerm("./.git"), ".git")
+        XCTAssertEqual(
+            CommandPaletteQuickOpenFileSearch.matchingTerm("./.git", workspaceRoot: root.path),
+            ".git"
+        )

Also applies to: 340-341, 345-345

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteQuickOpenFileSearchTests.swift`
around lines 331 - 334, Remove the process-global current working directory
mutation (lines 331-334 and similar occurrences at lines 340-341 and 345) by
deleting the previousDirectory assignment, the changeCurrentDirectoryPath calls,
and the defer statement. Instead, pass the root path using the workspaceRoot
parameter when calling matchingTerm to keep the test self-contained and avoid
race conditions during parallel test execution.
Sources/CommandPaletteQuickOpenFileSearch.swift (1)

72-81: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep .textEditor fallback non-executable.

At Line 79, falling back to NSWorkspace.shared.open(url) can execute scripts/binaries depending on system associations. Use a non-executing fallback (e.g., reveal in Finder) to preserve Quick Open’s safe-open guarantees.

Suggested patch
         case .textEditor(let url):
             guard let editorURL = NSWorkspace.shared.urlForApplication(toOpen: UTType.plainText) else {
-                NSWorkspace.shared.open(url)
+                NSWorkspace.shared.activateFileViewerSelecting([url])
                 return
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/CommandPaletteQuickOpenFileSearch.swift` around lines 72 - 81, The
.textEditor case in the performQuickOpenFileOpenAction method uses
NSWorkspace.shared.open(url) as a fallback when no text editor URL is found,
which can execute scripts or binaries based on system associations, violating
the safe-open guarantee. Replace this fallback with
NSWorkspace.shared.activateFileViewerSelecting([url]) instead to reveal the file
in Finder, which is a non-executable operation that preserves the intended
safety behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenFileSearch.swift`:
- Around line 208-214: The sorted function in
CommandPaletteQuickOpenFileSearch.swift is calling isDirectory(a) and
isDirectory(b) inside the sort comparator closure, which results in O(n log n)
filesystem metadata reads for hot path Quick Open operations. Precompute the
directory flags for all entries in the contents collection before sorting by
creating a mapping or array of tuples containing each item and its precomputed
isDirectory result, then sort based on these cached flags instead of calling
isDirectory repeatedly during the comparison operations.

---

Outside diff comments:
In
`@Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteQuickOpenFileSearchTests.swift`:
- Around line 331-334: Remove the process-global current working directory
mutation (lines 331-334 and similar occurrences at lines 340-341 and 345) by
deleting the previousDirectory assignment, the changeCurrentDirectoryPath calls,
and the defer statement. Instead, pass the root path using the workspaceRoot
parameter when calling matchingTerm to keep the test self-contained and avoid
race conditions during parallel test execution.

In `@Sources/CommandPaletteQuickOpenFileSearch.swift`:
- Around line 72-81: The .textEditor case in the performQuickOpenFileOpenAction
method uses NSWorkspace.shared.open(url) as a fallback when no text editor URL
is found, which can execute scripts or binaries based on system associations,
violating the safe-open guarantee. Replace this fallback with
NSWorkspace.shared.activateFileViewerSelecting([url]) instead to reveal the file
in Finder, which is a non-executable operation that preserves the intended
safety behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 96e97608-30e9-4ffb-b2fc-f563c2f45767

📥 Commits

Reviewing files that changed from the base of the PR and between 1e1b48f and f9f249f.

📒 Files selected for processing (6)
  • Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenFileOpenAction.swift
  • Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenFileSearch.swift
  • Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenScoredFile.swift
  • Packages/CmuxCommandPalette/Tests/CmuxCommandPaletteTests/CommandPaletteQuickOpenFileSearchTests.swift
  • Sources/CommandPaletteQuickOpenFileSearch.swift
  • cmux.xcodeproj/project.pbxproj
💤 Files with no reviewable changes (1)
  • cmux.xcodeproj/project.pbxproj

Comment on lines +208 to +214
let sorted = contents.sorted { a, b in
let aIsDir = isDirectory(a)
let bIsDir = isDirectory(b)
if aIsDir != bIsDir { return aIsDir }
return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
}
return Array(sorted.prefix(maxCount))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Precompute directory metadata before sorting.

At Line 208, isDirectory is called inside the sort comparator, causing repeated filesystem metadata reads (O(n log n) probes) on a hot Quick Open path. Compute directory flags once per entry, then sort on cached flags.

Suggested patch
-        let sorted = contents.sorted { a, b in
-            let aIsDir = isDirectory(a)
-            let bIsDir = isDirectory(b)
-            if aIsDir != bIsDir { return aIsDir }
-            return a.lastPathComponent.localizedCaseInsensitiveCompare(b.lastPathComponent) == .orderedAscending
-        }
-        return Array(sorted.prefix(maxCount))
+        let entries = contents.map { ($0, isDirectory($0)) }
+        let sorted = entries.sorted { a, b in
+            if a.1 != b.1 { return a.1 }
+            return a.0.lastPathComponent.localizedCaseInsensitiveCompare(b.0.lastPathComponent) == .orderedAscending
+        }
+        return Array(sorted.prefix(maxCount).map(\.0))

As per coding guidelines, production code over scalable user data should avoid repeated sort/filter/map work in hot paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/CmuxCommandPalette/Sources/CmuxCommandPalette/QuickOpen/CommandPaletteQuickOpenFileSearch.swift`
around lines 208 - 214, The sorted function in
CommandPaletteQuickOpenFileSearch.swift is calling isDirectory(a) and
isDirectory(b) inside the sort comparator closure, which results in O(n log n)
filesystem metadata reads for hot path Quick Open operations. Precompute the
directory flags for all entries in the contents collection before sorting by
creating a mapping or array of tuples containing each item and its precomputed
isDirectory result, then sort based on these cached flags instead of calling
isDirectory repeatedly during the comparison operations.

Source: Coding guidelines

@tctony tctony force-pushed the feat_quick_open branch from 78d5405 to 913403a Compare June 14, 2026 09:11
@tctony tctony force-pushed the feat_quick_open branch from 913403a to 2799e3d Compare June 14, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant