Skip to content

Commit ce6e181

Browse files
On type format '(', by adding closing ')' automatically
1 parent 029baaa commit ce6e181

File tree

2 files changed

+222
-17
lines changed

2 files changed

+222
-17
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,
@@ -91,7 +91,8 @@ fn on_char_typed_inner(
9191
'=' => conv(on_eq_typed(&file.tree(), offset)),
9292
'<' => on_left_angle_typed(&file.tree(), offset),
9393
'>' => conv(on_right_angle_typed(&file.tree(), offset)),
94-
'{' => conv(on_opening_brace_typed(file, offset)),
94+
'{' => conv(on_opening_bracket_typed(file, offset, '{')),
95+
'(' => conv(on_opening_bracket_typed(file, offset, '(')),
9596
_ => return None,
9697
};
9798

@@ -100,31 +101,43 @@ fn on_char_typed_inner(
100101
}
101102
}
102103

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

110121
let brace_token = file.tree().syntax().token_at_offset(offset).right_biased()?;
111-
if brace_token.kind() != SyntaxKind::L_CURLY {
122+
if brace_token.kind() != expected_ast_bracket {
112123
return None;
113124
}
114125

115-
// Remove the `{` to get a better parse tree, and reparse.
126+
// Remove the opening bracket to get a better parse tree, and reparse.
116127
let range = brace_token.text_range();
117-
if !stdx::always!(range.len() == TextSize::of('{')) {
128+
if !stdx::always!(range.len() == TextSize::of(opening_bracket)) {
118129
return None;
119130
}
120131
let file = file.reparse(&Indel::delete(range));
121132

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

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

130143
return None;
@@ -143,7 +156,12 @@ fn on_opening_brace_typed(file: &Parse<SourceFile>, offset: TextSize) -> Option<
143156
))
144157
}
145158

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

169-
// Insert `}` right after the expression.
187+
// Insert the closing bracket right after the expression.
170188
Some(TextEdit::insert(
171-
expr.syntax().text_range().end() + TextSize::of("{"),
172-
"}".to_string(),
189+
expr.syntax().text_range().end() + TextSize::of(opening_bracket),
190+
closing_bracket.to_string(),
173191
))
174192
}
175193
}
@@ -937,6 +955,193 @@ use some::pa$0th::to::Item;
937955
);
938956
}
939957

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

crates/rust-analyzer/src/caps.rs

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

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

0 commit comments

Comments
 (0)