Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/move_format_string_arg.rs')
| -rw-r--r-- | crates/ide-assists/src/handlers/move_format_string_arg.rs | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/move_format_string_arg.rs b/crates/ide-assists/src/handlers/move_format_string_arg.rs new file mode 100644 index 0000000000..696fd50b5c --- /dev/null +++ b/crates/ide-assists/src/handlers/move_format_string_arg.rs @@ -0,0 +1,209 @@ +use ide_db::{syntax_helpers::{format_string::is_format_string, format_string_exprs::{parse_format_exprs, Arg}}, assists::{AssistId, AssistKind}}; +use itertools::Itertools; +use syntax::{ast, AstToken, AstNode, NodeOrToken, SyntaxKind::COMMA, TextRange}; + +// Assist: move_format_string_arg +// +// Move an expression out of a format string. +// +// ``` +// fn main() { +// println!("{x + 1}$0"); +// } +// ``` +// -> +// ``` +// fn main() { +// println!("{a}", a$0 = x + 1); +// } +// ``` + +use crate::{AssistContext, /* AssistId, AssistKind, */ Assists}; + +pub(crate) fn move_format_string_arg (acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let t = ctx.find_token_at_offset::<ast::String>()?; + let tt = t.syntax().parent_ancestors().find_map(ast::TokenTree::cast)?; + + let expanded_t = ast::String::cast(ctx.sema.descend_into_macros_with_kind_preference(t.syntax().clone()))?; + + if !is_format_string(&expanded_t) { + return None; + } + + let target = tt.syntax().text_range(); + let extracted_args = parse_format_exprs(&t).ok()?; + let str_range = t.syntax().text_range(); + + let tokens = + tt.token_trees_and_tokens() + .filter_map(NodeOrToken::into_token) + .collect_vec(); + + acc.add(AssistId("move_format_string_arg", AssistKind::QuickFix), "Extract format args", target, |edit| { + let mut existing_args: Vec<String> = vec![]; + let mut current_arg = String::new(); + + if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] = tokens.as_slice() { + for t in tokens { + if t.kind() == COMMA { + existing_args.push(current_arg.trim().into()); + current_arg.clear(); + } else { + current_arg.push_str(t.text()); + } + } + existing_args.push(current_arg.trim().into()); + + // delete everything after the format string to the end bracket + // we're going to insert the new arguments later + edit.delete(TextRange::new(format_string.text_range().end(), end_bracket.text_range().start())); + } + + let mut existing_args = existing_args.into_iter(); + + // insert cursor at end of format string + edit.insert(str_range.end(), "$0"); + let mut placeholder_idx = 1; + let mut args = String::new(); + + for (text, extracted_args) in extracted_args { + // remove expr from format string + edit.delete(text); + + args.push_str(", "); + + match extracted_args { + Arg::Expr(s) => { + // insert arg + args.push_str(&s); + }, + Arg::Placeholder => { + // try matching with existing argument + match existing_args.next() { + Some(ea) => { + args.push_str(&ea); + }, + None => { + // insert placeholder + args.push_str(&format!("${placeholder_idx}")); + placeholder_idx += 1; + } + } + } + } + } + + edit.insert(str_range.end(), args); + }); + + Some(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::check_assist; + + const MACRO_DECL: &'static str = r#" +macro_rules! format_args { + ($lit:literal $(tt:tt)*) => { 0 }, +} +macro_rules! print { + ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*))); +} +"#; + + fn add_macro_decl (s: &'static str) -> String { + MACRO_DECL.to_string() + s + } + + #[test] + fn multiple_middle_arg() { + check_assist( + move_format_string_arg, + &add_macro_decl(r#" +fn main() { + print!("{} {x + 1:b} {}$0", y + 2, 2); +} +"#), + + &add_macro_decl(r#" +fn main() { + print!("{} {:b} {}"$0, y + 2, x + 1, 2); +} +"#), + ); + } + + #[test] + fn single_arg() { + check_assist( + move_format_string_arg, + &add_macro_decl(r#" +fn main() { + print!("{obj.value:b}$0",); +} +"#), + &add_macro_decl(r#" +fn main() { + print!("{:b}"$0, obj.value); +} +"#), + ); + } + + #[test] + fn multiple_middle_placeholders_arg() { + check_assist( + move_format_string_arg, + &add_macro_decl(r#" +fn main() { + print!("{} {x + 1:b} {} {}$0", y + 2, 2); +} +"#), + + &add_macro_decl(r#" +fn main() { + print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1); +} +"#), + ); + } + + #[test] + fn multiple_trailing_args() { + check_assist( + move_format_string_arg, + &add_macro_decl(r#" +fn main() { + print!("{} {x + 1:b} {Struct(1, 2)}$0", 1); +} +"#), + + &add_macro_decl(r#" +fn main() { + print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); +} +"#), + ); + } + + #[test] + fn improper_commas() { + check_assist( + move_format_string_arg, + &add_macro_decl(r#" +fn main() { + print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,); +} +"#), + + &add_macro_decl(r#" +fn main() { + print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2)); +} +"#), + ); + } + +} |