diff options
Diffstat (limited to 'crates/ra_assists/src/handlers/remove_dbg.rs')
-rw-r--r-- | crates/ra_assists/src/handlers/remove_dbg.rs | 150 |
1 files changed, 150 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs new file mode 100644 index 000000000..5085649b4 --- /dev/null +++ b/crates/ra_assists/src/handlers/remove_dbg.rs | |||
@@ -0,0 +1,150 @@ | |||
1 | use ra_syntax::{ | ||
2 | ast::{self, AstNode}, | ||
3 | TextUnit, T, | ||
4 | }; | ||
5 | |||
6 | use crate::{Assist, AssistCtx, AssistId}; | ||
7 | |||
8 | // Assist: remove_dbg | ||
9 | // | ||
10 | // Removes `dbg!()` macro call. | ||
11 | // | ||
12 | // ``` | ||
13 | // fn main() { | ||
14 | // <|>dbg!(92); | ||
15 | // } | ||
16 | // ``` | ||
17 | // -> | ||
18 | // ``` | ||
19 | // fn main() { | ||
20 | // 92; | ||
21 | // } | ||
22 | // ``` | ||
23 | pub(crate) fn remove_dbg(ctx: AssistCtx) -> Option<Assist> { | ||
24 | let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?; | ||
25 | |||
26 | if !is_valid_macrocall(¯o_call, "dbg")? { | ||
27 | return None; | ||
28 | } | ||
29 | |||
30 | let macro_range = macro_call.syntax().text_range(); | ||
31 | |||
32 | // If the cursor is inside the macro call, we'll try to maintain the cursor | ||
33 | // position by subtracting the length of dbg!( from the start of the file | ||
34 | // range, otherwise we'll default to using the start of the macro call | ||
35 | let cursor_pos = { | ||
36 | let file_range = ctx.frange.range; | ||
37 | |||
38 | let offset_start = file_range | ||
39 | .start() | ||
40 | .checked_sub(macro_range.start()) | ||
41 | .unwrap_or_else(|| TextUnit::from(0)); | ||
42 | |||
43 | let dbg_size = TextUnit::of_str("dbg!("); | ||
44 | |||
45 | if offset_start > dbg_size { | ||
46 | file_range.start() - dbg_size | ||
47 | } else { | ||
48 | macro_range.start() | ||
49 | } | ||
50 | }; | ||
51 | |||
52 | let macro_content = { | ||
53 | let macro_args = macro_call.token_tree()?.syntax().clone(); | ||
54 | |||
55 | let text = macro_args.text(); | ||
56 | let without_parens = TextUnit::of_char('(')..text.len() - TextUnit::of_char(')'); | ||
57 | text.slice(without_parens).to_string() | ||
58 | }; | ||
59 | |||
60 | ctx.add_assist(AssistId("remove_dbg"), "Remove dbg!()", |edit| { | ||
61 | edit.target(macro_call.syntax().text_range()); | ||
62 | edit.replace(macro_range, macro_content); | ||
63 | edit.set_cursor(cursor_pos); | ||
64 | }) | ||
65 | } | ||
66 | |||
67 | /// Verifies that the given macro_call actually matches the given name | ||
68 | /// and contains proper ending tokens | ||
69 | fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> { | ||
70 | let path = macro_call.path()?; | ||
71 | let name_ref = path.segment()?.name_ref()?; | ||
72 | |||
73 | // Make sure it is actually a dbg-macro call, dbg followed by ! | ||
74 | let excl = path.syntax().next_sibling_or_token()?; | ||
75 | |||
76 | if name_ref.text() != macro_name || excl.kind() != T![!] { | ||
77 | return None; | ||
78 | } | ||
79 | |||
80 | let node = macro_call.token_tree()?.syntax().clone(); | ||
81 | let first_child = node.first_child_or_token()?; | ||
82 | let last_child = node.last_child_or_token()?; | ||
83 | |||
84 | match (first_child.kind(), last_child.kind()) { | ||
85 | (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true), | ||
86 | _ => Some(false), | ||
87 | } | ||
88 | } | ||
89 | |||
90 | #[cfg(test)] | ||
91 | mod tests { | ||
92 | use super::*; | ||
93 | use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
94 | |||
95 | #[test] | ||
96 | fn test_remove_dbg() { | ||
97 | check_assist(remove_dbg, "<|>dbg!(1 + 1)", "<|>1 + 1"); | ||
98 | |||
99 | check_assist(remove_dbg, "dbg!<|>((1 + 1))", "<|>(1 + 1)"); | ||
100 | |||
101 | check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 <|>+ 1"); | ||
102 | |||
103 | check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = <|>1 + 1"); | ||
104 | |||
105 | check_assist( | ||
106 | remove_dbg, | ||
107 | " | ||
108 | fn foo(n: usize) { | ||
109 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
110 | // ... | ||
111 | } | ||
112 | } | ||
113 | ", | ||
114 | " | ||
115 | fn foo(n: usize) { | ||
116 | if let Some(_) = n.<|>checked_sub(4) { | ||
117 | // ... | ||
118 | } | ||
119 | } | ||
120 | ", | ||
121 | ); | ||
122 | } | ||
123 | #[test] | ||
124 | fn test_remove_dbg_with_brackets_and_braces() { | ||
125 | check_assist(remove_dbg, "dbg![<|>1 + 1]", "<|>1 + 1"); | ||
126 | check_assist(remove_dbg, "dbg!{<|>1 + 1}", "<|>1 + 1"); | ||
127 | } | ||
128 | |||
129 | #[test] | ||
130 | fn test_remove_dbg_not_applicable() { | ||
131 | check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]"); | ||
132 | check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)"); | ||
133 | check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7"); | ||
134 | } | ||
135 | |||
136 | #[test] | ||
137 | fn remove_dbg_target() { | ||
138 | check_assist_target( | ||
139 | remove_dbg, | ||
140 | " | ||
141 | fn foo(n: usize) { | ||
142 | if let Some(_) = dbg!(n.<|>checked_sub(4)) { | ||
143 | // ... | ||
144 | } | ||
145 | } | ||
146 | ", | ||
147 | "dbg!(n.checked_sub(4))", | ||
148 | ); | ||
149 | } | ||
150 | } | ||