4
4
use ide_db:: base_db:: { FilePosition , SourceDatabase } ;
5
5
use ide_db:: RootDatabase ;
6
6
use syntax:: {
7
- ast:: { self , AstToken } ,
7
+ algo:: find_node_at_offset,
8
+ ast:: { self , edit:: IndentLevel , AstToken } ,
8
9
AstNode , SmolStr , SourceFile ,
9
10
SyntaxKind :: * ,
10
- SyntaxToken , TextRange , TextSize , TokenAtOffset ,
11
+ SyntaxNode , SyntaxToken , TextRange , TextSize , TokenAtOffset ,
11
12
} ;
12
13
13
14
use text_edit:: TextEdit ;
@@ -19,6 +20,7 @@ use text_edit::TextEdit;
19
20
// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
20
21
// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
21
22
// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
23
+ // - kbd:[Enter] after `{` indents contents and closing `}` of single-line block
22
24
//
23
25
// This action needs to be assigned to shortcut explicitly.
24
26
//
@@ -38,25 +40,43 @@ use text_edit::TextEdit;
38
40
pub ( crate ) fn on_enter ( db : & RootDatabase , position : FilePosition ) -> Option < TextEdit > {
39
41
let parse = db. parse ( position. file_id ) ;
40
42
let file = parse. tree ( ) ;
41
- let comment = file
42
- . syntax ( )
43
- . token_at_offset ( position. offset )
44
- . left_biased ( )
45
- . and_then ( ast:: Comment :: cast) ?;
43
+ let token = file. syntax ( ) . token_at_offset ( position. offset ) . left_biased ( ) ?;
46
44
45
+ if let Some ( comment) = ast:: Comment :: cast ( token. clone ( ) ) {
46
+ return on_enter_in_comment ( & comment, & file, position. offset ) ;
47
+ }
48
+
49
+ if token. kind ( ) == L_CURLY {
50
+ // Typing enter after the `{` of a block expression, where the `}` is on the same line
51
+ if let Some ( edit) = find_node_at_offset ( file. syntax ( ) , position. offset - TextSize :: of ( '{' ) )
52
+ . and_then ( |block| on_enter_in_block ( block, position) )
53
+ {
54
+ cov_mark:: hit!( indent_block_contents) ;
55
+ return Some ( edit) ;
56
+ }
57
+ }
58
+
59
+ None
60
+ }
61
+
62
+ fn on_enter_in_comment (
63
+ comment : & ast:: Comment ,
64
+ file : & ast:: SourceFile ,
65
+ offset : TextSize ,
66
+ ) -> Option < TextEdit > {
47
67
if comment. kind ( ) . shape . is_block ( ) {
48
68
return None ;
49
69
}
50
70
51
71
let prefix = comment. prefix ( ) ;
52
72
let comment_range = comment. syntax ( ) . text_range ( ) ;
53
- if position . offset < comment_range. start ( ) + TextSize :: of ( prefix) {
73
+ if offset < comment_range. start ( ) + TextSize :: of ( prefix) {
54
74
return None ;
55
75
}
56
76
57
77
let mut remove_trailing_whitespace = false ;
58
78
// Continuing single-line non-doc comments (like this one :) ) is annoying
59
- if prefix == "//" && comment_range. end ( ) == position . offset {
79
+ if prefix == "//" && comment_range. end ( ) == offset {
60
80
if comment. text ( ) . ends_with ( ' ' ) {
61
81
cov_mark:: hit!( continues_end_of_line_comment_with_space) ;
62
82
remove_trailing_whitespace = true ;
@@ -70,14 +90,42 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
70
90
let delete = if remove_trailing_whitespace {
71
91
let trimmed_len = comment. text ( ) . trim_end ( ) . len ( ) as u32 ;
72
92
let trailing_whitespace_len = comment. text ( ) . len ( ) as u32 - trimmed_len;
73
- TextRange :: new ( position . offset - TextSize :: from ( trailing_whitespace_len) , position . offset )
93
+ TextRange :: new ( offset - TextSize :: from ( trailing_whitespace_len) , offset)
74
94
} else {
75
- TextRange :: empty ( position . offset )
95
+ TextRange :: empty ( offset)
76
96
} ;
77
97
let edit = TextEdit :: replace ( delete, inserted) ;
78
98
Some ( edit)
79
99
}
80
100
101
+ fn on_enter_in_block ( block : ast:: BlockExpr , position : FilePosition ) -> Option < TextEdit > {
102
+ let contents = block_contents ( & block) ?;
103
+
104
+ if block. syntax ( ) . text ( ) . contains_char ( '\n' ) {
105
+ return None ;
106
+ }
107
+
108
+ let indent = IndentLevel :: from_node ( block. syntax ( ) ) ;
109
+ let mut edit = TextEdit :: insert ( position. offset , format ! ( "\n {}$0" , indent + 1 ) ) ;
110
+ edit. union ( TextEdit :: insert ( contents. text_range ( ) . end ( ) , format ! ( "\n {}" , indent) ) ) . ok ( ) ?;
111
+ Some ( edit)
112
+ }
113
+
114
+ fn block_contents ( block : & ast:: BlockExpr ) -> Option < SyntaxNode > {
115
+ let mut node = block. tail_expr ( ) . map ( |e| e. syntax ( ) . clone ( ) ) ;
116
+
117
+ for stmt in block. statements ( ) {
118
+ if node. is_some ( ) {
119
+ // More than 1 node in the block
120
+ return None ;
121
+ }
122
+
123
+ node = Some ( stmt. syntax ( ) . clone ( ) ) ;
124
+ }
125
+
126
+ node
127
+ }
128
+
81
129
fn followed_by_comment ( comment : & ast:: Comment ) -> bool {
82
130
let ws = match comment. syntax ( ) . next_token ( ) . and_then ( ast:: Whitespace :: cast) {
83
131
Some ( it) => it,
@@ -296,4 +344,144 @@ fn main() {
296
344
" ,
297
345
) ;
298
346
}
347
+
348
+ #[ test]
349
+ fn indents_fn_body_block ( ) {
350
+ cov_mark:: check!( indent_block_contents) ;
351
+ do_check (
352
+ r#"
353
+ fn f() {$0()}
354
+ "# ,
355
+ r#"
356
+ fn f() {
357
+ $0()
358
+ }
359
+ "# ,
360
+ ) ;
361
+ }
362
+
363
+ #[ test]
364
+ fn indents_block_expr ( ) {
365
+ do_check (
366
+ r#"
367
+ fn f() {
368
+ let x = {$0()};
369
+ }
370
+ "# ,
371
+ r#"
372
+ fn f() {
373
+ let x = {
374
+ $0()
375
+ };
376
+ }
377
+ "# ,
378
+ ) ;
379
+ }
380
+
381
+ #[ test]
382
+ fn indents_match_arm ( ) {
383
+ do_check (
384
+ r#"
385
+ fn f() {
386
+ match 6 {
387
+ 1 => {$0f()},
388
+ _ => (),
389
+ }
390
+ }
391
+ "# ,
392
+ r#"
393
+ fn f() {
394
+ match 6 {
395
+ 1 => {
396
+ $0f()
397
+ },
398
+ _ => (),
399
+ }
400
+ }
401
+ "# ,
402
+ ) ;
403
+ }
404
+
405
+ #[ test]
406
+ fn indents_block_with_statement ( ) {
407
+ do_check (
408
+ r#"
409
+ fn f() {$0a = b}
410
+ "# ,
411
+ r#"
412
+ fn f() {
413
+ $0a = b
414
+ }
415
+ "# ,
416
+ ) ;
417
+ do_check (
418
+ r#"
419
+ fn f() {$0fn f() {}}
420
+ "# ,
421
+ r#"
422
+ fn f() {
423
+ $0fn f() {}
424
+ }
425
+ "# ,
426
+ ) ;
427
+ }
428
+
429
+ #[ test]
430
+ fn indents_nested_blocks ( ) {
431
+ do_check (
432
+ r#"
433
+ fn f() {$0{}}
434
+ "# ,
435
+ r#"
436
+ fn f() {
437
+ $0{}
438
+ }
439
+ "# ,
440
+ ) ;
441
+ }
442
+
443
+ #[ test]
444
+ fn does_not_indent_empty_block ( ) {
445
+ do_check_noop (
446
+ r#"
447
+ fn f() {$0}
448
+ "# ,
449
+ ) ;
450
+ do_check_noop (
451
+ r#"
452
+ fn f() {{$0}}
453
+ "# ,
454
+ ) ;
455
+ }
456
+
457
+ #[ test]
458
+ fn does_not_indent_block_with_too_much_content ( ) {
459
+ do_check_noop (
460
+ r#"
461
+ fn f() {$0 a = b; ()}
462
+ "# ,
463
+ ) ;
464
+ do_check_noop (
465
+ r#"
466
+ fn f() {$0 a = b; a = b; }
467
+ "# ,
468
+ ) ;
469
+ }
470
+
471
+ #[ test]
472
+ fn does_not_indent_multiline_block ( ) {
473
+ do_check_noop (
474
+ r#"
475
+ fn f() {$0
476
+ }
477
+ "# ,
478
+ ) ;
479
+ do_check_noop (
480
+ r#"
481
+ fn f() {$0
482
+
483
+ }
484
+ "# ,
485
+ ) ;
486
+ }
299
487
}
0 commit comments