@@ -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}