Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions crates/ide-assists/src/handlers/convert_char_literal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use syntax::{AstToken, ast};

use crate::{AssistContext, AssistId, Assists, GroupLabel};

// Assist: convert_char_literal
//
// Converts character literals between different representations. Currently supports normal character -> ASCII / Unicode escape.
// ```
// const _: char = 'a'$0;
// ```
// ->
// ```
// const _: char = '\x61';
// ```
pub(crate) fn convert_char_literal(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
if !ctx.has_empty_selection() {
return None;
}

let literal = ctx.find_node_at_offset::<ast::Literal>()?;
let literal = match literal.kind() {
ast::LiteralKind::Char(it) => it,
_ => return None,
};

let value = literal.value().ok()?;
let text = literal.syntax().text().to_owned();
let range = literal.syntax().text_range();
let group_id = GroupLabel("Convert char representation".into());

let mut add_assist = |converted: String| {
// Skip no-op assists (e.g. `'const C: char = '\\x61';'` already matches the ASCII form).
if converted == text {
return;
}
let label = format!("Convert {text} to {converted}");
acc.add_group(
&group_id,
AssistId::refactor_rewrite("convert_char_literal"),
label,
range,
|builder| builder.replace(range, converted),
);
};

if value.is_ascii() {
add_assist(format!("'\\x{:02x}'", value as u32));
}

add_assist(format!("'\\u{{{:x}}}'", value as u32));

Some(())
}

#[cfg(test)]
mod tests {
use crate::tests::check_assist_by_label;

use super::convert_char_literal;

#[test]
fn ascii_char_to_ascii_and_unicode() {
let before = "const _: char = 'a'$0;";
check_assist_by_label(
convert_char_literal,
before,
"const _: char = '\\x61';",
"Convert 'a' to '\\x61'",
);
check_assist_by_label(
convert_char_literal,
before,
"const _: char = '\\u{61}';",
"Convert 'a' to '\\u{61}'",
);
}

#[test]
fn non_ascii_char_only_unicode() {
check_assist_by_label(
convert_char_literal,
"const _: char = '😀'$0;",
"const _: char = '\\u{1f600}';",
"Convert '😀' to '\\u{1f600}'",
);
}

#[test]
fn ascii_escape_can_convert_to_unicode() {
check_assist_by_label(
convert_char_literal,
"const _: char = '\\x61'$0;",
"const _: char = '\\u{61}';",
"Convert '\\x61' to '\\u{61}'",
);
}
}
2 changes: 2 additions & 0 deletions crates/ide-assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ mod handlers {
mod change_visibility;
mod convert_bool_then;
mod convert_bool_to_enum;
mod convert_char_literal;
mod convert_closure_to_fn;
mod convert_comment_block;
mod convert_comment_from_or_to_doc;
Expand Down Expand Up @@ -256,6 +257,7 @@ mod handlers {
convert_bool_then::convert_bool_then_to_if,
convert_bool_then::convert_if_to_bool_then,
convert_bool_to_enum::convert_bool_to_enum,
convert_char_literal::convert_char_literal,
convert_closure_to_fn::convert_closure_to_fn,
convert_comment_block::convert_comment_block,
convert_comment_from_or_to_doc::convert_comment_from_or_to_doc,
Expand Down
13 changes: 13 additions & 0 deletions crates/ide-assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,19 @@ fn main() {
)
}

#[test]
fn doctest_convert_char_literal() {
check_doc_test(
"convert_char_literal",
r#####"
const _: char = 'a'$0;
"#####,
r#####"
const _: char = '\x61';
"#####,
)
}

#[test]
fn doctest_convert_closure_to_fn() {
check_doc_test(
Expand Down