|
| 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 | +} |
0 commit comments