@@ -20,9 +20,9 @@ use ide_db::{
20
20
RootDatabase ,
21
21
} ;
22
22
use syntax:: {
23
- algo:: find_node_at_offset,
23
+ algo:: { ancestors_at_offset , find_node_at_offset} ,
24
24
ast:: { self , edit:: IndentLevel , AstToken } ,
25
- AstNode , Parse , SourceFile , SyntaxKind , TextRange , TextSize ,
25
+ AstNode , Parse , SourceFile , SyntaxKind , TextRange , TextSize , T ,
26
26
} ;
27
27
28
28
use text_edit:: { Indel , TextEdit } ;
@@ -32,7 +32,12 @@ use crate::SourceChange;
32
32
pub ( crate ) use on_enter:: on_enter;
33
33
34
34
// 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
+ }
36
41
37
42
// Feature: On Typing Assists
38
43
//
@@ -68,23 +73,30 @@ pub(crate) fn on_char_typed(
68
73
return None ;
69
74
}
70
75
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)
72
79
}
73
80
74
81
fn on_char_typed_inner (
75
82
file : & Parse < SourceFile > ,
76
83
offset : TextSize ,
77
84
char_typed : char ,
78
- ) -> Option < TextEdit > {
85
+ ) -> Option < ExtendedTextEdit > {
79
86
if !stdx:: always!( TRIGGER_CHARS . contains( char_typed) ) {
80
87
return None ;
81
88
}
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) ) ,
87
95
_ => unreachable ! ( ) ,
96
+ } ;
97
+
98
+ fn conv ( text_edit : Option < TextEdit > ) -> Option < ExtendedTextEdit > {
99
+ Some ( ExtendedTextEdit { edit : text_edit?, is_snippet : false } )
88
100
}
89
101
}
90
102
@@ -302,8 +314,49 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
302
314
Some ( TextEdit :: replace ( TextRange :: new ( offset - current_indent_len, offset) , target_indent) )
303
315
}
304
316
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
+
305
358
/// 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 > {
307
360
let file_text = file. syntax ( ) . text ( ) ;
308
361
if !stdx:: always!( file_text. char_at( offset) == Some ( '>' ) ) {
309
362
return None ;
@@ -325,6 +378,12 @@ mod tests {
325
378
326
379
use super :: * ;
327
380
381
+ impl ExtendedTextEdit {
382
+ fn apply ( & self , text : & mut String ) {
383
+ self . edit . apply ( text) ;
384
+ }
385
+ }
386
+
328
387
fn do_type_char ( char_typed : char , before : & str ) -> Option < String > {
329
388
let ( offset, mut before) = extract_offset ( before) ;
330
389
let edit = TextEdit :: insert ( offset, char_typed. to_string ( ) ) ;
@@ -869,6 +928,255 @@ use some::pa$0th::to::Item;
869
928
) ;
870
929
}
871
930
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
+
872
1180
#[ test]
873
1181
fn regression_629 ( ) {
874
1182
type_char_noop (
0 commit comments