2020-01-06 22:06:09 +00:00
|
|
|
#![allow(clippy::enum_glob_use)]
|
2019-05-10 11:36:16 +00:00
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
use std::fmt::{self, Debug, Display};
|
2019-05-10 11:36:16 +00:00
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
use bitflags::bitflags;
|
2020-01-06 22:06:09 +00:00
|
|
|
use glutin::event::VirtualKeyCode::*;
|
|
|
|
use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode};
|
2019-05-10 11:36:16 +00:00
|
|
|
use serde::de::Error as SerdeError;
|
|
|
|
use serde::de::{self, MapAccess, Unexpected, Visitor};
|
|
|
|
use serde::{Deserialize, Deserializer};
|
2020-03-18 02:35:08 +00:00
|
|
|
use serde_yaml::Value as SerdeValue;
|
2019-01-17 09:17:26 +00:00
|
|
|
|
2020-12-21 02:44:38 +00:00
|
|
|
use alacritty_config_derive::ConfigDeserialize;
|
|
|
|
|
2020-06-04 22:10:31 +00:00
|
|
|
use alacritty_terminal::config::Program;
|
2019-10-05 00:29:26 +00:00
|
|
|
use alacritty_terminal::term::TermMode;
|
2020-03-18 02:35:08 +00:00
|
|
|
use alacritty_terminal::vi_mode::ViMotion;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Describes a state and action to take in that state.
|
2019-10-05 00:29:26 +00:00
|
|
|
///
|
2020-05-05 22:50:23 +00:00
|
|
|
/// This is the shared component of `MouseBinding` and `KeyBinding`.
|
2019-10-05 00:29:26 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
|
|
pub struct Binding<T> {
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Modifier keys required to activate binding.
|
2019-10-05 00:29:26 +00:00
|
|
|
pub mods: ModifiersState,
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// String to send to PTY if mods and mode match.
|
2019-10-05 00:29:26 +00:00
|
|
|
pub action: Action,
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
/// Binding mode required to activate binding.
|
|
|
|
pub mode: BindingMode,
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
/// Excluded binding modes where the binding won't be activated.
|
|
|
|
pub notmode: BindingMode,
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
/// This property is used as part of the trigger detection code.
|
|
|
|
///
|
|
|
|
/// For example, this might be a key like "G", or a mouse button.
|
|
|
|
pub trigger: T,
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Bindings that are triggered by a keyboard key.
|
2019-10-05 00:29:26 +00:00
|
|
|
pub type KeyBinding = Binding<Key>;
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Bindings that are triggered by a mouse button.
|
2019-10-05 00:29:26 +00:00
|
|
|
pub type MouseBinding = Binding<MouseButton>;
|
|
|
|
|
|
|
|
impl<T: Eq> Binding<T> {
|
|
|
|
#[inline]
|
2020-12-19 04:07:20 +00:00
|
|
|
pub fn is_triggered_by(&self, mode: BindingMode, mods: ModifiersState, input: &T) -> bool {
|
2019-10-05 00:29:26 +00:00
|
|
|
// Check input first since bindings are stored in one big list. This is
|
|
|
|
// the most likely item to fail so prioritizing it here allows more
|
|
|
|
// checks to be short circuited.
|
|
|
|
self.trigger == *input
|
2020-06-15 00:12:39 +00:00
|
|
|
&& self.mods == mods
|
2019-10-05 00:29:26 +00:00
|
|
|
&& mode.contains(self.mode)
|
|
|
|
&& !mode.intersects(self.notmode)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn triggers_match(&self, binding: &Binding<T>) -> bool {
|
2020-05-05 22:50:23 +00:00
|
|
|
// Check the binding's key and modifiers.
|
2019-10-05 00:29:26 +00:00
|
|
|
if self.trigger != binding.trigger || self.mods != binding.mods {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
let selfmode = if self.mode.is_empty() { BindingMode::all() } else { self.mode };
|
|
|
|
let bindingmode = if binding.mode.is_empty() { BindingMode::all() } else { binding.mode };
|
2020-06-15 00:12:39 +00:00
|
|
|
|
|
|
|
if !selfmode.intersects(bindingmode) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The bindings are never active at the same time when the required modes of one binding
|
|
|
|
// are part of the forbidden bindings of the other.
|
|
|
|
if self.mode.intersects(binding.notmode) || binding.mode.intersects(self.notmode) {
|
|
|
|
return false;
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
2020-06-15 00:12:39 +00:00
|
|
|
true
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-21 02:44:38 +00:00
|
|
|
#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)]
|
2019-10-05 00:29:26 +00:00
|
|
|
pub enum Action {
|
|
|
|
/// Write an escape sequence.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[config(skip)]
|
2019-10-05 00:29:26 +00:00
|
|
|
Esc(String),
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
/// Run given command.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[config(skip)]
|
2020-06-04 22:10:31 +00:00
|
|
|
Command(Program),
|
2020-03-18 02:35:08 +00:00
|
|
|
|
|
|
|
/// Move vi mode cursor.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[config(skip)]
|
2020-03-18 02:35:08 +00:00
|
|
|
ViMotion(ViMotion),
|
|
|
|
|
|
|
|
/// Perform vi mode action.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[config(skip)]
|
2020-03-18 02:35:08 +00:00
|
|
|
ViAction(ViAction),
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
/// Perform search mode action.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[config(skip)]
|
2020-12-19 04:07:20 +00:00
|
|
|
SearchAction(SearchAction),
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Paste contents of system clipboard.
|
|
|
|
Paste,
|
|
|
|
|
|
|
|
/// Store current selection into clipboard.
|
|
|
|
Copy,
|
|
|
|
|
2020-03-23 23:46:33 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", windows)))]
|
|
|
|
/// Store current selection into selection buffer.
|
|
|
|
CopySelection,
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Paste contents of selection buffer.
|
|
|
|
PasteSelection,
|
|
|
|
|
|
|
|
/// Increase font size.
|
|
|
|
IncreaseFontSize,
|
|
|
|
|
|
|
|
/// Decrease font size.
|
|
|
|
DecreaseFontSize,
|
|
|
|
|
|
|
|
/// Reset font size to the config value.
|
|
|
|
ResetFontSize,
|
|
|
|
|
|
|
|
/// Scroll exactly one page up.
|
|
|
|
ScrollPageUp,
|
|
|
|
|
|
|
|
/// Scroll exactly one page down.
|
|
|
|
ScrollPageDown,
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
/// Scroll half a page up.
|
|
|
|
ScrollHalfPageUp,
|
|
|
|
|
|
|
|
/// Scroll half a page down.
|
|
|
|
ScrollHalfPageDown,
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Scroll one line up.
|
|
|
|
ScrollLineUp,
|
|
|
|
|
|
|
|
/// Scroll one line down.
|
|
|
|
ScrollLineDown,
|
|
|
|
|
|
|
|
/// Scroll all the way to the top.
|
|
|
|
ScrollToTop,
|
|
|
|
|
|
|
|
/// Scroll all the way to the bottom.
|
|
|
|
ScrollToBottom,
|
|
|
|
|
|
|
|
/// Clear the display buffer(s) to remove history.
|
|
|
|
ClearHistory,
|
|
|
|
|
|
|
|
/// Hide the Alacritty window.
|
|
|
|
Hide,
|
|
|
|
|
2020-01-05 01:54:14 +00:00
|
|
|
/// Minimize the Alacritty window.
|
|
|
|
Minimize,
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Quit Alacritty.
|
|
|
|
Quit,
|
|
|
|
|
|
|
|
/// Clear warning and error notices.
|
|
|
|
ClearLogNotice,
|
|
|
|
|
|
|
|
/// Spawn a new instance of Alacritty.
|
|
|
|
SpawnNewInstance,
|
|
|
|
|
|
|
|
/// Toggle fullscreen.
|
|
|
|
ToggleFullscreen,
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Toggle simple fullscreen on macOS.
|
2019-10-05 00:29:26 +00:00
|
|
|
#[cfg(target_os = "macos")]
|
|
|
|
ToggleSimpleFullscreen,
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
/// Clear active selection.
|
|
|
|
ClearSelection,
|
|
|
|
|
|
|
|
/// Toggle vi mode.
|
|
|
|
ToggleViMode,
|
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// Allow receiving char input.
|
|
|
|
ReceiveChar,
|
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
/// Start a forward buffer search.
|
|
|
|
SearchForward,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2020-07-15 21:27:32 +00:00
|
|
|
/// Start a backward buffer search.
|
|
|
|
SearchBackward,
|
2020-07-09 21:45:22 +00:00
|
|
|
|
2019-10-05 00:29:26 +00:00
|
|
|
/// No action.
|
|
|
|
None,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&'static str> for Action {
|
|
|
|
fn from(s: &'static str) -> Action {
|
|
|
|
Action::Esc(s.into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SearchAction> for Action {
|
|
|
|
fn from(action: SearchAction) -> Self {
|
|
|
|
Self::SearchAction(action)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
/// 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.
|
2020-12-21 02:44:38 +00:00
|
|
|
#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
|
2020-03-18 02:35:08 +00:00
|
|
|
pub enum ViAction {
|
|
|
|
/// Toggle normal vi selection.
|
|
|
|
ToggleNormalSelection,
|
|
|
|
/// Toggle line vi selection.
|
|
|
|
ToggleLineSelection,
|
|
|
|
/// Toggle block vi selection.
|
|
|
|
ToggleBlockSelection,
|
|
|
|
/// Toggle semantic vi selection.
|
|
|
|
ToggleSemanticSelection,
|
2020-07-09 21:45:22 +00:00
|
|
|
/// Jump to the beginning of the next match.
|
|
|
|
SearchNext,
|
|
|
|
/// Jump to the beginning of the previous match.
|
|
|
|
SearchPrevious,
|
2020-07-14 15:17:29 +00:00
|
|
|
/// Jump to the next start of a match to the left of the origin.
|
|
|
|
SearchStart,
|
|
|
|
/// Jump to the next end of a match to the right of the origin.
|
|
|
|
SearchEnd,
|
2020-03-18 02:35:08 +00:00
|
|
|
/// Launch the URL below the vi mode cursor.
|
|
|
|
Open,
|
|
|
|
}
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
/// Search mode specific actions.
|
|
|
|
#[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
|
|
|
|
pub enum SearchAction {
|
|
|
|
/// Move the focus to the next search match.
|
|
|
|
SearchFocusNext,
|
|
|
|
/// Move the focus to the previous search match.
|
|
|
|
SearchFocusPrevious,
|
|
|
|
/// Confirm the active search.
|
|
|
|
SearchConfirm,
|
|
|
|
/// Cancel the active search.
|
|
|
|
SearchCancel,
|
|
|
|
/// Reset the search regex.
|
|
|
|
SearchClear,
|
|
|
|
/// Delete the last word in the search regex.
|
|
|
|
SearchDeleteWord,
|
2020-12-20 04:27:08 +00:00
|
|
|
/// Go to the previous regex in the search history.
|
|
|
|
SearchHistoryPrevious,
|
|
|
|
/// Go to the next regex in the search history.
|
|
|
|
SearchHistoryNext,
|
2020-03-18 02:35:08 +00:00
|
|
|
}
|
|
|
|
|
2019-01-17 09:17:26 +00:00
|
|
|
macro_rules! bindings {
|
2020-01-06 22:06:09 +00:00
|
|
|
(
|
|
|
|
KeyBinding;
|
|
|
|
$(
|
|
|
|
$key:ident
|
|
|
|
$(,$mods:expr)*
|
|
|
|
$(,+$mode:expr)*
|
|
|
|
$(,~$notmode:expr)*
|
|
|
|
;$action:expr
|
|
|
|
);*
|
|
|
|
$(;)*
|
|
|
|
) => {{
|
|
|
|
bindings!(
|
|
|
|
KeyBinding;
|
|
|
|
$(
|
|
|
|
Key::Keycode($key)
|
|
|
|
$(,$mods)*
|
|
|
|
$(,+$mode)*
|
|
|
|
$(,~$notmode)*
|
|
|
|
;$action
|
|
|
|
);*
|
|
|
|
)
|
|
|
|
}};
|
2019-01-17 09:17:26 +00:00
|
|
|
(
|
|
|
|
$ty:ident;
|
|
|
|
$(
|
2020-01-06 22:06:09 +00:00
|
|
|
$key:expr
|
2020-01-05 01:00:50 +00:00
|
|
|
$(,$mods:expr)*
|
2019-01-17 09:17:26 +00:00
|
|
|
$(,+$mode:expr)*
|
|
|
|
$(,~$notmode:expr)*
|
|
|
|
;$action:expr
|
|
|
|
);*
|
|
|
|
$(;)*
|
|
|
|
) => {{
|
|
|
|
let mut v = Vec::new();
|
|
|
|
|
|
|
|
$(
|
2020-01-05 01:00:50 +00:00
|
|
|
let mut _mods = ModifiersState::empty();
|
|
|
|
$(_mods = $mods;)*
|
2020-12-19 04:07:20 +00:00
|
|
|
let mut _mode = BindingMode::empty();
|
2020-03-18 02:35:08 +00:00
|
|
|
$(_mode.insert($mode);)*
|
2020-12-19 04:07:20 +00:00
|
|
|
let mut _notmode = BindingMode::empty();
|
2020-03-18 02:35:08 +00:00
|
|
|
$(_notmode.insert($notmode);)*
|
2019-01-17 09:17:26 +00:00
|
|
|
|
|
|
|
v.push($ty {
|
|
|
|
trigger: $key,
|
|
|
|
mods: _mods,
|
|
|
|
mode: _mode,
|
|
|
|
notmode: _notmode,
|
2020-03-18 02:35:08 +00:00
|
|
|
action: $action.into(),
|
2019-01-17 09:17:26 +00:00
|
|
|
});
|
|
|
|
)*
|
|
|
|
|
|
|
|
v
|
2020-01-06 22:06:09 +00:00
|
|
|
}};
|
2019-01-17 09:17:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn default_mouse_bindings() -> Vec<MouseBinding> {
|
|
|
|
bindings!(
|
|
|
|
MouseBinding;
|
2020-12-19 04:07:20 +00:00
|
|
|
MouseButton::Middle, ~BindingMode::VI; Action::PasteSelection;
|
2019-01-17 09:17:26 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn default_key_bindings() -> Vec<KeyBinding> {
|
|
|
|
let mut bindings = bindings!(
|
|
|
|
KeyBinding;
|
2020-01-06 22:06:09 +00:00
|
|
|
Copy; Action::Copy;
|
2020-12-19 04:07:20 +00:00
|
|
|
Copy, +BindingMode::VI; Action::ClearSelection;
|
|
|
|
Paste, ~BindingMode::VI; Action::Paste;
|
2020-01-06 22:06:09 +00:00
|
|
|
L, ModifiersState::CTRL; Action::ClearLogNotice;
|
2020-12-19 04:07:20 +00:00
|
|
|
L, ModifiersState::CTRL, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x0c".into());
|
|
|
|
Tab, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[Z".into());
|
|
|
|
Back, ModifiersState::ALT, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b\x7f".into());
|
|
|
|
Back, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x7f".into());
|
|
|
|
Home, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToTop;
|
|
|
|
End, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollToBottom;
|
|
|
|
PageUp, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageUp;
|
|
|
|
PageDown, ModifiersState::SHIFT, ~BindingMode::ALT_SCREEN; Action::ScrollPageDown;
|
|
|
|
Home, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN,
|
|
|
|
~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[1;2H".into());
|
|
|
|
End, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN,
|
|
|
|
~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[1;2F".into());
|
|
|
|
PageUp, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN,
|
|
|
|
~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[5;2~".into());
|
|
|
|
PageDown, ModifiersState::SHIFT, +BindingMode::ALT_SCREEN,
|
|
|
|
~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[6;2~".into());
|
|
|
|
Home, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOH".into());
|
|
|
|
Home, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[H".into());
|
|
|
|
End, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOF".into());
|
|
|
|
End, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[F".into());
|
|
|
|
Up, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOA".into());
|
|
|
|
Up, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[A".into());
|
|
|
|
Down, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOB".into());
|
|
|
|
Down, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[B".into());
|
|
|
|
Right, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOC".into());
|
|
|
|
Right, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[C".into());
|
|
|
|
Left, +BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1bOD".into());
|
|
|
|
Left, ~BindingMode::APP_CURSOR, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[D".into());
|
|
|
|
Back, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x7f".into());
|
|
|
|
Insert, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[2~".into());
|
|
|
|
Delete, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[3~".into());
|
|
|
|
PageUp, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[5~".into());
|
|
|
|
PageDown, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[6~".into());
|
|
|
|
F1, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOP".into());
|
|
|
|
F2, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOQ".into());
|
|
|
|
F3, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOR".into());
|
|
|
|
F4, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1bOS".into());
|
|
|
|
F5, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[15~".into());
|
|
|
|
F6, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[17~".into());
|
|
|
|
F7, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[18~".into());
|
|
|
|
F8, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[19~".into());
|
|
|
|
F9, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[20~".into());
|
|
|
|
F10, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[21~".into());
|
|
|
|
F11, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[23~".into());
|
|
|
|
F12, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[24~".into());
|
|
|
|
F13, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[25~".into());
|
|
|
|
F14, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[26~".into());
|
|
|
|
F15, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[28~".into());
|
|
|
|
F16, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[29~".into());
|
|
|
|
F17, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[31~".into());
|
|
|
|
F18, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[32~".into());
|
|
|
|
F19, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[33~".into());
|
|
|
|
F20, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\x1b[34~".into());
|
|
|
|
NumpadEnter, ~BindingMode::VI, ~BindingMode::SEARCH; Action::Esc("\n".into());
|
|
|
|
Space, ModifiersState::SHIFT | ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollToBottom;
|
|
|
|
Space, ModifiersState::SHIFT | ModifiersState::CTRL, ~BindingMode::SEARCH;
|
|
|
|
Action::ToggleViMode;
|
|
|
|
Escape, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ClearSelection;
|
|
|
|
I, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollToBottom;
|
|
|
|
I, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ToggleViMode;
|
|
|
|
C, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ToggleViMode;
|
|
|
|
Y, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollLineUp;
|
|
|
|
E, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollLineDown;
|
|
|
|
G, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollToTop;
|
|
|
|
G, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollToBottom;
|
|
|
|
B, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollPageUp;
|
|
|
|
F, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollPageDown;
|
|
|
|
U, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollHalfPageUp;
|
|
|
|
D, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ScrollHalfPageDown;
|
|
|
|
Y, +BindingMode::VI, ~BindingMode::SEARCH; Action::Copy;
|
|
|
|
Y, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::ClearSelection;
|
|
|
|
Slash, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::SearchForward;
|
|
|
|
Slash, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::SearchBackward;
|
|
|
|
V, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::ToggleNormalSelection;
|
|
|
|
V, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::ToggleLineSelection;
|
|
|
|
V, ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::ToggleBlockSelection;
|
|
|
|
V, ModifiersState::ALT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::ToggleSemanticSelection;
|
|
|
|
N, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::SearchNext;
|
|
|
|
N, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::SearchPrevious;
|
|
|
|
Return, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViAction::Open;
|
|
|
|
K, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Up;
|
|
|
|
J, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Down;
|
|
|
|
H, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Left;
|
|
|
|
L, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Right;
|
|
|
|
Up, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Up;
|
|
|
|
Down, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Down;
|
|
|
|
Left, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Left;
|
|
|
|
Right, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Right;
|
|
|
|
Key0, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::First;
|
|
|
|
Key4, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Last;
|
|
|
|
Key6, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::FirstOccupied;
|
|
|
|
H, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::High;
|
|
|
|
M, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Middle;
|
|
|
|
L, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Low;
|
|
|
|
B, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::SemanticLeft;
|
|
|
|
W, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::SemanticRight;
|
|
|
|
E, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::SemanticRightEnd;
|
|
|
|
B, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::WordLeft;
|
|
|
|
W, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::WordRight;
|
|
|
|
E, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::WordRightEnd;
|
|
|
|
Key5, ModifiersState::SHIFT, +BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
ViMotion::Bracket;
|
|
|
|
Return, +BindingMode::SEARCH, +BindingMode::VI;
|
|
|
|
SearchAction::SearchConfirm;
|
|
|
|
Escape, +BindingMode::SEARCH; SearchAction::SearchCancel;
|
2020-12-31 05:33:58 +00:00
|
|
|
C, ModifiersState::CTRL, +BindingMode::SEARCH; SearchAction::SearchCancel;
|
2020-12-19 04:07:20 +00:00
|
|
|
U, ModifiersState::CTRL, +BindingMode::SEARCH; SearchAction::SearchClear;
|
|
|
|
W, ModifiersState::CTRL, +BindingMode::SEARCH; SearchAction::SearchDeleteWord;
|
2020-12-20 04:27:08 +00:00
|
|
|
P, ModifiersState::CTRL, +BindingMode::SEARCH; SearchAction::SearchHistoryPrevious;
|
|
|
|
N, ModifiersState::CTRL, +BindingMode::SEARCH; SearchAction::SearchHistoryNext;
|
|
|
|
Up, +BindingMode::SEARCH; SearchAction::SearchHistoryPrevious;
|
|
|
|
Down, +BindingMode::SEARCH; SearchAction::SearchHistoryNext;
|
2020-12-19 04:07:20 +00:00
|
|
|
Return, +BindingMode::SEARCH, ~BindingMode::VI;
|
|
|
|
SearchAction::SearchFocusNext;
|
|
|
|
Return, ModifiersState::SHIFT, +BindingMode::SEARCH, ~BindingMode::VI;
|
|
|
|
SearchAction::SearchFocusPrevious;
|
2019-01-17 09:17:26 +00:00
|
|
|
);
|
|
|
|
|
2019-09-18 19:21:01 +00:00
|
|
|
// Code Modifiers
|
|
|
|
// ---------+---------------------------
|
|
|
|
// 2 | Shift
|
|
|
|
// 3 | Alt
|
|
|
|
// 4 | Shift + Alt
|
|
|
|
// 5 | Control
|
|
|
|
// 6 | Shift + Control
|
|
|
|
// 7 | Alt + Control
|
|
|
|
// 8 | Shift + Alt + Control
|
|
|
|
// ---------+---------------------------
|
|
|
|
//
|
|
|
|
// from: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-PC-Style-Function-Keys
|
2020-01-05 01:00:50 +00:00
|
|
|
let mut modifiers = vec![
|
|
|
|
ModifiersState::SHIFT,
|
|
|
|
ModifiersState::ALT,
|
|
|
|
ModifiersState::SHIFT | ModifiersState::ALT,
|
|
|
|
ModifiersState::CTRL,
|
|
|
|
ModifiersState::SHIFT | ModifiersState::CTRL,
|
|
|
|
ModifiersState::ALT | ModifiersState::CTRL,
|
|
|
|
ModifiersState::SHIFT | ModifiersState::ALT | ModifiersState::CTRL,
|
2019-09-18 19:21:01 +00:00
|
|
|
];
|
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
for (index, mods) in modifiers.drain(..).enumerate() {
|
2019-09-18 19:21:01 +00:00
|
|
|
let modifiers_code = index + 2;
|
|
|
|
bindings.extend(bindings!(
|
|
|
|
KeyBinding;
|
2020-12-19 04:07:20 +00:00
|
|
|
Delete, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[3;{}~", modifiers_code));
|
|
|
|
Up, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}A", modifiers_code));
|
|
|
|
Down, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}B", modifiers_code));
|
|
|
|
Right, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}C", modifiers_code));
|
|
|
|
Left, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}D", modifiers_code));
|
|
|
|
F1, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}P", modifiers_code));
|
|
|
|
F2, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}Q", modifiers_code));
|
|
|
|
F3, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}R", modifiers_code));
|
|
|
|
F4, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}S", modifiers_code));
|
|
|
|
F5, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[15;{}~", modifiers_code));
|
|
|
|
F6, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[17;{}~", modifiers_code));
|
|
|
|
F7, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[18;{}~", modifiers_code));
|
|
|
|
F8, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[19;{}~", modifiers_code));
|
|
|
|
F9, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[20;{}~", modifiers_code));
|
|
|
|
F10, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[21;{}~", modifiers_code));
|
|
|
|
F11, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[23;{}~", modifiers_code));
|
|
|
|
F12, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[24;{}~", modifiers_code));
|
|
|
|
F13, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[25;{}~", modifiers_code));
|
|
|
|
F14, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[26;{}~", modifiers_code));
|
|
|
|
F15, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[28;{}~", modifiers_code));
|
|
|
|
F16, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[29;{}~", modifiers_code));
|
|
|
|
F17, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[31;{}~", modifiers_code));
|
|
|
|
F18, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[32;{}~", modifiers_code));
|
|
|
|
F19, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[33;{}~", modifiers_code));
|
|
|
|
F20, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[34;{}~", modifiers_code));
|
2019-09-18 19:21:01 +00:00
|
|
|
));
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// We're adding the following bindings with `Shift` manually above, so skipping them here.
|
2019-09-18 19:21:01 +00:00
|
|
|
if modifiers_code != 2 {
|
|
|
|
bindings.extend(bindings!(
|
|
|
|
KeyBinding;
|
2020-12-19 04:07:20 +00:00
|
|
|
Insert, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[2;{}~", modifiers_code));
|
|
|
|
PageUp, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[5;{}~", modifiers_code));
|
|
|
|
PageDown, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[6;{}~", modifiers_code));
|
|
|
|
End, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}F", modifiers_code));
|
|
|
|
Home, mods, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc(format!("\x1b[1;{}H", modifiers_code));
|
2019-09-18 19:21:01 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-17 09:17:26 +00:00
|
|
|
bindings.extend(platform_key_bindings());
|
|
|
|
|
|
|
|
bindings
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(any(target_os = "macos", test)))]
|
2019-04-23 19:05:47 +00:00
|
|
|
fn common_keybindings() -> Vec<KeyBinding> {
|
2019-01-17 09:17:26 +00:00
|
|
|
bindings!(
|
|
|
|
KeyBinding;
|
2020-12-19 04:07:20 +00:00
|
|
|
V, ModifiersState::CTRL | ModifiersState::SHIFT, ~BindingMode::VI; Action::Paste;
|
2020-01-06 22:06:09 +00:00
|
|
|
C, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Copy;
|
2020-12-19 04:07:20 +00:00
|
|
|
F, ModifiersState::CTRL | ModifiersState::SHIFT, ~BindingMode::SEARCH;
|
|
|
|
Action::SearchForward;
|
|
|
|
B, ModifiersState::CTRL | ModifiersState::SHIFT, ~BindingMode::SEARCH;
|
|
|
|
Action::SearchBackward;
|
|
|
|
C, ModifiersState::CTRL | ModifiersState::SHIFT,
|
|
|
|
+BindingMode::VI, ~BindingMode::SEARCH; Action::ClearSelection;
|
|
|
|
Insert, ModifiersState::SHIFT, ~BindingMode::VI; Action::PasteSelection;
|
2020-01-06 22:06:09 +00:00
|
|
|
Key0, ModifiersState::CTRL; Action::ResetFontSize;
|
|
|
|
Equals, ModifiersState::CTRL; Action::IncreaseFontSize;
|
2020-10-07 14:40:50 +00:00
|
|
|
Plus, ModifiersState::CTRL; Action::IncreaseFontSize;
|
|
|
|
NumpadAdd, ModifiersState::CTRL; Action::IncreaseFontSize;
|
|
|
|
Minus, ModifiersState::CTRL; Action::DecreaseFontSize;
|
|
|
|
NumpadSubtract, ModifiersState::CTRL; Action::DecreaseFontSize;
|
2019-01-17 09:17:26 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2019-04-23 19:05:47 +00:00
|
|
|
#[cfg(not(any(target_os = "macos", target_os = "windows", test)))]
|
|
|
|
pub fn platform_key_bindings() -> Vec<KeyBinding> {
|
|
|
|
common_keybindings()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(all(target_os = "windows", not(test)))]
|
|
|
|
pub fn platform_key_bindings() -> Vec<KeyBinding> {
|
|
|
|
let mut bindings = bindings!(
|
|
|
|
KeyBinding;
|
2020-01-06 22:06:09 +00:00
|
|
|
Return, ModifiersState::ALT; Action::ToggleFullscreen;
|
2019-04-23 19:05:47 +00:00
|
|
|
);
|
|
|
|
bindings.extend(common_keybindings());
|
|
|
|
bindings
|
|
|
|
}
|
|
|
|
|
2019-01-17 09:17:26 +00:00
|
|
|
#[cfg(all(target_os = "macos", not(test)))]
|
|
|
|
pub fn platform_key_bindings() -> Vec<KeyBinding> {
|
|
|
|
bindings!(
|
|
|
|
KeyBinding;
|
2020-12-19 04:07:20 +00:00
|
|
|
Key0, ModifiersState::LOGO; Action::ResetFontSize;
|
|
|
|
Equals, ModifiersState::LOGO; Action::IncreaseFontSize;
|
|
|
|
Plus, ModifiersState::LOGO; Action::IncreaseFontSize;
|
|
|
|
NumpadAdd, ModifiersState::LOGO; Action::IncreaseFontSize;
|
|
|
|
Minus, ModifiersState::LOGO; Action::DecreaseFontSize;
|
|
|
|
NumpadSubtract, ModifiersState::LOGO; Action::DecreaseFontSize;
|
|
|
|
Insert, ModifiersState::SHIFT, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x1b[2;2~".into());
|
|
|
|
K, ModifiersState::LOGO, ~BindingMode::VI, ~BindingMode::SEARCH;
|
|
|
|
Action::Esc("\x0c".into());
|
|
|
|
K, ModifiersState::LOGO, ~BindingMode::VI, ~BindingMode::SEARCH; Action::ClearHistory;
|
|
|
|
V, ModifiersState::LOGO, ~BindingMode::VI; Action::Paste;
|
2020-03-17 19:37:36 +00:00
|
|
|
N, ModifiersState::LOGO; Action::SpawnNewInstance;
|
2020-01-06 22:06:09 +00:00
|
|
|
F, ModifiersState::CTRL | ModifiersState::LOGO; Action::ToggleFullscreen;
|
|
|
|
C, ModifiersState::LOGO; Action::Copy;
|
2020-12-19 04:07:20 +00:00
|
|
|
C, ModifiersState::LOGO, +BindingMode::VI, ~BindingMode::SEARCH; Action::ClearSelection;
|
2020-01-06 22:06:09 +00:00
|
|
|
H, ModifiersState::LOGO; Action::Hide;
|
|
|
|
M, ModifiersState::LOGO; Action::Minimize;
|
|
|
|
Q, ModifiersState::LOGO; Action::Quit;
|
|
|
|
W, ModifiersState::LOGO; Action::Quit;
|
2020-12-19 04:07:20 +00:00
|
|
|
F, ModifiersState::LOGO, ~BindingMode::SEARCH; Action::SearchForward;
|
|
|
|
B, ModifiersState::LOGO, ~BindingMode::SEARCH; Action::SearchBackward;
|
2019-01-17 09:17:26 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
// Don't return any bindings for tests since they are commented-out by default.
|
2019-01-17 09:17:26 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
pub fn platform_key_bindings() -> Vec<KeyBinding> {
|
|
|
|
vec![]
|
|
|
|
}
|
2019-05-10 11:36:16 +00:00
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
2019-05-10 11:36:16 +00:00
|
|
|
pub enum Key {
|
|
|
|
Scancode(u32),
|
2020-01-06 22:06:09 +00:00
|
|
|
Keycode(VirtualKeyCode),
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
impl<'a> Deserialize<'a> for Key {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
2020-03-18 02:35:08 +00:00
|
|
|
let value = SerdeValue::deserialize(deserializer)?;
|
2020-01-07 15:59:23 +00:00
|
|
|
match u32::deserialize(value.clone()) {
|
|
|
|
Ok(scancode) => Ok(Key::Scancode(scancode)),
|
|
|
|
Err(_) => {
|
|
|
|
let keycode = VirtualKeyCode::deserialize(value).map_err(D::Error::custom)?;
|
|
|
|
Ok(Key::Keycode(keycode))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-05-10 11:36:16 +00:00
|
|
|
struct ModeWrapper {
|
2020-12-19 04:07:20 +00:00
|
|
|
pub mode: BindingMode,
|
|
|
|
pub not_mode: BindingMode,
|
|
|
|
}
|
|
|
|
|
|
|
|
bitflags! {
|
|
|
|
/// Modes available for key bindings.
|
|
|
|
pub struct BindingMode: u8 {
|
|
|
|
const APP_CURSOR = 0b0000_0001;
|
|
|
|
const APP_KEYPAD = 0b0000_0010;
|
|
|
|
const ALT_SCREEN = 0b0000_0100;
|
|
|
|
const VI = 0b0000_1000;
|
|
|
|
const SEARCH = 0b0001_0000;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl BindingMode {
|
|
|
|
pub fn new(mode: &TermMode, search: bool) -> BindingMode {
|
|
|
|
let mut binding_mode = BindingMode::empty();
|
|
|
|
binding_mode.set(BindingMode::APP_CURSOR, mode.contains(TermMode::APP_CURSOR));
|
|
|
|
binding_mode.set(BindingMode::APP_KEYPAD, mode.contains(TermMode::APP_KEYPAD));
|
|
|
|
binding_mode.set(BindingMode::ALT_SCREEN, mode.contains(TermMode::ALT_SCREEN));
|
|
|
|
binding_mode.set(BindingMode::VI, mode.contains(TermMode::VI));
|
|
|
|
binding_mode.set(BindingMode::SEARCH, search);
|
|
|
|
binding_mode
|
|
|
|
}
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for ModeWrapper {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
|
|
|
struct ModeVisitor;
|
|
|
|
|
|
|
|
impl<'a> Visitor<'a> for ModeVisitor {
|
|
|
|
type Value = ModeWrapper;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-01-10 00:44:41 +00:00
|
|
|
f.write_str(
|
2020-03-18 02:35:08 +00:00
|
|
|
"a combination of AppCursor | AppKeypad | Alt | Vi, possibly with negation (~)",
|
2020-01-10 00:44:41 +00:00
|
|
|
)
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn visit_str<E>(self, value: &str) -> Result<ModeWrapper, E>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
2020-12-19 04:07:20 +00:00
|
|
|
let mut res =
|
|
|
|
ModeWrapper { mode: BindingMode::empty(), not_mode: BindingMode::empty() };
|
2019-05-10 11:36:16 +00:00
|
|
|
|
|
|
|
for modifier in value.split('|') {
|
|
|
|
match modifier.trim().to_lowercase().as_str() {
|
2020-12-19 04:07:20 +00:00
|
|
|
"appcursor" => res.mode |= BindingMode::APP_CURSOR,
|
|
|
|
"~appcursor" => res.not_mode |= BindingMode::APP_CURSOR,
|
|
|
|
"appkeypad" => res.mode |= BindingMode::APP_KEYPAD,
|
|
|
|
"~appkeypad" => res.not_mode |= BindingMode::APP_KEYPAD,
|
|
|
|
"alt" => res.mode |= BindingMode::ALT_SCREEN,
|
|
|
|
"~alt" => res.not_mode |= BindingMode::ALT_SCREEN,
|
|
|
|
"vi" => res.mode |= BindingMode::VI,
|
|
|
|
"~vi" => res.not_mode |= BindingMode::VI,
|
|
|
|
"search" => res.mode |= BindingMode::SEARCH,
|
|
|
|
"~search" => res.not_mode |= BindingMode::SEARCH,
|
2020-03-18 02:35:08 +00:00
|
|
|
_ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)),
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deserializer.deserialize_str(ModeVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct MouseButtonWrapper(MouseButton);
|
|
|
|
|
|
|
|
impl MouseButtonWrapper {
|
|
|
|
fn into_inner(self) -> MouseButton {
|
|
|
|
self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for MouseButtonWrapper {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
|
|
|
struct MouseButtonVisitor;
|
|
|
|
|
|
|
|
impl<'a> Visitor<'a> for MouseButtonVisitor {
|
|
|
|
type Value = MouseButtonWrapper;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-12-11 00:40:32 +00:00
|
|
|
f.write_str("Left, Right, Middle, or a number from 0 to 65536")
|
2020-05-01 23:03:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_u64<E>(self, value: u64) -> Result<MouseButtonWrapper, E>
|
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
|
|
|
match value {
|
2020-12-11 00:40:32 +00:00
|
|
|
0..=65536 => Ok(MouseButtonWrapper(MouseButton::Other(value as u16))),
|
2020-05-01 23:03:28 +00:00
|
|
|
_ => Err(E::invalid_value(Unexpected::Unsigned(value), &self)),
|
|
|
|
}
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn visit_str<E>(self, value: &str) -> Result<MouseButtonWrapper, E>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
|
|
|
match value {
|
|
|
|
"Left" => Ok(MouseButtonWrapper(MouseButton::Left)),
|
|
|
|
"Right" => Ok(MouseButtonWrapper(MouseButton::Right)),
|
|
|
|
"Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)),
|
2020-05-01 23:03:28 +00:00
|
|
|
_ => Err(E::invalid_value(Unexpected::Str(value), &self)),
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-01 23:03:28 +00:00
|
|
|
deserializer.deserialize_any(MouseButtonVisitor)
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Bindings are deserialized into a `RawBinding` before being parsed as a
|
|
|
|
/// `KeyBinding` or `MouseBinding`.
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
struct RawBinding {
|
|
|
|
key: Option<Key>,
|
|
|
|
mouse: Option<MouseButton>,
|
|
|
|
mods: ModifiersState,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode,
|
|
|
|
notmode: BindingMode,
|
2019-05-10 11:36:16 +00:00
|
|
|
action: Action,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RawBinding {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn into_mouse_binding(self) -> Result<MouseBinding, Self> {
|
2019-05-10 11:36:16 +00:00
|
|
|
if let Some(mouse) = self.mouse {
|
|
|
|
Ok(Binding {
|
|
|
|
trigger: mouse,
|
|
|
|
mods: self.mods,
|
|
|
|
action: self.action,
|
|
|
|
mode: self.mode,
|
|
|
|
notmode: self.notmode,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Err(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn into_key_binding(self) -> Result<KeyBinding, Self> {
|
2019-05-10 11:36:16 +00:00
|
|
|
if let Some(key) = self.key {
|
|
|
|
Ok(KeyBinding {
|
|
|
|
trigger: key,
|
|
|
|
mods: self.mods,
|
|
|
|
action: self.action,
|
|
|
|
mode: self.mode,
|
|
|
|
notmode: self.notmode,
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Err(self)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for RawBinding {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
2020-03-18 02:35:08 +00:00
|
|
|
const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];
|
|
|
|
|
2019-05-10 11:36:16 +00:00
|
|
|
enum Field {
|
|
|
|
Key,
|
|
|
|
Mods,
|
|
|
|
Mode,
|
|
|
|
Action,
|
|
|
|
Chars,
|
|
|
|
Mouse,
|
|
|
|
Command,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for Field {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
|
|
|
struct FieldVisitor;
|
|
|
|
|
|
|
|
impl<'a> Visitor<'a> for FieldVisitor {
|
|
|
|
type Value = Field;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str("binding fields")
|
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Field, E>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
2020-12-21 02:44:38 +00:00
|
|
|
match value.to_ascii_lowercase().as_str() {
|
2019-05-10 11:36:16 +00:00
|
|
|
"key" => Ok(Field::Key),
|
|
|
|
"mods" => Ok(Field::Mods),
|
|
|
|
"mode" => Ok(Field::Mode),
|
|
|
|
"action" => Ok(Field::Action),
|
|
|
|
"chars" => Ok(Field::Chars),
|
|
|
|
"mouse" => Ok(Field::Mouse),
|
|
|
|
"command" => Ok(Field::Command),
|
|
|
|
_ => Err(E::unknown_field(value, FIELDS)),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserializer.deserialize_str(FieldVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct RawBindingVisitor;
|
|
|
|
impl<'a> Visitor<'a> for RawBindingVisitor {
|
|
|
|
type Value = RawBinding;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
f.write_str("binding specification")
|
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn visit_map<V>(self, mut map: V) -> Result<RawBinding, V::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
V: MapAccess<'a>,
|
|
|
|
{
|
|
|
|
let mut mods: Option<ModifiersState> = None;
|
|
|
|
let mut key: Option<Key> = None;
|
|
|
|
let mut chars: Option<String> = None;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut action: Option<Action> = None;
|
2020-12-19 04:07:20 +00:00
|
|
|
let mut mode: Option<BindingMode> = None;
|
|
|
|
let mut not_mode: Option<BindingMode> = None;
|
2019-05-10 11:36:16 +00:00
|
|
|
let mut mouse: Option<MouseButton> = None;
|
2020-06-04 22:10:31 +00:00
|
|
|
let mut command: Option<Program> = None;
|
2019-05-10 11:36:16 +00:00
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
use de::Error;
|
2019-05-10 11:36:16 +00:00
|
|
|
|
|
|
|
while let Some(struct_key) = map.next_key::<Field>()? {
|
|
|
|
match struct_key {
|
|
|
|
Field::Key => {
|
|
|
|
if key.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("key"));
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
let val = map.next_value::<SerdeValue>()?;
|
2019-05-10 11:36:16 +00:00
|
|
|
if val.is_u64() {
|
|
|
|
let scancode = val.as_u64().unwrap();
|
2020-03-18 02:35:08 +00:00
|
|
|
if scancode > u64::from(std::u32::MAX) {
|
2019-05-10 11:36:16 +00:00
|
|
|
return Err(<V::Error as Error>::custom(format!(
|
|
|
|
"Invalid key binding, scancode too big: {}",
|
|
|
|
scancode
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
key = Some(Key::Scancode(scancode as u32));
|
|
|
|
} else {
|
|
|
|
let k = Key::deserialize(val).map_err(V::Error::custom)?;
|
|
|
|
key = Some(k);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
Field::Mods => {
|
|
|
|
if mods.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("mods"));
|
|
|
|
}
|
|
|
|
|
|
|
|
mods = Some(map.next_value::<ModsWrapper>()?.into_inner());
|
|
|
|
},
|
|
|
|
Field::Mode => {
|
|
|
|
if mode.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("mode"));
|
|
|
|
}
|
|
|
|
|
|
|
|
let mode_deserializer = map.next_value::<ModeWrapper>()?;
|
|
|
|
mode = Some(mode_deserializer.mode);
|
|
|
|
not_mode = Some(mode_deserializer.not_mode);
|
|
|
|
},
|
|
|
|
Field::Action => {
|
|
|
|
if action.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("action"));
|
|
|
|
}
|
|
|
|
|
2020-03-18 02:35:08 +00:00
|
|
|
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())
|
2020-12-19 04:07:20 +00:00
|
|
|
} else if let Ok(search_action) =
|
|
|
|
SearchAction::deserialize(value.clone())
|
|
|
|
{
|
|
|
|
Some(search_action.into())
|
2020-03-18 02:35:08 +00:00
|
|
|
} 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
|
|
|
|
)));
|
|
|
|
},
|
|
|
|
}
|
|
|
|
};
|
2019-05-10 11:36:16 +00:00
|
|
|
},
|
|
|
|
Field::Chars => {
|
|
|
|
if chars.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("chars"));
|
|
|
|
}
|
|
|
|
|
|
|
|
chars = Some(map.next_value()?);
|
|
|
|
},
|
|
|
|
Field::Mouse => {
|
|
|
|
if chars.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("mouse"));
|
|
|
|
}
|
|
|
|
|
|
|
|
mouse = Some(map.next_value::<MouseButtonWrapper>()?.into_inner());
|
|
|
|
},
|
|
|
|
Field::Command => {
|
|
|
|
if command.is_some() {
|
|
|
|
return Err(<V::Error as Error>::duplicate_field("command"));
|
|
|
|
}
|
|
|
|
|
2020-06-04 22:10:31 +00:00
|
|
|
command = Some(map.next_value::<Program>()?);
|
2019-05-10 11:36:16 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
let mode = mode.unwrap_or_else(BindingMode::empty);
|
|
|
|
let not_mode = not_mode.unwrap_or_else(BindingMode::empty);
|
2020-03-18 02:35:08 +00:00
|
|
|
let mods = mods.unwrap_or_else(ModifiersState::default);
|
|
|
|
|
2019-05-10 11:36:16 +00:00
|
|
|
let action = match (action, chars, command) {
|
2020-03-18 02:35:08 +00:00
|
|
|
(Some(action @ Action::ViMotion(_)), None, None)
|
|
|
|
| (Some(action @ Action::ViAction(_)), None, None) => {
|
2020-12-19 04:07:20 +00:00
|
|
|
if !mode.intersects(BindingMode::VI) || not_mode.intersects(BindingMode::VI)
|
|
|
|
{
|
2020-03-18 02:35:08 +00:00
|
|
|
return Err(V::Error::custom(format!(
|
|
|
|
"action `{}` is only available in vi mode, try adding `mode: Vi`",
|
|
|
|
action,
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
action
|
|
|
|
},
|
2020-12-19 04:07:20 +00:00
|
|
|
(Some(action @ Action::SearchAction(_)), None, None) => {
|
|
|
|
if !mode.intersects(BindingMode::SEARCH) {
|
|
|
|
return Err(V::Error::custom(format!(
|
|
|
|
"action `{}` is only available in search mode, try adding `mode: \
|
|
|
|
Search`",
|
|
|
|
action,
|
|
|
|
)));
|
|
|
|
}
|
|
|
|
action
|
|
|
|
},
|
2019-05-10 11:36:16 +00:00
|
|
|
(Some(action), None, None) => action,
|
|
|
|
(None, Some(chars), None) => Action::Esc(chars),
|
2020-06-04 22:10:31 +00:00
|
|
|
(None, None, Some(cmd)) => Action::Command(cmd),
|
2019-05-10 11:36:16 +00:00
|
|
|
_ => {
|
2020-03-18 02:35:08 +00:00
|
|
|
return Err(V::Error::custom(
|
|
|
|
"must specify exactly one of chars, action or command",
|
|
|
|
))
|
2019-05-10 11:36:16 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
if mouse.is_none() && key.is_none() {
|
|
|
|
return Err(V::Error::custom("bindings require mouse button or key"));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for MouseBinding {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
|
|
|
let raw = RawBinding::deserialize(deserializer)?;
|
2020-03-18 02:35:08 +00:00
|
|
|
raw.into_mouse_binding()
|
|
|
|
.map_err(|_| D::Error::custom("expected mouse binding, got key binding"))
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> Deserialize<'a> for KeyBinding {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'a>,
|
|
|
|
{
|
|
|
|
let raw = RawBinding::deserialize(deserializer)?;
|
2020-03-18 02:35:08 +00:00
|
|
|
raw.into_key_binding()
|
|
|
|
.map_err(|_| D::Error::custom("expected key binding, got mouse binding"))
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-05 22:50:23 +00:00
|
|
|
/// Newtype for implementing deserialize on glutin Mods.
|
2019-05-10 11:36:16 +00:00
|
|
|
///
|
|
|
|
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
|
|
|
|
/// impl below.
|
|
|
|
#[derive(Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
|
|
|
|
pub struct ModsWrapper(ModifiersState);
|
|
|
|
|
|
|
|
impl ModsWrapper {
|
|
|
|
pub fn into_inner(self) -> ModifiersState {
|
|
|
|
self.0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> de::Deserialize<'a> for ModsWrapper {
|
2020-01-07 15:59:23 +00:00
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
D: de::Deserializer<'a>,
|
|
|
|
{
|
|
|
|
struct ModsVisitor;
|
|
|
|
|
|
|
|
impl<'a> Visitor<'a> for ModsVisitor {
|
|
|
|
type Value = ModsWrapper;
|
|
|
|
|
|
|
|
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2020-03-18 02:35:08 +00:00
|
|
|
f.write_str("a subset of Shift|Control|Super|Command|Alt|Option")
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
|
2020-01-07 15:59:23 +00:00
|
|
|
fn visit_str<E>(self, value: &str) -> Result<ModsWrapper, E>
|
2019-05-10 11:36:16 +00:00
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
2020-01-05 01:00:50 +00:00
|
|
|
let mut res = ModifiersState::empty();
|
2019-05-10 11:36:16 +00:00
|
|
|
for modifier in value.split('|') {
|
|
|
|
match modifier.trim().to_lowercase().as_str() {
|
2020-01-05 01:00:50 +00:00
|
|
|
"command" | "super" => res.insert(ModifiersState::LOGO),
|
|
|
|
"shift" => res.insert(ModifiersState::SHIFT),
|
|
|
|
"alt" | "option" => res.insert(ModifiersState::ALT),
|
|
|
|
"control" => res.insert(ModifiersState::CTRL),
|
2019-05-10 11:36:16 +00:00
|
|
|
"none" => (),
|
2020-03-18 02:35:08 +00:00
|
|
|
_ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)),
|
2019-05-10 11:36:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(ModsWrapper(res))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserializer.deserialize_str(ModsVisitor)
|
|
|
|
}
|
|
|
|
}
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
2020-01-28 12:32:35 +00:00
|
|
|
mod tests {
|
2020-12-19 04:07:20 +00:00
|
|
|
use super::*;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
use glutin::event::ModifiersState;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
type MockBinding = Binding<usize>;
|
|
|
|
|
|
|
|
impl Default for MockBinding {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
mods: Default::default(),
|
2020-03-18 02:35:08 +00:00
|
|
|
action: Action::None,
|
2020-12-19 04:07:20 +00:00
|
|
|
mode: BindingMode::empty(),
|
|
|
|
notmode: BindingMode::empty(),
|
2019-10-05 00:29:26 +00:00
|
|
|
trigger: Default::default(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_itself() {
|
|
|
|
let binding = MockBinding::default();
|
|
|
|
let identical_binding = MockBinding::default();
|
|
|
|
|
|
|
|
assert!(binding.triggers_match(&identical_binding));
|
|
|
|
assert!(identical_binding.triggers_match(&binding));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_different_action() {
|
|
|
|
let binding = MockBinding::default();
|
|
|
|
let mut different_action = MockBinding::default();
|
|
|
|
different_action.action = Action::ClearHistory;
|
|
|
|
|
|
|
|
assert!(binding.triggers_match(&different_action));
|
|
|
|
assert!(different_action.triggers_match(&binding));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn mods_binding_requires_strict_match() {
|
|
|
|
let mut superset_mods = MockBinding::default();
|
2020-01-05 01:00:50 +00:00
|
|
|
superset_mods.mods = ModifiersState::all();
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut subset_mods = MockBinding::default();
|
2020-01-05 01:00:50 +00:00
|
|
|
subset_mods.mods = ModifiersState::ALT;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(!superset_mods.triggers_match(&subset_mods));
|
|
|
|
assert!(!subset_mods.triggers_match(&superset_mods));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_identical_mode() {
|
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.mode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_without_mode_matches_any_mode() {
|
|
|
|
let b1 = MockBinding::default();
|
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.mode = BindingMode::APP_KEYPAD;
|
|
|
|
b2.notmode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_with_mode_matches_empty_mode() {
|
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::APP_KEYPAD;
|
|
|
|
b1.notmode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
let b2 = MockBinding::default();
|
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2020-06-15 00:12:39 +00:00
|
|
|
fn binding_matches_modes() {
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::ALT_SCREEN | BindingMode::APP_KEYPAD;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.mode = BindingMode::APP_KEYPAD;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_partial_intersection() {
|
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::ALT_SCREEN | BindingMode::APP_KEYPAD;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.mode = BindingMode::APP_KEYPAD | BindingMode::APP_CURSOR;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_mismatches_notmode() {
|
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.notmode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(!b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(!b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_mismatches_unrelated() {
|
|
|
|
let mut b1 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b2.mode = BindingMode::APP_KEYPAD;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
assert!(!b1.triggers_match(&b2));
|
2020-06-15 00:12:39 +00:00
|
|
|
assert!(!b2.triggers_match(&b1));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_notmodes() {
|
|
|
|
let mut subset_notmodes = MockBinding::default();
|
|
|
|
let mut superset_notmodes = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
subset_notmodes.notmode = BindingMode::VI | BindingMode::APP_CURSOR;
|
|
|
|
superset_notmodes.notmode = BindingMode::APP_CURSOR;
|
2020-06-15 00:12:39 +00:00
|
|
|
|
|
|
|
assert!(subset_notmodes.triggers_match(&superset_notmodes));
|
|
|
|
assert!(superset_notmodes.triggers_match(&subset_notmodes));
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_matches_mode_notmode() {
|
|
|
|
let mut b1 = MockBinding::default();
|
|
|
|
let mut b2 = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
b1.mode = BindingMode::VI;
|
|
|
|
b1.notmode = BindingMode::APP_CURSOR;
|
|
|
|
b2.notmode = BindingMode::APP_CURSOR;
|
2020-06-15 00:12:39 +00:00
|
|
|
|
|
|
|
assert!(b1.triggers_match(&b2));
|
|
|
|
assert!(b2.triggers_match(&b1));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_trigger_input() {
|
|
|
|
let mut binding = MockBinding::default();
|
|
|
|
binding.trigger = 13;
|
|
|
|
|
|
|
|
let mods = binding.mods;
|
|
|
|
let mode = binding.mode;
|
|
|
|
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!(binding.is_triggered_by(mode, mods, &13));
|
|
|
|
assert!(!binding.is_triggered_by(mode, mods, &32));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_trigger_mods() {
|
|
|
|
let mut binding = MockBinding::default();
|
2020-01-05 01:00:50 +00:00
|
|
|
binding.mods = ModifiersState::ALT | ModifiersState::LOGO;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
2020-01-05 01:00:50 +00:00
|
|
|
let superset_mods = ModifiersState::all();
|
|
|
|
let subset_mods = ModifiersState::empty();
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
let t = binding.trigger;
|
|
|
|
let mode = binding.mode;
|
|
|
|
|
2020-01-10 00:44:41 +00:00
|
|
|
assert!(binding.is_triggered_by(mode, binding.mods, &t));
|
|
|
|
assert!(!binding.is_triggered_by(mode, superset_mods, &t));
|
|
|
|
assert!(!binding.is_triggered_by(mode, subset_mods, &t));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_trigger_modes() {
|
|
|
|
let mut binding = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
binding.mode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
let t = binding.trigger;
|
|
|
|
let mods = binding.mods;
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
assert!(!binding.is_triggered_by(BindingMode::VI, mods, &t));
|
|
|
|
assert!(binding.is_triggered_by(BindingMode::ALT_SCREEN, mods, &t));
|
|
|
|
assert!(binding.is_triggered_by(BindingMode::ALT_SCREEN | BindingMode::VI, mods, &t));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn binding_trigger_notmodes() {
|
|
|
|
let mut binding = MockBinding::default();
|
2020-12-19 04:07:20 +00:00
|
|
|
binding.notmode = BindingMode::ALT_SCREEN;
|
2019-10-05 00:29:26 +00:00
|
|
|
|
|
|
|
let t = binding.trigger;
|
|
|
|
let mods = binding.mods;
|
|
|
|
|
2020-12-19 04:07:20 +00:00
|
|
|
assert!(binding.is_triggered_by(BindingMode::VI, mods, &t));
|
|
|
|
assert!(!binding.is_triggered_by(BindingMode::ALT_SCREEN, mods, &t));
|
|
|
|
assert!(!binding.is_triggered_by(BindingMode::ALT_SCREEN | BindingMode::VI, mods, &t));
|
2019-10-05 00:29:26 +00:00
|
|
|
}
|
|
|
|
}
|