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 {
330 ast::MethodCallExpr::cast(parent.clone())?.receiver()?
331 };
332
333 let receiver_is_multiline = receiver.syntax().text().find_char('\n').is_some();
334 let target_indent = match (receiver, receiver_is_multiline) {
335 (ast::Expr::MethodCallExpr(expr), true) => {
337 expr.dot_token().as_ref().map(IndentLevel::from_token)
338 }
339 (ast::Expr::FieldExpr(expr), true) => {
340 expr.dot_token().as_ref().map(IndentLevel::from_token)
341 }
342 (_, true) => Some(IndentLevel::from_node(&parent)),
344 _ => None,
345 };
346 let target_indent = match target_indent {
347 Some(x) => x,
348 None => IndentLevel::from_node(&parent) + 1,
350 }
351 .to_string();
352
353 if current_indent_len == TextSize::of(&target_indent) {
354 return None;
355 }
356
357 Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
358}
359
360fn on_left_angle_typed(
362 file: &SourceFile,
363 reparsed: &SourceFile,
364 offset: TextSize,
365) -> Option<TextEdit> {
366 let file_text = reparsed.syntax().text();
367
368 let mut next_offset = offset;
370 while file_text.char_at(next_offset) == Some(' ') {
371 next_offset += TextSize::of(' ')
372 }
373 if file_text.char_at(next_offset) == Some('>') {
374 return None;
375 }
376
377 if ancestors_at_offset(file.syntax(), offset)
378 .take_while(|n| !ast::Item::can_cast(n.kind()))
379 .any(|n| {
380 ast::GenericParamList::can_cast(n.kind())
381 || ast::GenericArgList::can_cast(n.kind())
382 || ast::UseBoundGenericArgs::can_cast(n.kind())
383 })
384 {
385 Some(TextEdit::insert(offset + TextSize::of('<'), '>'.to_string()))
387 } else {
388 None
389 }
390}
391
392fn on_pipe_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
393 let pipe_token = file.syntax().token_at_offset(offset).right_biased()?;
394 if pipe_token.kind() != SyntaxKind::PIPE {
395 return None;
396 }
397 if pipe_token.parent().and_then(ast::ParamList::cast)?.r_paren_token().is_some() {
398 return None;
399 }
400 let after_lpipe = offset + TextSize::of('|');
401 Some(TextEdit::insert(after_lpipe, "|".to_owned()))
402}
403
404fn on_plus_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
405 let plus_token = file.syntax().token_at_offset(offset).right_biased()?;
406 if plus_token.kind() != SyntaxKind::PLUS {
407 return None;
408 }
409 let mut ancestors = plus_token.parent_ancestors();
410 ancestors.next().and_then(ast::TypeBoundList::cast)?;
411 let trait_type =
412 ancestors.next().and_then(<Either<ast::DynTraitType, ast::ImplTraitType>>::cast)?;
413 let kind = ancestors.next()?.kind();
414
415 if ast::RefType::can_cast(kind) || ast::PtrType::can_cast(kind) || ast::RetType::can_cast(kind)
416 {
417 let mut builder = TextEdit::builder();
418 builder.insert(trait_type.syntax().text_range().start(), "(".to_owned());
419 builder.insert(trait_type.syntax().text_range().end(), ")".to_owned());
420 Some(builder.finish())
421 } else {
422 None
423 }
424}
425
426fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
428 let file_text = file.syntax().text();
429 let after_arrow = offset + TextSize::of('>');
430 if file_text.char_at(after_arrow) != Some('{') {
431 return None;
432 }
433 find_node_at_offset::<ast::RetType>(file.syntax(), offset)?;
434
435 Some(TextEdit::insert(after_arrow, " ".to_owned()))
436}
437
438#[cfg(test)]
439mod tests {
440 use test_utils::{assert_eq_text, extract_offset};
441
442 use super::*;
443
444 impl ExtendedTextEdit {
445 fn apply(&self, text: &mut String) {
446 self.edit.apply(text);
447 }
448 }
449
450 fn do_type_char(char_typed: char, before: &str) -> Option<String> {
451 let (offset, mut before) = extract_offset(before);
452 let edit = TextEdit::insert(offset, char_typed.to_string());
453 edit.apply(&mut before);
454 let parse = SourceFile::parse(&before, span::Edition::CURRENT);
455 on_char_typed_(&parse, offset, char_typed, span::Edition::CURRENT).map(|it| {
456 it.apply(&mut before);
457 before.to_string()
458 })
459 }
460
461 fn type_char(
462 char_typed: char,
463 #[rust_analyzer::rust_fixture] ra_fixture_before: &str,
464 #[rust_analyzer::rust_fixture] ra_fixture_after: &str,
465 ) {
466 let actual = do_type_char(char_typed, ra_fixture_before)
467 .unwrap_or_else(|| panic!("typing `{char_typed}` did nothing"));
468
469 assert_eq_text!(ra_fixture_after, &actual);
470 }
471
472 fn type_char_noop(char_typed: char, #[rust_analyzer::rust_fixture] ra_fixture_before: &str) {
473 let file_change = do_type_char(char_typed, ra_fixture_before);
474 assert_eq!(file_change, None)
475 }
476
477 #[test]
478 fn test_semi_after_let() {
479 type_char_noop(
480 '=',
481 r"
482fn foo() {
483 let foo =$0
484}
485",
486 );
487 type_char(
488 '=',
489 r#"
490fn foo() {
491 let foo $0 1 + 1
492}
493"#,
494 r#"
495fn foo() {
496 let foo = 1 + 1;
497}
498"#,
499 );
500 type_char_noop(
501 '=',
502 r#"
503fn foo() {
504 let difference $0(counts: &HashMap<(char, char), u64>, last: char) -> u64 {
505 // ...
506 }
507}
508"#,
509 );
510 type_char_noop(
511 '=',
512 r"
513fn foo() {
514 let foo =$0
515 let bar = 1;
516}
517",
518 );
519 type_char_noop(
520 '=',
521 r"
522fn foo() {
523 let foo =$0
524 1 + 1
525}
526",
527 );
528 }
529
530 #[test]
531 fn test_semi_after_assign() {
532 type_char(
533 '=',
534 r#"
535fn f() {
536 i $0 0
537}
538"#,
539 r#"
540fn f() {
541 i = 0;
542}
543"#,
544 );
545 type_char(
546 '=',
547 r#"
548fn f() {
549 i $0 0
550 i
551}
552"#,
553 r#"
554fn f() {
555 i = 0;
556 i
557}
558"#,
559 );
560 type_char_noop(
561 '=',
562 r#"
563fn f(x: u8) {
564 if x $0
565}
566"#,
567 );
568 type_char_noop(
569 '=',
570 r#"
571fn f(x: u8) {
572 if x $0 {}
573}
574"#,
575 );
576 type_char_noop(
577 '=',
578 r#"
579fn f(x: u8) {
580 if x $0 0 {}
581}
582"#,
583 );
584 type_char_noop(
585 '=',
586 r#"
587fn f() {
588 g(i $0 0);
589}
590"#,
591 );
592 }
593
594 #[test]
595 fn assign_to_eq() {
596 type_char(
597 '=',
598 r#"
599fn f(a: u8) {
600 a =$0 0;
601}
602"#,
603 r#"
604fn f(a: u8) {
605 a == 0
606}
607"#,
608 );
609 type_char(
610 '=',
611 r#"
612fn f(a: u8) {
613 a $0= 0;
614}
615"#,
616 r#"
617fn f(a: u8) {
618 a == 0
619}
620"#,
621 );
622 type_char_noop(
623 '=',
624 r#"
625fn f(a: u8) {
626 let e = a =$0 0;
627}
628"#,
629 );
630 type_char_noop(
631 '=',
632 r#"
633fn f(a: u8) {
634 let e = a =$0 0;
635 e
636}
637"#,
638 );
639 }
640
641 #[test]
642 fn indents_new_chain_call() {
643 type_char(
644 '.',
645 r#"
646fn main() {
647 xs.foo()
648 $0
649}
650 "#,
651 r#"
652fn main() {
653 xs.foo()
654 .
655}
656 "#,
657 );
658 type_char_noop(
659 '.',
660 r#"
661fn main() {
662 xs.foo()
663 $0
664}
665 "#,
666 )
667 }
668
669 #[test]
670 fn indents_new_chain_call_with_semi() {
671 type_char(
672 '.',
673 r"
674fn main() {
675 xs.foo()
676 $0;
677}
678 ",
679 r#"
680fn main() {
681 xs.foo()
682 .;
683}
684 "#,
685 );
686 type_char_noop(
687 '.',
688 r#"
689fn main() {
690 xs.foo()
691 $0;
692}
693 "#,
694 )
695 }
696
697 #[test]
698 fn indents_new_chain_call_with_let() {
699 type_char(
700 '.',
701 r#"
702fn main() {
703 let _ = foo
704 $0
705 bar()
706}
707"#,
708 r#"
709fn main() {
710 let _ = foo
711 .
712 bar()
713}
714"#,
715 );
716 }
717
718 #[test]
719 fn indents_continued_chain_call() {
720 type_char(
721 '.',
722 r#"
723fn main() {
724 xs.foo()
725 .first()
726 $0
727}
728 "#,
729 r#"
730fn main() {
731 xs.foo()
732 .first()
733 .
734}
735 "#,
736 );
737 type_char_noop(
738 '.',
739 r#"
740fn main() {
741 xs.foo()
742 .first()
743 $0
744}
745 "#,
746 );
747 }
748
749 #[test]
750 fn indents_middle_of_chain_call() {
751 type_char(
752 '.',
753 r#"
754fn source_impl() {
755 let var = enum_defvariant_list().unwrap()
756 $0
757 .nth(92)
758 .unwrap();
759}
760 "#,
761 r#"
762fn source_impl() {
763 let var = enum_defvariant_list().unwrap()
764 .
765 .nth(92)
766 .unwrap();
767}
768 "#,
769 );
770 type_char_noop(
771 '.',
772 r#"
773fn source_impl() {
774 let var = enum_defvariant_list().unwrap()
775 $0
776 .nth(92)
777 .unwrap();
778}
779 "#,
780 );
781 }
782
783 #[test]
784 fn dont_indent_freestanding_dot() {
785 type_char_noop(
786 '.',
787 r#"
788fn main() {
789 $0
790}
791 "#,
792 );
793 type_char_noop(
794 '.',
795 r#"
796fn main() {
797$0
798}
799 "#,
800 );
801 }
802
803 #[test]
804 fn adds_space_after_return_type() {
805 type_char(
806 '>',
807 r#"
808fn foo() -$0{ 92 }
809"#,
810 r#"
811fn foo() -> { 92 }
812"#,
813 );
814 }
815
816 #[test]
817 fn adds_closing_brace_for_expr() {
818 type_char(
819 '{',
820 r#"
821fn f() { match () { _ => $0() } }
822 "#,
823 r#"
824fn f() { match () { _ => {()} } }
825 "#,
826 );
827 type_char(
828 '{',
829 r#"
830fn f() { $0() }
831 "#,
832 r#"
833fn f() { {()} }
834 "#,
835 );
836 type_char(
837 '{',
838 r#"
839fn f() { let x = $0(); }
840 "#,
841 r#"
842fn f() { let x = {()}; }
843 "#,
844 );
845 type_char(
846 '{',
847 r#"
848fn f() { let x = $0a.b(); }
849 "#,
850 r#"
851fn f() { let x = {a.b()}; }
852 "#,
853 );
854 type_char(
855 '{',
856 r#"
857const S: () = $0();
858fn f() {}
859 "#,
860 r#"
861const S: () = {()};
862fn f() {}
863 "#,
864 );
865 type_char(
866 '{',
867 r#"
868const S: () = $0a.b();
869fn f() {}
870 "#,
871 r#"
872const S: () = {a.b()};
873fn f() {}
874 "#,
875 );
876 type_char(
877 '{',
878 r#"
879fn f() {
880 match x {
881 0 => $0(),
882 1 => (),
883 }
884}
885 "#,
886 r#"
887fn f() {
888 match x {
889 0 => {()},
890 1 => (),
891 }
892}
893 "#,
894 );
895 type_char(
896 '{',
897 r#"
898fn main() {
899 #[allow(unreachable_code)]
900 $0g();
901}
902 "#,
903 r#"
904fn main() {
905 #[allow(unreachable_code)]
906 {g()};
907}
908 "#,
909 );
910 }
911
912 #[test]
913 fn noop_in_string_literal() {
914 type_char_noop(
916 '{',
917 r##"
918fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
919 let base = r#"
920enum E { T(), R$0, C }
921use self::E::X;
922const Z: E = E::C;
923mod m {}
924asdasdasdasdasdasda
925sdasdasdasdasdasda
926sdasdasdasdasd
927"#;
928 let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
929 expect.assert_eq(&actual)
930}
931 "##,
932 );
933 }
934
935 #[test]
936 fn noop_in_item_position_with_macro() {
937 type_char_noop('{', r#"$0println!();"#);
938 type_char_noop(
939 '{',
940 r#"
941fn main() $0println!("hello");
942}"#,
943 );
944 }
945
946 #[test]
947 fn adds_closing_brace_for_use_tree() {
948 type_char(
949 '{',
950 r#"
951use some::$0Path;
952 "#,
953 r#"
954use some::{Path};
955 "#,
956 );
957 type_char(
958 '{',
959 r#"
960use some::{Path, $0Other};
961 "#,
962 r#"
963use some::{Path, {Other}};
964 "#,
965 );
966 type_char(
967 '{',
968 r#"
969use some::{$0Path, Other};
970 "#,
971 r#"
972use some::{{Path}, Other};
973 "#,
974 );
975 type_char(
976 '{',
977 r#"
978use some::path::$0to::Item;
979 "#,
980 r#"
981use some::path::{to::Item};
982 "#,
983 );
984 type_char(
985 '{',
986 r#"
987use some::$0path::to::Item;
988 "#,
989 r#"
990use some::{path::to::Item};
991 "#,
992 );
993 type_char(
994 '{',
995 r#"
996use $0some::path::to::Item;
997 "#,
998 r#"
999use {some::path::to::Item};
1000 "#,
1001 );
1002 type_char(
1003 '{',
1004 r#"
1005use some::path::$0to::{Item};
1006 "#,
1007 r#"
1008use some::path::{to::{Item}};
1009 "#,
1010 );
1011 type_char(
1012 '{',
1013 r#"
1014use $0Thing as _;
1015 "#,
1016 r#"
1017use {Thing as _};
1018 "#,
1019 );
1020
1021 type_char_noop(
1022 '{',
1023 r#"
1024use some::pa$0th::to::Item;
1025 "#,
1026 );
1027 }
1028
1029 #[test]
1030 fn adds_closing_parenthesis_for_expr() {
1031 type_char(
1032 '(',
1033 r#"
1034fn f() { match () { _ => $0() } }
1035 "#,
1036 r#"
1037fn f() { match () { _ => (()) } }
1038 "#,
1039 );
1040 type_char(
1041 '(',
1042 r#"
1043fn f() { $0() }
1044 "#,
1045 r#"
1046fn f() { (()) }
1047 "#,
1048 );
1049 type_char(
1050 '(',
1051 r#"
1052fn f() { let x = $0(); }
1053 "#,
1054 r#"
1055fn f() { let x = (()); }
1056 "#,
1057 );
1058 type_char(
1059 '(',
1060 r#"
1061fn f() { let x = $0a.b(); }
1062 "#,
1063 r#"
1064fn f() { let x = (a.b()); }
1065 "#,
1066 );
1067 type_char(
1068 '(',
1069 r#"
1070const S: () = $0();
1071fn f() {}
1072 "#,
1073 r#"
1074const S: () = (());
1075fn f() {}
1076 "#,
1077 );
1078 type_char(
1079 '(',
1080 r#"
1081const S: () = $0a.b();
1082fn f() {}
1083 "#,
1084 r#"
1085const S: () = (a.b());
1086fn f() {}
1087 "#,
1088 );
1089 type_char(
1090 '(',
1091 r#"
1092fn f() {
1093 match x {
1094 0 => $0(),
1095 1 => (),
1096 }
1097}
1098 "#,
1099 r#"
1100fn f() {
1101 match x {
1102 0 => (()),
1103 1 => (),
1104 }
1105}
1106 "#,
1107 );
1108 type_char(
1109 '(',
1110 r#"
1111 fn f() {
1112 let z = Some($03);
1113 }
1114 "#,
1115 r#"
1116 fn f() {
1117 let z = Some((3));
1118 }
1119 "#,
1120 );
1121 }
1122
1123 #[test]
1124 fn preceding_whitespace_is_significant_for_closing_brackets() {
1125 type_char_noop(
1126 '(',
1127 r#"
1128fn f() { a.b$0if true {} }
1129"#,
1130 );
1131 type_char_noop(
1132 '(',
1133 r#"
1134fn f() { foo$0{} }
1135"#,
1136 );
1137 }
1138
1139 #[test]
1140 fn adds_closing_parenthesis_for_pat() {
1141 type_char(
1142 '(',
1143 r#"
1144fn f() { match () { $0() => () } }
1145"#,
1146 r#"
1147fn f() { match () { (()) => () } }
1148"#,
1149 );
1150 type_char(
1151 '(',
1152 r#"
1153fn f($0n: ()) {}
1154"#,
1155 r#"
1156fn f((n): ()) {}
1157"#,
1158 );
1159 }
1160
1161 #[test]
1162 fn adds_closing_parenthesis_for_ty() {
1163 type_char(
1164 '(',
1165 r#"
1166fn f(n: $0()) {}
1167"#,
1168 r#"
1169fn f(n: (())) {}
1170"#,
1171 );
1172 type_char(
1173 '(',
1174 r#"
1175fn f(n: $0a::b::<d>::c) {}
1176"#,
1177 r#"
1178fn f(n: (a::b::<d>::c)) {}
1179"#,
1180 );
1181 }
1182
1183 #[test]
1184 fn adds_closing_angles_for_ty() {
1185 type_char(
1186 '<',
1187 r#"
1188fn f(n: $0()) {}
1189"#,
1190 r#"
1191fn f(n: <()>) {}
1192"#,
1193 );
1194 type_char(
1195 '<',
1196 r#"
1197fn f(n: $0a::b::<d>::c) {}
1198"#,
1199 r#"
1200fn f(n: <a::b::<d>::c>) {}
1201"#,
1202 );
1203 type_char(
1204 '<',
1205 r#"
1206fn f(n: a$0b::<d>::c) {}
1207"#,
1208 r#"
1209fn f(n: a<>b::<d>::c) {}
1210"#,
1211 );
1212 }
1213
1214 #[test]
1215 fn parenthesis_noop_in_string_literal() {
1216 type_char_noop(
1218 '(',
1219 r##"
1220fn check_with(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
1221 let base = r#"
1222enum E { T(), R$0, C }
1223use self::E::X;
1224const Z: E = E::C;
1225mod m {}
1226asdasdasdasdasdasda
1227sdasdasdasdasdasda
1228sdasdasdasdasd
1229"#;
1230 let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
1231 expect.assert_eq(&actual)
1232}
1233 "##,
1234 );
1235 }
1236
1237 #[test]
1238 fn parenthesis_noop_in_item_position_with_macro() {
1239 type_char_noop('(', r#"$0println!();"#);
1240 }
1241
1242 #[test]
1243 fn parenthesis_noop_in_use_tree() {
1244 type_char_noop(
1245 '(',
1246 r#"
1247use some::$0Path;
1248 "#,
1249 );
1250 type_char_noop(
1251 '(',
1252 r#"
1253use some::{Path, $0Other};
1254 "#,
1255 );
1256 type_char_noop(
1257 '(',
1258 r#"
1259use some::{$0Path, Other};
1260 "#,
1261 );
1262 type_char_noop(
1263 '(',
1264 r#"
1265use some::path::$0to::Item;
1266 "#,
1267 );
1268 type_char_noop(
1269 '(',
1270 r#"
1271use some::$0path::to::Item;
1272 "#,
1273 );
1274 type_char_noop(
1275 '(',
1276 r#"
1277use $0some::path::to::Item;
1278 "#,
1279 );
1280 type_char_noop(
1281 '(',
1282 r#"
1283use some::path::$0to::{Item};
1284 "#,
1285 );
1286 type_char_noop(
1287 '(',
1288 r#"
1289use $0Thing as _;
1290 "#,
1291 );
1292
1293 type_char_noop(
1294 '(',
1295 r#"
1296use some::pa$0th::to::Item;
1297 "#,
1298 );
1299 type_char_noop(
1300 '<',
1301 r#"
1302use some::pa$0th::to::Item;
1303 "#,
1304 );
1305 }
1306
1307 #[test]
1308 fn adds_closing_angle_bracket_for_generic_args() {
1309 type_char(
1310 '<',
1311 r#"
1312fn foo() {
1313 bar::$0
1314}
1315 "#,
1316 r#"
1317fn foo() {
1318 bar::<>
1319}
1320 "#,
1321 );
1322
1323 type_char(
1324 '<',
1325 r#"
1326fn foo(bar: &[u64]) {
1327 bar.iter().collect::$0();
1328}
1329 "#,
1330 r#"
1331fn foo(bar: &[u64]) {
1332 bar.iter().collect::<>();
1333}
1334 "#,
1335 );
1336 }
1337
1338 #[test]
1339 fn adds_closing_angle_bracket_for_generic_params() {
1340 type_char(
1341 '<',
1342 r#"
1343fn foo$0() {}
1344 "#,
1345 r#"
1346fn foo<>() {}
1347 "#,
1348 );
1349 type_char(
1350 '<',
1351 r#"
1352fn foo$0
1353 "#,
1354 r#"
1355fn foo<>
1356 "#,
1357 );
1358 type_char(
1359 '<',
1360 r#"
1361struct Foo$0 {}
1362 "#,
1363 r#"
1364struct 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#"
1388enum Foo$0
1389 "#,
1390 r#"
1391enum Foo<>
1392 "#,
1393 );
1394 type_char(
1395 '<',
1396 r#"
1397trait Foo$0
1398 "#,
1399 r#"
1400trait Foo<>
1401 "#,
1402 );
1403 type_char(
1404 '<',
1405 r#"
1406type Foo$0 = Bar;
1407 "#,
1408 r#"
1409type Foo<> = Bar;
1410 "#,
1411 );
1412 type_char(
1413 '<',
1414 r#"
1415impl<T> Foo$0 {}
1416 "#,
1417 r#"
1418impl<T> Foo<> {}
1419 "#,
1420 );
1421 type_char(
1422 '<',
1423 r#"
1424impl Foo$0 {}
1425 "#,
1426 r#"
1427impl Foo<> {}
1428 "#,
1429 );
1430 }
1431
1432 #[test]
1433 fn dont_add_closing_angle_bracket_for_comparison() {
1434 type_char_noop(
1435 '<',
1436 r#"
1437fn main() {
1438 42$0
1439}
1440 "#,
1441 );
1442 type_char_noop(
1443 '<',
1444 r#"
1445fn main() {
1446 42 $0
1447}
1448 "#,
1449 );
1450 type_char_noop(
1451 '<',
1452 r#"
1453fn main() {
1454 let foo = 42;
1455 foo $0
1456}
1457 "#,
1458 );
1459 }
1460
1461 #[test]
1462 fn dont_add_closing_angle_bracket_if_it_is_already_there() {
1463 type_char_noop(
1464 '<',
1465 r#"
1466fn foo() {
1467 bar::$0>
1468}
1469 "#,
1470 );
1471 type_char_noop(
1472 '<',
1473 r#"
1474fn foo(bar: &[u64]) {
1475 bar.iter().collect::$0 >();
1476}
1477 "#,
1478 );
1479 type_char_noop(
1480 '<',
1481 r#"
1482fn foo$0>() {}
1483 "#,
1484 );
1485 type_char_noop(
1486 '<',
1487 r#"
1488fn foo$0>
1489 "#,
1490 );
1491 type_char_noop(
1492 '<',
1493 r#"
1494struct Foo$0> {}
1495 "#,
1496 );
1497 type_char_noop(
1498 '<',
1499 r#"
1500struct Foo$0>();
1501 "#,
1502 );
1503 type_char_noop(
1504 '<',
1505 r#"
1506struct Foo$0>
1507 "#,
1508 );
1509 type_char_noop(
1510 '<',
1511 r#"
1512enum Foo$0>
1513 "#,
1514 );
1515 type_char_noop(
1516 '<',
1517 r#"
1518trait Foo$0>
1519 "#,
1520 );
1521 type_char_noop(
1522 '<',
1523 r#"
1524type Foo$0> = Bar;
1525 "#,
1526 );
1527 type_char_noop(
1528 '<',
1529 r#"
1530impl$0> Foo {}
1531 "#,
1532 );
1533 type_char_noop(
1534 '<',
1535 r#"
1536impl<T> Foo$0> {}
1537 "#,
1538 );
1539 type_char_noop(
1540 '<',
1541 r#"
1542impl Foo$0> {}
1543 "#,
1544 );
1545 }
1546
1547 #[test]
1548 fn regression_629() {
1549 type_char_noop(
1550 '.',
1551 r#"
1552fn foo() {
1553 CompletionItem::new(
1554 CompletionKind::Reference,
1555 ctx.source_range(),
1556 field.name().to_string(),
1557 )
1558 .foo()
1559 $0
1560}
1561"#,
1562 );
1563 type_char_noop(
1564 '.',
1565 r#"
1566fn foo() {
1567 CompletionItem::new(
1568 CompletionKind::Reference,
1569 ctx.source_range(),
1570 field.name().to_string(),
1571 )
1572 $0
1573}
1574"#,
1575 );
1576 }
1577
1578 #[test]
1579 fn completes_pipe_param_list() {
1580 type_char(
1581 '|',
1582 r#"
1583fn foo() {
1584 $0
1585}
1586"#,
1587 r#"
1588fn foo() {
1589 ||
1590}
1591"#,
1592 );
1593 type_char(
1594 '|',
1595 r#"
1596fn foo() {
1597 $0 a
1598}
1599"#,
1600 r#"
1601fn foo() {
1602 || a
1603}
1604"#,
1605 );
1606 type_char_noop(
1607 '|',
1608 r#"
1609fn foo() {
1610 let $0
1611}
1612"#,
1613 );
1614 }
1615
1616 #[test]
1617 fn adds_parentheses_around_trait_object_in_ref_type() {
1618 type_char(
1619 '+',
1620 r#"
1621fn foo(x: &dyn A$0) {}
1622"#,
1623 r#"
1624fn foo(x: &(dyn A+)) {}
1625"#,
1626 );
1627 type_char(
1628 '+',
1629 r#"
1630fn foo(x: &'static dyn A$0B) {}
1631"#,
1632 r#"
1633fn foo(x: &'static (dyn A+B)) {}
1634"#,
1635 );
1636 type_char_noop(
1637 '+',
1638 r#"
1639fn foo(x: &(dyn A$0)) {}
1640"#,
1641 );
1642 type_char_noop(
1643 '+',
1644 r#"
1645fn foo(x: Box<dyn A$0>) {}
1646"#,
1647 );
1648 }
1649
1650 #[test]
1651 fn adds_parentheses_around_trait_object_in_ptr_type() {
1652 type_char(
1653 '+',
1654 r#"
1655fn foo(x: *const dyn A$0) {}
1656"#,
1657 r#"
1658fn foo(x: *const (dyn A+)) {}
1659"#,
1660 );
1661 }
1662
1663 #[test]
1664 fn adds_parentheses_around_trait_object_in_return_type() {
1665 type_char(
1666 '+',
1667 r#"
1668fn foo(x: fn() -> dyn A$0) {}
1669"#,
1670 r#"
1671fn foo(x: fn() -> (dyn A+)) {}
1672"#,
1673 );
1674 }
1675}