aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers')
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs135
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 @@
1use syntax::{ast, AstNode, SmolStr};
2
3use 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// ```
16pub(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)]
67enum IntegerLiteralBase {
68 Binary,
69 Octal,
70 Decimal,
71 Hexadecimal,
72}
73
74impl 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}