1 use crate::{AssistContext, Assists};
3 assists::{AssistId, AssistKind},
5 format_string::is_format_string,
6 format_string_exprs::{parse_format_exprs, Arg},
9 use itertools::Itertools;
11 use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
13 // Assist: move_format_string_arg
15 // Move an expression out of a format string.
18 // macro_rules! format_args {
19 // ($lit:literal $(tt:tt)*) => { 0 },
21 // macro_rules! print {
22 // ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
26 // print!("{x + 1}$0");
31 // macro_rules! format_args {
32 // ($lit:literal $(tt:tt)*) => { 0 },
34 // macro_rules! print {
35 // ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
39 // print!("{}"$0, x + 1);
43 pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
44 let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
45 let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
47 let expanded_t = ast::String::cast(
48 ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
50 if !is_format_string(&expanded_t) {
54 let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
55 if extracted_args.is_empty() {
61 "move_format_string_arg",
62 // if there aren't any expressions, then make the assist a RefactorExtract
63 if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
64 AssistKind::RefactorExtract
69 "Extract format args",
70 tt.syntax().text_range(),
72 let fmt_range = fmt_string.syntax().text_range();
74 // Replace old format string with new format string whose arguments have been extracted
75 edit.replace(fmt_range, new_fmt);
77 // Insert cursor at end of format string
78 edit.insert(fmt_range.end(), "$0");
80 // Extract existing arguments in macro
82 tt.token_trees_and_tokens().collect_vec();
84 let mut existing_args: Vec<String> = vec![];
86 let mut current_arg = String::new();
87 if let [_opening_bracket, NodeOrToken::Token(format_string), _args_start_comma, tokens @ .., NodeOrToken::Token(end_bracket)] =
92 NodeOrToken::Node(n) => {
93 format_to!(current_arg, "{n}");
95 NodeOrToken::Token(t) if t.kind() == COMMA=> {
96 existing_args.push(current_arg.trim().into());
99 NodeOrToken::Token(t) => {
100 current_arg.push_str(t.text());
104 existing_args.push(current_arg.trim().into());
106 // delete everything after the format string till end bracket
107 // we're going to insert the new arguments later
108 edit.delete(TextRange::new(
109 format_string.text_range().end(),
110 end_bracket.text_range().start(),
114 // Start building the new args
115 let mut existing_args = existing_args.into_iter();
116 let mut args = String::new();
118 let mut placeholder_idx = 1;
120 for extracted_args in extracted_args {
121 // remove expr from format string
124 match extracted_args {
125 Arg::Ident(s) | Arg::Expr(s) => {
129 Arg::Placeholder => {
130 // try matching with existing argument
131 match existing_args.next() {
136 // insert placeholder
137 args.push_str(&format!("${placeholder_idx}"));
138 placeholder_idx += 1;
146 edit.insert(fmt_range.end(), args);
156 use crate::tests::check_assist;
158 const MACRO_DECL: &'static str = r#"
159 macro_rules! format_args {
160 ($lit:literal $(tt:tt)*) => { 0 },
163 ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
167 fn add_macro_decl(s: &'static str) -> String {
168 MACRO_DECL.to_string() + s
172 fn multiple_middle_arg() {
174 move_format_string_arg,
178 print!("{} {x + 1:b} {}$0", y + 2, 2);
185 print!("{} {:b} {}"$0, y + 2, x + 1, 2);
195 move_format_string_arg,
199 print!("{obj.value:b}$0",);
206 print!("{:b}"$0, obj.value);
214 fn multiple_middle_placeholders_arg() {
216 move_format_string_arg,
220 print!("{} {x + 1:b} {} {}$0", y + 2, 2);
227 print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
235 fn multiple_trailing_args() {
237 move_format_string_arg,
241 print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
248 print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
256 fn improper_commas() {
258 move_format_string_arg,
262 print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
269 print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
279 move_format_string_arg,
283 print!("My name is {} {x$0 + x}", stringify!(Paperino))
290 print!("My name is {} {}"$0, stringify!(Paperino), x + x)