diff options
Diffstat (limited to 'crates/assists/src/handlers/convert_integer_literal.rs')
-rw-r--r-- | crates/assists/src/handlers/convert_integer_literal.rs | 135 |
1 files changed, 135 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs new file mode 100644 index 000000000..889f5d030 --- /dev/null +++ b/crates/assists/src/handlers/convert_integer_literal.rs | |||
@@ -0,0 +1,135 @@ | |||
1 | use syntax::{ast, AstNode, SmolStr}; | ||
2 | |||
3 | use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; | ||
4 | |||
5 | // Assist: convert_integer_literal | ||
6 | // | ||
7 | // Converts the base of integer literals to other bases. | ||
8 | // | ||
9 | // ``` | ||
10 | // const _: i32 = 10<|>; | ||
11 | // ``` | ||
12 | // -> | ||
13 | // ``` | ||
14 | // const _: i32 = 0b1010; | ||
15 | // ``` | ||
16 | pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
17 | let literal = ctx.find_node_at_offset::<ast::Literal>()?; | ||
18 | let range = literal.syntax().text_range(); | ||
19 | let group_id = GroupLabel("Convert integer base".into()); | ||
20 | |||
21 | let suffix = match literal.kind() { | ||
22 | ast::LiteralKind::IntNumber { suffix } => suffix, | ||
23 | _ => return None, | ||
24 | }; | ||
25 | let suffix_len = suffix.as_ref().map(|s| s.len()).unwrap_or(0); | ||
26 | let raw_literal_text = literal.syntax().to_string(); | ||
27 | |||
28 | // Gets the literal's text without the type suffix and without underscores. | ||
29 | let literal_text = raw_literal_text | ||
30 | .chars() | ||
31 | .take(raw_literal_text.len() - suffix_len) | ||
32 | .filter(|c| *c != '_') | ||
33 | .collect::<SmolStr>(); | ||
34 | let literal_base = IntegerLiteralBase::identify(&literal_text)?; | ||
35 | |||
36 | for base in IntegerLiteralBase::bases() { | ||
37 | if *base == literal_base { | ||
38 | continue; | ||
39 | } | ||
40 | |||
41 | let mut converted = literal_base.convert(&literal_text, base); | ||
42 | |||
43 | let label = if let Some(suffix) = &suffix { | ||
44 | format!("Convert {} ({}) to {}", &literal_text, suffix, &converted) | ||
45 | } else { | ||
46 | format!("Convert {} to {}", &literal_text, &converted) | ||
47 | }; | ||
48 | |||
49 | // Appends the type suffix back into the new literal if it exists. | ||
50 | if let Some(suffix) = &suffix { | ||
51 | converted.push_str(&suffix); | ||
52 | } | ||
53 | |||
54 | acc.add_group( | ||
55 | &group_id, | ||
56 | AssistId("convert_integer_literal", AssistKind::RefactorInline), | ||
57 | label, | ||
58 | range, | ||
59 | |builder| builder.replace(range, converted), | ||
60 | ); | ||
61 | } | ||
62 | |||
63 | Some(()) | ||
64 | } | ||
65 | |||
66 | #[derive(Debug, PartialEq, Eq)] | ||
67 | enum IntegerLiteralBase { | ||
68 | Binary, | ||
69 | Octal, | ||
70 | Decimal, | ||
71 | Hexadecimal, | ||
72 | } | ||
73 | |||
74 | impl IntegerLiteralBase { | ||
75 | fn identify(literal_text: &str) -> Option<Self> { | ||
76 | // We cannot express a literal in anything other than decimal in under 3 characters, so we return here if possible. | ||
77 | if literal_text.len() < 3 && literal_text.chars().all(|c| c.is_digit(10)) { | ||
78 | return Some(Self::Decimal); | ||
79 | } | ||
80 | |||
81 | let base = match &literal_text[..2] { | ||
82 | "0b" => Self::Binary, | ||
83 | "0o" => Self::Octal, | ||
84 | "0x" => Self::Hexadecimal, | ||
85 | _ => Self::Decimal, | ||
86 | }; | ||
87 | |||
88 | // Checks that all characters after the base prefix are all valid digits for that base. | ||
89 | if literal_text[base.prefix_len()..] | ||
90 | .chars() | ||
91 | .all(|c| c.is_digit(base.base())) | ||
92 | { | ||
93 | Some(base) | ||
94 | } else { | ||
95 | None | ||
96 | } | ||
97 | } | ||
98 | |||
99 | fn convert(&self, literal_text: &str, to: &IntegerLiteralBase) -> String { | ||
100 | let digits = &literal_text[self.prefix_len()..]; | ||
101 | let value = u128::from_str_radix(digits, self.base()).unwrap(); | ||
102 | |||
103 | match to { | ||
104 | Self::Binary => format!("0b{:b}", value), | ||
105 | Self::Octal => format!("0o{:o}", value), | ||
106 | Self::Decimal => value.to_string(), | ||
107 | Self::Hexadecimal => format!("0x{:X}", value), | ||
108 | } | ||
109 | } | ||
110 | |||
111 | const fn base(&self) -> u32 { | ||
112 | match self { | ||
113 | Self::Binary => 2, | ||
114 | Self::Octal => 8, | ||
115 | Self::Decimal => 10, | ||
116 | Self::Hexadecimal => 16, | ||
117 | } | ||
118 | } | ||
119 | |||
120 | const fn prefix_len(&self) -> usize { | ||
121 | match self { | ||
122 | Self::Decimal => 0, | ||
123 | _ => 2, | ||
124 | } | ||
125 | } | ||
126 | |||
127 | const fn bases() -> &'static [IntegerLiteralBase] { | ||
128 | &[ | ||
129 | IntegerLiteralBase::Binary, | ||
130 | IntegerLiteralBase::Octal, | ||
131 | IntegerLiteralBase::Decimal, | ||
132 | IntegerLiteralBase::Hexadecimal, | ||
133 | ] | ||
134 | } | ||
135 | } | ||