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