Fix crash due to mutating command palette's candidates on confirmation (#3575)

I was seeing a crash when confirming the command palette. It was caused
by getting the palette's `commands` (match candidates) and `matches`
getting out of sync because we mutated `commands` when removing the
selected command.
This commit is contained in:
Max Brunsfeld 2023-12-09 11:16:04 -08:00 committed by GitHub
commit 188d727d31
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -7,7 +7,7 @@ use collections::{CommandPaletteFilter, HashMap};
use fuzzy::{StringMatch, StringMatchCandidate}; use fuzzy::{StringMatch, StringMatchCandidate};
use gpui::{ use gpui::{
actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView, actions, Action, AppContext, DismissEvent, Div, EventEmitter, FocusHandle, FocusableView,
Keystroke, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView, ParentElement, Render, Styled, View, ViewContext, VisualContext, WeakView,
}; };
use picker::{Picker, PickerDelegate}; use picker::{Picker, PickerDelegate};
@ -61,7 +61,6 @@ impl CommandPalette {
Some(Command { Some(Command {
name: humanize_action_name(&name), name: humanize_action_name(&name),
action, action,
keystrokes: vec![], // todo!()
}) })
}) })
.collect(); .collect();
@ -110,7 +109,6 @@ pub struct CommandPaletteDelegate {
struct Command { struct Command {
name: String, name: String,
action: Box<dyn Action>, action: Box<dyn Action>,
keystrokes: Vec<Keystroke>,
} }
impl Clone for Command { impl Clone for Command {
@ -118,7 +116,6 @@ impl Clone for Command {
Self { Self {
name: self.name.clone(), name: self.name.clone(),
action: self.action.boxed_clone(), action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
} }
} }
} }
@ -229,6 +226,7 @@ impl PickerDelegate for CommandPaletteDelegate {
}) })
} }
} }
if let Some(CommandInterceptResult { if let Some(CommandInterceptResult {
action, action,
string, string,
@ -244,7 +242,6 @@ impl PickerDelegate for CommandPaletteDelegate {
commands.push(Command { commands.push(Command {
name: string.clone(), name: string.clone(),
action, action,
keystrokes: vec![],
}); });
matches.insert( matches.insert(
0, 0,
@ -256,6 +253,7 @@ impl PickerDelegate for CommandPaletteDelegate {
}, },
) )
} }
picker picker
.update(&mut cx, |picker, _| { .update(&mut cx, |picker, _| {
let delegate = &mut picker.delegate; let delegate = &mut picker.delegate;
@ -285,6 +283,8 @@ impl PickerDelegate for CommandPaletteDelegate {
} }
let action_ix = self.matches[self.selected_ix].candidate_id; let action_ix = self.matches[self.selected_ix].candidate_id;
let command = self.commands.swap_remove(action_ix); let command = self.commands.swap_remove(action_ix);
self.matches.clear();
self.commands.clear();
cx.update_global(|hit_counts: &mut HitCounts, _| { cx.update_global(|hit_counts: &mut HitCounts, _| {
*hit_counts.0.entry(command.name).or_default() += 1; *hit_counts.0.entry(command.name).or_default() += 1;
}); });
@ -300,13 +300,8 @@ impl PickerDelegate for CommandPaletteDelegate {
selected: bool, selected: bool,
cx: &mut ViewContext<Picker<Self>>, cx: &mut ViewContext<Picker<Self>>,
) -> Option<Self::ListItem> { ) -> Option<Self::ListItem> {
let Some(r#match) = self.matches.get(ix) else { let r#match = self.matches.get(ix)?;
return None; let command = self.commands.get(r#match.candidate_id)?;
};
let Some(command) = self.commands.get(r#match.candidate_id) else {
return None;
};
Some( Some(
ListItem::new(ix).inset(true).selected(selected).child( ListItem::new(ix).inset(true).selected(selected).child(
h_stack() h_stack()
@ -354,8 +349,7 @@ impl std::fmt::Debug for Command {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Command") f.debug_struct("Command")
.field("name", &self.name) .field("name", &self.name)
.field("keystrokes", &self.keystrokes) .finish_non_exhaustive()
.finish()
} }
} }