diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index 83b4c585f14..27f7e18ecc7 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -78,6 +78,7 @@ pub enum SearchQuery { include_ignored: bool, one_match_per_line: bool, inner: SearchInputs, + escaped: bool, }, } @@ -169,6 +170,7 @@ impl SearchQuery { include_ignored, one_match_per_line, inner, + false, ) } @@ -202,6 +204,7 @@ impl SearchQuery { include_ignored, false, inner, + true, ) } @@ -212,6 +215,7 @@ impl SearchQuery { include_ignored: bool, one_match_per_line: bool, inner: SearchInputs, + escaped: bool, ) -> Result { if let Some((case_sensitive_from_pattern, new_pattern)) = Self::case_sensitive_from_pattern(&pattern) @@ -253,6 +257,7 @@ impl SearchQuery { include_ignored, inner, one_match_per_line, + escaped, }) } @@ -450,27 +455,36 @@ impl SearchQuery { /// Replaces search hits if replacement is set. `text` is assumed to be a string that matches this `SearchQuery` exactly, without any leftovers on either side. pub fn replacement_for<'a>(&self, text: &'a str) -> Option> { match self { - SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from), + SearchQuery::Text { replacement, .. } + | SearchQuery::Regex { + replacement, + escaped: true, + .. + } => replacement.clone().map(Cow::from), + SearchQuery::Regex { - regex, replacement, .. + regex, + replacement: Some(replacement), + escaped: false, + .. } => { - if let Some(replacement) = replacement { - static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: LazyLock = - LazyLock::new(|| Regex::new(r"\\\\|\\n|\\t").unwrap()); - let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX.replace_all( - replacement, - |c: &Captures| match c.get(0).unwrap().as_str() { - r"\\" => "\\", - r"\n" => "\n", - r"\t" => "\t", - x => unreachable!("Unexpected escape sequence: {}", x), - }, - ); - Some(regex.replace(text, replacement)) - } else { - None - } + static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: LazyLock = + LazyLock::new(|| Regex::new(r"\\\\|\\n|\\t").unwrap()); + let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX.replace_all( + replacement, + |c: &Captures| match c.get(0).unwrap().as_str() { + r"\\" => "\\", + r"\n" => "\n", + r"\t" => "\t", + x => unreachable!("Unexpected escape sequence: {}", x), + }, + ); + Some(regex.replace(text, replacement)) } + + SearchQuery::Regex { + replacement: None, .. + } => None, } } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index ea03a1a7f9f..07c91d7659d 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -4036,6 +4036,34 @@ mod tests { }); } + #[gpui::test] + async fn test_replace_with_non_ascii_characters(cx: &mut TestAppContext) { + let (editor, search_bar, cx) = init_test(cx); + + editor.update_in(cx, |editor, window, cx| { + editor.set_text("¥100 ¥200 ¥100", window, cx) + }); + + search_bar + .update_in(cx, |search_bar, window, cx| { + search_bar.search("¥", None, true, window, cx) + }) + .await + .unwrap(); + + search_bar.update_in(cx, |search_bar, window, cx| { + search_bar.replacement_editor.update(cx, |editor, cx| { + editor.set_text("\\n", window, cx); + }); + search_bar.replace_all(&ReplaceAll, window, cx) + }); + + assert_eq!( + editor.read_with(cx, |this, cx| this.text(cx)), + "\\n100 \\n200 \\n100" + ); + } + fn update_search_settings(search_settings: SearchSettings, cx: &mut TestAppContext) { cx.update(|cx| { SettingsStore::update_global(cx, |store, cx| {