Z 1200/replace in buffer (#2922)

This is still WIP, mostly pending styling. I added a pretty rudimentary
text field and no buttons whatsoever other than that. I am targeting a
Preview of 09.13, as I am gonna be on PTO for the next week.

I dislike the current implementation slightly because of `regex`'s crate
syntax and lack of support of backreferences. What strikes me as odd wrt
to syntax is that it will just replace a capture name with empty string
if that capture is missing from the regex. While this is perfectly fine
behaviour for conditionally-matched capture groups (e.g. `(foo)?`), I
think it should still error out if there's no group with a given name
(conditional or not).
Release Notes:

- Added "Replace" functionality to buffer search.
This commit is contained in:
Piotr Osiewicz 2023-09-12 18:46:54 +02:00 committed by GitHub
parent c545788168
commit 4cb8647702
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 471 additions and 96 deletions

View file

@ -7,6 +7,7 @@ use language::{char_kind, BufferSnapshot};
use regex::{Regex, RegexBuilder};
use smol::future::yield_now;
use std::{
borrow::Cow,
io::{BufRead, BufReader, Read},
ops::Range,
path::{Path, PathBuf},
@ -35,6 +36,7 @@ impl SearchInputs {
pub enum SearchQuery {
Text {
search: Arc<AhoCorasick<usize>>,
replacement: Option<String>,
whole_word: bool,
case_sensitive: bool,
inner: SearchInputs,
@ -42,7 +44,7 @@ pub enum SearchQuery {
Regex {
regex: Regex,
replacement: Option<String>,
multiline: bool,
whole_word: bool,
case_sensitive: bool,
@ -95,6 +97,7 @@ impl SearchQuery {
};
Self::Text {
search: Arc::new(search),
replacement: None,
whole_word,
case_sensitive,
inner,
@ -130,6 +133,7 @@ impl SearchQuery {
};
Ok(Self::Regex {
regex,
replacement: None,
multiline,
whole_word,
case_sensitive,
@ -156,7 +160,21 @@ impl SearchQuery {
))
}
}
pub fn with_replacement(mut self, new_replacement: Option<String>) -> Self {
match self {
Self::Text {
ref mut replacement,
..
}
| Self::Regex {
ref mut replacement,
..
} => {
*replacement = new_replacement;
self
}
}
}
pub fn to_proto(&self, project_id: u64) -> proto::SearchProject {
proto::SearchProject {
project_id,
@ -214,7 +232,20 @@ impl SearchQuery {
}
}
}
pub fn replacement<'a>(&self, text: &'a str) -> Option<Cow<'a, str>> {
match self {
SearchQuery::Text { replacement, .. } => replacement.clone().map(Cow::from),
SearchQuery::Regex {
regex, replacement, ..
} => {
if let Some(replacement) = replacement {
Some(regex.replace(text, replacement))
} else {
None
}
}
}
}
pub async fn search(
&self,
buffer: &BufferSnapshot,