Rewrite, reparse modified file

This commit is contained in:
Jonas Schievink 2021-04-07 16:37:47 +02:00
parent 17a1011a12
commit 3f599ae4ed

View File

@ -22,12 +22,12 @@
use syntax::{ use syntax::{
algo::find_node_at_offset, algo::find_node_at_offset,
ast::{self, edit::IndentLevel, AstToken}, ast::{self, edit::IndentLevel, AstToken},
AstNode, SourceFile, AstNode, Parse, SourceFile,
SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR},
TextRange, TextSize, TextRange, TextSize,
}; };
use text_edit::TextEdit; use text_edit::{Indel, TextEdit};
use crate::SourceChange; use crate::SourceChange;
@ -59,18 +59,22 @@ pub(crate) fn on_char_typed(
char_typed: char, char_typed: char,
) -> Option<SourceChange> { ) -> Option<SourceChange> {
assert!(TRIGGER_CHARS.contains(char_typed)); assert!(TRIGGER_CHARS.contains(char_typed));
let file = &db.parse(position.file_id).tree(); let file = &db.parse(position.file_id);
assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); assert_eq!(file.tree().syntax().text().char_at(position.offset), Some(char_typed));
let edit = on_char_typed_inner(file, position.offset, char_typed)?; let edit = on_char_typed_inner(file, position.offset, char_typed)?;
Some(SourceChange::from_text_edit(position.file_id, edit)) Some(SourceChange::from_text_edit(position.file_id, edit))
} }
fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> { fn on_char_typed_inner(
file: &Parse<SourceFile>,
offset: TextSize,
char_typed: char,
) -> Option<TextEdit> {
assert!(TRIGGER_CHARS.contains(char_typed)); assert!(TRIGGER_CHARS.contains(char_typed));
match char_typed { match char_typed {
'.' => on_dot_typed(file, offset), '.' => on_dot_typed(&file.tree(), offset),
'=' => on_eq_typed(file, offset), '=' => on_eq_typed(&file.tree(), offset),
'>' => on_arrow_typed(file, offset), '>' => on_arrow_typed(&file.tree(), offset),
'{' => on_opening_brace_typed(file, offset), '{' => on_opening_brace_typed(file, offset),
_ => unreachable!(), _ => unreachable!(),
} }
@ -78,23 +82,38 @@ fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) ->
/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a /// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
/// block. /// block.
fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
stdx::always!(file.syntax().text().char_at(offset) == Some('{')); stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{'));
let brace_token = file.syntax().token_at_offset(offset).right_biased()?;
let block = ast::BlockExpr::cast(brace_token.parent()?)?;
// We expect a block expression enclosing exactly 1 preexisting expression. It can be parsed as let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
// either the trailing expr or an ExprStmt.
let offset = match block.statements().next() { // Remove the `{` to get a better parse tree, and reparse
Some(ast::Stmt::ExprStmt(it)) => { let file = file.reparse(&Indel::delete(brace_token.text_range()));
// Use the expression span to place `}` before the `;`
it.expr()?.syntax().text_range().end() let mut expr: ast::Expr = find_node_at_offset(file.tree().syntax(), offset)?;
if expr.syntax().text_range().start() != offset {
return None;
}
// Enclose the outermost expression starting at `offset`
while let Some(parent) = expr.syntax().parent() {
if parent.text_range().start() != expr.syntax().text_range().start() {
break;
} }
None => block.tail_expr()?.syntax().text_range().end(),
_ => return None,
};
Some(TextEdit::insert(offset, "}".to_string())) match ast::Expr::cast(parent) {
Some(parent) => expr = parent,
None => break,
}
}
// If it's a statement in a block, we don't know how many statements should be included
if ast::ExprStmt::can_cast(expr.syntax().parent()?.kind()) {
return None;
}
// Insert `}` right after the expression.
Some(TextEdit::insert(expr.syntax().text_range().end() + TextSize::of("{"), "}".to_string()))
} }
/// Returns an edit which should be applied after `=` was typed. Primarily, /// Returns an edit which should be applied after `=` was typed. Primarily,
@ -175,7 +194,7 @@ fn do_type_char(char_typed: char, before: &str) -> Option<String> {
let edit = TextEdit::insert(offset, char_typed.to_string()); let edit = TextEdit::insert(offset, char_typed.to_string());
edit.apply(&mut before); edit.apply(&mut before);
let parse = SourceFile::parse(&before); let parse = SourceFile::parse(&before);
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| { on_char_typed_inner(&parse, offset, char_typed).map(|it| {
it.apply(&mut before); it.apply(&mut before);
before.to_string() before.to_string()
}) })
@ -399,36 +418,82 @@ fn adds_space_after_return_type() {
#[test] #[test]
fn adds_closing_brace() { fn adds_closing_brace() {
type_char('{', r"fn f() { match () { _ => $0() } }", r"fn f() { match () { _ => {()} } }");
type_char('{', r"fn f() { $0(); }", r"fn f() { {()}; }");
type_char('{', r"fn f() { let x = $0(); }", r"fn f() { let x = {()}; }");
type_char( type_char(
'{', '{',
r" r#"
const S: () = $0(); fn f() { match () { _ => $0() } }
fn f() {} "#,
", r#"
r" fn f() { match () { _ => {()} } }
const S: () = {()}; "#,
fn f() {}
",
); );
type_char( type_char(
'{', '{',
r" r#"
fn f() { fn f() { $0() }
match x { "#,
0 => $0(), r#"
1 => (), fn f() { {()} }
} "#,
}", );
r" type_char(
fn f() { '{',
match x { r#"
0 => {()}, fn f() { let x = $0(); }
1 => (), "#,
} r#"
}", fn f() { let x = {()}; }
"#,
);
type_char(
'{',
r#"
fn f() { let x = $0a.b(); }
"#,
r#"
fn f() { let x = {a.b()}; }
"#,
);
type_char(
'{',
r#"
const S: () = $0();
fn f() {}
"#,
r#"
const S: () = {()};
fn f() {}
"#,
);
type_char(
'{',
r#"
const S: () = $0a.b();
fn f() {}
"#,
r#"
const S: () = {a.b()};
fn f() {}
"#,
);
type_char(
'{',
r#"
fn f() {
match x {
0 => $0(),
1 => (),
}
}
"#,
r#"
fn f() {
match x {
0 => {()},
1 => (),
}
}
"#,
); );
} }
} }