@@ -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,7 @@ 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
36
37
37
struct ExtendedTextEdit {
38
38
edit : TextEdit ,
@@ -92,7 +92,8 @@ fn on_char_typed_inner(
92
92
match char_typed {
93
93
'.' => conv ( on_dot_typed ( & file. tree ( ) , offset) ) ,
94
94
'=' => conv ( on_eq_typed ( & file. tree ( ) , offset) ) ,
95
- '>' => conv ( on_arrow_typed ( & file. tree ( ) , offset) ) ,
95
+ '<' => on_left_angle_typed ( & file. tree ( ) , offset) ,
96
+ '>' => conv ( on_right_angle_typed ( & file. tree ( ) , offset) ) ,
96
97
'{' => conv ( on_opening_brace_typed ( file, offset) ) ,
97
98
_ => unreachable ! ( ) ,
98
99
}
@@ -312,8 +313,40 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
312
313
Some ( TextEdit :: replace ( TextRange :: new ( offset - current_indent_len, offset) , target_indent) )
313
314
}
314
315
316
+ /// Add closing `>` for generic arguments/parameters.
317
+ fn on_left_angle_typed ( file : & SourceFile , offset : TextSize ) -> Option < ExtendedTextEdit > {
318
+ let file_text = file. syntax ( ) . text ( ) ;
319
+ if !stdx:: always!( file_text. char_at( offset) == Some ( '<' ) ) {
320
+ return None ;
321
+ }
322
+ let range = TextRange :: at ( offset, TextSize :: of ( '<' ) ) ;
323
+
324
+ if let Some ( t) = file. syntax ( ) . token_at_offset ( offset) . left_biased ( ) {
325
+ if T ! [ impl ] == t. kind ( ) {
326
+ return Some ( ExtendedTextEdit {
327
+ edit : TextEdit :: replace ( range, "<$0>" . to_string ( ) ) ,
328
+ is_snippet : true ,
329
+ } ) ;
330
+ }
331
+ }
332
+
333
+ if ancestors_at_offset ( file. syntax ( ) , offset)
334
+ . find ( |n| {
335
+ ast:: GenericParamList :: can_cast ( n. kind ( ) ) || ast:: GenericArgList :: can_cast ( n. kind ( ) )
336
+ } )
337
+ . is_some ( )
338
+ {
339
+ return Some ( ExtendedTextEdit {
340
+ edit : TextEdit :: replace ( range, "<$0>" . to_string ( ) ) ,
341
+ is_snippet : true ,
342
+ } ) ;
343
+ }
344
+
345
+ None
346
+ }
347
+
315
348
/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
316
- fn on_arrow_typed ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
349
+ fn on_right_angle_typed ( file : & SourceFile , offset : TextSize ) -> Option < TextEdit > {
317
350
let file_text = file. syntax ( ) . text ( ) ;
318
351
if !stdx:: always!( file_text. char_at( offset) == Some ( '>' ) ) {
319
352
return None ;
@@ -335,6 +368,12 @@ mod tests {
335
368
336
369
use super :: * ;
337
370
371
+ impl ExtendedTextEdit {
372
+ fn apply ( & self , text : & mut String ) {
373
+ self . edit . apply ( text) ;
374
+ }
375
+ }
376
+
338
377
fn do_type_char ( char_typed : char , before : & str ) -> Option < String > {
339
378
let ( offset, mut before) = extract_offset ( before) ;
340
379
let edit = TextEdit :: insert ( offset, char_typed. to_string ( ) ) ;
@@ -879,6 +918,169 @@ use some::pa$0th::to::Item;
879
918
) ;
880
919
}
881
920
921
+ #[ test]
922
+ fn adds_closing_angle_bracket_for_generic_args ( ) {
923
+ type_char (
924
+ '<' ,
925
+ r#"
926
+ fn foo() {
927
+ bar::$0
928
+ }
929
+ "# ,
930
+ r#"
931
+ fn foo() {
932
+ bar::<$0>
933
+ }
934
+ "# ,
935
+ ) ;
936
+
937
+ type_char (
938
+ '<' ,
939
+ r#"
940
+ fn foo(bar: &[u64]) {
941
+ bar.iter().collect::$0();
942
+ }
943
+ "# ,
944
+ r#"
945
+ fn foo(bar: &[u64]) {
946
+ bar.iter().collect::<$0>();
947
+ }
948
+ "# ,
949
+ ) ;
950
+ }
951
+
952
+ #[ test]
953
+ fn adds_closing_angle_bracket_for_generic_params ( ) {
954
+ type_char (
955
+ '<' ,
956
+ r#"
957
+ fn foo$0() {}
958
+ "# ,
959
+ r#"
960
+ fn foo<$0>() {}
961
+ "# ,
962
+ ) ;
963
+ type_char (
964
+ '<' ,
965
+ r#"
966
+ fn foo$0
967
+ "# ,
968
+ r#"
969
+ fn foo<$0>
970
+ "# ,
971
+ ) ;
972
+ type_char (
973
+ '<' ,
974
+ r#"
975
+ struct Foo$0 {}
976
+ "# ,
977
+ r#"
978
+ struct Foo<$0> {}
979
+ "# ,
980
+ ) ;
981
+ type_char (
982
+ '<' ,
983
+ r#"
984
+ struct Foo$0();
985
+ "# ,
986
+ r#"
987
+ struct Foo<$0>();
988
+ "# ,
989
+ ) ;
990
+ type_char (
991
+ '<' ,
992
+ r#"
993
+ struct Foo$0
994
+ "# ,
995
+ r#"
996
+ struct Foo<$0>
997
+ "# ,
998
+ ) ;
999
+ type_char (
1000
+ '<' ,
1001
+ r#"
1002
+ enum Foo$0
1003
+ "# ,
1004
+ r#"
1005
+ enum Foo<$0>
1006
+ "# ,
1007
+ ) ;
1008
+ type_char (
1009
+ '<' ,
1010
+ r#"
1011
+ trait Foo$0
1012
+ "# ,
1013
+ r#"
1014
+ trait Foo<$0>
1015
+ "# ,
1016
+ ) ;
1017
+ type_char (
1018
+ '<' ,
1019
+ r#"
1020
+ type Foo$0 = Bar;
1021
+ "# ,
1022
+ r#"
1023
+ type Foo<$0> = Bar;
1024
+ "# ,
1025
+ ) ;
1026
+ type_char (
1027
+ '<' ,
1028
+ r#"
1029
+ impl$0 Foo {}
1030
+ "# ,
1031
+ r#"
1032
+ impl<$0> Foo {}
1033
+ "# ,
1034
+ ) ;
1035
+ type_char (
1036
+ '<' ,
1037
+ r#"
1038
+ impl<T> Foo$0 {}
1039
+ "# ,
1040
+ r#"
1041
+ impl<T> Foo<$0> {}
1042
+ "# ,
1043
+ ) ;
1044
+ type_char (
1045
+ '<' ,
1046
+ r#"
1047
+ impl Foo$0 {}
1048
+ "# ,
1049
+ r#"
1050
+ impl Foo<$0> {}
1051
+ "# ,
1052
+ ) ;
1053
+ }
1054
+
1055
+ #[ test]
1056
+ fn dont_add_closing_angle_bracket_for_comparison ( ) {
1057
+ type_char_noop (
1058
+ '<' ,
1059
+ r#"
1060
+ fn main() {
1061
+ 42$0
1062
+ }
1063
+ "# ,
1064
+ ) ;
1065
+ type_char_noop (
1066
+ '<' ,
1067
+ r#"
1068
+ fn main() {
1069
+ 42 $0
1070
+ }
1071
+ "# ,
1072
+ ) ;
1073
+ type_char_noop (
1074
+ '<' ,
1075
+ r#"
1076
+ fn main() {
1077
+ let foo = 42;
1078
+ foo $0
1079
+ }
1080
+ "# ,
1081
+ ) ;
1082
+ }
1083
+
882
1084
#[ test]
883
1085
fn regression_629 ( ) {
884
1086
type_char_noop (
0 commit comments