aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src
diff options
context:
space:
mode:
authorJonas Schievink <[email protected]>2021-04-06 22:55:39 +0100
committerJonas Schievink <[email protected]>2021-04-07 15:38:04 +0100
commit36cd724b7b146c33804db4b110111ad71be9cb72 (patch)
treec23b6b685a78c92a02d7102f7c5204dd5c16ba47 /crates/ide/src
parent8e900cb4a1c5a4faef801518272d56a5683dd488 (diff)
Autoclose blocks when typing `{`
Diffstat (limited to 'crates/ide/src')
-rw-r--r--crates/ide/src/typing.rs47
1 files changed, 46 insertions, 1 deletions
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs
index 11408d445..de65632e3 100644
--- a/crates/ide/src/typing.rs
+++ b/crates/ide/src/typing.rs
@@ -33,7 +33,8 @@ use crate::SourceChange;
33 33
34pub(crate) use on_enter::on_enter; 34pub(crate) use on_enter::on_enter;
35 35
36pub(crate) const TRIGGER_CHARS: &str = ".=>"; 36// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
37pub(crate) const TRIGGER_CHARS: &str = ".=>{";
37 38
38// Feature: On Typing Assists 39// Feature: On Typing Assists
39// 40//
@@ -70,10 +71,47 @@ fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) ->
70 '.' => on_dot_typed(file, offset), 71 '.' => on_dot_typed(file, offset),
71 '=' => on_eq_typed(file, offset), 72 '=' => on_eq_typed(file, offset),
72 '>' => on_arrow_typed(file, offset), 73 '>' => on_arrow_typed(file, offset),
74 '{' => on_opening_brace_typed(file, offset),
73 _ => unreachable!(), 75 _ => unreachable!(),
74 } 76 }
75} 77}
76 78
79/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
80/// block.
81fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
82 assert_eq!(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 = {
89 match block.tail_expr() {
90 Some(expr) => {
91 if block.statements().next().is_some() {
92 return None;
93 }
94 expr.syntax().text_range().end()
95 }
96 None => {
97 if block.statements().count() != 1 {
98 return None;
99 }
100
101 match block.statements().next()? {
102 ast::Stmt::ExprStmt(it) => {
103 // Use the expression span to place `}` before the `;`
104 it.expr()?.syntax().text_range().end()
105 }
106 _ => return None,
107 }
108 }
109 }
110 };
111
112 Some(TextEdit::insert(offset, "}".to_string()))
113}
114
77/// Returns an edit which should be applied after `=` was typed. Primarily, 115/// Returns an edit which should be applied after `=` was typed. Primarily,
78/// this works when adding `let =`. 116/// this works when adding `let =`.
79// FIXME: use a snippet completion instead of this hack here. 117// FIXME: use a snippet completion instead of this hack here.
@@ -373,4 +411,11 @@ fn main() {
373 fn adds_space_after_return_type() { 411 fn adds_space_after_return_type() {
374 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") 412 type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }")
375 } 413 }
414
415 #[test]
416 fn adds_closing_brace() {
417 type_char('{', r"fn f() { match () { _ => $0() } }", r"fn f() { match () { _ => {()} } }");
418 type_char('{', r"fn f() { $0(); }", r"fn f() { {()}; }");
419 type_char('{', r"fn f() { let x = $0(); }", r"fn f() { let x = {()}; }");
420 }
376} 421}