Improve StringMatchCandidate::new interface (#22011)

Michael Sloan created

Release Notes:

- N/A

Change summary

crates/assistant/src/context_store.rs                     |   2 
crates/assistant/src/prompt_library.rs                    |   5 
crates/assistant/src/slash_command.rs                     |   6 
crates/assistant/src/slash_command/diagnostics_command.rs |   5 
crates/assistant/src/slash_command/tab_command.rs         |   6 
crates/collab_ui/src/chat_panel/message_editor.rs         |  12 
crates/collab_ui/src/collab_panel.rs                      | 104 +++-----
crates/collab_ui/src/collab_panel/channel_modal.rs        |   6 
crates/command_palette/src/command_palette.rs             |   6 
crates/editor/src/code_context_menus.rs                   |   4 
crates/editor/src/editor.rs                               |   2 
crates/extensions_ui/src/extension_version_selector.rs    |   8 
crates/extensions_ui/src/extensions_ui.rs                 |   6 
crates/file_finder/src/open_path_prompt.rs                |   2 
crates/fuzzy/src/strings.rs                               |  26 +-
crates/indexed_docs/src/store.rs                          |   2 
crates/language/src/outline.rs                            |   4 
crates/language_selector/src/language_selector.rs         |   2 
crates/outline_panel/src/outline_panel.rs                 |  32 +-
crates/project_symbols/src/project_symbols.rs             |   4 
crates/recent_projects/src/recent_projects.rs             |   2 
crates/snippets_ui/src/snippets_ui.rs                     |   2 
crates/storybook/src/stories/picker.rs                    |   6 
crates/tasks_ui/src/modal.rs                              |   2 
crates/theme_selector/src/theme_selector.rs               |   6 
crates/toolchain_selector/src/toolchain_selector.rs       |   2 
crates/vcs_menu/src/lib.rs                                |   6 
crates/welcome/src/base_keymap_picker.rs                  |   6 
28 files changed, 92 insertions(+), 184 deletions(-)

Detailed changes

crates/assistant/src/context_store.rs 🔗

@@ -716,7 +716,7 @@ impl ContextStore {
                 let candidates = metadata
                     .iter()
                     .enumerate()
-                    .map(|(id, metadata)| StringMatchCandidate::new(id, metadata.title.clone()))
+                    .map(|(id, metadata)| StringMatchCandidate::new(id, &metadata.title))
                     .collect::<Vec<_>>();
                 let matches = fuzzy::match_strings(
                     &candidates,

crates/assistant/src/prompt_library.rs 🔗

@@ -1439,10 +1439,7 @@ impl PromptStore {
                     .iter()
                     .enumerate()
                     .filter_map(|(ix, metadata)| {
-                        Some(StringMatchCandidate::new(
-                            ix,
-                            metadata.title.as_ref()?.to_string(),
-                        ))
+                        Some(StringMatchCandidate::new(ix, metadata.title.as_ref()?))
                     })
                     .collect::<Vec<_>>();
                 let matches = fuzzy::match_strings(

crates/assistant/src/slash_command.rs 🔗

@@ -78,11 +78,7 @@ impl SlashCommandCompletionProvider {
             .command_names(cx)
             .into_iter()
             .enumerate()
-            .map(|(ix, def)| StringMatchCandidate {
-                id: ix,
-                string: def.to_string(),
-                char_bag: def.as_ref().into(),
-            })
+            .map(|(ix, def)| StringMatchCandidate::new(ix, &def))
             .collect::<Vec<_>>();
         let command_name = command_name.to_string();
         let editor = self.editor.clone();

crates/assistant/src/slash_command/diagnostics_command.rs 🔗

@@ -218,10 +218,7 @@ impl Options {
     }
 
     fn match_candidates_for_args() -> [StringMatchCandidate; 1] {
-        [StringMatchCandidate::new(
-            0,
-            INCLUDE_WARNINGS_ARGUMENT.to_string(),
-        )]
+        [StringMatchCandidate::new(0, INCLUDE_WARNINGS_ARGUMENT)]
     }
 }
 

crates/assistant/src/slash_command/tab_command.rs 🔗

@@ -249,11 +249,7 @@ fn tab_items_for_queries(
                         .enumerate()
                         .filter_map(|(id, (full_path, ..))| {
                             let path_string = full_path.as_deref()?.to_string_lossy().to_string();
-                            Some(fuzzy::StringMatchCandidate {
-                                id,
-                                char_bag: path_string.as_str().into(),
-                                string: path_string,
-                            })
+                            Some(fuzzy::StringMatchCandidate::new(id, &path_string))
                         })
                         .collect::<Vec<_>>();
                     let mut processed_matches = HashSet::default();

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -381,11 +381,7 @@ impl MessageEditor {
 
         let candidates = names
             .into_iter()
-            .map(|user| StringMatchCandidate {
-                id: 0,
-                string: user.clone(),
-                char_bag: user.chars().collect(),
-            })
+            .map(|user| StringMatchCandidate::new(0, &user))
             .collect::<Vec<_>>();
 
         Some((start_anchor, query, candidates))
@@ -401,11 +397,7 @@ impl MessageEditor {
             LazyLock::new(|| {
                 let emojis = emojis::iter()
                     .flat_map(|s| s.shortcodes())
-                    .map(|emoji| StringMatchCandidate {
-                        id: 0,
-                        string: emoji.to_string(),
-                        char_bag: emoji.chars().collect(),
-                    })
+                    .map(|emoji| StringMatchCandidate::new(0, emoji))
                     .collect::<Vec<_>>();
                 emojis
             });

crates/collab_ui/src/collab_panel.rs 🔗

@@ -393,11 +393,8 @@ impl CollabPanel {
                 // Populate the active user.
                 if let Some(user) = user_store.current_user() {
                     self.match_candidates.clear();
-                    self.match_candidates.push(StringMatchCandidate {
-                        id: 0,
-                        string: user.github_login.clone(),
-                        char_bag: user.github_login.chars().collect(),
-                    });
+                    self.match_candidates
+                        .push(StringMatchCandidate::new(0, &user.github_login));
                     let matches = executor.block(match_strings(
                         &self.match_candidates,
                         &query,
@@ -436,11 +433,10 @@ impl CollabPanel {
                 self.match_candidates.clear();
                 self.match_candidates
                     .extend(room.remote_participants().values().map(|participant| {
-                        StringMatchCandidate {
-                            id: participant.user.id as usize,
-                            string: participant.user.github_login.clone(),
-                            char_bag: participant.user.github_login.chars().collect(),
-                        }
+                        StringMatchCandidate::new(
+                            participant.user.id as usize,
+                            &participant.user.github_login,
+                        )
                     }));
                 let mut matches = executor.block(match_strings(
                     &self.match_candidates,
@@ -489,10 +485,8 @@ impl CollabPanel {
                 self.match_candidates.clear();
                 self.match_candidates
                     .extend(room.pending_participants().iter().enumerate().map(
-                        |(id, participant)| StringMatchCandidate {
-                            id,
-                            string: participant.github_login.clone(),
-                            char_bag: participant.github_login.chars().collect(),
+                        |(id, participant)| {
+                            StringMatchCandidate::new(id, &participant.github_login)
                         },
                     ));
                 let matches = executor.block(match_strings(
@@ -519,17 +513,12 @@ impl CollabPanel {
 
         if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
             self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    channel_store
-                        .ordered_channels()
-                        .enumerate()
-                        .map(|(ix, (_, channel))| StringMatchCandidate {
-                            id: ix,
-                            string: channel.name.clone().into(),
-                            char_bag: channel.name.chars().collect(),
-                        }),
-                );
+            self.match_candidates.extend(
+                channel_store
+                    .ordered_channels()
+                    .enumerate()
+                    .map(|(ix, (_, channel))| StringMatchCandidate::new(ix, &channel.name)),
+            );
             let matches = executor.block(match_strings(
                 &self.match_candidates,
                 &query,
@@ -600,14 +589,12 @@ impl CollabPanel {
         let channel_invites = channel_store.channel_invitations();
         if !channel_invites.is_empty() {
             self.match_candidates.clear();
-            self.match_candidates
-                .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
-                    StringMatchCandidate {
-                        id: ix,
-                        string: channel.name.clone().into(),
-                        char_bag: channel.name.chars().collect(),
-                    }
-                }));
+            self.match_candidates.extend(
+                channel_invites
+                    .iter()
+                    .enumerate()
+                    .map(|(ix, channel)| StringMatchCandidate::new(ix, &channel.name)),
+            );
             let matches = executor.block(match_strings(
                 &self.match_candidates,
                 &query,
@@ -637,17 +624,12 @@ impl CollabPanel {
         let incoming = user_store.incoming_contact_requests();
         if !incoming.is_empty() {
             self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    incoming
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, user)| StringMatchCandidate {
-                            id: ix,
-                            string: user.github_login.clone(),
-                            char_bag: user.github_login.chars().collect(),
-                        }),
-                );
+            self.match_candidates.extend(
+                incoming
+                    .iter()
+                    .enumerate()
+                    .map(|(ix, user)| StringMatchCandidate::new(ix, &user.github_login)),
+            );
             let matches = executor.block(match_strings(
                 &self.match_candidates,
                 &query,
@@ -666,17 +648,12 @@ impl CollabPanel {
         let outgoing = user_store.outgoing_contact_requests();
         if !outgoing.is_empty() {
             self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    outgoing
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, user)| StringMatchCandidate {
-                            id: ix,
-                            string: user.github_login.clone(),
-                            char_bag: user.github_login.chars().collect(),
-                        }),
-                );
+            self.match_candidates.extend(
+                outgoing
+                    .iter()
+                    .enumerate()
+                    .map(|(ix, user)| StringMatchCandidate::new(ix, &user.github_login)),
+            );
             let matches = executor.block(match_strings(
                 &self.match_candidates,
                 &query,
@@ -703,17 +680,12 @@ impl CollabPanel {
         let contacts = user_store.contacts();
         if !contacts.is_empty() {
             self.match_candidates.clear();
-            self.match_candidates
-                .extend(
-                    contacts
-                        .iter()
-                        .enumerate()
-                        .map(|(ix, contact)| StringMatchCandidate {
-                            id: ix,
-                            string: contact.user.github_login.clone(),
-                            char_bag: contact.user.github_login.chars().collect(),
-                        }),
-                );
+            self.match_candidates.extend(
+                contacts
+                    .iter()
+                    .enumerate()
+                    .map(|(ix, contact)| StringMatchCandidate::new(ix, &contact.user.github_login)),
+            );
 
             let matches = executor.block(match_strings(
                 &self.match_candidates,

crates/collab_ui/src/collab_panel/channel_modal.rs 🔗

@@ -272,11 +272,7 @@ impl PickerDelegate for ChannelModalDelegate {
                     self.match_candidates.clear();
                     self.match_candidates
                         .extend(self.members.iter().enumerate().map(|(id, member)| {
-                            StringMatchCandidate {
-                                id,
-                                string: member.user.github_login.clone(),
-                                char_bag: member.user.github_login.chars().collect(),
-                            }
+                            StringMatchCandidate::new(id, &member.user.github_login)
                         }));
 
                     let matches = cx.background_executor().block(match_strings(

crates/command_palette/src/command_palette.rs 🔗

@@ -283,11 +283,7 @@ impl PickerDelegate for CommandPaletteDelegate {
                 let candidates = commands
                     .iter()
                     .enumerate()
-                    .map(|(ix, command)| StringMatchCandidate {
-                        id: ix,
-                        string: command.name.to_string(),
-                        char_bag: command.name.chars().collect(),
-                    })
+                    .map(|(ix, command)| StringMatchCandidate::new(ix, &command.name))
                     .collect::<Vec<_>>();
                 let matches = if query.is_empty() {
                     candidates

crates/editor/src/code_context_menus.rs 🔗

@@ -163,7 +163,7 @@ impl CompletionsMenu {
             .map(|(id, completion)| {
                 StringMatchCandidate::new(
                     id,
-                    completion.label.text[completion.label.filter_range.clone()].into(),
+                    &completion.label.text[completion.label.filter_range.clone()],
                 )
             })
             .collect();
@@ -211,7 +211,7 @@ impl CompletionsMenu {
         let match_candidates = choices
             .iter()
             .enumerate()
-            .map(|(id, completion)| StringMatchCandidate::new(id, completion.to_string()))
+            .map(|(id, completion)| StringMatchCandidate::new(id, &completion))
             .collect();
         let matches = choices
             .iter()

crates/editor/src/editor.rs 🔗

@@ -13293,7 +13293,7 @@ fn snippet_completions(
                 snippet
                     .prefix
                     .iter()
-                    .map(move |prefix| StringMatchCandidate::new(ix, prefix.clone()))
+                    .map(move |prefix| StringMatchCandidate::new(ix, &prefix))
             })
             .collect::<Vec<StringMatchCandidate>>();
 

crates/extensions_ui/src/extension_version_selector.rs 🔗

@@ -113,13 +113,7 @@ impl PickerDelegate for ExtensionVersionSelectorDelegate {
             .iter()
             .enumerate()
             .map(|(id, extension)| {
-                let text = format!("v{}", extension.manifest.version);
-
-                StringMatchCandidate {
-                    id,
-                    char_bag: text.as_str().into(),
-                    string: text,
-                }
+                StringMatchCandidate::new(id, &format!("v{}", extension.manifest.version))
             })
             .collect::<Vec<_>>();
 

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -328,11 +328,7 @@ impl ExtensionsPage {
                 let match_candidates = dev_extensions
                     .iter()
                     .enumerate()
-                    .map(|(ix, manifest)| StringMatchCandidate {
-                        id: ix,
-                        string: manifest.name.clone(),
-                        char_bag: manifest.name.as_str().into(),
-                    })
+                    .map(|(ix, manifest)| StringMatchCandidate::new(ix, &manifest.name))
                     .collect::<Vec<_>>();
 
                 let matches = match_strings(

crates/file_finder/src/open_path_prompt.rs 🔗

@@ -131,7 +131,7 @@ impl PickerDelegate for OpenPathDelegate {
                                 .iter()
                                 .enumerate()
                                 .map(|(ix, path)| {
-                                    StringMatchCandidate::new(ix, path.to_string_lossy().into())
+                                    StringMatchCandidate::new(ix, &path.to_string_lossy())
                                 })
                                 .collect::<Vec<_>>();
 

crates/fuzzy/src/strings.rs 🔗

@@ -18,22 +18,12 @@ pub struct StringMatchCandidate {
     pub char_bag: CharBag,
 }
 
-impl Match for StringMatch {
-    fn score(&self) -> f64 {
-        self.score
-    }
-
-    fn set_positions(&mut self, positions: Vec<usize>) {
-        self.positions = positions;
-    }
-}
-
 impl StringMatchCandidate {
-    pub fn new(id: usize, string: String) -> Self {
+    pub fn new(id: usize, string: &str) -> Self {
         Self {
             id,
-            char_bag: CharBag::from(string.as_str()),
-            string,
+            string: string.into(),
+            char_bag: string.into(),
         }
     }
 }
@@ -56,6 +46,16 @@ pub struct StringMatch {
     pub string: String,
 }
 
+impl Match for StringMatch {
+    fn score(&self) -> f64 {
+        self.score
+    }
+
+    fn set_positions(&mut self, positions: Vec<usize>) {
+        self.positions = positions;
+    }
+}
+
 impl StringMatch {
     pub fn ranges(&self) -> impl '_ + Iterator<Item = Range<usize>> {
         let mut positions = self.positions.iter().peekable();

crates/indexed_docs/src/store.rs 🔗

@@ -208,7 +208,7 @@ impl IndexedDocsStore {
             let candidates = items
                 .iter()
                 .enumerate()
-                .map(|(ix, item_path)| StringMatchCandidate::new(ix, item_path.clone()))
+                .map(|(ix, item_path)| StringMatchCandidate::new(ix, &item_path))
                 .collect::<Vec<_>>();
 
             let matches = fuzzy::match_strings(

crates/language/src/outline.rs 🔗

@@ -73,8 +73,8 @@ impl<T> Outline<T> {
                 .map(|range| &item.text[range.start..range.end])
                 .collect::<String>();
 
-            path_candidates.push(StringMatchCandidate::new(id, path_text.clone()));
-            candidates.push(StringMatchCandidate::new(id, candidate_text));
+            path_candidates.push(StringMatchCandidate::new(id, &path_text));
+            candidates.push(StringMatchCandidate::new(id, &candidate_text));
         }
 
         Self {

crates/language_selector/src/language_selector.rs 🔗

@@ -112,7 +112,7 @@ impl LanguageSelectorDelegate {
                     .then_some(name)
             })
             .enumerate()
-            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
+            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, &name))
             .collect::<Vec<_>>();
 
         Self {

crates/outline_panel/src/outline_panel.rs 🔗

@@ -3296,40 +3296,32 @@ impl OutlinePanel {
                     if let Some(file_name) =
                         self.relative_path(fs_entry, cx).as_deref().map(file_name)
                     {
-                        state.match_candidates.push(StringMatchCandidate {
-                            id,
-                            string: file_name.to_string(),
-                            char_bag: file_name.chars().collect(),
-                        });
+                        state
+                            .match_candidates
+                            .push(StringMatchCandidate::new(id, &file_name));
                     }
                 }
                 PanelEntry::FoldedDirs(worktree_id, entries) => {
                     let dir_names = self.dir_names_string(entries, *worktree_id, cx);
                     {
-                        state.match_candidates.push(StringMatchCandidate {
-                            id,
-                            string: dir_names.clone(),
-                            char_bag: dir_names.chars().collect(),
-                        });
+                        state
+                            .match_candidates
+                            .push(StringMatchCandidate::new(id, &dir_names));
                     }
                 }
                 PanelEntry::Outline(outline_entry) => match outline_entry {
                     OutlineEntry::Outline(_, _, outline) => {
-                        state.match_candidates.push(StringMatchCandidate {
-                            id,
-                            string: outline.text.clone(),
-                            char_bag: outline.text.chars().collect(),
-                        });
+                        state
+                            .match_candidates
+                            .push(StringMatchCandidate::new(id, &outline.text));
                     }
                     OutlineEntry::Excerpt(..) => {}
                 },
                 PanelEntry::Search(new_search_entry) => {
                     if let Some(search_data) = new_search_entry.render_data.get() {
-                        state.match_candidates.push(StringMatchCandidate {
-                            id,
-                            char_bag: search_data.context_text.chars().collect(),
-                            string: search_data.context_text.clone(),
-                        });
+                        state
+                            .match_candidates
+                            .push(StringMatchCandidate::new(id, &search_data.context_text));
                     }
                 }
             }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -179,7 +179,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
                         .map(|(id, symbol)| {
                             StringMatchCandidate::new(
                                 id,
-                                symbol.label.text[symbol.label.filter_range.clone()].to_string(),
+                                &symbol.label.text[symbol.label.filter_range.clone()],
                             )
                         })
                         .partition(|candidate| {
@@ -313,7 +313,7 @@ mod tests {
                     let candidates = fake_symbols
                         .iter()
                         .enumerate()
-                        .map(|(id, symbol)| StringMatchCandidate::new(id, symbol.name.clone()))
+                        .map(|(id, symbol)| StringMatchCandidate::new(id, &symbol.name))
                         .collect::<Vec<_>>();
                     let matches = if params.query.is_empty() {
                         Vec::new()

crates/recent_projects/src/recent_projects.rs 🔗

@@ -228,7 +228,7 @@ impl PickerDelegate for RecentProjectsDelegate {
                         .join(""),
                 };
 
-                StringMatchCandidate::new(id, combined_string)
+                StringMatchCandidate::new(id, &combined_string)
             })
             .collect::<Vec<_>>();
         self.matches = smol::block_on(fuzzy::match_strings(

crates/snippets_ui/src/snippets_ui.rs 🔗

@@ -96,7 +96,7 @@ impl ScopeSelectorDelegate {
         let candidates = candidates
             .chain(languages)
             .enumerate()
-            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, name))
+            .map(|(candidate_id, name)| StringMatchCandidate::new(candidate_id, &name))
             .collect::<Vec<_>>();
 
         Self {

crates/storybook/src/stories/picker.rs 🔗

@@ -22,11 +22,7 @@ impl Delegate {
                 .iter()
                 .copied()
                 .enumerate()
-                .map(|(id, string)| StringMatchCandidate {
-                    id,
-                    char_bag: string.into(),
-                    string: string.into(),
-                })
+                .map(|(id, string)| StringMatchCandidate::new(id, string))
                 .collect(),
             matches: vec![],
             selected_ix: 0,

crates/tasks_ui/src/modal.rs 🔗

@@ -516,7 +516,7 @@ fn string_match_candidates<'a>(
         .map(|(index, (_, candidate))| StringMatchCandidate {
             id: index,
             char_bag: candidate.resolved_label.chars().collect(),
-            string: candidate.display_label().to_owned(),
+            string: candidate.display_label().into(),
         })
         .collect()
 }

crates/theme_selector/src/theme_selector.rs 🔗

@@ -230,11 +230,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
             .themes
             .iter()
             .enumerate()
-            .map(|(id, meta)| StringMatchCandidate {
-                id,
-                char_bag: meta.name.as_ref().into(),
-                string: meta.name.to_string(),
-            })
+            .map(|(id, meta)| StringMatchCandidate::new(id, &meta.name))
             .collect::<Vec<_>>();
 
         cx.spawn(|this, mut cx| async move {

crates/toolchain_selector/src/toolchain_selector.rs 🔗

@@ -296,7 +296,7 @@ impl PickerDelegate for ToolchainSelectorDelegate {
                     .map(|(candidate_id, toolchain)| {
                         let path = Self::relativize_path(toolchain.path, &worktree_root_path);
                         let string = format!("{}{}", toolchain.name, path);
-                        StringMatchCandidate::new(candidate_id, string)
+                        StringMatchCandidate::new(candidate_id, &string)
                     })
                     .collect::<Vec<_>>();
                 match_strings(

crates/vcs_menu/src/lib.rs 🔗

@@ -172,11 +172,7 @@ impl PickerDelegate for BranchListDelegate {
                 branches
                     .into_iter()
                     .enumerate()
-                    .map(|(ix, command)| StringMatchCandidate {
-                        id: ix,
-                        char_bag: command.name.chars().collect(),
-                        string: command.name.into(),
-                    })
+                    .map(|(ix, command)| StringMatchCandidate::new(ix, &command.name))
                     .collect::<Vec<StringMatchCandidate>>()
             });
             let Some(candidates) = candidates.log_err() else {

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -127,11 +127,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
         let background = cx.background_executor().clone();
         let candidates = BaseKeymap::names()
             .enumerate()
-            .map(|(id, name)| StringMatchCandidate {
-                id,
-                char_bag: name.into(),
-                string: name.into(),
-            })
+            .map(|(id, name)| StringMatchCandidate::new(id, name))
             .collect::<Vec<_>>();
 
         cx.spawn(|this, mut cx| async move {