ide_assists/handlers/
toggle_macro_delimiter.rs

1use ide_db::assists::AssistId;
2use syntax::{
3    AstNode, SyntaxToken, T,
4    ast::{self, syntax_factory::SyntaxFactory},
5};
6
7use crate::{AssistContext, Assists};
8
9// Assist: toggle_macro_delimiter
10//
11// Change macro delimiters in the order of `( -> { -> [ -> (`.
12//
13// ```
14// macro_rules! sth {
15//     () => {};
16// }
17//
18// sth!$0( );
19// ```
20// ->
21// ```
22// macro_rules! sth {
23//     () => {};
24// }
25//
26// sth!{ }
27// ```
28pub(crate) fn toggle_macro_delimiter(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
29    #[derive(Debug)]
30    enum MacroDelims {
31        LPar,
32        RPar,
33        LBra,
34        RBra,
35        LCur,
36        RCur,
37    }
38
39    let makro = ctx.find_node_at_offset::<ast::MacroCall>()?;
40
41    let cursor_offset = ctx.offset();
42    let semicolon = macro_semicolon(&makro);
43    let token_tree = makro.token_tree()?;
44
45    let ltoken = token_tree.left_delimiter_token()?;
46    let rtoken = token_tree.right_delimiter_token()?;
47
48    if !ltoken.text_range().contains(cursor_offset) && !rtoken.text_range().contains(cursor_offset)
49    {
50        return None;
51    }
52
53    let token = match ltoken.kind() {
54        T!['{'] => MacroDelims::LCur,
55        T!['('] => MacroDelims::LPar,
56        T!['['] => MacroDelims::LBra,
57        T!['}'] => MacroDelims::RBra,
58        T![')'] => MacroDelims::RPar,
59        T!['}'] => MacroDelims::RCur,
60        _ => return None,
61    };
62
63    acc.add(
64        AssistId::refactor("toggle_macro_delimiter"),
65        match token {
66            MacroDelims::LPar | MacroDelims::RPar => "Replace delimiters with braces",
67            MacroDelims::LBra | MacroDelims::RBra => "Replace delimiters with parentheses",
68            MacroDelims::LCur | MacroDelims::RCur => "Replace delimiters with brackets",
69        },
70        token_tree.syntax().text_range(),
71        |builder| {
72            let make = SyntaxFactory::with_mappings();
73            let mut editor = builder.make_editor(makro.syntax());
74
75            match token {
76                MacroDelims::LPar | MacroDelims::RPar => {
77                    editor.replace(ltoken, make.token(T!['{']));
78                    editor.replace(rtoken, make.token(T!['}']));
79                    if let Some(sc) = semicolon {
80                        editor.delete(sc);
81                    }
82                }
83                MacroDelims::LBra | MacroDelims::RBra => {
84                    editor.replace(ltoken, make.token(T!['(']));
85                    editor.replace(rtoken, make.token(T![')']));
86                }
87                MacroDelims::LCur | MacroDelims::RCur => {
88                    editor.replace(ltoken, make.token(T!['[']));
89                    editor.replace(rtoken, make.token(T![']']));
90                }
91            }
92            editor.add_mappings(make.finish_with_mappings());
93            builder.add_file_edits(ctx.vfs_file_id(), editor);
94        },
95    )
96}
97
98fn macro_semicolon(makro: &ast::MacroCall) -> Option<SyntaxToken> {
99    makro.semicolon_token().or_else(|| {
100        let macro_expr = ast::MacroExpr::cast(makro.syntax().parent()?)?;
101        let expr_stmt = ast::ExprStmt::cast(macro_expr.syntax().parent()?)?;
102        expr_stmt.semicolon_token()
103    })
104}
105
106#[cfg(test)]
107mod tests {
108    use crate::tests::{check_assist, check_assist_not_applicable};
109
110    use super::*;
111
112    #[test]
113    fn test_par() {
114        check_assist(
115            toggle_macro_delimiter,
116            r#"
117macro_rules! sth {
118    () => {};
119}
120
121sth!$0( );
122            "#,
123            r#"
124macro_rules! sth {
125    () => {};
126}
127
128sth!{ }
129            "#,
130        );
131
132        check_assist(
133            toggle_macro_delimiter,
134            r#"
135macro_rules! sth {
136    () => {};
137}
138
139fn foo() {
140    sth!$0( );
141}
142            "#,
143            r#"
144macro_rules! sth {
145    () => {};
146}
147
148fn foo() {
149    sth!{ }
150}
151            "#,
152        );
153    }
154
155    #[test]
156    fn test_braces() {
157        check_assist(
158            toggle_macro_delimiter,
159            r#"
160macro_rules! sth {
161    () => {};
162}
163
164sth!$0{ };
165            "#,
166            r#"
167macro_rules! sth {
168    () => {};
169}
170
171sth![ ];
172            "#,
173        )
174    }
175
176    #[test]
177    fn test_brackets() {
178        check_assist(
179            toggle_macro_delimiter,
180            r#"
181macro_rules! sth {
182    () => {};
183}
184
185sth!$0[ ];
186            "#,
187            r#"
188macro_rules! sth {
189    () => {};
190}
191
192sth!( );
193            "#,
194        )
195    }
196
197    #[test]
198    fn test_indent() {
199        check_assist(
200            toggle_macro_delimiter,
201            r#"
202mod abc {
203    macro_rules! sth {
204        () => {};
205    }
206
207    sth!$0{ };
208}
209            "#,
210            r#"
211mod abc {
212    macro_rules! sth {
213        () => {};
214    }
215
216    sth![ ];
217}
218            "#,
219        )
220    }
221
222    #[test]
223    fn test_unrelated_par() {
224        check_assist_not_applicable(
225            toggle_macro_delimiter,
226            r#"
227macro_rules! prt {
228    ($e:expr) => {{
229        println!("{}", stringify!{$e});
230    }};
231}
232
233prt!(($03 + 5));
234
235            "#,
236        )
237    }
238
239    #[test]
240    fn test_longer_macros() {
241        check_assist(
242            toggle_macro_delimiter,
243            r#"
244macro_rules! prt {
245    ($e:expr) => {{
246        println!("{}", stringify!{$e});
247    }};
248}
249
250prt!$0((3 + 5));
251"#,
252            r#"
253macro_rules! prt {
254    ($e:expr) => {{
255        println!("{}", stringify!{$e});
256    }};
257}
258
259prt!{(3 + 5)}
260"#,
261        )
262    }
263
264    // FIXME @alibektas : Inner macro_call is not seen as such. So this doesn't work.
265    #[test]
266    fn test_nested_macros() {
267        check_assist_not_applicable(
268            toggle_macro_delimiter,
269            r#"
270macro_rules! prt {
271    ($e:expr) => {{
272        println!("{}", stringify!{$e});
273    }};
274}
275
276macro_rules! abc {
277    ($e:expr) => {{
278        println!("{}", stringify!{$e});
279    }};
280}
281
282prt!{abc!($03 + 5)};
283"#,
284        )
285    }
286}