on master help sign in
Auto pairs selection (#1254)
5b4540fSkyler Hawthorne
helix-core/src/auto_pairs.rs+255−80
@@ -1,7 +1,7 @@
1//! When typing the opening character of one of the possible pairs defined below,
1//! When typing the opening character of one of the possible pairs defined below,
2//! this module provides the functionality to insert the paired closing character.
2//! this module provides the functionality to insert the paired closing character.
3
3
4−use crate::{Range, Rope, Selection, Tendril, Transaction};
4+use crate::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
5use log::debug;
5use log::debug;
6use smallvec::SmallVec;
6use smallvec::SmallVec;
7
7
@@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
30
30
31// [TODO]
31// [TODO]
32// * delete implementation where it erases the whole bracket (|) -> |
32// * delete implementation where it erases the whole bracket (|) -> |
33−// * do not reduce to cursors; use whole selections, and surround with pair
34// * change to multi character pairs to handle cases like placing the cursor in the
33// * change to multi character pairs to handle cases like placing the cursor in the
35// middle of triple quotes, and more exotic pairs like Jinja's {% %}
34// middle of triple quotes, and more exotic pairs like Jinja's {% %}
36
35
@@ -38,20 +37,18 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
38pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
37pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
39 debug!("autopairs hook selection: {:#?}", selection);
38 debug!("autopairs hook selection: {:#?}", selection);
40
39
41− let cursors = selection.clone().cursors(doc.slice(..));
42−
43 for &(open, close) in PAIRS {
40 for &(open, close) in PAIRS {
44 if open == ch {
41 if open == ch {
45 if open == close {
42 if open == close {
46− return Some(handle_same(doc, &cursors, open, CLOSE_BEFORE, OPEN_BEFORE));
43+ return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
47 } else {
44 } else {
48− return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
45+ return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
49 }
46 }
50 }
47 }
51
48
52 if close == ch {
49 if close == ch {
53 // && char_at pos == close
50 // && char_at pos == close
54− return Some(handle_close(doc, &cursors, open, close));
51+ return Some(handle_close(doc, selection, open, close));
55 }
52 }
56 }
53 }
57
54
@@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
66 doc.get_char(pos - 1)
63 doc.get_char(pos - 1)
67}
64}
68
65
66+/// calculate what the resulting range should be for an auto pair insertion
67+fn get_next_range(
68+ start_range: &Range,
69+ offset: usize,
70+ typed_char: char,
71+ len_inserted: usize,
72+) -> Range {
73+ let end_head = start_range.head + offset + typed_char.len_utf8();
74+
75+ let end_anchor = match (start_range.len(), start_range.direction()) {
76+ // if we have a zero width cursor, it shifts to the same number
77+ (0, _) => end_head,
78+
79+ // if we are inserting for a regular one-width cursor, the anchor
80+ // moves with the head
81+ (1, Direction::Forward) => end_head - 1,
82+ (1, Direction::Backward) => end_head + 1,
83+
84+ // if we are appending, the anchor stays where it is; only offset
85+ // for multiple range insertions
86+ (_, Direction::Forward) => start_range.anchor + offset,
87+
88+ // when we are inserting in front of a selection, we need to move
89+ // the anchor over by however many characters were inserted overall
90+ (_, Direction::Backward) => start_range.anchor + offset + len_inserted,
91+ };
92+
93+ Range::new(end_anchor, end_head)
94+}
95+
69fn handle_open(
96fn handle_open(
70 doc: &Rope,
97 doc: &Rope,
71 selection: &Selection,
98 selection: &Selection,
@@ -74,36 +101,32 @@ fn handle_open(
74 close_before: &str,
101 close_before: &str,
75) -> Transaction {
102) -> Transaction {
76 let mut end_ranges = SmallVec::with_capacity(selection.len());
103 let mut end_ranges = SmallVec::with_capacity(selection.len());
77−
78 let mut offs = 0;
104 let mut offs = 0;
79
105
80 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
106 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
81− let start_head = start_range.head;
107+ let cursor = start_range.cursor(doc.slice(..));
82−
83− let next = doc.get_char(start_head);
108+ let next_char = doc.get_char(cursor);
84− let end_head = start_head + offs + open.len_utf8();
109+ let len_inserted;
85−
86− let end_anchor = if start_range.is_empty() {
87− end_head
88− } else {
89− start_range.anchor + offs
90− };
91−
92− end_ranges.push(Range::new(end_anchor, end_head));
93
110
94− match next {
111+ let change = match next_char {
95 Some(ch) if !close_before.contains(ch) => {
112 Some(ch) if !close_before.contains(ch) => {
96− offs += open.len_utf8();
113+ len_inserted = open.len_utf8();
97− (start_head, start_head, Some(Tendril::from_char(open)))
114+ (cursor, cursor, Some(Tendril::from_char(open)))
98 }
115 }
99 // None | Some(ch) if close_before.contains(ch) => {}
116 // None | Some(ch) if close_before.contains(ch) => {}
100 _ => {
117 _ => {
101 // insert open & close
118 // insert open & close
102 let pair = Tendril::from_iter([open, close]);
119 let pair = Tendril::from_iter([open, close]);
103− offs += open.len_utf8() + close.len_utf8();
120+ len_inserted = open.len_utf8() + close.len_utf8();
104− (start_head, start_head, Some(pair))
121+ (cursor, cursor, Some(pair))
105 }
122 }
106− }
123+ };
124+
125+ let next_range = get_next_range(start_range, offs, open, len_inserted);
126+ end_ranges.push(next_range);
127+ offs += len_inserted;
128+
129+ change
107 });
130 });
108
131
109 let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
132 let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
@@ -117,28 +140,28 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
117 let mut offs = 0;
140 let mut offs = 0;
118
141
119 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
142 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
120− let start_head = start_range.head;
143+ let cursor = start_range.cursor(doc.slice(..));
121− let next = doc.get_char(start_head);
144+ let next_char = doc.get_char(cursor);
122− let end_head = start_head + offs + close.len_utf8();
145+ let mut len_inserted = 0;
123
146
124− let end_anchor = if start_range.is_empty() {
147+ let change = if next_char == Some(close) {
125− end_head
148+ // return transaction that moves past close
149+ (cursor, cursor, None) // no-op
126 } else {
150 } else {
127− start_range.anchor + offs
151+ len_inserted += close.len_utf8();
152+ (cursor, cursor, Some(Tendril::from_char(close)))
128 };
153 };
129
154
155+ let next_range = get_next_range(start_range, offs, close, len_inserted);
130− end_ranges.push(Range::new(end_anchor, end_head));
156+ end_ranges.push(next_range);
157+ offs += len_inserted;
131
158
132− if next == Some(close) {
159+ change
133− // return transaction that moves past close
134− (start_head, start_head, None) // no-op
135− } else {
136− offs += close.len_utf8();
137− (start_head, start_head, Some(Tendril::from_char(close)))
138− }
139 });
160 });
140
161
141− transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
162+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
163+ debug!("auto pair transaction: {:#?}", t);
164+ t
142}
165}
143
166
144/// handle cases where open and close is the same, or in triples ("""docstring""")
167/// handle cases where open and close is the same, or in triples ("""docstring""")
@@ -154,42 +177,41 @@ fn handle_same(
154 let mut offs = 0;
177 let mut offs = 0;
155
178
156 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
179 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
157− let start_head = start_range.head;
180+ let cursor = start_range.cursor(doc.slice(..));
158− let end_head = start_head + offs + token.len_utf8();
181+ let mut len_inserted = 0;
159
182
160− // if selection, retain anchor, if cursor, move over
183+ let next_char = doc.get_char(cursor);
161− let end_anchor = if start_range.is_empty() {
184+ let prev_char = prev_char(doc, cursor);
162− end_head
163− } else {
164− start_range.anchor + offs
165− };
166
185
167− end_ranges.push(Range::new(end_anchor, end_head));
168−
169− let next = doc.get_char(start_head);
170− let prev = prev_char(doc, start_head);
171−
172− if next == Some(token) {
186+ let change = if next_char == Some(token) {
173 // return transaction that moves past close
187 // return transaction that moves past close
174− (start_head, start_head, None) // no-op
188+ (cursor, cursor, None) // no-op
175 } else {
189 } else {
176 let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
190 let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
177 pair.push_char(token);
191 pair.push_char(token);
178
192
179 // for equal pairs, don't insert both open and close if either
193 // for equal pairs, don't insert both open and close if either
180 // side has a non-pair char
194 // side has a non-pair char
181− if (next.is_none() || close_before.contains(next.unwrap()))
195+ if (next_char.is_none() || close_before.contains(next_char.unwrap()))
182− && (prev.is_none() || open_before.contains(prev.unwrap()))
196+ && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
183 {
197 {
184 pair.push_char(token);
198 pair.push_char(token);
185 }
199 }
186
200
187− offs += pair.len();
201+ len_inserted += pair.len();
188− (start_head, start_head, Some(pair))
202+ (cursor, cursor, Some(pair))
189− }
203+ };
204+
205+ let next_range = get_next_range(start_range, offs, token, len_inserted);
206+ end_ranges.push(next_range);
207+ offs += len_inserted;
208+
209+ change
190 });
210 });
191
211
192− transaction.with_selection(Selection::new(end_ranges, selection.primary_index()))
212+ let t = transaction.with_selection(Selection::new(end_ranges, selection.primary_index()));
213+ debug!("auto pair transaction: {:#?}", t);
214+ t
193}
215}
194
216
195#[cfg(test)]
217#[cfg(test)]
@@ -252,7 +274,20 @@ mod test {
252 &Selection::single(1, 0),
274 &Selection::single(1, 0),
253 PAIRS,
275 PAIRS,
254 |open, close| format!("{}{}", open, close),
276 |open, close| format!("{}{}", open, close),
255− &Selection::single(1, 1),
277+ &Selection::single(2, 1),
278+ );
279+ }
280+
281+ /// [] -> append ( -> ([])
282+ #[test]
283+ fn test_append_blank() {
284+ test_hooks_with_pairs(
285+ // this is what happens when you have a totally blank document and then append
286+ &Rope::from("\n\n"),
287+ &Selection::single(0, 2),
288+ PAIRS,
289+ |open, close| format!("\n{}{}\n", open, close),
290+ &Selection::single(0, 3),
256 );
291 );
257 }
292 }
258
293
@@ -276,26 +311,50 @@ mod test {
276 )
311 )
277 },
312 },
278 &Selection::new(
313 &Selection::new(
279− smallvec!(Range::point(1), Range::point(4), Range::point(7),),
314+ smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
280 0,
315 0,
281 ),
316 ),
282 );
317 );
283 }
318 }
284
319
285− // [TODO] broken until it works with selections
286 /// fo[o] -> append ( -> fo[o(])
320 /// fo[o] -> append ( -> fo[o(])
287− #[ignore]
288 #[test]
321 #[test]
289 fn test_append() {
322 fn test_append() {
290 test_hooks_with_pairs(
323 test_hooks_with_pairs(
291− &Rope::from("foo"),
324+ &Rope::from("foo\n"),
292 &Selection::single(2, 4),
325 &Selection::single(2, 4),
293− PAIRS,
326+ differing_pairs(),
294− |open, close| format!("foo{}{}", open, close),
327+ |open, close| format!("foo{}{}\n", open, close),
295 &Selection::single(2, 5),
328 &Selection::single(2, 5),
296 );
329 );
297 }
330 }
298
331
332+ /// fo[o] fo[o(])
333+ /// fo[o] -> append ( -> fo[o(])
334+ /// fo[o] fo[o(])
335+ #[test]
336+ fn test_append_multi() {
337+ test_hooks_with_pairs(
338+ &Rope::from("foo\nfoo\nfoo\n"),
339+ &Selection::new(
340+ smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
341+ 0,
342+ ),
343+ differing_pairs(),
344+ |open, close| {
345+ format!(
346+ "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
347+ open = open,
348+ close = close
349+ )
350+ },
351+ &Selection::new(
352+ smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
353+ 0,
354+ ),
355+ );
356+ }
357+
299 /// ([]) -> insert ) -> ()[]
358 /// ([]) -> insert ) -> ()[]
300 #[test]
359 #[test]
301 fn test_insert_close_inside_pair() {
360 fn test_insert_close_inside_pair() {
@@ -307,7 +366,23 @@ mod test {
307 &Selection::single(2, 1),
366 &Selection::single(2, 1),
308 *close,
367 *close,
309 &doc,
368 &doc,
310− &Selection::point(2),
369+ &Selection::single(3, 2),
370+ );
371+ }
372+ }
373+
374+ /// [(]) -> append ) -> [()]
375+ #[test]
376+ fn test_append_close_inside_pair() {
377+ for (open, close) in PAIRS {
378+ let doc = Rope::from(format!("{}{}\n", open, close));
379+
380+ test_hooks(
381+ &doc,
382+ &Selection::single(0, 2),
383+ *close,
384+ &doc,
385+ &Selection::single(0, 3),
311 );
386 );
312 }
387 }
313 }
388 }
@@ -323,8 +398,33 @@ mod test {
323 );
398 );
324
399
325 let expected_sel = Selection::new(
400 let expected_sel = Selection::new(
326− // smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
401+ smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
402+ 0,
403+ );
404+
405+ for (open, close) in PAIRS {
406+ let doc = Rope::from(format!(
407+ "{open}{close}\n{open}{close}\n{open}{close}\n",
408+ open = open,
409+ close = close
410+ ));
411+
412+ test_hooks(&doc, &sel, *close, &doc, &expected_sel);
413+ }
414+ }
415+
416+ /// [(]) [()]
417+ /// [(]) -> append ) -> [()]
418+ /// [(]) [()]
419+ #[test]
420+ fn test_append_close_inside_pair_multi_cursor() {
421+ let sel = Selection::new(
327− smallvec!(Range::point(2), Range::point(5), Range::point(8),),
422+ smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
423+ 0,
424+ );
425+
426+ let expected_sel = Selection::new(
427+ smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
328 0,
428 0,
329 );
429 );
330
430
@@ -343,7 +443,7 @@ mod test {
343 #[test]
443 #[test]
344 fn test_insert_open_inside_pair() {
444 fn test_insert_open_inside_pair() {
345 let sel = Selection::single(2, 1);
445 let sel = Selection::single(2, 1);
346− let expected_sel = Selection::point(2);
446+ let expected_sel = Selection::single(3, 2);
347
447
348 for (open, close) in differing_pairs() {
448 for (open, close) in differing_pairs() {
349 let doc = Rope::from(format!("{}{}", open, close));
449 let doc = Rope::from(format!("{}{}", open, close));
@@ -357,11 +457,49 @@ mod test {
357 }
457 }
358 }
458 }
359
459
460+ /// [word(]) -> append ( -> [word((]))
461+ #[test]
462+ fn test_append_open_inside_pair() {
463+ let sel = Selection::single(0, 6);
464+ let expected_sel = Selection::single(0, 7);
465+
466+ for (open, close) in differing_pairs() {
467+ let doc = Rope::from(format!("word{}{}", open, close));
468+ let expected_doc = Rope::from(format!(
469+ "word{open}{open}{close}{close}",
470+ open = open,
471+ close = close
472+ ));
473+
474+ test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
475+ }
476+ }
477+
360 /// ([]) -> insert " -> ("[]")
478 /// ([]) -> insert " -> ("[]")
361 #[test]
479 #[test]
362 fn test_insert_nested_open_inside_pair() {
480 fn test_insert_nested_open_inside_pair() {
363 let sel = Selection::single(2, 1);
481 let sel = Selection::single(2, 1);
364− let expected_sel = Selection::point(2);
482+ let expected_sel = Selection::single(3, 2);
483+
484+ for (outer_open, outer_close) in differing_pairs() {
485+ let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
486+
487+ for (inner_open, inner_close) in matching_pairs() {
488+ let expected_doc = Rope::from(format!(
489+ "{}{}{}{}",
490+ outer_open, inner_open, inner_close, outer_close
491+ ));
492+
493+ test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
494+ }
495+ }
496+ }
497+
498+ /// [(]) -> append " -> [("]")
499+ #[test]
500+ fn test_append_nested_open_inside_pair() {
501+ let sel = Selection::single(0, 2);
502+ let expected_sel = Selection::single(0, 3);
365
503
366 for (outer_open, outer_close) in differing_pairs() {
504 for (outer_open, outer_close) in differing_pairs() {
367 let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
505 let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
@@ -385,21 +523,44 @@ mod test {
385 &Selection::single(1, 0),
523 &Selection::single(1, 0),
386 PAIRS,
524 PAIRS,
387 |open, _| format!("{}word", open),
525 |open, _| format!("{}word", open),
388− &Selection::point(1),
526+ &Selection::single(2, 1),
389 )
527 )
390 }
528 }
391
529
392− // [TODO] broken until it works with selections
393 /// [wor]d -> insert ( -> ([wor]d
530 /// [wor]d -> insert ( -> ([wor]d
394 #[test]
531 #[test]
395− #[ignore]
396 fn test_insert_open_with_selection() {
532 fn test_insert_open_with_selection() {
397 test_hooks_with_pairs(
533 test_hooks_with_pairs(
398 &Rope::from("word"),
534 &Rope::from("word"),
399− &Selection::single(0, 4),
535+ &Selection::single(3, 0),
400 PAIRS,
536 PAIRS,
401 |open, _| format!("{}word", open),
537 |open, _| format!("{}word", open),
402− &Selection::single(1, 5),
538+ &Selection::single(4, 1),
539+ )
540+ }
541+
542+ /// [wor]d -> append ) -> [wor)]d
543+ #[test]
544+ fn test_append_close_inside_non_pair_with_selection() {
545+ let sel = Selection::single(0, 4);
546+ let expected_sel = Selection::single(0, 5);
547+
548+ for (_, close) in PAIRS {
549+ let doc = Rope::from("word");
550+ let expected_doc = Rope::from(format!("wor{}d", close));
551+ test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
552+ }
553+ }
554+
555+ /// foo[ wor]d -> insert ( -> foo([) wor]d
556+ #[test]
557+ fn test_insert_open_trailing_word_with_selection() {
558+ test_hooks_with_pairs(
559+ &Rope::from("foo word"),
560+ &Selection::single(7, 3),
561+ differing_pairs(),
562+ |open, close| format!("foo{}{} word", open, close),
563+ &Selection::single(9, 4),
403 )
564 )
404 }
565 }
405
566
@@ -413,7 +574,7 @@ mod test {
413 fn test_insert_open_after_non_pair() {
574 fn test_insert_open_after_non_pair() {
414 let doc = Rope::from("word");
575 let doc = Rope::from("word");
415 let sel = Selection::single(5, 4);
576 let sel = Selection::single(5, 4);
416− let expected_sel = Selection::point(5);
577+ let expected_sel = Selection::single(6, 5);
417
578
418 test_hooks_with_pairs(
579 test_hooks_with_pairs(
419 &doc,
580 &doc,
@@ -431,4 +592,18 @@ mod test {
431 &expected_sel,
592 &expected_sel,
432 );
593 );
433 }
594 }
595+
596+ /// appending with only a cursor should stay a cursor
597+ ///
598+ /// [] -> append to end "foo -> "foo[]"
599+ #[test]
600+ fn test_append_single_cursor() {
601+ test_hooks_with_pairs(
602+ &Rope::from("\n"),
603+ &Selection::single(0, 1),
604+ PAIRS,
605+ |open, close| format!("{}{}\n", open, close),
606+ &Selection::single(1, 2),
607+ );
608+ }
434}
609}
helix-core/src/selection.rs+20
@@ -7,6 +7,7 @@ use crate::{
7 ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
7 ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
8 prev_grapheme_boundary,
8 prev_grapheme_boundary,
9 },
9 },
10+ movement::Direction,
10 Assoc, ChangeSet, RopeSlice,
11 Assoc, ChangeSet, RopeSlice,
11};
12};
12use smallvec::{smallvec, SmallVec};
13use smallvec::{smallvec, SmallVec};
@@ -82,6 +83,13 @@ impl Range {
82 std::cmp::max(self.anchor, self.head)
83 std::cmp::max(self.anchor, self.head)
83 }
84 }
84
85
86+ /// Total length of the range.
87+ #[inline]
88+ #[must_use]
89+ pub fn len(&self) -> usize {
90+ self.to() - self.from()
91+ }
92+
85 /// The (inclusive) range of lines that the range overlaps.
93 /// The (inclusive) range of lines that the range overlaps.
86 #[inline]
94 #[inline]
87 #[must_use]
95 #[must_use]
@@ -102,6 +110,18 @@ impl Range {
102 self.anchor == self.head
110 self.anchor == self.head
103 }
111 }
104
112
113+ /// `Direction::Backward` when head < anchor.
114+ /// `Direction::Backward` otherwise.
115+ #[inline]
116+ #[must_use]
117+ pub fn direction(&self) -> Direction {
118+ if self.head < self.anchor {
119+ Direction::Backward
120+ } else {
121+ Direction::Forward
122+ }
123+ }
124+
105 /// Check two ranges for overlap.
125 /// Check two ranges for overlap.
106 #[must_use]
126 #[must_use]
107 pub fn overlaps(&self, other: &Self) -> bool {
127 pub fn overlaps(&self, other: &Self) -> bool {