Skip to content

Commit 1412ac0

Browse files
Merge pull request #21957 from asukaminato0721/1665
fix: enhance part of onEnter
2 parents 38fb8f9 + 57dee1c commit 1412ac0

2 files changed

Lines changed: 133 additions & 62 deletions

File tree

crates/ide/src/typing/on_enter.rs

Lines changed: 132 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
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.
1+
//! Handles the `Enter` key press, including comment continuation and
2+
//! indentation in brace-delimited constructs.
33
44
use ide_db::{FilePosition, RootDatabase};
55
use syntax::{
66
AstNode, SmolStr, SourceFile,
77
SyntaxKind::*,
8-
SyntaxNode, SyntaxToken, TextRange, TextSize, TokenAtOffset,
9-
algo::find_node_at_offset,
8+
SyntaxToken, TextRange, TextSize, TokenAtOffset,
109
ast::{self, AstToken, edit::IndentLevel},
1110
};
1211

@@ -19,7 +18,8 @@ use ide_db::text_edit::TextEdit;
1918
// - <kbd>Enter</kbd> inside triple-slash comments automatically inserts `///`
2019
// - <kbd>Enter</kbd> in the middle or after a trailing space in `//` inserts `//`
2120
// - <kbd>Enter</kbd> inside `//!` doc comments automatically inserts `//!`
22-
// - <kbd>Enter</kbd> after `{` indents contents and closing `}` of single-line block
21+
// - <kbd>Enter</kbd> after `{` reformats single-line brace-delimited contents by
22+
// moving the text between `{` and the matching `}` onto an indented line
2323
//
2424
// This action needs to be assigned to shortcut explicitly.
2525
//
@@ -59,22 +59,11 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Text
5959
return on_enter_in_comment(&comment, &file, position.offset);
6060
}
6161

62-
if token.kind() == L_CURLY {
63-
// Typing enter after the `{` of a block expression, where the `}` is on the same line
64-
if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
65-
.and_then(|block| on_enter_in_block(block, position))
66-
{
67-
cov_mark::hit!(indent_block_contents);
68-
return Some(edit);
69-
}
70-
71-
// Typing enter after the `{` of a use tree list.
72-
if let Some(edit) = find_node_at_offset(file.syntax(), position.offset - TextSize::of('{'))
73-
.and_then(|list| on_enter_in_use_tree_list(list, position))
74-
{
75-
cov_mark::hit!(indent_block_contents);
76-
return Some(edit);
77-
}
62+
if token.kind() == L_CURLY
63+
&& let Some(edit) = on_enter_in_braces(token, position)
64+
{
65+
cov_mark::hit!(indent_block_contents);
66+
return Some(edit);
7867
}
7968

8069
None
@@ -119,44 +108,54 @@ fn on_enter_in_comment(
119108
Some(edit)
120109
}
121110

122-
fn on_enter_in_block(block: ast::BlockExpr, position: FilePosition) -> Option<TextEdit> {
123-
let contents = block_contents(&block)?;
124-
125-
if block.syntax().text().contains_char('\n') {
126-
return None;
127-
}
128-
129-
let indent = IndentLevel::from_node(block.syntax());
130-
let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
131-
edit.union(TextEdit::insert(contents.text_range().end(), format!("\n{indent}"))).ok()?;
132-
Some(edit)
133-
}
134-
135-
fn on_enter_in_use_tree_list(list: ast::UseTreeList, position: FilePosition) -> Option<TextEdit> {
136-
if list.syntax().text().contains_char('\n') {
111+
fn on_enter_in_braces(l_curly: SyntaxToken, position: FilePosition) -> Option<TextEdit> {
112+
if l_curly.text_range().end() != position.offset {
137113
return None;
138114
}
139115

140-
let indent = IndentLevel::from_node(list.syntax());
141-
let mut edit = TextEdit::insert(position.offset, format!("\n{}$0", indent + 1));
142-
edit.union(TextEdit::insert(list.r_curly_token()?.text_range().start(), format!("\n{indent}")))
143-
.ok()?;
144-
Some(edit)
116+
let (r_curly, content) = brace_contents_on_same_line(&l_curly)?;
117+
let indent = IndentLevel::from_token(&l_curly);
118+
Some(TextEdit::replace(
119+
TextRange::new(position.offset, r_curly.text_range().start()),
120+
format!("\n{}$0{}\n{indent}", indent + 1, content),
121+
))
145122
}
146123

147-
fn block_contents(block: &ast::BlockExpr) -> Option<SyntaxNode> {
148-
let mut node = block.tail_expr().map(|e| e.syntax().clone());
124+
fn brace_contents_on_same_line(l_curly: &SyntaxToken) -> Option<(SyntaxToken, String)> {
125+
let mut depth = 0_u32;
126+
let mut tokens = Vec::new();
127+
let mut token = l_curly.next_token()?;
149128

150-
for stmt in block.statements() {
151-
if node.is_some() {
152-
// More than 1 node in the block
129+
loop {
130+
if token.kind() == WHITESPACE && token.text().contains('\n') {
153131
return None;
154132
}
155133

156-
node = Some(stmt.syntax().clone());
157-
}
134+
match token.kind() {
135+
L_CURLY => {
136+
depth += 1;
137+
tokens.push(token.clone());
138+
}
139+
R_CURLY if depth == 0 => {
140+
let first = tokens.iter().position(|it| it.kind() != WHITESPACE);
141+
let last = tokens.iter().rposition(|it| it.kind() != WHITESPACE);
142+
let content = match first.zip(last) {
143+
Some((first, last)) => {
144+
tokens[first..=last].iter().map(|it| it.text()).collect()
145+
}
146+
None => String::new(),
147+
};
148+
return Some((token, content));
149+
}
150+
R_CURLY => {
151+
depth -= 1;
152+
tokens.push(token.clone());
153+
}
154+
_ => tokens.push(token.clone()),
155+
}
158156

159-
node
157+
token = token.next_token()?;
158+
}
160159
}
161160

162161
fn followed_by_comment(comment: &ast::Comment) -> bool {
@@ -382,10 +381,58 @@ fn main() {
382381
}
383382

384383
#[test]
385-
fn indents_fn_body_block() {
384+
fn indents_empty_brace_pairs() {
386385
cov_mark::check!(indent_block_contents);
387386
do_check(
388387
r#"
388+
fn f() {$0}
389+
"#,
390+
r#"
391+
fn f() {
392+
$0
393+
}
394+
"#,
395+
);
396+
do_check(
397+
r#"
398+
fn f() {
399+
let x = {$0};
400+
}
401+
"#,
402+
r#"
403+
fn f() {
404+
let x = {
405+
$0
406+
};
407+
}
408+
"#,
409+
);
410+
do_check(
411+
r#"
412+
use crate::{$0};
413+
"#,
414+
r#"
415+
use crate::{
416+
$0
417+
};
418+
"#,
419+
);
420+
do_check(
421+
r#"
422+
mod m {$0}
423+
"#,
424+
r#"
425+
mod m {
426+
$0
427+
}
428+
"#,
429+
);
430+
}
431+
432+
#[test]
433+
fn indents_fn_body_block() {
434+
do_check(
435+
r#"
389436
fn f() {$0()}
390437
"#,
391438
r#"
@@ -477,29 +524,39 @@ fn f() {
477524
}
478525

479526
#[test]
480-
fn does_not_indent_empty_block() {
481-
do_check_noop(
527+
fn indents_block_with_multiple_statements() {
528+
do_check(
482529
r#"
483-
fn f() {$0}
530+
fn f() {$0 a = b; ()}
531+
"#,
532+
r#"
533+
fn f() {
534+
$0a = b; ()
535+
}
484536
"#,
485537
);
486-
do_check_noop(
538+
do_check(
487539
r#"
488-
fn f() {{$0}}
540+
fn f() {$0 a = b; a = b; }
541+
"#,
542+
r#"
543+
fn f() {
544+
$0a = b; a = b;
545+
}
489546
"#,
490547
);
491548
}
492549

493550
#[test]
494-
fn does_not_indent_block_with_too_much_content() {
495-
do_check_noop(
551+
fn trims_spaces_around_brace_contents() {
552+
do_check(
496553
r#"
497-
fn f() {$0 a = b; ()}
554+
fn f() {$0 () }
498555
"#,
499-
);
500-
do_check_noop(
501556
r#"
502-
fn f() {$0 a = b; a = b; }
557+
fn f() {
558+
$0()
559+
}
503560
"#,
504561
);
505562
}
@@ -569,6 +626,20 @@ use {
569626
);
570627
}
571628

629+
#[test]
630+
fn indents_item_lists() {
631+
do_check(
632+
r#"
633+
mod m {$0}
634+
"#,
635+
r#"
636+
mod m {
637+
$0
638+
}
639+
"#,
640+
);
641+
}
642+
572643
#[test]
573644
fn does_not_indent_use_tree_list_when_not_at_curly_brace() {
574645
do_check_noop(

docs/book/src/contributing/lsp-extensions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ fn main() {
236236
```
237237

238238
The primary goal of `onEnter` is to handle automatic indentation when opening a new line.
239-
This is not yet implemented.
239+
This is partially implemented for single-line brace-delimited contents, in addition to comment continuation.
240240
The secondary goal is to handle fixing up syntax, like continuing doc strings and comments, and escaping `\n` in string literals.
241241

242242
As proper cursor positioning is raison d'être for `onEnter`, it uses `SnippetTextEdit`.

0 commit comments

Comments
 (0)