mirror of https://github.com/helix-editor/helix
Skip enclosed pairs in surround
Surround operations previously ignored other pairs that are enclosed within which should be skipped. For example if the cursor is on the `,` in `{{a},{b}}`, doing `md{` previously would delete the `{` on the left of `a` and `}` on the right of `b` instead of the outermost braces. This commit corrects this behavior.pull/345/head
parent
fb8e7dc25b
commit
394629ab73
|
@ -39,13 +39,101 @@ pub fn find_nth_pairs_pos(
|
||||||
n: usize,
|
n: usize,
|
||||||
) -> Option<(usize, usize)> {
|
) -> Option<(usize, usize)> {
|
||||||
let (open, close) = get_pair(ch);
|
let (open, close) = get_pair(ch);
|
||||||
// find_nth* do not consider current character; +1/-1 to include them
|
|
||||||
let open_pos = search::find_nth_prev(text, open, pos + 1, n, true)?;
|
let (open_pos, close_pos) = if open == close {
|
||||||
let close_pos = search::find_nth_next(text, close, pos - 1, n, true)?;
|
// find_nth* do not consider current character; +1/-1 to include them
|
||||||
|
(
|
||||||
|
search::find_nth_prev(text, open, pos + 1, n, true)?,
|
||||||
|
search::find_nth_next(text, close, pos - 1, n, true)?,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
find_nth_open_pair(text, open, close, pos, n)?,
|
||||||
|
find_nth_close_pair(text, open, close, pos, n)?,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Some((open_pos, close_pos))
|
Some((open_pos, close_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_nth_open_pair(
|
||||||
|
text: RopeSlice,
|
||||||
|
open: char,
|
||||||
|
close: char,
|
||||||
|
mut pos: usize,
|
||||||
|
n: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let mut chars = text.chars_at(pos + 1);
|
||||||
|
|
||||||
|
// Adjusts pos for the first iteration, and handles the case of the
|
||||||
|
// cursor being *on* the close character which will get falsely stepped over
|
||||||
|
// if not skipped here
|
||||||
|
if chars.prev()? == open {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..n {
|
||||||
|
let mut step_over: usize = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let c = chars.prev()?;
|
||||||
|
pos = pos.saturating_sub(1);
|
||||||
|
|
||||||
|
// ignore other surround pairs that are enclosed *within* our search scope
|
||||||
|
if c == close {
|
||||||
|
step_over += 1;
|
||||||
|
} else if c == open {
|
||||||
|
if step_over == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
step_over = step_over.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_nth_close_pair(
|
||||||
|
text: RopeSlice,
|
||||||
|
open: char,
|
||||||
|
close: char,
|
||||||
|
mut pos: usize,
|
||||||
|
n: usize,
|
||||||
|
) -> Option<usize> {
|
||||||
|
if pos >= text.len_chars() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut chars = text.chars_at(pos);
|
||||||
|
|
||||||
|
if chars.next()? == close {
|
||||||
|
return Some(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
for _ in 0..n {
|
||||||
|
let mut step_over: usize = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let c = chars.next()?;
|
||||||
|
pos += 1;
|
||||||
|
|
||||||
|
if c == open {
|
||||||
|
step_over += 1;
|
||||||
|
} else if c == close {
|
||||||
|
if step_over == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
step_over = step_over.saturating_sub(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(pos)
|
||||||
|
}
|
||||||
|
|
||||||
/// Find position of surround characters around every cursor. Returns None
|
/// Find position of surround characters around every cursor. Returns None
|
||||||
/// if any positions overlap. Note that the positions are in a flat Vec.
|
/// if any positions overlap. Note that the positions are in a flat Vec.
|
||||||
/// Use get_surround_pos().chunks(2) to get matching pairs of surround positions.
|
/// Use get_surround_pos().chunks(2) to get matching pairs of surround positions.
|
||||||
|
@ -101,6 +189,27 @@ mod test {
|
||||||
assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
|
assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_nth_pairs_pos_same() {
|
||||||
|
let doc = Rope::from("'so 'many 'good' text' here'");
|
||||||
|
let slice = doc.slice(..);
|
||||||
|
|
||||||
|
// cursor on go[o]d
|
||||||
|
assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
|
||||||
|
assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21)));
|
||||||
|
assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_find_nth_pairs_pos_step() {
|
||||||
|
let doc = Rope::from("((so)((many) good (text))(here))");
|
||||||
|
let slice = doc.slice(..);
|
||||||
|
|
||||||
|
// cursor on go[o]d
|
||||||
|
assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
|
||||||
|
assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31)));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_find_nth_pairs_pos_mixed() {
|
fn test_find_nth_pairs_pos_mixed() {
|
||||||
let doc = Rope::from("(so [many {good} text] here)");
|
let doc = Rope::from("(so [many {good} text] here)");
|
||||||
|
|
Loading…
Reference in New Issue