Skip to content

Commit 90236dd

Browse files
committed
Auto merge of rust-lang#11830 - nemethf:on-type-formatting, r=nemethf
On typing handler for angle brackets(<) with snippets I implemented my idea in rust-lang#11398 in "cargo cult programming"-style without actually know what I'm doing, so feedback is welcome. The PR is split into two commits to ease the review. I used `@unexge's` original prototype, which forms the basis of the PR.
2 parents 3de03d4 + f7c963c commit 90236dd

File tree

7 files changed

+354
-21
lines changed

7 files changed

+354
-21
lines changed

crates/ide/src/typing.rs

+319-11
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ use ide_db::{
2020
RootDatabase,
2121
};
2222
use syntax::{
23-
algo::find_node_at_offset,
23+
algo::{ancestors_at_offset, find_node_at_offset},
2424
ast::{self, edit::IndentLevel, AstToken},
25-
AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize,
25+
AstNode, Parse, SourceFile, SyntaxKind, TextRange, TextSize, T,
2626
};
2727

2828
use text_edit::{Indel, TextEdit};
@@ -32,7 +32,12 @@ 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 = ".=<>{";
36+
37+
struct ExtendedTextEdit {
38+
edit: TextEdit,
39+
is_snippet: bool,
40+
}
3641

3742
// Feature: On Typing Assists
3843
//
@@ -68,23 +73,30 @@ pub(crate) fn on_char_typed(
6873
return None;
6974
}
7075
let edit = on_char_typed_inner(file, position.offset, char_typed)?;
71-
Some(SourceChange::from_text_edit(position.file_id, edit))
76+
let mut sc = SourceChange::from_text_edit(position.file_id, edit.edit);
77+
sc.is_snippet = edit.is_snippet;
78+
Some(sc)
7279
}
7380

7481
fn on_char_typed_inner(
7582
file: &Parse<SourceFile>,
7683
offset: TextSize,
7784
char_typed: char,
78-
) -> Option<TextEdit> {
85+
) -> Option<ExtendedTextEdit> {
7986
if !stdx::always!(TRIGGER_CHARS.contains(char_typed)) {
8087
return None;
8188
}
82-
match char_typed {
83-
'.' => on_dot_typed(&file.tree(), offset),
84-
'=' => on_eq_typed(&file.tree(), offset),
85-
'>' => on_arrow_typed(&file.tree(), offset),
86-
'{' => on_opening_brace_typed(file, offset),
89+
return match char_typed {
90+
'.' => conv(on_dot_typed(&file.tree(), offset)),
91+
'=' => conv(on_eq_typed(&file.tree(), offset)),
92+
'<' => on_left_angle_typed(&file.tree(), offset),
93+
'>' => conv(on_right_angle_typed(&file.tree(), offset)),
94+
'{' => conv(on_opening_brace_typed(file, offset)),
8795
_ => unreachable!(),
96+
};
97+
98+
fn conv(text_edit: Option<TextEdit>) -> Option<ExtendedTextEdit> {
99+
Some(ExtendedTextEdit { edit: text_edit?, is_snippet: false })
88100
}
89101
}
90102

@@ -302,8 +314,49 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
302314
Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
303315
}
304316

317+
/// Add closing `>` for generic arguments/parameters.
318+
fn on_left_angle_typed(file: &SourceFile, offset: TextSize) -> Option<ExtendedTextEdit> {
319+
let file_text = file.syntax().text();
320+
if !stdx::always!(file_text.char_at(offset) == Some('<')) {
321+
return None;
322+
}
323+
324+
// Find the next non-whitespace char in the line.
325+
let mut next_offset = offset + TextSize::of('<');
326+
while file_text.char_at(next_offset) == Some(' ') {
327+
next_offset += TextSize::of(' ')
328+
}
329+
if file_text.char_at(next_offset) == Some('>') {
330+
return None;
331+
}
332+
333+
let range = TextRange::at(offset, TextSize::of('<'));
334+
if let Some(t) = file.syntax().token_at_offset(offset).left_biased() {
335+
if T![impl] == t.kind() {
336+
return Some(ExtendedTextEdit {
337+
edit: TextEdit::replace(range, "<$0>".to_string()),
338+
is_snippet: true,
339+
});
340+
}
341+
}
342+
343+
if ancestors_at_offset(file.syntax(), offset)
344+
.find(|n| {
345+
ast::GenericParamList::can_cast(n.kind()) || ast::GenericArgList::can_cast(n.kind())
346+
})
347+
.is_some()
348+
{
349+
return Some(ExtendedTextEdit {
350+
edit: TextEdit::replace(range, "<$0>".to_string()),
351+
is_snippet: true,
352+
});
353+
}
354+
355+
None
356+
}
357+
305358
/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
306-
fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
359+
fn on_right_angle_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
307360
let file_text = file.syntax().text();
308361
if !stdx::always!(file_text.char_at(offset) == Some('>')) {
309362
return None;
@@ -325,6 +378,12 @@ mod tests {
325378

326379
use super::*;
327380

381+
impl ExtendedTextEdit {
382+
fn apply(&self, text: &mut String) {
383+
self.edit.apply(text);
384+
}
385+
}
386+
328387
fn do_type_char(char_typed: char, before: &str) -> Option<String> {
329388
let (offset, mut before) = extract_offset(before);
330389
let edit = TextEdit::insert(offset, char_typed.to_string());
@@ -869,6 +928,255 @@ use some::pa$0th::to::Item;
869928
);
870929
}
871930

931+
#[test]
932+
fn adds_closing_angle_bracket_for_generic_args() {
933+
type_char(
934+
'<',
935+
r#"
936+
fn foo() {
937+
bar::$0
938+
}
939+
"#,
940+
r#"
941+
fn foo() {
942+
bar::<$0>
943+
}
944+
"#,
945+
);
946+
947+
type_char(
948+
'<',
949+
r#"
950+
fn foo(bar: &[u64]) {
951+
bar.iter().collect::$0();
952+
}
953+
"#,
954+
r#"
955+
fn foo(bar: &[u64]) {
956+
bar.iter().collect::<$0>();
957+
}
958+
"#,
959+
);
960+
}
961+
962+
#[test]
963+
fn adds_closing_angle_bracket_for_generic_params() {
964+
type_char(
965+
'<',
966+
r#"
967+
fn foo$0() {}
968+
"#,
969+
r#"
970+
fn foo<$0>() {}
971+
"#,
972+
);
973+
type_char(
974+
'<',
975+
r#"
976+
fn foo$0
977+
"#,
978+
r#"
979+
fn foo<$0>
980+
"#,
981+
);
982+
type_char(
983+
'<',
984+
r#"
985+
struct Foo$0 {}
986+
"#,
987+
r#"
988+
struct Foo<$0> {}
989+
"#,
990+
);
991+
type_char(
992+
'<',
993+
r#"
994+
struct Foo$0();
995+
"#,
996+
r#"
997+
struct Foo<$0>();
998+
"#,
999+
);
1000+
type_char(
1001+
'<',
1002+
r#"
1003+
struct Foo$0
1004+
"#,
1005+
r#"
1006+
struct Foo<$0>
1007+
"#,
1008+
);
1009+
type_char(
1010+
'<',
1011+
r#"
1012+
enum Foo$0
1013+
"#,
1014+
r#"
1015+
enum Foo<$0>
1016+
"#,
1017+
);
1018+
type_char(
1019+
'<',
1020+
r#"
1021+
trait Foo$0
1022+
"#,
1023+
r#"
1024+
trait Foo<$0>
1025+
"#,
1026+
);
1027+
type_char(
1028+
'<',
1029+
r#"
1030+
type Foo$0 = Bar;
1031+
"#,
1032+
r#"
1033+
type Foo<$0> = Bar;
1034+
"#,
1035+
);
1036+
type_char(
1037+
'<',
1038+
r#"
1039+
impl$0 Foo {}
1040+
"#,
1041+
r#"
1042+
impl<$0> Foo {}
1043+
"#,
1044+
);
1045+
type_char(
1046+
'<',
1047+
r#"
1048+
impl<T> Foo$0 {}
1049+
"#,
1050+
r#"
1051+
impl<T> Foo<$0> {}
1052+
"#,
1053+
);
1054+
type_char(
1055+
'<',
1056+
r#"
1057+
impl Foo$0 {}
1058+
"#,
1059+
r#"
1060+
impl Foo<$0> {}
1061+
"#,
1062+
);
1063+
}
1064+
1065+
#[test]
1066+
fn dont_add_closing_angle_bracket_for_comparison() {
1067+
type_char_noop(
1068+
'<',
1069+
r#"
1070+
fn main() {
1071+
42$0
1072+
}
1073+
"#,
1074+
);
1075+
type_char_noop(
1076+
'<',
1077+
r#"
1078+
fn main() {
1079+
42 $0
1080+
}
1081+
"#,
1082+
);
1083+
type_char_noop(
1084+
'<',
1085+
r#"
1086+
fn main() {
1087+
let foo = 42;
1088+
foo $0
1089+
}
1090+
"#,
1091+
);
1092+
}
1093+
1094+
#[test]
1095+
fn dont_add_closing_angle_bracket_if_it_is_already_there() {
1096+
type_char_noop(
1097+
'<',
1098+
r#"
1099+
fn foo() {
1100+
bar::$0>
1101+
}
1102+
"#,
1103+
);
1104+
type_char_noop(
1105+
'<',
1106+
r#"
1107+
fn foo(bar: &[u64]) {
1108+
bar.iter().collect::$0 >();
1109+
}
1110+
"#,
1111+
);
1112+
type_char_noop(
1113+
'<',
1114+
r#"
1115+
fn foo$0>() {}
1116+
"#,
1117+
);
1118+
type_char_noop(
1119+
'<',
1120+
r#"
1121+
fn foo$0>
1122+
"#,
1123+
);
1124+
type_char_noop(
1125+
'<',
1126+
r#"
1127+
struct Foo$0> {}
1128+
"#,
1129+
);
1130+
type_char_noop(
1131+
'<',
1132+
r#"
1133+
struct Foo$0>();
1134+
"#,
1135+
);
1136+
type_char_noop(
1137+
'<',
1138+
r#"
1139+
struct Foo$0>
1140+
"#,
1141+
);
1142+
type_char_noop(
1143+
'<',
1144+
r#"
1145+
enum Foo$0>
1146+
"#,
1147+
);
1148+
type_char_noop(
1149+
'<',
1150+
r#"
1151+
trait Foo$0>
1152+
"#,
1153+
);
1154+
type_char_noop(
1155+
'<',
1156+
r#"
1157+
type Foo$0> = Bar;
1158+
"#,
1159+
);
1160+
type_char_noop(
1161+
'<',
1162+
r#"
1163+
impl$0> Foo {}
1164+
"#,
1165+
);
1166+
type_char_noop(
1167+
'<',
1168+
r#"
1169+
impl<T> Foo$0> {}
1170+
"#,
1171+
);
1172+
type_char_noop(
1173+
'<',
1174+
r#"
1175+
impl Foo$0> {}
1176+
"#,
1177+
);
1178+
}
1179+
8721180
#[test]
8731181
fn regression_629() {
8741182
type_char_noop(

0 commit comments

Comments
 (0)