@@ -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)");