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