From 22e31a0d410dc22d2fbeabe8530b6836d107bf78 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 10 Sep 2025 23:06:09 +0530 Subject: [PATCH] Fix crash when filtering items in Picker (#37929) Closes #37617 We're already using `get` in a bunch of places, this PR updates the remaining spots to follow the same pattern. Note that the `ix` we read in `render_match` can sometimes be stale. The likely reason is that we run the match-update logic asynchronously (see [here](https://github.com/zed-industries/zed/blob/138117e0b15664079f5526cb56168750382b49b9/crates/picker/src/picker.rs#L643)). That means it's possible to render items after the list's [data update](https://github.com/zed-industries/zed/blob/138117e0b15664079f5526cb56168750382b49b9/crates/picker/src/picker.rs#L652) but before the [list reset](https://github.com/zed-industries/zed/blob/138117e0b15664079f5526cb56168750382b49b9/crates/picker/src/picker.rs#L662), in which case the `ix` can be greater than that of our updated data. Release Notes: - Fixed crash when filtering MCP tools. --- crates/agent_ui/src/agent_configuration/tool_picker.rs | 2 +- .../agent_ui/src/context_picker/file_context_picker.rs | 2 +- .../src/context_picker/rules_context_picker.rs | 2 +- .../src/context_picker/symbol_context_picker.rs | 2 +- .../src/context_picker/thread_context_picker.rs | 2 +- crates/collab_ui/src/collab_panel/contact_finder.rs | 2 +- crates/debugger_ui/src/attach_modal.rs | 2 +- crates/debugger_ui/src/new_process_modal.rs | 2 +- crates/extensions_ui/src/extension_version_selector.rs | 4 ++-- crates/file_finder/src/file_finder.rs | 5 +---- crates/git_ui/src/branch_picker.rs | 2 +- crates/git_ui/src/picker_prompt.rs | 2 +- crates/jj_ui/src/bookmark_picker.rs | 2 +- crates/language_selector/src/language_selector.rs | 2 +- .../line_ending_selector/src/line_ending_selector.rs | 4 ++-- crates/onboarding/src/base_keymap_picker.rs | 2 +- crates/project_symbols/src/project_symbols.rs | 4 ++-- .../src/settings_profile_selector.rs | 4 ++-- crates/snippets_ui/src/snippets_ui.rs | 2 +- crates/tab_switcher/src/tab_switcher.rs | 5 +---- crates/tasks_ui/src/modal.rs | 2 +- crates/theme_selector/src/icon_theme_selector.rs | 2 +- crates/theme_selector/src/theme_selector.rs | 2 +- crates/toolchain_selector/src/toolchain_selector.rs | 4 ++-- crates/vim/src/state.rs | 10 ++-------- 25 files changed, 31 insertions(+), 43 deletions(-) diff --git a/crates/agent_ui/src/agent_configuration/tool_picker.rs b/crates/agent_ui/src/agent_configuration/tool_picker.rs index 25947a1e589d96f1f4bfc64a04f43f48153420b9..2ba92fa6b7993664d278cfd57d851dcfd9cb0922 100644 --- a/crates/agent_ui/src/agent_configuration/tool_picker.rs +++ b/crates/agent_ui/src/agent_configuration/tool_picker.rs @@ -318,7 +318,7 @@ impl PickerDelegate for ToolPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let item = &self.filtered_items[ix]; + let item = &self.filtered_items.get(ix)?; match item { PickerItem::ContextServer { server_id, .. } => Some( div() diff --git a/crates/agent_ui/src/context_picker/file_context_picker.rs b/crates/agent_ui/src/context_picker/file_context_picker.rs index 6c224caf4c114c389991907fd7e382fb7e4e49d1..43b1fa5e92fcd792ee1e8567ac558652e933bbfa 100644 --- a/crates/agent_ui/src/context_picker/file_context_picker.rs +++ b/crates/agent_ui/src/context_picker/file_context_picker.rs @@ -160,7 +160,7 @@ impl PickerDelegate for FileContextPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let FileMatch { mat, .. } = &self.matches[ix]; + let FileMatch { mat, .. } = &self.matches.get(ix)?; Some( ListItem::new(ix) diff --git a/crates/agent_ui/src/context_picker/rules_context_picker.rs b/crates/agent_ui/src/context_picker/rules_context_picker.rs index f3982f61cb9822a238dc6573e81430de98c78838..677011577aef23296a34203acdb10e5228ca7cd7 100644 --- a/crates/agent_ui/src/context_picker/rules_context_picker.rs +++ b/crates/agent_ui/src/context_picker/rules_context_picker.rs @@ -146,7 +146,7 @@ impl PickerDelegate for RulesContextPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let thread = &self.matches[ix]; + let thread = &self.matches.get(ix)?; Some(ListItem::new(ix).inset(true).toggle_state(selected).child( render_thread_context_entry(thread, self.context_store.clone(), cx), diff --git a/crates/agent_ui/src/context_picker/symbol_context_picker.rs b/crates/agent_ui/src/context_picker/symbol_context_picker.rs index b00d4e3693d2aff12c54c5ebda9acf93c74bfc7d..993d65bd12ee4e01ca8d9767ccd46dd3fd645dd3 100644 --- a/crates/agent_ui/src/context_picker/symbol_context_picker.rs +++ b/crates/agent_ui/src/context_picker/symbol_context_picker.rs @@ -169,7 +169,7 @@ impl PickerDelegate for SymbolContextPickerDelegate { _window: &mut Window, _: &mut Context>, ) -> Option { - let mat = &self.matches[ix]; + let mat = &self.matches.get(ix)?; Some(ListItem::new(ix).inset(true).toggle_state(selected).child( render_symbol_context_entry(ElementId::named_usize("symbol-ctx-picker", ix), mat), diff --git a/crates/agent_ui/src/context_picker/thread_context_picker.rs b/crates/agent_ui/src/context_picker/thread_context_picker.rs index 66654f3d8c066701a9f84df4d53bd9f1d9c30d3b..9e843779c2216a89fe23dce514553e50043b8187 100644 --- a/crates/agent_ui/src/context_picker/thread_context_picker.rs +++ b/crates/agent_ui/src/context_picker/thread_context_picker.rs @@ -220,7 +220,7 @@ impl PickerDelegate for ThreadContextPickerDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let thread = &self.matches[ix]; + let thread = &self.matches.get(ix)?; Some(ListItem::new(ix).inset(true).toggle_state(selected).child( render_thread_context_entry(thread, self.context_store.clone(), cx), diff --git a/crates/collab_ui/src/collab_panel/contact_finder.rs b/crates/collab_ui/src/collab_panel/contact_finder.rs index 3c23ccc017838e8b97ec334dd432840e516ed413..e5823d0e78d9bf73ae3ded307116f608d7c06b22 100644 --- a/crates/collab_ui/src/collab_panel/contact_finder.rs +++ b/crates/collab_ui/src/collab_panel/contact_finder.rs @@ -148,7 +148,7 @@ impl PickerDelegate for ContactFinderDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let user = &self.potential_contacts[ix]; + let user = &self.potential_contacts.get(ix)?; let request_status = self.user_store.read(cx).contact_request_status(user); let icon_path = match request_status { diff --git a/crates/debugger_ui/src/attach_modal.rs b/crates/debugger_ui/src/attach_modal.rs index 3e3bc3ec27c3d1dbf0bacd445b883a50370d5b6f..8926b3bb6f61e6e612f75777584810fa24b616ba 100644 --- a/crates/debugger_ui/src/attach_modal.rs +++ b/crates/debugger_ui/src/attach_modal.rs @@ -289,7 +289,7 @@ impl PickerDelegate for AttachModalDelegate { _window: &mut Window, _: &mut Context>, ) -> Option { - let hit = &self.matches[ix]; + let hit = &self.matches.get(ix)?; let candidate = self.candidates.get(hit.candidate_id)?; Some( diff --git a/crates/debugger_ui/src/new_process_modal.rs b/crates/debugger_ui/src/new_process_modal.rs index efa5e4700af31b91c29803f56527a7b166c16b9e..eeed36ac1df18d5a0d1b33d6c3567c7df6a4b9c0 100644 --- a/crates/debugger_ui/src/new_process_modal.rs +++ b/crates/debugger_ui/src/new_process_modal.rs @@ -1446,7 +1446,7 @@ impl PickerDelegate for DebugDelegate { window: &mut Window, cx: &mut Context>, ) -> Option { - let hit = &self.matches[ix]; + let hit = &self.matches.get(ix)?; let highlighted_location = HighlightedMatch { text: hit.string.clone(), diff --git a/crates/extensions_ui/src/extension_version_selector.rs b/crates/extensions_ui/src/extension_version_selector.rs index aaf5d5e8eb8308f3833e2638f1e0e72186f3d983..fe7a419fbe8001b99d5c3ebf16dfc38cda3fc713 100644 --- a/crates/extensions_ui/src/extension_version_selector.rs +++ b/crates/extensions_ui/src/extension_version_selector.rs @@ -207,8 +207,8 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let version_match = &self.matches[ix]; - let extension_version = &self.extension_versions[version_match.candidate_id]; + let version_match = &self.matches.get(ix)?; + let extension_version = &self.extension_versions.get(version_match.candidate_id)?; let is_version_compatible = extension_host::is_version_compatible(ReleaseChannel::global(cx), extension_version); diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index e2f8d55cf2a0c8f21a1ed6b3eab870d267173bba..53cf0552f22a59c31ab2422d86eb8cbb76145908 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -1599,10 +1599,7 @@ impl PickerDelegate for FileFinderDelegate { ) -> Option { let settings = FileFinderSettings::get_global(cx); - let path_match = self - .matches - .get(ix) - .expect("Invalid matches state: no element for index {ix}"); + let path_match = self.matches.get(ix)?; let history_icon = match &path_match { Match::History { .. } => Icon::new(IconName::HistoryRerun) diff --git a/crates/git_ui/src/branch_picker.rs b/crates/git_ui/src/branch_picker.rs index e5bdce7722fcc87a935a4a7f11d8159fbb49e6af..42558b7b79bca1892b9d36ae4b39bb3cdb196d4f 100644 --- a/crates/git_ui/src/branch_picker.rs +++ b/crates/git_ui/src/branch_picker.rs @@ -454,7 +454,7 @@ impl PickerDelegate for BranchListDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let entry = &self.matches[ix]; + let entry = &self.matches.get(ix)?; let (commit_time, author_name, subject) = entry .branch diff --git a/crates/git_ui/src/picker_prompt.rs b/crates/git_ui/src/picker_prompt.rs index 3f1d507c42f71eddaef4b2b8a114a84f2aabf875..9997b0590cedfeab7cad6a7c52bce63f10657a80 100644 --- a/crates/git_ui/src/picker_prompt.rs +++ b/crates/git_ui/src/picker_prompt.rs @@ -216,7 +216,7 @@ impl PickerDelegate for PickerPromptDelegate { _window: &mut Window, _cx: &mut Context>, ) -> Option { - let hit = &self.matches[ix]; + let hit = &self.matches.get(ix)?; let shortened_option = util::truncate_and_trailoff(&hit.string, self.max_match_length); Some( diff --git a/crates/jj_ui/src/bookmark_picker.rs b/crates/jj_ui/src/bookmark_picker.rs index f6121fb9fc4cf40eaee2fa0f759e34e67d60429d..95c23e73f56059c92397222a132700351c710147 100644 --- a/crates/jj_ui/src/bookmark_picker.rs +++ b/crates/jj_ui/src/bookmark_picker.rs @@ -182,7 +182,7 @@ impl PickerDelegate for BookmarkPickerDelegate { _window: &mut Window, _cx: &mut Context>, ) -> Option { - let entry = &self.matches[ix]; + let entry = &self.matches.get(ix)?; Some( ListItem::new(ix) diff --git a/crates/language_selector/src/language_selector.rs b/crates/language_selector/src/language_selector.rs index f6e2d75015560582b30453767b1a3b30f7cce82e..991cce50baf82b2604e510a0eeb2eac4af1578dd 100644 --- a/crates/language_selector/src/language_selector.rs +++ b/crates/language_selector/src/language_selector.rs @@ -283,7 +283,7 @@ impl PickerDelegate for LanguageSelectorDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let mat = &self.matches[ix]; + let mat = &self.matches.get(ix)?; let (label, language_icon) = self.language_data_for_match(mat, cx); Some( ListItem::new(ix) diff --git a/crates/line_ending_selector/src/line_ending_selector.rs b/crates/line_ending_selector/src/line_ending_selector.rs index 532f0b051d79e25229d7cb72419ca557edd5b477..7f75a1ebe3550595c8fa78643ef5446ab2fa3a44 100644 --- a/crates/line_ending_selector/src/line_ending_selector.rs +++ b/crates/line_ending_selector/src/line_ending_selector.rs @@ -171,7 +171,7 @@ impl PickerDelegate for LineEndingSelectorDelegate { _: &mut Window, _: &mut Context>, ) -> Option { - let line_ending = self.matches[ix]; + let line_ending = self.matches.get(ix)?; let label = match line_ending { LineEnding::Unix => "LF", LineEnding::Windows => "CRLF", @@ -183,7 +183,7 @@ impl PickerDelegate for LineEndingSelectorDelegate { .toggle_state(selected) .child(Label::new(label)); - if self.line_ending == line_ending { + if &self.line_ending == line_ending { list_item = list_item.end_slot(Icon::new(IconName::Check).color(Color::Muted)); } diff --git a/crates/onboarding/src/base_keymap_picker.rs b/crates/onboarding/src/base_keymap_picker.rs index 79a716cc9eaf36a77668900f63ba42a896a334eb..950ed1d3133549a566808bd7cee002437708a2ad 100644 --- a/crates/onboarding/src/base_keymap_picker.rs +++ b/crates/onboarding/src/base_keymap_picker.rs @@ -213,7 +213,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { _window: &mut Window, _cx: &mut Context>, ) -> Option { - let keymap_match = &self.matches[ix]; + let keymap_match = &self.matches.get(ix)?; Some( ListItem::new(ix) diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index ea67499acb07fc7517028dcd43282b051d52c3eb..cec7729252ac4bf8a532c18d18f5c1c4636b308c 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -217,8 +217,8 @@ impl PickerDelegate for ProjectSymbolsDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let string_match = &self.matches[ix]; - let symbol = &self.symbols[string_match.candidate_id]; + let string_match = &self.matches.get(ix)?; + let symbol = &self.symbols.get(string_match.candidate_id)?; let syntax_runs = styled_runs_for_code_label(&symbol.label, cx.theme().syntax()); let mut path = symbol.path.path.to_string_lossy(); diff --git a/crates/settings_profile_selector/src/settings_profile_selector.rs b/crates/settings_profile_selector/src/settings_profile_selector.rs index d7ebd6488dd07647f29fbe05eac4a0eabf74765a..0bba83beafb2ddc1c20767aefd15cc2095ca71ba 100644 --- a/crates/settings_profile_selector/src/settings_profile_selector.rs +++ b/crates/settings_profile_selector/src/settings_profile_selector.rs @@ -257,8 +257,8 @@ impl PickerDelegate for SettingsProfileSelectorDelegate { _: &mut Window, _: &mut Context>, ) -> Option { - let mat = &self.matches[ix]; - let profile_name = &self.profile_names[mat.candidate_id]; + let mat = &self.matches.get(ix)?; + let profile_name = &self.profile_names.get(mat.candidate_id)?; Some( ListItem::new(ix) diff --git a/crates/snippets_ui/src/snippets_ui.rs b/crates/snippets_ui/src/snippets_ui.rs index db76ab6f9a503b0611910f1271d5b4773bfbf63e..7f2689f0be7a805e6b80a9b7c320cdaa13cc46ac 100644 --- a/crates/snippets_ui/src/snippets_ui.rs +++ b/crates/snippets_ui/src/snippets_ui.rs @@ -310,7 +310,7 @@ impl PickerDelegate for ScopeSelectorDelegate { _window: &mut Window, cx: &mut Context>, ) -> Option { - let mat = &self.matches[ix]; + let mat = &self.matches.get(ix)?; let name_label = mat.string.clone(); let scope_name = ScopeName(Cow::Owned( diff --git a/crates/tab_switcher/src/tab_switcher.rs b/crates/tab_switcher/src/tab_switcher.rs index 241642115a3025aba13fe1aa8788e2c382b24693..2923ee6dd4b53108f0566a0a298b7fffd7e836ee 100644 --- a/crates/tab_switcher/src/tab_switcher.rs +++ b/crates/tab_switcher/src/tab_switcher.rs @@ -649,10 +649,7 @@ impl PickerDelegate for TabSwitcherDelegate { window: &mut Window, cx: &mut Context>, ) -> Option { - let tab_match = self - .matches - .get(ix) - .expect("Invalid matches state: no element for index {ix}"); + let tab_match = self.matches.get(ix)?; let params = TabContentParams { detail: Some(tab_match.detail), diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 423c28c7100e9dc97339fe1d582b61dbb6d55f6a..e14b42af2b673ae161b5434e0c4f9b3f096e316a 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -443,7 +443,7 @@ impl PickerDelegate for TasksModalDelegate { cx: &mut Context>, ) -> Option { let candidates = self.candidates.as_ref()?; - let hit = &self.matches[ix]; + let hit = &self.matches.get(ix)?; let (source_kind, resolved_task) = &candidates.get(hit.candidate_id)?; let template = resolved_task.original_task(); let display_label = resolved_task.display_label(); diff --git a/crates/theme_selector/src/icon_theme_selector.rs b/crates/theme_selector/src/icon_theme_selector.rs index af7abdee62b7d0d86b8f7e22b91c28185e27b86c..5b26124079d9bf20edac38baa3dd49f2bb579456 100644 --- a/crates/theme_selector/src/icon_theme_selector.rs +++ b/crates/theme_selector/src/icon_theme_selector.rs @@ -287,7 +287,7 @@ impl PickerDelegate for IconThemeSelectorDelegate { _window: &mut Window, _cx: &mut Context>, ) -> Option { - let theme_match = &self.matches[ix]; + let theme_match = &self.matches.get(ix)?; Some( ListItem::new(ix) diff --git a/crates/theme_selector/src/theme_selector.rs b/crates/theme_selector/src/theme_selector.rs index 8c48f295ddc282718331657cff0c9cf106e1b623..8cc450c97fb1fe0b8bdf7866156880471d0673d4 100644 --- a/crates/theme_selector/src/theme_selector.rs +++ b/crates/theme_selector/src/theme_selector.rs @@ -345,7 +345,7 @@ impl PickerDelegate for ThemeSelectorDelegate { _window: &mut Window, _cx: &mut Context>, ) -> Option { - let theme_match = &self.matches[ix]; + let theme_match = &self.matches.get(ix)?; Some( ListItem::new(ix) diff --git a/crates/toolchain_selector/src/toolchain_selector.rs b/crates/toolchain_selector/src/toolchain_selector.rs index 2f946a69152f76912a1da996e429c48e3ec3be10..3804238634a1dc35212698b323baf04929f1675f 100644 --- a/crates/toolchain_selector/src/toolchain_selector.rs +++ b/crates/toolchain_selector/src/toolchain_selector.rs @@ -1011,8 +1011,8 @@ impl PickerDelegate for ToolchainSelectorDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let mat = &self.matches[ix]; - let (toolchain, scope) = &self.candidates[mat.candidate_id]; + let mat = &self.matches.get(ix)?; + let (toolchain, scope) = &self.candidates.get(mat.candidate_id)?; let label = toolchain.name.clone(); let path = Self::relativize_path(toolchain.path.clone(), &self.worktree_abs_path_root); diff --git a/crates/vim/src/state.rs b/crates/vim/src/state.rs index 8503bffca6ec120f1103fa1dcd72281c092ac941..cd9382da461b941e0162353583131d17ae052a25 100644 --- a/crates/vim/src/state.rs +++ b/crates/vim/src/state.rs @@ -1192,10 +1192,7 @@ impl PickerDelegate for RegistersViewDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let register_match = self - .matches - .get(ix) - .expect("Invalid matches state: no element for index {ix}"); + let register_match = self.matches.get(ix)?; let mut output = String::new(); let mut runs = Vec::new(); @@ -1584,10 +1581,7 @@ impl PickerDelegate for MarksViewDelegate { _: &mut Window, cx: &mut Context>, ) -> Option { - let mark_match = self - .matches - .get(ix) - .expect("Invalid matches state: no element for index {ix}"); + let mark_match = self.matches.get(ix)?; let mut left_output = String::new(); let mut left_runs = Vec::new();