From dfa469b9eeabc51a5f24def0e8916866a632c30e Mon Sep 17 00:00:00 2001
From: Ben Van Sleen <benvansleen@gmail.com>
Date: Sat, 18 Nov 2023 13:52:50 -0500
Subject: [PATCH] Introduce `(KeyCode, KeyCode)` tuple that functions as ESC in
 Vi Insert mode (e.g. press "jk" to exit insert mode)

---
 examples/demo.rs        |  6 +++-
 src/edit_mode/vi/mod.rs | 66 +++++++++++++++++++++++++++++++++--------
 src/lib.rs              |  3 +-
 3 files changed, 61 insertions(+), 14 deletions(-)

diff --git a/examples/demo.rs b/examples/demo.rs
index 2e7a402e..8f39667d 100644
--- a/examples/demo.rs
+++ b/examples/demo.rs
@@ -114,7 +114,11 @@ fn main() -> std::io::Result<()> {
 
         add_newline_keybinding(&mut insert_keybindings);
 
-        Box::new(Vi::new(insert_keybindings, normal_keybindings))
+        Box::new(Vi::new(
+            insert_keybindings,
+            normal_keybindings,
+            (KeyCode::Null, KeyCode::Null),
+        ))
     } else {
         let mut keybindings = default_emacs_keybindings();
         add_menu_keybindings(&mut keybindings);
diff --git a/src/edit_mode/vi/mod.rs b/src/edit_mode/vi/mod.rs
index 4428c645..e713ff68 100644
--- a/src/edit_mode/vi/mod.rs
+++ b/src/edit_mode/vi/mod.rs
@@ -30,6 +30,8 @@ pub struct Vi {
     previous: Option<ReedlineEvent>,
     // last f, F, t, T motion for ; and ,
     last_char_search: Option<ViCharSearch>,
+    alternate_esc_seq: (KeyCode, KeyCode),
+    most_recent_keycode: KeyCode,
 }
 
 impl Default for Vi {
@@ -37,32 +39,75 @@ impl Default for Vi {
         Vi {
             insert_keybindings: default_vi_insert_keybindings(),
             normal_keybindings: default_vi_normal_keybindings(),
+            alternate_esc_seq: (KeyCode::Null, KeyCode::Null),
             cache: Vec::new(),
             mode: ViMode::Insert,
             previous: None,
             last_char_search: None,
+            most_recent_keycode: KeyCode::Null,
         }
     }
 }
 
 impl Vi {
     /// Creates Vi editor using defined keybindings
-    pub fn new(insert_keybindings: Keybindings, normal_keybindings: Keybindings) -> Self {
+    pub fn new(
+        insert_keybindings: Keybindings,
+        normal_keybindings: Keybindings,
+        alternate_esc_seq: (KeyCode, KeyCode),
+    ) -> Self {
         Self {
             insert_keybindings,
             normal_keybindings,
+            alternate_esc_seq,
             ..Default::default()
         }
     }
 }
 
+fn exit_insert_mode(editor: &mut Vi) -> ReedlineEvent {
+    editor.most_recent_keycode = KeyCode::Null;
+    editor.cache.clear();
+    editor.mode = ViMode::Normal;
+    ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint])
+}
+
 impl EditMode for Vi {
     fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent {
         match event.into() {
             Event::Key(KeyEvent {
                 code, modifiers, ..
-            }) => match (self.mode, modifiers, code) {
-                (ViMode::Normal, modifier, KeyCode::Char(c)) => {
+            }) => match (
+                self.mode,
+                modifiers,
+                code,
+                self.alternate_esc_seq,
+                self.most_recent_keycode,
+            ) {
+                (ViMode::Insert, KeyModifiers::NONE, code, (e1, e2), mr)
+                    if code == e2 && mr == e1 =>
+                {
+                    exit_insert_mode(self)
+                }
+                (ViMode::Insert, KeyModifiers::NONE, code, (e1, _), _) if code == e1 => {
+                    self.most_recent_keycode = code;
+                    ReedlineEvent::None
+                }
+                (
+                    ViMode::Insert,
+                    KeyModifiers::NONE,
+                    KeyCode::Char(code),
+                    (KeyCode::Char(e1), KeyCode::Char(e2)),
+                    KeyCode::Char(mr),
+                ) if code != e2 && mr == e1 => {
+                    self.most_recent_keycode = KeyCode::Char(code);
+                    ReedlineEvent::Multiple(vec![
+                        ReedlineEvent::Edit(vec![EditCommand::InsertChar(e1)]),
+                        ReedlineEvent::Edit(vec![EditCommand::InsertChar(code)]),
+                    ])
+                }
+
+                (ViMode::Normal, modifier, KeyCode::Char(c), _, _) => {
                     let c = c.to_ascii_lowercase();
 
                     if let Some(event) = self
@@ -97,7 +142,7 @@ impl EditMode for Vi {
                         ReedlineEvent::None
                     }
                 }
-                (ViMode::Insert, modifier, KeyCode::Char(c)) => {
+                (ViMode::Insert, modifier, KeyCode::Char(c), _, _) => {
                     // Note. The modifier can also be a combination of modifiers, for
                     // example:
                     //     KeyModifiers::CONTROL | KeyModifiers::ALT
@@ -122,6 +167,7 @@ impl EditMode for Vi {
                                         | KeyModifiers::ALT
                                         | KeyModifiers::SHIFT
                             {
+                                self.most_recent_keycode = KeyCode::Char(c);
                                 ReedlineEvent::Edit(vec![EditCommand::InsertChar(
                                     if modifier == KeyModifiers::SHIFT {
                                         c.to_ascii_uppercase()
@@ -134,20 +180,16 @@ impl EditMode for Vi {
                             }
                         })
                 }
-                (_, KeyModifiers::NONE, KeyCode::Esc) => {
-                    self.cache.clear();
-                    self.mode = ViMode::Normal;
-                    ReedlineEvent::Multiple(vec![ReedlineEvent::Esc, ReedlineEvent::Repaint])
-                }
-                (_, KeyModifiers::NONE, KeyCode::Enter) => {
+                (_, KeyModifiers::NONE, KeyCode::Esc, _, _) => exit_insert_mode(self),
+                (_, KeyModifiers::NONE, KeyCode::Enter, _, _) => {
                     self.mode = ViMode::Insert;
                     ReedlineEvent::Enter
                 }
-                (ViMode::Normal, _, _) => self
+                (ViMode::Normal, _, _, _, _) => self
                     .normal_keybindings
                     .find_binding(modifiers, code)
                     .unwrap_or(ReedlineEvent::None),
-                (ViMode::Insert, _, _) => self
+                (ViMode::Insert, _, _, _, _) => self
                     .insert_keybindings
                     .find_binding(modifiers, code)
                     .unwrap_or(ReedlineEvent::None),
diff --git a/src/lib.rs b/src/lib.rs
index ec31a401..702c18bf 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -166,12 +166,13 @@
 //! // Create a reedline object with custom edit mode
 //! // This can define a keybinding setting or enable vi-emulation
 //! use reedline::{
-//!     default_vi_insert_keybindings, default_vi_normal_keybindings, EditMode, Reedline, Vi,
+//!     default_vi_insert_keybindings, default_vi_normal_keybindings, EditMode, Reedline, Vi, KeyCode
 //! };
 //!
 //! let mut line_editor = Reedline::create().with_edit_mode(Box::new(Vi::new(
 //!     default_vi_insert_keybindings(),
 //!     default_vi_normal_keybindings(),
+//!     (KeyCode::Null, KeyCode::Null),
 //! )));
 //! ```
 //!