@@ -1,7 +1,9 @@
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::{movement::Direction, Range, Rope, Selection, Tendril, Transaction};
4+use crate::{
5+ graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
6+};
57use log::debug;
68use smallvec::SmallVec;
79
@@ -63,31 +65,132 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
6365 doc.get_char(pos - 1)
6466}
6567
68+fn is_single_grapheme(doc: &Rope, range: &Range) -> bool {
69+ let mut graphemes = RopeGraphemes::new(doc.slice(range.from()..range.to()));
70+ let first = graphemes.next();
71+ let second = graphemes.next();
72+ debug!("first: {:#?}, second: {:#?}", first, second);
73+ first.is_some() && second.is_none()
74+}
75+
6676/// calculate what the resulting range should be for an auto pair insertion
6777fn get_next_range(
78+ doc: &Rope,
6879 start_range: &Range,
6980 offset: usize,
7081 typed_char: char,
7182 len_inserted: usize,
7283) -> Range {
73− let end_head = start_range.head + offset + typed_char.len_utf8();
84+ // When the character under the cursor changes due to complete pair
85+ // insertion, we must look backward a grapheme and then add the length
86+ // of the insertion to put the resulting cursor in the right place, e.g.
87+ //
88+ // foo[\r\n] - anchor: 3, head: 5
89+ // foo([)]\r\n - anchor: 4, head: 5
90+ //
91+ // foo[\r\n] - anchor: 3, head: 5
92+ // foo'[\r\n] - anchor: 4, head: 6
93+ //
94+ // foo([)]\r\n - anchor: 4, head: 5
95+ // foo()[\r\n] - anchor: 5, head: 7
96+ //
97+ // [foo]\r\n - anchor: 0, head: 3
98+ // [foo(])\r\n - anchor: 0, head: 5
99+
100+ // inserting at the very end of the document after the last newline
101+ if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
102+ return Range::new(
103+ start_range.anchor + offset + typed_char.len_utf8(),
104+ start_range.head + offset + typed_char.len_utf8(),
105+ );
106+ }
107+
108+ let single_grapheme = is_single_grapheme(doc, start_range);
109+ let doc_slice = doc.slice(..);
110+
111+ // just skip over graphemes
112+ if len_inserted == 0 {
113+ let end_anchor = if single_grapheme {
114+ graphemes::next_grapheme_boundary(doc_slice, start_range.anchor) + offset
115+
116+ // even for backward inserts with multiple grapheme selections,
117+ // we want the anchor to stay where it is so that the relative
118+ // selection does not change, e.g.:
119+ //
120+ // foo([) wor]d -> insert ) -> foo()[ wor]d
121+ } else {
122+ start_range.anchor + offset
123+ };
124+
125+ return Range::new(
126+ end_anchor,
127+ graphemes::next_grapheme_boundary(doc_slice, start_range.head) + offset,
128+ );
129+ }
130+
131+ // trivial case: only inserted a single-char opener, just move the selection
132+ if len_inserted == 1 {
133+ let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
134+ start_range.anchor + offset + typed_char.len_utf8()
135+ } else {
136+ start_range.anchor + offset
137+ };
138+
139+ return Range::new(
140+ end_anchor,
141+ start_range.head + offset + typed_char.len_utf8(),
142+ );
143+ }
144+
145+ // If the head = 0, then we must be in insert mode with a backward
146+ // cursor, which implies the head will just move
147+ let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
148+ start_range.head + offset + typed_char.len_utf8()
149+ } else {
150+ // We must have a forward cursor, which means we must move to the
151+ // other end of the grapheme to get to where the new characters
152+ // are inserted, then move the head to where it should be
153+ let prev_bound = graphemes::prev_grapheme_boundary(doc_slice, start_range.head);
154+ debug!(
155+ "prev_bound: {}, offset: {}, len_inserted: {}",
156+ prev_bound, offset, len_inserted
157+ );
158+ prev_bound + offset + len_inserted
159+ };
74160
75161 let end_anchor = match (start_range.len(), start_range.direction()) {
76162 // if we have a zero width cursor, it shifts to the same number
77163 (0, _) => end_head,
78164
79− // if we are inserting for a regular one-width cursor, the anchor
80− // moves with the head
165+ // If we are inserting for a regular one-width cursor, the anchor
166+ // moves with the head. This is the fast path for ASCII.
81167 (1, Direction::Forward) => end_head - 1,
82168 (1, Direction::Backward) => end_head + 1,
83169
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,
170+ (_, Direction::Forward) => {
171+ if single_grapheme {
172+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head)
173+ + typed_char.len_utf8()
87174
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,
175+ // if we are appending, the anchor stays where it is; only offset
176+ // for multiple range insertions
177+ } else {
178+ start_range.anchor + offset
179+ }
180+ }
181+
182+ (_, Direction::Backward) => {
183+ if single_grapheme {
184+ // if we're backward, then the head is at the first char
185+ // of the typed char, so we need to add the length of
186+ // the closing char
187+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor) + len_inserted
188+ } else {
189+ // when we are inserting in front of a selection, we need to move
190+ // the anchor over by however many characters were inserted overall
191+ start_range.anchor + offset + len_inserted
192+ }
193+ }
91194 };
92195
93196 Range::new(end_anchor, end_head)
@@ -122,7 +225,7 @@ fn handle_open(
122225 }
123226 };
124227
125− let next_range = get_next_range(start_range, offs, open, len_inserted);
228+ let next_range = get_next_range(doc, start_range, offs, open, len_inserted);
126229 end_ranges.push(next_range);
127230 offs += len_inserted;
128231
@@ -152,7 +255,7 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
152255 (cursor, cursor, Some(Tendril::from_char(close)))
153256 };
154257
155− let next_range = get_next_range(start_range, offs, close, len_inserted);
258+ let next_range = get_next_range(doc, start_range, offs, close, len_inserted);
156259 end_ranges.push(next_range);
157260 offs += len_inserted;
158261
@@ -202,7 +305,7 @@ fn handle_same(
202305 (cursor, cursor, Some(pair))
203306 };
204307
205− let next_range = get_next_range(start_range, offs, token, len_inserted);
308+ let next_range = get_next_range(doc, start_range, offs, token, len_inserted);
206309 end_ranges.push(next_range);
207310 offs += len_inserted;
208311
@@ -219,6 +322,8 @@ mod test {
219322 use super::*;
220323 use smallvec::smallvec;
221324
325+ const LINE_END: &'static str = crate::DEFAULT_LINE_ENDING.as_str();
326+
222327 fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
223328 PAIRS.iter().filter(|(open, close)| open != close)
224329 }
@@ -270,12 +375,59 @@ mod test {
270375 #[test]
271376 fn test_insert_blank() {
272377 test_hooks_with_pairs(
273− &Rope::new(),
378+ &Rope::from(LINE_END),
274379 &Selection::single(1, 0),
275380 PAIRS,
276− |open, close| format!("{}{}", open, close),
381+ |open, close| format!("{}{}{}", open, close, LINE_END),
277382 &Selection::single(2, 1),
278383 );
384+
385+ let empty_doc = Rope::from(format!("{line_end}{line_end}", line_end = LINE_END));
386+
387+ test_hooks_with_pairs(
388+ &empty_doc,
389+ &Selection::single(empty_doc.len_chars(), LINE_END.len()),
390+ PAIRS,
391+ |open, close| {
392+ format!(
393+ "{line_end}{open}{close}{line_end}",
394+ open = open,
395+ close = close,
396+ line_end = LINE_END
397+ )
398+ },
399+ &Selection::single(LINE_END.len() + 2, LINE_END.len() + 1),
400+ );
401+ }
402+
403+ #[test]
404+ fn test_insert_before_multi_code_point_graphemes() {
405+ test_hooks_with_pairs(
406+ &Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
407+ &Selection::single(13, 6),
408+ PAIRS,
409+ |open, _| format!("hello {}👨👩👧👦 goodbye{}", open, LINE_END),
410+ &Selection::single(14, 7),
411+ );
412+ }
413+
414+ #[test]
415+ fn test_insert_at_end_of_document() {
416+ test_hooks_with_pairs(
417+ &Rope::from(LINE_END),
418+ &Selection::single(LINE_END.len(), LINE_END.len()),
419+ PAIRS,
420+ |open, close| format!("{}{}{}", LINE_END, open, close),
421+ &Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
422+ );
423+
424+ test_hooks_with_pairs(
425+ &Rope::from(format!("foo{}", LINE_END)),
426+ &Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
427+ PAIRS,
428+ |open, close| format!("foo{}{}{}", LINE_END, open, close),
429+ &Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
430+ );
279431 }
280432
281433 /// [] -> append ( -> ([])
@@ -283,11 +435,20 @@ mod test {
283435 fn test_append_blank() {
284436 test_hooks_with_pairs(
285437 // this is what happens when you have a totally blank document and then append
286− &Rope::from("\n\n"),
287− &Selection::single(0, 2),
438+ &Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
439+ // before inserting the pair, the cursor covers all of both empty lines
440+ &Selection::single(0, LINE_END.len() * 2),
288441 PAIRS,
289− |open, close| format!("\n{}{}\n", open, close),
290− &Selection::single(0, 3),
442+ |open, close| {
443+ format!(
444+ "{line_end}{open}{close}{line_end}",
445+ line_end = LINE_END,
446+ open = open,
447+ close = close
448+ )
449+ },
450+ // after inserting pair, the cursor covers the first new line and the open char
451+ &Selection::single(0, LINE_END.len() + 2),
291452 );
292453 }
293454
@@ -329,6 +490,18 @@ mod test {
329490 );
330491 }
331492
493+ /// foo[] -> append to end of line ( -> foo([])
494+ #[test]
495+ fn test_append_single_cursor() {
496+ test_hooks_with_pairs(
497+ &Rope::from(format!("foo{}", LINE_END)),
498+ &Selection::single(3, 3 + LINE_END.len()),
499+ differing_pairs(),
500+ |open, close| format!("foo{}{}{}", open, close, LINE_END),
501+ &Selection::single(4, 5),
502+ );
503+ }
504+
332505 /// fo[o] fo[o(])
333506 /// fo[o] -> append ( -> fo[o(])
334507 /// fo[o] fo[o(])
@@ -355,18 +528,18 @@ mod test {
355528 );
356529 }
357530
358− /// ([]) -> insert ) -> ()[]
531+ /// ([)] -> insert ) -> ()[]
359532 #[test]
360533 fn test_insert_close_inside_pair() {
361534 for (open, close) in PAIRS {
362− let doc = Rope::from(format!("{}{}", open, close));
535+ let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
363536
364537 test_hooks(
365538 &doc,
366539 &Selection::single(2, 1),
367540 *close,
368541 &doc,
369− &Selection::single(3, 2),
542+ &Selection::single(2 + LINE_END.len(), 2),
370543 );
371544 }
372545 }
@@ -375,14 +548,14 @@ mod test {
375548 #[test]
376549 fn test_append_close_inside_pair() {
377550 for (open, close) in PAIRS {
378− let doc = Rope::from(format!("{}{}\n", open, close));
551+ let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
379552
380553 test_hooks(
381554 &doc,
382555 &Selection::single(0, 2),
383556 *close,
384557 &doc,
385− &Selection::single(0, 3),
558+ &Selection::single(0, 2 + LINE_END.len()),
386559 );
387560 }
388561 }
@@ -564,6 +737,20 @@ mod test {
564737 )
565738 }
566739
740+ /// foo([) wor]d -> insert ) -> foo()[ wor]d
741+ #[test]
742+ fn test_insert_close_inside_pair_trailing_word_with_selection() {
743+ for (open, close) in differing_pairs() {
744+ test_hooks(
745+ &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
746+ &Selection::single(9, 4),
747+ *close,
748+ &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
749+ &Selection::single(9, 5),
750+ )
751+ }
752+ }
753+
567754 /// we want pairs that are *not* the same char to be inserted after
568755 /// a non-pair char, for cases like functions, but for pairs that are
569756 /// the same char, we want to *not* insert a pair to handle cases like "I'm"
@@ -572,7 +759,7 @@ mod test {
572759 /// word[] -> insert ' -> word'[]
573760 #[test]
574761 fn test_insert_open_after_non_pair() {
575− let doc = Rope::from("word");
762+ let doc = Rope::from(format!("word{}", LINE_END));
576763 let sel = Selection::single(5, 4);
577764 let expected_sel = Selection::single(6, 5);
578765
@@ -580,7 +767,7 @@ mod test {
580767 &doc,
581768 &sel,
582769 differing_pairs(),
583− |open, close| format!("word{}{}", open, close),
770+ |open, close| format!("word{}{}{}", open, close, LINE_END),
584771 &expected_sel,
585772 );
586773
@@ -588,22 +775,8 @@ mod test {
588775 &doc,
589776 &sel,
590777 matching_pairs(),
591− |open, _| format!("word{}", open),
778+ |open, _| format!("word{}{}", open, LINE_END),
592779 &expected_sel,
593780 );
594781 }
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− }
609782}