diff --git a/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs new file mode 100644 index 0000000000..eedb2ea3b9 --- /dev/null +++ b/crates/ide-assists/src/handlers/toggle_macro_delimiter.rs @@ -0,0 +1,256 @@ +use ide_db::assists::{AssistId, AssistKind}; +use syntax::{ + ast::{self, make}, + ted, AstNode, T, +}; + +use crate::{AssistContext, Assists}; + +// Assist: toggle_macro_delimiter +// +// Change macro delimiters in the order of `( -> { -> [ -> (`. +// +// ``` +// macro_rules! sth { +// () => {}; +// } +// +// sth!$0( ); +// ``` +// -> +// ``` +// macro_rules! sth { +// () => {}; +// } +// +// sth!{ } +// ``` +pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + #[derive(Debug)] + enum MacroDelims { + LPar, + RPar, + LBra, + RBra, + LCur, + RCur, + } + + let makro = ctx.find_node_at_offset::()?.clone_for_update(); + let makro_text_range = makro.syntax().text_range(); + + let cursor_offset = ctx.offset(); + let semicolon = makro.semicolon_token(); + let token_tree = makro.token_tree()?; + + let ltoken = token_tree.left_delimiter_token()?; + let rtoken = token_tree.right_delimiter_token()?; + + if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset) + { + return None; + } + + let token = match ltoken.kind() { + T!['{'] => MacroDelims::LCur, + T!['('] => MacroDelims::LPar, + T!['['] => MacroDelims::LBra, + T!['}'] => MacroDelims::RBra, + T![')'] => MacroDelims::RPar, + T!['}'] => MacroDelims::RCur, + _ => return None, + }; + + acc.add( + AssistId("toggle_macro_delimiter", AssistKind::Refactor), + match token { + MacroDelims::LPar => "Replace delimiters with braces", + MacroDelims::RPar => "Replace delimiters with braces", + MacroDelims::LBra => "Replace delimiters with parentheses", + MacroDelims::RBra => "Replace delimiters with parentheses", + MacroDelims::LCur => "Replace delimiters with brackets", + MacroDelims::RCur => "Replace delimiters with brackets", + }, + token_tree.syntax().text_range(), + |builder| { + match token { + MacroDelims::LPar | MacroDelims::RPar => { + ted::replace(ltoken, make::token(T!['{'])); + ted::replace(rtoken, make::token(T!['}'])); + if let Some(sc) = semicolon { + ted::remove(sc); + } + } + MacroDelims::LBra | MacroDelims::RBra => { + ted::replace(ltoken, make::token(T!['('])); + ted::replace(rtoken, make::token(T![')'])); + } + MacroDelims::LCur | MacroDelims::RCur => { + ted::replace(ltoken, make::token(T!['['])); + ted::replace(rtoken, make::token(T![']'])); + } + } + builder.replace(makro_text_range, makro.syntax().text()); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn test_par() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0( ); + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth!{ } + "#, + ) + } + + #[test] + fn test_braces() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0{ }; + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth![ ]; + "#, + ) + } + + #[test] + fn test_brackets() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! sth { + () => {}; +} + +sth!$0[ ]; + "#, + r#" +macro_rules! sth { + () => {}; +} + +sth!( ); + "#, + ) + } + + #[test] + fn test_indent() { + check_assist( + toggle_macro_delimiter, + r#" +mod abc { + macro_rules! sth { + () => {}; + } + + sth!$0{ }; +} + "#, + r#" +mod abc { + macro_rules! sth { + () => {}; + } + + sth![ ]; +} + "#, + ) + } + + #[test] + fn test_unrelated_par() { + check_assist_not_applicable( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!(($03 + 5)); + + "#, + ) + } + + #[test] + fn test_longer_macros() { + check_assist( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!$0((3 + 5)); +"#, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!{(3 + 5)} +"#, + ) + } + + // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work. + #[test] + fn test_nested_macros() { + check_assist_not_applicable( + toggle_macro_delimiter, + r#" +macro_rules! prt { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +macro_rules! abc { + ($e:expr) => {{ + println!("{}", stringify!{$e}); + }}; +} + +prt!{abc!($03 + 5)}; +"#, + ) + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index a9399ba6b7..b97f0f2739 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -213,6 +213,7 @@ mod handlers { mod term_search; mod toggle_async_sugar; mod toggle_ignore; + mod toggle_macro_delimiter; mod unmerge_match_arm; mod unmerge_use; mod unnecessary_async; @@ -343,6 +344,7 @@ mod handlers { split_import::split_import, term_search::term_search, toggle_ignore::toggle_ignore, + toggle_macro_delimiter::toggle_macro_delimiter, unmerge_match_arm::unmerge_match_arm, unmerge_use::unmerge_use, unnecessary_async::unnecessary_async, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index a2287b2977..595ce1affb 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -3091,6 +3091,27 @@ fn arithmetics { ) } +#[test] +fn doctest_toggle_macro_delimiter() { + check_doc_test( + "toggle_macro_delimiter", + r#####" +macro_rules! sth { + () => {}; +} + +sth!$0( ); +"#####, + r#####" +macro_rules! sth { + () => {}; +} + +sth!{ } +"#####, + ) +} + #[test] fn doctest_unmerge_match_arm() { check_doc_test(