Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 101 additions & 37 deletions crates/ide-assists/src/handlers/wrap_unwrap_cfg_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ide_db::source_change::SourceChangeBuilder;
use itertools::Itertools;
use syntax::{
NodeOrToken, SyntaxToken, T, TextRange, algo,
ast::{self, AstNode, make, syntax_factory::SyntaxFactory},
ast::{self, AstNode, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory},
};

use crate::{AssistContext, AssistId, Assists};
Expand All @@ -27,7 +27,7 @@ use crate::{AssistContext, AssistId, Assists};

enum WrapUnwrapOption {
WrapDerive { derive: TextRange, attr: ast::Attr },
WrapAttr(ast::Attr),
WrapAttr(Vec<ast::Attr>),
}

/// Attempts to get the derive attribute from a derive attribute list
Expand Down Expand Up @@ -102,9 +102,9 @@ fn attempt_get_derive(attr: ast::Attr, ident: SyntaxToken) -> WrapUnwrapOption {
if ident.parent().and_then(ast::TokenTree::cast).is_none()
|| !attr.simple_name().map(|v| v.eq("derive")).unwrap_or_default()
{
WrapUnwrapOption::WrapAttr(attr)
WrapUnwrapOption::WrapAttr(vec![attr])
} else {
attempt_attr().unwrap_or(WrapUnwrapOption::WrapAttr(attr))
attempt_attr().unwrap_or_else(|| WrapUnwrapOption::WrapAttr(vec![attr]))
}
}
pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
Expand All @@ -118,13 +118,27 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
Some(attempt_get_derive(attr, ident))
}

(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(attr)),
(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(vec![attr])),
_ => None,
}
} else {
let covering_element = ctx.covering_element();
match covering_element {
NodeOrToken::Node(node) => ast::Attr::cast(node).map(WrapUnwrapOption::WrapAttr),
NodeOrToken::Node(node) => {
if let Some(attr) = ast::Attr::cast(node.clone()) {
Some(WrapUnwrapOption::WrapAttr(vec![attr]))
} else {
let attrs = node
.children()
.filter(|it| it.text_range().intersect(ctx.selection_trimmed()).is_some())
.map(ast::Attr::cast)
.collect::<Option<Vec<_>>>()?;
if attrs.is_empty() {
return None;
}
Some(WrapUnwrapOption::WrapAttr(attrs))
}
}
NodeOrToken::Token(ident) if ident.kind() == syntax::T![ident] => {
let attr = ident.parent_ancestors().find_map(ast::Attr::cast)?;
Some(attempt_get_derive(attr, ident))
Expand All @@ -133,10 +147,12 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
}
}?;
match option {
WrapUnwrapOption::WrapAttr(attr) if attr.simple_name().as_deref() == Some("cfg_attr") => {
unwrap_cfg_attr(acc, attr)
}
WrapUnwrapOption::WrapAttr(attr) => wrap_cfg_attr(acc, ctx, attr),
WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] {
[attr] if attr.simple_name().as_deref() == Some("cfg_attr") => {
unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap())
}
_ => wrap_cfg_attrs(acc, ctx, attrs),
},
WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive),
}
}
Expand Down Expand Up @@ -220,40 +236,51 @@ fn wrap_derive(
);
Some(())
}
fn wrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>, attr: ast::Attr) -> Option<()> {
let range = attr.syntax().text_range();
let path = attr.path()?;
fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec<ast::Attr>) -> Option<()> {
let (first_attr, last_attr) = (attrs.first()?, attrs.last()?);
let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range());
let path_attrs =
attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::<Option<Vec<_>>>()?;
let handle_source_change = |edit: &mut SourceChangeBuilder| {
let make = SyntaxFactory::with_mappings();
let mut editor = edit.make_editor(attr.syntax());
let mut raw_tokens =
vec![NodeOrToken::Token(make.token(T![,])), NodeOrToken::Token(make.whitespace(" "))];
path.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
if let Some(meta) = attr.meta() {
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
raw_tokens.push(NodeOrToken::Token(eq));
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
let mut editor = edit.make_editor(first_attr.syntax());
let mut raw_tokens = vec![];
for (path, attr) in path_attrs {
raw_tokens.extend([
NodeOrToken::Token(make.token(T![,])),
NodeOrToken::Token(make.whitespace(" ")),
]);
path.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
if let Some(meta) = attr.meta() {
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
raw_tokens.push(NodeOrToken::Token(eq));
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));

expr.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
} else if let Some(tt) = meta.token_tree() {
raw_tokens.extend(tt.token_trees_and_tokens());
expr.syntax().descendants_with_tokens().for_each(|it| {
if let NodeOrToken::Token(token) = it {
raw_tokens.push(NodeOrToken::Token(token));
}
});
} else if let Some(tt) = meta.token_tree() {
raw_tokens.extend(tt.token_trees_and_tokens());
}
}
}
let meta =
make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens));
let cfg_attr =
if attr.excl_token().is_some() { make.attr_inner(meta) } else { make.attr_outer(meta) };
let cfg_attr = if first_attr.excl_token().is_some() {
make.attr_inner(meta)
} else {
make.attr_outer(meta)
};

editor.replace(attr.syntax(), cfg_attr.syntax());
let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into();
editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]);

if let Some(snippet_cap) = ctx.config.snippet_cap
&& let Some(first_meta) =
Expand Down Expand Up @@ -332,7 +359,8 @@ fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> {
return None;
}
let handle_source_change = |f: &mut SourceChangeBuilder| {
let inner_attrs = inner_attrs.iter().map(|it| it.to_string()).join("\n");
let inner_attrs =
inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level()));
f.replace(range, inner_attrs);
};
acc.add(
Expand Down Expand Up @@ -414,6 +442,42 @@ mod tests {
}
"#,
);
check_assist(
wrap_unwrap_cfg_attr,
r#"
pub struct Test {
#[other_attr]
$0#[foo]
#[bar]$0
#[other_attr]
test: u32,
}
"#,
r#"
pub struct Test {
#[other_attr]
#[cfg_attr($0, foo, bar)]
#[other_attr]
test: u32,
}
"#,
);
check_assist(
wrap_unwrap_cfg_attr,
r#"
pub struct Test {
#[cfg_attr(debug_assertions$0, foo, bar)]
test: u32,
}
"#,
r#"
pub struct Test {
#[foo]
#[bar]
test: u32,
}
"#,
);
}
#[test]
fn to_from_eq_attr() {
Expand Down
Loading