From 36cd724b7b146c33804db4b110111ad71be9cb72 Mon Sep 17 00:00:00 2001 From: Jonas Schievink Date: Tue, 6 Apr 2021 23:55:39 +0200 Subject: Autoclose blocks when typing `{` --- crates/ide/src/typing.rs | 47 +++++++++++++++++++++++++++++++++++- crates/rust-analyzer/src/caps.rs | 2 +- crates/rust-analyzer/src/handlers.rs | 1 - 3 files changed, 47 insertions(+), 3 deletions(-) (limited to 'crates') 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; pub(crate) use on_enter::on_enter; -pub(crate) const TRIGGER_CHARS: &str = ".=>"; +// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. +pub(crate) const TRIGGER_CHARS: &str = ".=>{"; // Feature: On Typing Assists // @@ -70,10 +71,47 @@ fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> '.' => on_dot_typed(file, offset), '=' => on_eq_typed(file, offset), '>' => on_arrow_typed(file, offset), + '{' => on_opening_brace_typed(file, offset), _ => unreachable!(), } } +/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a +/// block. +fn on_opening_brace_typed(file: &SourceFile, offset: TextSize) -> Option { + assert_eq!(file.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 + // either the trailing expr or an ExprStmt. + let offset = { + match block.tail_expr() { + Some(expr) => { + if block.statements().next().is_some() { + return None; + } + expr.syntax().text_range().end() + } + None => { + if block.statements().count() != 1 { + return None; + } + + match block.statements().next()? { + ast::Stmt::ExprStmt(it) => { + // Use the expression span to place `}` before the `;` + it.expr()?.syntax().text_range().end() + } + _ => return None, + } + } + } + }; + + Some(TextEdit::insert(offset, "}".to_string())) +} + /// Returns an edit which should be applied after `=` was typed. Primarily, /// this works when adding `let =`. // FIXME: use a snippet completion instead of this hack here. @@ -373,4 +411,11 @@ fn main() { fn adds_space_after_return_type() { type_char('>', "fn foo() -$0{ 92 }", "fn foo() -> { 92 }") } + + #[test] + 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 = {()}; }"); + } } diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 7a5bcb8c7..3c87782f2 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -57,7 +57,7 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti document_range_formatting_provider: None, document_on_type_formatting_provider: Some(DocumentOnTypeFormattingOptions { first_trigger_character: "=".to_string(), - more_trigger_character: Some(vec![".".to_string(), ">".to_string()]), + more_trigger_character: Some(vec![".".to_string(), ">".to_string(), "{".to_string()]), }), selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)), diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 4d10a2ead..31d8c487b 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -231,7 +231,6 @@ pub(crate) fn handle_on_enter( Ok(Some(edit)) } -// Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. pub(crate) fn handle_on_type_formatting( snap: GlobalStateSnapshot, params: lsp_types::DocumentOnTypeFormattingParams, -- cgit v1.2.3