Rewrite, reparse modified file
This commit is contained in:
parent
17a1011a12
commit
3f599ae4ed
@ -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 => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user