@@ -1,6 +1,6 @@
1use std::fmt::Display;
1use std::fmt::Display;
3−use crate::{search, Range, Selection};
3+use crate::{movement::Direction, search, Range, Selection};
4use ropey::RopeSlice;
4use ropey::RopeSlice;
6pub const PAIRS: &[(char, char)] = &[
6pub const PAIRS: &[(char, char)] = &[
@@ -55,15 +55,18 @@ pub fn get_pair(ch: char) -> (char, char) {
55pub fn find_nth_closest_pairs_pos(
55pub fn find_nth_closest_pairs_pos(
56 text: RopeSlice,
56 text: RopeSlice,
57 range: Range,
57 range: Range,
58− n: usize,
58+ mut skip: usize,
59) -> Result<(usize, usize)> {
59) -> Result<(usize, usize)> {
60 let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
60 let is_open_pair = |ch| PAIRS.iter().any(|(open, _)| *open == ch);
61 let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
61 let is_close_pair = |ch| PAIRS.iter().any(|(_, close)| *close == ch);
63 let mut stack = Vec::with_capacity(2);
63 let mut stack = Vec::with_capacity(2);
64− let pos = range.cursor(text);
64+ let pos = range.from();
65+ let mut close_pos = pos.saturating_sub(1);
66 for ch in text.chars_at(pos) {
67 for ch in text.chars_at(pos) {
67 if is_open_pair(ch) {
70 if is_open_pair(ch) {
68 // Track open pairs encountered so that we can step over
71 // Track open pairs encountered so that we can step over
69 // the corresponding close pairs that will come up further
72 // the corresponding close pairs that will come up further
@@ -71,20 +74,46 @@ pub fn find_nth_closest_pairs_pos(
71 // open pair is before the cursor position.
74 // open pair is before the cursor position.
72 stack.push(ch);
75 stack.push(ch);
74− } else if is_close_pair(ch) {
79+ if !is_close_pair(ch) {
80+ // We don't care if this character isn't a brace pair item,
81+ // so short circuit here.
75− let (open, _) = get_pair(ch);
85+ let (open, close) = get_pair(ch);
76− if stack.last() == Some(&open) {
87+ if stack.last() == Some(&open) {
88+ // If we are encountering the closing pair for an opener
89+ // we just found while traversing, then its inside the
90+ // selection and should be skipped over.
77− stack.pop();
91+ stack.pop();
78− continue;
92+ continue;
80− // In the ideal case the stack would be empty here and the
94+
81− // current character would be the close pair that we are
95+ match find_nth_open_pair(text, open, close, close_pos, 1) {
82− // looking for. It could also be the case that the pairs
96+ // Before we accept this pair, we want to ensure that the
83− // are unbalanced and we encounter a close pair that doesn't
97+ // pair encloses the range rather than just the cursor.
84− // close the last seen open pair. In either case use this
98+ Some(open_pos)
85− // char as the auto-detected closest pair.
99+ if open_pos <= pos.saturating_add(1)
86− return find_nth_pairs_pos(text, ch, range, n);
100+ && close_pos >= range.to().saturating_sub(1) =>
102+ // Since we have special conditions for when to
103+ // accept, we can't just pass the skip parameter on
104+ // through to the find_nth_*_pair methods, so we
105+ // track skips manually here.
111+ return match range.direction() {
112+ Direction::Forward => Ok((open_pos, close_pos)),
113+ Direction::Backward => Ok((close_pos, open_pos)),
@@ -244,94 +273,6 @@ mod test {
244 use ropey::Rope;
273 use ropey::Rope;
245 use smallvec::SmallVec;
274 use smallvec::SmallVec;
247− #[allow(clippy::type_complexity)]
248− fn check_find_nth_pair_pos(
250− cases: Vec<(usize, char, usize, Result<(usize, usize)>)>,
252− let doc = Rope::from(text);
253− let slice = doc.slice(..);
255− for (cursor_pos, ch, n, expected_range) in cases {
256− let range = find_nth_pairs_pos(slice, ch, (cursor_pos, cursor_pos + 1).into(), n);
258− range, expected_range,
259− "Expected {:?}, got {:?}",
260− expected_range, range
266− fn test_find_nth_pairs_pos() {
267− check_find_nth_pair_pos(
271− (6, '(', 1, Ok((5, 10))),
272− (6, ')', 1, Ok((5, 10))),
274− (2, '(', 1, Err(Error::PairNotFound)),
275− // cursor on bracket itself
276− (5, '(', 1, Ok((5, 10))),
277− (10, '(', 1, Ok((5, 10))),
283− fn test_find_nth_pairs_pos_skip() {
284− check_find_nth_pair_pos(
285− "(so (many (good) text) here)",
288− (13, '(', 1, Ok((10, 15))),
289− (13, '(', 2, Ok((4, 21))),
290− (13, '(', 3, Ok((0, 27))),
296− fn test_find_nth_pairs_pos_same() {
297− check_find_nth_pair_pos(
298− "'so 'many 'good' text' here'",
301− (13, '\'', 1, Ok((10, 15))),
302− (13, '\'', 2, Ok((4, 21))),
303− (13, '\'', 3, Ok((0, 27))),
304− // cursor on the quotes
305− (10, '\'', 1, Err(Error::CursorOnAmbiguousPair)),
311− fn test_find_nth_pairs_pos_step() {
312− check_find_nth_pair_pos(
313− "((so)((many) good (text))(here))",
316− (15, '(', 1, Ok((5, 24))),
317− (15, '(', 2, Ok((0, 31))),
323− fn test_find_nth_pairs_pos_mixed() {
324− check_find_nth_pair_pos(
325− "(so [many {good} text] here)",
328− (13, '{', 1, Ok((10, 15))),
329− (13, '[', 1, Ok((4, 21))),
330− (13, '(', 1, Ok((0, 27))),
336 fn test_get_surround_pos() {
277 fn test_get_surround_pos() {
337 let doc = Rope::from("(some) (chars)\n(newline)");
278 let doc = Rope::from("(some) (chars)\n(newline)");