Skip to content

Commit dd1832c

Browse files
Indent block expressions on enter
1 parent 9f25676 commit dd1832c

File tree

1 file changed

+212
-12
lines changed

1 file changed

+212
-12
lines changed

crates/ide/src/typing/on_enter.rs

Lines changed: 212 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
//! Handles the `Enter` key press. At the momently, this only continues
22
//! comments, but should handle indent some time in the future as well.
33
4-
use ide_db::base_db::{FilePosition, SourceDatabase};
4+
use std::sync::Arc;
5+
56
use ide_db::RootDatabase;
7+
use ide_db::{
8+
base_db::{FilePosition, SourceDatabase},
9+
line_index::LineIndex,
10+
LineIndexDatabase,
11+
};
612
use syntax::{
7-
ast::{self, AstToken},
13+
algo::find_node_at_offset,
14+
ast::{self, edit::IndentLevel, AstToken},
815
AstNode, SmolStr, SourceFile,
916
SyntaxKind::*,
10-
SyntaxToken, TextRange, TextSize, TokenAtOffset,
17+
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset,
1118
};
1219

1320
use text_edit::TextEdit;
@@ -19,6 +26,7 @@ use text_edit::TextEdit;
1926
// - kbd:[Enter] inside triple-slash comments automatically inserts `///`
2027
// - kbd:[Enter] in the middle or after a trailing space in `//` inserts `//`
2128
// - kbd:[Enter] inside `//!` doc comments automatically inserts `//!`
29+
// - kbd:[Enter] after `{` indents contents and closing `}` of single-line block
2230
//
2331
// This action needs to be assigned to shortcut explicitly.
2432
//
@@ -38,25 +46,42 @@ use text_edit::TextEdit;
3846
pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<TextEdit> {
3947
let parse = db.parse(position.file_id);
4048
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+
}
4663

64+
None
65+
}
66+
67+
fn on_enter_in_comment(
68+
comment: &ast::Comment,
69+
file: &ast::SourceFile,
70+
offset: TextSize,
71+
) -> Option<TextEdit> {
4772
if comment.kind().shape.is_block() {
4873
return None;
4974
}
5075

5176
let prefix = comment.prefix();
5277
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) {
5479
return None;
5580
}
5681

5782
let mut remove_trailing_whitespace = false;
5883
// 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 {
6085
if comment.text().ends_with(' ') {
6186
cov_mark::hit!(continues_end_of_line_comment_with_space);
6287
remove_trailing_whitespace = true;
@@ -70,14 +95,50 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
7095
let delete = if remove_trailing_whitespace {
7196
let trimmed_len = comment.text().trim_end().len() as u32;
7297
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)
7499
} else {
75-
TextRange::empty(position.offset)
100+
TextRange::empty(offset)
76101
};
77102
let edit = TextEdit::replace(delete, inserted);
78103
Some(edit)
79104
}
80105

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+
81142
fn followed_by_comment(comment: &ast::Comment) -> bool {
82143
let ws = match comment.syntax().next_token().and_then(ast::Whitespace::cast) {
83144
Some(it) => it,
@@ -296,4 +357,143 @@ fn main() {
296357
",
297358
);
298359
}
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+
}
299499
}

0 commit comments

Comments
 (0)