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;
10 use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
12 // Assist: move_format_string_arg
14 // Move an expression out of a format string.
17 // macro_rules! format_args {
18 // ($lit:literal $(tt:tt)*) => { 0 },
20 // macro_rules! print {
21 // ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
25 // print!("{x + 1}$0");
30 // macro_rules! format_args {
31 // ($lit:literal $(tt:tt)*) => { 0 },
33 // macro_rules! print {
34 // ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
38 // print!("{}"$0, x + 1);
42 pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43 let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
44 let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;
46 let expanded_t = ast::String::cast(
47 ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
49 if !is_format_string(&expanded_t) {
53 let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
54 if extracted_args.is_empty() {
60 "move_format_string_arg",
61 // if there aren't any expressions, then make the assist a RefactorExtract
62 if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
63 AssistKind::RefactorExtract
68 "Extract format args",
69 tt.syntax().text_range(),
71 let fmt_range = fmt_string.syntax().text_range();
73 // Replace old format string with new format string whose arguments have been extracted
74 edit.replace(fmt_range, new_fmt);
76 // Insert cursor at end of format string
77 edit.insert(fmt_range.end(), "$0");
79 // Extract existing arguments in macro
81 tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();
83 let mut existing_args: Vec<String> = vec![];
85 let mut current_arg = String::new();
86 if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
90 if t.kind() == COMMA {
91 existing_args.push(current_arg.trim().into());
94 current_arg.push_str(t.text());
97 existing_args.push(current_arg.trim().into());
99 // delete everything after the format string till end bracket
100 // we're going to insert the new arguments later
101 edit.delete(TextRange::new(
102 format_string.text_range().end(),
103 end_bracket.text_range().start(),
107 // Start building the new args
108 let mut existing_args = existing_args.into_iter();
109 let mut args = String::new();
111 let mut placeholder_idx = 1;
113 for extracted_args in extracted_args {
114 // remove expr from format string
117 match extracted_args {
118 Arg::Ident(s) | Arg::Expr(s) => {
122 Arg::Placeholder => {
123 // try matching with existing argument
124 match existing_args.next() {
129 // insert placeholder
130 args.push_str(&format!("${placeholder_idx}"));
131 placeholder_idx += 1;
139 edit.insert(fmt_range.end(), args);
149 use crate::tests::check_assist;
151 const MACRO_DECL: &'static str = r#"
152 macro_rules! format_args {
153 ($lit:literal $(tt:tt)*) => { 0 },
156 ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
160 fn add_macro_decl(s: &'static str) -> String {
161 MACRO_DECL.to_string() + s
165 fn multiple_middle_arg() {
167 move_format_string_arg,
171 print!("{} {x + 1:b} {}$0", y + 2, 2);
178 print!("{} {:b} {}"$0, y + 2, x + 1, 2);
188 move_format_string_arg,
192 print!("{obj.value:b}$0",);
199 print!("{:b}"$0, obj.value);
207 fn multiple_middle_placeholders_arg() {
209 move_format_string_arg,
213 print!("{} {x + 1:b} {} {}$0", y + 2, 2);
220 print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
228 fn multiple_trailing_args() {
230 move_format_string_arg,
234 print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
241 print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
249 fn improper_commas() {
251 move_format_string_arg,
255 print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
262 print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));