on master help sign in
Fix multi byte auto pairs (#4024)
6a0b450Skyler Hawthorne
helix-core/src/auto_pairs.rs+20−574
@@ -146,13 +146,7 @@ fn prev_char(doc: &Rope, pos: usize) -> Option<char> {
146}
146}
147
147
148/// calculate what the resulting range should be for an auto pair insertion
148/// calculate what the resulting range should be for an auto pair insertion
149−fn get_next_range(
149+fn get_next_range(doc: &Rope, start_range: &Range, offset: usize, len_inserted: usize) -> Range {
150− doc: &Rope,
151− start_range: &Range,
152− offset: usize,
153− typed_char: char,
154− len_inserted: usize,
155−) -> Range {
156 // When the character under the cursor changes due to complete pair
150 // When the character under the cursor changes due to complete pair
157 // insertion, we must look backward a grapheme and then add the length
151 // insertion, we must look backward a grapheme and then add the length
158 // of the insertion to put the resulting cursor in the right place, e.g.
152 // of the insertion to put the resulting cursor in the right place, e.g.
@@ -172,8 +166,8 @@ fn get_next_range(
172 // inserting at the very end of the document after the last newline
166 // inserting at the very end of the document after the last newline
173 if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
167 if start_range.head == doc.len_chars() && start_range.anchor == doc.len_chars() {
174 return Range::new(
168 return Range::new(
175− start_range.anchor + offset + typed_char.len_utf8(),
169+ start_range.anchor + offset + 1,
176− start_range.head + offset + typed_char.len_utf8(),
170+ start_range.head + offset + 1,
177 );
171 );
178 }
172 }
179
173
@@ -203,21 +197,18 @@ fn get_next_range(
203 // trivial case: only inserted a single-char opener, just move the selection
197 // trivial case: only inserted a single-char opener, just move the selection
204 if len_inserted == 1 {
198 if len_inserted == 1 {
205 let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
199 let end_anchor = if single_grapheme || start_range.direction() == Direction::Backward {
206− start_range.anchor + offset + typed_char.len_utf8()
200+ start_range.anchor + offset + 1
207 } else {
201 } else {
208 start_range.anchor + offset
202 start_range.anchor + offset
209 };
203 };
210
204
211− return Range::new(
212− end_anchor,
213− start_range.head + offset + typed_char.len_utf8(),
205+ return Range::new(end_anchor, start_range.head + offset + 1);
214− );
215 }
206 }
216
207
217 // If the head = 0, then we must be in insert mode with a backward
208 // If the head = 0, then we must be in insert mode with a backward
218 // cursor, which implies the head will just move
209 // cursor, which implies the head will just move
219 let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
210 let end_head = if start_range.head == 0 || start_range.direction() == Direction::Backward {
220− start_range.head + offset + typed_char.len_utf8()
211+ start_range.head + offset + 1
221 } else {
212 } else {
222 // We must have a forward cursor, which means we must move to the
213 // We must have a forward cursor, which means we must move to the
223 // other end of the grapheme to get to where the new characters
214 // other end of the grapheme to get to where the new characters
@@ -243,8 +234,7 @@ fn get_next_range(
243
234
244 (_, Direction::Forward) => {
235 (_, Direction::Forward) => {
245 if single_grapheme {
236 if single_grapheme {
246− graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head)
237+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.head) + 1
247− + typed_char.len_utf8()
248
238
249 // if we are appending, the anchor stays where it is; only offset
239 // if we are appending, the anchor stays where it is; only offset
250 // for multiple range insertions
240 // for multiple range insertions
@@ -258,7 +248,9 @@ fn get_next_range(
258 // if we're backward, then the head is at the first char
248 // if we're backward, then the head is at the first char
259 // of the typed char, so we need to add the length of
249 // of the typed char, so we need to add the length of
260 // the closing char
250 // the closing char
261− graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor) + len_inserted
251+ graphemes::prev_grapheme_boundary(doc.slice(..), start_range.anchor)
252+ + len_inserted
253+ + offset
262 } else {
254 } else {
263 // when we are inserting in front of a selection, we need to move
255 // when we are inserting in front of a selection, we need to move
264 // the anchor over by however many characters were inserted overall
256 // the anchor over by however many characters were inserted overall
@@ -279,9 +271,12 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
279 let next_char = doc.get_char(cursor);
271 let next_char = doc.get_char(cursor);
280 let len_inserted;
272 let len_inserted;
281
273
274+ // Since auto pairs are currently limited to single chars, we're either
275+ // inserting exactly one or two chars. When arbitrary length pairs are
276+ // added, these will need to be changed.
282 let change = match next_char {
277 let change = match next_char {
283 Some(_) if !pair.should_close(doc, start_range) => {
278 Some(_) if !pair.should_close(doc, start_range) => {
284− len_inserted = pair.open.len_utf8();
279+ len_inserted = 1;
285 let mut tendril = Tendril::new();
280 let mut tendril = Tendril::new();
286 tendril.push(pair.open);
281 tendril.push(pair.open);
287 (cursor, cursor, Some(tendril))
282 (cursor, cursor, Some(tendril))
@@ -289,12 +284,12 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
289 _ => {
284 _ => {
290 // insert open & close
285 // insert open & close
291 let pair_str = Tendril::from_iter([pair.open, pair.close]);
286 let pair_str = Tendril::from_iter([pair.open, pair.close]);
292− len_inserted = pair.open.len_utf8() + pair.close.len_utf8();
287+ len_inserted = 2;
293 (cursor, cursor, Some(pair_str))
288 (cursor, cursor, Some(pair_str))
294 }
289 }
295 };
290 };
296
291
297− let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
292+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
298 end_ranges.push(next_range);
293 end_ranges.push(next_range);
299 offs += len_inserted;
294 offs += len_inserted;
300
295
@@ -308,7 +303,6 @@ fn handle_open(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
308
303
309fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
304fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
310 let mut end_ranges = SmallVec::with_capacity(selection.len());
305 let mut end_ranges = SmallVec::with_capacity(selection.len());
311−
312 let mut offs = 0;
306 let mut offs = 0;
313
307
314 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
308 let transaction = Transaction::change_by_selection(doc, selection, |start_range| {
@@ -320,13 +314,13 @@ fn handle_close(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
320 // return transaction that moves past close
314 // return transaction that moves past close
321 (cursor, cursor, None) // no-op
315 (cursor, cursor, None) // no-op
322 } else {
316 } else {
323− len_inserted += pair.close.len_utf8();
317+ len_inserted = 1;
324 let mut tendril = Tendril::new();
318 let mut tendril = Tendril::new();
325 tendril.push(pair.close);
319 tendril.push(pair.close);
326 (cursor, cursor, Some(tendril))
320 (cursor, cursor, Some(tendril))
327 };
321 };
328
322
329− let next_range = get_next_range(doc, start_range, offs, pair.close, len_inserted);
323+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
330 end_ranges.push(next_range);
324 end_ranges.push(next_range);
331 offs += len_inserted;
325 offs += len_inserted;
332
326
@@ -362,11 +356,11 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
362 pair_str.push(pair.close);
356 pair_str.push(pair.close);
363 }
357 }
364
358
365− len_inserted += pair_str.len();
359+ len_inserted += pair_str.chars().count();
366 (cursor, cursor, Some(pair_str))
360 (cursor, cursor, Some(pair_str))
367 };
361 };
368
362
369− let next_range = get_next_range(doc, start_range, offs, pair.open, len_inserted);
363+ let next_range = get_next_range(doc, start_range, offs, len_inserted);
370 end_ranges.push(next_range);
364 end_ranges.push(next_range);
371 offs += len_inserted;
365 offs += len_inserted;
372
366
@@ -377,551 +371,3 @@ fn handle_same(doc: &Rope, selection: &Selection, pair: &Pair) -> Transaction {
377 log::debug!("auto pair transaction: {:#?}", t);
371 log::debug!("auto pair transaction: {:#?}", t);
378 t
372 t
379}
373}
380−
381−#[cfg(test)]
382−mod test {
383− use super::*;
384− use smallvec::smallvec;
385−
386− const LINE_END: &str = crate::DEFAULT_LINE_ENDING.as_str();
387−
388− fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
389− DEFAULT_PAIRS.iter().filter(|(open, close)| open != close)
390− }
391−
392− fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> {
393− DEFAULT_PAIRS.iter().filter(|(open, close)| open == close)
394− }
395−
396− fn test_hooks(
397− in_doc: &Rope,
398− in_sel: &Selection,
399− ch: char,
400− pairs: &[(char, char)],
401− expected_doc: &Rope,
402− expected_sel: &Selection,
403− ) {
404− let pairs = AutoPairs::new(pairs.iter());
405− let trans = hook(in_doc, in_sel, ch, &pairs).unwrap();
406− let mut actual_doc = in_doc.clone();
407− assert!(trans.apply(&mut actual_doc));
408− assert_eq!(expected_doc, &actual_doc);
409− assert_eq!(expected_sel, trans.selection().unwrap());
410− }
411−
412− fn test_hooks_with_pairs<I, F, R>(
413− in_doc: &Rope,
414− in_sel: &Selection,
415− test_pairs: I,
416− pairs: &[(char, char)],
417− get_expected_doc: F,
418− actual_sel: &Selection,
419− ) where
420− I: IntoIterator<Item = &'static (char, char)>,
421− F: Fn(char, char) -> R,
422− R: Into<Rope>,
423− Rope: From<R>,
424− {
425− test_pairs.into_iter().for_each(|(open, close)| {
426− test_hooks(
427− in_doc,
428− in_sel,
429− *open,
430− pairs,
431− &Rope::from(get_expected_doc(*open, *close)),
432− actual_sel,
433− )
434− });
435− }
436−
437− // [] indicates range
438−
439− /// [] -> insert ( -> ([])
440− #[test]
441− fn test_insert_blank() {
442− test_hooks_with_pairs(
443− &Rope::from(LINE_END),
444− &Selection::single(1, 0),
445− DEFAULT_PAIRS,
446− DEFAULT_PAIRS,
447− |open, close| format!("{}{}{}", open, close, LINE_END),
448− &Selection::single(2, 1),
449− );
450−
451− let empty_doc = Rope::from(format!("{line_end}{line_end}", line_end = LINE_END));
452−
453− test_hooks_with_pairs(
454− &empty_doc,
455− &Selection::single(empty_doc.len_chars(), LINE_END.len()),
456− DEFAULT_PAIRS,
457− DEFAULT_PAIRS,
458− |open, close| {
459− format!(
460− "{line_end}{open}{close}{line_end}",
461− open = open,
462− close = close,
463− line_end = LINE_END
464− )
465− },
466− &Selection::single(LINE_END.len() + 2, LINE_END.len() + 1),
467− );
468− }
469−
470− #[test]
471− fn test_insert_before_multi_code_point_graphemes() {
472− for (_, close) in differing_pairs() {
473− test_hooks(
474− &Rope::from(format!("hello 👨‍👩‍👧‍👦 goodbye{}", LINE_END)),
475− &Selection::single(13, 6),
476− *close,
477− DEFAULT_PAIRS,
478− &Rope::from(format!("hello {}👨‍👩‍👧‍👦 goodbye{}", close, LINE_END)),
479− &Selection::single(14, 7),
480− );
481− }
482− }
483−
484− #[test]
485− fn test_insert_at_end_of_document() {
486− test_hooks_with_pairs(
487− &Rope::from(LINE_END),
488− &Selection::single(LINE_END.len(), LINE_END.len()),
489− DEFAULT_PAIRS,
490− DEFAULT_PAIRS,
491− |open, close| format!("{}{}{}", LINE_END, open, close),
492− &Selection::single(LINE_END.len() + 1, LINE_END.len() + 1),
493− );
494−
495− test_hooks_with_pairs(
496− &Rope::from(format!("foo{}", LINE_END)),
497− &Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
498− DEFAULT_PAIRS,
499− DEFAULT_PAIRS,
500− |open, close| format!("foo{}{}{}", LINE_END, open, close),
501− &Selection::single(LINE_END.len() + 4, LINE_END.len() + 4),
502− );
503− }
504−
505− /// [] -> append ( -> ([])
506− #[test]
507− fn test_append_blank() {
508− test_hooks_with_pairs(
509− // this is what happens when you have a totally blank document and then append
510− &Rope::from(format!("{line_end}{line_end}", line_end = LINE_END)),
511− // before inserting the pair, the cursor covers all of both empty lines
512− &Selection::single(0, LINE_END.len() * 2),
513− DEFAULT_PAIRS,
514− DEFAULT_PAIRS,
515− |open, close| {
516− format!(
517− "{line_end}{open}{close}{line_end}",
518− line_end = LINE_END,
519− open = open,
520− close = close
521− )
522− },
523− // after inserting pair, the cursor covers the first new line and the open char
524− &Selection::single(0, LINE_END.len() + 2),
525− );
526− }
527−
528− /// [] ([])
529− /// [] -> insert -> ([])
530− /// [] ([])
531− #[test]
532− fn test_insert_blank_multi_cursor() {
533− test_hooks_with_pairs(
534− &Rope::from("\n\n\n"),
535− &Selection::new(
536− smallvec!(Range::new(1, 0), Range::new(2, 1), Range::new(3, 2),),
537− 0,
538− ),
539− DEFAULT_PAIRS,
540− DEFAULT_PAIRS,
541− |open, close| {
542− format!(
543− "{open}{close}\n{open}{close}\n{open}{close}\n",
544− open = open,
545− close = close
546− )
547− },
548− &Selection::new(
549− smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
550− 0,
551− ),
552− );
553− }
554−
555− /// fo[o] -> append ( -> fo[o(])
556− #[test]
557− fn test_append() {
558− test_hooks_with_pairs(
559− &Rope::from("foo\n"),
560− &Selection::single(2, 4),
561− differing_pairs(),
562− DEFAULT_PAIRS,
563− |open, close| format!("foo{}{}\n", open, close),
564− &Selection::single(2, 5),
565− );
566− }
567−
568− /// foo[] -> append to end of line ( -> foo([])
569− #[test]
570− fn test_append_single_cursor() {
571− test_hooks_with_pairs(
572− &Rope::from(format!("foo{}", LINE_END)),
573− &Selection::single(3, 3 + LINE_END.len()),
574− differing_pairs(),
575− DEFAULT_PAIRS,
576− |open, close| format!("foo{}{}{}", open, close, LINE_END),
577− &Selection::single(4, 5),
578− );
579− }
580−
581− /// fo[o] fo[o(])
582− /// fo[o] -> append ( -> fo[o(])
583− /// fo[o] fo[o(])
584− #[test]
585− fn test_append_multi() {
586− test_hooks_with_pairs(
587− &Rope::from("foo\nfoo\nfoo\n"),
588− &Selection::new(
589− smallvec!(Range::new(2, 4), Range::new(6, 8), Range::new(10, 12)),
590− 0,
591− ),
592− differing_pairs(),
593− DEFAULT_PAIRS,
594− |open, close| {
595− format!(
596− "foo{open}{close}\nfoo{open}{close}\nfoo{open}{close}\n",
597− open = open,
598− close = close
599− )
600− },
601− &Selection::new(
602− smallvec!(Range::new(2, 5), Range::new(8, 11), Range::new(14, 17)),
603− 0,
604− ),
605− );
606− }
607−
608− /// ([)] -> insert ) -> ()[]
609− #[test]
610− fn test_insert_close_inside_pair() {
611− for (open, close) in DEFAULT_PAIRS {
612− let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
613−
614− test_hooks(
615− &doc,
616− &Selection::single(2, 1),
617− *close,
618− DEFAULT_PAIRS,
619− &doc,
620− &Selection::single(2 + LINE_END.len(), 2),
621− );
622− }
623− }
624−
625− /// [(]) -> append ) -> [()]
626− #[test]
627− fn test_append_close_inside_pair() {
628− for (open, close) in DEFAULT_PAIRS {
629− let doc = Rope::from(format!("{}{}{}", open, close, LINE_END));
630−
631− test_hooks(
632− &doc,
633− &Selection::single(0, 2),
634− *close,
635− DEFAULT_PAIRS,
636− &doc,
637− &Selection::single(0, 2 + LINE_END.len()),
638− );
639− }
640− }
641−
642− /// ([]) ()[]
643− /// ([]) -> insert ) -> ()[]
644− /// ([]) ()[]
645− #[test]
646− fn test_insert_close_inside_pair_multi_cursor() {
647− let sel = Selection::new(
648− smallvec!(Range::new(2, 1), Range::new(5, 4), Range::new(8, 7),),
649− 0,
650− );
651−
652− let expected_sel = Selection::new(
653− smallvec!(Range::new(3, 2), Range::new(6, 5), Range::new(9, 8),),
654− 0,
655− );
656−
657− for (open, close) in DEFAULT_PAIRS {
658− let doc = Rope::from(format!(
659− "{open}{close}\n{open}{close}\n{open}{close}\n",
660− open = open,
661− close = close
662− ));
663−
664− test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
665− }
666− }
667−
668− /// [(]) [()]
669− /// [(]) -> append ) -> [()]
670− /// [(]) [()]
671− #[test]
672− fn test_append_close_inside_pair_multi_cursor() {
673− let sel = Selection::new(
674− smallvec!(Range::new(0, 2), Range::new(3, 5), Range::new(6, 8),),
675− 0,
676− );
677−
678− let expected_sel = Selection::new(
679− smallvec!(Range::new(0, 3), Range::new(3, 6), Range::new(6, 9),),
680− 0,
681− );
682−
683− for (open, close) in DEFAULT_PAIRS {
684− let doc = Rope::from(format!(
685− "{open}{close}\n{open}{close}\n{open}{close}\n",
686− open = open,
687− close = close
688− ));
689−
690− test_hooks(&doc, &sel, *close, DEFAULT_PAIRS, &doc, &expected_sel);
691− }
692− }
693−
694− /// ([]) -> insert ( -> (([]))
695− #[test]
696− fn test_insert_open_inside_pair() {
697− let sel = Selection::single(2, 1);
698− let expected_sel = Selection::single(3, 2);
699−
700− for (open, close) in differing_pairs() {
701− let doc = Rope::from(format!("{}{}", open, close));
702− let expected_doc = Rope::from(format!(
703− "{open}{open}{close}{close}",
704− open = open,
705− close = close
706− ));
707−
708− test_hooks(
709− &doc,
710− &sel,
711− *open,
712− DEFAULT_PAIRS,
713− &expected_doc,
714− &expected_sel,
715− );
716− }
717− }
718−
719− /// [word(]) -> append ( -> [word((]))
720− #[test]
721− fn test_append_open_inside_pair() {
722− let sel = Selection::single(0, 6);
723− let expected_sel = Selection::single(0, 7);
724−
725− for (open, close) in differing_pairs() {
726− let doc = Rope::from(format!("word{}{}", open, close));
727− let expected_doc = Rope::from(format!(
728− "word{open}{open}{close}{close}",
729− open = open,
730− close = close
731− ));
732−
733− test_hooks(
734− &doc,
735− &sel,
736− *open,
737− DEFAULT_PAIRS,
738− &expected_doc,
739− &expected_sel,
740− );
741− }
742− }
743−
744− /// ([]) -> insert " -> ("[]")
745− #[test]
746− fn test_insert_nested_open_inside_pair() {
747− let sel = Selection::single(2, 1);
748− let expected_sel = Selection::single(3, 2);
749−
750− for (outer_open, outer_close) in differing_pairs() {
751− let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
752−
753− for (inner_open, inner_close) in matching_pairs() {
754− let expected_doc = Rope::from(format!(
755− "{}{}{}{}",
756− outer_open, inner_open, inner_close, outer_close
757− ));
758−
759− test_hooks(
760− &doc,
761− &sel,
762− *inner_open,
763− DEFAULT_PAIRS,
764− &expected_doc,
765− &expected_sel,
766− );
767− }
768− }
769− }
770−
771− /// [(]) -> append " -> [("]")
772− #[test]
773− fn test_append_nested_open_inside_pair() {
774− let sel = Selection::single(0, 2);
775− let expected_sel = Selection::single(0, 3);
776−
777− for (outer_open, outer_close) in differing_pairs() {
778− let doc = Rope::from(format!("{}{}", outer_open, outer_close,));
779−
780− for (inner_open, inner_close) in matching_pairs() {
781− let expected_doc = Rope::from(format!(
782− "{}{}{}{}",
783− outer_open, inner_open, inner_close, outer_close
784− ));
785−
786− test_hooks(
787− &doc,
788− &sel,
789− *inner_open,
790− DEFAULT_PAIRS,
791− &expected_doc,
792− &expected_sel,
793− );
794− }
795− }
796− }
797−
798− /// []word -> insert ( -> ([]word
799− #[test]
800− fn test_insert_open_before_non_pair() {
801− test_hooks_with_pairs(
802− &Rope::from("word"),
803− &Selection::single(1, 0),
804− DEFAULT_PAIRS,
805− DEFAULT_PAIRS,
806− |open, _| format!("{}word", open),
807− &Selection::single(2, 1),
808− )
809− }
810−
811− /// [wor]d -> insert ( -> ([wor]d
812− #[test]
813− fn test_insert_open_with_selection() {
814− test_hooks_with_pairs(
815− &Rope::from("word"),
816− &Selection::single(3, 0),
817− DEFAULT_PAIRS,
818− DEFAULT_PAIRS,
819− |open, _| format!("{}word", open),
820− &Selection::single(4, 1),
821− )
822− }
823−
824− /// [wor]d -> append ) -> [wor)]d
825− #[test]
826− fn test_append_close_inside_non_pair_with_selection() {
827− let sel = Selection::single(0, 4);
828− let expected_sel = Selection::single(0, 5);
829−
830− for (_, close) in DEFAULT_PAIRS {
831− let doc = Rope::from("word");
832− let expected_doc = Rope::from(format!("wor{}d", close));
833− test_hooks(
834− &doc,
835− &sel,
836− *close,
837− DEFAULT_PAIRS,
838− &expected_doc,
839− &expected_sel,
840− );
841− }
842− }
843−
844− /// foo[ wor]d -> insert ( -> foo([) wor]d
845− #[test]
846− fn test_insert_open_trailing_word_with_selection() {
847− test_hooks_with_pairs(
848− &Rope::from("foo word"),
849− &Selection::single(7, 3),
850− differing_pairs(),
851− DEFAULT_PAIRS,
852− |open, close| format!("foo{}{} word", open, close),
853− &Selection::single(9, 4),
854− )
855− }
856−
857− /// foo([) wor]d -> insert ) -> foo()[ wor]d
858− #[test]
859− fn test_insert_close_inside_pair_trailing_word_with_selection() {
860− for (open, close) in differing_pairs() {
861− test_hooks(
862− &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
863− &Selection::single(9, 4),
864− *close,
865− DEFAULT_PAIRS,
866− &Rope::from(format!("foo{}{} word{}", open, close, LINE_END)),
867− &Selection::single(9, 5),
868− )
869− }
870− }
871−
872− /// we want pairs that are *not* the same char to be inserted after
873− /// a non-pair char, for cases like functions, but for pairs that are
874− /// the same char, we want to *not* insert a pair to handle cases like "I'm"
875− ///
876− /// word[] -> insert ( -> word([])
877− /// word[] -> insert ' -> word'[]
878− #[test]
879− fn test_insert_open_after_non_pair() {
880− let doc = Rope::from(format!("word{}", LINE_END));
881− let sel = Selection::single(5, 4);
882− let expected_sel = Selection::single(6, 5);
883−
884− test_hooks_with_pairs(
885− &doc,
886− &sel,
887− differing_pairs(),
888− DEFAULT_PAIRS,
889− |open, close| format!("word{}{}{}", open, close, LINE_END),
890− &expected_sel,
891− );
892−
893− test_hooks_with_pairs(
894− &doc,
895− &sel,
896− matching_pairs(),
897− DEFAULT_PAIRS,
898− |open, _| format!("word{}{}", open, LINE_END),
899− &expected_sel,
900− );
901− }
902−
903− #[test]
904− fn test_configured_pairs() {
905− let test_pairs = &[('`', ':'), ('+', '-')];
906−
907− test_hooks_with_pairs(
908− &Rope::from(LINE_END),
909− &Selection::single(1, 0),
910− test_pairs,
911− test_pairs,
912− |open, close| format!("{}{}{}", open, close, LINE_END),
913− &Selection::single(2, 1),
914− );
915−
916− let doc = Rope::from(format!("foo`: word{}", LINE_END));
917−
918− test_hooks(
919− &doc,
920− &Selection::single(9, 4),
921− ':',
922− test_pairs,
923− &doc,
924− &Selection::single(9, 5),
925− )
926− }
927−}
helix-core/src/test.rs+124−3
@@ -34,7 +34,7 @@ pub fn print(s: &str) -> (String, Selection) {
34 let mut left = String::with_capacity(s.len());
34 let mut left = String::with_capacity(s.len());
35
35
36 'outer: while let Some(c) = iter.next() {
36 'outer: while let Some(c) = iter.next() {
37− let start = left.len();
37+ let start = left.chars().count();
38
38
39 if c != '#' {
39 if c != '#' {
40 left.push(c);
40 left.push(c);
@@ -63,6 +63,7 @@ pub fn print(s: &str) -> (String, Selection) {
63 left.push(c);
63 left.push(c);
64 continue;
64 continue;
65 }
65 }
66+
66 if !head_at_beg {
67 if !head_at_beg {
67 let prev = left.pop().unwrap();
68 let prev = left.pop().unwrap();
68 if prev != '|' {
69 if prev != '|' {
@@ -71,15 +72,18 @@ pub fn print(s: &str) -> (String, Selection) {
71 continue;
72 continue;
72 }
73 }
73 }
74 }
75+
74 iter.next(); // skip "#"
76 iter.next(); // skip "#"
75
77
76 if is_primary {
78 if is_primary {
77 primary_idx = Some(ranges.len());
79 primary_idx = Some(ranges.len());
78 }
80 }
81+
79 let (anchor, head) = match head_at_beg {
82 let (anchor, head) = match head_at_beg {
80− true => (left.len(), start),
83+ true => (left.chars().count(), start),
81− false => (start, left.len()),
84+ false => (start, left.chars().count()),
82 };
85 };
86+
83 ranges.push(Range::new(anchor, head));
87 ranges.push(Range::new(anchor, head));
84 continue 'outer;
88 continue 'outer;
85 }
89 }
@@ -95,6 +99,7 @@ pub fn print(s: &str) -> (String, Selection) {
95 Some(i) => i,
99 Some(i) => i,
96 None => panic!("missing primary `#[|]#` {:?}", s),
100 None => panic!("missing primary `#[|]#` {:?}", s),
97 };
101 };
102+
98 let selection = Selection::new(ranges, primary);
103 let selection = Selection::new(ranges, primary);
99 (left, selection)
104 (left, selection)
100}
105}
@@ -141,3 +146,119 @@ pub fn plain(s: &str, selection: Selection) -> String {
141 }
146 }
142 out
147 out
143}
148}
149+
150+#[cfg(test)]
151+mod test {
152+ use super::*;
153+
154+ #[test]
155+ fn print_single() {
156+ assert_eq!(
157+ (String::from("hello"), Selection::single(1, 0)),
158+ print("#[|h]#ello")
159+ );
160+ assert_eq!(
161+ (String::from("hello"), Selection::single(0, 1)),
162+ print("#[h|]#ello")
163+ );
164+ assert_eq!(
165+ (String::from("hello"), Selection::single(4, 0)),
166+ print("#[|hell]#o")
167+ );
168+ assert_eq!(
169+ (String::from("hello"), Selection::single(0, 4)),
170+ print("#[hell|]#o")
171+ );
172+ assert_eq!(
173+ (String::from("hello"), Selection::single(5, 0)),
174+ print("#[|hello]#")
175+ );
176+ assert_eq!(
177+ (String::from("hello"), Selection::single(0, 5)),
178+ print("#[hello|]#")
179+ );
180+ }
181+
182+ #[test]
183+ fn print_multi() {
184+ assert_eq!(
185+ (
186+ String::from("hello"),
187+ Selection::new(
188+ SmallVec::from_slice(&[Range::new(1, 0), Range::new(5, 4)]),
189+ 0
190+ )
191+ ),
192+ print("#[|h]#ell#(|o)#")
193+ );
194+ assert_eq!(
195+ (
196+ String::from("hello"),
197+ Selection::new(
198+ SmallVec::from_slice(&[Range::new(0, 1), Range::new(4, 5)]),
199+ 0
200+ )
201+ ),
202+ print("#[h|]#ell#(o|)#")
203+ );
204+ assert_eq!(
205+ (
206+ String::from("hello"),
207+ Selection::new(
208+ SmallVec::from_slice(&[Range::new(2, 0), Range::new(5, 3)]),
209+ 0
210+ )
211+ ),
212+ print("#[|he]#l#(|lo)#")
213+ );
214+ assert_eq!(
215+ (
216+ String::from("hello\r\nhello\r\nhello\r\n"),
217+ Selection::new(
218+ SmallVec::from_slice(&[
219+ Range::new(7, 5),
220+ Range::new(21, 19),
221+ Range::new(14, 12)
222+ ]),
223+ 0
224+ )
225+ ),
226+ print("hello#[|\r\n]#hello#(|\r\n)#hello#(|\r\n)#")
227+ );
228+ }
229+
230+ #[test]
231+ fn print_multi_byte_code_point() {
232+ assert_eq!(
233+ (String::from("„“"), Selection::single(1, 0)),
234+ print("#[|„]#“")
235+ );
236+ assert_eq!(
237+ (String::from("„“"), Selection::single(2, 1)),
238+ print("„#[|“]#")
239+ );
240+ assert_eq!(
241+ (String::from("„“"), Selection::single(0, 1)),
242+ print("#[„|]#“")
243+ );
244+ assert_eq!(
245+ (String::from("„“"), Selection::single(1, 2)),
246+ print("„#[“|]#")
247+ );
248+ assert_eq!(
249+ (String::from("they said „hello“"), Selection::single(11, 10)),
250+ print("they said #[|„]#hello“")
251+ );
252+ }
253+
254+ #[test]
255+ fn print_multi_code_point_grapheme() {
256+ assert_eq!(
257+ (
258+ String::from("hello 👨‍👩‍👧‍👦 goodbye"),
259+ Selection::single(13, 6)
260+ ),
261+ print("hello #[|👨‍👩‍👧‍👦]# goodbye")
262+ );
263+ }
264+}
helix-term/tests/test/auto_pairs.rs+538−13
@@ -1,22 +1,547 @@
1+use helix_core::{auto_pairs::DEFAULT_PAIRS, hashmap};
2+
1use super::*;
3use super::*;
2
4
5+const LINE_END: &str = helix_core::DEFAULT_LINE_ENDING.as_str();
6+
7+fn differing_pairs() -> impl Iterator<Item = &'static (char, char)> {
8+ DEFAULT_PAIRS.iter().filter(|(open, close)| open != close)
9+}
10+
11+fn matching_pairs() -> impl Iterator<Item = &'static (char, char)> {
12+ DEFAULT_PAIRS.iter().filter(|(open, close)| open == close)
13+}
14+
3#[tokio::test]
15#[tokio::test]
4−async fn auto_pairs_basic() -> anyhow::Result<()> {
16+async fn insert_basic() -> anyhow::Result<()> {
17+ for pair in DEFAULT_PAIRS {
18+ test((
19+ format!("#[{}|]#", LINE_END),
20+ format!("i{}", pair.0),
21+ format!("{}#[|{}]#{}", pair.0, pair.1, LINE_END),
22+ ))
5− test(("#[\n|]#", "i(<esc>", "(#[|)]#\n")).await?;
23+ .await?;
24+ }
6
25
7− test_with_config(
26+ Ok(())
8− Args::default(),
27+}
28+
29+#[tokio::test]
30+async fn insert_configured_multi_byte_chars() -> anyhow::Result<()> {
31+ // NOTE: these are multi-byte Unicode characters
32+ let pairs = hashmap!('„' => '“', '‚' => '‘', '「' => '」');
33+
9− Config {
34+ let config = Config {
10− editor: helix_view::editor::Config {
35+ editor: helix_view::editor::Config {
11− auto_pairs: AutoPairConfig::Enable(false),
36+ auto_pairs: AutoPairConfig::Pairs(pairs.clone()),
12− ..Default::default()
13− },
14 ..Default::default()
37 ..Default::default()
15 },
38 },
39+ ..Default::default()
40+ };
41+
42+ for (open, close) in pairs.iter() {
43+ test_with_config(
44+ Args::default(),
45+ config.clone(),
16− helpers::test_syntax_conf(None),
46+ helpers::test_syntax_conf(None),
17− ("#[\n|]#", "i(<esc>", "(#[|\n]#"),
47+ (
18− )
48+ format!("#[{}|]#", LINE_END),
49+ format!("i{}", open),
50+ format!("{}#[|{}]#{}", open, close, LINE_END),
51+ ),
52+ )
53+ .await?;
54+
55+ test_with_config(
56+ Args::default(),
57+ config.clone(),
58+ helpers::test_syntax_conf(None),
59+ (
60+ format!("{}#[{}|]#{}", open, close, LINE_END),
61+ format!("i{}", close),
62+ format!("{}{}#[|{}]#", open, close, LINE_END),
63+ ),
64+ )
65+ .await?;
66+ }
67+
68+ Ok(())
69+}
70+
71+#[tokio::test]
72+async fn insert_after_word() -> anyhow::Result<()> {
73+ for pair in differing_pairs() {
74+ test((
75+ format!("foo#[{}|]#", LINE_END),
76+ format!("i{}", pair.0),
77+ format!("foo{}#[|{}]#{}", pair.0, pair.1, LINE_END),
78+ ))
79+ .await?;
80+ }
81+
82+ for pair in matching_pairs() {
83+ test((
84+ format!("foo#[{}|]#", LINE_END),
85+ format!("i{}", pair.0),
86+ format!("foo{}#[|{}]#", pair.0, LINE_END),
87+ ))
88+ .await?;
89+ }
90+
91+ Ok(())
92+}
93+
94+#[tokio::test]
95+async fn insert_before_word() -> anyhow::Result<()> {
96+ for pair in DEFAULT_PAIRS {
97+ test((
98+ format!("#[f|]#oo{}", LINE_END),
99+ format!("i{}", pair.0),
100+ format!("{}#[|f]#oo{}", pair.0, LINE_END),
101+ ))
19− .await?;
102+ .await?;
103+ }
104+
105+ Ok(())
106+}
107+
108+#[tokio::test]
109+async fn insert_before_word_selection() -> anyhow::Result<()> {
110+ for pair in DEFAULT_PAIRS {
111+ test((
112+ format!("#[foo|]#{}", LINE_END),
113+ format!("i{}", pair.0),
114+ format!("{}#[|foo]#{}", pair.0, LINE_END),
115+ ))
116+ .await?;
117+ }
118+
119+ Ok(())
120+}
121+
122+#[tokio::test]
123+async fn insert_before_word_selection_trailing_word() -> anyhow::Result<()> {
124+ for pair in differing_pairs() {
125+ test((
126+ format!("foo#[ wor|]#{}", LINE_END),
127+ format!("i{}", pair.0),
128+ format!("foo{}#[|{} wor]#{}", pair.0, pair.1, LINE_END),
129+ ))
130+ .await?;
131+ }
132+
133+ Ok(())
134+}
135+
136+#[tokio::test]
137+async fn insert_closer_selection_trailing_word() -> anyhow::Result<()> {
138+ for pair in differing_pairs() {
139+ test((
140+ format!("foo{}#[|{} wor]#{}", pair.0, pair.1, LINE_END),
141+ format!("i{}", pair.1),
142+ format!("foo{}{}#[| wor]#{}", pair.0, pair.1, LINE_END),
143+ ))
144+ .await?;
145+ }
146+
147+ Ok(())
148+}
149+
150+#[tokio::test]
151+async fn insert_before_eol() -> anyhow::Result<()> {
152+ for pair in DEFAULT_PAIRS {
153+ test((
154+ format!("{0}#[{0}|]#", LINE_END),
155+ format!("i{}", pair.0),
156+ format!(
157+ "{eol}{open}#[|{close}]#{eol}",
158+ eol = LINE_END,
159+ open = pair.0,
160+ close = pair.1
161+ ),
162+ ))
163+ .await?;
164+ }
165+
166+ Ok(())
167+}
168+
169+#[tokio::test]
170+async fn insert_auto_pairs_disabled() -> anyhow::Result<()> {
171+ for pair in DEFAULT_PAIRS {
172+ test_with_config(
173+ Args::default(),
174+ Config {
175+ editor: helix_view::editor::Config {
176+ auto_pairs: AutoPairConfig::Enable(false),
177+ ..Default::default()
178+ },
179+ ..Default::default()
180+ },
181+ helpers::test_syntax_conf(None),
182+ (
183+ format!("#[{}|]#", LINE_END),
184+ format!("i{}", pair.0),
185+ format!("{}#[|{}]#", pair.0, LINE_END),
186+ ),
187+ )
188+ .await?;
189+ }
190+
191+ Ok(())
192+}
193+
194+#[tokio::test]
195+async fn insert_multi_range() -> anyhow::Result<()> {
196+ for pair in DEFAULT_PAIRS {
197+ test((
198+ format!("#[{eol}|]##({eol}|)##({eol}|)#", eol = LINE_END),
199+ format!("i{}", pair.0),
200+ format!(
201+ "{open}#[|{close}]#{eol}{open}#(|{close})#{eol}{open}#(|{close})#{eol}",
202+ open = pair.0,
203+ close = pair.1,
204+ eol = LINE_END
205+ ),
206+ ))
207+ .await?;
208+ }
209+
210+ Ok(())
211+}
212+
213+#[tokio::test]
214+async fn insert_before_multi_code_point_graphemes() -> anyhow::Result<()> {
215+ for pair in differing_pairs() {
216+ test((
217+ format!("hello #[👨‍👩‍👧‍👦|]# goodbye{}", LINE_END),
218+ format!("i{}", pair.1),
219+ format!("hello {}#[|👨‍👩‍👧‍👦]# goodbye{}", pair.1, LINE_END),
220+ ))
221+ .await?;
222+ }
223+ Ok(())
224+}
225+
226+#[tokio::test]
227+async fn insert_at_end_of_document() -> anyhow::Result<()> {
228+ for pair in DEFAULT_PAIRS {
229+ test(TestCase {
230+ in_text: String::from(LINE_END),
231+ in_selection: Selection::single(LINE_END.len(), LINE_END.len()),
232+ in_keys: format!("i{}", pair.0),
233+ out_text: format!("{}{}{}", LINE_END, pair.0, pair.1),
234+ out_selection: Selection::single(LINE_END.len() + 1, LINE_END.len() + 2),
235+ })
236+ .await?;
237+
238+ test(TestCase {
239+ in_text: format!("foo{}", LINE_END),
240+ in_selection: Selection::single(3 + LINE_END.len(), 3 + LINE_END.len()),
241+ in_keys: format!("i{}", pair.0),
242+ out_text: format!("foo{}{}{}", LINE_END, pair.0, pair.1),
243+ out_selection: Selection::single(LINE_END.len() + 4, LINE_END.len() + 5),
244+ })
245+ .await?;
246+ }
247+
248+ Ok(())
249+}
250+
251+#[tokio::test]
252+async fn insert_close_inside_pair() -> anyhow::Result<()> {
253+ for pair in DEFAULT_PAIRS {
254+ test((
255+ format!(
256+ "{open}#[{close}|]#{eol}",
257+ open = pair.0,
258+ close = pair.1,
259+ eol = LINE_END
260+ ),
261+ format!("i{}", pair.1),
262+ format!(
263+ "{open}{close}#[|{eol}]#",
264+ open = pair.0,
265+ close = pair.1,
266+ eol = LINE_END
267+ ),
268+ ))
269+ .await?;
270+ }
271+
272+ Ok(())
273+}
274+
275+#[tokio::test]
276+async fn insert_close_inside_pair_multi() -> anyhow::Result<()> {
277+ for pair in DEFAULT_PAIRS {
278+ test((
279+ format!(
280+ "{open}#[{close}|]#{eol}{open}#({close}|)#{eol}{open}#({close}|)#{eol}",
281+ open = pair.0,
282+ close = pair.1,
283+ eol = LINE_END
284+ ),
285+ format!("i{}", pair.1),
286+ format!(
287+ "{open}{close}#[|{eol}]#{open}{close}#(|{eol})#{open}{close}#(|{eol})#",
288+ open = pair.0,
289+ close = pair.1,
290+ eol = LINE_END
291+ ),
292+ ))
293+ .await?;
294+ }
295+
296+ Ok(())
297+}
298+
299+#[tokio::test]
300+async fn insert_nested_open_inside_pair() -> anyhow::Result<()> {
301+ for pair in differing_pairs() {
302+ test((
303+ format!(
304+ "{open}#[{close}|]#{eol}",
305+ open = pair.0,
306+ close = pair.1,
307+ eol = LINE_END
308+ ),
309+ format!("i{}", pair.0),
310+ format!(
311+ "{open}{open}#[|{close}]#{close}{eol}",
312+ open = pair.0,
313+ close = pair.1,
314+ eol = LINE_END
315+ ),
316+ ))
317+ .await?;
318+ }
319+
320+ Ok(())
321+}
322+
323+#[tokio::test]
324+async fn insert_nested_open_inside_pair_multi() -> anyhow::Result<()> {
325+ for outer_pair in DEFAULT_PAIRS {
326+ for inner_pair in DEFAULT_PAIRS {
327+ if inner_pair.0 == outer_pair.0 {
328+ continue;
329+ }
330+
331+ test((
332+ format!(
333+ "{outer_open}#[{outer_close}|]#{eol}{outer_open}#({outer_close}|)#{eol}{outer_open}#({outer_close}|)#{eol}",
334+ outer_open = outer_pair.0,
335+ outer_close = outer_pair.1,
336+ eol = LINE_END
337+ ),
338+ format!("i{}", inner_pair.0),
339+ format!(
340+ "{outer_open}{inner_open}#[|{inner_close}]#{outer_close}{eol}{outer_open}{inner_open}#(|{inner_close})#{outer_close}{eol}{outer_open}{inner_open}#(|{inner_close})#{outer_close}{eol}",
341+ outer_open = outer_pair.0,
342+ outer_close = outer_pair.1,
343+ inner_open = inner_pair.0,
344+ inner_close = inner_pair.1,
345+ eol = LINE_END
346+ ),
347+ ))
348+ .await?;
349+ }
350+ }
351+
352+ Ok(())
353+}
354+
355+#[tokio::test]
356+async fn append_basic() -> anyhow::Result<()> {
357+ for pair in DEFAULT_PAIRS {
358+ test((
359+ format!("#[{}|]#", LINE_END),
360+ format!("a{}", pair.0),
361+ format!(
362+ "#[{eol}{open}{close}|]#{eol}",
363+ open = pair.0,
364+ close = pair.1,
365+ eol = LINE_END
366+ ),
367+ ))
368+ .await?;
369+ }
370+
371+ Ok(())
372+}
373+
374+#[tokio::test]
375+async fn append_multi_range() -> anyhow::Result<()> {
376+ for pair in DEFAULT_PAIRS {
377+ test((
378+ format!("#[ |]#{eol}#( |)#{eol}#( |)#{eol}", eol = LINE_END),
379+ format!("a{}", pair.0),
380+ format!(
381+ "#[ {open}{close}|]#{eol}#( {open}{close}|)#{eol}#( {open}{close}|)#{eol}",
382+ open = pair.0,
383+ close = pair.1,
384+ eol = LINE_END
385+ ),
386+ ))
387+ .await?;
388+ }
389+
390+ Ok(())
391+}
392+
393+#[tokio::test]
394+async fn append_close_inside_pair() -> anyhow::Result<()> {
395+ for pair in DEFAULT_PAIRS {
396+ test((
397+ format!(
398+ "#[{open}|]#{close}{eol}",
399+ open = pair.0,
400+ close = pair.1,
401+ eol = LINE_END
402+ ),
403+ format!("a{}", pair.1),
404+ format!(
405+ "#[{open}{close}{eol}|]#",
406+ open = pair.0,
407+ close = pair.1,
408+ eol = LINE_END
409+ ),
410+ ))
411+ .await?;
412+ }
413+
414+ Ok(())
415+}
416+
417+#[tokio::test]
418+async fn append_close_inside_pair_multi() -> anyhow::Result<()> {
419+ for pair in DEFAULT_PAIRS {
420+ test((
421+ format!(
422+ "#[{open}|]#{close}{eol}#({open}|)#{close}{eol}#({open}|)#{close}{eol}",
423+ open = pair.0,
424+ close = pair.1,
425+ eol = LINE_END
426+ ),
427+ format!("a{}", pair.1),
428+ format!(
429+ "#[{open}{close}{eol}|]##({open}{close}{eol}|)##({open}{close}{eol}|)#",
430+ open = pair.0,
431+ close = pair.1,
432+ eol = LINE_END
433+ ),
434+ ))
435+ .await?;
436+ }
437+
438+ Ok(())
439+}
440+
441+#[tokio::test]
442+async fn append_end_of_word() -> anyhow::Result<()> {
443+ for pair in differing_pairs() {
444+ test((
445+ format!("fo#[o|]#{}", LINE_END),
446+ format!("a{}", pair.0),
447+ format!(
448+ "fo#[o{open}{close}|]#{eol}",
449+ open = pair.0,
450+ close = pair.1,
451+ eol = LINE_END
452+ ),
453+ ))
454+ .await?;
455+ }
456+
457+ Ok(())
458+}
459+
460+#[tokio::test]
461+async fn append_middle_of_word() -> anyhow::Result<()> {
462+ for pair in differing_pairs() {
463+ test((
464+ format!("#[wo|]#rd{}", LINE_END),
465+ format!("a{}", pair.1),
466+ format!("#[wo{}r|]#d{}", pair.1, LINE_END),
467+ ))
468+ .await?;
469+ }
470+
471+ Ok(())
472+}
473+
474+#[tokio::test]
475+async fn append_end_of_word_multi() -> anyhow::Result<()> {
476+ for pair in differing_pairs() {
477+ test((
478+ format!("fo#[o|]#{eol}fo#(o|)#{eol}fo#(o|)#{eol}", eol = LINE_END),
479+ format!("a{}", pair.0),
480+ format!(
481+ "fo#[o{open}{close}|]#{eol}fo#(o{open}{close}|)#{eol}fo#(o{open}{close}|)#{eol}",
482+ open = pair.0,
483+ close = pair.1,
484+ eol = LINE_END
485+ ),
486+ ))
487+ .await?;
488+ }
489+
490+ Ok(())
491+}
492+
493+#[tokio::test]
494+async fn append_inside_nested_pair() -> anyhow::Result<()> {
495+ for pair in differing_pairs() {
496+ test((
497+ format!(
498+ "f#[oo{open}|]#{close}{eol}",
499+ open = pair.0,
500+ close = pair.1,
501+ eol = LINE_END
502+ ),
503+ format!("a{}", pair.0),
504+ format!(
505+ "f#[oo{open}{open}{close}|]#{close}{eol}",
506+ open = pair.0,
507+ close = pair.1,
508+ eol = LINE_END
509+ ),
510+ ))
511+ .await?;
512+ }
513+
514+ Ok(())
515+}
516+
517+#[tokio::test]
518+async fn append_inside_nested_pair_multi() -> anyhow::Result<()> {
519+ for outer_pair in DEFAULT_PAIRS {
520+ for inner_pair in DEFAULT_PAIRS {
521+ if inner_pair.0 == outer_pair.0 {
522+ continue;
523+ }
524+
525+ test((
526+ format!(
527+ "f#[oo{outer_open}|]#{outer_close}{eol}f#(oo{outer_open}|)#{outer_close}{eol}f#(oo{outer_open}|)#{outer_close}{eol}",
528+ outer_open = outer_pair.0,
529+ outer_close = outer_pair.1,
530+ eol = LINE_END
531+ ),
532+ format!("a{}", inner_pair.0),
533+ format!(
534+ "f#[oo{outer_open}{inner_open}{inner_close}|]#{outer_close}{eol}f#(oo{outer_open}{inner_open}{inner_close}|)#{outer_close}{eol}f#(oo{outer_open}{inner_open}{inner_close}|)#{outer_close}{eol}",
535+ outer_open = outer_pair.0,
536+ outer_close = outer_pair.1,
537+ inner_open = inner_pair.0,
538+ inner_close = inner_pair.1,
539+ eol = LINE_END
540+ ),
541+ ))
542+ .await?;
543+ }
544+ }
20
545
21 Ok(())
546 Ok(())
22}
547}
helix-view/src/clipboard.rs+1−1
@@ -17,7 +17,7 @@ pub trait ClipboardProvider: std::fmt::Debug {
17#[cfg(not(windows))]
17#[cfg(not(windows))]
18macro_rules! command_provider {
18macro_rules! command_provider {
19 (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
19 (paste => $get_prg:literal $( , $get_arg:literal )* ; copy => $set_prg:literal $( , $set_arg:literal )* ; ) => {{
20− log::info!(
20+ log::debug!(
21 "Using {} to interact with the system clipboard",
21 "Using {} to interact with the system clipboard",
22 if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
22 if $set_prg != $get_prg { format!("{}+{}", $set_prg, $get_prg)} else { $set_prg.to_string() }
23 );
23 );