Detailed changes
@@ -227,6 +227,8 @@
"g u": "vim::PushLowercase",
"g shift-u": "vim::PushUppercase",
"g ~": "vim::PushOppositeCase",
+ "g ?": "vim::PushRot13",
+ // "g ?": "vim::PushRot47",
"\"": "vim::PushRegister",
"g w": "vim::PushRewrap",
"g q": "vim::PushRewrap",
@@ -298,6 +300,8 @@
"g r": ["vim::Paste", { "preserve_clipboard": true }],
"g c": "vim::ToggleComments",
"g q": "vim::Rewrap",
+ "g ?": "vim::ConvertToRot13",
+ // "g ?": "vim::ConvertToRot47",
"\"": "vim::PushRegister",
// tree-sitter related commands
"[ x": "editor::SelectLargerSyntaxNode",
@@ -477,6 +481,13 @@
"~": "vim::CurrentLine"
}
},
+ {
+ "context": "vim_operator == g?",
+ "bindings": {
+ "g ?": "vim::CurrentLine",
+ "?": "vim::CurrentLine"
+ }
+ },
{
"context": "vim_operator == gq",
"bindings": {
@@ -274,6 +274,8 @@ actions!(
ConvertToTitleCase,
ConvertToUpperCamelCase,
ConvertToUpperCase,
+ ConvertToRot13,
+ ConvertToRot47,
Copy,
CopyAndTrim,
CopyFileLocation,
@@ -9168,6 +9168,42 @@ impl Editor {
})
}
+ pub fn convert_to_rot13(
+ &mut self,
+ _: &ConvertToRot13,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.manipulate_text(window, cx, |text| {
+ text.chars()
+ .map(|c| match c {
+ 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
+ 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
+ _ => c,
+ })
+ .collect()
+ })
+ }
+
+ pub fn convert_to_rot47(
+ &mut self,
+ _: &ConvertToRot47,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.manipulate_text(window, cx, |text| {
+ text.chars()
+ .map(|c| {
+ let code_point = c as u32;
+ if code_point >= 33 && code_point <= 126 {
+ return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
+ }
+ c
+ })
+ .collect()
+ })
+ }
+
fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
where
Fn: FnMut(&str) -> String,
@@ -223,6 +223,8 @@ impl EditorElement {
register_action(editor, window, Editor::convert_to_upper_camel_case);
register_action(editor, window, Editor::convert_to_lower_camel_case);
register_action(editor, window, Editor::convert_to_opposite_case);
+ register_action(editor, window, Editor::convert_to_rot13);
+ register_action(editor, window, Editor::convert_to_rot47);
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);
@@ -412,7 +412,7 @@ fn is_identifier_char(c: char) -> bool {
}
fn is_vim_operator_char(c: char) -> bool {
- c == '>' || c == '<' || c == '~' || c == '"'
+ c == '>' || c == '<' || c == '~' || c == '"' || c == '?'
}
fn skip_whitespace(source: &str) -> &str {
@@ -1,5 +1,5 @@
-mod case;
mod change;
+mod convert;
mod delete;
mod increment;
pub(crate) mod mark;
@@ -22,8 +22,8 @@ use crate::{
state::{Mark, Mode, Operator},
surrounds::SurroundsType,
};
-use case::CaseTarget;
use collections::BTreeSet;
+use convert::ConvertTarget;
use editor::Anchor;
use editor::Bias;
use editor::Editor;
@@ -55,6 +55,8 @@ actions!(
ChangeCase,
ConvertToUpperCase,
ConvertToLowerCase,
+ ConvertToRot13,
+ ConvertToRot47,
ToggleComments,
ShowLocation,
Undo,
@@ -73,6 +75,8 @@ pub(crate) fn register(editor: &mut Editor, cx: &mut Context<Vim>) {
Vim::action(editor, cx, Vim::change_case);
Vim::action(editor, cx, Vim::convert_to_upper_case);
Vim::action(editor, cx, Vim::convert_to_lower_case);
+ Vim::action(editor, cx, Vim::convert_to_rot13);
+ Vim::action(editor, cx, Vim::convert_to_rot47);
Vim::action(editor, cx, Vim::yank_line);
Vim::action(editor, cx, Vim::toggle_comments);
Vim::action(editor, cx, Vim::paste);
@@ -171,13 +175,19 @@ impl Vim {
}
Some(Operator::ShellCommand) => self.shell_command_motion(motion, times, window, cx),
Some(Operator::Lowercase) => {
- self.change_case_motion(motion, times, CaseTarget::Lowercase, window, cx)
+ self.convert_motion(motion, times, ConvertTarget::LowerCase, window, cx)
}
Some(Operator::Uppercase) => {
- self.change_case_motion(motion, times, CaseTarget::Uppercase, window, cx)
+ self.convert_motion(motion, times, ConvertTarget::UpperCase, window, cx)
}
Some(Operator::OppositeCase) => {
- self.change_case_motion(motion, times, CaseTarget::OppositeCase, window, cx)
+ self.convert_motion(motion, times, ConvertTarget::OppositeCase, window, cx)
+ }
+ Some(Operator::Rot13) => {
+ self.convert_motion(motion, times, ConvertTarget::Rot13, window, cx)
+ }
+ Some(Operator::Rot47) => {
+ self.convert_motion(motion, times, ConvertTarget::Rot47, window, cx)
}
Some(Operator::ToggleComments) => {
self.toggle_comments_motion(motion, times, window, cx)
@@ -216,13 +226,19 @@ impl Vim {
}
Some(Operator::Rewrap) => self.rewrap_object(object, around, window, cx),
Some(Operator::Lowercase) => {
- self.change_case_object(object, around, CaseTarget::Lowercase, window, cx)
+ self.convert_object(object, around, ConvertTarget::LowerCase, window, cx)
}
Some(Operator::Uppercase) => {
- self.change_case_object(object, around, CaseTarget::Uppercase, window, cx)
+ self.convert_object(object, around, ConvertTarget::UpperCase, window, cx)
}
Some(Operator::OppositeCase) => {
- self.change_case_object(object, around, CaseTarget::OppositeCase, window, cx)
+ self.convert_object(object, around, ConvertTarget::OppositeCase, window, cx)
+ }
+ Some(Operator::Rot13) => {
+ self.convert_object(object, around, ConvertTarget::Rot13, window, cx)
+ }
+ Some(Operator::Rot47) => {
+ self.convert_object(object, around, ConvertTarget::Rot47, window, cx)
}
Some(Operator::AddSurrounds { target: None }) => {
waiting_operator = Some(Operator::AddSurrounds {
@@ -7,23 +7,25 @@ use multi_buffer::MultiBufferRow;
use crate::{
Vim,
motion::Motion,
- normal::{ChangeCase, ConvertToLowerCase, ConvertToUpperCase},
+ normal::{ChangeCase, ConvertToLowerCase, ConvertToRot13, ConvertToRot47, ConvertToUpperCase},
object::Object,
state::Mode,
};
-pub enum CaseTarget {
- Lowercase,
- Uppercase,
+pub enum ConvertTarget {
+ LowerCase,
+ UpperCase,
OppositeCase,
+ Rot13,
+ Rot47,
}
impl Vim {
- pub fn change_case_motion(
+ pub fn convert_motion(
&mut self,
motion: Motion,
times: Option<usize>,
- mode: CaseTarget,
+ mode: ConvertTarget,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -41,15 +43,21 @@ impl Vim {
});
});
match mode {
- CaseTarget::Lowercase => {
+ ConvertTarget::LowerCase => {
editor.convert_to_lower_case(&Default::default(), window, cx)
}
- CaseTarget::Uppercase => {
+ ConvertTarget::UpperCase => {
editor.convert_to_upper_case(&Default::default(), window, cx)
}
- CaseTarget::OppositeCase => {
+ ConvertTarget::OppositeCase => {
editor.convert_to_opposite_case(&Default::default(), window, cx)
}
+ ConvertTarget::Rot13 => {
+ editor.convert_to_rot13(&Default::default(), window, cx)
+ }
+ ConvertTarget::Rot47 => {
+ editor.convert_to_rot47(&Default::default(), window, cx)
+ }
}
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
@@ -62,11 +70,11 @@ impl Vim {
});
}
- pub fn change_case_object(
+ pub fn convert_object(
&mut self,
object: Object,
around: bool,
- mode: CaseTarget,
+ mode: ConvertTarget,
window: &mut Window,
cx: &mut Context<Self>,
) {
@@ -85,15 +93,21 @@ impl Vim {
});
});
match mode {
- CaseTarget::Lowercase => {
+ ConvertTarget::LowerCase => {
editor.convert_to_lower_case(&Default::default(), window, cx)
}
- CaseTarget::Uppercase => {
+ ConvertTarget::UpperCase => {
editor.convert_to_upper_case(&Default::default(), window, cx)
}
- CaseTarget::OppositeCase => {
+ ConvertTarget::OppositeCase => {
editor.convert_to_opposite_case(&Default::default(), window, cx)
}
+ ConvertTarget::Rot13 => {
+ editor.convert_to_rot13(&Default::default(), window, cx)
+ }
+ ConvertTarget::Rot47 => {
+ editor.convert_to_rot47(&Default::default(), window, cx)
+ }
}
editor.change_selections(None, window, cx, |s| {
s.move_with(|map, selection| {
@@ -134,6 +148,36 @@ impl Vim {
self.manipulate_text(window, cx, |c| c.to_lowercase().collect::<Vec<char>>())
}
+ pub fn convert_to_rot13(
+ &mut self,
+ _: &ConvertToRot13,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.manipulate_text(window, cx, |c| {
+ vec![match c {
+ 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
+ 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
+ _ => c,
+ }]
+ })
+ }
+
+ pub fn convert_to_rot47(
+ &mut self,
+ _: &ConvertToRot47,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.manipulate_text(window, cx, |c| {
+ let code_point = c as u32;
+ if code_point >= 33 && code_point <= 126 {
+ return vec![char::from_u32(33 + ((code_point + 14) % 94)).unwrap()];
+ }
+ vec![c]
+ })
+ }
+
fn manipulate_text<F>(&mut self, window: &mut Window, cx: &mut Context<Self>, transform: F)
where
F: Fn(char) -> Vec<char> + Copy,
@@ -308,4 +352,60 @@ mod test {
cx.simulate_shared_keystrokes("g shift-u i w").await;
cx.shared_state().await.assert_eq("abc ˇDEF\n");
}
+
+ #[gpui::test]
+ async fn test_convert_to_rot13(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("g ?").await;
+ cx.shared_state().await.assert_eq("a😀CˇqÉ1*s\n");
+
+ // works with line selections
+ cx.set_shared_state("abˇC\n").await;
+ cx.simulate_shared_keystrokes("shift-v g ?").await;
+ cx.shared_state().await.assert_eq("ˇnoP\n");
+
+ // works in visual block mode
+ cx.set_shared_state("ˇaa\nbb\ncc").await;
+ cx.simulate_shared_keystrokes("ctrl-v j g ?").await;
+ cx.shared_state().await.assert_eq("ˇna\nob\ncc");
+ }
+
+ #[gpui::test]
+ async fn test_change_rot13_motion(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.set_shared_state("ˇabc def").await;
+ cx.simulate_shared_keystrokes("g ? w").await;
+ cx.shared_state().await.assert_eq("ˇnop def");
+
+ cx.simulate_shared_keystrokes("g ? w").await;
+ cx.shared_state().await.assert_eq("ˇabc def");
+
+ cx.simulate_shared_keystrokes(".").await;
+ cx.shared_state().await.assert_eq("ˇnop def");
+
+ cx.set_shared_state("abˇc def").await;
+ cx.simulate_shared_keystrokes("g ? i w").await;
+ cx.shared_state().await.assert_eq("ˇnop def");
+
+ cx.simulate_shared_keystrokes(".").await;
+ cx.shared_state().await.assert_eq("ˇabc def");
+
+ cx.simulate_shared_keystrokes("g ? $").await;
+ cx.shared_state().await.assert_eq("ˇnop qrs");
+ }
+
+ #[gpui::test]
+ async fn test_change_rot13_object(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+
+ cx.set_shared_state("ˇabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+ .await;
+ cx.simulate_shared_keystrokes("g ? i w").await;
+ cx.shared_state()
+ .await
+ .assert_eq("ˇnopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM");
+ }
}
@@ -116,6 +116,8 @@ pub enum Operator {
Lowercase,
Uppercase,
OppositeCase,
+ Rot13,
+ Rot47,
Digraph {
first_char: Option<char>,
},
@@ -958,6 +960,8 @@ impl Operator {
Operator::Uppercase => "gU",
Operator::Lowercase => "gu",
Operator::OppositeCase => "g~",
+ Operator::Rot13 => "g?",
+ Operator::Rot47 => "g?",
Operator::Register => "\"",
Operator::RecordRegister => "q",
Operator::ReplayRegister => "@",
@@ -1006,6 +1010,8 @@ impl Operator {
| Operator::ShellCommand
| Operator::Lowercase
| Operator::Uppercase
+ | Operator::Rot13
+ | Operator::Rot47
| Operator::ReplaceWithRegister
| Operator::Exchange
| Operator::Object { .. }
@@ -1026,6 +1032,8 @@ impl Operator {
| Operator::Lowercase
| Operator::Uppercase
| Operator::OppositeCase
+ | Operator::Rot13
+ | Operator::Rot47
| Operator::ToggleComments
| Operator::ReplaceWithRegister
| Operator::Rewrap
@@ -153,6 +153,8 @@ actions!(
PushLowercase,
PushUppercase,
PushOppositeCase,
+ PushRot13,
+ PushRot47,
ToggleRegistersView,
PushRegister,
PushRecordRegister,
@@ -619,6 +621,14 @@ impl Vim {
vim.push_operator(Operator::OppositeCase, window, cx)
});
+ Vim::action(editor, cx, |vim, _: &PushRot13, window, cx| {
+ vim.push_operator(Operator::Rot13, window, cx)
+ });
+
+ Vim::action(editor, cx, |vim, _: &PushRot47, window, cx| {
+ vim.push_operator(Operator::Rot47, window, cx)
+ });
+
Vim::action(editor, cx, |vim, _: &PushRegister, window, cx| {
vim.push_operator(Operator::Register, window, cx)
});
@@ -0,0 +1,23 @@
+{"Put":{"state":"ˇabc def"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Key":"w"}
+{"Get":{"state":"ˇnop def","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Key":"w"}
+{"Get":{"state":"ˇabc def","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"ˇnop def","mode":"Normal"}}
+{"Put":{"state":"abˇc def"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"ˇnop def","mode":"Normal"}}
+{"Key":"."}
+{"Get":{"state":"ˇabc def","mode":"Normal"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Key":"$"}
+{"Get":{"state":"ˇnop qrs","mode":"Normal"}}
@@ -0,0 +1,6 @@
+{"Put":{"state":"ˇabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Key":"i"}
+{"Key":"w"}
+{"Get":{"state":"ˇnopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM","mode":"Normal"}}
@@ -0,0 +1,15 @@
+{"Put":{"state":"a😀C«dÉ1*fˇ»\n"}}
+{"Key":"g"}
+{"Key":"?"}
+{"Get":{"state":"a😀CˇqÉ1*s\n","mode":"Normal"}}
+{"Put":{"state":"abˇC\n"}}
+{"Key":"shift-v"}
+{"Key":"g"}
+{"Key":"?"}
+{"Get":{"state":"ˇnoP\n","mode":"Normal"}}
+{"Put":{"state":"ˇaa\nbb\ncc"}}
+{"Key":"ctrl-v"}
+{"Key":"j"}
+{"Key":"g"}
+{"Key":"?"}
+{"Get":{"state":"ˇna\nob\ncc","mode":"Normal"}}