@@ -400,7 +400,8 @@
{
"context": "Editor && vim_mode == visual && !VimWaiting && !VimObject",
"bindings": {
- "u": "editor::Undo",
+ "u": "vim::ConvertToLowerCase",
+ "U": "vim::ConvertToUpperCase",
"o": "vim::OtherEnd",
"shift-o": "vim::OtherEnd",
"d": "vim::VisualDelete",
@@ -26,7 +26,7 @@ use log::error;
use workspace::Workspace;
use self::{
- case::change_case,
+ case::{change_case, convert_to_lower_case, convert_to_upper_case},
change::{change_motion, change_object},
delete::{delete_motion, delete_object},
yank::{yank_motion, yank_object},
@@ -48,6 +48,8 @@ actions!(
Yank,
YankLine,
ChangeCase,
+ ConvertToUpperCase,
+ ConvertToLowerCase,
JoinLines,
]
);
@@ -60,6 +62,8 @@ pub(crate) fn register(workspace: &mut Workspace, cx: &mut ViewContext<Workspace
workspace.register_action(insert_line_above);
workspace.register_action(insert_line_below);
workspace.register_action(change_case);
+ workspace.register_action(convert_to_upper_case);
+ workspace.register_action(convert_to_lower_case);
workspace.register_action(yank_line);
workspace.register_action(|_: &mut Workspace, _: &DeleteLeft, cx| {
@@ -3,9 +3,40 @@ use gpui::ViewContext;
use language::{Bias, Point};
use workspace::Workspace;
-use crate::{normal::ChangeCase, state::Mode, Vim};
+use crate::{
+ normal::ChangeCase, normal::ConvertToLowerCase, normal::ConvertToUpperCase, state::Mode, Vim,
+};
pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Workspace>) {
+ transform_case(cx, |c| {
+ if c.is_lowercase() {
+ c.to_uppercase().collect::<Vec<char>>()
+ } else {
+ c.to_lowercase().collect::<Vec<char>>()
+ }
+ })
+}
+
+pub fn convert_to_upper_case(
+ _: &mut Workspace,
+ _: &ConvertToUpperCase,
+ cx: &mut ViewContext<Workspace>,
+) {
+ transform_case(cx, |c| c.to_uppercase().collect::<Vec<char>>())
+}
+
+pub fn convert_to_lower_case(
+ _: &mut Workspace,
+ _: &ConvertToLowerCase,
+ cx: &mut ViewContext<Workspace>,
+) {
+ transform_case(cx, |c| c.to_lowercase().collect::<Vec<char>>())
+}
+
+fn transform_case<F>(cx: &mut ViewContext<Workspace>, transform: F)
+where
+ F: Fn(char) -> Vec<char> + Copy,
+{
Vim::update(cx, |vim, cx| {
vim.record_current_action(cx);
let count = vim.take_count(cx).unwrap_or(1) as u32;
@@ -54,13 +85,7 @@ pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Works
let text = snapshot
.text_for_range(range.start..range.end)
.flat_map(|s| s.chars())
- .flat_map(|c| {
- if c.is_lowercase() {
- c.to_uppercase().collect::<Vec<char>>()
- } else {
- c.to_lowercase().collect::<Vec<char>>()
- }
- })
+ .flat_map(|c| transform(c))
.collect::<String>();
buffer.edit([(range, text)], None, cx)
@@ -74,6 +99,7 @@ pub fn change_case(_: &mut Workspace, _: &ChangeCase, cx: &mut ViewContext<Works
vim.switch_mode(Mode::Normal, true, cx)
})
}
+
#[cfg(test)]
mod test {
use crate::{state::Mode, test::NeovimBackedTestContext};
@@ -113,4 +139,42 @@ mod test {
cx.simulate_keystroke("~");
cx.assert_state("aSSˇcdˇE\n", Mode::Normal);
}
+
+ #[gpui::test]
+ async fn test_convert_to_upper_case(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ // works in visual mode
+ cx.set_shared_state("a😀C«dÉ1*fˇ»\n").await;
+ cx.simulate_shared_keystrokes(["U"]).await;
+ cx.assert_shared_state("a😀CˇDÉ1*F\n").await;
+
+ // works with line selections
+ cx.set_shared_state("abˇC\n").await;
+ cx.simulate_shared_keystrokes(["shift-v", "U"]).await;
+ cx.assert_shared_state("ˇABC\n").await;
+
+ // works in visual block mode
+ cx.set_shared_state("ˇaa\nbb\ncc").await;
+ cx.simulate_shared_keystrokes(["ctrl-v", "j", "U"]).await;
+ cx.assert_shared_state("ˇAa\nBb\ncc").await;
+ }
+
+ #[gpui::test]
+ async fn test_convert_to_lower_case(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ // works in visual mode
+ cx.set_shared_state("A😀c«DÉ1*fˇ»\n").await;
+ cx.simulate_shared_keystrokes(["u"]).await;
+ cx.assert_shared_state("A😀cˇdé1*f\n").await;
+
+ // works with line selections
+ cx.set_shared_state("ABˇc\n").await;
+ cx.simulate_shared_keystrokes(["shift-v", "u"]).await;
+ cx.assert_shared_state("ˇabc\n").await;
+
+ // works in visual block mode
+ cx.set_shared_state("ˇAa\nBb\nCc").await;
+ cx.simulate_shared_keystrokes(["ctrl-v", "j", "u"]).await;
+ cx.assert_shared_state("ˇaa\nbb\nCc").await;
+ }
}
@@ -0,0 +1,12 @@
+{"Put":{"state":"A😀c«DÉ1*fˇ»\n"}}
+{"Key":"u"}
+{"Get":{"state":"A😀cˇdé1*f\n","mode":"Normal"}}
+{"Put":{"state":"ABˇc\n"}}
+{"Key":"shift-v"}
+{"Key":"u"}
+{"Get":{"state":"ˇabc\n","mode":"Normal"}}
+{"Put":{"state":"ˇAa\nBb\nCc"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"u"}
+{"Get":{"state":"ˇaa\nbb\nCc","mode":"Normal"}}
@@ -0,0 +1,12 @@
+{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}}
+{"Key":"U"}
+{"Get":{"state":"a😀CˇDÉ1*F\n","mode":"Normal"}}
+{"Put":{"state":"abˇC\n"}}
+{"Key":"shift-v"}
+{"Key":"U"}
+{"Get":{"state":"ˇABC\n","mode":"Normal"}}
+{"Put":{"state":"ˇaa\nbb\ncc"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"U"}
+{"Get":{"state":"ˇAa\nBb\ncc","mode":"Normal"}}