aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-08-28 09:12:42 +0100
committerAleksey Kladov <[email protected]>2018-08-28 09:12:42 +0100
commit2fa90e736b026ee979d9eb59178dc1f792228250 (patch)
treeadba3a569241a2030bf66e4e0b24ac5ffeaccbc3 /crates
parent13110f48e948d7554500aefc336e72f96041386b (diff)
better recovery for exprs
Diffstat (limited to 'crates')
-rw-r--r--crates/libeditor/src/lib.rs2
-rw-r--r--crates/libeditor/src/typing.rs24
-rw-r--r--crates/libeditor/tests/test.rs46
-rw-r--r--crates/libsyntax2/src/ast/generated.rs78
-rw-r--r--crates/libsyntax2/src/ast/mod.rs9
-rw-r--r--crates/libsyntax2/src/grammar.ron10
-rw-r--r--crates/libsyntax2/src/grammar/expressions/atom.rs5
-rw-r--r--crates/libsyntax2/src/parser_api.rs19
-rw-r--r--crates/libsyntax2/tests/data/parser/err/0017_incomplete_binexpr.txt5
-rw-r--r--crates/libsyntax2/tests/data/parser/err/0018_incomplete_fn.txt19
-rw-r--r--crates/libsyntax2/tests/data/parser/err/0019_let_recover.rs4
-rw-r--r--crates/libsyntax2/tests/data/parser/err/0019_let_recover.txt39
-rw-r--r--crates/server/src/caps.rs6
-rw-r--r--crates/server/src/main_loop/handlers.rs19
-rw-r--r--crates/server/src/main_loop/mod.rs4
-rw-r--r--crates/server/src/req.rs1
16 files changed, 263 insertions, 27 deletions
diff --git a/crates/libeditor/src/lib.rs b/crates/libeditor/src/lib.rs
index d39e56d81..34056b3c0 100644
--- a/crates/libeditor/src/lib.rs
+++ b/crates/libeditor/src/lib.rs
@@ -27,7 +27,7 @@ pub use self::{
27 ActionResult, 27 ActionResult,
28 flip_comma, add_derive, add_impl, 28 flip_comma, add_derive, add_impl,
29 }, 29 },
30 typing::join_lines, 30 typing::{join_lines, on_eq_typed},
31 completion::scope_completion, 31 completion::scope_completion,
32}; 32};
33 33
diff --git a/crates/libeditor/src/typing.rs b/crates/libeditor/src/typing.rs
index 060095f28..48a8d6bb0 100644
--- a/crates/libeditor/src/typing.rs
+++ b/crates/libeditor/src/typing.rs
@@ -7,11 +7,11 @@ use libsyntax2::{
7 walk::preorder, 7 walk::preorder,
8 find_covering_node, 8 find_covering_node,
9 }, 9 },
10 text_utils::intersect, 10 text_utils::{intersect, contains_offset_nonstrict},
11 SyntaxKind::*, 11 SyntaxKind::*,
12}; 12};
13 13
14use {ActionResult, EditBuilder}; 14use {ActionResult, EditBuilder, find_node_at_offset};
15 15
16pub fn join_lines(file: &File, range: TextRange) -> ActionResult { 16pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
17 let range = if range.is_empty() { 17 let range = if range.is_empty() {
@@ -56,6 +56,26 @@ pub fn join_lines(file: &File, range: TextRange) -> ActionResult {
56 } 56 }
57} 57}
58 58
59pub fn on_eq_typed(file: &File, offset: TextUnit) -> Option<ActionResult> {
60 let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
61 if let_stmt.has_semi() {
62 return None;
63 }
64 if let Some(expr) = let_stmt.initializer() {
65 let expr_range = expr.syntax().range();
66 if contains_offset_nonstrict(expr_range, offset) && offset != expr_range.start() {
67 return None;
68 }
69 }
70 let offset = let_stmt.syntax().range().end();
71 let mut edit = EditBuilder::new();
72 edit.insert(offset, ";".to_string());
73 Some(ActionResult {
74 edit: edit.finish(),
75 cursor_position: None,
76 })
77}
78
59fn remove_newline( 79fn remove_newline(
60 edit: &mut EditBuilder, 80 edit: &mut EditBuilder,
61 node: SyntaxNodeRef, 81 node: SyntaxNodeRef,
diff --git a/crates/libeditor/tests/test.rs b/crates/libeditor/tests/test.rs
index d8c24610d..17926d5ae 100644
--- a/crates/libeditor/tests/test.rs
+++ b/crates/libeditor/tests/test.rs
@@ -9,7 +9,7 @@ use libeditor::{
9 ActionResult, 9 ActionResult,
10 highlight, runnables, extend_selection, file_structure, 10 highlight, runnables, extend_selection, file_structure,
11 flip_comma, add_derive, add_impl, matching_brace, 11 flip_comma, add_derive, add_impl, matching_brace,
12 join_lines, scope_completion, 12 join_lines, on_eq_typed, scope_completion,
13}; 13};
14 14
15#[test] 15#[test]
@@ -227,7 +227,7 @@ pub fn reparse(&self, edit: &AtomEdit) -> File {
227#[test] 227#[test]
228fn test_join_lines_selection() { 228fn test_join_lines_selection() {
229 fn do_check(before: &str, after: &str) { 229 fn do_check(before: &str, after: &str) {
230 let (sel, before) = extract_range(&before); 230 let (sel, before) = extract_range(before);
231 let file = file(&before); 231 let file = file(&before);
232 let result = join_lines(&file, sel); 232 let result = join_lines(&file, sel);
233 let actual = result.edit.apply(&before); 233 let actual = result.edit.apply(&before);
@@ -257,6 +257,48 @@ struct Foo { f: u32 }
257} 257}
258 258
259#[test] 259#[test]
260fn test_on_eq_typed() {
261 fn do_check(before: &str, after: &str) {
262 let (offset, before) = extract_offset(before);
263 let file = file(&before);
264 let result = on_eq_typed(&file, offset).unwrap();
265 let actual = result.edit.apply(&before);
266 assert_eq_text!(after, &actual);
267 }
268
269 do_check(r"
270fn foo() {
271 let foo =<|>
272}
273", r"
274fn foo() {
275 let foo =;
276}
277");
278 do_check(r"
279fn foo() {
280 let foo =<|> 1 + 1
281}
282", r"
283fn foo() {
284 let foo = 1 + 1;
285}
286");
287// do_check(r"
288// fn foo() {
289// let foo =<|>
290// let bar = 1;
291// }
292// ", r"
293// fn foo() {
294// let foo =;
295// let bar = 1;
296// }
297// ");
298
299}
300
301#[test]
260fn test_completion() { 302fn test_completion() {
261 fn do_check(code: &str, expected_completions: &str) { 303 fn do_check(code: &str, expected_completions: &str) {
262 let (off, code) = extract_offset(&code); 304 let (off, code) = extract_offset(&code);
diff --git a/crates/libsyntax2/src/ast/generated.rs b/crates/libsyntax2/src/ast/generated.rs
index f99d1274a..6181aada8 100644
--- a/crates/libsyntax2/src/ast/generated.rs
+++ b/crates/libsyntax2/src/ast/generated.rs
@@ -439,6 +439,24 @@ impl<'a> ExprStmt<'a> {
439 } 439 }
440} 440}
441 441
442// ExternCrateItem
443#[derive(Debug, Clone, Copy)]
444pub struct ExternCrateItem<'a> {
445 syntax: SyntaxNodeRef<'a>,
446}
447
448impl<'a> AstNode<'a> for ExternCrateItem<'a> {
449 fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
450 match syntax.kind() {
451 EXTERN_CRATE_ITEM => Some(ExternCrateItem { syntax }),
452 _ => None,
453 }
454 }
455 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
456}
457
458impl<'a> ExternCrateItem<'a> {}
459
442// FieldExpr 460// FieldExpr
443#[derive(Debug, Clone, Copy)] 461#[derive(Debug, Clone, Copy)]
444pub struct FieldExpr<'a> { 462pub struct FieldExpr<'a> {
@@ -839,11 +857,51 @@ impl<'a> AstNode<'a> for Module<'a> {
839impl<'a> ast::NameOwner<'a> for Module<'a> {} 857impl<'a> ast::NameOwner<'a> for Module<'a> {}
840impl<'a> ast::AttrsOwner<'a> for Module<'a> {} 858impl<'a> ast::AttrsOwner<'a> for Module<'a> {}
841impl<'a> Module<'a> { 859impl<'a> Module<'a> {
842 pub fn modules(self) -> impl Iterator<Item = Module<'a>> + 'a { 860 pub fn items(self) -> impl Iterator<Item = ModuleItem<'a>> + 'a {
843 super::children(self) 861 super::children(self)
844 } 862 }
845} 863}
846 864
865// ModuleItem
866#[derive(Debug, Clone, Copy)]
867pub enum ModuleItem<'a> {
868 StructDef(StructDef<'a>),
869 EnumDef(EnumDef<'a>),
870 FnDef(FnDef<'a>),
871 TraitDef(TraitDef<'a>),
872 ImplItem(ImplItem<'a>),
873 UseItem(UseItem<'a>),
874 ExternCrateItem(ExternCrateItem<'a>),
875}
876
877impl<'a> AstNode<'a> for ModuleItem<'a> {
878 fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
879 match syntax.kind() {
880 STRUCT_DEF => Some(ModuleItem::StructDef(StructDef { syntax })),
881 ENUM_DEF => Some(ModuleItem::EnumDef(EnumDef { syntax })),
882 FN_DEF => Some(ModuleItem::FnDef(FnDef { syntax })),
883 TRAIT_DEF => Some(ModuleItem::TraitDef(TraitDef { syntax })),
884 IMPL_ITEM => Some(ModuleItem::ImplItem(ImplItem { syntax })),
885 USE_ITEM => Some(ModuleItem::UseItem(UseItem { syntax })),
886 EXTERN_CRATE_ITEM => Some(ModuleItem::ExternCrateItem(ExternCrateItem { syntax })),
887 _ => None,
888 }
889 }
890 fn syntax(self) -> SyntaxNodeRef<'a> {
891 match self {
892 ModuleItem::StructDef(inner) => inner.syntax(),
893 ModuleItem::EnumDef(inner) => inner.syntax(),
894 ModuleItem::FnDef(inner) => inner.syntax(),
895 ModuleItem::TraitDef(inner) => inner.syntax(),
896 ModuleItem::ImplItem(inner) => inner.syntax(),
897 ModuleItem::UseItem(inner) => inner.syntax(),
898 ModuleItem::ExternCrateItem(inner) => inner.syntax(),
899 }
900 }
901}
902
903impl<'a> ModuleItem<'a> {}
904
847// Name 905// Name
848#[derive(Debug, Clone, Copy)] 906#[derive(Debug, Clone, Copy)]
849pub struct Name<'a> { 907pub struct Name<'a> {
@@ -1762,6 +1820,24 @@ impl<'a> AstNode<'a> for TypeRef<'a> {
1762 1820
1763impl<'a> TypeRef<'a> {} 1821impl<'a> TypeRef<'a> {}
1764 1822
1823// UseItem
1824#[derive(Debug, Clone, Copy)]
1825pub struct UseItem<'a> {
1826 syntax: SyntaxNodeRef<'a>,
1827}
1828
1829impl<'a> AstNode<'a> for UseItem<'a> {
1830 fn cast(syntax: SyntaxNodeRef<'a>) -> Option<Self> {
1831 match syntax.kind() {
1832 USE_ITEM => Some(UseItem { syntax }),
1833 _ => None,
1834 }
1835 }
1836 fn syntax(self) -> SyntaxNodeRef<'a> { self.syntax }
1837}
1838
1839impl<'a> UseItem<'a> {}
1840
1765// WhereClause 1841// WhereClause
1766#[derive(Debug, Clone, Copy)] 1842#[derive(Debug, Clone, Copy)]
1767pub struct WhereClause<'a> { 1843pub struct WhereClause<'a> {
diff --git a/crates/libsyntax2/src/ast/mod.rs b/crates/libsyntax2/src/ast/mod.rs
index 2ebee6a4f..9941138a7 100644
--- a/crates/libsyntax2/src/ast/mod.rs
+++ b/crates/libsyntax2/src/ast/mod.rs
@@ -115,6 +115,15 @@ impl<'a> Module<'a> {
115 } 115 }
116} 116}
117 117
118impl<'a> LetStmt<'a> {
119 pub fn has_semi(self) -> bool {
120 match self.syntax().last_child() {
121 None => false,
122 Some(node) => node.kind() == SEMI,
123 }
124 }
125}
126
118impl<'a> IfExpr<'a> { 127impl<'a> IfExpr<'a> {
119 pub fn then_branch(self) -> Option<Block<'a>> { 128 pub fn then_branch(self) -> Option<Block<'a>> {
120 self.blocks().nth(0) 129 self.blocks().nth(0)
diff --git a/crates/libsyntax2/src/grammar.ron b/crates/libsyntax2/src/grammar.ron
index a98e9e2fd..7217a4633 100644
--- a/crates/libsyntax2/src/grammar.ron
+++ b/crates/libsyntax2/src/grammar.ron
@@ -273,7 +273,7 @@ Grammar(
273 "Module": ( 273 "Module": (
274 traits: ["NameOwner", "AttrsOwner"], 274 traits: ["NameOwner", "AttrsOwner"],
275 collections: [ 275 collections: [
276 ["modules", "Module"] 276 ["items", "ModuleItem"]
277 ] 277 ]
278 ), 278 ),
279 "ConstDef": ( traits: [ 279 "ConstDef": ( traits: [
@@ -331,6 +331,10 @@ Grammar(
331 "AttrsOwner" 331 "AttrsOwner"
332 ], 332 ],
333 ), 333 ),
334 "ModuleItem": (
335 enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "ImplItem",
336 "UseItem", "ExternCrateItem" ]
337 ),
334 338
335 "TupleExpr": (), 339 "TupleExpr": (),
336 "ArrayExpr": (), 340 "ArrayExpr": (),
@@ -479,6 +483,8 @@ Grammar(
479 ), 483 ),
480 "Param": ( 484 "Param": (
481 options: [["pat", "Pat"]], 485 options: [["pat", "Pat"]],
482 ) 486 ),
487 "UseItem": (),
488 "ExternCrateItem": (),
483 }, 489 },
484) 490)
diff --git a/crates/libsyntax2/src/grammar/expressions/atom.rs b/crates/libsyntax2/src/grammar/expressions/atom.rs
index ab4aa49d2..0769bb5a8 100644
--- a/crates/libsyntax2/src/grammar/expressions/atom.rs
+++ b/crates/libsyntax2/src/grammar/expressions/atom.rs
@@ -33,6 +33,9 @@ pub(super) const ATOM_EXPR_FIRST: TokenSet =
33 RETURN_KW, IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ], 33 RETURN_KW, IDENT, SELF_KW, SUPER_KW, COLONCOLON, BREAK_KW, CONTINUE_KW, LIFETIME ],
34 ]; 34 ];
35 35
36const EXPR_RECOVERY_SET: TokenSet =
37 token_set![LET_KW];
38
36pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMarker> { 39pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMarker> {
37 match literal(p) { 40 match literal(p) {
38 Some(m) => return Some(m), 41 Some(m) => return Some(m),
@@ -73,7 +76,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<CompletedMark
73 CONTINUE_KW => continue_expr(p), 76 CONTINUE_KW => continue_expr(p),
74 BREAK_KW => break_expr(p), 77 BREAK_KW => break_expr(p),
75 _ => { 78 _ => {
76 p.err_and_bump("expected expression"); 79 p.err_recover("expected expression", EXPR_RECOVERY_SET);
77 return None; 80 return None;
78 } 81 }
79 }; 82 };
diff --git a/crates/libsyntax2/src/parser_api.rs b/crates/libsyntax2/src/parser_api.rs
index 0a3b29b70..10b9b64ac 100644
--- a/crates/libsyntax2/src/parser_api.rs
+++ b/crates/libsyntax2/src/parser_api.rs
@@ -12,6 +12,8 @@ fn mask(kind: SyntaxKind) -> u128 {
12} 12}
13 13
14impl TokenSet { 14impl TokenSet {
15 const EMPTY: TokenSet = TokenSet(0);
16
15 pub fn contains(&self, kind: SyntaxKind) -> bool { 17 pub fn contains(&self, kind: SyntaxKind) -> bool {
16 self.0 & mask(kind) != 0 18 self.0 & mask(kind) != 0
17 } 19 }
@@ -139,12 +141,21 @@ impl<'t> Parser<'t> {
139 141
140 /// Create an error node and consume the next token. 142 /// Create an error node and consume the next token.
141 pub(crate) fn err_and_bump(&mut self, message: &str) { 143 pub(crate) fn err_and_bump(&mut self, message: &str) {
142 let m = self.start(); 144 self.err_recover(message, TokenSet::EMPTY);
143 self.error(message); 145 }
144 if !self.at(SyntaxKind::L_CURLY) && !self.at(SyntaxKind::R_CURLY) { 146
147 /// Create an error node and consume the next token.
148 pub(crate) fn err_recover(&mut self, message: &str, recovery_set: TokenSet) {
149 if self.at(SyntaxKind::L_CURLY)
150 || self.at(SyntaxKind::R_CURLY)
151 || recovery_set.contains(self.current()) {
152 self.error(message);
153 } else {
154 let m = self.start();
155 self.error(message);
145 self.bump(); 156 self.bump();
157 m.complete(self, ERROR);
146 } 158 }
147 m.complete(self, ERROR);
148 } 159 }
149} 160}
150 161
diff --git a/crates/libsyntax2/tests/data/parser/err/0017_incomplete_binexpr.txt b/crates/libsyntax2/tests/data/parser/err/0017_incomplete_binexpr.txt
index db9a2f175..f0be287ad 100644
--- a/crates/libsyntax2/tests/data/parser/err/0017_incomplete_binexpr.txt
+++ b/crates/libsyntax2/tests/data/parser/err/0017_incomplete_binexpr.txt
@@ -35,13 +35,12 @@ ROOT@[0; 47)
35 INT_NUMBER@[33; 35) "92" 35 INT_NUMBER@[33; 35) "92"
36 SEMI@[35; 36) 36 SEMI@[35; 36)
37 WHITESPACE@[36; 41) 37 WHITESPACE@[36; 41)
38 BIN_EXPR@[41; 45) 38 BIN_EXPR@[41; 44)
39 LITERAL@[41; 42) 39 LITERAL@[41; 42)
40 INT_NUMBER@[41; 42) "1" 40 INT_NUMBER@[41; 42) "1"
41 WHITESPACE@[42; 43) 41 WHITESPACE@[42; 43)
42 PLUS@[43; 44) 42 PLUS@[43; 44)
43 WHITESPACE@[44; 45)
44 err: `expected expression` 43 err: `expected expression`
45 ERROR@[45; 45) 44 WHITESPACE@[44; 45)
46 R_CURLY@[45; 46) 45 R_CURLY@[45; 46)
47 WHITESPACE@[46; 47) 46 WHITESPACE@[46; 47)
diff --git a/crates/libsyntax2/tests/data/parser/err/0018_incomplete_fn.txt b/crates/libsyntax2/tests/data/parser/err/0018_incomplete_fn.txt
index 3f3a7784b..58e39a341 100644
--- a/crates/libsyntax2/tests/data/parser/err/0018_incomplete_fn.txt
+++ b/crates/libsyntax2/tests/data/parser/err/0018_incomplete_fn.txt
@@ -11,12 +11,12 @@ ROOT@[0; 183)
11 ITEM_LIST@[14; 182) 11 ITEM_LIST@[14; 182)
12 L_CURLY@[14; 15) 12 L_CURLY@[14; 15)
13 WHITESPACE@[15; 20) 13 WHITESPACE@[15; 20)
14 FN_DEF@[20; 181) 14 FN_DEF@[20; 180)
15 FN_KW@[20; 22) 15 FN_KW@[20; 22)
16 WHITESPACE@[22; 23) 16 WHITESPACE@[22; 23)
17 NAME@[23; 32) 17 NAME@[23; 32)
18 IDENT@[23; 32) "new_scope" 18 IDENT@[23; 32) "new_scope"
19 PARAM_LIST@[32; 181) 19 PARAM_LIST@[32; 180)
20 L_PAREN@[32; 33) 20 L_PAREN@[32; 33)
21 PARAM@[33; 38) 21 PARAM@[33; 38)
22 REF_PAT@[33; 35) 22 REF_PAT@[33; 35)
@@ -163,17 +163,16 @@ ROOT@[0; 183)
163 err: `expected parameters` 163 err: `expected parameters`
164 err: `expected COMMA` 164 err: `expected COMMA`
165 WHITESPACE@[169; 170) 165 WHITESPACE@[169; 170)
166 PARAM@[170; 181) 166 PARAM@[170; 180)
167 BIND_PAT@[170; 180) 167 BIND_PAT@[170; 180)
168 NAME@[170; 180) 168 NAME@[170; 180)
169 IDENT@[170; 180) "set_parent" 169 IDENT@[170; 180) "set_parent"
170 err: `expected COLON` 170 err: `expected COLON`
171 WHITESPACE@[180; 181) 171 err: `expected type`
172 err: `expected type` 172 err: `expected COMMA`
173 err: `expected COMMA` 173 err: `expected value parameter`
174 err: `expected value parameter` 174 err: `expected R_PAREN`
175 err: `expected R_PAREN` 175 err: `expected a block`
176 err: `expected a block` 176 WHITESPACE@[180; 181)
177 ERROR@[181; 181)
178 R_CURLY@[181; 182) 177 R_CURLY@[181; 182)
179 WHITESPACE@[182; 183) 178 WHITESPACE@[182; 183)
diff --git a/crates/libsyntax2/tests/data/parser/err/0019_let_recover.rs b/crates/libsyntax2/tests/data/parser/err/0019_let_recover.rs
new file mode 100644
index 000000000..52bddd494
--- /dev/null
+++ b/crates/libsyntax2/tests/data/parser/err/0019_let_recover.rs
@@ -0,0 +1,4 @@
1fn foo() {
2 let foo =
3 let bar = 1;
4}
diff --git a/crates/libsyntax2/tests/data/parser/err/0019_let_recover.txt b/crates/libsyntax2/tests/data/parser/err/0019_let_recover.txt
new file mode 100644
index 000000000..9ffa9d781
--- /dev/null
+++ b/crates/libsyntax2/tests/data/parser/err/0019_let_recover.txt
@@ -0,0 +1,39 @@
1ROOT@[0; 44)
2 FN_DEF@[0; 43)
3 FN_KW@[0; 2)
4 WHITESPACE@[2; 3)
5 NAME@[3; 6)
6 IDENT@[3; 6) "foo"
7 PARAM_LIST@[6; 8)
8 L_PAREN@[6; 7)
9 R_PAREN@[7; 8)
10 WHITESPACE@[8; 9)
11 BLOCK@[9; 43)
12 L_CURLY@[9; 10)
13 WHITESPACE@[10; 15)
14 LET_STMT@[15; 24)
15 LET_KW@[15; 18)
16 WHITESPACE@[18; 19)
17 BIND_PAT@[19; 22)
18 NAME@[19; 22)
19 IDENT@[19; 22) "foo"
20 WHITESPACE@[22; 23)
21 EQ@[23; 24)
22 err: `expected expression`
23 err: `expected SEMI`
24 WHITESPACE@[24; 29)
25 LET_STMT@[29; 41)
26 LET_KW@[29; 32)
27 WHITESPACE@[32; 33)
28 BIND_PAT@[33; 36)
29 NAME@[33; 36)
30 IDENT@[33; 36) "bar"
31 WHITESPACE@[36; 37)
32 EQ@[37; 38)
33 WHITESPACE@[38; 39)
34 LITERAL@[39; 40)
35 INT_NUMBER@[39; 40) "1"
36 SEMI@[40; 41)
37 WHITESPACE@[41; 42)
38 R_CURLY@[42; 43)
39 WHITESPACE@[43; 44)
diff --git a/crates/server/src/caps.rs b/crates/server/src/caps.rs
index 98499bb05..7456aea8a 100644
--- a/crates/server/src/caps.rs
+++ b/crates/server/src/caps.rs
@@ -5,6 +5,7 @@ use languageserver_types::{
5 TextDocumentSyncKind, 5 TextDocumentSyncKind,
6 ExecuteCommandOptions, 6 ExecuteCommandOptions,
7 CompletionOptions, 7 CompletionOptions,
8 DocumentOnTypeFormattingOptions,
8}; 9};
9 10
10pub fn server_capabilities() -> ServerCapabilities { 11pub fn server_capabilities() -> ServerCapabilities {
@@ -35,7 +36,10 @@ pub fn server_capabilities() -> ServerCapabilities {
35 code_lens_provider: None, 36 code_lens_provider: None,
36 document_formatting_provider: None, 37 document_formatting_provider: None,
37 document_range_formatting_provider: None, 38 document_range_formatting_provider: None,
38 document_on_type_formatting_provider: None, 39 document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions {
40 first_trigger_character: "=".to_string(),
41 more_trigger_character: None,
42 }),
39 rename_provider: None, 43 rename_provider: None,
40 color_provider: None, 44 color_provider: None,
41 execute_command_provider: Some(ExecuteCommandOptions { 45 execute_command_provider: Some(ExecuteCommandOptions {
diff --git a/crates/server/src/main_loop/handlers.rs b/crates/server/src/main_loop/handlers.rs
index ec5421f06..ca5cd5ab1 100644
--- a/crates/server/src/main_loop/handlers.rs
+++ b/crates/server/src/main_loop/handlers.rs
@@ -314,6 +314,25 @@ pub fn handle_completion(
314 Ok(Some(req::CompletionResponse::Array(items))) 314 Ok(Some(req::CompletionResponse::Array(items)))
315} 315}
316 316
317pub fn handle_on_type_formatting(
318 world: ServerWorld,
319 params: req::DocumentOnTypeFormattingParams,
320) -> Result<Option<Vec<TextEdit>>> {
321 if params.ch != "=" {
322 return Ok(None);
323 }
324
325 let file_id = params.text_document.try_conv_with(&world)?;
326 let line_index = world.analysis().file_line_index(file_id)?;
327 let offset = params.position.conv_with(&line_index);
328 let file = world.analysis().file_syntax(file_id)?;
329 let action = match libeditor::on_eq_typed(&file, offset) {
330 None => return Ok(None),
331 Some(action) => action,
332 };
333 Ok(Some(action.edit.conv_with(&line_index)))
334}
335
317pub fn handle_execute_command( 336pub fn handle_execute_command(
318 world: ServerWorld, 337 world: ServerWorld,
319 mut params: req::ExecuteCommandParams, 338 mut params: req::ExecuteCommandParams,
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index da1121cb4..accb13878 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -31,6 +31,7 @@ use {
31 handle_completion, 31 handle_completion,
32 handle_runnables, 32 handle_runnables,
33 handle_decorations, 33 handle_decorations,
34 handle_on_type_formatting,
34 }, 35 },
35}; 36};
36 37
@@ -161,6 +162,9 @@ fn on_request(
161 handle_request_on_threadpool::<req::DecorationsRequest>( 162 handle_request_on_threadpool::<req::DecorationsRequest>(
162 &mut req, pool, world, sender, handle_decorations, 163 &mut req, pool, world, sender, handle_decorations,
163 )?; 164 )?;
165 handle_request_on_threadpool::<req::OnTypeFormatting>(
166 &mut req, pool, world, sender, handle_on_type_formatting,
167 )?;
164 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| { 168 dispatch::handle_request::<req::ExecuteCommand, _>(&mut req, |params, resp| {
165 io.send(RawMsg::Response(resp.into_response(Ok(None))?)); 169 io.send(RawMsg::Response(resp.into_response(Ok(None))?));
166 170
diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs
index 999fdb7c2..881069b1f 100644
--- a/crates/server/src/req.rs
+++ b/crates/server/src/req.rs
@@ -14,6 +14,7 @@ pub use languageserver_types::{
14 TextDocumentPositionParams, 14 TextDocumentPositionParams,
15 TextEdit, 15 TextEdit,
16 CompletionParams, CompletionResponse, 16 CompletionParams, CompletionResponse,
17 DocumentOnTypeFormattingParams,
17}; 18};
18 19
19 20