Repository: osa1/tiny (language: rust)

Issue:
String input key binding
Currently, there is a key action `input: CHAR` which adds the `CHAR` to input area when triggered. However, I would like to be able to input a list of chars (aka string) so that I could use, for example, `ctrl_l` to send `/clear`, or `ctrl_d` to send `/close`. I see no way to do this with actions available at the moment, but it does not seem to be hard to implement
String input key binding
Currently, there is a key action `input: CHAR` which adds the `CHAR` to input area when triggered. However, I would like to be able to input a list of chars (aka string) so that I could use, for example, `ctrl_l` to send `/clear`, or `ctrl_d` to send `/close`. I see no way to do this with actions available at the moment, but it does not seem to be hard to implement


Discussion / hints:

Fix patch:
diff --git a/crates/libtiny_tui/src/exit_dialogue.rs b/crates/libtiny_tui/src/exit_dialogue.rs
index bfc0ad55..bb17a463 100644
--- a/crates/libtiny_tui/src/exit_dialogue.rs
+++ b/crates/libtiny_tui/src/exit_dialogue.rs
@@ -48,7 +48,7 @@ impl ExitDialogue {
         }
     }
 
-    pub(crate) fn keypressed(&self, key_action: KeyAction) -> WidgetRet {
+    pub(crate) fn keypressed(&self, key_action: &KeyAction) -> WidgetRet {
         match key_action {
             KeyAction::Input('y') | KeyAction::InputSend => WidgetRet::Abort,
             _ => WidgetRet::Remove,
diff --git a/crates/libtiny_tui/src/input_area/mod.rs b/crates/libtiny_tui/src/input_area/mod.rs
index 475487ed..fd311ac9 100644
--- a/crates/libtiny_tui/src/input_area/mod.rs
+++ b/crates/libtiny_tui/src/input_area/mod.rs
@@ -211,7 +211,7 @@ impl InputArea {
         }
     }
 
-    pub(crate) fn keypressed(&mut self, key_action: KeyAction) -> WidgetRet {
+    pub(crate) fn keypressed(&mut self, key_action: &KeyAction) -> WidgetRet {
         match key_action {
             KeyAction::InputSend => {
                 if self.current_buffer_len() > 0 {
@@ -330,10 +330,11 @@ impl InputArea {
             }
             KeyAction::Input(ch) => {
                 self.modify();
-                self.buffer.insert(self.cursor as usize, ch);
+                self.buffer.insert(self.cursor as usize, *ch);
                 self.inc_cursor();
                 WidgetRet::KeyHandled
             }
+            KeyAction::RunCommand(cmd) => WidgetRet::Command(cmd.to_owned()),
             _ => WidgetRet::KeyIgnored,
         }
     }
@@ -777,18 +778,18 @@ mod tests {
     #[test]
     fn text_field_bug() {
         let mut text_field = InputArea::new(10, 50);
-        text_field.keypressed(KeyAction::Input('a'));
-        text_field.keypressed(KeyAction::Input(' '));
-        text_field.keypressed(KeyAction::Input('b'));
-        text_field.keypressed(KeyAction::Input(' '));
-        text_field.keypressed(KeyAction::Input('c'));
-        text_field.keypressed(KeyAction::InputSend);
-        text_field.keypressed(KeyAction::InputPrevEntry);
+        text_field.keypressed(&KeyAction::Input('a'));
+        text_field.keypressed(&KeyAction::Input(' '));
+        text_field.keypressed(&KeyAction::Input('b'));
+        text_field.keypressed(&KeyAction::Input(' '));
+        text_field.keypressed(&KeyAction::Input('c'));
+        text_field.keypressed(&KeyAction::InputSend);
+        text_field.keypressed(&KeyAction::InputPrevEntry);
         // this panics:
-        text_field.keypressed(KeyAction::InputMoveWordLeft);
+        text_field.keypressed(&KeyAction::InputMoveWordLeft);
         // a b ^c
         assert_eq!(text_field.cursor, 4);
-        text_field.keypressed(KeyAction::InputMoveWordRight);
+        text_field.keypressed(&KeyAction::InputMoveWordRight);
         assert_eq!(text_field.cursor, 5);
     }
 
diff --git a/crates/libtiny_tui/src/key_map.rs b/crates/libtiny_tui/src/key_map.rs
index febb0973..f6a452bc 100644
--- a/crates/libtiny_tui/src/key_map.rs
+++ b/crates/libtiny_tui/src/key_map.rs
@@ -8,7 +8,7 @@ use term_input::{Arrow, FKey, Key};
 #[derive(Debug, PartialEq)]
 pub(crate) struct KeyMap(HashMap<Key, KeyAction>);
 
-#[derive(Debug, Copy, Clone, Deserialize, PartialEq)]
+#[derive(Debug, Clone, Deserialize, PartialEq)]
 #[serde(rename_all = "snake_case")]
 pub(crate) enum KeyAction {
     Cancel,
@@ -31,6 +31,7 @@ pub(crate) enum KeyAction {
     MessagesScrollBottom,
 
     Input(char),
+    RunCommand(String),
     InputAutoComplete,
     InputNextEntry,
     InputPrevEntry,
@@ -67,6 +68,7 @@ impl Display for KeyAction {
             KeyAction::MessagesScrollTop => "messages_scroll_top",
             KeyAction::MessagesScrollBottom => "messages_scroll_bottom",
             KeyAction::Input(c) => return writeln!(f, "input_{}", c),
+            KeyAction::RunCommand(string) => return writeln!(f, "run_command_{}", string),
             KeyAction::InputAutoComplete => "input_auto_complete",
             KeyAction::InputNextEntry => "input_next_entry",
             KeyAction::InputPrevEntry => "input_prev_entry",
@@ -150,7 +152,7 @@ impl KeyMap {
     }
 
     pub(crate) fn load(&mut self, key_map: &KeyMap) {
-        self.0.extend(key_map.0.iter())
+        self.0.extend(key_map.0.clone().into_iter())
     }
 }
 
@@ -357,6 +359,8 @@ fn deser_keymap() {
       tab_goto: 1
     a: 
       input: b
+    b:
+      run_command: clear
     "#;
     let mut expect = KeyMap(HashMap::new());
     expect
@@ -364,6 +368,9 @@ fn deser_keymap() {
         .insert(Key::Ctrl('a'), KeyAction::InputMoveCursStart);
     expect.0.insert(Key::Ctrl('e'), KeyAction::TabGoto('1'));
     expect.0.insert(Key::Char('a'), KeyAction::Input('b'));
+    expect
+        .0
+        .insert(Key::Char('b'), KeyAction::RunCommand("clear".to_string()));
 
     let key_map: KeyMap = serde_yaml::from_str(s).unwrap();
     assert_eq!(expect, key_map);
diff --git a/crates/libtiny_tui/src/lib.rs b/crates/libtiny_tui/src/lib.rs
index efc2e985..8dea4535 100644
--- a/crates/libtiny_tui/src/lib.rs
+++ b/crates/libtiny_tui/src/lib.rs
@@ -178,6 +178,20 @@ async fn input_handler<S>(
                         return;
                     }
                     TUIRet::KeyHandled | TUIRet::KeyIgnored(_) | TUIRet::EventIgnored(_) => {}
+                    TUIRet::KeyCommand { cmd, from } => {
+                        let result = tui.borrow_mut().try_handle_cmd(&cmd, &from);
+                        match result {
+                            CmdResult::Ok => {}
+                            CmdResult::Continue => {
+                                snd_ev.try_send(Event::Cmd { cmd, source: from }).unwrap()
+                            }
+                            CmdResult::Quit(msg) => {
+                                snd_ev.try_send(Event::Abort { msg }).unwrap();
+                                let _ = snd_abort.try_send(());
+                                return;
+                            }
+                        }
+                    }
                     TUIRet::Input { msg, from } => {
                         if msg[0] == '/' {
                             // Handle TUI commands, send others to downstream
diff --git a/crates/libtiny_tui/src/messaging.rs b/crates/libtiny_tui/src/messaging.rs
index f81cf790..5634087a 100644
--- a/crates/libtiny_tui/src/messaging.rs
+++ b/crates/libtiny_tui/src/messaging.rs
@@ -130,7 +130,7 @@ impl MessagingUI {
         self.msg_area.draw(tb, colors, pos_x, pos_y);
     }
 
-    pub(crate) fn keypressed(&mut self, key_action: KeyAction) -> WidgetRet {
+    pub(crate) fn keypressed(&mut self, key_action: &KeyAction) -> WidgetRet {
         match key_action {
             KeyAction::Exit => {
                 self.toggle_exit_dialogue();
diff --git a/crates/libtiny_tui/src/tui.rs b/crates/libtiny_tui/src/tui.rs
index 081ce182..94195285 100644
--- a/crates/libtiny_tui/src/tui.rs
+++ b/crates/libtiny_tui/src/tui.rs
@@ -33,6 +33,11 @@ pub(crate) enum TUIRet {
     KeyIgnored(Key),
     EventIgnored(Event),
 
+    KeyCommand {
+        cmd: String,
+        from: MsgSource,
+    },
+
     /// INVARIANT: The vec will have at least one char.
     // Can't make MsgSource a ref because of this weird error:
     // https://users.rust-lang.org/t/borrow-checker-bug/5165
@@ -670,9 +675,13 @@ impl TUI {
         });
 
         if let Some(key_action) = key_action {
-            match self.tabs[self.active_idx].widget.keypressed(key_action) {
+            match self.tabs[self.active_idx].widget.keypressed(&key_action) {
                 WidgetRet::KeyHandled => TUIRet::KeyHandled,
                 WidgetRet::KeyIgnored => self.handle_keypress(key, key_action, rcv_editor_ret),
+                WidgetRet::Command(cmd) => TUIRet::KeyCommand {
+                    cmd,
+                    from: self.tabs[self.active_idx].src.clone(),
+                },
                 WidgetRet::Input(input) => TUIRet::Input {
                     msg: input,
                     from: self.tabs[self.active_idx].src.clone(),
diff --git a/crates/libtiny_tui/src/widget.rs b/crates/libtiny_tui/src/widget.rs
index 83debb07..f3a2b170 100644
--- a/crates/libtiny_tui/src/widget.rs
+++ b/crates/libtiny_tui/src/widget.rs
@@ -8,6 +8,9 @@ pub(crate) enum WidgetRet {
     /// An input is submitted.
     Input(Vec<char>),
 
+    /// A command is ran
+    Command(String),
+
     /// Remove the widget. E.g. close the tab, hide the dialogue etc.
     Remove,
 


Test patch: