diff options
author | Christian Duerr <contact@christianduerr.com> | 2020-03-18 02:35:08 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-18 02:35:08 +0000 |
commit | 1a8cd172e520e493bacc9c6a2ae6f80de086eaa3 (patch) | |
tree | 0b837f1f52f72fe00e258afc34094d60b5d18f04 /alacritty/src/config/bindings.rs | |
parent | 64db7d3daaed4e06fb8292227622bbc4cdaa2cf0 (diff) | |
download | r-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.tar.gz r-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.tar.bz2 r-alacritty-1a8cd172e520e493bacc9c6a2ae6f80de086eaa3.zip |
Add modal keyboard motion mode
This implements a basic mode for navigating inside of Alacritty's
history with keyboard bindings. They're bound by default to vi's motion
shortcuts but are fully customizable. Since this relies on key bindings
only single key bindings are currently supported (so no `ge`, or
repetition).
Other than navigating the history and moving the viewport, this mode
should enable making use of all available selection modes to copy
content to the clipboard and launch URLs below the cursor.
This also changes the rendering of the block cursor at the side of
selections, since previously it could be inverted to be completely
invisible. Since that would have caused some troubles with this keyboard
selection mode, the block cursor now is no longer inverted when it is at
the edges of a selection.
Fixes #262.
Diffstat (limited to 'alacritty/src/config/bindings.rs')
-rw-r--r-- | alacritty/src/config/bindings.rs | 417 |
1 files changed, 264 insertions, 153 deletions
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs index 48a1b9fb..7485cd54 100644 --- a/alacritty/src/config/bindings.rs +++ b/alacritty/src/config/bindings.rs @@ -13,18 +13,18 @@ // limitations under the License. #![allow(clippy::enum_glob_use)] -use std::fmt; +use std::fmt::{self, Debug, Display}; use std::str::FromStr; use glutin::event::VirtualKeyCode::*; use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode}; -use log::error; use serde::de::Error as SerdeError; use serde::de::{self, MapAccess, Unexpected, Visitor}; use serde::{Deserialize, Deserializer}; +use serde_yaml::Value as SerdeValue; -use alacritty_terminal::config::LOG_TARGET_CONFIG; use alacritty_terminal::term::TermMode; +use alacritty_terminal::vi_mode::ViMotion; /// Describes a state and action to take in that state /// @@ -55,30 +55,6 @@ pub type KeyBinding = Binding<Key>; /// Bindings that are triggered by a mouse button pub type MouseBinding = Binding<MouseButton>; -impl Default for KeyBinding { - fn default() -> KeyBinding { - KeyBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: Key::Keycode(A), - } - } -} - -impl Default for MouseBinding { - fn default() -> MouseBinding { - MouseBinding { - mods: Default::default(), - action: Action::Esc(String::new()), - mode: TermMode::NONE, - notmode: TermMode::NONE, - trigger: MouseButton::Left, - } - } -} - impl<T: Eq> Binding<T> { #[inline] pub fn is_triggered_by(&self, mode: TermMode, mods: ModifiersState, input: &T) -> bool { @@ -117,6 +93,18 @@ pub enum Action { #[serde(skip)] Esc(String), + /// Run given command. + #[serde(skip)] + Command(String, Vec<String>), + + /// Move vi mode cursor. + #[serde(skip)] + ViMotion(ViMotion), + + /// Perform vi mode action. + #[serde(skip)] + ViAction(ViAction), + /// Paste contents of system clipboard. Paste, @@ -141,6 +129,12 @@ pub enum Action { /// Scroll exactly one page down. ScrollPageDown, + /// Scroll half a page up. + ScrollHalfPageUp, + + /// Scroll half a page down. + ScrollHalfPageDown, + /// Scroll one line up. ScrollLineUp, @@ -156,10 +150,6 @@ pub enum Action { /// Clear the display buffer(s) to remove history. ClearHistory, - /// Run given command. - #[serde(skip)] - Command(String, Vec<String>), - /// Hide the Alacritty window. Hide, @@ -182,6 +172,12 @@ pub enum Action { #[cfg(target_os = "macos")] ToggleSimpleFullscreen, + /// Clear active selection. + ClearSelection, + + /// Toggle vi mode. + ToggleViMode, + /// Allow receiving char input. ReceiveChar, @@ -189,18 +185,50 @@ pub enum Action { None, } -impl Default for Action { - fn default() -> Action { - Action::None - } -} - impl From<&'static str> for Action { fn from(s: &'static str) -> Action { Action::Esc(s.into()) } } +/// Display trait used for error logging. +impl Display for Action { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Action::ViMotion(motion) => motion.fmt(f), + Action::ViAction(action) => action.fmt(f), + _ => write!(f, "{:?}", self), + } + } +} + +/// Vi mode specific actions. +#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] +pub enum ViAction { + /// Toggle normal vi selection. + ToggleNormalSelection, + /// Toggle line vi selection. + ToggleLineSelection, + /// Toggle block vi selection. + ToggleBlockSelection, + /// Toggle semantic vi selection. + ToggleSemanticSelection, + /// Launch the URL below the vi mode cursor. + Open, +} + +impl From<ViAction> for Action { + fn from(action: ViAction) -> Self { + Self::ViAction(action) + } +} + +impl From<ViMotion> for Action { + fn from(motion: ViMotion) -> Self { + Self::ViMotion(motion) + } +} + macro_rules! bindings { ( KeyBinding; @@ -241,16 +269,16 @@ macro_rules! bindings { let mut _mods = ModifiersState::empty(); $(_mods = $mods;)* let mut _mode = TermMode::empty(); - $(_mode = $mode;)* + $(_mode.insert($mode);)* let mut _notmode = TermMode::empty(); - $(_notmode = $notmode;)* + $(_notmode.insert($notmode);)* v.push($ty { trigger: $key, mods: _mods, mode: _mode, notmode: _notmode, - action: $action, + action: $action.into(), }); )* @@ -261,65 +289,109 @@ macro_rules! bindings { pub fn default_mouse_bindings() -> Vec<MouseBinding> { bindings!( MouseBinding; - MouseButton::Middle; Action::PasteSelection; + MouseButton::Middle, ~TermMode::VI; Action::PasteSelection; ) } pub fn default_key_bindings() -> Vec<KeyBinding> { let mut bindings = bindings!( KeyBinding; - Paste; Action::Paste; Copy; Action::Copy; + Paste, ~TermMode::VI; Action::Paste; L, ModifiersState::CTRL; Action::ClearLogNotice; - L, ModifiersState::CTRL; Action::Esc("\x0c".into()); - PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp; - PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown; + L, ModifiersState::CTRL, ~TermMode::VI; Action::Esc("\x0c".into()); + Tab, ModifiersState::SHIFT, ~TermMode::VI; Action::Esc("\x1b[Z".into()); + Back, ModifiersState::ALT, ~TermMode::VI; Action::Esc("\x1b\x7f".into()); Home, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToTop; End, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToBottom; - Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into()); - Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into()); - Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into()); - End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into()); - End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into()); - End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into()); - PageUp; Action::Esc("\x1b[5~".into()); - PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into()); - PageDown; Action::Esc("\x1b[6~".into()); - PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into()); - Tab, ModifiersState::SHIFT; Action::Esc("\x1b[Z".into()); - Back; Action::Esc("\x7f".into()); - Back, ModifiersState::ALT; Action::Esc("\x1b\x7f".into()); - Insert; Action::Esc("\x1b[2~".into()); - Delete; Action::Esc("\x1b[3~".into()); - Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into()); - Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into()); - Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into()); - Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into()); - Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into()); - Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into()); - Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into()); - Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into()); - F1; Action::Esc("\x1bOP".into()); - F2; Action::Esc("\x1bOQ".into()); - F3; Action::Esc("\x1bOR".into()); - F4; Action::Esc("\x1bOS".into()); - F5; Action::Esc("\x1b[15~".into()); - F6; Action::Esc("\x1b[17~".into()); - F7; Action::Esc("\x1b[18~".into()); - F8; Action::Esc("\x1b[19~".into()); - F9; Action::Esc("\x1b[20~".into()); - F10; Action::Esc("\x1b[21~".into()); - F11; Action::Esc("\x1b[23~".into()); - F12; Action::Esc("\x1b[24~".into()); - F13; Action::Esc("\x1b[25~".into()); - F14; Action::Esc("\x1b[26~".into()); - F15; Action::Esc("\x1b[28~".into()); - F16; Action::Esc("\x1b[29~".into()); - F17; Action::Esc("\x1b[31~".into()); - F18; Action::Esc("\x1b[32~".into()); - F19; Action::Esc("\x1b[33~".into()); - F20; Action::Esc("\x1b[34~".into()); - NumpadEnter; Action::Esc("\n".into()); + PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp; + PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown; + Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[1;2H".into()); + End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[1;2F".into()); + PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[5;2~".into()); + PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN, ~TermMode::VI; + Action::Esc("\x1b[6;2~".into()); + Home, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOH".into()); + Home, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[H".into()); + End, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOF".into()); + End, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[F".into()); + Up, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOA".into()); + Up, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[A".into()); + Down, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOB".into()); + Down, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[B".into()); + Right, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOC".into()); + Right, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[C".into()); + Left, +TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1bOD".into()); + Left, ~TermMode::APP_CURSOR, ~TermMode::VI; Action::Esc("\x1b[D".into()); + Back, ~TermMode::VI; Action::Esc("\x7f".into()); + Insert, ~TermMode::VI; Action::Esc("\x1b[2~".into()); + Delete, ~TermMode::VI; Action::Esc("\x1b[3~".into()); + PageUp, ~TermMode::VI; Action::Esc("\x1b[5~".into()); + PageDown, ~TermMode::VI; Action::Esc("\x1b[6~".into()); + F1, ~TermMode::VI; Action::Esc("\x1bOP".into()); + F2, ~TermMode::VI; Action::Esc("\x1bOQ".into()); + F3, ~TermMode::VI; Action::Esc("\x1bOR".into()); + F4, ~TermMode::VI; Action::Esc("\x1bOS".into()); + F5, ~TermMode::VI; Action::Esc("\x1b[15~".into()); + F6, ~TermMode::VI; Action::Esc("\x1b[17~".into()); + F7, ~TermMode::VI; Action::Esc("\x1b[18~".into()); + F8, ~TermMode::VI; Action::Esc("\x1b[19~".into()); + F9, ~TermMode::VI; Action::Esc("\x1b[20~".into()); + F10, ~TermMode::VI; Action::Esc("\x1b[21~".into()); + F11, ~TermMode::VI; Action::Esc("\x1b[23~".into()); + F12, ~TermMode::VI; Action::Esc("\x1b[24~".into()); + F13, ~TermMode::VI; Action::Esc("\x1b[25~".into()); + F14, ~TermMode::VI; Action::Esc("\x1b[26~".into()); + F15, ~TermMode::VI; Action::Esc("\x1b[28~".into()); + F16, ~TermMode::VI; Action::Esc("\x1b[29~".into()); + F17, ~TermMode::VI; Action::Esc("\x1b[31~".into()); + F18, ~TermMode::VI; Action::Esc("\x1b[32~".into()); + F19, ~TermMode::VI; Action::Esc("\x1b[33~".into()); + F20, ~TermMode::VI; Action::Esc("\x1b[34~".into()); + NumpadEnter, ~TermMode::VI; Action::Esc("\n".into()); + Space, ModifiersState::SHIFT | ModifiersState::CTRL, +TermMode::VI; Action::ScrollToBottom; + Space, ModifiersState::SHIFT | ModifiersState::CTRL; Action::ToggleViMode; + Escape, +TermMode::VI; Action::ClearSelection; + I, +TermMode::VI; Action::ScrollToBottom; + I, +TermMode::VI; Action::ToggleViMode; + Y, ModifiersState::CTRL, +TermMode::VI; Action::ScrollLineUp; + E, ModifiersState::CTRL, +TermMode::VI; Action::ScrollLineDown; + G, +TermMode::VI; Action::ScrollToTop; + G, ModifiersState::SHIFT, +TermMode::VI; Action::ScrollToBottom; + B, ModifiersState::CTRL, +TermMode::VI; Action::ScrollPageUp; + F, ModifiersState::CTRL, +TermMode::VI; Action::ScrollPageDown; + U, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageUp; + D, ModifiersState::CTRL, +TermMode::VI; Action::ScrollHalfPageDown; + Y, +TermMode::VI; Action::Copy; + V, +TermMode::VI; ViAction::ToggleNormalSelection; + V, ModifiersState::SHIFT, +TermMode::VI; ViAction::ToggleLineSelection; + V, ModifiersState::CTRL, +TermMode::VI; ViAction::ToggleBlockSelection; + V, ModifiersState::ALT, +TermMode::VI; ViAction::ToggleSemanticSelection; + Return, +TermMode::VI; ViAction::Open; + K, +TermMode::VI; ViMotion::Up; + J, +TermMode::VI; ViMotion::Down; + H, +TermMode::VI; ViMotion::Left; + L, +TermMode::VI; ViMotion::Right; + Up, +TermMode::VI; ViMotion::Up; + Down, +TermMode::VI; ViMotion::Down; + Left, +TermMode::VI; ViMotion::Left; + Right, +TermMode::VI; ViMotion::Right; + Key0, +TermMode::VI; ViMotion::First; + Key4, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Last; + Key6, ModifiersState::SHIFT, +TermMode::VI; ViMotion::FirstOccupied; + H, ModifiersState::SHIFT, +TermMode::VI; ViMotion::High; + M, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Middle; + L, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Low; + B, +TermMode::VI; ViMotion::SemanticLeft; + W, +TermMode::VI; ViMotion::SemanticRight; + E, +TermMode::VI; ViMotion::SemanticRightEnd; + B, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordLeft; + W, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordRight; + E, ModifiersState::SHIFT, +TermMode::VI; ViMotion::WordRightEnd; + Key5, ModifiersState::SHIFT, +TermMode::VI; ViMotion::Bracket; ); // Code Modifiers @@ -348,31 +420,31 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { let modifiers_code = index + 2; bindings.extend(bindings!( KeyBinding; - Delete, mods; Action::Esc(format!("\x1b[3;{}~", modifiers_code)); - Up, mods; Action::Esc(format!("\x1b[1;{}A", modifiers_code)); - Down, mods; Action::Esc(format!("\x1b[1;{}B", modifiers_code)); - Right, mods; Action::Esc(format!("\x1b[1;{}C", modifiers_code)); - Left, mods; Action::Esc(format!("\x1b[1;{}D", modifiers_code)); - F1, mods; Action::Esc(format!("\x1b[1;{}P", modifiers_code)); - F2, mods; Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); - F3, mods; Action::Esc(format!("\x1b[1;{}R", modifiers_code)); - F4, mods; Action::Esc(format!("\x1b[1;{}S", modifiers_code)); - F5, mods; Action::Esc(format!("\x1b[15;{}~", modifiers_code)); - F6, mods; Action::Esc(format!("\x1b[17;{}~", modifiers_code)); - F7, mods; Action::Esc(format!("\x1b[18;{}~", modifiers_code)); - F8, mods; Action::Esc(format!("\x1b[19;{}~", modifiers_code)); - F9, mods; Action::Esc(format!("\x1b[20;{}~", modifiers_code)); - F10, mods; Action::Esc(format!("\x1b[21;{}~", modifiers_code)); - F11, mods; Action::Esc(format!("\x1b[23;{}~", modifiers_code)); - F12, mods; Action::Esc(format!("\x1b[24;{}~", modifiers_code)); - F13, mods; Action::Esc(format!("\x1b[25;{}~", modifiers_code)); - F14, mods; Action::Esc(format!("\x1b[26;{}~", modifiers_code)); - F15, mods; Action::Esc(format!("\x1b[28;{}~", modifiers_code)); - F16, mods; Action::Esc(format!("\x1b[29;{}~", modifiers_code)); - F17, mods; Action::Esc(format!("\x1b[31;{}~", modifiers_code)); - F18, mods; Action::Esc(format!("\x1b[32;{}~", modifiers_code)); - F19, mods; Action::Esc(format!("\x1b[33;{}~", modifiers_code)); - F20, mods; Action::Esc(format!("\x1b[34;{}~", modifiers_code)); + Delete, mods, ~TermMode::VI; Action::Esc(format!("\x1b[3;{}~", modifiers_code)); + Up, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}A", modifiers_code)); + Down, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}B", modifiers_code)); + Right, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}C", modifiers_code)); + Left, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}D", modifiers_code)); + F1, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}P", modifiers_code)); + F2, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); + F3, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}R", modifiers_code)); + F4, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}S", modifiers_code)); + F5, mods, ~TermMode::VI; Action::Esc(format!("\x1b[15;{}~", modifiers_code)); + F6, mods, ~TermMode::VI; Action::Esc(format!("\x1b[17;{}~", modifiers_code)); + F7, mods, ~TermMode::VI; Action::Esc(format!("\x1b[18;{}~", modifiers_code)); + F8, mods, ~TermMode::VI; Action::Esc(format!("\x1b[19;{}~", modifiers_code)); + F9, mods, ~TermMode::VI; Action::Esc(format!("\x1b[20;{}~", modifiers_code)); + F10, mods, ~TermMode::VI; Action::Esc(format!("\x1b[21;{}~", modifiers_code)); + F11, mods, ~TermMode::VI; Action::Esc(format!("\x1b[23;{}~", modifiers_code)); + F12, mods, ~TermMode::VI; Action::Esc(format!("\x1b[24;{}~", modifiers_code)); + F13, mods, ~TermMode::VI; Action::Esc(format!("\x1b[25;{}~", modifiers_code)); + F14, mods, ~TermMode::VI; Action::Esc(format!("\x1b[26;{}~", modifiers_code)); + F15, mods, ~TermMode::VI; Action::Esc(format!("\x1b[28;{}~", modifiers_code)); + F16, mods, ~TermMode::VI; Action::Esc(format!("\x1b[29;{}~", modifiers_code)); + F17, mods, ~TermMode::VI; Action::Esc(format!("\x1b[31;{}~", modifiers_code)); + F18, mods, ~TermMode::VI; Action::Esc(format!("\x1b[32;{}~", modifiers_code)); + F19, mods, ~TermMode::VI; Action::Esc(format!("\x1b[33;{}~", modifiers_code)); + F20, mods, ~TermMode::VI; Action::Esc(format!("\x1b[34;{}~", modifiers_code)); )); // We're adding the following bindings with `Shift` manually above, so skipping them here @@ -380,11 +452,11 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { if modifiers_code != 2 { bindings.extend(bindings!( KeyBinding; - Insert, mods; Action::Esc(format!("\x1b[2;{}~", modifiers_code)); - PageUp, mods; Action::Esc(format!("\x1b[5;{}~", modifiers_code)); - PageDown, mods; Action::Esc(format!("\x1b[6;{}~", modifiers_code)); - End, mods; Action::Esc(format!("\x1b[1;{}F", modifiers_code)); - Home, mods; Action::Esc(format!("\x1b[1;{}H", modifiers_code)); + Insert, mods, ~TermMode::VI; Action::Esc(format!("\x1b[2;{}~", modifiers_code)); + PageUp, mods, ~TermMode::VI; Action::Esc(format!("\x1b[5;{}~", modifiers_code)); + PageDown, mods, ~TermMode::VI; Action::Esc(format!("\x1b[6;{}~", modifiers_code)); + End, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}F", modifiers_code)); + Home, mods, ~TermMode::VI; Action::Esc(format!("\x1b[1;{}H", modifiers_code)); )); } } @@ -398,9 +470,9 @@ pub fn default_key_bindings() -> Vec<KeyBinding> { fn common_keybindings() -> Vec<KeyBinding> { bindings!( KeyBinding; - V, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Paste; + V, ModifiersState::CTRL | ModifiersState::SHIFT, ~TermMode::VI; Action::Paste; C, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Copy; - Insert, ModifiersState::SHIFT; Action::PasteSelection; + Insert, ModifiersState::SHIFT, ~TermMode::VI; Action::PasteSelection; Key0, ModifiersState::CTRL; Action::ResetFontSize; Equals, ModifiersState::CTRL; Action::IncreaseFontSize; Add, ModifiersState::CTRL; Action::IncreaseFontSize; @@ -428,16 +500,16 @@ pub fn platform_key_bindings() -> Vec<KeyBinding> { pub fn platform_key_bindings() -> Vec<KeyBinding> { bindings!( KeyBinding; - Key0, ModifiersState::LOGO; Action::ResetFontSize; - Equals, ModifiersState::LOGO; Action::IncreaseFontSize; - Add, ModifiersState::LOGO; Action::IncreaseFontSize; - Minus, ModifiersState::LOGO; Action::DecreaseFontSize; - Insert, ModifiersState::SHIFT; Action::Esc("\x1b[2;2~".into()); + Key0, ModifiersState::LOGO; Action::ResetFontSize; + Equals, ModifiersState::LOGO; Action::IncreaseFontSize; + Add, ModifiersState::LOGO; Action::IncreaseFontSize; + Minus, ModifiersState::LOGO; Action::DecreaseFontSize; + Insert, ModifiersState::SHIFT, ~TermMode::VI; Action::Esc("\x1b[2;2~".into()); + K, ModifiersState::LOGO, ~TermMode::VI; Action::Esc("\x0c".into()); + V, ModifiersState::LOGO, ~TermMode::VI; Action::Paste; N, ModifiersState::LOGO; Action::SpawnNewInstance; F, ModifiersState::CTRL | ModifiersState::LOGO; Action::ToggleFullscreen; K, ModifiersState::LOGO; Action::ClearHistory; - K, ModifiersState::LOGO; Action::Esc("\x0c".into()); - V, ModifiersState::LOGO; Action::Paste; C, ModifiersState::LOGO; Action::Copy; H, ModifiersState::LOGO; Action::Hide; M, ModifiersState::LOGO; Action::Minimize; @@ -463,7 +535,7 @@ impl<'a> Deserialize<'a> for Key { where D: Deserializer<'a>, { - let value = serde_yaml::Value::deserialize(deserializer)?; + let value = SerdeValue::deserialize(deserializer)?; match u32::deserialize(value.clone()) { Ok(scancode) => Ok(Key::Scancode(scancode)), Err(_) => { @@ -491,7 +563,7 @@ impl<'a> Deserialize<'a> for ModeWrapper { fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str( - "Combination of AppCursor | AppKeypad | Alt, possibly with negation (~)", + "a combination of AppCursor | AppKeypad | Alt | Vi, possibly with negation (~)", ) } @@ -509,7 +581,9 @@ impl<'a> Deserialize<'a> for ModeWrapper { "~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD, "alt" => res.mode |= TermMode::ALT_SCREEN, "~alt" => res.not_mode |= TermMode::ALT_SCREEN, - _ => error!(target: LOG_TARGET_CONFIG, "Unknown mode {:?}", modifier), + "vi" => res.mode |= TermMode::VI, + "~vi" => res.not_mode |= TermMode::VI, + _ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)), } } @@ -612,6 +686,8 @@ impl<'a> Deserialize<'a> for RawBinding { where D: Deserializer<'a>, { + const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; + enum Field { Key, Mods, @@ -629,9 +705,6 @@ impl<'a> Deserialize<'a> for RawBinding { { struct FieldVisitor; - static FIELDS: &[&str] = - &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - impl<'a> Visitor<'a> for FieldVisitor { type Value = Field; @@ -681,7 +754,7 @@ impl<'a> Deserialize<'a> for RawBinding { let mut mouse: Option<MouseButton> = None; let mut command: Option<CommandWrapper> = None; - use ::serde::de::Error; + use de::Error; while let Some(struct_key) = map.next_key::<Field>()? { match struct_key { @@ -690,10 +763,10 @@ impl<'a> Deserialize<'a> for RawBinding { return Err(<V::Error as Error>::duplicate_field("key")); } - let val = map.next_value::<serde_yaml::Value>()?; + let val = map.next_value::<SerdeValue>()?; if val.is_u64() { let scancode = val.as_u64().unwrap(); - if scancode > u64::from(::std::u32::MAX) { + if scancode > u64::from(std::u32::MAX) { return Err(<V::Error as Error>::custom(format!( "Invalid key binding, scancode too big: {}", scancode @@ -726,7 +799,36 @@ impl<'a> Deserialize<'a> for RawBinding { return Err(<V::Error as Error>::duplicate_field("action")); } - action = Some(map.next_value::<Action>()?); + let value = map.next_value::<SerdeValue>()?; + + action = if let Ok(vi_action) = ViAction::deserialize(value.clone()) { + Some(vi_action.into()) + } else if let Ok(vi_motion) = ViMotion::deserialize(value.clone()) { + Some(vi_motion.into()) + } else { + match Action::deserialize(value.clone()).map_err(V::Error::custom) { + Ok(action) => Some(action), + Err(err) => { + let value = match value { + SerdeValue::String(string) => string, + SerdeValue::Mapping(map) if map.len() == 1 => { + match map.into_iter().next() { + Some(( + SerdeValue::String(string), + SerdeValue::Null, + )) => string, + _ => return Err(err), + } + }, + _ => return Err(err), + }; + return Err(V::Error::custom(format!( + "unknown keyboard action `{}`", + value + ))); + }, + } + }; }, Field::Chars => { if chars.is_some() { @@ -752,7 +854,21 @@ impl<'a> Deserialize<'a> for RawBinding { } } + let mode = mode.unwrap_or_else(TermMode::empty); + let not_mode = not_mode.unwrap_or_else(TermMode::empty); + let mods = mods.unwrap_or_else(ModifiersState::default); + let action = match (action, chars, command) { + (Some(action @ Action::ViMotion(_)), None, None) + | (Some(action @ Action::ViAction(_)), None, None) => { + if !mode.intersects(TermMode::VI) || not_mode.intersects(TermMode::VI) { + return Err(V::Error::custom(format!( + "action `{}` is only available in vi mode, try adding `mode: Vi`", + action, + ))); + } + action + }, (Some(action), None, None) => action, (None, Some(chars), None) => Action::Esc(chars), (None, None, Some(cmd)) => match cmd { @@ -761,18 +877,13 @@ impl<'a> Deserialize<'a> for RawBinding { Action::Command(program, args) }, }, - (None, None, None) => { - return Err(V::Error::custom("must specify chars, action or command")); - }, _ => { - return Err(V::Error::custom("must specify only chars, action or command")) + return Err(V::Error::custom( + "must specify exactly one of chars, action or command", + )) }, }; - let mode = mode.unwrap_or_else(TermMode::empty); - let not_mode = not_mode.unwrap_or_else(TermMode::empty); - let mods = mods.unwrap_or_else(ModifiersState::default); - if mouse.is_none() && key.is_none() { return Err(V::Error::custom("bindings require mouse button or key")); } @@ -781,8 +892,6 @@ impl<'a> Deserialize<'a> for RawBinding { } } - const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"]; - deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) } } @@ -793,7 +902,8 @@ impl<'a> Deserialize<'a> for MouseBinding { D: Deserializer<'a>, { let raw = RawBinding::deserialize(deserializer)?; - raw.into_mouse_binding().map_err(|_| D::Error::custom("expected mouse binding")) + raw.into_mouse_binding() + .map_err(|_| D::Error::custom("expected mouse binding, got key binding")) } } @@ -803,7 +913,8 @@ impl<'a> Deserialize<'a> for KeyBinding { D: Deserializer<'a>, { let raw = RawBinding::deserialize(deserializer)?; - raw.into_key_binding().map_err(|_| D::Error::custom("expected key binding")) + raw.into_key_binding() + .map_err(|_| D::Error::custom("expected key binding, got mouse binding")) } } @@ -858,7 +969,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { type Value = ModsWrapper; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("Some subset of Command|Shift|Super|Alt|Option|Control") + f.write_str("a subset of Shift|Control|Super|Command|Alt|Option") } fn visit_str<E>(self, value: &str) -> Result<ModsWrapper, E> @@ -873,7 +984,7 @@ impl<'a> de::Deserialize<'a> for ModsWrapper { "alt" | "option" => res.insert(ModifiersState::ALT), "control" => res.insert(ModifiersState::CTRL), "none" => (), - _ => error!(target: LOG_TARGET_CONFIG, "Unknown modifier {:?}", modifier), + _ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)), } } @@ -899,7 +1010,7 @@ mod tests { fn default() -> Self { Self { mods: Default::default(), - action: Default::default(), + action: Action::None, mode: TermMode::empty(), notmode: TermMode::empty(), trigger: Default::default(), |