@@ -20,9 +20,9 @@ use ide_db::{
2020 RootDatabase ,
2121} ;
2222use 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
2828use text_edit:: { Indel , TextEdit } ;
@@ -32,7 +32,12 @@ use crate::SourceChange;
3232pub ( 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
7481fn 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