Gracefully handle models searching for empty glob (#27370)

Richard Feldman created

Sometimes we've seen models provide an empty string for the path search
glob. This assumes they meant "*" when that happens.

Separately, this also removes an unnecessary `clone` of a `String`.

Release Notes:

- N/A

Change summary

crates/assistant_tools/src/edit_files_tool/replace.rs | 5 +++--
crates/assistant_tools/src/path_search_tool.rs        | 6 +++++-
crates/util/src/paths.rs                              | 6 +++---
3 files changed, 11 insertions(+), 6 deletions(-)

Detailed changes

crates/assistant_tools/src/edit_files_tool/replace.rs 🔗

@@ -1,5 +1,6 @@
 use language::{BufferSnapshot, Diff, Point, ToOffset};
 use project::search::SearchQuery;
+use std::iter;
 use util::{paths::PathMatcher, ResultExt as _};
 
 /// Performs an exact string replacement in a buffer, requiring precise character-for-character matching.
@@ -11,8 +12,8 @@ pub async fn replace_exact(old: &str, new: &str, snapshot: &BufferSnapshot) -> O
         false,
         true,
         true,
-        PathMatcher::new(&[]).ok()?,
-        PathMatcher::new(&[]).ok()?,
+        PathMatcher::new(iter::empty::<&str>()).ok()?,
+        PathMatcher::new(iter::empty::<&str>()).ok()?,
         None,
     )
     .log_err()?;

crates/assistant_tools/src/path_search_tool.rs 🔗

@@ -71,7 +71,11 @@ impl Tool for PathSearchTool {
             Ok(input) => (input.offset.unwrap_or(0), input.glob),
             Err(err) => return Task::ready(Err(anyhow!(err))),
         };
-        let path_matcher = match PathMatcher::new(&[glob.clone()]) {
+
+        let path_matcher = match PathMatcher::new([
+            // Sometimes models try to search for "". In this case, return all paths in the project.
+            if glob.is_empty() { "*" } else { &glob },
+        ]) {
             Ok(matcher) => matcher,
             Err(err) => return Task::ready(Err(anyhow!("Invalid glob: {err}"))),
         };

crates/util/src/paths.rs 🔗

@@ -377,10 +377,10 @@ impl PartialEq for PathMatcher {
 impl Eq for PathMatcher {}
 
 impl PathMatcher {
-    pub fn new(globs: &[String]) -> Result<Self, globset::Error> {
+    pub fn new(globs: impl IntoIterator<Item = impl AsRef<str>>) -> Result<Self, globset::Error> {
         let globs = globs
-            .iter()
-            .map(|glob| Glob::new(glob))
+            .into_iter()
+            .map(|as_str| Glob::new(as_str.as_ref()))
             .collect::<Result<Vec<_>, _>>()?;
         let sources = globs.iter().map(|glob| glob.glob().to_owned()).collect();
         let mut glob_builder = GlobSetBuilder::new();