@@ -22,12 +22,12 @@ use ide_db::{
22
22
use syntax:: {
23
23
algo:: find_node_at_offset,
24
24
ast:: { self , edit:: IndentLevel , AstToken } ,
25
- AstNode , SourceFile ,
25
+ AstNode , Parse , SourceFile ,
26
26
SyntaxKind :: { FIELD_EXPR , METHOD_CALL_EXPR } ,
27
27
TextRange , TextSize ,
28
28
} ;
29
29
30
- use text_edit:: TextEdit ;
30
+ use text_edit:: { Indel , TextEdit } ;
31
31
32
32
use crate :: SourceChange ;
33
33
@@ -59,42 +59,61 @@ pub(crate) fn on_char_typed(
59
59
char_typed : char ,
60
60
) -> Option < SourceChange > {
61
61
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) ) ;
64
64
let edit = on_char_typed_inner ( file, position. offset , char_typed) ?;
65
65
Some ( SourceChange :: from_text_edit ( position. file_id , edit) )
66
66
}
67
67
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 > {
69
73
assert ! ( TRIGGER_CHARS . contains( char_typed) ) ;
70
74
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) ,
74
78
'{' => on_opening_brace_typed ( file, offset) ,
75
79
_ => unreachable ! ( ) ,
76
80
}
77
81
}
78
82
79
83
/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
80
84
/// 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 ;
92
102
}
93
- None => block. tail_expr ( ) ?. syntax ( ) . text_range ( ) . end ( ) ,
94
- _ => return None ,
95
- } ;
96
103
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 ( ) ) )
98
117
}
99
118
100
119
/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -175,7 +194,7 @@ mod tests {
175
194
let edit = TextEdit :: insert ( offset, char_typed. to_string ( ) ) ;
176
195
edit. apply ( & mut before) ;
177
196
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| {
179
198
it. apply ( & mut before) ;
180
199
before. to_string ( )
181
200
} )
@@ -399,36 +418,82 @@ fn main() {
399
418
400
419
#[ test]
401
420
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 = {()}; }" ) ;
405
421
type_char (
406
422
'{' ,
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
+ "# ,
415
429
) ;
416
430
type_char (
417
431
'{' ,
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
+ "# ,
432
497
) ;
433
498
}
434
499
}
0 commit comments