diff --git a/component-generated/src/action.rs b/component-generated/src/action.rs index 2830433..c8a3abb 100644 --- a/component-generated/src/action.rs +++ b/component-generated/src/action.rs @@ -1,3 +1,4 @@ +use crossterm::event::KeyEvent; use serde::{Deserialize, Serialize}; use strum::Display; @@ -8,6 +9,7 @@ pub enum Action { Resize(u16, u16), Suspend, Resume, + RawKeyEvent(KeyEvent), Quit, ClearScreen, Error(String), diff --git a/component-generated/src/app.rs b/component-generated/src/app.rs index 951ca7b..7c61bf5 100644 --- a/component-generated/src/app.rs +++ b/component-generated/src/app.rs @@ -110,21 +110,36 @@ impl App { let Some(keymap) = self.config.keybindings.get(&self.mode) else { return Ok(()); }; - match keymap.get(&vec![key]) { - Some(action) => { - info!("Got action: {action:?}"); - action_tx.send(action.clone())?; + if let Some(action) = keymap.get(&vec![key]) { + info!("Got action: {action:?}"); + // Look for components in editing mode + // and that should receive a raw key event instead of the action. + for component in &self.components { + // Is it in editing mode and is the action not in the escape list ? + if component.is_editing() && !component.escape_editing_mode().contains(action) { + info!("Action was sent as raw key"); + action_tx.send(Action::RawKeyEvent(key))?; + return Ok(()); + } + } + // Send the actual action to components + action_tx.send(action.clone())?; + } else { + // If there is a component in editing mode, send the raw key + if self.components.iter().any(|c| c.is_editing()) { + info!("Got raw key: {key:?}"); + action_tx.send(Action::RawKeyEvent(key))?; + return Ok(()); } - _ => { - // If the key was not handled as a single key action, - // then consider it for multi-key combinations. - self.last_tick_key_events.push(key); - // Check for multi-key combinations - if let Some(action) = keymap.get(&self.last_tick_key_events) { - info!("Got action: {action:?}"); - action_tx.send(action.clone())?; - } + // If the key was not handled as a single key action, + // then consider it for multi-key combinations. + self.last_tick_key_events.push(key); + + // Check for multi-key combinations + if let Some(action) = keymap.get(&self.last_tick_key_events) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; } } Ok(()) diff --git a/component-generated/src/components.rs b/component-generated/src/components.rs index 84c12c9..81ddd0b 100644 --- a/component-generated/src/components.rs +++ b/component-generated/src/components.rs @@ -55,6 +55,20 @@ pub trait Component { let _ = area; // to appease clippy Ok(()) } + /// Whether the app should send `Action::RawKeyEvent` or the corresponding `Action` variant. + /// + /// # Returns + /// * `bool` - Whether the component is waiting only for raw key events actions or not. + fn is_editing(&self) -> bool { + false + } + /// Returns zero or more `Action` that should never be sent as `Action::RawKeyEvent` to this component even if `Self::is_editing` returns `true`. + /// + /// # Returns + /// * `Vec` - A list of `Action` that should never be sent as `Action::RawKeyEvent`. + fn escape_editing_mode(&self) -> Vec { + vec![] + } /// Handle incoming events and produce actions if necessary. /// /// # Arguments diff --git a/component/template/src/action.rs b/component/template/src/action.rs index 2830433..c8a3abb 100644 --- a/component/template/src/action.rs +++ b/component/template/src/action.rs @@ -1,3 +1,4 @@ +use crossterm::event::KeyEvent; use serde::{Deserialize, Serialize}; use strum::Display; @@ -8,6 +9,7 @@ pub enum Action { Resize(u16, u16), Suspend, Resume, + RawKeyEvent(KeyEvent), Quit, ClearScreen, Error(String), diff --git a/component/template/src/app.rs b/component/template/src/app.rs index 951ca7b..7c61bf5 100644 --- a/component/template/src/app.rs +++ b/component/template/src/app.rs @@ -110,21 +110,36 @@ impl App { let Some(keymap) = self.config.keybindings.get(&self.mode) else { return Ok(()); }; - match keymap.get(&vec![key]) { - Some(action) => { - info!("Got action: {action:?}"); - action_tx.send(action.clone())?; + if let Some(action) = keymap.get(&vec![key]) { + info!("Got action: {action:?}"); + // Look for components in editing mode + // and that should receive a raw key event instead of the action. + for component in &self.components { + // Is it in editing mode and is the action not in the escape list ? + if component.is_editing() && !component.escape_editing_mode().contains(action) { + info!("Action was sent as raw key"); + action_tx.send(Action::RawKeyEvent(key))?; + return Ok(()); + } + } + // Send the actual action to components + action_tx.send(action.clone())?; + } else { + // If there is a component in editing mode, send the raw key + if self.components.iter().any(|c| c.is_editing()) { + info!("Got raw key: {key:?}"); + action_tx.send(Action::RawKeyEvent(key))?; + return Ok(()); } - _ => { - // If the key was not handled as a single key action, - // then consider it for multi-key combinations. - self.last_tick_key_events.push(key); - // Check for multi-key combinations - if let Some(action) = keymap.get(&self.last_tick_key_events) { - info!("Got action: {action:?}"); - action_tx.send(action.clone())?; - } + // If the key was not handled as a single key action, + // then consider it for multi-key combinations. + self.last_tick_key_events.push(key); + + // Check for multi-key combinations + if let Some(action) = keymap.get(&self.last_tick_key_events) { + info!("Got action: {action:?}"); + action_tx.send(action.clone())?; } } Ok(()) diff --git a/component/template/src/components.rs b/component/template/src/components.rs index 84c12c9..81ddd0b 100644 --- a/component/template/src/components.rs +++ b/component/template/src/components.rs @@ -55,6 +55,20 @@ pub trait Component { let _ = area; // to appease clippy Ok(()) } + /// Whether the app should send `Action::RawKeyEvent` or the corresponding `Action` variant. + /// + /// # Returns + /// * `bool` - Whether the component is waiting only for raw key events actions or not. + fn is_editing(&self) -> bool { + false + } + /// Returns zero or more `Action` that should never be sent as `Action::RawKeyEvent` to this component even if `Self::is_editing` returns `true`. + /// + /// # Returns + /// * `Vec` - A list of `Action` that should never be sent as `Action::RawKeyEvent`. + fn escape_editing_mode(&self) -> Vec { + vec![] + } /// Handle incoming events and produce actions if necessary. /// /// # Arguments