Skip to content

Commit 3f599ae

Browse files
Rewrite, reparse modified file
1 parent 17a1011 commit 3f599ae

File tree

1 file changed

+114
-49
lines changed

1 file changed

+114
-49
lines changed

crates/ide/src/typing.rs

Lines changed: 114 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,12 @@ use ide_db::{
2222
use syntax::{
2323
algo::find_node_at_offset,
2424
ast::{self, edit::IndentLevel, AstToken},
25-
AstNode, SourceFile,
25+
AstNode, Parse, SourceFile,
2626
SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
2727
TextRange, TextSize,
2828
};
2929

30-
use text_edit::TextEdit;
30+
use text_edit::{Indel, TextEdit};
3131

3232
use crate::SourceChange;
3333

@@ -59,42 +59,61 @@ pub(crate) fn on_char_typed(
5959
char_typed: char,
6060
) -> Option<SourceChange> {
6161
assert!(TRIGGER_CHARS.contains(char_typed));
62-
let file = &db.parse(position.file_id).tree();
63-
assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
62+
let file = &db.parse(position.file_id);
63+
assert_eq!(file.tree().syntax().text().char_at(position.offset), Some(char_typed));
6464
let edit = on_char_typed_inner(file, position.offset, char_typed)?;
6565
Some(SourceChange::from_text_edit(position.file_id, edit))
6666
}
6767

68-
fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
68+
fn on_char_typed_inner(
69+
file: &Parse<SourceFile>,
70+
offset: TextSize,
71+
char_typed: char,
72+
) -> Option<TextEdit> {
6973
assert!(TRIGGER_CHARS.contains(char_typed));
7074
match char_typed {
71-
'.' => on_dot_typed(file, offset),
72-
'=' => on_eq_typed(file, offset),
73-
'>' => on_arrow_typed(file, offset),
75+
'.' => on_dot_typed(&file.tree(), offset),
76+
'=' => on_eq_typed(&file.tree(), offset),
77+
'>' => on_arrow_typed(&file.tree(), offset),
7478
'{' => on_opening_brace_typed(file, offset),
7579
_ => unreachable!(),
7680
}
7781
}
7882

7983
/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
8084
/// block.
81-
fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
82-
stdx::always!(file.syntax().text().char_at(offset) == Some('{'));
83-
let brace_token = file.syntax().token_at_offset(offset).right_biased()?;
84-
let block = ast::BlockExpr::cast(brace_token.parent()?)?;
85-
86-
// We expect a block expression enclosing exactly 1 preexisting expression. It can be parsed as
87-
// either the trailing expr or an ExprStmt.
88-
let offset = match block.statements().next() {
89-
Some(ast::Stmt::ExprStmt(it)) => {
90-
// Use the expression span to place `}` before the `;`
91-
it.expr()?.syntax().text_range().end()
85+
fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
86+
stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{'));
87+
88+
let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
89+
90+
// Remove the `{` to get a better parse tree, and reparse
91+
let file = file.reparse(&Indel::delete(brace_token.text_range()));
92+
93+
let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
94+
if expr.syntax().text_range().start() != offset {
95+
return None;
96+
}
97+
98+
// Enclose the outermost expression starting at `offset`
99+
while let Some(parent) = expr.syntax().parent() {
100+
if parent.text_range().start() != expr.syntax().text_range().start() {
101+
break;
92102
}
93-
None => block.tail_expr()?.syntax().text_range().end(),
94-
_ => return None,
95-
};
96103

97-
Some(TextEdit::insert(offset, "}".to_string()))
104+
match ast::Expr::cast(parent) {
105+
Some(parent) => expr = parent,
106+
None => break,
107+
}
108+
}
109+
110+
// If it's a statement in a block, we don't know how many statements should be included
111+
if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
112+
return None;
113+
}
114+
115+
// Insert `}` right after the expression.
116+
Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
98117
}
99118

100119
/// Returns an edit which should be applied after `=` was typed. Primarily,
@@ -175,7 +194,7 @@ mod tests {
175194
let edit = TextEdit::insert(offset, char_typed.to_string());
176195
edit.apply(&mut before);
177196
let parse = SourceFile::parse(&before);
178-
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
197+
on_char_typed_inner(&parse, offset, char_typed).map(|it| {
179198
it.apply(&mut before);
180199
before.to_string()
181200
})
@@ -399,36 +418,82 @@ fn main() {
399418

400419
#[test]
401420
fn adds_closing_brace() {
402-
type_char('{', r"fn f() { match () { _ => $0() } }", r"fn f() { match () { _ => {()} } }");
403-
type_char('{', r"fn f() { $0(); }", r"fn f() { {()}; }");
404-
type_char('{', r"fn f() { let x = $0(); }", r"fn f() { let x = {()}; }");
405421
type_char(
406422
'{',
407-
r"
408-
const S: () = $0();
409-
fn f() {}
410-
",
411-
r"
412-
const S: () = {()};
413-
fn f() {}
414-
",
423+
r#"
424+
fn f() { match () { _ => $0() } }
425+
"#,
426+
r#"
427+
fn f() { match () { _ => {()} } }
428+
"#,
415429
);
416430
type_char(
417431
'{',
418-
r"
419-
fn f() {
420-
match x {
421-
0 => $0(),
422-
1 => (),
423-
}
424-
}",
425-
r"
426-
fn f() {
427-
match x {
428-
0 => {()},
429-
1 => (),
430-
}
431-
}",
432+
r#"
433+
fn f() { $0() }
434+
"#,
435+
r#"
436+
fn f() { {()} }
437+
"#,
438+
);
439+
type_char(
440+
'{',
441+
r#"
442+
fn f() { let x = $0(); }
443+
"#,
444+
r#"
445+
fn f() { let x = {()}; }
446+
"#,
447+
);
448+
type_char(
449+
'{',
450+
r#"
451+
fn f() { let x = $0a.b(); }
452+
"#,
453+
r#"
454+
fn f() { let x = {a.b()}; }
455+
"#,
456+
);
457+
type_char(
458+
'{',
459+
r#"
460+
const S: () = $0();
461+
fn f() {}
462+
"#,
463+
r#"
464+
const S: () = {()};
465+
fn f() {}
466+
"#,
467+
);
468+
type_char(
469+
'{',
470+
r#"
471+
const S: () = $0a.b();
472+
fn f() {}
473+
"#,
474+
r#"
475+
const S: () = {a.b()};
476+
fn f() {}
477+
"#,
478+
);
479+
type_char(
480+
'{',
481+
r#"
482+
fn f() {
483+
match x {
484+
0 => $0(),
485+
1 => (),
486+
}
487+
}
488+
"#,
489+
r#"
490+
fn f() {
491+
match x {
492+
0 => {()},
493+
1 => (),
494+
}
495+
}
496+
"#,
432497
);
433498
}
434499
}

0 commit comments

Comments
 (0)