mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 20:42:04 +00:00
suggest ExtractRefactor if no expressions found
Added `Ident` variant to arg enum.
This commit is contained in:
parent
a5cbee4d11
commit
fb5ae9906b
2 changed files with 86 additions and 32 deletions
|
@ -56,7 +56,15 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
}
|
}
|
||||||
|
|
||||||
acc.add(
|
acc.add(
|
||||||
AssistId("move_format_string_arg", AssistKind::QuickFix),
|
AssistId(
|
||||||
|
"move_format_string_arg",
|
||||||
|
// if there aren't any expressions, then make the assist a RefactorExtract
|
||||||
|
if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
|
||||||
|
AssistKind::RefactorExtract
|
||||||
|
} else {
|
||||||
|
AssistKind::QuickFix
|
||||||
|
},
|
||||||
|
),
|
||||||
"Extract format args",
|
"Extract format args",
|
||||||
tt.syntax().text_range(),
|
tt.syntax().text_range(),
|
||||||
|edit| {
|
|edit| {
|
||||||
|
@ -107,7 +115,7 @@ pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||||
args.push_str(", ");
|
args.push_str(", ");
|
||||||
|
|
||||||
match extracted_args {
|
match extracted_args {
|
||||||
Arg::Expr(s) => {
|
Arg::Ident(s) | Arg::Expr(s) => {
|
||||||
// insert arg
|
// insert arg
|
||||||
args.push_str(&s);
|
args.push_str(&s);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,19 @@
|
||||||
/// Enum for represenging extraced format string args.
|
/// Enum for represenging extraced format string args.
|
||||||
/// Can either be extracted expressions (which includes identifiers),
|
/// Can either be extracted expressions (which includes identifiers),
|
||||||
/// or placeholders `{}`.
|
/// or placeholders `{}`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Arg {
|
pub enum Arg {
|
||||||
Placeholder,
|
Placeholder,
|
||||||
|
Ident(String),
|
||||||
Expr(String),
|
Expr(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`].
|
Add placeholders like `$1` and `$2` in place of [`Arg::Placeholder`],
|
||||||
|
and unwraps the [`Arg::Ident`] and [`Arg::Expr`] enums.
|
||||||
```rust
|
```rust
|
||||||
assert_eq!(vec![Arg::Expr("expr"), Arg::Placeholder, Arg::Expr("expr")], vec!["expr", "$1", "expr"])
|
# use ide_db::syntax_helpers::format_string_exprs::*;
|
||||||
|
assert_eq!(with_placeholders(vec![Arg::Ident("ident".to_owned()), Arg::Placeholder, Arg::Expr("expr + 2".to_owned())]), vec!["ident".to_owned(), "$1".to_owned(), "expr + 2".to_owned()])
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -21,7 +24,7 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
|
||||||
let mut placeholder_id = 1;
|
let mut placeholder_id = 1;
|
||||||
args.into_iter()
|
args.into_iter()
|
||||||
.map(move |a| match a {
|
.map(move |a| match a {
|
||||||
Arg::Expr(s) => s,
|
Arg::Expr(s) | Arg::Ident(s) => s,
|
||||||
Arg::Placeholder => {
|
Arg::Placeholder => {
|
||||||
let s = format!("${placeholder_id}");
|
let s = format!("${placeholder_id}");
|
||||||
placeholder_id += 1;
|
placeholder_id += 1;
|
||||||
|
@ -40,21 +43,22 @@ pub fn with_placeholders(args: Vec<Arg>) -> Vec<String> {
|
||||||
Splits a format string that may contain expressions
|
Splits a format string that may contain expressions
|
||||||
like
|
like
|
||||||
```rust
|
```rust
|
||||||
assert_eq!(parse("{expr} {} {expr} ").unwrap(), ("{} {} {}", vec![Arg::Expr("expr"), Arg::Placeholder, Arg::Expr("expr")]));
|
assert_eq!(parse("{ident} {} {expr + 42} ").unwrap(), ("{} {} {}", vec![Arg::Ident("ident"), Arg::Placeholder, Arg::Expr("expr + 42")]));
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum State {
|
enum State {
|
||||||
NotExpr,
|
NotArg,
|
||||||
MaybeExpr,
|
MaybeArg,
|
||||||
Expr,
|
Expr,
|
||||||
|
Ident,
|
||||||
MaybeIncorrect,
|
MaybeIncorrect,
|
||||||
FormatOpts,
|
FormatOpts,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut state = State::NotArg;
|
||||||
let mut current_expr = String::new();
|
let mut current_expr = String::new();
|
||||||
let mut state = State::NotExpr;
|
|
||||||
let mut extracted_expressions = Vec::new();
|
let mut extracted_expressions = Vec::new();
|
||||||
let mut output = String::new();
|
let mut output = String::new();
|
||||||
|
|
||||||
|
@ -66,15 +70,15 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
let mut chars = input.chars().peekable();
|
let mut chars = input.chars().peekable();
|
||||||
while let Some(chr) = chars.next() {
|
while let Some(chr) = chars.next() {
|
||||||
match (state, chr) {
|
match (state, chr) {
|
||||||
(State::NotExpr, '{') => {
|
(State::NotArg, '{') => {
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
state = State::MaybeExpr;
|
state = State::MaybeArg;
|
||||||
}
|
}
|
||||||
(State::NotExpr, '}') => {
|
(State::NotArg, '}') => {
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
state = State::MaybeIncorrect;
|
state = State::MaybeIncorrect;
|
||||||
}
|
}
|
||||||
(State::NotExpr, _) => {
|
(State::NotArg, _) => {
|
||||||
if matches!(chr, '\\' | '$') {
|
if matches!(chr, '\\' | '$') {
|
||||||
output.push('\\');
|
output.push('\\');
|
||||||
}
|
}
|
||||||
|
@ -83,51 +87,72 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
(State::MaybeIncorrect, '}') => {
|
(State::MaybeIncorrect, '}') => {
|
||||||
// It's okay, we met "}}".
|
// It's okay, we met "}}".
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
state = State::NotExpr;
|
state = State::NotArg;
|
||||||
}
|
}
|
||||||
(State::MaybeIncorrect, _) => {
|
(State::MaybeIncorrect, _) => {
|
||||||
// Error in the string.
|
// Error in the string.
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
(State::MaybeExpr, '{') => {
|
// Escaped braces `{{`
|
||||||
|
(State::MaybeArg, '{') => {
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
state = State::NotExpr;
|
state = State::NotArg;
|
||||||
}
|
}
|
||||||
(State::MaybeExpr, '}') => {
|
(State::MaybeArg, '}') => {
|
||||||
// This is an empty sequence '{}'. Replace it with placeholder.
|
// This is an empty sequence '{}'.
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
extracted_expressions.push(Arg::Placeholder);
|
extracted_expressions.push(Arg::Placeholder);
|
||||||
state = State::NotExpr;
|
state = State::NotArg;
|
||||||
}
|
}
|
||||||
(State::MaybeExpr, _) => {
|
(State::MaybeArg, _) => {
|
||||||
if matches!(chr, '\\' | '$') {
|
if matches!(chr, '\\' | '$') {
|
||||||
current_expr.push('\\');
|
current_expr.push('\\');
|
||||||
}
|
}
|
||||||
current_expr.push(chr);
|
current_expr.push(chr);
|
||||||
state = State::Expr;
|
|
||||||
|
// While Rust uses the unicode sets of XID_start and XID_continue for Identifiers
|
||||||
|
// this is probably the best we can do to avoid a false positive
|
||||||
|
if chr.is_alphabetic() || chr == '_' {
|
||||||
|
state = State::Ident;
|
||||||
|
} else {
|
||||||
|
state = State::Expr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(State::Expr, '}') => {
|
(State::Ident | State::Expr, '}') => {
|
||||||
if inexpr_open_count == 0 {
|
if inexpr_open_count == 0 {
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
|
|
||||||
|
if matches!(state, State::Expr) {
|
||||||
|
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
|
||||||
|
} else {
|
||||||
|
extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
|
||||||
|
}
|
||||||
|
|
||||||
current_expr = String::new();
|
current_expr = String::new();
|
||||||
state = State::NotExpr;
|
state = State::NotArg;
|
||||||
} else {
|
} else {
|
||||||
// We're closing one brace met before inside of the expression.
|
// We're closing one brace met before inside of the expression.
|
||||||
current_expr.push(chr);
|
current_expr.push(chr);
|
||||||
inexpr_open_count -= 1;
|
inexpr_open_count -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(State::Expr, ':') if matches!(chars.peek(), Some(':')) => {
|
(State::Ident | State::Expr, ':') if matches!(chars.peek(), Some(':')) => {
|
||||||
// path separator
|
// path separator
|
||||||
|
state = State::Expr;
|
||||||
current_expr.push_str("::");
|
current_expr.push_str("::");
|
||||||
chars.next();
|
chars.next();
|
||||||
}
|
}
|
||||||
(State::Expr, ':') => {
|
(State::Ident | State::Expr, ':') => {
|
||||||
if inexpr_open_count == 0 {
|
if inexpr_open_count == 0 {
|
||||||
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
|
// We're outside of braces, thus assume that it's a specifier, like "{Some(value):?}"
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
|
|
||||||
|
if matches!(state, State::Expr) {
|
||||||
|
extracted_expressions.push(Arg::Expr(current_expr.trim().into()));
|
||||||
|
} else {
|
||||||
|
extracted_expressions.push(Arg::Ident(current_expr.trim().into()));
|
||||||
|
}
|
||||||
|
|
||||||
current_expr = String::new();
|
current_expr = String::new();
|
||||||
state = State::FormatOpts;
|
state = State::FormatOpts;
|
||||||
} else {
|
} else {
|
||||||
|
@ -135,11 +160,16 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
current_expr.push(chr);
|
current_expr.push(chr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(State::Expr, '{') => {
|
(State::Ident | State::Expr, '{') => {
|
||||||
|
state = State::Expr;
|
||||||
current_expr.push(chr);
|
current_expr.push(chr);
|
||||||
inexpr_open_count += 1;
|
inexpr_open_count += 1;
|
||||||
}
|
}
|
||||||
(State::Expr, _) => {
|
(State::Ident | State::Expr, _) => {
|
||||||
|
if !(chr.is_alphanumeric() || chr == '_' || chr == '#') {
|
||||||
|
state = State::Expr;
|
||||||
|
}
|
||||||
|
|
||||||
if matches!(chr, '\\' | '$') {
|
if matches!(chr, '\\' | '$') {
|
||||||
current_expr.push('\\');
|
current_expr.push('\\');
|
||||||
}
|
}
|
||||||
|
@ -147,7 +177,7 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
}
|
}
|
||||||
(State::FormatOpts, '}') => {
|
(State::FormatOpts, '}') => {
|
||||||
output.push(chr);
|
output.push(chr);
|
||||||
state = State::NotExpr;
|
state = State::NotArg;
|
||||||
}
|
}
|
||||||
(State::FormatOpts, _) => {
|
(State::FormatOpts, _) => {
|
||||||
if matches!(chr, '\\' | '$') {
|
if matches!(chr, '\\' | '$') {
|
||||||
|
@ -158,7 +188,7 @@ pub fn parse_format_exprs(input: &str) -> Result<(String, Vec<Arg>), ()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if state != State::NotExpr {
|
if state != State::NotArg {
|
||||||
return Err(());
|
return Err(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -218,4 +248,20 @@ mod tests {
|
||||||
check(input, output)
|
check(input, output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn arg_type() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_format_exprs("{_ident} {r#raw_ident} {expr.obj} {name {thing: 42} } {}")
|
||||||
|
.unwrap()
|
||||||
|
.1,
|
||||||
|
vec![
|
||||||
|
Arg::Ident("_ident".to_owned()),
|
||||||
|
Arg::Ident("r#raw_ident".to_owned()),
|
||||||
|
Arg::Expr("expr.obj".to_owned()),
|
||||||
|
Arg::Expr("name {thing: 42}".to_owned()),
|
||||||
|
Arg::Placeholder
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue