Skip to content

refactor: Refactors the VS Code file completion system to use fuzzy search#2437

Merged
qqqys merged 6 commits intoQwenLM:mainfrom
qqqys:feat/vsocde_file_search
Mar 18, 2026
Merged

refactor: Refactors the VS Code file completion system to use fuzzy search#2437
qqqys merged 6 commits intoQwenLM:mainfrom
qqqys:feat/vsocde_file_search

Conversation

@qqqys
Copy link
Collaborator

@qqqys qqqys commented Mar 17, 2026

TLDR

Refactors the VS Code file completion system to use backend fuzzy search instead of client-side substring matching, providing better search results and improved performance for large workspaces.

file

Dive Deeper

This PR addresses several issues with the file completion feature in the VS Code IDE companion:

1. Backend Fuzzy Search Integration

  • Replaces simple client-side includes() filtering with the existing FileSearchFactory from @qwen-code/qwen-code-core
  • Enables fuzzy matching that can find files even with partial or misspelled queries
  • Leverages existing caching mechanisms with 30-second TTL for performance

2. File Watcher Implementation

  • Adds file system watchers to detect changes in the workspace
  • Automatically invalidates file search cache when files are created, modified, or deleted
  • Handles workspace folder additions and removals

3. Trigger Character Logic Fixes

  • Fixes priority between @ and / completion triggers
  • Allows path-like queries (e.g., src/components/Button) within @ mentions
  • Correctly handles slash trigger positioning only when in slash command mode

4. Improved State Management

  • Resets last query reference when a file is selected, allowing fresh searches after backspacing
  • Better handling of loading states for file completion

Reviewer Test Plan

  1. Open a project in VS Code with the companion extension
  2. Type @ in the chat input and verify file completions appear
  3. Try fuzzy queries like src/comp/but to match src/components/Button.tsx
  4. Create/delete/rename a file and verify completions update accordingly
  5. Verify / slash commands still work correctly
  6. Test with paths containing slashes in @ mentions

Testing Matrix

🍏 🪟 🐧
npm run
npx
Docker
Podman - -
Seatbelt - -

Linked issues / bugs

Resolves #2325

…dling

- Removed client-side filtering for search queries; fuzzy search is now handled by the backend.
- Enhanced file search initialization and caching mechanisms in FileMessageHandler.
- Added file watchers for cache invalidation on file system changes.
- Updated completion trigger logic to prioritize '@' over '/' for path-like queries.
- Reset last query on file selection to ensure fresh search results.

This refactor improves search efficiency and maintains accurate file references in the application.
@github-actions
Copy link
Contributor

📋 Review Summary

This PR refactors the VS Code file completion system to use backend fuzzy search (FileSearchFactory) instead of client-side substring matching, adds file watchers for cache invalidation, and fixes trigger character logic for @ and / completions. The implementation is well-structured and leverages existing core utilities effectively, though there are some areas that could benefit from additional attention.

🔍 General Feedback

  • Positive: The integration of FileSearchFactory from @qwen-code/qwen-code-core is a smart reuse of existing infrastructure, avoiding duplicate search logic
  • Positive: File watcher implementation with cache invalidation is well-designed and handles workspace folder changes
  • Positive: The fix for trigger character priority (@ vs /) addresses a real UX issue with path-like queries
  • Pattern: Good use of Maps for caching (fileSearchInstances, fileSearchInitializing) with proper cleanup
  • Concern: The diff shows the old code in FileMessageHandler.ts but the new code appears to be incomplete in the diff output (file cuts off mid-function)
  • Pattern: Consistent error handling pattern using getErrorMessage() utility

🎯 Specific Feedback

🔴 Critical

  • File: FileMessageHandler.ts:437 - The handleGetWorkspaceFiles method still contains the OLD implementation using vscode.workspace.findFiles() with glob patterns. The diff shows the new code was supposed to replace lines 282+ but the old code (lines 282-303) remains unchanged in the actual file. This appears to be an incomplete PR where the new fuzzy search implementation wasn't fully applied.

  • File: App.tsx:137-146 - The client-side filtering code was removed but the condition for showing the loading placeholder changed from allItems.length === 0 to allItems.length === 0 && query && query.length >= 1. This could cause the loading state to not show when query is empty but files haven't loaded yet, potentially showing an empty completion list instead of "Searching files…"

🟡 High

  • File: FileMessageHandler.ts:68-162 - The setupFileWatchers() method creates file watchers but there's no corresponding cleanup/disposal mechanism when the FileMessageHandler is disposed. The watchers are stored in fileWatchers array but the dispose() method is only called on the returned disposable from WebViewProvider.ts:93, which may not cover all lifecycle scenarios.

  • File: FileMessageHandler.ts:32-34 - The fileSearchInitializing Map stores promises that could potentially reject. While there's error handling in getOrCreateFileSearch(), if a promise rejects, subsequent calls will retry initialization indefinitely. Consider adding a retry limit or cooldown period.

  • File: useCompletionTrigger.ts:305-314 - The trigger character logic change from > to >= comparison (lastAtMatch >= 0 and lastSlashMatch >= 0) could cause issues when both are -1 (not found). The condition lastAtMatch >= 0 will be false for -1, but the logic flow should be verified to ensure it doesn't incorrectly prioritize one trigger over the other in edge cases.

🟢 Medium

  • File: FileMessageHandler.ts:100-107 - The cache invalidation logs every time a file changes. For large workspaces with frequent file changes (e.g., build outputs, git operations), this could spam the console. Consider debouncing cache invalidation or using a less verbose log level.

  • File: App.tsx:671-676 - The comment "Only consider slash as trigger if we're in slash command mode" is helpful, but the logic could be clearer. Consider extracting this into a named helper function like getSlashTriggerPosition() for better readability.

  • File: useFileContext.ts:123-127 - The addFileReference function now resets lastQueryRef.current, but there's no comment explaining why this is necessary. Adding a brief comment would help future maintainers understand the backspace-and-retype scenario.

🔵 Low

  • File: FileMessageHandler.ts:30 - The import from @qwen-code/qwen-code-core/src/utils/filesearch/fileSearch.js uses a direct src path. Consider using the package's public export if available (e.g., @qwen-code/qwen-code-core/utils/filesearch) to avoid potential issues with internal path changes.

  • File: MessageRouter.ts:4-75 - The fileHandler is now stored as an instance property (this.fileHandler) to expose setupFileWatchers(), but this breaks the pattern of other handlers which remain local variables. For consistency, consider either storing all handlers as properties or finding an alternative way to expose the file watchers setup.

  • File: App.tsx:137 - The comment "Fuzzy search is handled by the backend" should mention which backend component (FileSearchFactory) for better discoverability.

✅ Highlights

  • Excellent: The file watcher implementation properly handles workspace folder additions and removals with the onDidChangeWorkspaceFolders listener
  • Well-done: The getOrCreateFileSearch() pattern with promise deduplication prevents race conditions when multiple searches are requested simultaneously
  • Good UX improvement: Resetting lastQueryRef on file selection enables the intuitive behavior of backspacing and re-typing to get fresh search results
  • Solid fix: The trigger character priority fix allows natural path-like queries (e.g., src/components/Button) within @ mentions while preserving / slash command functionality

// Priority: @ trigger takes precedence over / trigger
// This allows path-like queries (e.g., "src/components/Button") in @ mentions
// But skip if the trigger is inside a file tag
if (lastAtMatch >= 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nice improvement here for supporting path-like queries inside @ mentions. One thing I think may still regress, though: this now gives @ priority whenever an @ appears anywhere before the cursor, which can swallow a later / trigger on the same line.

For example, with input like @src/foo.ts /fix, once the cursor is after /fix, this still picks the earlier @ and builds the query from there, so slash-command completion never opens.

Would it make sense to prefer the nearest valid trigger before the cursor, rather than always preferring @ globally? That should preserve the path-query behavior while still allowing / completion to work after a file mention.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. Normally @ and / shouldn't interfere with each other's triggers. A good expected behavior would be:

Input Cursor position Result
@src/foo.ts /fix After /fix / triggers (closer to cursor)
@src/components After components @ triggers
/explain @file.ts After file.ts @ triggers (closer to cursor)
@src/foo/bar After bar @ triggers (inner / not at word boundary)

const rootPath = folder.uri.fsPath;
this.invalidateFileSearchCache(rootPath);
}
for (const folder of e.added) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Really like the direction here with cache invalidation on workspace changes. I think there is one lifecycle gap to watch out for: when a new workspace folder is added, we invalidate its cache entry, but we do not register a new file watcher for that folder.

That means searches in the newly added root can build a FileSearch index once, but later file create/change/delete events in that root will not invalidate it, so results can become stale until the provider is recreated.

It may be worth creating and tracking a watcher for each newly added folder inside onDidChangeWorkspaceFolders, so the invalidation behavior stays consistent for roots added after initialization too.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You're right. I should create a watcher for each folder. The ideal behavior would be:

Event Before After
Init with folders A, B Watchers for A, B Watchers for A, B
Add folder C Cache invalidated, no watcher Cache invalidated, watcher created for C
Remove folder B Cache invalidated, watcher leaks Cache invalidated, watcher disposed
Dispose all All disposed All disposed

Copy link
Collaborator

@tanzhenxin tanzhenxin left a comment

Choose a reason for hiding this comment

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

LGTM!

@qqqys qqqys merged commit 27f4a3e into QwenLM:main Mar 18, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Enhanced @ Mention in VS Code Extension

3 participants