write to search registers when selecting by regex

pull/8088/head
Jesse Luehrs 2025-01-18 02:04:13 -05:00
parent 3318953bf6
commit c5a4fa78aa
3 changed files with 70 additions and 20 deletions

View File

@ -783,13 +783,15 @@ pub fn select_on_matches(
text: RopeSlice,
selection: &Selection,
regex: &rope::Regex,
) -> Option<Selection> {
) -> Option<(Selection, Vec<Vec<String>>)> {
let mut result = SmallVec::with_capacity(selection.len());
let mut captures = vec![vec![]; regex.captures_len()];
for sel in selection {
for mat in regex.find_iter(text.regex_input_at(sel.from()..sel.to())) {
for cap in regex.captures_iter(text.regex_input_at(sel.from()..sel.to())) {
// TODO: retain range direction
let mat = cap.get_match().unwrap();
let start = text.byte_to_char(mat.start());
let end = text.byte_to_char(mat.end());
@ -798,13 +800,20 @@ pub fn select_on_matches(
// These invalid matches can come from using RegEx anchors like `^`, `$`
if range != Range::point(sel.to()) {
result.push(range);
for (i, captures) in captures.iter_mut().enumerate() {
captures.push(
cap.get_group(i)
.map(|group| text.slice(group.range()).to_string())
.unwrap_or_default(),
);
}
}
}
}
// TODO: figure out a new primary index
if !result.is_empty() {
return Some(Selection::new(result, 0));
return Some((Selection::new(result, 0), captures));
}
None
@ -1099,9 +1108,9 @@ mod test {
let selection = Selection::single(0, r.len_chars());
assert_eq!(
select_on_matches(s, &selection, &rope::Regex::new(r"[A-Z][a-z]*").unwrap()),
Some(Selection::new(
smallvec![Range::new(0, 6), Range::new(19, 26)],
0
Some((
Selection::new(smallvec![Range::new(0, 6), Range::new(19, 26)], 0),
vec![vec!["Nobody".to_string(), "Spanish".to_string()]]
))
);
@ -1120,7 +1129,7 @@ mod test {
// line without ending
assert_eq!(
select_on_matches(s, &Selection::single(0, 4), &start_of_line),
Some(Selection::single(0, 0))
Some((Selection::single(0, 0), vec![vec!["".to_string()]]))
);
assert_eq!(
select_on_matches(s, &Selection::single(0, 4), &end_of_line),
@ -1129,23 +1138,23 @@ mod test {
// line with ending
assert_eq!(
select_on_matches(s, &Selection::single(0, 5), &start_of_line),
Some(Selection::single(0, 0))
Some((Selection::single(0, 0), vec![vec!["".to_string()]]))
);
assert_eq!(
select_on_matches(s, &Selection::single(0, 5), &end_of_line),
Some(Selection::single(4, 4))
Some((Selection::single(4, 4), vec![vec!["".to_string()]]))
);
// line with start of next line
assert_eq!(
select_on_matches(s, &Selection::single(0, 6), &start_of_line),
Some(Selection::new(
smallvec![Range::point(0), Range::point(5)],
0
Some((
Selection::new(smallvec![Range::point(0), Range::point(5)], 0),
vec![vec!["".to_string(), "".to_string()]]
))
);
assert_eq!(
select_on_matches(s, &Selection::single(0, 6), &end_of_line),
Some(Selection::single(4, 4))
Some((Selection::single(4, 4), vec![vec!["".to_string()]]))
);
// multiple lines
@ -1158,9 +1167,16 @@ mod test {
.build(r"^[a-z ]*$")
.unwrap()
),
Some(Selection::new(
Some((
Selection::new(
smallvec![Range::point(12), Range::new(13, 30), Range::new(31, 36)],
0
),
vec![vec![
"".to_string(),
"contains multiple".to_string(),
"lines".to_string()
]]
))
);
}

View File

@ -2012,10 +2012,11 @@ fn select_regex(cx: &mut Context) {
return;
}
let text = doc.text().slice(..);
if let Some(selection) =
if let Some((selection, captures)) =
selection::select_on_matches(text, doc.selection(view.id), &regex)
{
doc.set_selection(view.id, selection);
cx.editor.registers.write_search_results(captures);
} else {
cx.editor.set_error("nothing selected");
}

View File

@ -80,7 +80,9 @@ impl Registers {
pub fn write(&mut self, name: char, mut values: Vec<String>) -> Result<()> {
match name {
'_' => Ok(()),
'#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support writing")),
'#' | '.' | '%' | '&' | '1'..='9' => {
Err(anyhow::anyhow!("Register {name} does not support writing"))
}
'*' | '+' => {
self.clipboard_provider.load().set_contents(
&values.join(NATIVE_LINE_ENDING.as_str()),
@ -105,7 +107,9 @@ impl Registers {
pub fn push(&mut self, name: char, mut value: String) -> Result<()> {
match name {
'_' => Ok(()),
'#' | '.' | '%' => Err(anyhow::anyhow!("Register {name} does not support pushing")),
'#' | '.' | '%' | '&' | '1'..='9' => {
Err(anyhow::anyhow!("Register {name} does not support pushing"))
}
'*' | '+' => {
let clipboard_type = match name {
'+' => ClipboardType::Clipboard,
@ -140,6 +144,17 @@ impl Registers {
}
}
pub fn write_search_results(&mut self, search_results: Vec<Vec<String>>) {
let len = search_results.len();
for (i, mut values) in search_results.into_iter().enumerate() {
values.reverse();
self.inner.insert(search_register_name(i), values);
}
for i in len..=9 {
self.inner.remove(&search_register_name(i));
}
}
pub fn first<'a>(&'a self, name: char, editor: &'a Editor) -> Option<Cow<'a, str>> {
self.read(name, editor).and_then(|mut values| values.next())
}
@ -168,6 +183,16 @@ impl Registers {
('%', "<document path>"),
('+', "<system clipboard>"),
('*', "<primary clipboard>"),
('&', "<last regex match>"),
('1', "<last regex match group 1>"),
('2', "<last regex match group 2>"),
('3', "<last regex match group 3>"),
('4', "<last regex match group 4>"),
('5', "<last regex match group 5>"),
('6', "<last regex match group 6>"),
('7', "<last regex match group 7>"),
('8', "<last regex match group 8>"),
('9', "<last regex match group 9>"),
]
.iter()
.copied(),
@ -192,7 +217,7 @@ impl Registers {
true
}
'_' | '#' | '.' | '%' => false,
'_' | '#' | '.' | '%' | '&' | '1'..='9' => false,
_ => self.inner.remove(&name).is_some(),
}
}
@ -218,6 +243,14 @@ impl Registers {
}
}
fn search_register_name(i: usize) -> char {
if i == 0 {
'&'
} else {
char::from(i as u8 + b'0')
}
}
fn read_from_clipboard<'a>(
provider: &ClipboardProvider,
saved_values: Option<&'a Vec<String>>,