on master help sign in
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.

commit394629ab73442ed07af210fbe2dddecd60b83300 authorGokul Soumya <gokulps15@gmail.com> dateThu 24 Jun 2021, 00:20 (5 years ago) parentfb8e7dc
1 file changed+112−3
helix-core/src/surround.rs+112−3
helix-core/src/surround.rs+112−3
@@ -39,13 +39,101 @@ pub fn find_nth_pairs_pos(
3939 n: usize,
4040) -> Option<(usize, usize)> {
4141 let (open, close) = get_pair(ch);
42− // find_nth* do not consider current character; +1/-1 to include them
43− let open_pos = search::find_nth_prev(text, open, pos + 1, n, true)?;
44− let close_pos = search::find_nth_next(text, close, pos - 1, n, true)?;
42+
43+ let (open_pos, close_pos) = if open == close {
44+ // find_nth* do not consider current character; +1/-1 to include them
45+ (
46+ search::find_nth_prev(text, open, pos + 1, n, true)?,
47+ search::find_nth_next(text, close, pos - 1, n, true)?,
48+ )
49+ } else {
50+ (
51+ find_nth_open_pair(text, open, close, pos, n)?,
52+ find_nth_close_pair(text, open, close, pos, n)?,
53+ )
54+ };
4555
4656 Some((open_pos, close_pos))
4757}
4858
59+fn find_nth_open_pair(
60+ text: RopeSlice,
61+ open: char,
62+ close: char,
63+ mut pos: usize,
64+ n: usize,
65+) -> Option<usize> {
66+ let mut chars = text.chars_at(pos + 1);
67+
68+ // Adjusts pos for the first iteration, and handles the case of the
69+ // cursor being *on* the close character which will get falsely stepped over
70+ // if not skipped here
71+ if chars.prev()? == open {
72+ return Some(pos);
73+ }
74+
75+ for _ in 0..n {
76+ let mut step_over: usize = 0;
77+
78+ loop {
79+ let c = chars.prev()?;
80+ pos = pos.saturating_sub(1);
81+
82+ // ignore other surround pairs that are enclosed *within* our search scope
83+ if c == close {
84+ step_over += 1;
85+ } else if c == open {
86+ if step_over == 0 {
87+ break;
88+ }
89+
90+ step_over = step_over.saturating_sub(1);
91+ }
92+ }
93+ }
94+
95+ Some(pos)
96+}
97+
98+fn find_nth_close_pair(
99+ text: RopeSlice,
100+ open: char,
101+ close: char,
102+ mut pos: usize,
103+ n: usize,
104+) -> Option<usize> {
105+ if pos >= text.len_chars() {
106+ return None;
107+ }
108+
109+ let mut chars = text.chars_at(pos);
110+
111+ if chars.next()? == close {
112+ return Some(pos);
113+ }
114+
115+ for _ in 0..n {
116+ let mut step_over: usize = 0;
117+
118+ loop {
119+ let c = chars.next()?;
120+ pos += 1;
121+
122+ if c == open {
123+ step_over += 1;
124+ } else if c == close {
125+ if step_over == 0 {
126+ break;
127+ }
128+
129+ step_over = step_over.saturating_sub(1);
130+ }
131+ }
132+ }
133+
134+ Some(pos)
135+}
136+
49137/// Find position of surround characters around every cursor. Returns None
50138/// if any positions overlap. Note that the positions are in a flat Vec.
51139/// Use get_surround_pos().chunks(2) to get matching pairs of surround positions.
@@ -101,6 +189,27 @@ mod test {
101189 assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
102190 }
103191
192+ #[test]
193+ fn test_find_nth_pairs_pos_same() {
194+ let doc = Rope::from("'so 'many 'good' text' here'");
195+ let slice = doc.slice(..);
196+
197+ // cursor on go[o]d
198+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
199+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21)));
200+ assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27)));
201+ }
202+
203+ #[test]
204+ fn test_find_nth_pairs_pos_step() {
205+ let doc = Rope::from("((so)((many) good (text))(here))");
206+ let slice = doc.slice(..);
207+
208+ // cursor on go[o]d
209+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
210+ assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31)));
211+ }
212+
104213 #[test]
105214 fn test_find_nth_pairs_pos_mixed() {
106215 let doc = Rope::from("(so [many {good} text] here)");