aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <bors[bot]@users.noreply.github.com>2019-02-07 22:56:14 +0000
committerbors[bot] <bors[bot]@users.noreply.github.com>2019-02-07 22:56:14 +0000
commit9a1d2a46c249fa81294c156b9e23b624e14495cd (patch)
tree774b4ad5f5f769e6045330c1b886ac1995eb2066
parent7e3776ca56a7f042a6a07f7cc2a4b27feb652516 (diff)
parentbe7c5eecc4ec9e099bf34e78289cd672aacbd168 (diff)
Merge #760
760: Add new assist to remove dbg!() calls r=matklad a=vipentti This fixes #758. Currently we try to maintain the cursor position relative to the statement under cursor, if the cursor is inside the dbg! macro call. Meaning: ```rust let foo = dbg!(some.complex<|>().expression()); ``` Should turn into: ```rust let foo = some.complex<|>().expression(); ``` With the cursor staying in place. Co-authored-by: Ville Penttinen <[email protected]>
-rw-r--r--crates/ra_assists/src/lib.rs2
-rw-r--r--crates/ra_assists/src/remove_dbg.rs123
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;
73mod introduce_variable; 73mod introduce_variable;
74mod replace_if_let_with_match; 74mod replace_if_let_with_match;
75mod split_import; 75mod split_import;
76mod remove_dbg;
76fn all_assists<DB: HirDatabase>() -> &'static [fn(AssistCtx<DB>) -> Option<Assist>] { 77fn 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 @@
1use hir::db::HirDatabase;
2use 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};
9use crate::{AssistCtx, Assist};
10
11pub(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
57fn 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)]
79mod 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 "
96fn foo(n: usize) {
97 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
98 // ...
99 }
100}
101",
102 "
103fn 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}