on master help sign in
Auto pairs selection (#1254)

* use auto pairs with selections Previously, the auto pairs code was converting the user selection into its cursor form, and setting the transaction's selection to that cursor. This has the effect of destroying the user's selection if they type a pair character that gets auto completed. This fixes the code to work with the user's selection, inserting auto pairs where appropriate, but either keeping or extending the user's selection. * use movement::Direction instead of bool * assume 0-width cursor is forward

commit5b4540fc2d8201f4bfe336cc10c1096059217225 authorSkyler Hawthorne <skyler@dead10ck.com> dateTue 21 Dec 2021, 04:17 (5 years ago) parent1c082cb
2 files changed+275−80
helix-core/src/auto_pairs.rs+255−80 helix-core/src/selection.rs+20
helix-core/src/auto_pairs.rs+255−80
@@ -1,7 +1,7 @@
11//! When typing the opening character of one of the possible pairs defined below,
22//! this module provides the functionality to insert the paired closing character.
33
4−use crate::{Range, Rope, Selection, Tendril, Transaction};
4+use crate::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
55use log::debug;
66use smallvec::SmallVec;
77
@@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
3030
3131// [TODO]
3232// * delete implementation where it erases the whole bracket (|) -> |
33−// * do not reduce to cursors; use whole selections, and surround with pair
3433// * change to multi character pairs to handle cases like placing the cursor in the
3534// middle of triple quotes, and more exotic pairs like Jinja's {% %}
3635
@@ -38,20 +37,18 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
3837pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
3938 debug!("autopairs hook selection: {:#?}", selection);
4039
41− let cursors = selection.clone().cursors(doc.slice(..));
42−
4340 for &(open, close) in PAIRS {
4441 if open == ch {
4542 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));
4744 } else {
48− return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
45+ return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
4946 }
5047 }
5148
5249 if close == ch {
5350 // && char_at pos == close
54− return Some(handle_close(doc, &cursors, open, close));
51+ return Some(handle_close(doc, selection, open, close));
5552 }
5653 }
5754
@@ -66,6 +63,36 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
6663 doc.get_char(pos - 1)
6764}
6865
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+
6996fn handle_open(
7097 doc: &Rope,
7198 selection: &Selection,
@@ -74,36 +101,32 @@ fn handle_open(
74101 close_before: &str,
75102) -> Transaction {
76103 let mut end_ranges = SmallVec::with_capacity(selection.len());
77−
78104 let mut offs = 0;
79105
80106 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
81− let start_head = start_range.head;
82−
83− let next = doc.get_char(start_head);
84− let end_head = start_head + offs + open.len_utf8();
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));
107+ let cursor = start_range.cursor(doc.slice(..));
108+ let next_char = doc.get_char(cursor);
109+ let len_inserted;
93110
94− match next {
111+ let change = match next_char {
95112 Some(ch) if !close_before.contains(ch) => {
96− offs += open.len_utf8();
97− (start_head, start_head, Some(Tendril::from_char(open)))
113+ len_inserted = open.len_utf8();
114+ (cursor, cursor, Some(Tendril::from_char(open)))
98115 }
99116 // None | Some(ch) if close_before.contains(ch) => {}
100117 _ => {
101118 // insert open & close
102119 let pair = Tendril::from_iter([open, close]);
103− offs += open.len_utf8() + close.len_utf8();
104− (start_head, start_head, Some(pair))
120+ len_inserted = open.len_utf8() + close.len_utf8();
121+ (cursor, cursor, Some(pair))
105122 }
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
107130 });
108131
109132 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) ->
117140 let mut offs = 0;
118141
119142 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
120− let start_head = start_range.head;
121− let next = doc.get_char(start_head);
122− let end_head = start_head + offs + close.len_utf8();
143+ let cursor = start_range.cursor(doc.slice(..));
144+ let next_char = doc.get_char(cursor);
145+ let mut len_inserted = 0;
123146
124− let end_anchor = if start_range.is_empty() {
125− end_head
147+ let change = if next_char == Some(close) {
148+ // return transaction that moves past close
149+ (cursor, cursor, None) // no-op
126150 } else {
127− start_range.anchor + offs
151+ len_inserted += close.len_utf8();
152+ (cursor, cursor, Some(Tendril::from_char(close)))
128153 };
129154
130− end_ranges.push(Range::new(end_anchor, end_head));
155+ let next_range = get_next_range(start_range, offs, close, len_inserted);
156+ end_ranges.push(next_range);
157+ offs += len_inserted;
131158
132− if next == Some(close) {
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− }
159+ change
139160 });
140161
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
142165}
143166
144167/// handle cases where open and close is the same, or in triples ("""docstring""")
@@ -154,42 +177,41 @@ fn handle_same(
154177 let mut offs = 0;
155178
156179 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
157− let start_head = start_range.head;
158− let end_head = start_head + offs + token.len_utf8();
180+ let cursor = start_range.cursor(doc.slice(..));
181+ let mut len_inserted = 0;
159182
160− // if selection, retain anchor, if cursor, move over
161− let end_anchor = if start_range.is_empty() {
162− end_head
163− } else {
164− start_range.anchor + offs
165− };
183+ let next_char = doc.get_char(cursor);
184+ let prev_char = prev_char(doc, cursor);
166185
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) {
173187 // return transaction that moves past close
174− (start_head, start_head, None) // no-op
188+ (cursor, cursor, None) // no-op
175189 } else {
176190 let mut pair = Tendril::with_capacity(2 * token.len_utf8() as u32);
177191 pair.push_char(token);
178192
179193 // for equal pairs, don't insert both open and close if either
180194 // side has a non-pair char
181− if (next.is_none() || close_before.contains(next.unwrap()))
182− && (prev.is_none() || open_before.contains(prev.unwrap()))
195+ if (next_char.is_none() || close_before.contains(next_char.unwrap()))
196+ && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
183197 {
184198 pair.push_char(token);
185199 }
186200
187− offs += pair.len();
188− (start_head, start_head, Some(pair))
189− }
201+ len_inserted += pair.len();
202+ (cursor, cursor, Some(pair))
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
190210 });
191211
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
193215}
194216
195217#[cfg(test)]
@@ -252,7 +274,20 @@ mod test {
252274 &Selection::single(1, 0),
253275 PAIRS,
254276 |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),
256291 );
257292 }
258293
@@ -276,26 +311,50 @@ mod test {
276311 )
277312 },
278313 &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),),
280315 0,
281316 ),
282317 );
283318 }
284319
285− // [TODO] broken until it works with selections
286320 /// fo[o] -> append ( -> fo[o(])
287− #[ignore]
288321 #[test]
289322 fn test_append() {
290323 test_hooks_with_pairs(
291− &Rope::from("foo"),
324+ &Rope::from("foo\n"),
292325 &Selection::single(2, 4),
293− PAIRS,
294− |open, close| format!("foo{}{}", open, close),
326+ differing_pairs(),
327+ |open, close| format!("foo{}{}\n", open, close),
295328 &Selection::single(2, 5),
296329 );
297330 }
298331
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+
299358 /// ([]) -> insert ) -> ()[]
300359 #[test]
301360 fn test_insert_close_inside_pair() {
@@ -307,7 +366,23 @@ mod test {
307366 &Selection::single(2, 1),
308367 *close,
309368 &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),
311386 );
312387 }
313388 }
@@ -323,8 +398,33 @@ mod test {
323398 );
324399
325400 let expected_sel = Selection::new(
326− // smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
327− smallvec!(Range::point(2), Range::point(5), Range::point(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(
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),),
328428 0,
329429 );
330430
@@ -343,7 +443,7 @@ mod test {
343443 #[test]
344444 fn test_insert_open_inside_pair() {
345445 let sel = Selection::single(2, 1);
346− let expected_sel = Selection::point(2);
446+ let expected_sel = Selection::single(3, 2);
347447
348448 for (open, close) in differing_pairs() {
349449 let doc = Rope::from(format!("{}{}", open, close));
@@ -357,11 +457,49 @@ mod test {
357457 }
358458 }
359459
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+
360478 /// ([]) -> insert " -> ("[]")
361479 #[test]
362480 fn test_insert_nested_open_inside_pair() {
363481 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);
365503
366504 for (outer_open, outer_close) in differing_pairs() {
367505 let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
@@ -385,21 +523,44 @@ mod test {
385523 &Selection::single(1, 0),
386524 PAIRS,
387525 |open, _| format!("{}word", open),
388− &Selection::point(1),
526+ &Selection::single(2, 1),
389527 )
390528 }
391529
392− // [TODO] broken until it works with selections
393530 /// [wor]d -> insert ( -> ([wor]d
394531 #[test]
395− #[ignore]
396532 fn test_insert_open_with_selection() {
397533 test_hooks_with_pairs(
398534 &Rope::from("word"),
399− &Selection::single(0, 4),
535+ &Selection::single(3, 0),
400536 PAIRS,
401537 |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),
403564 )
404565 }
405566
@@ -413,7 +574,7 @@ mod test {
413574 fn test_insert_open_after_non_pair() {
414575 let doc = Rope::from("word");
415576 let sel = Selection::single(5, 4);
416− let expected_sel = Selection::point(5);
577+ let expected_sel = Selection::single(6, 5);
417578
418579 test_hooks_with_pairs(
419580 &doc,
@@ -431,4 +592,18 @@ mod test {
431592 &expected_sel,
432593 );
433594 }
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+ }
434609}
helix-core/src/selection.rs+20
@@ -7,6 +7,7 @@ use crate::{
77 ensure_grapheme_boundary_next, ensure_grapheme_boundary_prev, next_grapheme_boundary,
88 prev_grapheme_boundary,
99 },
10+ movement::Direction,
1011 Assoc, ChangeSet, RopeSlice,
1112};
1213use smallvec::{smallvec, SmallVec};
@@ -82,6 +83,13 @@ impl Range {
8283 std::cmp::max(self.anchor, self.head)
8384 }
8485
86+ /// Total length of the range.
87+ #[inline]
88+ #[must_use]
89+ pub fn len(&self) -> usize {
90+ self.to() - self.from()
91+ }
92+
8593 /// The (inclusive) range of lines that the range overlaps.
8694 #[inline]
8795 #[must_use]
@@ -102,6 +110,18 @@ impl Range {
102110 self.anchor == self.head
103111 }
104112
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+
105125 /// Check two ranges for overlap.
106126 #[must_use]
107127 pub fn overlaps(&self, other: &Self) -> bool {