diff options
| author | bors <bors@rust-lang.org> | 2023-09-06 20:32:04 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-09-06 20:32:04 +0000 |
| commit | 77b359ae31427051d62a8c7e39b56bc9798f23ed (patch) | |
| tree | b31600984ad36a8de15f151a62959d9e1f3162a9 | |
| parent | 5906c26e851cc554e986d5116d9536b16cebf8b4 (diff) | |
| parent | da786170f82717bba8086e6618382e57b5e98180 (diff) | |
| download | rust-77b359ae31427051d62a8c7e39b56bc9798f23ed.tar.gz rust-77b359ae31427051d62a8c7e39b56bc9798f23ed.zip | |
Auto merge of #15532 - SomeoneToIgnore:more-brackets-on-type-formatting, r=Veykril
On type format '(', by adding closing ')' automatically
If I understand right, `()` can surround pretty much the same `{}` can, so add another on type formatting pair for convenience: sometimes it's not that pleasant to write parenthesis in `Some(2).map(|i| (i, i+1))` cases and I would prefer r-a to do that for me.
One note: currently, https://github.com/rust-lang/rust-analyzer/blob/b06503b6ec98c9ed44698870cbf3302b8560b442/crates/rust-analyzer/src/handlers/request.rs#L357 fires always.
Should we remove the assertion entirely now, since apparently things work in release despite that check?
| -rw-r--r-- | crates/ide/src/typing.rs | 237 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/caps.rs | 2 | ||||
| -rw-r--r-- | crates/rust-analyzer/src/handlers/request.rs | 2 |
3 files changed, 223 insertions, 18 deletions
diff --git a/crates/ide/src/typing.rs b/crates/ide/src/typing.rs index 27dedab13ea..b40509715ba 100644 --- a/crates/ide/src/typing.rs +++ b/crates/ide/src/typing.rs @@ -32,7 +32,7 @@ use crate::SourceChange; pub(crate) use on_enter::on_enter; // Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`. -pub(crate) const TRIGGER_CHARS: &str = ".=<>{"; +pub(crate) const TRIGGER_CHARS: &str = ".=<>{("; struct ExtendedTextEdit { edit: TextEdit, @@ -94,36 +94,49 @@ fn on_char_typed_inner( '=' => conv(on_eq_typed(&file.tree(), offset)), '<' => on_left_angle_typed(&file.tree(), offset), '>' => conv(on_right_angle_typed(&file.tree(), offset)), - '{' => conv(on_opening_brace_typed(file, offset)), + '{' => conv(on_opening_bracket_typed(file, offset, '{')), + '(' => conv(on_opening_bracket_typed(file, offset, '(')), _ => None, } } -/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a -/// block, or a part of a `use` item. -fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> { - if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) { +/// Inserts a closing bracket when the user types an opening bracket, wrapping an existing expression in a +/// block, or a part of a `use` item (for `{`). +fn on_opening_bracket_typed( + file: &Parse<SourceFile>, + offset: TextSize, + opening_bracket: char, +) -> Option<TextEdit> { + let (closing_bracket, expected_ast_bracket) = match opening_bracket { + '{' => ('}', SyntaxKind::L_CURLY), + '(' => (')', SyntaxKind::L_PAREN), + _ => return None, + }; + + if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some(opening_bracket)) { return None; } let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?; - if brace_token.kind() != SyntaxKind::L_CURLY { + if brace_token.kind() != expected_ast_bracket { return None; } - // Remove the `{` to get a better parse tree, and reparse. + // Remove the opening bracket to get a better parse tree, and reparse. let range = brace_token.text_range(); - if !stdx::always!(range.len() == TextSize::of('{')) { + if !stdx::always!(range.len() == TextSize::of(opening_bracket)) { return None; } let file = file.reparse(&Indel::delete(range)); - if let Some(edit) = brace_expr(&file.tree(), offset) { + if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) { return Some(edit); } - if let Some(edit) = brace_use_path(&file.tree(), offset) { - return Some(edit); + if closing_bracket == '}' { + if let Some(edit) = brace_use_path(&file.tree(), offset) { + return Some(edit); + } } return None; @@ -142,7 +155,12 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option< )) } - fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> { + fn bracket_expr( + file: &SourceFile, + offset: TextSize, + opening_bracket: char, + closing_bracket: char, + ) -> Option<TextEdit> { let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?; if expr.syntax().text_range().start() != offset { return None; @@ -165,10 +183,10 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option< return None; } - // Insert `}` right after the expression. + // Insert the closing bracket right after the expression. Some(TextEdit::insert( - expr.syntax().text_range().end() + TextSize::of("{"), - "}".to_string(), + expr.syntax().text_range().end() + TextSize::of(opening_bracket), + closing_bracket.to_string(), )) } } @@ -937,6 +955,193 @@ use some::pa$0th::to::Item; } #[test] + fn adds_closing_parenthesis_for_expr() { + 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( + '(', + 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 => (), + } +} + "#, + ); + type_char( + '(', + r#" + fn f() { + let z = Some($03); + } + "#, + r#" + fn f() { + let z = Some((3)); + } + "#, + ); + } + + #[test] + fn parenthesis_noop_in_string_literal() { + // Regression test for #9351 + type_char_noop( + '(', + r##" +fn check_with(ra_fixture: &str, expect: Expect) { + let base = r#" +enum E { T(), R$0, C } +use self::E::X; +const Z: E = E::C; +mod m {} +asdasdasdasdasdasda +sdasdasdasdasdasda +sdasdasdasdasd +"#; + let actual = completion_list(&format!("{}\n{}", base, ra_fixture)); + expect.assert_eq(&actual) +} + "##, + ); + } + + #[test] + fn parenthesis_noop_in_item_position_with_macro() { + type_char_noop('(', r#"$0println!();"#); + type_char_noop( + '(', + r#" +fn main() $0println!("hello"); +}"#, + ); + } + + #[test] + fn parenthesis_noop_in_use_tree() { + type_char_noop( + '(', + r#" +use some::$0Path; + "#, + ); + type_char_noop( + '(', + r#" +use some::{Path, $0Other}; + "#, + ); + type_char_noop( + '(', + r#" +use some::{$0Path, Other}; + "#, + ); + type_char_noop( + '(', + r#" +use some::path::$0to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use some::$0path::to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use $0some::path::to::Item; + "#, + ); + type_char_noop( + '(', + r#" +use some::path::$0to::{Item}; + "#, + ); + type_char_noop( + '(', + r#" +use $0Thing as _; + "#, + ); + + type_char_noop( + '(', + r#" +use some::pa$0th::to::Item; + "#, + ); + } + + #[test] fn adds_closing_angle_bracket_for_generic_args() { type_char( '<', diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 822811e67f5..8c9261ab05e 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -220,7 +220,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi } fn more_trigger_character(config: &Config) -> Vec<String> { - let mut res = vec![".".to_string(), ">".to_string(), "{".to_string()]; + let mut res = vec![".".to_string(), ">".to_string(), "{".to_string(), "(".to_string()]; if config.snippet_cap() { res.push("<".to_string()); } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index 4b81b40436a..425bcce7a22 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -356,7 +356,7 @@ pub(crate) fn handle_on_type_formatting( // This should be a single-file edit let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap(); - stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); + stdx::always!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets"); let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit); Ok(Some(change)) |
