ide/
typing.rs

1//! This module handles auto-magic editing actions applied together with users
2//! edits. For example, if the user typed
3//!
4//! ```text
5//!     foo
6//!         .bar()
7//!         .baz()
8//!     |   // <- cursor is here
9//! ```
10//!
11//! and types `.` next, we want to indent the dot.
12//!
13//! Language server executes such typing assists synchronously. That is, they
14//! block user's typing and should be pretty fast for this reason!
15
16mod 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
36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &[char] = &['.', '=', '<', '>', '{', '(', '|', '+'];
38
39struct ExtendedTextEdit {
40    edit: TextEdit,
41    is_snippet: bool,
42}
43
44// Feature: On Typing Assists
45//
46// Some features trigger on typing certain characters:
47//
48// - typing `let =` tries to smartly add `;` if `=` is followed by an existing expression
49// - typing `=` between two expressions adds `;` when in statement position
50// - typing `=` to turn an assignment into an equality comparison removes `;` when in expression position
51// - typing `.` in a chain method call auto-indents
52// - typing `{` or `(` in front of an expression inserts a closing `}` or `)` after the expression
53// - typing `{` in a use item adds a closing `}` in the right place
54// - typing `>` to complete a return type `->` will insert a whitespace after it
55//
56// #### VS Code
57//
58// Add the following to `settings.json`:
59// ```json
60// "editor.formatOnType": true,
61// ```
62//
63// ![On Typing Assists](https://user-images.githubusercontent.com/48062697/113166163-69758500-923a-11eb-81ee-eb33ec380399.gif)
64// ![On Typing Assists](https://user-images.githubusercontent.com/48062697/113171066-105c2000-923f-11eb-87ab-f4a263346567.gif)
65pub(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
115/// Inserts a closing delimiter when the user types an opening bracket, wrapping an existing expression in a
116/// block, or a part of a `use` item (for `{`).
117fn 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    // Remove the opening bracket to get a better parse tree, and reparse.
142    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    // Insert the closing bracket right after the node.
212    Some(TextEdit::insert(
213        node.text_range().end() + TextSize::of(opening_bracket),
214        closing_bracket.to_string(),
215    ))
216}
217/// Returns an edit which should be applied after `=` was typed. Primarily,
218/// this works when adding `let =`.
219// FIXME: use a snippet completion instead of this hack here.
220fn 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    // don't attempt to add `;` if there is a newline after the `=`, the intent is likely to write
227    // out the expression afterwards!
228    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        // Parent must be `ExprStmt` or `StmtList` for `;` to be valid.
251        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    /// `a =$0 b;` removes the semicolon if an expression is valid in this context.
272    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            // Not the last statement in the list.
284            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        // Good indicator that we will insert into a bad spot, so bail out.
304        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
312/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
313fn 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    // if prior is fn call over multiple lines dont indent
318    // or if previous is method call over multiples lines keep that indent
319    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    // Make sure dot is a part of call chain
328    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        // if receiver is multiline field or method call, just take the previous `.` indentation
339        (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        // if receiver is multiline expression, just keeps its indentation
346        (_, true) => Some(IndentLevel::from_node(&parent)),
347        _ => None,
348    };
349    let target_indent = match target_indent {
350        Some(x) => x,
351        // in all other cases, take previous indentation and indent once
352        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
363/// Add closing `>` for generic arguments/parameters.
364fn 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    // Find the next non-whitespace char in the line, check if its a `>`
372    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        // Insert the closing bracket right after
389        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
429/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
430fn 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        // Regression test for #9351
918        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        // Regression test for #9351
1220        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}