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