@@ -22,12 +22,12 @@ use ide_db::{
2222use syntax:: {
2323 algo:: find_node_at_offset,
2424 ast:: { self , edit:: IndentLevel , AstToken } ,
25- AstNode , SourceFile ,
25+ AstNode , Parse , SourceFile ,
2626 SyntaxKind :: { FIELD_EXPR , METHOD_CALL_EXPR } ,
2727 TextRange , TextSize ,
2828} ;
2929
30- use text_edit:: TextEdit ;
30+ use text_edit:: { Indel , TextEdit } ;
3131
3232use crate :: SourceChange ;
3333
@@ -59,42 +59,61 @@ pub(crate) fn on_char_typed(
5959 char_typed : char ,
6060) -> Option < SourceChange > {
6161 assert ! ( TRIGGER_CHARS . contains( char_typed) ) ;
62- let file = & db. parse ( position. file_id ) . tree ( ) ;
63- assert_eq ! ( file. syntax( ) . text( ) . char_at( position. offset) , Some ( char_typed) ) ;
62+ let file = & db. parse ( position. file_id ) ;
63+ assert_eq ! ( file. tree ( ) . syntax( ) . text( ) . char_at( position. offset) , Some ( char_typed) ) ;
6464 let edit = on_char_typed_inner ( file, position. offset , char_typed) ?;
6565 Some ( SourceChange :: from_text_edit ( position. file_id , edit) )
6666}
6767
68- fn on_char_typed_inner ( file : & SourceFile , offset : TextSize , char_typed : char ) -> Option < TextEdit > {
68+ fn on_char_typed_inner (
69+ file : & Parse < SourceFile > ,
70+ offset : TextSize ,
71+ char_typed : char ,
72+ ) -> Option < TextEdit > {
6973 assert ! ( TRIGGER_CHARS . contains( char_typed) ) ;
7074 match char_typed {
71- '.' => on_dot_typed ( file, offset) ,
72- '=' => on_eq_typed ( file, offset) ,
73- '>' => on_arrow_typed ( file, offset) ,
75+ '.' => on_dot_typed ( & file. tree ( ) , offset) ,
76+ '=' => on_eq_typed ( & file. tree ( ) , offset) ,
77+ '>' => on_arrow_typed ( & file. tree ( ) , offset) ,
7478 '{' => on_opening_brace_typed ( file, offset) ,
7579 _ => unreachable ! ( ) ,
7680 }
7781}
7882
7983/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
8084/// block.
81- fn on_opening_brace_typed ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
82- stdx:: always!( file. syntax( ) . text( ) . char_at( offset) == Some ( '{' ) ) ;
83- let brace_token = file. syntax ( ) . token_at_offset ( offset) . right_biased ( ) ?;
84- let block = ast:: BlockExpr :: cast ( brace_token. parent ( ) ?) ?;
85-
86- // We expect a block expression enclosing exactly 1 preexisting expression. It can be parsed as
87- // either the trailing expr or an ExprStmt.
88- let offset = match block. statements ( ) . next ( ) {
89- Some ( ast:: Stmt :: ExprStmt ( it) ) => {
90- // Use the expression span to place `}` before the `;`
91- it. expr ( ) ?. syntax ( ) . text_range ( ) . end ( )
85+ fn on_opening_brace_typed ( file : & Parse < SourceFile > , offset : TextSize ) -> Option < TextEdit > {
86+ stdx:: always!( file. tree( ) . syntax( ) . text( ) . char_at( offset) == Some ( '{' ) ) ;
87+
88+ let brace_token = file. tree ( ) . syntax ( ) . token_at_offset ( offset) . right_biased ( ) ?;
89+
90+ // Remove the `{` to get a better parse tree, and reparse
91+ let file = file. reparse ( & Indel :: delete ( brace_token. text_range ( ) ) ) ;
92+
93+ let mut expr: ast:: Expr = find_node_at_offset ( file. tree ( ) . syntax ( ) , offset) ?;
94+ if expr. syntax ( ) . text_range ( ) . start ( ) != offset {
95+ return None ;
96+ }
97+
98+ // Enclose the outermost expression starting at `offset`
99+ while let Some ( parent) = expr. syntax ( ) . parent ( ) {
100+ if parent. text_range ( ) . start ( ) != expr. syntax ( ) . text_range ( ) . start ( ) {
101+ break ;
92102 }
93- None => block. tail_expr ( ) ?. syntax ( ) . text_range ( ) . end ( ) ,
94- _ => return None ,
95- } ;
96103
97- Some ( TextEdit :: insert ( offset, "}" . to_string ( ) ) )
104+ match ast:: Expr :: cast ( parent) {
105+ Some ( parent) => expr = parent,
106+ None => break ,
107+ }
108+ }
109+
110+ // If it's a statement in a block, we don't know how many statements should be included
111+ if ast:: ExprStmt :: can_cast ( expr. syntax ( ) . parent ( ) ?. kind ( ) ) {
112+ return None ;
113+ }
114+
115+ // Insert `}` right after the expression.
116+ Some ( TextEdit :: insert ( expr. syntax ( ) . text_range ( ) . end ( ) + TextSize :: of ( "{" ) , "}" . to_string ( ) ) )
98117}
99118
100119/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -175,7 +194,7 @@ mod tests {
175194 let edit = TextEdit :: insert ( offset, char_typed. to_string ( ) ) ;
176195 edit. apply ( & mut before) ;
177196 let parse = SourceFile :: parse ( & before) ;
178- on_char_typed_inner ( & parse. tree ( ) , offset, char_typed) . map ( |it| {
197+ on_char_typed_inner ( & parse, offset, char_typed) . map ( |it| {
179198 it. apply ( & mut before) ;
180199 before. to_string ( )
181200 } )
@@ -399,36 +418,82 @@ fn main() {
399418
400419 #[ test]
401420 fn adds_closing_brace ( ) {
402- type_char ( '{' , r"fn f() { match () { _ => $0() } }" , r"fn f() { match () { _ => {()} } }" ) ;
403- type_char ( '{' , r"fn f() { $0(); }" , r"fn f() { {()}; }" ) ;
404- type_char ( '{' , r"fn f() { let x = $0(); }" , r"fn f() { let x = {()}; }" ) ;
405421 type_char (
406422 '{' ,
407- r"
408- const S: () = $0();
409- fn f() {}
410- " ,
411- r"
412- const S: () = {()};
413- fn f() {}
414- " ,
423+ r#"
424+ fn f() { match () { _ => $0() } }
425+ "# ,
426+ r#"
427+ fn f() { match () { _ => {()} } }
428+ "# ,
415429 ) ;
416430 type_char (
417431 '{' ,
418- r"
419- fn f() {
420- match x {
421- 0 => $0(),
422- 1 => (),
423- }
424- }" ,
425- r"
426- fn f() {
427- match x {
428- 0 => {()},
429- 1 => (),
430- }
431- }" ,
432+ r#"
433+ fn f() { $0() }
434+ "# ,
435+ r#"
436+ fn f() { {()} }
437+ "# ,
438+ ) ;
439+ type_char (
440+ '{' ,
441+ r#"
442+ fn f() { let x = $0(); }
443+ "# ,
444+ r#"
445+ fn f() { let x = {()}; }
446+ "# ,
447+ ) ;
448+ type_char (
449+ '{' ,
450+ r#"
451+ fn f() { let x = $0a.b(); }
452+ "# ,
453+ r#"
454+ fn f() { let x = {a.b()}; }
455+ "# ,
456+ ) ;
457+ type_char (
458+ '{' ,
459+ r#"
460+ const S: () = $0();
461+ fn f() {}
462+ "# ,
463+ r#"
464+ const S: () = {()};
465+ fn f() {}
466+ "# ,
467+ ) ;
468+ type_char (
469+ '{' ,
470+ r#"
471+ const S: () = $0a.b();
472+ fn f() {}
473+ "# ,
474+ r#"
475+ const S: () = {a.b()};
476+ fn f() {}
477+ "# ,
478+ ) ;
479+ type_char (
480+ '{' ,
481+ r#"
482+ fn f() {
483+ match x {
484+ 0 => $0(),
485+ 1 => (),
486+ }
487+ }
488+ "# ,
489+ r#"
490+ fn f() {
491+ match x {
492+ 0 => {()},
493+ 1 => (),
494+ }
495+ }
496+ "# ,
432497 ) ;
433498 }
434499}
0 commit comments