Unnamed repository; edit this file 'description' to name the repository.
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.rs | 115 |
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(..); |