better recovery for exprs

This commit is contained in:
Aleksey Kladov 2018-08-28 11:12:42 +03:00
parent 13110f48e9
commit 2fa90e736b
16 changed files with 263 additions and 27 deletions

View file

@ -27,7 +27,7 @@ pub use self::{
ActionResult, ActionResult,
flip_comma, add_derive, add_impl, flip_comma, add_derive, add_impl,
}, },
typing::join_lines, typing::{join_lines, on_eq_typed},
completion::scope_completion, completion::scope_completion,
}; };

View file

@ -7,11 +7,11 @@ use libsyntax2::{
walk::preorder, walk::preorder,
find_covering_node, find_covering_node,
}, },
text_utils::intersect, text_utils::{intersect, contains_offset_nonstrict},
SyntaxKind::*, SyntaxKind::*,
}; };
use {ActionResult, EditBuilder}; use {ActionResult, EditBuilder, find_node_at_offset};
pub fn join_lines(file: &File, range: TextRange) -> ActionResult { pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
let range = if range.is_empty() { let range = if range.is_empty() {
@ -56,6 +56,26 @@ pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
} }
} }
pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> {
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
if let_stmt.has_semi() {
return None;
}
if let Some(expr) = let_stmt.initializer() {
let expr_range = expr.syntax().range();
if contains_offset_nonstrict(expr_range, offset) && offset != expr_range.start() {
return None;
}
}
let offset = let_stmt.syntax().range().end();
let mut edit = EditBuilder::new();
edit.insert(offset, ";".to_string());
Some(ActionResult {
edit: edit.finish(),
cursor_position: None,
})
}
fn remove_newline( fn remove_newline(
edit: &mut EditBuilder, edit: &mut EditBuilder,
node: SyntaxNodeRef, node: SyntaxNodeRef,

View file

@ -9,7 +9,7 @@ use libeditor::{
ActionResult, ActionResult,
highlight, runnables, extend_selection, file_structure, highlight, runnables, extend_selection, file_structure,
flip_comma, add_derive, add_impl, matching_brace, flip_comma, add_derive, add_impl, matching_brace,
join_lines, scope_completion, join_lines, on_eq_typed, scope_completion,
}; };
#[test] #[test]
@ -227,7 +227,7 @@ pub fn reparse(&self, edit: &AtomEdit) -> File {
#[test] #[test]
fn test_join_lines_selection() { fn test_join_lines_selection() {
fn do_check(before: &str, after: &str) { fn do_check(before: &str, after: &str) {
let (sel, before) = extract_range(&before); let (sel, before) = extract_range(before);
let file = file(&before); let file = file(&before);
let result = join_lines(&file, sel); let result = join_lines(&file, sel);
let actual = result.edit.apply(&before); let actual = result.edit.apply(&before);
@ -256,6 +256,48 @@ struct Foo { f: u32 }
"); ");
} }
#[test]
fn test_on_eq_typed() {
fn do_check(before: &str, after: &str) {
let (offset, before) = extract_offset(before);
let file = file(&before);
let result = on_eq_typed(&file, offset).unwrap();
let actual = result.edit.apply(&before);
assert_eq_text!(after, &actual);
}
do_check(r"
fn foo() {
let foo =<|>
}
", r"
fn foo() {
let foo =;
}
");
do_check(r"
fn foo() {
let foo =<|> 1 + 1
}
", r"
fn foo() {
let foo = 1 + 1;
}
");
// do_check(r"
// fn foo() {
// let foo =<|>
// let bar = 1;
// }
// ", r"
// fn foo() {
// let foo =;
// let bar = 1;
// }
// ");
}
#[test] #[test]
fn test_completion() { fn test_completion() {
fn do_check(code: &str, expected_completions: &str) { fn do_check(code: &str, expected_completions: &str) {

View file

@ -439,6 +439,24 @@ impl<'a> ExprStmt<'a> {
} }
} }
// ExternCrateItem
#[derive(Debug, Clone, Copy)]
pub struct ExternCrateItem<'a> {
syntax: SyntaxNodeRef<'a>,
}
impl<'a> AstNode<'a> for ExternCrateItem<'a> {
fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
match syntax.kind() {
EXTERN_CRATE_ITEM => Some(ExternCrateItem { syntax }),
_ => None,
}
}
fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
}
impl<'a> ExternCrateItem<'a> {}
// FieldExpr // FieldExpr
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct FieldExpr<'a> { pub struct FieldExpr<'a> {
@ -839,11 +857,51 @@ impl<'a> AstNode<'a> for Module<'a> {
impl<'a> ast::NameOwner<'a> for Module<'a> {} impl<'a> ast::NameOwner<'a> for Module<'a> {}
impl<'a> ast::AttrsOwner<'a> for Module<'a> {} impl<'a> ast::AttrsOwner<'a> for Module<'a> {}
impl<'a> Module<'a> { impl<'a> Module<'a> {
pub fn modules(self) -> impl Iterator<Item = Module<'a>> + 'a { pub fn items(self) -> impl Iterator<Item = ModuleItem<'a>> + 'a {
super::children(self) super::children(self)
} }
} }
// ModuleItem
#[derive(Debug, Clone, Copy)]
pub enum ModuleItem<'a> {
StructDef(StructDef<'a>),
EnumDef(EnumDef<'a>),
FnDef(FnDef<'a>),
TraitDef(TraitDef<'a>),
ImplItem(ImplItem<'a>),
UseItem(UseItem<'a>),
ExternCrateItem(ExternCrateItem<'a>),
}
impl<'a> AstNode<'a> for ModuleItem<'a> {
fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
match syntax.kind() {
STRUCT_DEF => Some(ModuleItem::StructDef(StructDef { syntax })),
ENUM_DEF => Some(ModuleItem::EnumDef(EnumDef { syntax })),
FN_DEF => Some(ModuleItem::FnDef(FnDef { syntax })),
TRAIT_DEF => Some(ModuleItem::TraitDef(TraitDef { syntax })),
IMPL_ITEM => Some(ModuleItem::ImplItem(ImplItem { syntax })),
USE_ITEM => Some(ModuleItem::UseItem(UseItem { syntax })),
EXTERN_CRATE_ITEM => Some(ModuleItem::ExternCrateItem(ExternCrateItem { syntax })),
_ => None,
}
}
fn syntax(self) -> SyntaxNodeRef<'a> {
match self {
ModuleItem::StructDef(inner) => inner.syntax(),
ModuleItem::EnumDef(inner) => inner.syntax(),
ModuleItem::FnDef(inner) => inner.syntax(),
ModuleItem::TraitDef(inner) => inner.syntax(),
ModuleItem::ImplItem(inner) => inner.syntax(),
ModuleItem::UseItem(inner) => inner.syntax(),
ModuleItem::ExternCrateItem(inner) => inner.syntax(),
}
}
}
impl<'a> ModuleItem<'a> {}
// Name // Name
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct Name<'a> { pub struct Name<'a> {
@ -1762,6 +1820,24 @@ impl<'a> AstNode<'a> for TypeRef<'a> {
impl<'a> TypeRef<'a> {} impl<'a> TypeRef<'a> {}
// UseItem
#[derive(Debug, Clone, Copy)]
pub struct UseItem<'a> {
syntax: SyntaxNodeRef<'a>,
}
impl<'a> AstNode<'a> for UseItem<'a> {
fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
match syntax.kind() {
USE_ITEM => Some(UseItem { syntax }),
_ => None,
}
}
fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
}
impl<'a> UseItem<'a> {}
// WhereClause // WhereClause
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub struct WhereClause<'a> { pub struct WhereClause<'a> {

View file

@ -115,6 +115,15 @@ impl<'a> Module<'a> {
} }
} }
impl<'a> LetStmt<'a> {
pub fn has_semi(self) -> bool {
match self.syntax().last_child() {
None => false,
Some(node) => node.kind() == SEMI,
}
}
}
impl<'a> IfExpr<'a> { impl<'a> IfExpr<'a> {
pub fn then_branch(self) -> Option<Block<'a>> { pub fn then_branch(self) -> Option<Block<'a>> {
self.blocks().nth(0) self.blocks().nth(0)

View file

@ -273,7 +273,7 @@ Grammar(
"Module": ( "Module": (
traits: ["NameOwner", "AttrsOwner"], traits: ["NameOwner", "AttrsOwner"],
collections: [ collections: [
["modules", "Module"] ["items", "ModuleItem"]
] ]
), ),
"ConstDef": ( traits: [ "ConstDef": ( traits: [
@ -331,6 +331,10 @@ Grammar(
"AttrsOwner" "AttrsOwner"
], ],
), ),
"ModuleItem": (
enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "ImplItem",
"UseItem", "ExternCrateItem" ]
),
"TupleExpr": (), "TupleExpr": (),
"ArrayExpr": (), "ArrayExpr": (),
@ -479,6 +483,8 @@ Grammar(
), ),
"Param": ( "Param": (
options: [["pat", "Pat"]], options: [["pat", "Pat"]],
) ),
"UseItem": (),
"ExternCrateItem": (),
}, },
) )

View file

@ -33,6 +33,9 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
RETURN_KW, IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ], RETURN_KW, IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ],
]; ];
const EXPR_RECOVERY_SET: TokenSet =
token_set![LET_KW];
pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMarker> { pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMarker> {
match literal(p) { match literal(p) {
Some(m) => return Some(m), Some(m) => return Some(m),
@ -73,7 +76,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMark
CONTINUE_KW => continue_expr(p), CONTINUE_KW => continue_expr(p),
BREAK_KW => break_expr(p), BREAK_KW => break_expr(p),
_ => { _ => {
p.err_and_bump("expected expression"); p.err_recover("expected expression", EXPR_RECOVERY_SET);
return None; return None;
} }
}; };

View file

@ -12,6 +12,8 @@ fn mask(kind: SyntaxKind) -> u128 {
} }
impl TokenSet { impl TokenSet {
const EMPTY: TokenSet = TokenSet(0);
pub fn contains(&self, kind: SyntaxKind) -> bool { pub fn contains(&self, kind: SyntaxKind) -> bool {
self.0 & mask(kind) != 0 self.0 & mask(kind) != 0
} }
@ -139,13 +141,22 @@ impl<'t> Parser<'t> {
/// Create an error node and consume the next token. /// Create an error node and consume the next token.
pub(crate) fn err_and_bump(&mut self, message: &str) { pub(crate) fn err_and_bump(&mut self, message: &str) {
self.err_recover(message, TokenSet::EMPTY);
}
/// Create an error node and consume the next token.
pub(crate) fn err_recover(&mut self, message: &str, recovery_set: TokenSet) {
if self.at(SyntaxKind::L_CURLY)
|| self.at(SyntaxKind::R_CURLY)
|| recovery_set.contains(self.current()) {
self.error(message);
} else {
let m = self.start(); let m = self.start();
self.error(message); self.error(message);
if !self.at(SyntaxKind::L_CURLY) && !self.at(SyntaxKind::R_CURLY) {
self.bump(); self.bump();
}
m.complete(self, ERROR); m.complete(self, ERROR);
} }
}
} }
/// See `Parser::start`. /// See `Parser::start`.

View file

@ -35,13 +35,12 @@ ROOT@[0; 47)
INT_NUMBER@[33; 35) "92" INT_NUMBER@[33; 35) "92"
SEMI@[35; 36) SEMI@[35; 36)
WHITESPACE@[36; 41) WHITESPACE@[36; 41)
BIN_EXPR@[41; 45) BIN_EXPR@[41; 44)
LITERAL@[41; 42) LITERAL@[41; 42)
INT_NUMBER@[41; 42) "1" INT_NUMBER@[41; 42) "1"
WHITESPACE@[42; 43) WHITESPACE@[42; 43)
PLUS@[43; 44) PLUS@[43; 44)
WHITESPACE@[44; 45)
err: `expected expression` err: `expected expression`
ERROR@[45; 45) WHITESPACE@[44; 45)
R_CURLY@[45; 46) R_CURLY@[45; 46)
WHITESPACE@[46; 47) WHITESPACE@[46; 47)

View file

@ -11,12 +11,12 @@ ROOT@[0; 183)
ITEM_LIST@[14; 182) ITEM_LIST@[14; 182)
L_CURLY@[14; 15) L_CURLY@[14; 15)
WHITESPACE@[15; 20) WHITESPACE@[15; 20)
FN_DEF@[20; 181) FN_DEF@[20; 180)
FN_KW@[20; 22) FN_KW@[20; 22)
WHITESPACE@[22; 23) WHITESPACE@[22; 23)
NAME@[23; 32) NAME@[23; 32)
IDENT@[23; 32) "new_scope" IDENT@[23; 32) "new_scope"
PARAM_LIST@[32; 181) PARAM_LIST@[32; 180)
L_PAREN@[32; 33) L_PAREN@[32; 33)
PARAM@[33; 38) PARAM@[33; 38)
REF_PAT@[33; 35) REF_PAT@[33; 35)
@ -163,17 +163,16 @@ ROOT@[0; 183)
err: `expected parameters` err: `expected parameters`
err: `expected COMMA` err: `expected COMMA`
WHITESPACE@[169; 170) WHITESPACE@[169; 170)
PARAM@[170; 181) PARAM@[170; 180)
BIND_PAT@[170; 180) BIND_PAT@[170; 180)
NAME@[170; 180) NAME@[170; 180)
IDENT@[170; 180) "set_parent" IDENT@[170; 180) "set_parent"
err: `expected COLON` err: `expected COLON`
WHITESPACE@[180; 181)
err: `expected type` err: `expected type`
err: `expected COMMA` err: `expected COMMA`
err: `expected value parameter` err: `expected value parameter`
err: `expected R_PAREN` err: `expected R_PAREN`
err: `expected a block` err: `expected a block`
ERROR@[181; 181) WHITESPACE@[180; 181)
R_CURLY@[181; 182) R_CURLY@[181; 182)
WHITESPACE@[182; 183) WHITESPACE@[182; 183)

View file

@ -0,0 +1,4 @@
fn foo() {
let foo =
let bar = 1;
}

View file

@ -0,0 +1,39 @@
ROOT@[0; 44)
FN_DEF@[0; 43)
FN_KW@[0; 2)
WHITESPACE@[2; 3)
NAME@[3; 6)
IDENT@[3; 6) "foo"
PARAM_LIST@[6; 8)
L_PAREN@[6; 7)
R_PAREN@[7; 8)
WHITESPACE@[8; 9)
BLOCK@[9; 43)
L_CURLY@[9; 10)
WHITESPACE@[10; 15)
LET_STMT@[15; 24)
LET_KW@[15; 18)
WHITESPACE@[18; 19)
BIND_PAT@[19; 22)
NAME@[19; 22)
IDENT@[19; 22) "foo"
WHITESPACE@[22; 23)
EQ@[23; 24)
err: `expected expression`
err: `expected SEMI`
WHITESPACE@[24; 29)
LET_STMT@[29; 41)
LET_KW@[29; 32)
WHITESPACE@[32; 33)
BIND_PAT@[33; 36)
NAME@[33; 36)
IDENT@[33; 36) "bar"
WHITESPACE@[36; 37)
EQ@[37; 38)
WHITESPACE@[38; 39)
LITERAL@[39; 40)
INT_NUMBER@[39; 40) "1"
SEMI@[40; 41)
WHITESPACE@[41; 42)
R_CURLY@[42; 43)
WHITESPACE@[43; 44)

View file

@ -5,6 +5,7 @@ use languageserver_types::{
TextDocumentSyncKind, TextDocumentSyncKind,
ExecuteCommandOptions, ExecuteCommandOptions,
CompletionOptions, CompletionOptions,
DocumentOnTypeFormattingOptions,
}; };
pub fn server_capabilities() -> ServerCapabilities { pub fn server_capabilities() -> ServerCapabilities {
@ -35,7 +36,10 @@ pub fn server_capabilities() -> ServerCapabilities {
code_lens_provider: None, code_lens_provider: None,
document_formatting_provider: None, document_formatting_provider: None,
document_range_formatting_provider: None, document_range_formatting_provider: None,
document_on_type_formatting_provider: None, document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
first_trigger_character: "=".to_string(),
more_trigger_character: None,
}),
rename_provider: None, rename_provider: None,
color_provider: None, color_provider: None,
execute_command_provider: Some(ExecuteCommandOptions { execute_command_provider: Some(ExecuteCommandOptions {

View file

@ -314,6 +314,25 @@ pub fn handle_completion(
Ok(Some(req::CompletionResponse::Array(items))) Ok(Some(req::CompletionResponse::Array(items)))
} }
pub fn handle_on_type_formatting(
world: ServerWorld,
params: req::DocumentOnTypeFormattingParams,
) -> Result<Option<Vec<TextEdit>>> {
if params.ch != "=" {
return Ok(None);
}
let file_id = params.text_document.try_conv_with(&world)?;
let line_index = world.analysis().file_line_index(file_id)?;
let offset = params.position.conv_with(&line_index);
let file = world.analysis().file_syntax(file_id)?;
let action = match libeditor::on_eq_typed(&file, offset) {
None => return Ok(None),
Some(action) => action,
};
Ok(Some(action.edit.conv_with(&line_index)))
}
pub fn handle_execute_command( pub fn handle_execute_command(
world: ServerWorld, world: ServerWorld,
mut params: req::ExecuteCommandParams, mut params: req::ExecuteCommandParams,

View file

@ -31,6 +31,7 @@ use {
handle_completion, handle_completion,
handle_runnables, handle_runnables,
handle_decorations, handle_decorations,
handle_on_type_formatting,
}, },
}; };
@ -161,6 +162,9 @@ fn on_request(
handle_request_on_threadpool::<req::DecorationsRequest>( handle_request_on_threadpool::<req::DecorationsRequest>(
&mut req, pool, world, sender, handle_decorations, &mut req, pool, world, sender, handle_decorations,
)?; )?;
handle_request_on_threadpool::<req::OnTypeFormatting>(
&mut req, pool, world, sender, handle_on_type_formatting,
)?;
dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| { dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
io.send(RawMsg::Response(resp.into_response(Ok(None))?)); io.send(RawMsg::Response(resp.into_response(Ok(None))?));

View file

@ -14,6 +14,7 @@ pub use languageserver_types::{
TextDocumentPositionParams, TextDocumentPositionParams,
TextEdit, TextEdit,
CompletionParams, CompletionResponse, CompletionParams, CompletionResponse,
DocumentOnTypeFormattingParams,
}; };