1mod on_enter;
17
18use either::Either;
19use hir::EditionedFileId;
20use ide_db::{
21 FilePosition, RootDatabase,
22 base_db::{RootQueryDb, SourceDatabase},
23};
24use span::Edition;
25use std::iter;
26
27use syntax::{
28 AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize,
29 algo::{ancestors_at_offset, find_node_at_offset},
30 ast::{self, AstToken, edit::IndentLevel},
31};
32
33use ide_db::text_edit::TextEdit;
34
35use crate::SourceChange;
36
37pub(crate) use on_enter::on_enter;
38
39pub(crate) const TRIGGER_CHARS: &[char] = &['.', '=', '<', '>', '{', '(', '|', '+'];
41
42struct ExtendedTextEdit {
43 edit: TextEdit,
44 is_snippet: bool,
45}
46
47pub(crate) fn on_char_typed(
69 db: &RootDatabase,
70 position: FilePosition,
71 char_typed: char,
72) -> Option<SourceChange> {
73 if !TRIGGER_CHARS.contains(&char_typed) {
74 return None;
75 }
76 let edition = db
77 .source_root_crates(db.file_source_root(position.file_id).source_root_id(db))
78 .first()
79 .map_or(Edition::CURRENT, |crates| crates.data(db).edition);
80 let editioned_file_id_wrapper = EditionedFileId::from_span_guess_origin(
83 db,
84 span::EditionedFileId::new(position.file_id, edition),
85 );
86 let file = &db.parse(editioned_file_id_wrapper);
87 let char_matches_position =
88 file.tree().syntax().text().char_at(position.offset) == Some(char_typed);
89 if !stdx::always!(char_matches_position) {
90 return None;
91 }
92
93 let edit = on_char_typed_(file, position.offset, char_typed, edition)?;
94
95 let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit);
96 sc.is_snippet = edit.is_snippet;
97 Some(sc)
98}
99
100fn on_char_typed_(
101 file: &Parse<SourceFile>,
102 offset: TextSize,
103 char_typed: char,
104 edition: Edition,
105) -> Option<ExtendedTextEdit> {
106 match char_typed {
107 '.' => on_dot_typed(&file.tree(), offset),
108 '=' => on_eq_typed(&file.tree(), offset),
109 '>' => on_right_angle_typed(&file.tree(), offset),
110 '{' | '(' | '<' => on_opening_delimiter_typed(file, offset, char_typed, edition),
111 '|' => on_pipe_typed(&file.tree(), offset),
112 '+' => on_plus_typed(&file.tree(), offset),
113 _ => None,
114 }
115 .map(conv)
116}
117
118fn conv(edit: TextEdit) -> ExtendedTextEdit {
119 ExtendedTextEdit { edit, is_snippet: false }
120}
121
122fn on_opening_delimiter_typed(
125 file: &Parse<SourceFile>,
126 offset: TextSize,
127 opening_bracket: char,
128 edition: Edition,
129) -> Option<TextEdit> {
130 type FilterFn = fn(SyntaxKind) -> bool;
131 let (closing_bracket, expected_ast_bracket, allowed_kinds) = match opening_bracket {
132 '{' => ('}', SyntaxKind::L_CURLY, &[ast::Expr::can_cast as FilterFn] as &[FilterFn]),
133 '(' => (
134 ')',
135 SyntaxKind::L_PAREN,
136 &[ast::Expr::can_cast as FilterFn, ast::Pat::can_cast, ast::Type::can_cast]
137 as &[FilterFn],
138 ),
139 '<' => ('>', SyntaxKind::L_ANGLE, &[ast::Type::can_cast as FilterFn] as &[FilterFn]),
140 _ => return None,
141 };
142
143 let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
144 if brace_token.kind() != expected_ast_bracket {
145 return None;
146 }
147
148 let range = brace_token.text_range();
150 if !stdx::always!(range.len() == TextSize::of(opening_bracket)) {
151 return None;
152 }
153 let reparsed = file.reparse(range, "", edition).tree();
154
155 if let Some(edit) =
156 on_delimited_node_typed(&reparsed, offset, opening_bracket, closing_bracket, allowed_kinds)
157 {
158 return Some(edit);
159 }
160
161 match opening_bracket {
162 '{' => on_left_brace_typed(&reparsed, offset),
163 '<' => on_left_angle_typed(&file.tree(), &reparsed, offset),
164 _ => None,
165 }
166}
167
168fn on_left_brace_typed(reparsed: &SourceFile, offset: TextSize) -> Option<TextEdit> {
169 let segment: ast::PathSegment = find_node_at_offset(reparsed.syntax(), offset)?;
170 if segment.syntax().text_range().start() != offset {
171 return None;
172 }
173
174 let tree: ast::UseTree = find_node_at_offset(reparsed.syntax(), offset)?;
175
176 Some(TextEdit::insert(tree.syntax().text_range().end() + TextSize::of("{"), "}".to_owned()))
177}
178
179fn on_delimited_node_typed(
180 reparsed: &SourceFile,
181 offset: TextSize,
182 opening_bracket: char,
183 closing_bracket: char,
184 kinds: &[fn(SyntaxKind) -> bool],
185) -> Option<TextEdit> {
186 let t = reparsed.syntax().token_at_offset(offset).right_biased()?;
187 if t.prev_token().is_some_and(|t| t.kind().is_any_identifier()) {
188 return None;
189 }
190 let (filter, node) = t
191 .parent_ancestors()
192 .take_while(|n| n.text_range().start() == offset)
193 .find_map(|n| kinds.iter().find(|&kind_filter| kind_filter(n.kind())).zip(Some(n)))?;
194 let mut node = node
195 .ancestors()
196 .take_while(|n| n.text_range().start() == offset && filter(n.kind()))
197 .last()?;
198
199 if let Some(parent) = node.parent().filter(|it| filter(it.kind())) {
200 let all_prev_sib_attr = {
201 let mut node = node.clone();
202 loop {
203 match node.prev_sibling() {
204 Some(sib) if sib.kind().is_trivia() || sib.kind() == SyntaxKind::ATTR => {
205 node = sib
206 }
207 Some(_) => break false,
208 None => break true,
209 };
210 }
211 };
212
213 if all_prev_sib_attr {
214 node = parent;
215 }
216 }
217
218 Some(TextEdit::insert(
220 node.text_range().end() + TextSize::of(opening_bracket),
221 closing_bracket.to_string(),
222 ))
223}
224fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
228 let text = file.syntax().text();
229 let has_newline = iter::successors(Some(offset), |&offset| Some(offset + TextSize::new(1)))
230 .filter_map(|offset| text.char_at(offset))
231 .find(|&c| !c.is_whitespace() || c == '\n')
232 == Some('n');
233 if has_newline {
236 return None;
237 }
238
239 if let Some(edit) = let_stmt(file, offset) {
240 return Some(edit);
241 }
242 if let Some(edit) = assign_expr(file, offset) {
243 return Some(edit);
244 }
245 if let Some(edit) = assign_to_eq(file, offset) {
246 return Some(edit);
247 }
248
249 return None;
250
251 fn assign_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
252 let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
253 if !matches!(binop.op_kind(), Some(ast::BinaryOp::Assignment { op: None })) {
254 return None;
255 }
256
257 if let Some(expr_stmt) = ast::ExprStmt::cast(binop.syntax().parent()?) {
259 if expr_stmt.semicolon_token().is_some() {
260 return None;
261 }
262 } else if !ast::StmtList::can_cast(binop.syntax().parent()?.kind()) {
263 return None;
264 }
265
266 let expr = binop.rhs()?;
267 let expr_range = expr.syntax().text_range();
268 if expr_range.contains(offset) && offset != expr_range.start() {
269 return None;
270 }
271 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
272 return None;
273 }
274 let offset = expr.syntax().text_range().end();
275 Some(TextEdit::insert(offset, ";".to_owned()))
276 }
277
278 fn assign_to_eq(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
280 let binop: ast::BinExpr = find_node_at_offset(file.syntax(), offset)?;
281 if !matches!(binop.op_kind(), Some(ast::BinaryOp::CmpOp(ast::CmpOp::Eq { negated: false })))
282 {
283 return None;
284 }
285
286 let expr_stmt = ast::ExprStmt::cast(binop.syntax().parent()?)?;
287 let semi = expr_stmt.semicolon_token()?;
288
289 if expr_stmt.syntax().next_sibling().is_some() {
290 return None;
292 }
293
294 Some(TextEdit::delete(semi.text_range()))
295 }
296
297 fn let_stmt(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
298 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
299 if let_stmt.semicolon_token().is_some() {
300 return None;
301 }
302 let expr = let_stmt.initializer()?;
303 let expr_range = expr.syntax().text_range();
304 if expr_range.contains(offset) && offset != expr_range.start() {
305 return None;
306 }
307 if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') {
308 return None;
309 }
310 if expr.syntax().descendants().any(|it| it.kind() == SyntaxKind::ERROR) {
312 return None;
313 }
314 let offset = let_stmt.syntax().text_range().end();
315 Some(TextEdit::insert(offset, ";".to_owned()))
316 }
317}
318
319fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
321 let whitespace =
322 file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
323
324 let current_indent = {
327 let text = whitespace.text();
328 let (_prefix, suffix) = text.rsplit_once('\n')?;
329 suffix
330 };
331 let current_indent_len = TextSize::of(current_indent);
332
333 let parent = whitespace.syntax().parent()?;
334 let receiver = if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) {
336 field_expr.expr()?
337 } else if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent.clone()) {
338 method_call_expr.receiver()?
339 } else {
340 return None;
341 };
342
343 let receiver_is_multiline = receiver.syntax().text().find_char('\n').is_some();
344 let target_indent = match (receiver, receiver_is_multiline) {
345 (ast::Expr::MethodCallExpr(expr), true) => {
347 expr.dot_token().as_ref().map(IndentLevel::from_token)
348 }
349 (ast::Expr::FieldExpr(expr), true) => {
350 expr.dot_token().as_ref().map(IndentLevel::from_token)
351 }
352 (_, true) => Some(IndentLevel::from_node(&parent)),
354 _ => None,
355 };
356 let target_indent = match target_indent {
357 Some(x) => x,
358 None => IndentLevel::from_node(&parent) + 1,
360 }
361 .to_string();
362
363 if current_indent_len == TextSize::of(&target_indent) {
364 return None;
365 }
366
367 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
368}
369
370fn on_left_angle_typed(
372 file: &SourceFile,
373 reparsed: &SourceFile,
374 offset: TextSize,
375) -> Option<TextEdit> {
376 let file_text = reparsed.syntax().text();
377
378 let mut next_offset = offset;
380 while file_text.char_at(next_offset) == Some(' ') {
381 next_offset += TextSize::of(' ')
382 }
383 if file_text.char_at(next_offset) == Some('>') {
384 return None;
385 }
386
387 if ancestors_at_offset(file.syntax(), offset)
388 .take_while(|n| !ast::Item::can_cast(n.kind()))
389 .any(|n| {
390 ast::GenericParamList::can_cast(n.kind())
391 || ast::GenericArgList::can_cast(n.kind())
392 || ast::UseBoundGenericArgs::can_cast(n.kind())
393 })
394 {
395 Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string()))
397 } else {
398 None
399 }
400}
401
402fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
403 let pipe_token = file.syntax().token_at_offset(offset).right_biased()?;
404 if pipe_token.kind() != SyntaxKind::PIPE {
405 return None;
406 }
407 if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() {
408 return None;
409 }
410 let after_lpipe = offset + TextSize::of('|');
411 Some(TextEdit::insert(after_lpipe, "|".to_owned()))
412}
413
414fn on_plus_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
415 let plus_token = file.syntax().token_at_offset(offset).right_biased()?;
416 if plus_token.kind() != SyntaxKind::PLUS {
417 return None;
418 }
419 let mut ancestors = plus_token.parent_ancestors();
420 ancestors.next().and_then(ast::TypeBoundList::cast)?;
421 let trait_type =
422 ancestors.next().and_then(<Either<ast::DynTraitType, ast::ImplTraitType>>::cast)?;
423 let kind = ancestors.next()?.kind();
424
425 if ast::RefType::can_cast(kind) || ast::PtrType::can_cast(kind) || ast::RetType::can_cast(kind)
426 {
427 let mut builder = TextEdit::builder();
428 builder.insert(trait_type.syntax().text_range().start(), "(".to_owned());
429 builder.insert(trait_type.syntax().text_range().end(), ")".to_owned());
430 Some(builder.finish())
431 } else {
432 None
433 }
434}
435
436fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
438 let file_text = file.syntax().text();
439 let after_arrow = offset + TextSize::of('>');
440 if file_text.char_at(after_arrow) != Some('{') {
441 return None;
442 }
443 find_node_at_offset::<ast::RetType>(file.syntax(), offset)?;
444
445 Some(TextEdit::insert(after_arrow, " ".to_owned()))
446}
447
448#[cfg(test)]
449mod tests {
450 use test_utils::{assert_eq_text, extract_offset};
451
452 use super::*;
453
454 impl ExtendedTextEdit {
455 fn apply(&self, text: &mut String) {
456 self.edit.apply(text);
457 }
458 }
459
460 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
461 let (offset, mut before) = extract_offset(before);
462 let edit = TextEdit::insert(offset, char_typed.to_string());
463 edit.apply(&mut before);
464 let parse = SourceFile::parse(&before, span::Edition::CURRENT);
465 on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT).map(|it| {
466 it.apply(&mut before);
467 before.to_string()
468 })
469 }
470
471 fn type_char(
472 char_typed: char,
473 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
474 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
475 ) {
476 let actual = do_type_char(char_typed, ra_fixture_before)
477 .unwrap_or_else(|| panic!("typing `{char_typed}` did nothing"));
478
479 assert_eq_text!(ra_fixture_after, &actual);
480 }
481
482 fn type_char_noop(char_typed: char, #[rust_analyzer::rust_fixture] ra_fixture_before: &str) {
483 let file_change = do_type_char(char_typed, ra_fixture_before);
484 assert_eq!(file_change, None)
485 }
486
487 #[test]
488 fn test_semi_after_let() {
489 type_char_noop(
490 '=',
491 r"
492fn foo() {
493 let foo =$0
494}
495",
496 );
497 type_char(
498 '=',
499 r#"
500fn foo() {
501 let foo $0 1 + 1
502}
503"#,
504 r#"
505fn foo() {
506 let foo = 1 + 1;
507}
508"#,
509 );
510 type_char_noop(
511 '=',
512 r#"
513fn foo() {
514 let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 {
515 // ...
516 }
517}
518"#,
519 );
520 type_char_noop(
521 '=',
522 r"
523fn foo() {
524 let foo =$0
525 let bar = 1;
526}
527",
528 );
529 type_char_noop(
530 '=',
531 r"
532fn foo() {
533 let foo =$0
534 1 + 1
535}
536",
537 );
538 }
539
540 #[test]
541 fn test_semi_after_assign() {
542 type_char(
543 '=',
544 r#"
545fn f() {
546 i $0 0
547}
548"#,
549 r#"
550fn f() {
551 i = 0;
552}
553"#,
554 );
555 type_char(
556 '=',
557 r#"
558fn f() {
559 i $0 0
560 i
561}
562"#,
563 r#"
564fn f() {
565 i = 0;
566 i
567}
568"#,
569 );
570 type_char_noop(
571 '=',
572 r#"
573fn f(x: u8) {
574 if x $0
575}
576"#,
577 );
578 type_char_noop(
579 '=',
580 r#"
581fn f(x: u8) {
582 if x $0 {}
583}
584"#,
585 );
586 type_char_noop(
587 '=',
588 r#"
589fn f(x: u8) {
590 if x $0 0 {}
591}
592"#,
593 );
594 type_char_noop(
595 '=',
596 r#"
597fn f() {
598 g(i $0 0);
599}
600"#,
601 );
602 }
603
604 #[test]
605 fn assign_to_eq() {
606 type_char(
607 '=',
608 r#"
609fn f(a: u8) {
610 a =$0 0;
611}
612"#,
613 r#"
614fn f(a: u8) {
615 a == 0
616}
617"#,
618 );
619 type_char(
620 '=',
621 r#"
622fn f(a: u8) {
623 a $0= 0;
624}
625"#,
626 r#"
627fn f(a: u8) {
628 a == 0
629}
630"#,
631 );
632 type_char_noop(
633 '=',
634 r#"
635fn f(a: u8) {
636 let e = a =$0 0;
637}
638"#,
639 );
640 type_char_noop(
641 '=',
642 r#"
643fn f(a: u8) {
644 let e = a =$0 0;
645 e
646}
647"#,
648 );
649 }
650
651 #[test]
652 fn indents_new_chain_call() {
653 type_char(
654 '.',
655 r#"
656fn main() {
657 xs.foo()
658 $0
659}
660 "#,
661 r#"
662fn main() {
663 xs.foo()
664 .
665}
666 "#,
667 );
668 type_char_noop(
669 '.',
670 r#"
671fn main() {
672 xs.foo()
673 $0
674}
675 "#,
676 )
677 }
678
679 #[test]
680 fn indents_new_chain_call_with_semi() {
681 type_char(
682 '.',
683 r"
684fn main() {
685 xs.foo()
686 $0;
687}
688 ",
689 r#"
690fn main() {
691 xs.foo()
692 .;
693}
694 "#,
695 );
696 type_char_noop(
697 '.',
698 r#"
699fn main() {
700 xs.foo()
701 $0;
702}
703 "#,
704 )
705 }
706
707 #[test]
708 fn indents_new_chain_call_with_let() {
709 type_char(
710 '.',
711 r#"
712fn main() {
713 let _ = foo
714 $0
715 bar()
716}
717"#,
718 r#"
719fn main() {
720 let _ = foo
721 .
722 bar()
723}
724"#,
725 );
726 }
727
728 #[test]
729 fn indents_continued_chain_call() {
730 type_char(
731 '.',
732 r#"
733fn main() {
734 xs.foo()
735 .first()
736 $0
737}
738 "#,
739 r#"
740fn main() {
741 xs.foo()
742 .first()
743 .
744}
745 "#,
746 );
747 type_char_noop(
748 '.',
749 r#"
750fn main() {
751 xs.foo()
752 .first()
753 $0
754}
755 "#,
756 );
757 }
758
759 #[test]
760 fn indents_middle_of_chain_call() {
761 type_char(
762 '.',
763 r#"
764fn source_impl() {
765 let var = enum_defvariant_list().unwrap()
766 $0
767 .nth(92)
768 .unwrap();
769}
770 "#,
771 r#"
772fn source_impl() {
773 let var = enum_defvariant_list().unwrap()
774 .
775 .nth(92)
776 .unwrap();
777}
778 "#,
779 );
780 type_char_noop(
781 '.',
782 r#"
783fn source_impl() {
784 let var = enum_defvariant_list().unwrap()
785 $0
786 .nth(92)
787 .unwrap();
788}
789 "#,
790 );
791 }
792
793 #[test]
794 fn dont_indent_freestanding_dot() {
795 type_char_noop(
796 '.',
797 r#"
798fn main() {
799 $0
800}
801 "#,
802 );
803 type_char_noop(
804 '.',
805 r#"
806fn main() {
807$0
808}
809 "#,
810 );
811 }
812
813 #[test]
814 fn adds_space_after_return_type() {
815 type_char(
816 '>',
817 r#"
818fn foo() -$0{ 92 }
819"#,
820 r#"
821fn foo() -> { 92 }
822"#,
823 );
824 }
825
826 #[test]
827 fn adds_closing_brace_for_expr() {
828 type_char(
829 '{',
830 r#"
831fn f() { match () { _ => $0() } }
832 "#,
833 r#"
834fn f() { match () { _ => {()} } }
835 "#,
836 );
837 type_char(
838 '{',
839 r#"
840fn f() { $0() }
841 "#,
842 r#"
843fn f() { {()} }
844 "#,
845 );
846 type_char(
847 '{',
848 r#"
849fn f() { let x = $0(); }
850 "#,
851 r#"
852fn f() { let x = {()}; }
853 "#,
854 );
855 type_char(
856 '{',
857 r#"
858fn f() { let x = $0a.b(); }
859 "#,
860 r#"
861fn f() { let x = {a.b()}; }
862 "#,
863 );
864 type_char(
865 '{',
866 r#"
867const S: () = $0();
868fn f() {}
869 "#,
870 r#"
871const S: () = {()};
872fn f() {}
873 "#,
874 );
875 type_char(
876 '{',
877 r#"
878const S: () = $0a.b();
879fn f() {}
880 "#,
881 r#"
882const S: () = {a.b()};
883fn f() {}
884 "#,
885 );
886 type_char(
887 '{',
888 r#"
889fn f() {
890 match x {
891 0 => $0(),
892 1 => (),
893 }
894}
895 "#,
896 r#"
897fn f() {
898 match x {
899 0 => {()},
900 1 => (),
901 }
902}
903 "#,
904 );
905 type_char(
906 '{',
907 r#"
908fn main() {
909 #[allow(unreachable_code)]
910 $0g();
911}
912 "#,
913 r#"
914fn main() {
915 #[allow(unreachable_code)]
916 {g()};
917}
918 "#,
919 );
920 }
921
922 #[test]
923 fn noop_in_string_literal() {
924 type_char_noop(
926 '{',
927 r##"
928fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
929 let base = r#"
930enum E { T(), R$0, C }
931use self::E::X;
932const Z: E = E::C;
933mod m {}
934asdasdasdasdasdasda
935sdasdasdasdasdasda
936sdasdasdasdasd
937"#;
938 let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
939 expect.assert_eq(&actual)
940}
941 "##,
942 );
943 }
944
945 #[test]
946 fn noop_in_item_position_with_macro() {
947 type_char_noop('{', r#"$0println!();"#);
948 type_char_noop(
949 '{',
950 r#"
951fn main() $0println!("hello");
952}"#,
953 );
954 }
955
956 #[test]
957 fn adds_closing_brace_for_use_tree() {
958 type_char(
959 '{',
960 r#"
961use some::$0Path;
962 "#,
963 r#"
964use some::{Path};
965 "#,
966 );
967 type_char(
968 '{',
969 r#"
970use some::{Path, $0Other};
971 "#,
972 r#"
973use some::{Path, {Other}};
974 "#,
975 );
976 type_char(
977 '{',
978 r#"
979use some::{$0Path, Other};
980 "#,
981 r#"
982use some::{{Path}, Other};
983 "#,
984 );
985 type_char(
986 '{',
987 r#"
988use some::path::$0to::Item;
989 "#,
990 r#"
991use some::path::{to::Item};
992 "#,
993 );
994 type_char(
995 '{',
996 r#"
997use some::$0path::to::Item;
998 "#,
999 r#"
1000use some::{path::to::Item};
1001 "#,
1002 );
1003 type_char(
1004 '{',
1005 r#"
1006use $0some::path::to::Item;
1007 "#,
1008 r#"
1009use {some::path::to::Item};
1010 "#,
1011 );
1012 type_char(
1013 '{',
1014 r#"
1015use some::path::$0to::{Item};
1016 "#,
1017 r#"
1018use some::path::{to::{Item}};
1019 "#,
1020 );
1021 type_char(
1022 '{',
1023 r#"
1024use $0Thing as _;
1025 "#,
1026 r#"
1027use {Thing as _};
1028 "#,
1029 );
1030
1031 type_char_noop(
1032 '{',
1033 r#"
1034use some::pa$0th::to::Item;
1035 "#,
1036 );
1037 }
1038
1039 #[test]
1040 fn adds_closing_parenthesis_for_expr() {
1041 type_char(
1042 '(',
1043 r#"
1044fn f() { match () { _ => $0() } }
1045 "#,
1046 r#"
1047fn f() { match () { _ => (()) } }
1048 "#,
1049 );
1050 type_char(
1051 '(',
1052 r#"
1053fn f() { $0() }
1054 "#,
1055 r#"
1056fn f() { (()) }
1057 "#,
1058 );
1059 type_char(
1060 '(',
1061 r#"
1062fn f() { let x = $0(); }
1063 "#,
1064 r#"
1065fn f() { let x = (()); }
1066 "#,
1067 );
1068 type_char(
1069 '(',
1070 r#"
1071fn f() { let x = $0a.b(); }
1072 "#,
1073 r#"
1074fn f() { let x = (a.b()); }
1075 "#,
1076 );
1077 type_char(
1078 '(',
1079 r#"
1080const S: () = $0();
1081fn f() {}
1082 "#,
1083 r#"
1084const S: () = (());
1085fn f() {}
1086 "#,
1087 );
1088 type_char(
1089 '(',
1090 r#"
1091const S: () = $0a.b();
1092fn f() {}
1093 "#,
1094 r#"
1095const S: () = (a.b());
1096fn f() {}
1097 "#,
1098 );
1099 type_char(
1100 '(',
1101 r#"
1102fn f() {
1103 match x {
1104 0 => $0(),
1105 1 => (),
1106 }
1107}
1108 "#,
1109 r#"
1110fn f() {
1111 match x {
1112 0 => (()),
1113 1 => (),
1114 }
1115}
1116 "#,
1117 );
1118 type_char(
1119 '(',
1120 r#"
1121 fn f() {
1122 let z = Some($03);
1123 }
1124 "#,
1125 r#"
1126 fn f() {
1127 let z = Some((3));
1128 }
1129 "#,
1130 );
1131 }
1132
1133 #[test]
1134 fn preceding_whitespace_is_significant_for_closing_brackets() {
1135 type_char_noop(
1136 '(',
1137 r#"
1138fn f() { a.b$0if true {} }
1139"#,
1140 );
1141 type_char_noop(
1142 '(',
1143 r#"
1144fn f() { foo$0{} }
1145"#,
1146 );
1147 }
1148
1149 #[test]
1150 fn adds_closing_parenthesis_for_pat() {
1151 type_char(
1152 '(',
1153 r#"
1154fn f() { match () { $0() => () } }
1155"#,
1156 r#"
1157fn f() { match () { (()) => () } }
1158"#,
1159 );
1160 type_char(
1161 '(',
1162 r#"
1163fn f($0n: ()) {}
1164"#,
1165 r#"
1166fn f((n): ()) {}
1167"#,
1168 );
1169 }
1170
1171 #[test]
1172 fn adds_closing_parenthesis_for_ty() {
1173 type_char(
1174 '(',
1175 r#"
1176fn f(n: $0()) {}
1177"#,
1178 r#"
1179fn f(n: (())) {}
1180"#,
1181 );
1182 type_char(
1183 '(',
1184 r#"
1185fn f(n: $0a::b::<d>::c) {}
1186"#,
1187 r#"
1188fn f(n: (a::b::<d>::c)) {}
1189"#,
1190 );
1191 }
1192
1193 #[test]
1194 fn adds_closing_angles_for_ty() {
1195 type_char(
1196 '<',
1197 r#"
1198fn f(n: $0()) {}
1199"#,
1200 r#"
1201fn f(n: <()>) {}
1202"#,
1203 );
1204 type_char(
1205 '<',
1206 r#"
1207fn f(n: $0a::b::<d>::c) {}
1208"#,
1209 r#"
1210fn f(n: <a::b::<d>::c>) {}
1211"#,
1212 );
1213 type_char(
1214 '<',
1215 r#"
1216fn f(n: a$0b::<d>::c) {}
1217"#,
1218 r#"
1219fn f(n: a<>b::<d>::c) {}
1220"#,
1221 );
1222 }
1223
1224 #[test]
1225 fn parenthesis_noop_in_string_literal() {
1226 type_char_noop(
1228 '(',
1229 r##"
1230fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
1231 let base = r#"
1232enum E { T(), R$0, C }
1233use self::E::X;
1234const Z: E = E::C;
1235mod m {}
1236asdasdasdasdasdasda
1237sdasdasdasdasdasda
1238sdasdasdasdasd
1239"#;
1240 let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
1241 expect.assert_eq(&actual)
1242}
1243 "##,
1244 );
1245 }
1246
1247 #[test]
1248 fn parenthesis_noop_in_item_position_with_macro() {
1249 type_char_noop('(', r#"$0println!();"#);
1250 type_char_noop(
1251 '(',
1252 r#"
1253fn main() $0println!("hello");
1254}"#,
1255 );
1256 }
1257
1258 #[test]
1259 fn parenthesis_noop_in_use_tree() {
1260 type_char_noop(
1261 '(',
1262 r#"
1263use some::$0Path;
1264 "#,
1265 );
1266 type_char_noop(
1267 '(',
1268 r#"
1269use some::{Path, $0Other};
1270 "#,
1271 );
1272 type_char_noop(
1273 '(',
1274 r#"
1275use some::{$0Path, Other};
1276 "#,
1277 );
1278 type_char_noop(
1279 '(',
1280 r#"
1281use some::path::$0to::Item;
1282 "#,
1283 );
1284 type_char_noop(
1285 '(',
1286 r#"
1287use some::$0path::to::Item;
1288 "#,
1289 );
1290 type_char_noop(
1291 '(',
1292 r#"
1293use $0some::path::to::Item;
1294 "#,
1295 );
1296 type_char_noop(
1297 '(',
1298 r#"
1299use some::path::$0to::{Item};
1300 "#,
1301 );
1302 type_char_noop(
1303 '(',
1304 r#"
1305use $0Thing as _;
1306 "#,
1307 );
1308
1309 type_char_noop(
1310 '(',
1311 r#"
1312use some::pa$0th::to::Item;
1313 "#,
1314 );
1315 type_char_noop(
1316 '<',
1317 r#"
1318use some::pa$0th::to::Item;
1319 "#,
1320 );
1321 }
1322
1323 #[test]
1324 fn adds_closing_angle_bracket_for_generic_args() {
1325 type_char(
1326 '<',
1327 r#"
1328fn foo() {
1329 bar::$0
1330}
1331 "#,
1332 r#"
1333fn foo() {
1334 bar::<>
1335}
1336 "#,
1337 );
1338
1339 type_char(
1340 '<',
1341 r#"
1342fn foo(bar: &[u64]) {
1343 bar.iter().collect::$0();
1344}
1345 "#,
1346 r#"
1347fn foo(bar: &[u64]) {
1348 bar.iter().collect::<>();
1349}
1350 "#,
1351 );
1352 }
1353
1354 #[test]
1355 fn adds_closing_angle_bracket_for_generic_params() {
1356 type_char(
1357 '<',
1358 r#"
1359fn foo$0() {}
1360 "#,
1361 r#"
1362fn foo<>() {}
1363 "#,
1364 );
1365 type_char(
1366 '<',
1367 r#"
1368fn foo$0
1369 "#,
1370 r#"
1371fn foo<>
1372 "#,
1373 );
1374 type_char(
1375 '<',
1376 r#"
1377struct Foo$0 {}
1378 "#,
1379 r#"
1380struct Foo<> {}
1381 "#,
1382 );
1383 type_char(
1384 '<',
1385 r#"
1386struct Foo$0();
1387 "#,
1388 r#"
1389struct Foo<>();
1390 "#,
1391 );
1392 type_char(
1393 '<',
1394 r#"
1395struct Foo$0
1396 "#,
1397 r#"
1398struct Foo<>
1399 "#,
1400 );
1401 type_char(
1402 '<',
1403 r#"
1404enum Foo$0
1405 "#,
1406 r#"
1407enum Foo<>
1408 "#,
1409 );
1410 type_char(
1411 '<',
1412 r#"
1413trait Foo$0
1414 "#,
1415 r#"
1416trait Foo<>
1417 "#,
1418 );
1419 type_char(
1420 '<',
1421 r#"
1422type Foo$0 = Bar;
1423 "#,
1424 r#"
1425type Foo<> = Bar;
1426 "#,
1427 );
1428 type_char(
1429 '<',
1430 r#"
1431impl<T> Foo$0 {}
1432 "#,
1433 r#"
1434impl<T> Foo<> {}
1435 "#,
1436 );
1437 type_char(
1438 '<',
1439 r#"
1440impl Foo$0 {}
1441 "#,
1442 r#"
1443impl Foo<> {}
1444 "#,
1445 );
1446 }
1447
1448 #[test]
1449 fn dont_add_closing_angle_bracket_for_comparison() {
1450 type_char_noop(
1451 '<',
1452 r#"
1453fn main() {
1454 42$0
1455}
1456 "#,
1457 );
1458 type_char_noop(
1459 '<',
1460 r#"
1461fn main() {
1462 42 $0
1463}
1464 "#,
1465 );
1466 type_char_noop(
1467 '<',
1468 r#"
1469fn main() {
1470 let foo = 42;
1471 foo $0
1472}
1473 "#,
1474 );
1475 }
1476
1477 #[test]
1478 fn dont_add_closing_angle_bracket_if_it_is_already_there() {
1479 type_char_noop(
1480 '<',
1481 r#"
1482fn foo() {
1483 bar::$0>
1484}
1485 "#,
1486 );
1487 type_char_noop(
1488 '<',
1489 r#"
1490fn foo(bar: &[u64]) {
1491 bar.iter().collect::$0 >();
1492}
1493 "#,
1494 );
1495 type_char_noop(
1496 '<',
1497 r#"
1498fn foo$0>() {}
1499 "#,
1500 );
1501 type_char_noop(
1502 '<',
1503 r#"
1504fn foo$0>
1505 "#,
1506 );
1507 type_char_noop(
1508 '<',
1509 r#"
1510struct Foo$0> {}
1511 "#,
1512 );
1513 type_char_noop(
1514 '<',
1515 r#"
1516struct Foo$0>();
1517 "#,
1518 );
1519 type_char_noop(
1520 '<',
1521 r#"
1522struct Foo$0>
1523 "#,
1524 );
1525 type_char_noop(
1526 '<',
1527 r#"
1528enum Foo$0>
1529 "#,
1530 );
1531 type_char_noop(
1532 '<',
1533 r#"
1534trait Foo$0>
1535 "#,
1536 );
1537 type_char_noop(
1538 '<',
1539 r#"
1540type Foo$0> = Bar;
1541 "#,
1542 );
1543 type_char_noop(
1544 '<',
1545 r#"
1546impl$0> Foo {}
1547 "#,
1548 );
1549 type_char_noop(
1550 '<',
1551 r#"
1552impl<T> Foo$0> {}
1553 "#,
1554 );
1555 type_char_noop(
1556 '<',
1557 r#"
1558impl Foo$0> {}
1559 "#,
1560 );
1561 }
1562
1563 #[test]
1564 fn regression_629() {
1565 type_char_noop(
1566 '.',
1567 r#"
1568fn foo() {
1569 CompletionItem::new(
1570 CompletionKind::Reference,
1571 ctx.source_range(),
1572 field.name().to_string(),
1573 )
1574 .foo()
1575 $0
1576}
1577"#,
1578 );
1579 type_char_noop(
1580 '.',
1581 r#"
1582fn foo() {
1583 CompletionItem::new(
1584 CompletionKind::Reference,
1585 ctx.source_range(),
1586 field.name().to_string(),
1587 )
1588 $0
1589}
1590"#,
1591 );
1592 }
1593
1594 #[test]
1595 fn completes_pipe_param_list() {
1596 type_char(
1597 '|',
1598 r#"
1599fn foo() {
1600 $0
1601}
1602"#,
1603 r#"
1604fn foo() {
1605 ||
1606}
1607"#,
1608 );
1609 type_char(
1610 '|',
1611 r#"
1612fn foo() {
1613 $0 a
1614}
1615"#,
1616 r#"
1617fn foo() {
1618 || a
1619}
1620"#,
1621 );
1622 type_char_noop(
1623 '|',
1624 r#"
1625fn foo() {
1626 let $0
1627}
1628"#,
1629 );
1630 }
1631
1632 #[test]
1633 fn adds_parentheses_around_trait_object_in_ref_type() {
1634 type_char(
1635 '+',
1636 r#"
1637fn foo(x: &dyn A$0) {}
1638"#,
1639 r#"
1640fn foo(x: &(dyn A+)) {}
1641"#,
1642 );
1643 type_char(
1644 '+',
1645 r#"
1646fn foo(x: &'static dyn A$0B) {}
1647"#,
1648 r#"
1649fn foo(x: &'static (dyn A+B)) {}
1650"#,
1651 );
1652 type_char_noop(
1653 '+',
1654 r#"
1655fn foo(x: &(dyn A$0)) {}
1656"#,
1657 );
1658 type_char_noop(
1659 '+',
1660 r#"
1661fn foo(x: Box<dyn A$0>) {}
1662"#,
1663 );
1664 }
1665
1666 #[test]
1667 fn adds_parentheses_around_trait_object_in_ptr_type() {
1668 type_char(
1669 '+',
1670 r#"
1671fn foo(x: *const dyn A$0) {}
1672"#,
1673 r#"
1674fn foo(x: *const (dyn A+)) {}
1675"#,
1676 );
1677 }
1678
1679 #[test]
1680 fn adds_parentheses_around_trait_object_in_return_type() {
1681 type_char(
1682 '+',
1683 r#"
1684fn foo(x: fn() -> dyn A$0) {}
1685"#,
1686 r#"
1687fn foo(x: fn() -> (dyn A+)) {}
1688"#,
1689 );
1690 }
1691}