Skip to content

Add stable_id support for match restoration - #44829

Closed
hoodie wants to merge 16 commits into
zed-industries:mainfrom
hoodie:feature/stabil-picker-selection
Closed

Add stable_id support for match restoration#44829
hoodie wants to merge 16 commits into
zed-industries:mainfrom
hoodie:feature/stabil-picker-selection

Conversation

@hoodie

@hoodie hoodie commented Dec 14, 2025

Copy link
Copy Markdown
Contributor
  • Implement match_stable_id and find_match_by_stable_id for Outline
  • Generate stable_id as "text:depth" for outline items
  • Implement stable_id lookups for Project Symbols
  • Implement stable_id support in Icon Theme Selector and Theme Selector
  • Use stable_id to preserve and restore manual selections in Picker

Closes #35440

Release Notes:

  • Added stickiness to manually selected items in project symbols and outline

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Dec 14, 2025
@hoodie
hoodie force-pushed the feature/stabil-picker-selection branch from 94770d9 to be27920 Compare December 14, 2025 22:24
@SomeoneToIgnore SomeoneToIgnore self-assigned this Jan 5, 2026

@SomeoneToIgnore SomeoneToIgnore left a comment

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.

Sorry for the long wait time: had had quite a rough time and then a company-wide holiday.

Looks really great when I've tried it, thank you so much for finding a way to improve things generically.

One concern besides the listed ones: we need to test it.
At least, a test to show that the stable sorting works, and, ideally, another one to cover

Item no longer in results, clear manual selection and reset to first item

branch.

Comment thread crates/picker/src/picker.rs Outdated
self.delegate.set_selected_index(ix, window, cx);
let current_index = self.delegate.selected_index();

// Track manually selected item to preserve across match updates

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.

This comment states pretty much the same what the code below does, so not needed?

Also, can we move the whole thing into the if previous_index != current_index block?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

actually not, it subtly breaks the behavior, but I had to test it to find that out. Seems like I should add a few tests though to make sure of it.

Comment thread crates/outline/src/outline.rs Outdated
///
/// Use this for user-driven selections (keyboard navigation, mouse clicks) where you want
/// the user's choice to be maintained as they continue typing. For programmatic selections
/// that should not persist, use `set_selected_index` instead.

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.

For programmatic selections that should not persist, use set_selected_index instead.

Curious, what is the use case for these?
If we use select_index_sticky everywhere, what does it change?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

intuitively that should be fine, I was just a bit careful to not change the default in too many places at once, because I had no good overview of what needs to be tested

Comment thread crates/picker/src/picker.rs Outdated
} else {
// Item no longer in results, clear manual selection and reset to first item
self.manually_selected_stable_id = None;
let ix = 0.min(match_count.saturating_sub(1));

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.

0.min(any_other_usize) is 0 or am I missing something?

Also, should we try to fall back to closest selected index instead, to avoid large list jumps?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good catch. from my manual testing I didn't perceive this as too bad, since usually you're already down to only a handful of matches by the time this happens. I'll change this you jump to the closest

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

about those large jumps. wasn't there also a ticket about the heuristic that selects the very first item when opening the picker? I can't find it right now. anyway, right now the picker already picks a "relatively random" item instead of "the closest to the cursor". maybe I find that ticket again some day

@hoodie
hoodie force-pushed the feature/stabil-picker-selection branch from be27920 to 958bfc6 Compare February 18, 2026 22:23
@hoodie
hoodie requested review from a team as code owners February 18, 2026 22:23
@hoodie
hoodie requested review from dinocosta and reflectronic and removed request for a team February 18, 2026 22:23
@hoodie

hoodie commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author

I've added a StableId trait that allows you to have a type-specific struct per DelegateImplementation. This resulted in me having to touch every single implementation of the PickerDelegate unfortunately, but most of these are just () and the default impl still is just the old behavior. But with this we can have specific implementations for each kind of match.
Works good so far.

@hoodie

hoodie commented Feb 18, 2026

Copy link
Copy Markdown
Contributor Author
Screen.Recording.2026-02-19.at.00.24.47.mov

I've added it for the recent projects picker too

@hoodie
hoodie force-pushed the feature/stabil-picker-selection branch from e8ac6b1 to aac841d Compare February 19, 2026 17:45
@hoodie

hoodie commented Feb 19, 2026

Copy link
Copy Markdown
Contributor Author

and now with correct formatting 🙄

@dinocosta
dinocosta removed their request for review February 19, 2026 17:48
@SomeoneToIgnore
SomeoneToIgnore removed request for a team February 19, 2026 18:07
@SomeoneToIgnore

Copy link
Copy Markdown
Contributor

Yes, I think I can take over from now on, if that's fine with you to wait another few weeks potentially.
So far, grinding down PR backlog starting from smaller and less impactful PRs.

@hoodie

hoodie commented Mar 17, 2026

Copy link
Copy Markdown
Contributor Author

cool

@github-actions github-actions Bot added size/XL large-pr Automation related label for detecting large contributor PRs labels Mar 19, 2026
@github-actions

Copy link
Copy Markdown

📏 PR Size: 1332 lines changed (size/XL)

Please note: this PR exceeds the 400 LOC soft limit.

  • Consider splitting into separate PRs if the changes are separable
  • Ensure the PR description includes a guided tour in the "How to Review" section so reviewers know where to start

@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026
@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026
@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026
@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026
@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026
@SomeoneToIgnore
SomeoneToIgnore force-pushed the feature/stabil-picker-selection branch from 66a85ad to 94665c4 Compare March 19, 2026 13:56
@github-actions github-actions Bot added size/XL and removed size/XL labels Mar 19, 2026

@SomeoneToIgnore SomeoneToIgnore left a comment

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.

I have trimmed it down to the scope where only the pickers influenced by the change have their code adjusted.


When re-testing, I've found 2 things with the selection counter-intuitive: when I select an item manually and first get a query that mismatches the manually selected item entirely (this it's not on the list anymore), it would be good get the manually selected item re-selected on backspace

Now, it seems that backspacing leaves the selection in some odd places sometimes, even without this fallback?
Would be good to keep it closer to top by default, as we try to do now after recent PRs.

} else {
// No manual selection - clamp current index to valid range
let current_index = self.delegate.selected_index();
let ix = current_index.min(match_count.saturating_sub(1));

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.

I think this and the one above break recent attempts of #50594 to select the first match by default, can we ensure we try and select something closer to the top here?

@SomeoneToIgnore

Copy link
Copy Markdown
Contributor

Sorry, I see the conflicts start to appear, hence I'll close this for now: it seems that no one had upvoted the issue still, so it's not that popular, and I'm not able to find time to push the logical fixes further.

I think what's left is to go over backspace interaction better, add more tests and make it look "natural" when modifying the query.

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

Labels

cla-signed The user has signed the Contributor License Agreement large-pr Automation related label for detecting large contributor PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Outline picker loses selection when modifying the query.

3 participants