aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2021-04-12 19:35:38 +0100
committerJonas Schievink <[email protected]>2021-04-12 19:35:38 +0100
commitdd1832c01661e19b8fc4da1111c0263468144ef2 (patch)
tree2e800b88fe2e25ec7e52ba9ad4cc44d96c5dd8de /crates/ide
parent9f25676f0cb8e89d621d9da5951a54ef814dbd0f (diff)
Indent block expressions on enter
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/typing/on_enter.rs224
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
4use ide_db::base_db::{FilePosition, SourceDatabase}; 4use std::sync::Arc;
5
5use ide_db::RootDatabase; 6use ide_db::RootDatabase;
7use ide_db::{
8 base_db::{FilePosition, SourceDatabase},
9 line_index::LineIndex,
10 LineIndexDatabase,
11};
6use syntax::{ 12use 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
13use text_edit::TextEdit; 20use 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;
38pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> { 46pub(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
67fn 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
106fn 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
127fn 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
81fn followed_by_comment(comment: &ast::Comment) -> bool { 142fn 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#"
365fn f() {$0()}
366 "#,
367 r#"
368fn f() {
369 $0()
370}
371 "#,
372 );
373 }
374
375 #[test]
376 fn indents_block_expr() {
377 do_check(
378 r#"
379fn f() {
380 let x = {$0()};
381}
382 "#,
383 r#"
384fn f() {
385 let x = {
386 $0()
387 };
388}
389 "#,
390 );
391 }
392
393 #[test]
394 fn indents_match_arm() {
395 do_check(
396 r#"
397fn f() {
398 match 6 {
399 1 => {$0f()},
400 _ => (),
401 }
402}
403 "#,
404 r#"
405fn 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#"
421fn f() {$0a = b}
422 "#,
423 r#"
424fn f() {
425 $0a = b
426}
427 "#,
428 );
429 do_check(
430 r#"
431fn f() {$0fn f() {}}
432 "#,
433 r#"
434fn f() {
435 $0fn f() {}
436}
437 "#,
438 );
439 }
440
441 #[test]
442 fn indents_nested_blocks() {
443 do_check(
444 r#"
445fn f() {$0{}}
446 "#,
447 r#"
448fn f() {
449 $0{}
450}
451 "#,
452 );
453 }
454
455 #[test]
456 fn does_not_indent_empty_block() {
457 do_check_noop(
458 r#"
459fn f() {$0}
460 "#,
461 );
462 do_check_noop(
463 r#"
464fn f() {{$0}}
465 "#,
466 );
467 }
468
469 #[test]
470 fn does_not_indent_block_with_too_much_content() {
471 do_check_noop(
472 r#"
473fn f() {$0 a = b; ()}
474 "#,
475 );
476 do_check_noop(
477 r#"
478fn 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#"
487fn f() {$0
488}
489 "#,
490 );
491 do_check_noop(
492 r#"
493fn f() {$0
494
495}
496 "#,
497 );
498 }
299} 499}