Skip to content

Commit 2584d48

Browse files
committed
wip
1 parent b7e8b9a commit 2584d48

File tree

6 files changed

+463
-234
lines changed

6 files changed

+463
-234
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use ide_db::{syntax_helpers::{format_string::is_format_string, format_string_exprs::{parse_format_exprs, Arg}}, assists::{AssistId, AssistKind}};
2+
use itertools::Itertools;
3+
use syntax::{ast, AstToken, AstNode, NodeOrToken, SyntaxKind::COMMA, TextRange};
4+
5+
// Assist: move_format_string_arg
6+
//
7+
// Move an expression out of a format string.
8+
//
9+
// ```
10+
// fn main() {
11+
// println!("{x + 1}$0");
12+
// }
13+
// ```
14+
// ->
15+
// ```
16+
// fn main() {
17+
// println!("{a}", a$0 = x + 1);
18+
// }
19+
// ```
20+
21+
use crate::{AssistContext, /* AssistId, AssistKind, */ Assists};
22+
23+
pub(crate) fn move_format_string_arg (acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
24+
let t = ctx.find_token_at_offset::<ast::String>()?;
25+
let tt = t.syntax().parent_ancestors().find_map(ast::TokenTree::cast)?;
26+
27+
let expanded_t = ast::String::cast(ctx.sema.descend_into_macros_with_kind_preference(t.syntax().clone()))?;
28+
29+
if !is_format_string(&expanded_t) {
30+
return None;
31+
}
32+
33+
let target = tt.syntax().text_range();
34+
let extracted_args = parse_format_exprs(&t).ok()?;
35+
let str_range = t.syntax().text_range();
36+
37+
let tokens =
38+
tt.token_trees_and_tokens()
39+
.filter_map(NodeOrToken::into_token)
40+
.collect_vec();
41+
42+
acc.add(AssistId("move_format_string_arg", AssistKind::QuickFix), "Extract format args", target, |edit| {
43+
let mut existing_args: Vec<String> = vec![];
44+
let mut current_arg = String::new();
45+
46+
if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] = tokens.as_slice() {
47+
for t in tokens {
48+
if t.kind() == COMMA {
49+
existing_args.push(current_arg.trim().into());
50+
current_arg.clear();
51+
} else {
52+
current_arg.push_str(t.text());
53+
}
54+
}
55+
existing_args.push(current_arg.trim().into());
56+
57+
// delete everything after the format string to the end bracket
58+
// we're going to insert the new arguments later
59+
edit.delete(TextRange::new(format_string.text_range().end(), end_bracket.text_range().start()));
60+
}
61+
62+
let mut existing_args = existing_args.into_iter();
63+
64+
// insert cursor at end of format string
65+
edit.insert(str_range.end(), "$0");
66+
let mut placeholder_idx = 1;
67+
let mut args = String::new();
68+
69+
for (text, extracted_args) in extracted_args {
70+
// remove expr from format string
71+
edit.delete(text);
72+
73+
args.push_str(", ");
74+
75+
match extracted_args {
76+
Arg::Expr(s) => {
77+
// insert arg
78+
args.push_str(&s);
79+
},
80+
Arg::Placeholder => {
81+
// try matching with existing argument
82+
match existing_args.next() {
83+
Some(ea) => {
84+
args.push_str(&ea);
85+
},
86+
None => {
87+
// insert placeholder
88+
args.push_str(&format!("${placeholder_idx}"));
89+
placeholder_idx += 1;
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
edit.insert(str_range.end(), args);
97+
});
98+
99+
Some(())
100+
}
101+
102+
#[cfg(test)]
103+
mod tests {
104+
use super::*;
105+
use crate::tests::check_assist;
106+
107+
const MACRO_DECL: &'static str = r#"
108+
macro_rules! format_args {
109+
($lit:literal $(tt:tt)*) => { 0 },
110+
}
111+
macro_rules! print {
112+
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
113+
}
114+
"#;
115+
116+
fn add_macro_decl (s: &'static str) -> String {
117+
MACRO_DECL.to_string() + s
118+
}
119+
120+
#[test]
121+
fn multiple_middle_arg() {
122+
check_assist(
123+
move_format_string_arg,
124+
&add_macro_decl(r#"
125+
fn main() {
126+
print!("{} {x + 1:b} {}$0", y + 2, 2);
127+
}
128+
"#),
129+
130+
&add_macro_decl(r#"
131+
fn main() {
132+
print!("{} {:b} {}"$0, y + 2, x + 1, 2);
133+
}
134+
"#),
135+
);
136+
}
137+
138+
#[test]
139+
fn single_arg() {
140+
check_assist(
141+
move_format_string_arg,
142+
&add_macro_decl(r#"
143+
fn main() {
144+
print!("{obj.value:b}$0",);
145+
}
146+
"#),
147+
&add_macro_decl(r#"
148+
fn main() {
149+
print!("{:b}"$0, obj.value);
150+
}
151+
"#),
152+
);
153+
}
154+
155+
#[test]
156+
fn multiple_middle_placeholders_arg() {
157+
check_assist(
158+
move_format_string_arg,
159+
&add_macro_decl(r#"
160+
fn main() {
161+
print!("{} {x + 1:b} {} {}$0", y + 2, 2);
162+
}
163+
"#),
164+
165+
&add_macro_decl(r#"
166+
fn main() {
167+
print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
168+
}
169+
"#),
170+
);
171+
}
172+
173+
#[test]
174+
fn multiple_trailing_args() {
175+
check_assist(
176+
move_format_string_arg,
177+
&add_macro_decl(r#"
178+
fn main() {
179+
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
180+
}
181+
"#),
182+
183+
&add_macro_decl(r#"
184+
fn main() {
185+
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
186+
}
187+
"#),
188+
);
189+
}
190+
191+
#[test]
192+
fn improper_commas() {
193+
check_assist(
194+
move_format_string_arg,
195+
&add_macro_decl(r#"
196+
fn main() {
197+
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
198+
}
199+
"#),
200+
201+
&add_macro_decl(r#"
202+
fn main() {
203+
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
204+
}
205+
"#),
206+
);
207+
}
208+
209+
}

crates/ide-assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ mod handlers {
136136
mod flip_binexpr;
137137
mod flip_comma;
138138
mod flip_trait_bound;
139+
mod move_format_string_arg;
139140
mod generate_constant;
140141
mod generate_default_from_enum_variant;
141142
mod generate_default_from_new;

crates/ide-assists/src/tests/generated.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,23 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
15911591
)
15921592
}
15931593

1594+
#[test]
1595+
fn doctest_move_format_string_arg() {
1596+
check_doc_test(
1597+
"move_format_string_arg",
1598+
r#####"
1599+
fn main() {
1600+
println!("{x + 1}$0");
1601+
}
1602+
"#####,
1603+
r#####"
1604+
fn main() {
1605+
println!("{a}", a$0 = x + 1);
1606+
}
1607+
"#####,
1608+
)
1609+
}
1610+
15941611
#[test]
15951612
fn doctest_move_from_mod_rs() {
15961613
check_doc_test(

0 commit comments

Comments
 (0)