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