summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGokul Soumya2021-06-23 18:50:19 +0000
committerBlaž Hrastnik2021-06-24 04:02:56 +0000
commit394629ab73442ed07af210fbe2dddecd60b83300 (patch)
treef3a5323f3d0312a04213eb03afe8b1d7e71aa9e2
parentfb8e7dc25bbead56e012356c1f5f29162d0c9a17 (diff)
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.
-rw-r--r--helix-core/src/surround.rs115
1 files changed, 112 insertions, 3 deletions
diff --git a/helix-core/src/surround.rs b/helix-core/src/surround.rs
index d7314609..61981d6e 100644
--- a/helix-core/src/surround.rs
+++ b/helix-core/src/surround.rs
@@ -39,13 +39,101 @@ pub fn find_nth_pairs_pos(
n: usize,
) -> Option<(usize, usize)> {
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 close_pos = search::find_nth_next(text, close, pos - 1, n, true)?;
+
+ let (open_pos, close_pos) = if open == close {
+ // 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))
}
+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
/// 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.
@@ -102,6 +190,27 @@ mod test {
}
#[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]
fn test_find_nth_pairs_pos_mixed() {
let doc = Rope::from("(so [many {good} text] here)");
let slice = doc.slice(..);