From 92eb2eac85723b5da98a4362c8baa6c9288f69fc Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Sat, 22 Nov 2025 12:33:44 +0800 Subject: [PATCH 1/3] feat: add assist to convert char literal --- .../src/handlers/convert_char_literal.rs | 46 +++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + 2 files changed, 48 insertions(+) create mode 100644 crates/ide-assists/src/handlers/convert_char_literal.rs diff --git a/crates/ide-assists/src/handlers/convert_char_literal.rs b/crates/ide-assists/src/handlers/convert_char_literal.rs new file mode 100644 index 000000000000..7269ade006b4 --- /dev/null +++ b/crates/ide-assists/src/handlers/convert_char_literal.rs @@ -0,0 +1,46 @@ +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. +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::()?; + let literal = match literal.kind() { + ast::LiteralKind::Char(it) => it, + _ => return None, + }; + + let value = literal.value().ok()?; + let text = literal.syntax().text().to_string(); + 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(()) +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 4b4aa9427955..ca468905fb6b 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -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; @@ -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, From 37217aed5aea5ad3993a8df0727b616136b61a9d Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Sat, 22 Nov 2025 12:40:10 +0800 Subject: [PATCH 2/3] test: add test case for convert_char_literal assist --- .../src/handlers/convert_char_literal.rs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/ide-assists/src/handlers/convert_char_literal.rs b/crates/ide-assists/src/handlers/convert_char_literal.rs index 7269ade006b4..1ab01637526b 100644 --- a/crates/ide-assists/src/handlers/convert_char_literal.rs +++ b/crates/ide-assists/src/handlers/convert_char_literal.rs @@ -44,3 +44,47 @@ pub(crate) fn convert_char_literal(acc: &mut Assists, ctx: &AssistContext<'_>) - 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}'", + ); + } +} From 8169446bf6f861945279c375d15ea94eb143fe43 Mon Sep 17 00:00:00 2001 From: Young-Flash Date: Sat, 22 Nov 2025 12:54:03 +0800 Subject: [PATCH 3/3] internal: update codegen assists-doc-tests & make clippy happy --- .../src/handlers/convert_char_literal.rs | 9 ++++++++- crates/ide-assists/src/tests/generated.rs | 13 +++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/ide-assists/src/handlers/convert_char_literal.rs b/crates/ide-assists/src/handlers/convert_char_literal.rs index 1ab01637526b..0a50ba86ba6f 100644 --- a/crates/ide-assists/src/handlers/convert_char_literal.rs +++ b/crates/ide-assists/src/handlers/convert_char_literal.rs @@ -5,6 +5,13 @@ 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; @@ -17,7 +24,7 @@ pub(crate) fn convert_char_literal(acc: &mut Assists, ctx: &AssistContext<'_>) - }; let value = literal.value().ok()?; - let text = literal.syntax().text().to_string(); + let text = literal.syntax().text().to_owned(); let range = literal.syntax().text_range(); let group_id = GroupLabel("Convert char representation".into()); diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 160b31af0ae9..7eef257b95f1 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -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(