diff options
author | Jonas Schievink <[email protected]> | 2021-04-12 19:35:38 +0100 |
---|---|---|
committer | Jonas Schievink <[email protected]> | 2021-04-12 19:35:38 +0100 |
commit | dd1832c01661e19b8fc4da1111c0263468144ef2 (patch) | |
tree | 2e800b88fe2e25ec7e52ba9ad4cc44d96c5dd8de /crates/ide | |
parent | 9f25676f0cb8e89d621d9da5951a54ef814dbd0f (diff) |
Indent block expressions on enter
Diffstat (limited to 'crates/ide')
-rw-r--r-- | crates/ide/src/typing/on_enter.rs | 224 |
1 files changed, 212 insertions, 12 deletions
diff --git a/crates/ide/src/typing/on_enter.rs b/crates/ide/src/typing/on_enter.rs index 6f1ce3689..9a0696c6c 100644 --- a/crates/ide/src/typing/on_enter.rs +++ b/crates/ide/src/typing/on_enter.rs | |||
@@ -1,13 +1,20 @@ | |||
1 | //! Handles the `Enter` key press. At the momently, this only continues | 1 | //! Handles the `Enter` key press. At the momently, this only continues |
2 | //! comments, but should handle indent some time in the future as well. | 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 | use ide_db::RootDatabase; | 6 | use ide_db::RootDatabase; |
7 | use ide_db::{ | ||
8 | base_db::{FilePosition, SourceDatabase}, | ||
9 | line_index::LineIndex, | ||
10 | LineIndexDatabase, | ||
11 | }; | ||
6 | use syntax::{ | 12 | use syntax::{ |
7 | ast::{self, AstToken}, | 13 | algo::find_node_at_offset, |
14 | ast::{self, edit::IndentLevel, AstToken}, | ||
8 | AstNode, SmolStr, SourceFile, | 15 | AstNode, SmolStr, SourceFile, |
9 | SyntaxKind::*, | 16 | SyntaxKind::*, |
10 | SyntaxToken, TextRange, TextSize, TokenAtOffset, | 17 | SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset, |
11 | }; | 18 | }; |
12 | 19 | ||
13 | use text_edit::TextEdit; | 20 | use text_edit::TextEdit; |
@@ -19,6 +26,7 @@ use text_edit::TextEdit; | |||
19 | // - kbd:[Enter] inside triple-slash comments automatically inserts `///` | 26 | // - kbd:[Enter] inside triple-slash comments automatically inserts `///` |
20 | // - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` | 27 | // - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//` |
21 | // - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` | 28 | // - kbd:[Enter] inside `//!` doc comments automatically inserts `//!` |
29 | // - kbd:[Enter] after `{` indents contents and closing `}` of single-line block | ||
22 | // | 30 | // |
23 | // This action needs to be assigned to shortcut explicitly. | 31 | // This action needs to be assigned to shortcut explicitly. |
24 | // | 32 | // |
@@ -38,25 +46,42 @@ use text_edit::TextEdit; | |||
38 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { | 46 | pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { |
39 | let parse = db.parse(position.file_id); | 47 | let parse = db.parse(position.file_id); |
40 | let file = parse.tree(); | 48 | let file = parse.tree(); |
41 | let comment = file | 49 | let token = file.syntax().token_at_offset(position.offset).left_biased()?; |
42 | .syntax() | 50 | |
43 | .token_at_offset(position.offset) | 51 | if let Some(comment) = ast::Comment::cast(token.clone()) { |
44 | .left_biased() | 52 | return on_enter_in_comment(&comment, &file, position.offset); |
45 | .and_then(ast::Comment::cast)?; | 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 | if comment.kind().shape.is_block() { | 72 | if comment.kind().shape.is_block() { |
48 | return None; | 73 | return None; |
49 | } | 74 | } |
50 | 75 | ||
51 | let prefix = comment.prefix(); | 76 | let prefix = comment.prefix(); |
52 | let comment_range = comment.syntax().text_range(); | 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 | return None; | 79 | return None; |
55 | } | 80 | } |
56 | 81 | ||
57 | let mut remove_trailing_whitespace = false; | 82 | let mut remove_trailing_whitespace = false; |
58 | // Continuing single-line non-doc comments (like this one :) ) is annoying | 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 | if comment.text().ends_with(' ') { | 85 | if comment.text().ends_with(' ') { |
61 | cov_mark::hit!(continues_end_of_line_comment_with_space); | 86 | cov_mark::hit!(continues_end_of_line_comment_with_space); |
62 | remove_trailing_whitespace = true; | 87 | remove_trailing_whitespace = true; |
@@ -70,14 +95,50 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text | |||
70 | let delete = if remove_trailing_whitespace { | 95 | let delete = if remove_trailing_whitespace { |
71 | let trimmed_len = comment.text().trim_end().len() as u32; | 96 | let trimmed_len = comment.text().trim_end().len() as u32; |
72 | let trailing_whitespace_len = comment.text().len() as u32 - trimmed_len; | 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 | } else { | 99 | } else { |
75 | TextRange::empty(position.offset) | 100 | TextRange::empty(offset) |
76 | }; | 101 | }; |
77 | let edit = TextEdit::replace(delete, inserted); | 102 | let edit = TextEdit::replace(delete, inserted); |
78 | Some(edit) | 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 | fn followed_by_comment(comment: &ast::Comment) -> bool { | 142 | fn followed_by_comment(comment: &ast::Comment) -> bool { |
82 | let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { | 143 | let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) { |
83 | Some(it) => it, | 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 | } |