1
- use crate :: { AssistContext , Assists } ;
1
+ use crate :: { utils , AssistContext , Assists } ;
2
2
use hir:: DescendPreference ;
3
3
use ide_db:: {
4
4
assists:: { AssistId , AssistKind } ,
@@ -8,8 +8,12 @@ use ide_db::{
8
8
} ,
9
9
} ;
10
10
use itertools:: Itertools ;
11
- use stdx:: format_to;
12
- use syntax:: { ast, AstNode , AstToken , NodeOrToken , SyntaxKind :: COMMA , TextRange } ;
11
+ use syntax:: {
12
+ ast:: { self , make} ,
13
+ ted, AstNode , AstToken , NodeOrToken ,
14
+ SyntaxKind :: WHITESPACE ,
15
+ T ,
16
+ } ;
13
17
14
18
// Assist: extract_expressions_from_format_string
15
19
//
@@ -34,6 +38,7 @@ pub(crate) fn extract_expressions_from_format_string(
34
38
) -> Option < ( ) > {
35
39
let fmt_string = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
36
40
let tt = fmt_string. syntax ( ) . parent ( ) . and_then ( ast:: TokenTree :: cast) ?;
41
+ let tt_delimiter = tt. left_delimiter_token ( ) ?. kind ( ) ;
37
42
38
43
let expanded_t = ast:: String :: cast (
39
44
ctx. sema
@@ -61,81 +66,95 @@ pub(crate) fn extract_expressions_from_format_string(
61
66
"Extract format expressions" ,
62
67
tt. syntax ( ) . text_range ( ) ,
63
68
|edit| {
64
- let fmt_range = fmt_string. syntax ( ) . text_range ( ) ;
65
-
66
- // Replace old format string with new format string whose arguments have been extracted
67
- edit. replace ( fmt_range, new_fmt) ;
68
-
69
- // Insert cursor at end of format string
70
- edit. insert ( fmt_range. end ( ) , "$0" ) ;
69
+ let tt = edit. make_mut ( tt) ;
71
70
72
71
// Extract existing arguments in macro
73
- let tokens =
74
- tt. token_trees_and_tokens ( ) . collect_vec ( ) ;
75
-
76
- let mut existing_args: Vec < String > = vec ! [ ] ;
72
+ let tokens = tt. token_trees_and_tokens ( ) . collect_vec ( ) ;
77
73
78
- let mut current_arg = String :: new ( ) ;
79
- if let [ _opening_bracket, NodeOrToken :: Token ( format_string) , _args_start_comma, tokens @ .., NodeOrToken :: Token ( end_bracket) ] =
74
+ let existing_args = if let [ _opening_bracket, NodeOrToken :: Token ( _format_string) , _args_start_comma, tokens @ .., NodeOrToken :: Token ( _end_bracket) ] =
80
75
tokens. as_slice ( )
81
76
{
82
- for t in tokens {
83
- match t {
84
- NodeOrToken :: Node ( n) => {
85
- format_to ! ( current_arg, "{n}" ) ;
86
- } ,
87
- NodeOrToken :: Token ( t) if t. kind ( ) == COMMA => {
88
- existing_args. push ( current_arg. trim ( ) . into ( ) ) ;
89
- current_arg. clear ( ) ;
90
- } ,
91
- NodeOrToken :: Token ( t) => {
92
- current_arg. push_str ( t. text ( ) ) ;
93
- } ,
94
- }
95
- }
96
- existing_args. push ( current_arg. trim ( ) . into ( ) ) ;
77
+ let args = tokens. split ( |it| matches ! ( it, NodeOrToken :: Token ( t) if t. kind( ) == T ![ , ] ) ) . map ( |arg| {
78
+ // Strip off leading and trailing whitespace tokens
79
+ let arg = match arg. split_first ( ) {
80
+ Some ( ( NodeOrToken :: Token ( t) , rest) ) if t. kind ( ) == WHITESPACE => rest,
81
+ _ => arg,
82
+ } ;
83
+ let arg = match arg. split_last ( ) {
84
+ Some ( ( NodeOrToken :: Token ( t) , rest) ) if t. kind ( ) == WHITESPACE => rest,
85
+ _ => arg,
86
+ } ;
87
+ arg
88
+ } ) ;
97
89
98
- // delete everything after the format string till end bracket
99
- // we're going to insert the new arguments later
100
- edit. delete ( TextRange :: new (
101
- format_string. text_range ( ) . end ( ) ,
102
- end_bracket. text_range ( ) . start ( ) ,
103
- ) ) ;
104
- }
90
+ args. collect ( )
91
+ } else {
92
+ vec ! [ ]
93
+ } ;
105
94
106
95
// Start building the new args
107
96
let mut existing_args = existing_args. into_iter ( ) ;
108
- let mut args = String :: new ( ) ;
97
+ let mut new_tt_bits = vec ! [ NodeOrToken :: Token ( make:: tokens:: literal( & new_fmt) ) ] ;
98
+ let mut placeholder_indexes = vec ! [ ] ;
109
99
110
- let mut placeholder_idx = 1 ;
100
+ for arg in extracted_args {
101
+ if matches ! ( arg, Arg :: Expr ( _) | Arg :: Placeholder ) {
102
+ // insert ", " before each arg
103
+ new_tt_bits. extend_from_slice ( & [
104
+ NodeOrToken :: Token ( make:: token ( T ! [ , ] ) ) ,
105
+ NodeOrToken :: Token ( make:: tokens:: single_space ( ) ) ,
106
+ ] ) ;
107
+ }
111
108
112
- for extracted_args in extracted_args {
113
- match extracted_args {
114
- Arg :: Expr ( s) => {
115
- args. push_str ( ", " ) ;
109
+ match arg {
110
+ Arg :: Expr ( s) => {
116
111
// insert arg
117
- args. push_str ( & s) ;
112
+ // FIXME: use the crate's edition for parsing
113
+ let expr = ast:: Expr :: parse ( & s, syntax:: Edition :: CURRENT ) . syntax_node ( ) ;
114
+ let mut expr_tt = utils:: tt_from_syntax ( expr) ;
115
+ new_tt_bits. append ( & mut expr_tt) ;
118
116
}
119
117
Arg :: Placeholder => {
120
- args. push_str ( ", " ) ;
121
118
// try matching with existing argument
122
119
match existing_args. next ( ) {
123
- Some ( ea ) => {
124
- args . push_str ( & ea ) ;
120
+ Some ( arg ) => {
121
+ new_tt_bits . extend_from_slice ( arg ) ;
125
122
}
126
123
None => {
127
- // insert placeholder
128
- args. push_str ( & format ! ( "${placeholder_idx}" ) ) ;
129
- placeholder_idx += 1 ;
124
+ placeholder_indexes. push ( new_tt_bits. len ( ) ) ;
125
+ new_tt_bits. push ( NodeOrToken :: Token ( make:: token ( T ! [ _] ) ) ) ;
130
126
}
131
127
}
132
128
}
133
129
Arg :: Ident ( _s) => ( ) ,
134
130
}
135
131
}
136
132
133
+
137
134
// Insert new args
138
- edit. insert ( fmt_range. end ( ) , args) ;
135
+ let new_tt = make:: token_tree ( tt_delimiter, new_tt_bits) . clone_for_update ( ) ;
136
+ ted:: replace ( tt. syntax ( ) , new_tt. syntax ( ) ) ;
137
+
138
+ if let Some ( cap) = ctx. config . snippet_cap {
139
+ // Add placeholder snippets over placeholder args
140
+ for pos in placeholder_indexes {
141
+ // Skip the opening delimiter
142
+ let Some ( NodeOrToken :: Token ( placeholder) ) =
143
+ new_tt. token_trees_and_tokens ( ) . skip ( 1 ) . nth ( pos)
144
+ else {
145
+ continue ;
146
+ } ;
147
+
148
+ if stdx:: always!( placeholder. kind( ) == T ![ _] ) {
149
+ edit. add_placeholder_snippet_token ( cap, placeholder) ;
150
+ }
151
+ }
152
+
153
+ // Add the final tabstop after the format literal
154
+ if let Some ( NodeOrToken :: Token ( literal) ) = new_tt. token_trees_and_tokens ( ) . nth ( 1 ) {
155
+ edit. add_tabstop_after_token ( cap, literal) ;
156
+ }
157
+ }
139
158
} ,
140
159
) ;
141
160
@@ -145,7 +164,7 @@ pub(crate) fn extract_expressions_from_format_string(
145
164
#[ cfg( test) ]
146
165
mod tests {
147
166
use super :: * ;
148
- use crate :: tests:: check_assist;
167
+ use crate :: tests:: { check_assist, check_assist_no_snippet_cap } ;
149
168
150
169
#[ test]
151
170
fn multiple_middle_arg ( ) {
@@ -195,7 +214,7 @@ fn main() {
195
214
"# ,
196
215
r#"
197
216
fn main() {
198
- print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1 );
217
+ print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, ${1:_} );
199
218
}
200
219
"# ,
201
220
) ;
@@ -292,4 +311,22 @@ fn main() {
292
311
"# ,
293
312
) ;
294
313
}
314
+
315
+ #[ test]
316
+ fn without_snippets ( ) {
317
+ check_assist_no_snippet_cap (
318
+ extract_expressions_from_format_string,
319
+ r#"
320
+ //- minicore: fmt
321
+ fn main() {
322
+ print!("{} {x + 1:b} {} {}$0", y + 2, 2);
323
+ }
324
+ "# ,
325
+ r#"
326
+ fn main() {
327
+ print!("{} {:b} {} {}", y + 2, x + 1, 2, _);
328
+ }
329
+ "# ,
330
+ ) ;
331
+ }
295
332
}
0 commit comments