1- use crate :: { AssistContext , Assists } ;
1+ use crate :: { utils , AssistContext , Assists } ;
22use hir:: DescendPreference ;
33use ide_db:: {
44 assists:: { AssistId , AssistKind } ,
@@ -8,8 +8,12 @@ use ide_db::{
88 } ,
99} ;
1010use 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+ } ;
1317
1418// Assist: extract_expressions_from_format_string
1519//
@@ -34,6 +38,7 @@ pub(crate) fn extract_expressions_from_format_string(
3438) -> Option < ( ) > {
3539 let fmt_string = ctx. find_token_at_offset :: < ast:: String > ( ) ?;
3640 let tt = fmt_string. syntax ( ) . parent ( ) . and_then ( ast:: TokenTree :: cast) ?;
41+ let tt_delimiter = tt. left_delimiter_token ( ) ?. kind ( ) ;
3742
3843 let expanded_t = ast:: String :: cast (
3944 ctx. sema
@@ -61,81 +66,95 @@ pub(crate) fn extract_expressions_from_format_string(
6166 "Extract format expressions" ,
6267 tt. syntax ( ) . text_range ( ) ,
6368 |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) ;
7170
7271 // 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 ( ) ;
7773
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) ] =
8075 tokens. as_slice ( )
8176 {
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+ } ) ;
9789
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+ } ;
10594
10695 // Start building the new args
10796 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 ! [ ] ;
10999
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+ }
111108
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) => {
116111 // 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) ;
118116 }
119117 Arg :: Placeholder => {
120- args. push_str ( ", " ) ;
121118 // try matching with existing argument
122119 match existing_args. next ( ) {
123- Some ( ea ) => {
124- args . push_str ( & ea ) ;
120+ Some ( arg ) => {
121+ new_tt_bits . extend_from_slice ( arg ) ;
125122 }
126123 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 ! [ _] ) ) ) ;
130126 }
131127 }
132128 }
133129 Arg :: Ident ( _s) => ( ) ,
134130 }
135131 }
136132
133+
137134 // 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+ }
139158 } ,
140159 ) ;
141160
@@ -145,7 +164,7 @@ pub(crate) fn extract_expressions_from_format_string(
145164#[ cfg( test) ]
146165mod tests {
147166 use super :: * ;
148- use crate :: tests:: check_assist;
167+ use crate :: tests:: { check_assist, check_assist_no_snippet_cap } ;
149168
150169 #[ test]
151170 fn multiple_middle_arg ( ) {
@@ -195,7 +214,7 @@ fn main() {
195214"# ,
196215 r#"
197216fn main() {
198- print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1 );
217+ print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, ${1:_} );
199218}
200219"# ,
201220 ) ;
@@ -292,4 +311,22 @@ fn main() {
292311 "# ,
293312 ) ;
294313 }
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+ }
295332}
0 commit comments