diff --git a/Cargo.lock b/Cargo.lock index 60b2d49816..6ee81109be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5467,6 +5467,7 @@ dependencies = [ "aho-corasick", "anyhow", "assets", + "base64 0.22.1", "breadcrumbs", "buffer_diff", "client", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index c88d1d2c4c..813a8a9bc5 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -34,6 +34,7 @@ test-support = [ aho-corasick.workspace = true anyhow.workspace = true assets.workspace = true +base64.workspace = true breadcrumbs.workspace = true client.workspace = true clock.workspace = true diff --git a/crates/editor/src/actions.rs b/crates/editor/src/actions.rs index ccd6018652..01f52e7064 100644 --- a/crates/editor/src/actions.rs +++ b/crates/editor/src/actions.rs @@ -460,6 +460,10 @@ actions!( ConvertToRot13, /// Applies ROT47 cipher to selected text. ConvertToRot47, + /// Base64-encodes the selected text or word under cursor. + ConvertToBase64, + /// Base64-decodes the selected text or word under cursor. + ConvertFromBase64, /// Copies selected text to the clipboard. Copy, /// Copies selected text to the clipboard with leading/trailing whitespace trimmed. diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ea38200cfe..af8d6f6ccc 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -13362,6 +13362,35 @@ impl Editor { }) } + pub fn convert_to_base64( + &mut self, + _: &ConvertToBase64, + window: &mut Window, + cx: &mut Context, + ) { + use base64::Engine as _; + self.manipulate_text(window, cx, |text| { + base64::engine::general_purpose::STANDARD.encode(text) + }) + } + + pub fn convert_from_base64( + &mut self, + _: &ConvertFromBase64, + window: &mut Window, + cx: &mut Context, + ) { + use base64::Engine as _; + self.manipulate_text( + window, + cx, + |text| match base64::engine::general_purpose::STANDARD.decode(text) { + Ok(bytes) => String::from_utf8(bytes).unwrap_or_else(|_| text.to_string()), + Err(_) => text.to_string(), + }, + ) + } + fn manipulate_text(&mut self, window: &mut Window, cx: &mut Context, mut callback: Fn) where Fn: FnMut(&str) -> String, diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 03f332821a..304f44d3c3 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -6864,6 +6864,40 @@ async fn test_convert_to_sentence_case(cx: &mut TestAppContext) { "}); } +#[gpui::test] +async fn test_convert_to_base64(cx: &mut TestAppContext) { + init_test(cx, |_| {}); + + let mut cx = EditorTestContext::new(cx).await; + + // Encode a plain text selection + cx.set_state(indoc! {" + «helloˇ» + "}); + cx.update_editor(|e, window, cx| e.convert_to_base64(&ConvertToBase64, window, cx)); + cx.assert_editor_state(indoc! {" + «aGVsbG8=ˇ» + "}); + + // Decode a valid base64 selection + cx.set_state(indoc! {" + «aGVsbG8=ˇ» + "}); + cx.update_editor(|e, window, cx| e.convert_from_base64(&ConvertFromBase64, window, cx)); + cx.assert_editor_state(indoc! {" + «helloˇ» + "}); + + // Decode invalid base64 — should leave text unchanged + cx.set_state(indoc! {" + «not!!!ˇ» + "}); + cx.update_editor(|e, window, cx| e.convert_from_base64(&ConvertFromBase64, window, cx)); + cx.assert_editor_state(indoc! {" + «not!!!ˇ» + "}); +} + #[gpui::test] async fn test_manipulate_text(cx: &mut TestAppContext) { init_test(cx, |_| {}); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index a8c0d443d7..9db33bb9ba 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -555,6 +555,8 @@ impl EditorElement { register_action(editor, window, Editor::toggle_case); register_action(editor, window, Editor::convert_to_rot13); register_action(editor, window, Editor::convert_to_rot47); + register_action(editor, window, Editor::convert_to_base64); + register_action(editor, window, Editor::convert_from_base64); register_action(editor, window, Editor::delete_to_previous_word_start); register_action(editor, window, Editor::delete_to_previous_subword_start); register_action(editor, window, Editor::delete_to_next_word_end);