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