Skip to content

Commit 77b359a

Browse files
committed
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?
2 parents 5906c26 + da78617 commit 77b359a

File tree

3 files changed

+223
-18
lines changed

3 files changed

+223
-18
lines changed

crates/ide/src/typing.rs

Lines changed: 221 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ use crate::SourceChange;
3232
pub(crate) use on_enter::on_enter;
3333

3434
// Don't forget to add new trigger characters to `server_capabilities` in `caps.rs`.
35-
pub(crate) const TRIGGER_CHARS: &str = ".=<>{";
35+
pub(crate) const TRIGGER_CHARS: &str = ".=<>{(";
3636

3737
struct ExtendedTextEdit {
3838
edit: TextEdit,
@@ -94,36 +94,49 @@ fn on_char_typed_inner(
9494
'=' => conv(on_eq_typed(&file.tree(), offset)),
9595
'<' => on_left_angle_typed(&file.tree(), offset),
9696
'>' => conv(on_right_angle_typed(&file.tree(), offset)),
97-
'{' => conv(on_opening_brace_typed(file, offset)),
97+
'{' => conv(on_opening_bracket_typed(file, offset, '{')),
98+
'(' => conv(on_opening_bracket_typed(file, offset, '(')),
9899
_ => None,
99100
}
100101
}
101102

102-
/// Inserts a closing `}` when the user types an opening `{`, wrapping an existing expression in a
103-
/// block, or a part of a `use` item.
104-
fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<TextEdit> {
105-
if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some('{')) {
103+
/// Inserts a closing bracket when the user types an opening bracket, wrapping an existing expression in a
104+
/// block, or a part of a `use` item (for `{`).
105+
fn on_opening_bracket_typed(
106+
file: &Parse<SourceFile>,
107+
offset: TextSize,
108+
opening_bracket: char,
109+
) -> Option<TextEdit> {
110+
let (closing_bracket, expected_ast_bracket) = match opening_bracket {
111+
'{' => ('}', SyntaxKind::L_CURLY),
112+
'(' => (')', SyntaxKind::L_PAREN),
113+
_ => return None,
114+
};
115+
116+
if !stdx::always!(file.tree().syntax().text().char_at(offset) == Some(opening_bracket)) {
106117
return None;
107118
}
108119

109120
let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
110-
if brace_token.kind() != SyntaxKind::L_CURLY {
121+
if brace_token.kind() != expected_ast_bracket {
111122
return None;
112123
}
113124

114-
// Remove the `{` to get a better parse tree, and reparse.
125+
// Remove the opening bracket to get a better parse tree, and reparse.
115126
let range = brace_token.text_range();
116-
if !stdx::always!(range.len() == TextSize::of('{')) {
127+
if !stdx::always!(range.len() == TextSize::of(opening_bracket)) {
117128
return None;
118129
}
119130
let file = file.reparse(&Indel::delete(range));
120131

121-
if let Some(edit) = brace_expr(&file.tree(), offset) {
132+
if let Some(edit) = bracket_expr(&file.tree(), offset, opening_bracket, closing_bracket) {
122133
return Some(edit);
123134
}
124135

125-
if let Some(edit) = brace_use_path(&file.tree(), offset) {
126-
return Some(edit);
136+
if closing_bracket == '}' {
137+
if let Some(edit) = brace_use_path(&file.tree(), offset) {
138+
return Some(edit);
139+
}
127140
}
128141

129142
return None;
@@ -142,7 +155,12 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
142155
))
143156
}
144157

145-
fn brace_expr(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
158+
fn bracket_expr(
159+
file: &SourceFile,
160+
offset: TextSize,
161+
opening_bracket: char,
162+
closing_bracket: char,
163+
) -> Option<TextEdit> {
146164
let mut expr: ast::Expr = find_node_at_offset(file.syntax(), offset)?;
147165
if expr.syntax().text_range().start() != offset {
148166
return None;
@@ -165,10 +183,10 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
165183
return None;
166184
}
167185

168-
// Insert `}` right after the expression.
186+
// Insert the closing bracket right after the expression.
169187
Some(TextEdit::insert(
170-
expr.syntax().text_range().end() + TextSize::of("{"),
171-
"}".to_string(),
188+
expr.syntax().text_range().end() + TextSize::of(opening_bracket),
189+
closing_bracket.to_string(),
172190
))
173191
}
174192
}
@@ -936,6 +954,193 @@ use some::pa$0th::to::Item;
936954
);
937955
}
938956

957+
#[test]
958+
fn adds_closing_parenthesis_for_expr() {
959+
type_char(
960+
'(',
961+
r#"
962+
fn f() { match () { _ => $0() } }
963+
"#,
964+
r#"
965+
fn f() { match () { _ => (()) } }
966+
"#,
967+
);
968+
type_char(
969+
'(',
970+
r#"
971+
fn f() { $0() }
972+
"#,
973+
r#"
974+
fn f() { (()) }
975+
"#,
976+
);
977+
type_char(
978+
'(',
979+
r#"
980+
fn f() { let x = $0(); }
981+
"#,
982+
r#"
983+
fn f() { let x = (()); }
984+
"#,
985+
);
986+
type_char(
987+
'(',
988+
r#"
989+
fn f() { let x = $0a.b(); }
990+
"#,
991+
r#"
992+
fn f() { let x = (a.b()); }
993+
"#,
994+
);
995+
type_char(
996+
'(',
997+
r#"
998+
const S: () = $0();
999+
fn f() {}
1000+
"#,
1001+
r#"
1002+
const S: () = (());
1003+
fn f() {}
1004+
"#,
1005+
);
1006+
type_char(
1007+
'(',
1008+
r#"
1009+
const S: () = $0a.b();
1010+
fn f() {}
1011+
"#,
1012+
r#"
1013+
const S: () = (a.b());
1014+
fn f() {}
1015+
"#,
1016+
);
1017+
type_char(
1018+
'(',
1019+
r#"
1020+
fn f() {
1021+
match x {
1022+
0 => $0(),
1023+
1 => (),
1024+
}
1025+
}
1026+
"#,
1027+
r#"
1028+
fn f() {
1029+
match x {
1030+
0 => (()),
1031+
1 => (),
1032+
}
1033+
}
1034+
"#,
1035+
);
1036+
type_char(
1037+
'(',
1038+
r#"
1039+
fn f() {
1040+
let z = Some($03);
1041+
}
1042+
"#,
1043+
r#"
1044+
fn f() {
1045+
let z = Some((3));
1046+
}
1047+
"#,
1048+
);
1049+
}
1050+
1051+
#[test]
1052+
fn parenthesis_noop_in_string_literal() {
1053+
// Regression test for #9351
1054+
type_char_noop(
1055+
'(',
1056+
r##"
1057+
fn check_with(ra_fixture: &str, expect: Expect) {
1058+
let base = r#"
1059+
enum E { T(), R$0, C }
1060+
use self::E::X;
1061+
const Z: E = E::C;
1062+
mod m {}
1063+
asdasdasdasdasdasda
1064+
sdasdasdasdasdasda
1065+
sdasdasdasdasd
1066+
"#;
1067+
let actual = completion_list(&format!("{}\n{}", base, ra_fixture));
1068+
expect.assert_eq(&actual)
1069+
}
1070+
"##,
1071+
);
1072+
}
1073+
1074+
#[test]
1075+
fn parenthesis_noop_in_item_position_with_macro() {
1076+
type_char_noop('(', r#"$0println!();"#);
1077+
type_char_noop(
1078+
'(',
1079+
r#"
1080+
fn main() $0println!("hello");
1081+
}"#,
1082+
);
1083+
}
1084+
1085+
#[test]
1086+
fn parenthesis_noop_in_use_tree() {
1087+
type_char_noop(
1088+
'(',
1089+
r#"
1090+
use some::$0Path;
1091+
"#,
1092+
);
1093+
type_char_noop(
1094+
'(',
1095+
r#"
1096+
use some::{Path, $0Other};
1097+
"#,
1098+
);
1099+
type_char_noop(
1100+
'(',
1101+
r#"
1102+
use some::{$0Path, Other};
1103+
"#,
1104+
);
1105+
type_char_noop(
1106+
'(',
1107+
r#"
1108+
use some::path::$0to::Item;
1109+
"#,
1110+
);
1111+
type_char_noop(
1112+
'(',
1113+
r#"
1114+
use some::$0path::to::Item;
1115+
"#,
1116+
);
1117+
type_char_noop(
1118+
'(',
1119+
r#"
1120+
use $0some::path::to::Item;
1121+
"#,
1122+
);
1123+
type_char_noop(
1124+
'(',
1125+
r#"
1126+
use some::path::$0to::{Item};
1127+
"#,
1128+
);
1129+
type_char_noop(
1130+
'(',
1131+
r#"
1132+
use $0Thing as _;
1133+
"#,
1134+
);
1135+
1136+
type_char_noop(
1137+
'(',
1138+
r#"
1139+
use some::pa$0th::to::Item;
1140+
"#,
1141+
);
1142+
}
1143+
9391144
#[test]
9401145
fn adds_closing_angle_bracket_for_generic_args() {
9411146
type_char(

crates/rust-analyzer/src/caps.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ fn code_action_capabilities(client_caps: &ClientCapabilities) -> CodeActionProvi
220220
}
221221

222222
fn more_trigger_character(config: &Config) -> Vec<String> {
223-
let mut res = vec![".".to_string(), ">".to_string(), "{".to_string()];
223+
let mut res = vec![".".to_string(), ">".to_string(), "{".to_string(), "(".to_string()];
224224
if config.snippet_cap() {
225225
res.push("<".to_string());
226226
}

crates/rust-analyzer/src/handlers/request.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ pub(crate) fn handle_on_type_formatting(
356356

357357
// This should be a single-file edit
358358
let (_, (text_edit, snippet_edit)) = edit.source_file_edits.into_iter().next().unwrap();
359-
stdx::never!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
359+
stdx::always!(snippet_edit.is_none(), "on type formatting shouldn't use structured snippets");
360360

361361
let change = to_proto::snippet_text_edit_vec(&line_index, edit.is_snippet, text_edit);
362362
Ok(Some(change))

0 commit comments

Comments
 (0)