@@ -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.
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;
@@ -30,7 +30,6 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
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 {% %}
@@ -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);
41− let cursors = selection.clone().cursors(doc.slice(..));
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));
48− return Some(handle_open(doc, &cursors, open, close, CLOSE_BEFORE));
45+ return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
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));
@@ -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)
66+/// calculate what the resulting range should be for an auto pair insertion
73+ let end_head = start_range.head + offset + typed_char.len_utf8();
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
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,
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,
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,
93+ Range::new(end_anchor, end_head)
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());
78 let mut offs = 0;
104 let mut offs = 0;
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(..));
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;
86− let end_anchor = if start_range.is_empty() {
89− start_range.anchor + offs
92− end_ranges.push(Range::new(end_anchor, end_head));
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)))
99 // None | Some(ch) if close_before.contains(ch) => {}
116 // None | Some(ch) if close_before.contains(ch) => {}
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))
125+ let next_range = get_next_range(start_range, offs, open, len_inserted);
126+ end_ranges.push(next_range);
127+ offs += len_inserted;
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;
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;
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
127− start_range.anchor + offs
151+ len_inserted += close.len_utf8();
152+ (cursor, cursor, Some(Tendril::from_char(close)))
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;
132− if next == Some(close) {
159+ change
133− // return transaction that moves past close
134− (start_head, start_head, None) // no-op
136− offs += close.len_utf8();
137− (start_head, start_head, Some(Tendril::from_char(close)))
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);
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;
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;
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);
164− start_range.anchor + offs
167− end_ranges.push(Range::new(end_anchor, end_head));
169− let next = doc.get_char(start_head);
170− let prev = prev_char(doc, start_head);
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
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);
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()))
184 pair.push_char(token);
198 pair.push_char(token);
187− offs += pair.len();
201+ len_inserted += pair.len();
188− (start_head, start_head, Some(pair))
202+ (cursor, cursor, Some(pair))
205+ let next_range = get_next_range(start_range, offs, token, len_inserted);
206+ end_ranges.push(next_range);
207+ offs += len_inserted;
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);
195#[cfg(test)]
217#[cfg(test)]
@@ -252,7 +274,20 @@ mod test {
252 &Selection::single(1, 0),
274 &Selection::single(1, 0),
254 |open, close| format!("{}{}", open, close),
276 |open, close| format!("{}{}", open, close),
255− &Selection::single(1, 1),
277+ &Selection::single(2, 1),
281+ /// [] -> append ( -> ([])
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),
289+ |open, close| format!("\n{}{}\n", open, close),
290+ &Selection::single(0, 3),
@@ -276,26 +311,50 @@ mod test {
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),),
285− // [TODO] broken until it works with selections
286 /// fo[o] -> append ( -> fo[o(])
320 /// fo[o] -> append ( -> fo[o(])
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),
333+ /// fo[o] -> append ( -> fo[o(])
336+ fn test_append_multi() {
337+ test_hooks_with_pairs(
338+ &Rope::from("foo\nfoo\nfoo\n"),
340+ smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
346+ "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
352+ smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
299 /// ([]) -> insert ) -> ()[]
358 /// ([]) -> insert ) -> ()[]
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),
310− &Selection::point(2),
369+ &Selection::single(3, 2),
374+ /// [(]) -> append ) -> [()]
376+ fn test_append_close_inside_pair() {
377+ for (open, close) in PAIRS {
378+ let doc = Rope::from(format!("{}{}\n", open, close));
382+ &Selection::single(0, 2),
385+ &Selection::single(0, 3),
@@ -323,8 +398,33 @@ mod test {
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),),
405+ for (open, close) in PAIRS {
406+ let doc = Rope::from(format!(
407+ "{open}{close}\n{open}{close}\n{open}{close}\n",
412+ test_hooks(&doc, &sel, *close, &doc, &expected_sel);
417+ /// [(]) -> append ) -> [()]
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),),
426+ let expected_sel = Selection::new(
427+ smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
@@ -343,7 +443,7 @@ mod 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);
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 {
460+ /// [word(]) -> append ( -> [word((]))
462+ fn test_append_open_inside_pair() {
463+ let sel = Selection::single(0, 6);
464+ let expected_sel = Selection::single(0, 7);
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}",
474+ test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
360 /// ([]) -> insert " -> ("[]")
478 /// ([]) -> insert " -> ("[]")
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);
484+ for (outer_open, outer_close) in differing_pairs() {
485+ let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
487+ for (inner_open, inner_close) in matching_pairs() {
488+ let expected_doc = Rope::from(format!(
490+ outer_open, inner_open, inner_close, outer_close
493+ test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
498+ /// [(]) -> append " -> [("]")
500+ fn test_append_nested_open_inside_pair() {
501+ let sel = Selection::single(0, 2);
502+ let expected_sel = Selection::single(0, 3);
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),
387 |open, _| format!("{}word", open),
525 |open, _| format!("{}word", open),
388− &Selection::point(1),
526+ &Selection::single(2, 1),
392− // [TODO] broken until it works with selections
393 /// [wor]d -> insert ( -> ([wor]d
530 /// [wor]d -> insert ( -> ([wor]d
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),
401 |open, _| format!("{}word", open),
537 |open, _| format!("{}word", open),
402− &Selection::single(1, 5),
538+ &Selection::single(4, 1),
542+ /// [wor]d -> append ) -> [wor)]d
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);
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);
555+ /// foo[ wor]d -> insert ( -> foo([) wor]d
557+ fn test_insert_open_trailing_word_with_selection() {
558+ test_hooks_with_pairs(
559+ &Rope::from("foo word"),
560+ &Selection::single(7, 3),
562+ |open, close| format!("foo{}{} word", open, close),
563+ &Selection::single(9, 4),
@@ -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);
418 test_hooks_with_pairs(
579 test_hooks_with_pairs(
@@ -431,4 +592,18 @@ mod test {
431 &expected_sel,
592 &expected_sel,
596+ /// appending with only a cursor should stay a cursor
598+ /// [] -> append to end "foo -> "foo[]"
600+ fn test_append_single_cursor() {
601+ test_hooks_with_pairs(
603+ &Selection::single(0, 1),
605+ |open, close| format!("{}{}\n", open, close),
606+ &Selection::single(1, 2),