on master help sign in
Fix surround cursor position calculation (#1183)
dc53e65Gokul Soumya
helix-core/src/selection.rs+3−3
@@ -308,10 +308,10 @@ impl Range {
308}
308}
309
309
310impl From<(usize, usize)> for Range {
310impl From<(usize, usize)> for Range {
311− fn from(tuple: (usize, usize)) -> Self {
311+ fn from((anchor, head): (usize, usize)) -> Self {
312 Self {
312 Self {
313− anchor: tuple.0,
313+ anchor,
314− head: tuple.1,
314+ head,
315 horiz: None,
315 horiz: None,
316 }
316 }
317 }
317 }
helix-core/src/surround.rs+83−65
@@ -1,4 +1,4 @@
1−use crate::{search, Selection};
1+use crate::{search, Range, Selection};
2use ropey::RopeSlice;
2use ropey::RopeSlice;
3
3
4pub const PAIRS: &[(char, char)] = &[
4pub const PAIRS: &[(char, char)] = &[
@@ -35,33 +35,27 @@ pub fn get_pair(ch: char) -> (char, char) {
35pub fn find_nth_pairs_pos(
35pub fn find_nth_pairs_pos(
36 text: RopeSlice,
36 text: RopeSlice,
37 ch: char,
37 ch: char,
38− pos: usize,
38+ range: Range,
39 n: usize,
39 n: usize,
40) -> Option<(usize, usize)> {
40) -> Option<(usize, usize)> {
41− let (open, close) = get_pair(ch);
42−
43− if text.len_chars() < 2 || pos >= text.len_chars() {
41+ if text.len_chars() < 2 || range.to() >= text.len_chars() {
44 return None;
42 return None;
45 }
43 }
46
44
45+ let (open, close) = get_pair(ch);
46+ let pos = range.cursor(text);
47+
47 if open == close {
48 if open == close {
48 if Some(open) == text.get_char(pos) {
49 if Some(open) == text.get_char(pos) {
49− // Special case: cursor is directly on a matching char.
50+ // Cursor is directly on match char. We return no match
50− match pos {
51− 0 => Some((pos, search::find_nth_next(text, close, pos + 1, n)?)),
52− _ if (pos + 1) == text.len_chars() => {
53− Some((search::find_nth_prev(text, open, pos, n)?, pos))
54− }
55− // We return no match because there's no way to know which
51+ // because there's no way to know which side of the char
56− // side of the char we should be searching on.
52+ // we should be searching on.
57− _ => None,
53+ return None;
58− }
59− } else {
60− Some((
61− search::find_nth_prev(text, open, pos, n)?,
62− search::find_nth_next(text, close, pos, n)?,
63− ))
64 }
54 }
55+ Some((
56+ search::find_nth_prev(text, open, pos, n)?,
57+ search::find_nth_next(text, close, pos, n)?,
58+ ))
65 } else {
59 } else {
66 Some((
60 Some((
67 find_nth_open_pair(text, open, close, pos, n)?,
61 find_nth_open_pair(text, open, close, pos, n)?,
@@ -160,8 +154,8 @@ pub fn get_surround_pos(
160) -> Option<Vec<usize>> {
154) -> Option<Vec<usize>> {
161 let mut change_pos = Vec::new();
155 let mut change_pos = Vec::new();
162
156
163− for range in selection {
157+ for &range in selection {
164− let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range.head, skip)?;
158+ let (open_pos, close_pos) = find_nth_pairs_pos(text, ch, range, skip)?;
165 if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) {
159 if change_pos.contains(&open_pos) || change_pos.contains(&close_pos) {
166 return None;
160 return None;
167 }
161 }
@@ -178,67 +172,91 @@ mod test {
178 use ropey::Rope;
172 use ropey::Rope;
179 use smallvec::SmallVec;
173 use smallvec::SmallVec;
180
174
181− #[test]
175+ fn check_find_nth_pair_pos(
182− fn test_find_nth_pairs_pos() {
176+ text: &str,
177+ cases: Vec<(usize, char, usize, Option<(usize, usize)>)>,
178+ ) {
183− let doc = Rope::from("some (text) here");
179+ let doc = Rope::from(text);
184 let slice = doc.slice(..);
180 let slice = doc.slice(..);
185
181
186− // cursor on [t]ext
182+ for (cursor_pos, ch, n, expected_range) in cases {
187− assert_eq!(find_nth_pairs_pos(slice, '(', 6, 1), Some((5, 10)));
188− assert_eq!(find_nth_pairs_pos(slice, ')', 6, 1), Some((5, 10)));
189− // cursor on so[m]e
190− assert_eq!(find_nth_pairs_pos(slice, '(', 2, 1), None);
183+ let range = find_nth_pairs_pos(slice, ch, (cursor_pos, cursor_pos + 1).into(), n);
191− // cursor on bracket itself
184+ assert_eq!(
192− assert_eq!(find_nth_pairs_pos(slice, '(', 5, 1), Some((5, 10)));
185+ range, expected_range,
193− assert_eq!(find_nth_pairs_pos(slice, '(', 10, 1), Some((5, 10)));
186+ "Expected {:?}, got {:?}",
187+ expected_range, range
188+ );
189+ }
194 }
190 }
195
191
196 #[test]
192 #[test]
197− fn test_find_nth_pairs_pos_skip() {
193+ fn test_find_nth_pairs_pos() {
198− let doc = Rope::from("(so (many (good) text) here)");
194+ check_find_nth_pair_pos(
199− let slice = doc.slice(..);
195+ "some (text) here",
196+ vec![
197+ // cursor on [t]ext
198+ (6, '(', 1, Some((5, 10))),
199+ (6, ')', 1, Some((5, 10))),
200+ // cursor on so[m]e
201+ (2, '(', 1, None),
202+ // cursor on bracket itself
203+ (5, '(', 1, Some((5, 10))),
204+ (10, '(', 1, Some((5, 10))),
205+ ],
206+ );
207+ }
200
208
209+ #[test]
210+ fn test_find_nth_pairs_pos_skip() {
211+ check_find_nth_pair_pos(
212+ "(so (many (good) text) here)",
213+ vec![
201− // cursor on go[o]d
214+ // cursor on go[o]d
202− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((10, 15)));
215+ (13, '(', 1, Some((10, 15))),
203− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 2), Some((4, 21)));
216+ (13, '(', 2, Some((4, 21))),
204− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 3), Some((0, 27)));
217+ (13, '(', 3, Some((0, 27))),
218+ ],
219+ );
205 }
220 }
206
221
207 #[test]
222 #[test]
208 fn test_find_nth_pairs_pos_same() {
223 fn test_find_nth_pairs_pos_same() {
224+ check_find_nth_pair_pos(
209− let doc = Rope::from("'so 'many 'good' text' here'");
225+ "'so 'many 'good' text' here'",
210− let slice = doc.slice(..);
226+ vec![
211−
212− // cursor on go[o]d
227+ // cursor on go[o]d
213− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 1), Some((10, 15)));
228+ (13, '\'', 1, Some((10, 15))),
214− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 2), Some((4, 21)));
229+ (13, '\'', 2, Some((4, 21))),
215− assert_eq!(find_nth_pairs_pos(slice, '\'', 13, 3), Some((0, 27)));
230+ (13, '\'', 3, Some((0, 27))),
216− // cursor on the quotes
231+ // cursor on the quotes
217− assert_eq!(find_nth_pairs_pos(slice, '\'', 10, 1), None);
232+ (10, '\'', 1, None),
218− // this is the best we can do since opening and closing pairs are same
233+ ],
219− assert_eq!(find_nth_pairs_pos(slice, '\'', 0, 1), Some((0, 4)));
234+ )
220− assert_eq!(find_nth_pairs_pos(slice, '\'', 27, 1), Some((21, 27)));
221 }
235 }
222
236
223 #[test]
237 #[test]
224 fn test_find_nth_pairs_pos_step() {
238 fn test_find_nth_pairs_pos_step() {
239+ check_find_nth_pair_pos(
225− let doc = Rope::from("((so)((many) good (text))(here))");
240+ "((so)((many) good (text))(here))",
226− let slice = doc.slice(..);
241+ vec![
227−
228− // cursor on go[o]d
242+ // cursor on go[o]d
229− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 1), Some((5, 24)));
243+ (15, '(', 1, Some((5, 24))),
230− assert_eq!(find_nth_pairs_pos(slice, '(', 15, 2), Some((0, 31)));
244+ (15, '(', 2, Some((0, 31))),
245+ ],
246+ )
231 }
247 }
232
248
233 #[test]
249 #[test]
234 fn test_find_nth_pairs_pos_mixed() {
250 fn test_find_nth_pairs_pos_mixed() {
251+ check_find_nth_pair_pos(
235− let doc = Rope::from("(so [many {good} text] here)");
252+ "(so [many {good} text] here)",
236− let slice = doc.slice(..);
253+ vec![
237−
238− // cursor on go[o]d
254+ // cursor on go[o]d
239− assert_eq!(find_nth_pairs_pos(slice, '{', 13, 1), Some((10, 15)));
255+ (13, '{', 1, Some((10, 15))),
240− assert_eq!(find_nth_pairs_pos(slice, '[', 13, 1), Some((4, 21)));
256+ (13, '[', 1, Some((4, 21))),
241− assert_eq!(find_nth_pairs_pos(slice, '(', 13, 1), Some((0, 27)));
257+ (13, '(', 1, Some((0, 27))),
258+ ],
259+ )
242 }
260 }
243
261
244 #[test]
262 #[test]
helix-core/src/textobject.rs+6−4
@@ -114,7 +114,7 @@ pub fn textobject_surround(
114 ch: char,
114 ch: char,
115 count: usize,
115 count: usize,
116) -> Range {
116) -> Range {
117− surround::find_nth_pairs_pos(slice, ch, range.head, count)
117+ surround::find_nth_pairs_pos(slice, ch, range, count)
118 .map(|(anchor, head)| match textobject {
118 .map(|(anchor, head)| match textobject {
119 TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
119 TextObject::Inside => Range::new(next_grapheme_boundary(slice, anchor), head),
120 TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
120 TextObject::Around => Range::new(anchor, next_grapheme_boundary(slice, head)),
@@ -170,7 +170,7 @@ mod test {
170
170
171 #[test]
171 #[test]
172 fn test_textobject_word() {
172 fn test_textobject_word() {
173− // (text, [(cursor position, textobject, final range), ...])
173+ // (text, [(char position, textobject, final range), ...])
174 let tests = &[
174 let tests = &[
175 (
175 (
176 "cursor at beginning of doc",
176 "cursor at beginning of doc",
@@ -269,7 +269,9 @@ mod test {
269 let slice = doc.slice(..);
269 let slice = doc.slice(..);
270 for &case in scenario {
270 for &case in scenario {
271 let (pos, objtype, expected_range) = case;
271 let (pos, objtype, expected_range) = case;
272+ // cursor is a single width selection
273+ let range = Range::new(pos, pos + 1);
272− let result = textobject_word(slice, Range::point(pos), objtype, 1, false);
274+ let result = textobject_word(slice, range, objtype, 1, false);
273 assert_eq!(
275 assert_eq!(
274 result,
276 result,
275 expected_range.into(),
277 expected_range.into(),
@@ -283,7 +285,7 @@ mod test {
283
285
284 #[test]
286 #[test]
285 fn test_textobject_surround() {
287 fn test_textobject_surround() {
286− // (text, [(cursor position, textobject, final range, count), ...])
288+ // (text, [(cursor position, textobject, final range, surround char, count), ...])
287 let tests = &[
289 let tests = &[
288 (
290 (
289 "simple (single) surround pairs",
291 "simple (single) surround pairs",