@@ -4,12 +4,14 @@
44use crate::{
55 graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
66};
7+use std::collections::HashMap;
8+
79use log::debug;
810use smallvec::SmallVec;
911
1012// Heavily based on https://github.com/codemirror/closebrackets/
1113
12−pub const PAIRS: &[(char, char)] = &[
14+pub const DEFAULT_PAIRS: &[(char, char)] = &[
1315 ('(', ')'),
1416 ('{', '}'),
1517 ('[', ']'),
@@ -18,9 +20,95 @@ pub const PAIRS: &[(char, char)] = &[
1820 ('`', '`'),
1921];
2022
21−// [TODO] build this dynamically in language config. see #992
22−const OPEN_BEFORE: &str = "([{'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}";
23−const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; // includes space and newlines
23+/// The type that represents the collection of auto pairs,
24+/// keyed by the opener.
25+#[derive(Debug, Clone)]
26+pub struct AutoPairs(HashMap<char, Pair>);
27+
28+/// Represents the config for a particular pairing.
29+#[derive(Debug, Clone, Copy)]
30+pub struct Pair {
31+ pub open: char,
32+ pub close: char,
33+}
34+
35+impl Pair {
36+ /// true if open == close
37+ pub fn same(&self) -> bool {
38+ self.open == self.close
39+ }
40+
41+ /// true if all of the pair's conditions hold for the given document and range
42+ pub fn should_close(&self, doc: &Rope, range: &Range) -> bool {
43+ let mut should_close = Self::next_is_not_alpha(doc, range);
44+
45+ if self.same() {
46+ should_close &= Self::prev_is_not_alpha(doc, range);
47+ }
48+
49+ should_close
50+ }
51+
52+ pub fn next_is_not_alpha(doc: &Rope, range: &Range) -> bool {
53+ let cursor = range.cursor(doc.slice(..));
54+ let next_char = doc.get_char(cursor);
55+ next_char.map(|c| !c.is_alphanumeric()).unwrap_or(true)
56+ }
57+
58+ pub fn prev_is_not_alpha(doc: &Rope, range: &Range) -> bool {
59+ let cursor = range.cursor(doc.slice(..));
60+ let prev_char = prev_char(doc, cursor);
61+ prev_char.map(|c| !c.is_alphanumeric()).unwrap_or(true)
62+ }
63+}
64+
65+impl From<&(char, char)> for Pair {
66+ fn from(&(open, close): &(char, char)) -> Self {
67+ Self { open, close }
68+ }
69+}
70+
71+impl From<(&char, &char)> for Pair {
72+ fn from((open, close): (&char, &char)) -> Self {
73+ Self {
74+ open: *open,
75+ close: *close,
76+ }
77+ }
78+}
79+
80+impl AutoPairs {
81+ /// Make a new AutoPairs set with the given pairs and default conditions.
82+ pub fn new<'a, V: 'a, A>(pairs: V) -> Self
83+ where
84+ V: IntoIterator<Item = A>,
85+ A: Into<Pair>,
86+ {
87+ let mut auto_pairs = HashMap::new();
88+
89+ for pair in pairs.into_iter() {
90+ let auto_pair = pair.into();
91+
92+ auto_pairs.insert(auto_pair.open, auto_pair);
93+
94+ if auto_pair.open != auto_pair.close {
95+ auto_pairs.insert(auto_pair.close, auto_pair);
96+ }
97+ }
98+
99+ Self(auto_pairs)
100+ }
101+
102+ pub fn get(&self, ch: char) -> Option<&Pair> {
103+ self.0.get(&ch)
104+ }
105+}
106+
107+impl Default for AutoPairs {
108+ fn default() -> Self {
109+ AutoPairs::new(DEFAULT_PAIRS.iter())
110+ }
111+}
24112
25113// insert hook:
26114// Fn(doc, selection, char) => Option<Transaction>
@@ -36,21 +124,17 @@ const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{20
36124// middle of triple quotes, and more exotic pairs like Jinja's {% %}
37125
38126#[must_use]
39−pub fn hook(doc: &Rope, selection: &Selection, ch: char) -> Option<Transaction> {
127+pub fn hook(doc: &Rope, selection: &Selection, ch: char, pairs: &AutoPairs) -> Option<Transaction> {
40128 debug!("autopairs hook selection: {:#?}", selection);
41129
42− for &(open, close) in PAIRS {
43− if open == ch {
44− if open == close {
45− return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
46− } else {
47− return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
48− }
49− }
50−
51− if close == ch {
130+ if let Some(pair) = pairs.get(ch) {
131+ if pair.same() {
132+ return Some(handle_same(doc, selection, pair));
133+ } else if pair.open == ch {
134+ return Some(handle_open(doc, selection, pair));
135+ } else if pair.close == ch {
52136 // && char_at pos == close
53− return Some(handle_close(doc, selection, open, close));
137+ return Some(handle_close(doc, selection, pair));
54138 }
55139 }
56140
@@ -196,13 +280,7 @@ fn get_next_range(
196280 Range::new(end_anchor, end_head)
197281}
198282
199−fn handle_open(
200− doc: &Rope,
201− selection: &Selection,
202− open: char,
203− close: char,
204− close_before: &str,
205−) -> Transaction {
283+fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
206284 let mut end_ranges = SmallVec::with_capacity(selection.len());
207285 let mut offs = 0;
208286
@@ -212,22 +290,21 @@ fn handle_open(
212290 let len_inserted;
213291
214292 let change = match next_char {
215− Some(ch) if !close_before.contains(ch) => {
216− len_inserted = open.len_utf8();
293+ Some(_) if !pair.should_close(doc, start_range) => {
294+ len_inserted = pair.open.len_utf8();
217295 let mut tendril = Tendril::new();
218− tendril.push(open);
296+ tendril.push(pair.open);
219297 (cursor, cursor, Some(tendril))
220298 }
221− // None | Some(ch) if close_before.contains(ch) => {}
222299 _ => {
223300 // insert open & close
224− let pair = Tendril::from_iter([open, close]);
225− len_inserted = open.len_utf8() + close.len_utf8();
226− (cursor, cursor, Some(pair))
301+ let pair_str = Tendril::from_iter([pair.open, pair.close]);
302+ len_inserted = pair.open.len_utf8() + pair.close.len_utf8();
303+ (cursor, cursor, Some(pair_str))
227304 }
228305 };
229306
230− let next_range = get_next_range(doc, start_range, offs, open, len_inserted);
307+ let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
231308 end_ranges.push(next_range);
232309 offs += len_inserted;
233310
@@ -239,7 +316,7 @@ fn handle_open(
239316 t
240317}
241318
242−fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction {
319+fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
243320 let mut end_ranges = SmallVec::with_capacity(selection.len());
244321
245322 let mut offs = 0;
@@ -249,17 +326,17 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
249326 let next_char = doc.get_char(cursor);
250327 let mut len_inserted = 0;
251328
252− let change = if next_char == Some(close) {
329+ let change = if next_char == Some(pair.close) {
253330 // return transaction that moves past close
254331 (cursor, cursor, None) // no-op
255332 } else {
256− len_inserted += close.len_utf8();
333+ len_inserted += pair.close.len_utf8();
257334 let mut tendril = Tendril::new();
258− tendril.push(close);
335+ tendril.push(pair.close);
259336 (cursor, cursor, Some(tendril))
260337 };
261338
262− let next_range = get_next_range(doc, start_range, offs, close, len_inserted);
339+ let next_range = get_next_range(doc, start_range, offs, pair.close, len_inserted);
263340 end_ranges.push(next_range);
264341 offs += len_inserted;
265342
@@ -272,13 +349,7 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
272349}
273350
274351/// handle cases where open and close is the same, or in triples ("""docstring""")
275−fn handle_same(
276− doc: &Rope,
277− selection: &Selection,
278− token: char,
279− close_before: &str,
280− open_before: &str,
281−) -> Transaction {
352+fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
282353 let mut end_ranges = SmallVec::with_capacity(selection.len());
283354
284355 let mut offs = 0;
@@ -286,30 +357,26 @@ fn handle_same(
286357 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
287358 let cursor = start_range.cursor(doc.slice(..));
288359 let mut len_inserted = 0;
289−
290360 let next_char = doc.get_char(cursor);
291− let prev_char = prev_char(doc, cursor);
292361
293− let change = if next_char == Some(token) {
362+ let change = if next_char == Some(pair.open) {
294363 // return transaction that moves past close
295364 (cursor, cursor, None) // no-op
296365 } else {
297− let mut pair = Tendril::new();
298− pair.push(token);
366+ let mut pair_str = Tendril::new();
367+ pair_str.push(pair.open);
299368
300369 // for equal pairs, don't insert both open and close if either
301370 // side has a non-pair char
302− if (next_char.is_none() || close_before.contains(next_char.unwrap()))
303− && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
304− {
305− pair.push(token);
371+ if pair.should_close(doc, start_range) {
372+ pair_str.push(pair.close);
306373 }
307374
308− len_inserted += pair.len();
309− (cursor, cursor, Some(pair))
375+ len_inserted += pair_str.len();
376+ (cursor, cursor, Some(pair_str))
310377 };
311378
312− let next_range = get_next_range(doc, start_range, offs, token, len_inserted);
379+ let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
313380 end_ranges.push(next_range);
314381 offs += len_inserted;
315382
@@ -329,21 +396,23 @@ mod test {
329396 const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str();
330397
331398 fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
332− PAIRS.iter().filter(|(open, close)| open != close)
399+ DEFAULT_PAIRS.iter().filter(|(open, close)| open != close)
333400 }
334401
335402 fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> {
336− PAIRS.iter().filter(|(open, close)| open == close)
403+ DEFAULT_PAIRS.iter().filter(|(open, close)| open == close)
337404 }
338405
339406 fn test_hooks(
340407 in_doc: &Rope,
341408 in_sel: &Selection,
342409 ch: char,
410+ pairs: &[(char, char)],
343411 expected_doc: &Rope,
344412 expected_sel: &Selection,
345413 ) {
346− let trans = hook(in_doc, in_sel, ch).unwrap();
414+ let pairs = AutoPairs::new(pairs.iter());
415+ let trans = hook(in_doc, in_sel, ch, &pairs).unwrap();
347416 let mut actual_doc = in_doc.clone();
348417 assert!(trans.apply(&mut actual_doc));
349418 assert_eq!(expected_doc, &actual_doc);
@@ -353,7 +422,8 @@ mod test {
353422 fn test_hooks_with_pairs<I, F, R>(
354423 in_doc: &Rope,
355424 in_sel: &Selection,
356− pairs: I,
425+ test_pairs: I,
426+ pairs: &[(char, char)],
357427 get_expected_doc: F,
358428 actual_sel: &Selection,
359429 ) where
@@ -362,11 +432,12 @@ mod test {
362432 R: Into<Rope>,
363433 Rope: From<R>,
364434 {
365− pairs.into_iter().for_each(|(open, close)| {
435+ test_pairs.into_iter().for_each(|(open, close)| {
366436 test_hooks(
367437 in_doc,
368438 in_sel,
369439 *open,
440+ pairs,
370441 &Rope::from(get_expected_doc(*open, *close)),
371442 actual_sel,
372443 )
@@ -381,7 +452,8 @@ mod test {
381452 test_hooks_with_pairs(
382453 &Rope::from(LINE_END),
383454 &Selection::single(1, 0),
384− PAIRS,
455+ DEFAULT_PAIRS,
456+ DEFAULT_PAIRS,
385457 |open, close| format!("{}{}{}", open, close, LINE_END),
386458 &Selection::single(2, 1),
387459 );
@@ -391,7 +463,8 @@ mod test {
391463 test_hooks_with_pairs(
392464 &empty_doc,
393465 &Selection::single(empty_doc.len_chars(), LINE_END.len()),
394− PAIRS,
466+ DEFAULT_PAIRS,
467+ DEFAULT_PAIRS,
395468 |open, close| {
396469 format!(
397470 "{line_end}{open}{close}{line_end}",
@@ -406,13 +479,16 @@ mod test {
406479
407480 #[test]
408481 fn test_insert_before_multi_code_point_graphemes() {
409− test_hooks_with_pairs(
410− &Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
411− &Selection::single(13, 6),
412− PAIRS,
413− |open, _| format!("hello {}👨👩👧👦 goodbye{}", open, LINE_END),
414− &Selection::single(14, 7),
415− );
482+ for (_, close) in differing_pairs() {
483+ test_hooks(
484+ &Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
485+ &Selection::single(13, 6),
486+ *close,
487+ DEFAULT_PAIRS,
488+ &Rope::from(format!("hello {}👨👩👧👦 goodbye{}", close, LINE_END)),
489+ &Selection::single(14, 7),
490+ );
491+ }
416492 }
417493
418494 #[test]
@@ -420,7 +496,8 @@ mod test {
420496 test_hooks_with_pairs(
421497 &Rope::from(LINE_END),
422498 &Selection::single(LINE_END.len(), LINE_END.len()),
423− PAIRS,
499+ DEFAULT_PAIRS,
500+ DEFAULT_PAIRS,
424501 |open, close| format!("{}{}{}", LINE_END, open, close),
425502 &Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
426503 );
@@ -428,7 +505,8 @@ mod test {
428505 test_hooks_with_pairs(
429506 &Rope::from(format!("foo{}", LINE_END)),
430507 &Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
431− PAIRS,
508+ DEFAULT_PAIRS,
509+ DEFAULT_PAIRS,
432510 |open, close| format!("foo{}{}{}", LINE_END, open, close),
433511 &Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
434512 );
@@ -442,7 +520,8 @@ mod test {
442520 &Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
443521 // before inserting the pair, the cursor covers all of both empty lines
444522 &Selection::single(0, LINE_END.len() * 2),
445− PAIRS,
523+ DEFAULT_PAIRS,
524+ DEFAULT_PAIRS,
446525 |open, close| {
447526 format!(
448527 "{line_end}{open}{close}{line_end}",
@@ -467,7 +546,8 @@ mod test {
467546 smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),),
468547 0,
469548 ),
470− PAIRS,
549+ DEFAULT_PAIRS,
550+ DEFAULT_PAIRS,
471551 |open, close| {
472552 format!(
473553 "{open}{close}\n{open}{close}\n{open}{close}\n",
@@ -489,6 +569,7 @@ mod test {
489569 &Rope::from("foo\n"),
490570 &Selection::single(2, 4),
491571 differing_pairs(),
572+ DEFAULT_PAIRS,
492573 |open, close| format!("foo{}{}\n", open, close),
493574 &Selection::single(2, 5),
494575 );
@@ -501,6 +582,7 @@ mod test {
501582 &Rope::from(format!("foo{}", LINE_END)),
502583 &Selection::single(3, 3 + LINE_END.len()),
503584 differing_pairs(),
585+ DEFAULT_PAIRS,
504586 |open, close| format!("foo{}{}{}", open, close, LINE_END),
505587 &Selection::single(4, 5),
506588 );
@@ -518,6 +600,7 @@ mod test {
518600 0,
519601 ),
520602 differing_pairs(),
603+ DEFAULT_PAIRS,
521604 |open, close| {
522605 format!(
523606 "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
@@ -535,13 +618,14 @@ mod test {
535618 /// ([)] -> insert ) -> ()[]
536619 #[test]
537620 fn test_insert_close_inside_pair() {
538− for (open, close) in PAIRS {
621+ for (open, close) in DEFAULT_PAIRS {
539622 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
540623
541624 test_hooks(
542625 &doc,
543626 &Selection::single(2, 1),
544627 *close,
628+ DEFAULT_PAIRS,
545629 &doc,
546630 &Selection::single(2 + LINE_END.len(), 2),
547631 );
@@ -551,13 +635,14 @@ mod test {
551635 /// [(]) -> append ) -> [()]
552636 #[test]
553637 fn test_append_close_inside_pair() {
554− for (open, close) in PAIRS {
638+ for (open, close) in DEFAULT_PAIRS {
555639 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
556640
557641 test_hooks(
558642 &doc,
559643 &Selection::single(0, 2),
560644 *close,
645+ DEFAULT_PAIRS,
561646 &doc,
562647 &Selection::single(0, 2 + LINE_END.len()),
563648 );
@@ -579,14 +664,14 @@ mod test {
579664 0,
580665 );
581666
582− for (open, close) in PAIRS {
667+ for (open, close) in DEFAULT_PAIRS {
583668 let doc = Rope::from(format!(
584669 "{open}{close}\n{open}{close}\n{open}{close}\n",
585670 open = open,
586671 close = close
587672 ));
588673
589− test_hooks(&doc, &sel, *close, &doc, &expected_sel);
674+ test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
590675 }
591676 }
592677
@@ -605,14 +690,14 @@ mod test {
605690 0,
606691 );
607692
608− for (open, close) in PAIRS {
693+ for (open, close) in DEFAULT_PAIRS {
609694 let doc = Rope::from(format!(
610695 "{open}{close}\n{open}{close}\n{open}{close}\n",
611696 open = open,
612697 close = close
613698 ));
614699
615− test_hooks(&doc, &sel, *close, &doc, &expected_sel);
700+ test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
616701 }
617702 }
618703
@@ -630,7 +715,14 @@ mod test {
630715 close = close
631716 ));
632717
633− test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
718+ test_hooks(
719+ &doc,
720+ &sel,
721+ *open,
722+ DEFAULT_PAIRS,
723+ &expected_doc,
724+ &expected_sel,
725+ );
634726 }
635727 }
636728
@@ -648,7 +740,14 @@ mod test {
648740 close = close
649741 ));
650742
651− test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
743+ test_hooks(
744+ &doc,
745+ &sel,
746+ *open,
747+ DEFAULT_PAIRS,
748+ &expected_doc,
749+ &expected_sel,
750+ );
652751 }
653752 }
654753
@@ -667,7 +766,14 @@ mod test {
667766 outer_open, inner_open, inner_close, outer_close
668767 ));
669768
670− test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
769+ test_hooks(
770+ &doc,
771+ &sel,
772+ *inner_open,
773+ DEFAULT_PAIRS,
774+ &expected_doc,
775+ &expected_sel,
776+ );
671777 }
672778 }
673779 }
@@ -687,7 +793,14 @@ mod test {
687793 outer_open, inner_open, inner_close, outer_close
688794 ));
689795
690− test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
796+ test_hooks(
797+ &doc,
798+ &sel,
799+ *inner_open,
800+ DEFAULT_PAIRS,
801+ &expected_doc,
802+ &expected_sel,
803+ );
691804 }
692805 }
693806 }
@@ -698,7 +811,8 @@ mod test {
698811 test_hooks_with_pairs(
699812 &Rope::from("word"),
700813 &Selection::single(1, 0),
701− PAIRS,
814+ DEFAULT_PAIRS,
815+ DEFAULT_PAIRS,
702816 |open, _| format!("{}word", open),
703817 &Selection::single(2, 1),
704818 )
@@ -710,7 +824,8 @@ mod test {
710824 test_hooks_with_pairs(
711825 &Rope::from("word"),
712826 &Selection::single(3, 0),
713− PAIRS,
827+ DEFAULT_PAIRS,
828+ DEFAULT_PAIRS,
714829 |open, _| format!("{}word", open),
715830 &Selection::single(4, 1),
716831 )
@@ -722,10 +837,17 @@ mod test {
722837 let sel = Selection::single(0, 4);
723838 let expected_sel = Selection::single(0, 5);
724839
725− for (_, close) in PAIRS {
840+ for (_, close) in DEFAULT_PAIRS {
726841 let doc = Rope::from("word");
727842 let expected_doc = Rope::from(format!("wor{}d", close));
728− test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
843+ test_hooks(
844+ &doc,
845+ &sel,
846+ *close,
847+ DEFAULT_PAIRS,
848+ &expected_doc,
849+ &expected_sel,
850+ );
729851 }
730852 }
731853
@@ -736,6 +858,7 @@ mod test {
736858 &Rope::from("foo word"),
737859 &Selection::single(7, 3),
738860 differing_pairs(),
861+ DEFAULT_PAIRS,
739862 |open, close| format!("foo{}{} word", open, close),
740863 &Selection::single(9, 4),
741864 )
@@ -749,6 +872,7 @@ mod test {
749872 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
750873 &Selection::single(9, 4),
751874 *close,
875+ DEFAULT_PAIRS,
752876 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
753877 &Selection::single(9, 5),
754878 )
@@ -771,6 +895,7 @@ mod test {
771895 &doc,
772896 &sel,
773897 differing_pairs(),
898+ DEFAULT_PAIRS,
774899 |open, close| format!("word{}{}{}", open, close, LINE_END),
775900 &expected_sel,
776901 );
@@ -779,8 +904,34 @@ mod test {
779904 &doc,
780905 &sel,
781906 matching_pairs(),
907+ DEFAULT_PAIRS,
782908 |open, _| format!("word{}{}", open, LINE_END),
783909 &expected_sel,
784910 );
785911 }
912+
913+ #[test]
914+ fn test_configured_pairs() {
915+ let test_pairs = &[('`', ':'), ('+', '-')];
916+
917+ test_hooks_with_pairs(
918+ &Rope::from(LINE_END),
919+ &Selection::single(1, 0),
920+ test_pairs,
921+ test_pairs,
922+ |open, close| format!("{}{}{}", open, close, LINE_END),
923+ &Selection::single(2, 1),
924+ );
925+
926+ let doc = Rope::from(format!("foo`: word{}", LINE_END));
927+
928+ test_hooks(
929+ &doc,
930+ &Selection::single(9, 4),
931+ ':',
932+ test_pairs,
933+ &doc,
934+ &Selection::single(9, 5),
935+ )
936+ }
786937}