@@ -4,12 +4,14 @@
4use crate::{
4use crate::{
5 graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
5 graphemes, movement::Direction, Range, Rope, RopeGraphemes, Selection, Tendril, Transaction,
7+use std::collections::HashMap;
7use log::debug;
9use log::debug;
8use smallvec::SmallVec;
10use smallvec::SmallVec;
10// Heavily based on https://github.com/codemirror/closebrackets/
12// Heavily based on https://github.com/codemirror/closebrackets/
12−pub const PAIRS: &[(char, char)] = &[
14+pub const DEFAULT_PAIRS: &[(char, char)] = &[
13 ('(', ')'),
15 ('(', ')'),
14 ('{', '}'),
16 ('{', '}'),
15 ('[', ']'),
17 ('[', ']'),
@@ -18,9 +20,95 @@ pub const PAIRS: &[(char, char)] = &[
18 ('`', '`'),
20 ('`', '`'),
21−// [TODO] build this dynamically in language config. see #992
23+/// The type that represents the collection of auto pairs,
22−const OPEN_BEFORE: &str = "([{'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}";
24+/// keyed by the opener.
23−const CLOSE_BEFORE: &str = ")]}'\":;,> \n\r\u{000B}\u{000C}\u{0085}\u{2028}\u{2029}"; // includes space and newlines
25+#[derive(Debug, Clone)]
26+pub struct AutoPairs(HashMap<char, Pair>);
28+/// Represents the config for a particular pairing.
29+#[derive(Debug, Clone, Copy)]
36+ /// true if open == close
37+ pub fn same(&self) -> bool {
38+ self.open == self.close
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);
46+ should_close &= Self::prev_is_not_alpha(doc, range);
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)
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)
65+impl From<&(char, char)> for Pair {
66+ fn from(&(open, close): &(char, char)) -> Self {
71+impl From<(&char, &char)> for Pair {
72+ fn from((open, close): (&char, &char)) -> Self {
81+ /// Make a new AutoPairs set with the given pairs and default conditions.
82+ pub fn new<'a, V: 'a, A>(pairs: V) -> Self
84+ V: IntoIterator<Item = A>,
87+ let mut auto_pairs = HashMap::new();
89+ for pair in pairs.into_iter() {
90+ let auto_pair = pair.into();
92+ auto_pairs.insert(auto_pair.open, auto_pair);
94+ if auto_pair.open != auto_pair.close {
95+ auto_pairs.insert(auto_pair.close, auto_pair);
102+ pub fn get(&self, ch: char) -> Option<&Pair> {
107+impl Default for AutoPairs {
108+ fn default() -> Self {
109+ AutoPairs::new(DEFAULT_PAIRS.iter())
25// insert hook:
113// insert hook:
26// Fn(doc, selection, char) => Option<Transaction>
114// 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
36// middle of triple quotes, and more exotic pairs like Jinja's {% %}
124// middle of triple quotes, and more exotic pairs like Jinja's {% %}
38#[must_use]
126#[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> {
40 debug!("autopairs hook selection: {:#?}", selection);
128 debug!("autopairs hook selection: {:#?}", selection);
42− for &(open, close) in PAIRS {
130+ if let Some(pair) = pairs.get(ch) {
43− if open == ch {
131+ if pair.same() {
45− return Some(handle_same(doc, selection, open, CLOSE_BEFORE, OPEN_BEFORE));
132+ return Some(handle_same(doc, selection, pair));
46− } else {
133+ } else if pair.open == ch {
47− return Some(handle_open(doc, selection, open, close, CLOSE_BEFORE));
134+ return Some(handle_open(doc, selection, pair));
51− if close == ch {
135+ } else if pair.close == ch {
52 // && char_at pos == close
136 // && char_at pos == close
53− return Some(handle_close(doc, selection, open, close));
137+ return Some(handle_close(doc, selection, pair));
@@ -196,13 +280,7 @@ fn get_next_range(
196 Range::new(end_anchor, end_head)
280 Range::new(end_anchor, end_head)
201− selection: &Selection,
283+fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
206 let mut end_ranges = SmallVec::with_capacity(selection.len());
284 let mut end_ranges = SmallVec::with_capacity(selection.len());
207 let mut offs = 0;
285 let mut offs = 0;
@@ -212,22 +290,21 @@ fn handle_open(
212 let len_inserted;
290 let len_inserted;
214 let change = match next_char {
292 let change = match next_char {
215− Some(ch) if !close_before.contains(ch) => {
293+ Some(_) if !pair.should_close(doc, start_range) => {
216− len_inserted = open.len_utf8();
294+ len_inserted = pair.open.len_utf8();
217 let mut tendril = Tendril::new();
295 let mut tendril = Tendril::new();
218− tendril.push(open);
296+ tendril.push(pair.open);
219 (cursor, cursor, Some(tendril))
297 (cursor, cursor, Some(tendril))
221− // None | Some(ch) if close_before.contains(ch) => {}
223 // insert open & close
300 // insert open & close
224− let pair = Tendril::from_iter([open, close]);
301+ let pair_str = Tendril::from_iter([pair.open, pair.close]);
225− len_inserted = open.len_utf8() + close.len_utf8();
302+ len_inserted = pair.open.len_utf8() + pair.close.len_utf8();
226− (cursor, cursor, Some(pair))
303+ (cursor, cursor, Some(pair_str))
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);
231 end_ranges.push(next_range);
308 end_ranges.push(next_range);
232 offs += len_inserted;
309 offs += len_inserted;
@@ -239,7 +316,7 @@ fn handle_open(
242−fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) -> Transaction {
319+fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
243 let mut end_ranges = SmallVec::with_capacity(selection.len());
320 let mut end_ranges = SmallVec::with_capacity(selection.len());
245 let mut offs = 0;
322 let mut offs = 0;
@@ -249,17 +326,17 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
249 let next_char = doc.get_char(cursor);
326 let next_char = doc.get_char(cursor);
250 let mut len_inserted = 0;
327 let mut len_inserted = 0;
252− let change = if next_char == Some(close) {
329+ let change = if next_char == Some(pair.close) {
253 // return transaction that moves past close
330 // return transaction that moves past close
254 (cursor, cursor, None) // no-op
331 (cursor, cursor, None) // no-op
256− len_inserted += close.len_utf8();
333+ len_inserted += pair.close.len_utf8();
257 let mut tendril = Tendril::new();
334 let mut tendril = Tendril::new();
258− tendril.push(close);
335+ tendril.push(pair.close);
259 (cursor, cursor, Some(tendril))
336 (cursor, cursor, Some(tendril))
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);
263 end_ranges.push(next_range);
340 end_ranges.push(next_range);
264 offs += len_inserted;
341 offs += len_inserted;
@@ -272,13 +349,7 @@ fn handle_close(doc: &Rope, selection: &Selection, _open: char, close: char) ->
274/// handle cases where open and close is the same, or in triples ("""docstring""")
351/// handle cases where open and close is the same, or in triples ("""docstring""")
277− selection: &Selection,
352+fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
282 let mut end_ranges = SmallVec::with_capacity(selection.len());
353 let mut end_ranges = SmallVec::with_capacity(selection.len());
284 let mut offs = 0;
355 let mut offs = 0;
@@ -286,30 +357,26 @@ fn handle_same(
286 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
357 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
287 let cursor = start_range.cursor(doc.slice(..));
358 let cursor = start_range.cursor(doc.slice(..));
288 let mut len_inserted = 0;
359 let mut len_inserted = 0;
290 let next_char = doc.get_char(cursor);
360 let next_char = doc.get_char(cursor);
291− let prev_char = prev_char(doc, cursor);
293− let change = if next_char == Some(token) {
362+ let change = if next_char == Some(pair.open) {
294 // return transaction that moves past close
363 // return transaction that moves past close
295 (cursor, cursor, None) // no-op
364 (cursor, cursor, None) // no-op
297− let mut pair = Tendril::new();
366+ let mut pair_str = Tendril::new();
298− pair.push(token);
367+ pair_str.push(pair.open);
300 // for equal pairs, don't insert both open and close if either
369 // for equal pairs, don't insert both open and close if either
301 // side has a non-pair char
370 // side has a non-pair char
302− if (next_char.is_none() || close_before.contains(next_char.unwrap()))
371+ if pair.should_close(doc, start_range) {
303− && (prev_char.is_none() || open_before.contains(prev_char.unwrap()))
372+ pair_str.push(pair.close);
308− len_inserted += pair.len();
375+ len_inserted += pair_str.len();
309− (cursor, cursor, Some(pair))
376+ (cursor, cursor, Some(pair_str))
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);
313 end_ranges.push(next_range);
380 end_ranges.push(next_range);
314 offs += len_inserted;
381 offs += len_inserted;
@@ -329,21 +396,23 @@ mod test {
329 const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str();
396 const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str();
331 fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
398 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)
335 fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> {
402 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)
339 fn test_hooks(
406 fn test_hooks(
340 in_doc: &Rope,
407 in_doc: &Rope,
341 in_sel: &Selection,
408 in_sel: &Selection,
342 ch: char,
409 ch: char,
410+ pairs: &[(char, char)],
343 expected_doc: &Rope,
411 expected_doc: &Rope,
344 expected_sel: &Selection,
412 expected_sel: &Selection,
414+ let pairs = AutoPairs::new(pairs.iter());
346− let trans = hook(in_doc, in_sel, ch).unwrap();
415+ let trans = hook(in_doc, in_sel, ch, &pairs).unwrap();
347 let mut actual_doc = in_doc.clone();
416 let mut actual_doc = in_doc.clone();
348 assert!(trans.apply(&mut actual_doc));
417 assert!(trans.apply(&mut actual_doc));
349 assert_eq!(expected_doc, &actual_doc);
418 assert_eq!(expected_doc, &actual_doc);
@@ -353,7 +422,8 @@ mod test {
353 fn test_hooks_with_pairs<I, F, R>(
422 fn test_hooks_with_pairs<I, F, R>(
354 in_doc: &Rope,
423 in_doc: &Rope,
355 in_sel: &Selection,
424 in_sel: &Selection,
356− pairs: I,
425+ test_pairs: I,
426+ pairs: &[(char, char)],
357 get_expected_doc: F,
427 get_expected_doc: F,
358 actual_sel: &Selection,
428 actual_sel: &Selection,
@@ -362,11 +432,12 @@ mod test {
362 R: Into<Rope>,
432 R: Into<Rope>,
363 Rope: From<R>,
433 Rope: From<R>,
365− pairs.into_iter().for_each(|(open, close)| {
435+ test_pairs.into_iter().for_each(|(open, close)| {
366 test_hooks(
436 test_hooks(
370 &Rope::from(get_expected_doc(*open, *close)),
441 &Rope::from(get_expected_doc(*open, *close)),
371 actual_sel,
442 actual_sel,
@@ -381,7 +452,8 @@ mod test {
381 test_hooks_with_pairs(
452 test_hooks_with_pairs(
382 &Rope::from(LINE_END),
453 &Rope::from(LINE_END),
383 &Selection::single(1, 0),
454 &Selection::single(1, 0),
384− PAIRS,
455+ DEFAULT_PAIRS,
385 |open, close| format!("{}{}{}", open, close, LINE_END),
457 |open, close| format!("{}{}{}", open, close, LINE_END),
386 &Selection::single(2, 1),
458 &Selection::single(2, 1),
@@ -391,7 +463,8 @@ mod test {
391 test_hooks_with_pairs(
463 test_hooks_with_pairs(
392 &empty_doc,
464 &empty_doc,
393 &Selection::single(empty_doc.len_chars(), LINE_END.len()),
465 &Selection::single(empty_doc.len_chars(), LINE_END.len()),
394− PAIRS,
466+ DEFAULT_PAIRS,
395 |open, close| {
468 |open, close| {
397 "{line_end}{open}{close}{line_end}",
470 "{line_end}{open}{close}{line_end}",
@@ -406,13 +479,16 @@ mod test {
408 fn test_insert_before_multi_code_point_graphemes() {
481 fn test_insert_before_multi_code_point_graphemes() {
409− test_hooks_with_pairs(
482+ for (_, close) in differing_pairs() {
410− &Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
484+ &Rope::from(format!("hello 👨👩👧👦 goodbye{}", LINE_END)),
411− &Selection::single(13, 6),
485+ &Selection::single(13, 6),
413− |open, _| format!("hello {}👨👩👧👦 goodbye{}", open, LINE_END),
488+ &Rope::from(format!("hello {}👨👩👧👦 goodbye{}", close, LINE_END)),
414− &Selection::single(14, 7),
489+ &Selection::single(14, 7),
@@ -420,7 +496,8 @@ mod test {
420 test_hooks_with_pairs(
496 test_hooks_with_pairs(
421 &Rope::from(LINE_END),
497 &Rope::from(LINE_END),
422 &Selection::single(LINE_END.len(), LINE_END.len()),
498 &Selection::single(LINE_END.len(), LINE_END.len()),
423− PAIRS,
499+ DEFAULT_PAIRS,
424 |open, close| format!("{}{}{}", LINE_END, open, close),
501 |open, close| format!("{}{}{}", LINE_END, open, close),
425 &Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
502 &Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
@@ -428,7 +505,8 @@ mod test {
428 test_hooks_with_pairs(
505 test_hooks_with_pairs(
429 &Rope::from(format!("foo{}", LINE_END)),
506 &Rope::from(format!("foo{}", LINE_END)),
430 &Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
507 &Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
431− PAIRS,
508+ DEFAULT_PAIRS,
432 |open, close| format!("foo{}{}{}", LINE_END, open, close),
510 |open, close| format!("foo{}{}{}", LINE_END, open, close),
433 &Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
511 &Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
@@ -442,7 +520,8 @@ mod test {
442 &Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
520 &Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
443 // before inserting the pair, the cursor covers all of both empty lines
521 // before inserting the pair, the cursor covers all of both empty lines
444 &Selection::single(0, LINE_END.len() * 2),
522 &Selection::single(0, LINE_END.len() * 2),
445− PAIRS,
523+ DEFAULT_PAIRS,
446 |open, close| {
525 |open, close| {
448 "{line_end}{open}{close}{line_end}",
527 "{line_end}{open}{close}{line_end}",
@@ -467,7 +546,8 @@ mod test {
467 smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),),
546 smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),),
470− PAIRS,
549+ DEFAULT_PAIRS,
471 |open, close| {
551 |open, close| {
473 "{open}{close}\n{open}{close}\n{open}{close}\n",
553 "{open}{close}\n{open}{close}\n{open}{close}\n",
@@ -489,6 +569,7 @@ mod test {
489 &Rope::from("foo\n"),
569 &Rope::from("foo\n"),
490 &Selection::single(2, 4),
570 &Selection::single(2, 4),
491 differing_pairs(),
571 differing_pairs(),
492 |open, close| format!("foo{}{}\n", open, close),
573 |open, close| format!("foo{}{}\n", open, close),
493 &Selection::single(2, 5),
574 &Selection::single(2, 5),
@@ -501,6 +582,7 @@ mod test {
501 &Rope::from(format!("foo{}", LINE_END)),
582 &Rope::from(format!("foo{}", LINE_END)),
502 &Selection::single(3, 3 + LINE_END.len()),
583 &Selection::single(3, 3 + LINE_END.len()),
503 differing_pairs(),
584 differing_pairs(),
504 |open, close| format!("foo{}{}{}", open, close, LINE_END),
586 |open, close| format!("foo{}{}{}", open, close, LINE_END),
505 &Selection::single(4, 5),
587 &Selection::single(4, 5),
@@ -518,6 +600,7 @@ mod test {
520 differing_pairs(),
602 differing_pairs(),
521 |open, close| {
604 |open, close| {
523 "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
606 "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
@@ -535,13 +618,14 @@ mod test {
535 /// ([)] -> insert ) -> ()[]
618 /// ([)] -> insert ) -> ()[]
537 fn test_insert_close_inside_pair() {
620 fn test_insert_close_inside_pair() {
538− for (open, close) in PAIRS {
621+ for (open, close) in DEFAULT_PAIRS {
539 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
622 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
541 test_hooks(
624 test_hooks(
543 &Selection::single(2, 1),
626 &Selection::single(2, 1),
546 &Selection::single(2 + LINE_END.len(), 2),
630 &Selection::single(2 + LINE_END.len(), 2),
@@ -551,13 +635,14 @@ mod test {
551 /// [(]) -> append ) -> [()]
635 /// [(]) -> append ) -> [()]
553 fn test_append_close_inside_pair() {
637 fn test_append_close_inside_pair() {
554− for (open, close) in PAIRS {
638+ for (open, close) in DEFAULT_PAIRS {
555 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
639 let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
557 test_hooks(
641 test_hooks(
559 &Selection::single(0, 2),
643 &Selection::single(0, 2),
562 &Selection::single(0, 2 + LINE_END.len()),
647 &Selection::single(0, 2 + LINE_END.len()),
@@ -579,14 +664,14 @@ mod test {
582− for (open, close) in PAIRS {
667+ for (open, close) in DEFAULT_PAIRS {
583 let doc = Rope::from(format!(
668 let doc = Rope::from(format!(
584 "{open}{close}\n{open}{close}\n{open}{close}\n",
669 "{open}{close}\n{open}{close}\n{open}{close}\n",
585 open = open,
670 open = open,
586 close = close
671 close = close
589− test_hooks(&doc, &sel, *close, &doc, &expected_sel);
674+ test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
@@ -605,14 +690,14 @@ mod test {
608− for (open, close) in PAIRS {
693+ for (open, close) in DEFAULT_PAIRS {
609 let doc = Rope::from(format!(
694 let doc = Rope::from(format!(
610 "{open}{close}\n{open}{close}\n{open}{close}\n",
695 "{open}{close}\n{open}{close}\n{open}{close}\n",
611 open = open,
696 open = open,
612 close = close
697 close = close
615− test_hooks(&doc, &sel, *close, &doc, &expected_sel);
700+ test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
@@ -630,7 +715,14 @@ mod test {
630 close = close
715 close = close
633− test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
723+ &expected_doc,
@@ -648,7 +740,14 @@ mod test {
648 close = close
740 close = close
651− test_hooks(&doc, &sel, *open, &expected_doc, &expected_sel);
748+ &expected_doc,
@@ -667,7 +766,14 @@ mod test {
667 outer_open, inner_open, inner_close, outer_close
766 outer_open, inner_open, inner_close, outer_close
670− test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
769+ test_hooks(
@@ -687,7 +793,14 @@ mod test {
687 outer_open, inner_open, inner_close, outer_close
793 outer_open, inner_open, inner_close, outer_close
690− test_hooks(&doc, &sel, *inner_open, &expected_doc, &expected_sel);
796+ test_hooks(
@@ -698,7 +811,8 @@ mod test {
698 test_hooks_with_pairs(
811 test_hooks_with_pairs(
699 &Rope::from("word"),
812 &Rope::from("word"),
700 &Selection::single(1, 0),
813 &Selection::single(1, 0),
701− PAIRS,
814+ DEFAULT_PAIRS,
702 |open, _| format!("{}word", open),
816 |open, _| format!("{}word", open),
703 &Selection::single(2, 1),
817 &Selection::single(2, 1),
@@ -710,7 +824,8 @@ mod test {
710 test_hooks_with_pairs(
824 test_hooks_with_pairs(
711 &Rope::from("word"),
825 &Rope::from("word"),
712 &Selection::single(3, 0),
826 &Selection::single(3, 0),
713− PAIRS,
827+ DEFAULT_PAIRS,
714 |open, _| format!("{}word", open),
829 |open, _| format!("{}word", open),
715 &Selection::single(4, 1),
830 &Selection::single(4, 1),
@@ -722,10 +837,17 @@ mod test {
722 let sel = Selection::single(0, 4);
837 let sel = Selection::single(0, 4);
723 let expected_sel = Selection::single(0, 5);
838 let expected_sel = Selection::single(0, 5);
725− for (_, close) in PAIRS {
840+ for (_, close) in DEFAULT_PAIRS {
726 let doc = Rope::from("word");
841 let doc = Rope::from("word");
727 let expected_doc = Rope::from(format!("wor{}d", close));
842 let expected_doc = Rope::from(format!("wor{}d", close));
728− test_hooks(&doc, &sel, *close, &expected_doc, &expected_sel);
848+ &expected_doc,
@@ -736,6 +858,7 @@ mod test {
736 &Rope::from("foo word"),
858 &Rope::from("foo word"),
737 &Selection::single(7, 3),
859 &Selection::single(7, 3),
738 differing_pairs(),
860 differing_pairs(),
739 |open, close| format!("foo{}{} word", open, close),
862 |open, close| format!("foo{}{} word", open, close),
740 &Selection::single(9, 4),
863 &Selection::single(9, 4),
@@ -749,6 +872,7 @@ mod test {
749 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
872 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
750 &Selection::single(9, 4),
873 &Selection::single(9, 4),
752 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
876 &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
753 &Selection::single(9, 5),
877 &Selection::single(9, 5),
@@ -771,6 +895,7 @@ mod test {
773 differing_pairs(),
897 differing_pairs(),
774 |open, close| format!("word{}{}{}", open, close, LINE_END),
899 |open, close| format!("word{}{}{}", open, close, LINE_END),
775 &expected_sel,
900 &expected_sel,
@@ -779,8 +904,34 @@ mod test {
781 matching_pairs(),
906 matching_pairs(),
782 |open, _| format!("word{}{}", open, LINE_END),
908 |open, _| format!("word{}{}", open, LINE_END),
783 &expected_sel,
909 &expected_sel,
914+ fn test_configured_pairs() {
915+ let test_pairs = &[('`', ':'), ('+', '-')];
917+ test_hooks_with_pairs(
918+ &Rope::from(LINE_END),
919+ &Selection::single(1, 0),
922+ |open, close| format!("{}{}{}", open, close, LINE_END),
923+ &Selection::single(2, 1),
926+ let doc = Rope::from(format!("foo`: word{}", LINE_END));
930+ &Selection::single(9, 4),
934+ &Selection::single(9, 5),