mirror of
https://github.com/zed-industries/zed.git
synced 2026-08-26 17:42:21 +00:00
project: Treat replacement literally when dealing non-ASCII text search (#56123)
Self-Review Checklist:
- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content is consistent with the [UI/UX
checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable
Closes #55503
In #28752, to support case-insensitive search for non-ASCII queries, Zed
internally falls back to a regex search. However, this also affected the
replacement behavior, as the replacement code implemented a simple match
logic:
dccea211ed/crates/project/src/search.rs (L452-L457)
Since the regex fallback is an internal implementation detail (the user
never enabled regex mode), the replacement should behave the same as a
normal text replacement. This PR fixes that.
Release Notes:
- Fixed replacement text being treated as a regex pattern when
performing case-insensitive text search with non-ASCII characters.
This commit is contained in:
parent
9cc78ac691
commit
3fde4e285e
2 changed files with 60 additions and 18 deletions
|
|
@ -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<Self> {
|
||||
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<Cow<'a, str>> {
|
||||
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<Regex> =
|
||||
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<Regex> =
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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| {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue