Support newline and tab literals in regex search-and-replace operations (#9609)

Closes #7645

Release Notes:

- Added support for inserting newlines (`\n`) and tabs (`\t`) in editor
Regex search replacements
([#7645](https://github.com/zed-industries/zed/issues/7645)).
This commit is contained in:
Mayfield 2024-03-25 07:21:04 -04:00 committed by GitHub
parent eb3264c0ad
commit 4785520d99
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 122 additions and 2 deletions

View File

@ -3,17 +3,19 @@ use anyhow::{Context, Result};
use client::proto; use client::proto;
use itertools::Itertools; use itertools::Itertools;
use language::{char_kind, BufferSnapshot}; use language::{char_kind, BufferSnapshot};
use regex::{Regex, RegexBuilder}; use regex::{Captures, Regex, RegexBuilder};
use smol::future::yield_now; use smol::future::yield_now;
use std::{ use std::{
borrow::Cow, borrow::Cow,
io::{BufRead, BufReader, Read}, io::{BufRead, BufReader, Read},
ops::Range, ops::Range,
path::Path, path::Path,
sync::Arc, sync::{Arc, OnceLock},
}; };
use util::paths::PathMatcher; use util::paths::PathMatcher;
static TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX: OnceLock<Regex> = OnceLock::new();
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SearchInputs { pub struct SearchInputs {
query: Arc<str>, query: Arc<str>,
@ -231,6 +233,16 @@ impl SearchQuery {
regex, replacement, .. regex, replacement, ..
} => { } => {
if let Some(replacement) = replacement { if let Some(replacement) = replacement {
let replacement = TEXT_REPLACEMENT_SPECIAL_CHARACTERS_REGEX
.get_or_init(|| Regex::new(r"\\\\|\\n|\\t").unwrap())
.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)) Some(regex.replace(text, replacement))
} else { } else {
None None

View File

@ -1918,6 +1918,114 @@ mod tests {
); );
} }
struct ReplacementTestParams<'a> {
editor: &'a View<Editor>,
search_bar: &'a View<BufferSearchBar>,
cx: &'a mut VisualTestContext,
search_mode: SearchMode,
search_text: &'static str,
search_options: Option<SearchOptions>,
replacement_text: &'static str,
replace_all: bool,
expected_text: String,
}
async fn run_replacement_test(options: ReplacementTestParams<'_>) {
options
.search_bar
.update(options.cx, |search_bar, cx| {
search_bar.activate_search_mode(options.search_mode, cx);
search_bar.search(options.search_text, options.search_options, cx)
})
.await
.unwrap();
options.search_bar.update(options.cx, |search_bar, cx| {
search_bar.replacement_editor.update(cx, |editor, cx| {
editor.set_text(options.replacement_text, cx);
});
if options.replace_all {
search_bar.replace_all(&ReplaceAll, cx)
} else {
search_bar.replace_next(&ReplaceNext, cx)
}
});
assert_eq!(
options
.editor
.update(options.cx, |this, cx| { this.text(cx) }),
options.expected_text
);
}
#[gpui::test]
async fn test_replace_special_characters(cx: &mut TestAppContext) {
let (editor, search_bar, cx) = init_test(cx);
run_replacement_test(ReplacementTestParams {
editor: &editor,
search_bar: &search_bar,
cx,
search_mode: SearchMode::Text,
search_text: "expression",
search_options: None,
replacement_text: r"\n",
replace_all: true,
expected_text: r#"
A regular \n (shortened as regex or regexp;[1] also referred to as
rational \n[2][3]) is a sequence of characters that specifies a search
pattern in text. Usually such patterns are used by string-searching algorithms
for "find" or "find and replace" operations on strings, or for input validation.
"#
.unindent(),
})
.await;
run_replacement_test(ReplacementTestParams {
editor: &editor,
search_bar: &search_bar,
cx,
search_mode: SearchMode::Regex,
search_text: "or",
search_options: Some(SearchOptions::WHOLE_WORD),
replacement_text: r"\\\n\\\\",
replace_all: false,
expected_text: r#"
A regular \n (shortened as regex \
\\ regexp;[1] also referred to as
rational \n[2][3]) is a sequence of characters that specifies a search
pattern in text. Usually such patterns are used by string-searching algorithms
for "find" or "find and replace" operations on strings, or for input validation.
"#
.unindent(),
})
.await;
run_replacement_test(ReplacementTestParams {
editor: &editor,
search_bar: &search_bar,
cx,
search_mode: SearchMode::Regex,
search_text: r"(that|used) ",
search_options: None,
replacement_text: r"$1\n",
replace_all: true,
expected_text: r#"
A regular \n (shortened as regex \
\\ regexp;[1] also referred to as
rational \n[2][3]) is a sequence of characters that
specifies a search
pattern in text. Usually such patterns are used
by string-searching algorithms
for "find" or "find and replace" operations on strings, or for input validation.
"#
.unindent(),
})
.await;
}
#[gpui::test] #[gpui::test]
async fn test_invalid_regexp_search_after_valid(cx: &mut TestAppContext) { async fn test_invalid_regexp_search_after_valid(cx: &mut TestAppContext) {
let (editor, search_bar, cx) = init_test(cx); let (editor, search_bar, cx) = init_test(cx);