mirror of
https://github.com/zed-industries/zed.git
synced 2024-12-26 08:02:22 +03:00
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:
parent
eb3264c0ad
commit
4785520d99
@ -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
|
||||||
|
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user