ide_assists/handlers/
toggle_macro_delimiter.rs1use ide_db::assists::AssistId;
2use syntax::{
3 AstNode, SyntaxToken, T,
4 ast::{self, syntax_factory::SyntaxFactory},
5};
6
7use crate::{AssistContext, Assists};
8
9pub(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 #[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}