aboutsummaryrefslogtreecommitdiff
path: root/helix-core/src/surround.rs
diff options
context:
space:
mode:
Diffstat (limited to 'helix-core/src/surround.rs')
-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(..);