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