Skip to content

[issues/356] File Picker — Secondary "More Files..." Picker#370

Open
couimet wants to merge 16 commits intomainfrom
issues/356
Open

[issues/356] File Picker — Secondary "More Files..." Picker#370
couimet wants to merge 16 commits intomainfrom
issues/356

Conversation

@couimet
Copy link
Owner

@couimet couimet commented Feb 25, 2026

Summary

Completes the file picker flow (#245) by wiring the "More files..." overflow item to a secondary QuickPick that shows all open files organized into sections. When users have more open files than fit inline in the main destination picker, they can now access them all without losing context — escaping the secondary picker returns them to the main one.

Changes

  • Add FilePickerHandlers<T> type, parallel to TerminalPickerHandlers<T>, defining onSelected, onDismissed, and getPlaceholder callbacks
  • Add getAllFileItems() to DestinationAvailabilityService — runs the full file pipeline (eligible → bound-mark → sort → disambiguate → build) for ALL open files, not just the current-in-group subset used by inline items
  • Fix markBoundFile to match on URI + viewColumn together, preventing false "bound" badges when the same file is open in multiple tab groups
  • Add buildFilePickerItems() — groups FileBindableQuickPickItem[] into an "Active Files" section (current-in-group files) plus per-"Tab Group N" sections, using vscode.QuickPickItemKind.Separator between sections
  • Add showFilePicker<T>() presenter — validates items, builds sectioned list, shows QuickPick with FILE_PICKER_TITLE and delegated placeholder, routes selection/dismiss to FilePickerHandlers
  • Wire file-more case in DestinationPicker — replace stub with showSecondaryFilePicker(), which calls getAllFileItems() then showFilePicker(); dismiss returns { outcome: 'returned-to-main-picker' } to loop back to main picker
  • Thread boundFileUriString and boundFileViewColumn through DestinationPickerOptions to support correct bound-state marking when the same URI is open in multiple columns
  • Document feature in CHANGELOG and README

Test Plan

  • All existing tests pass (1490 tests, 76 suites)
  • New tests added for:
    • markBoundFile — URI-only match (backward compat), URI+viewColumn match, same URI different column stays unbound
    • DestinationAvailabilityService.getAllFileItems() — all files returned, bound state enriched, disambiguators applied, same-URI/different-column scenarios
    • buildFilePickerItems() — single group, multi-group, all-active, bound badge, disambiguated descriptions, empty active section
    • showFilePicker() — empty validation, selection, dismiss with/without handler, async handlers, separator/non-file item guard, placeholder delegation
    • DestinationPicker — file-more selection returns correct bindOptions, dismiss returns to main picker loop, boundFileUriString/boundFileViewColumn passed through to getAllFileItems
  • Formatter: no changes

Related

Summary by CodeRabbit

  • New Features

    • File Picker to choose open files as destinations with improved organization.
    • Active file per tab group shown inline and prioritized; bound files get a "bound" badge and are sorted to the top.
    • Duplicate names disambiguated by path.
    • Overflow handled via a "More files..." secondary picker with "Active Files" and per-tab-group sections; Esc returns to the main picker.
  • Documentation

    • Updated README guidance describing File Picker behavior and flows.

…e method

Lays the foundation for the secondary file picker. FilePickerHandlers<T> is the handler interface parallel to TerminalPickerHandlers<T> — same shape, typed for EligibleFile. getAllFileItems() runs the full pipeline (getEligibleFiles → markBoundFile → sortEligibleFiles → disambiguateFilenames → buildFileItem) for ALL files regardless of isCurrentInGroup, where getFileItems() only returns the current-in-group subset used by the main picker.

The distinction matters: the main picker shows one file per tab group + "More files..." overflow; the secondary picker will show everything organized into sections.

Benefits:
- FilePickerHandlers<T> gives the secondary picker the same handler pattern as the terminal picker
- getAllFileItems() gives DestinationPicker access to all files it needs to populate the secondary picker
- 4 new tests covering empty state, non-current-in-group inclusion, bound state enrichment, and disambiguator application
The same file can be open in multiple tab groups simultaneously, each with a
different viewColumn. Matching on URI alone would mark every copy as bound,
but only the copy in the specific editor the user bound to should show the
bound badge.

Benefits:
- Bound badge appears only on the correct editor instance when a file is open in multiple tab groups
- getGroupedDestinationItems, getFileItems, and getAllFileItems all thread viewColumn precisely
- Backward-compatible: callers without a viewColumn fall back to URI-only matching
…tions

The secondary file picker needs to show all open files organized into sections,
not a flat list. Active files (one per tab group, the visible tab) go first under
"Active Files", and the remaining overflow files are grouped by their tab group
under "Tab Group N" separators — matching how VSCode itself labels tab groups.

Benefits:
- Secondary picker presents files in a structured, scannable layout
- Section separators omitted for empty sections (e.g., no per-group section when all files are active)
- Per-group sections sort by viewColumn for deterministic ordering
- Passes pre-built labels and descriptions through unchanged (disambiguation and bound badges survive the transform)
The secondary file picker needs a presenter function that validates inputs,
builds sectioned items, and delegates outcomes to caller-provided handlers —
the same pattern already established by showTerminalPicker for terminals.

Benefits:
- Callers own only what varies: onSelected, onDismissed, and placeholder text
- Type guard on fileInfo ensures separators returned by QuickPick are handled safely
- FILE_PICKER_EMPTY_ITEMS error on empty input matches terminal picker contract
…cker

The file-more item was a stub returning cancelled — clicking "More files..." did
nothing useful. Now it delegates to showSecondaryFilePicker(), mirroring the
showSecondaryTerminalPicker() pattern already proven for terminals.

Benefits:
- Selecting "More files..." shows all open files organized into sections
- Dismissing the secondary picker returns to the main destination picker (loop-back)
- boundFileUriString and boundFileViewColumn thread through to getAllFileItems for
  correct bound-state badges when the same URI is open in multiple tab groups
Users need to know the file picker exists and how it works — without changelog
and README coverage, the feature ships invisible. This closes out #356 documentation.

Benefits:
- CHANGELOG gives release notes the full multi-bullet detail the feature warrants
- README helps users discover the inline items, "More files..." overflow, and bound-file badge
…M menu

The destinations section (files, terminals, AI assistants) ran directly into the "Go to Link" command item with no visual boundary, making the menu harder to scan. A blank separator provides a clear break between "bind a destination" items and "navigate/utility" items.

Benefits:
- Clear visual boundary between the destinations block and command items in all menu states (bound, unbound, no destinations)
- Consistent with how the bookmarks section already uses blank separators for its own boundaries
…r menu

Selecting "More files..." silently dismissed the R-M menu because the handler was a TODO stub. This wires it up to the same showFilePicker infrastructure used by DestinationPicker, so the overflow flow works consistently in both contexts.

Benefits:
- "More files..." now opens the secondary file picker instead of silently closing the menu
- Selecting a file from the overflow picker binds it directly
- Dismissing the picker re-opens the R-M menu, matching the terminal overflow behavior
@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between f10eb62 and a0f5437.

📒 Files selected for processing (25)
  • packages/rangelink-vscode-extension/README.md
  • packages/rangelink-vscode-extension/src/RangeLinkService.ts
  • packages/rangelink-vscode-extension/src/__tests__/RangeLinkService.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/commands/BindToDestinationCommand.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/commands/BindToTextEditorCommand.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationPicker.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/PasteDestinationManager.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/buildDestinationQuickPickItems.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/buildFilePickerItems.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/showFilePicker.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/extension.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/createMockBindableQuickPickItem.ts
  • packages/rangelink-vscode-extension/src/__tests__/statusBar/RangeLinkStatusBar.test.ts
  • packages/rangelink-vscode-extension/src/commands/BindToDestinationCommand.ts
  • packages/rangelink-vscode-extension/src/commands/BindToTextEditorCommand.ts
  • packages/rangelink-vscode-extension/src/destinations/DestinationAvailabilityService.ts
  • packages/rangelink-vscode-extension/src/destinations/DestinationPicker.ts
  • packages/rangelink-vscode-extension/src/destinations/PasteDestinationManager.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/buildDestinationQuickPickItems.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/showFilePicker.ts
  • packages/rangelink-vscode-extension/src/i18n/messages.en.ts
  • packages/rangelink-vscode-extension/src/statusBar/RangeLinkStatusBar.ts
  • packages/rangelink-vscode-extension/src/types/MessageCode.ts
  • packages/rangelink-vscode-extension/src/types/QuickPickBindResult.ts
 _____________________________________________________________
< What, what, what, what, what, what, what, what, what, what? >
 -------------------------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ

✏️ Tip: You can disable in-progress messages and the fortune message in your review settings.

Walkthrough

This pull request implements the secondary file picker feature for the RangeLink VS Code extension, allowing users to select specific open files as text editor destinations from a "More files..." overflow menu, organized with active files section and per-tab-group sections.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md
Added feature description explaining File Picker UX behavior: open file appearance, active file display per tab group, "More files..." secondary picker organization, bound file labeling, and escape-to-return-to-destination navigation.
File Picker Core
src/destinations/utils/showFilePicker.ts, src/destinations/utils/buildFilePickerItems.ts, src/destinations/types/FilePickerHandlers.ts
New file picker presentation layer with showFilePicker<T>() function accepting items and handlers, buildFilePickerItems() organizing items into "Active Files" and per-tab-group sections with separators, and FilePickerHandlers<T> interface defining onSelected, onDismissed, and getPlaceholder callbacks.
Service Updates
src/destinations/DestinationAvailabilityService.ts, src/destinations/utils/markBoundFile.ts
Added getAllFileItems() method for full-pipeline file item generation; extended getFileItems() and markBoundFile() to accept boundFileViewColumn for precise view-column matching of bound files alongside URI matching.
Picker Integration
src/destinations/DestinationPicker.ts, src/statusBar/RangeLinkStatusBar.ts
Wired secondary file picker flow: added boundFileUriString and boundFileViewColumn to picker options, created showSecondaryFilePicker() method to handle "More files..." selection with bind-on-select and return-to-main-on-dismiss behavior; status bar now invokes file picker with appropriate handlers and error messaging.
Type System
src/destinations/types/index.ts, src/types/GroupedDestinationKinds.ts
Exported new FilePickerHandlers type; added boundFileViewColumn option to GetAvailableDestinationItemsOptions for view-column-aware grouping.
Utils Index
src/destinations/utils/index.ts
Exported buildFilePickerItems and showFilePicker functions for public use.
Test Infrastructure
src/__tests__/helpers/spyOnShowFilePicker.ts, src/__tests__/helpers/createMockBindableQuickPickItem.ts, src/__tests__/helpers/createMockDestinationAvailabilityService.ts, src/__tests__/helpers/index.ts
Added spy factory for showFilePicker, mock factory for FileMoreQuickPickItem, and extended availability service mock with getAllFileItems method.
Comprehensive Test Coverage
src/__tests__/destinations/utils/showFilePicker.test.ts, src/__tests__/destinations/utils/buildFilePickerItems.test.ts, src/__tests__/destinations/utils/markBoundFile.test.ts, src/__tests__/destinations/DestinationAvailabilityService.test.ts, src/__tests__/destinations/DestinationPicker.test.ts, src/__tests__/statusBar/RangeLinkStatusBar.test.ts
New and extended test suites validating file picker presentation, item grouping by activity and tab-group, bound-file URI+viewColumn matching, secondary picker selection/dismissal flows, and integration with destination picker and status bar.

Sequence Diagram

sequenceDiagram
    actor User
    participant DestinationPicker
    participant showSecondaryFilePicker
    participant showFilePicker
    participant QuickPickProvider
    participant Handlers as FilePickerHandlers

    User->>DestinationPicker: Select "More files..." item
    DestinationPicker->>showSecondaryFilePicker: Invoke with bound file context
    showSecondaryFilePicker->>DestinationAvailabilityService: getAllFileItems(boundURI, boundViewColumn)
    DestinationAvailabilityService-->>showSecondaryFilePicker: FileBindableQuickPickItem[]
    showSecondaryFilePicker->>showFilePicker: Present items with handlers
    showFilePicker->>showFilePicker: buildFilePickerItems() → organize by section
    showFilePicker->>QuickPickProvider: Render with "Active Files" + per-group sections
    alt User selects file
        User->>QuickPickProvider: Choose eligible file
        QuickPickProvider-->>showFilePicker: Selected item
        showFilePicker->>Handlers: onSelected(file)
        Handlers-->>showFilePicker: T | Promise<T>
        showFilePicker-->>showSecondaryFilePicker: Result
        showSecondaryFilePicker->>DestinationService: Bind selected file as destination
    else User dismisses
        User->>QuickPickProvider: Cancel
        QuickPickProvider-->>showFilePicker: Dismissed
        showFilePicker->>Handlers: onDismissed()?
        Handlers-->>showFilePicker: Result | undefined
        showFilePicker-->>showSecondaryFilePicker: undefined
        showSecondaryFilePicker->>DestinationPicker: Return to main picker
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly Related Issues

  • File Picker — Secondary "More Files..." Picker #356 — File Picker — Secondary "More Files..." Picker — This PR fully implements the secondary file picker feature with all specified deliverables: showFilePicker(), buildFilePickerItems(), FilePickerHandlers<T> type, file-more integration in DestinationPicker, and documentation updates.

  • Text Editor Binding — File Picker for Destinations #245 — Text Editor Binding — File Picker for Destinations — Parent epic that this PR contributes to by implementing the secondary picker component for file destination selection.

Possibly Related PRs

Poem

🐰 A secondary picker springs to life,
Organizing files through section and strife—
"Active" at the top, tab groups below,
Bound badges gleam with a radiant glow!
More files no longer lost in the dark,
Your editor binding's now leaving its mark! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: implementing the secondary file picker for the 'More Files...' overflow feature, directly matching the issue #356.
Linked Issues check ✅ Passed All deliverables from issue #356 are implemented: showFilePicker() presenter, buildFilePickerItems() utility, FilePickerHandlers type, file-more case in DestinationPicker with secondary picker flow, proper bound-file matching with viewColumn, documentation updates, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #356 objectives. No unrelated modifications detected; all additions support the secondary file picker feature, its integration, and testing.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issues/356

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.

@couimet
Copy link
Owner Author

couimet commented Feb 25, 2026

@coderabbitai review

I hit rate limit on #370 (comment)

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

@couimet: I'll review the changes now. Sorry about the rate limit issue on the previous comment — I'll provide a fresh review of the PR.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
packages/rangelink-vscode-extension/README.md (1)

136-136: Consider adding <sup>Unreleased</sup> to the "File Picker" paragraph.

The File Picker is an unreleased feature documented within the released "Text Editor" section. While the coding guidelines require <sup>Unreleased</sup> markers on new section headers and new table rows (and the [!IMPORTANT] banner already exists), this inline paragraph sits in a grey area. The analogous "Terminal Picker:" paragraph (Line 107) is a released feature, so they are currently visually equivalent but semantically different. Adding <sup>Unreleased</sup> on the bold lead would make the unreleased status visible to readers scanning the section.

📝 Suggested change
-**File Picker:** In the destination picker and RangeLink Menu, open text editor files appear as individual items.
+**File Picker:** <sup>Unreleased</sup> In the destination picker and RangeLink Menu, open text editor files appear as individual items.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rangelink-vscode-extension/README.md` at line 136, The "File Picker"
inline paragraph is an unreleased feature but lacks the required visual marker;
update the bold lead "**File Picker:**" in the README.md so it reads "**File
Picker: <sup>Unreleased</sup>**" (matching the convention used for new
sections/rows) to clearly indicate unreleased status, keeping the rest of the
paragraph text unchanged and consistent with the existing "[!IMPORTANT]" banner
and the released "Terminal Picker" entry.
packages/rangelink-vscode-extension/src/destinations/utils/showFilePicker.ts (1)

59-62: Separator guard is safely defensive but practically unreachable.

VS Code's QuickPick API does not return separator items as a selection result, so the !('fileInfo' in selected) branch (Line 59) will never execute in production. It's harmless as a safety net, but worth noting it cannot be exercised by tests either.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rangelink-vscode-extension/src/destinations/utils/showFilePicker.ts`
around lines 59 - 62, The guard in showFilePicker that checks if (!('fileInfo'
in selected)) is unreachable because VS Code QuickPick never returns separator
items as selections; remove this dead branch (the logger.debug and return
undefined) and rely on the existing type narrowing/path that handles real
selections for the variable selected (and its fileInfo property), or if you
prefer to keep a defensive note, replace the branch with a single-line comment
explaining why the check is intentionally omitted rather than leaving
unreachable code in the function.
packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts (1)

1035-1057: Property-level assertions may hide regressions in the full item shape.

Lines 1055–1056 only assert the description property on each item rather than the full FileBindableQuickPickItem structure. If other properties change unexpectedly (e.g., bindOptions, boundState), the test will not catch it.

♻️ Proposed change — assert full item shape
-      expect(result[0].description).toBe('…/a');
-      expect(result[1].description).toBe('…/b');
+      expect(result).toStrictEqual([
+        expect.objectContaining({ description: '…/a' }),
+        expect.objectContaining({ description: '…/b' }),
+      ]);

Actually, per the guideline of no partial matchers, prefer the full exact structure:

-      expect(result[0].description).toBe('…/a');
-      expect(result[1].description).toBe('…/b');
+      expect(result[0]).toStrictEqual({
+        label: 'util.ts',
+        displayName: 'util.ts',
+        description: '…/a',
+        bindOptions: { kind: 'text-editor', uri: uri1, viewColumn: 1 },
+        itemKind: 'bindable',
+        fileInfo: {
+          uri: uri1,
+          filename: 'util.ts',
+          displayPath: 'src/a/util.ts',
+          viewColumn: 1,
+          isCurrentInGroup: true,
+          isActiveEditor: false,
+          boundState: 'not-bound',
+        },
+        boundState: 'not-bound',
+      });
+      expect(result[1]).toStrictEqual({
+        label: 'util.ts',
+        displayName: 'util.ts',
+        description: '…/b',
+        bindOptions: { kind: 'text-editor', uri: uri2, viewColumn: 1 },
+        itemKind: 'bindable',
+        fileInfo: {
+          uri: uri2,
+          filename: 'util.ts',
+          displayPath: 'src/b/util.ts',
+          viewColumn: 1,
+          isCurrentInGroup: false,
+          isActiveEditor: false,
+          boundState: 'not-bound',
+        },
+        boundState: 'not-bound',
+      });

As per coding guidelines: "Never use partial matchers like expect.objectContaining() or expect.stringContaining() - assert exact values instead."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts`
around lines 1035 - 1057, The test in DestinationAvailabilityService.test.ts is
only asserting the description property of items returned by
service.getAllFileItems(), which can miss regressions in the full
FileBindableQuickPickItem shape (e.g., bindOptions, boundState); update the
assertions to compare the entire item objects exactly for result[0] and
result[1] (not just description) by constructing the expected full
FileBindableQuickPickItem values using the same fixtures (uris from
createMockUri('/workspace/src/a/util.ts') and
createMockUri('/workspace/src/b/util.ts'), tabs from createMockTab, and group
via createMockTabGroup) and assert equality (strict/deep equals) against
result[0] and result[1] so the test fails if any property changes.
packages/rangelink-vscode-extension/src/__tests__/statusBar/RangeLinkStatusBar.test.ts (1)

694-706: Optional: extract repeated FileBindableQuickPickItem fixture creation.

The same object shape is duplicated across three tests; a small helper would reduce noise and simplify future updates.

Also applies to: 754-766, 819-831

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/rangelink-vscode-extension/src/__tests__/statusBar/RangeLinkStatusBar.test.ts`
around lines 694 - 706, Tests repeat construction of a FileBindableQuickPickItem
fixture (object using FileBindableQuickPickItem, eligibleFile, bindOptions,
itemKind, fileInfo, boundState) across multiple places; extract a small factory
helper like createFileBindableQuickPickItem(eligibleFile) and replace the inline
objects in the tests (including the occurrences around FileBindableQuickPickItem
at lines referenced) to return the same shaped object so the tests reuse the
fixture and reduce duplication.
packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationPicker.test.ts (1)

158-250: Consider adding an empty-secondary-file-list regression test.

A test where getAllFileItems() returns [] and the flow returns cleanly to the main picker would lock down the race-condition behavior.

As per coding guidelines **/*.test.{ts,tsx}: Add tests for all new features and bug fixes with descriptive test names that explain the scenario.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationPicker.test.ts`
around lines 158 - 250, Add a regression test in DestinationPicker.test.ts that
simulates the secondary file list being empty and ensures the picker returns to
the main picker: set mockAvailabilityService.getGroupedDestinationItems to
include a 'file-more' item, have mockAvailabilityService.getAllFileItems return
[] (empty array), make showQuickPickMock return the 'file-more' item first then
a main item on retry, and have showFilePickerSpy resolve undefined (or call
handlers.onDismissed) to simulate dismissing the empty secondary list; then call
picker.pick(...) and assert showQuickPickMock was called twice,
showFilePickerSpy was invoked, and the final result is the main picker's
selection and appropriate debug logs from
DestinationPicker.showSecondaryFilePicker and DestinationPicker.pick were
emitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts`:
- Around line 938-975: The two tests "marks bound file with bound state" and
"marks only the file in the matching viewColumn as bound" call
DestinationAvailabilityService.getAllFileItems but omit assertions on the
logger; add assertions that mockLogger.debug was called with the message 'Built
all file items' and a payload containing fileCount: 1 for the "marks bound file
with bound state" test and fileCount: 2 for the two-tab-group test (use the
existing mockLogger instance used in the tests and assert the debug call after
calling getAllFileItems).
- Around line 674-734: Add assertions for the logger in this test to mirror
other getGroupedDestinationItems specs: after calling
service.getGroupedDestinationItems, assert mockLogger.debug was called with the
same debug messages/args used in the other tests (e.g., the messages emitted by
DestinationAvailabilityService.getGroupedDestinationItems when scanning tab
groups and building file items). Keep the assertions in this test (not a
separate logging-only test), referencing mockLogger.debug and the same
expectations used in the "returns file items when eligible files exist" test so
that the logger behavior for the case with duplicate URIs in different
viewColumns is validated alongside the result assertions.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/utils/showFilePicker.test.ts`:
- Around line 92-143: The tests for showFilePicker omit assertions about
logging; update each test that calls showFilePicker (the three shown plus the
ones at 211-227) to create and reuse the mock logger from createMockLogger and
assert its mock methods were called appropriately after the call: e.g., verify
logger received a selection/log entry when a file is chosen and a handler result
is produced (for the first two tests assert the logger mock was called with a
message containing the file name or handler result), and for the placeholder
test assert the logger mock recorded the placeholder usage or that a
showQuickPick invocation was logged; use the existing logger variable and its
mocked methods (from createMockLogger) and toHaveBeenCalled/
toHaveBeenCalledWith assertions to validate logging behavior.

In `@packages/rangelink-vscode-extension/src/destinations/DestinationPicker.ts`:
- Around line 156-163: The call in DestinationPicker that passes fileItems (from
availabilityService.getAllFileItems) into showFilePicker must guard against an
empty array because showFilePicker rejects empty input; before calling
showFilePicker check if fileItems is empty and, if so, skip calling
showFilePicker and return/continue the main picker flow (e.g., return undefined
or the appropriate fallback used by the surrounding method). Apply the same
empty-array guard to both places where fileItems is passed into showFilePicker
so you never call showFilePicker with an empty list.

In `@packages/rangelink-vscode-extension/src/statusBar/RangeLinkStatusBar.ts`:
- Around line 187-194: The call to availabilityService.getAllFileItems(...) can
return an empty array and showFilePicker will throw; before calling
showFilePicker check the returned fileItems (from getAllFileItems) for
empty/undefined and short-circuit (e.g., return or show a user message) to avoid
calling showFilePicker with no items; apply the same guard to the other similar
block(s) in RangeLinkStatusBar (the code around showFilePicker usages between
lines ~192-220) so every invocation verifies fileItems.length > 0 before calling
showFilePicker.

---

Nitpick comments:
In `@packages/rangelink-vscode-extension/README.md`:
- Line 136: The "File Picker" inline paragraph is an unreleased feature but
lacks the required visual marker; update the bold lead "**File Picker:**" in the
README.md so it reads "**File Picker: <sup>Unreleased</sup>**" (matching the
convention used for new sections/rows) to clearly indicate unreleased status,
keeping the rest of the paragraph text unchanged and consistent with the
existing "[!IMPORTANT]" banner and the released "Terminal Picker" entry.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts`:
- Around line 1035-1057: The test in DestinationAvailabilityService.test.ts is
only asserting the description property of items returned by
service.getAllFileItems(), which can miss regressions in the full
FileBindableQuickPickItem shape (e.g., bindOptions, boundState); update the
assertions to compare the entire item objects exactly for result[0] and
result[1] (not just description) by constructing the expected full
FileBindableQuickPickItem values using the same fixtures (uris from
createMockUri('/workspace/src/a/util.ts') and
createMockUri('/workspace/src/b/util.ts'), tabs from createMockTab, and group
via createMockTabGroup) and assert equality (strict/deep equals) against
result[0] and result[1] so the test fails if any property changes.

In
`@packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationPicker.test.ts`:
- Around line 158-250: Add a regression test in DestinationPicker.test.ts that
simulates the secondary file list being empty and ensures the picker returns to
the main picker: set mockAvailabilityService.getGroupedDestinationItems to
include a 'file-more' item, have mockAvailabilityService.getAllFileItems return
[] (empty array), make showQuickPickMock return the 'file-more' item first then
a main item on retry, and have showFilePickerSpy resolve undefined (or call
handlers.onDismissed) to simulate dismissing the empty secondary list; then call
picker.pick(...) and assert showQuickPickMock was called twice,
showFilePickerSpy was invoked, and the final result is the main picker's
selection and appropriate debug logs from
DestinationPicker.showSecondaryFilePicker and DestinationPicker.pick were
emitted.

In
`@packages/rangelink-vscode-extension/src/__tests__/statusBar/RangeLinkStatusBar.test.ts`:
- Around line 694-706: Tests repeat construction of a FileBindableQuickPickItem
fixture (object using FileBindableQuickPickItem, eligibleFile, bindOptions,
itemKind, fileInfo, boundState) across multiple places; extract a small factory
helper like createFileBindableQuickPickItem(eligibleFile) and replace the inline
objects in the tests (including the occurrences around FileBindableQuickPickItem
at lines referenced) to return the same shaped object so the tests reuse the
fixture and reduce duplication.

In
`@packages/rangelink-vscode-extension/src/destinations/utils/showFilePicker.ts`:
- Around line 59-62: The guard in showFilePicker that checks if (!('fileInfo' in
selected)) is unreachable because VS Code QuickPick never returns separator
items as selections; remove this dead branch (the logger.debug and return
undefined) and rely on the existing type narrowing/path that handles real
selections for the variable selected (and its fileInfo property), or if you
prefer to keep a defensive note, replace the branch with a single-line comment
explaining why the check is intentionally omitted rather than leaving
unreachable code in the function.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 919c17d and f10eb62.

📒 Files selected for processing (22)
  • packages/rangelink-vscode-extension/CHANGELOG.md
  • packages/rangelink-vscode-extension/README.md
  • packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationAvailabilityService.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/DestinationPicker.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/buildFilePickerItems.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/markBoundFile.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/destinations/utils/showFilePicker.test.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/createMockBindableQuickPickItem.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/createMockDestinationAvailabilityService.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/index.ts
  • packages/rangelink-vscode-extension/src/__tests__/helpers/spyOnShowFilePicker.ts
  • packages/rangelink-vscode-extension/src/__tests__/statusBar/RangeLinkStatusBar.test.ts
  • packages/rangelink-vscode-extension/src/destinations/DestinationAvailabilityService.ts
  • packages/rangelink-vscode-extension/src/destinations/DestinationPicker.ts
  • packages/rangelink-vscode-extension/src/destinations/types/FilePickerHandlers.ts
  • packages/rangelink-vscode-extension/src/destinations/types/index.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/buildFilePickerItems.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/index.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/markBoundFile.ts
  • packages/rangelink-vscode-extension/src/destinations/utils/showFilePicker.ts
  • packages/rangelink-vscode-extension/src/statusBar/RangeLinkStatusBar.ts
  • packages/rangelink-vscode-extension/src/types/GroupedDestinationKinds.ts

Comment on lines 156 to 163
const fileItems = this.availabilityService.getAllFileItems(
boundFileUriString,
boundFileViewColumn,
);

const result = await showFilePicker<InternalPickerResult>(
fileItems,
this.uiProvider,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard empty fileItems before invoking showFilePicker.

showFilePicker rejects empty input. If open files change between main pick and secondary pick, this can throw instead of returning to the main picker flow.

Suggested fix
   const fileItems = this.availabilityService.getAllFileItems(
     boundFileUriString,
     boundFileViewColumn,
   );
+  if (fileItems.length === 0) {
+    this.logger.debug(logCtx, 'No files available for secondary file picker');
+    return { outcome: 'returned-to-main-picker' };
+  }

   const result = await showFilePicker<InternalPickerResult>(
     fileItems,

Also applies to: 161-179

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/rangelink-vscode-extension/src/destinations/DestinationPicker.ts`
around lines 156 - 163, The call in DestinationPicker that passes fileItems
(from availabilityService.getAllFileItems) into showFilePicker must guard
against an empty array because showFilePicker rejects empty input; before
calling showFilePicker check if fileItems is empty and, if so, skip calling
showFilePicker and return/continue the main picker flow (e.g., return undefined
or the appropriate fallback used by the surrounding method). Apply the same
empty-array guard to both places where fileItems is passed into showFilePicker
so you never call showFilePicker with an empty list.

Without viewColumn context, a user with multiple side-by-side editor groups sees identical "active" descriptions for each group's representative file — there's no way to tell which item belongs to which group. This is most visible when viewColumns 2 and 3 each contain a single file: both appear under the "Files" separator as plain "active" entries.

The fix appends "Tab Group N" at render time inside `buildDestinationQuickPickItems`, which is only called for the R-M inline list and the destination picker — the secondary "More files…" picker is unaffected because it uses a separate rendering path (`buildFilePickerItems`).

Benefits:
- Users with multi-column workspaces can immediately identify which Tab Group each inline file belongs to
- The bound badge ("bound · Tab Group N") and active badge ("active · Tab Group N") now carry full location context
- Secondary file picker descriptions are unchanged — Tab Group separators there already provide context
…o foreground

When a user selects a background-tab file during an R-V flow, the cursor lands at an unknown position — pasting immediately would write content somewhere unintended. The bind should succeed so the file is registered as the destination, but the auto-paste for that invocation should be skipped so the user can position their cursor first.

Benefits:
- R-V with a background tab now binds the file without blindly pasting at an unknown cursor position
- suppressAutoPaste on BindSuccessInfo is a typed, explicit signal the caller can inspect — no hidden state or side channels
- Normal bind paths are unaffected; the field is absent unless a background tab was opened
…to-paste

When R-V selects a background-tab file, the bind should succeed but pasting immediately at an unknown cursor position is unsafe. The new outcome propagates the suppressAutoPaste signal from BindSuccessInfo through showPickerAndBindForPaste() so all three R-V callers abort without writing to the clipboard.

Benefits:
- R-V flow binds the background-tab file correctly and skips the unsafe auto-paste
- All existing callers handle 'bound-no-paste' for free — their `outcome !== 'bound'` guard already returns early
- Typed discriminated union makes the suppression path explicit and exhaustiveness-checked
Without the CTA, the toast only confirmed the file was opened — the user had no explicit prompt to move their cursor before issuing R-V again. The updated message tells them exactly what to do next.

Benefits:
- Users understand they must reposition the cursor before pasting
- Consistent with the suppressed-auto-paste behavior introduced in the same flow
…() callers

BindToDestinationCommand and showPickerAndBindForPaste() were calling pick() with no bound-state options, so the destination picker never highlighted which file or terminal was already bound. The DestinationPicker plumbing already supported boundFileUriString, boundFileViewColumn, and boundTerminalProcessId — the callers just weren't supplying them.

Benefits:
- The 'bound' badge surfaces on the currently-bound file or terminal in both R-D and the R-D secondary file picker
- Consistent with RangeLinkStatusBar.showSecondaryFilePicker, which already follows this pattern
- Conditional spread means callers with no bound destination emit no extra fields, keeping existing tests green
…ile workspaces

The command palette 'Bind to Text Editor' action used a flat QuickPick fed by getFileItems(), which returned only one active tab per group. With multiple tab groups open, users had no way to distinguish files from different groups or reach non-active tabs. The single-file auto-bind shortcut is preserved.

Benefits:
- Multi-file case now shows Active Files + Tab Group N sections via showFilePicker, matching the R-M secondary picker and R-D
- Bound badge appears on the currently-bound file when re-binding
- Files from all tabs are reachable, not just the active one per group
- getFileItems() removed — it was the only caller and is now replaced by getAllFileItems()
…s, and factory

Five categories of reviewer feedback addressed: missing logger assertions (T007), two uncaught-throw race conditions, a misleading warn log on a TypeScript-only dead branch, incomplete test assertions (T002/T004), a missing README unreleased marker, and inconsistent fixture construction across 9 test files.

Benefits:
- Guards in DestinationPicker and RangeLinkStatusBar prevent showFilePicker from throwing FILE_PICKER_EMPTY_ITEMS when all tabs close between the "More files..." click and secondary picker opening; RangeLinkStatusBar now also shows an error toast so the user knows why the menu disappeared
- Logger assertions added to getAllFileItems bound tests, getGroupedDestinationItems duplicate-URI test, and showFilePicker happy-path tests — logging is now part of the observable test contract
- Dead-branch warn replaced with a clarifying comment explaining the TypeScript narrowing requirement; 100% branch coverage preserved
- 'applies disambiguators' test upgraded from property-level checks to full toStrictEqual, catching any unintended shape changes
- createMockTextEditorQuickPickItem factory updated to accept fileInfo and description params; 71 inline FileBindableQuickPickItem constructions across 6 test files migrated to the factory
- README File Picker entry marked <sup>Unreleased</sup> per trunk-based documentation convention

Ref: #370 (review)
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.

File Picker — Secondary "More Files..." Picker

1 participant