alacritty/alacritty/src/config/bindings.rs

1447 lines
55 KiB
Rust
Raw Normal View History

2020-01-06 22:06:09 +00:00
#![allow(clippy::enum_glob_use)]
use std::fmt::{self, Debug, Display};
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};
use serde::de::{self, Error as SerdeError, MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use serde_yaml::Value as SerdeValue;
use alacritty_config_derive::{ConfigDeserialize, SerdeReplace};
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
use alacritty_terminal::config::Program;
use alacritty_terminal::term::TermMode;
use alacritty_terminal::vi_mode::ViMotion;
use crate::config::ui_config::Hint;
2020-05-05 22:50:23 +00:00
/// Describes a state and action to take in that state.
///
2020-05-05 22:50:23 +00:00
/// This is the shared component of `MouseBinding` and `KeyBinding`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Binding<T> {
2020-05-05 22:50:23 +00:00
/// Modifier keys required to activate binding.
pub mods: ModifiersState,
2020-05-05 22:50:23 +00:00
/// String to send to PTY if mods and mode match.
pub action: Action,
2020-12-19 04:07:20 +00:00
/// Binding mode required to activate binding.
pub mode: BindingMode,
2020-12-19 04:07:20 +00:00
/// Excluded binding modes where the binding won't be activated.
pub notmode: BindingMode,
/// 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.
pub type KeyBinding = Binding<Key>;
2020-05-05 22:50:23 +00:00
/// Bindings that are triggered by a mouse button.
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 {
// 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
&& self.mods == mods
&& 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.
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 };
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;
}
true
}
}
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[derive(ConfigDeserialize, Debug, Clone, PartialEq, Eq)]
pub enum Action {
/// Write an escape sequence.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[config(skip)]
Esc(String),
/// Run given command.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[config(skip)]
Command(Program),
/// Regex keyboard hints.
#[config(skip)]
Hint(Hint),
/// Move vi mode cursor.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[config(skip)]
ViMotion(ViMotion),
/// Perform vi mode action.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[config(skip)]
2021-07-03 03:06:52 +00:00
Vi(ViAction),
2020-12-19 04:07:20 +00:00
/// Perform search mode action.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[config(skip)]
2021-07-03 03:06:52 +00:00
Search(SearchAction),
2020-12-19 04:07:20 +00:00
/// Perform mouse binding exclusive action.
#[config(skip)]
Mouse(MouseAction),
/// Paste contents of system clipboard.
Paste,
/// Store current selection into clipboard.
Copy,
#[cfg(not(any(target_os = "macos", windows)))]
/// Store current selection into selection buffer.
CopySelection,
/// 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,
/// Scroll half a page up.
ScrollHalfPageUp,
/// Scroll half a page down.
ScrollHalfPageDown,
/// 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,
/// Hide all windows other than Alacritty on macOS.
#[cfg(target_os = "macos")]
HideOtherApplications,
/// Minimize the Alacritty window.
Minimize,
/// Quit Alacritty.
Quit,
/// Clear warning and error notices.
ClearLogNotice,
/// Spawn a new instance of Alacritty.
SpawnNewInstance,
/// Create a new Alacritty window.
CreateNewWindow,
/// Toggle fullscreen.
ToggleFullscreen,
/// Toggle maximized.
ToggleMaximized,
2020-05-05 22:50:23 +00:00
/// Toggle simple fullscreen on macOS.
#[cfg(target_os = "macos")]
ToggleSimpleFullscreen,
/// Clear active selection.
ClearSelection,
/// Toggle vi mode.
ToggleViMode,
/// Allow receiving char input.
ReceiveChar,
/// Start a forward buffer search.
SearchForward,
/// Start a backward buffer search.
SearchBackward,
/// 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 {
2021-07-03 03:06:52 +00:00
Self::Vi(action)
2020-12-19 04:07:20 +00:00
}
}
impl From<ViMotion> for Action {
fn from(motion: ViMotion) -> Self {
Self::ViMotion(motion)
}
}
impl From<SearchAction> for Action {
fn from(action: SearchAction) -> Self {
2021-07-03 03:06:52 +00:00
Self::Search(action)
2020-12-19 04:07:20 +00:00
}
}
impl From<MouseAction> for Action {
fn from(action: MouseAction) -> Self {
Self::Mouse(action)
}
}
/// 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),
2021-07-03 03:06:52 +00:00
Action::Vi(action) => action.fmt(f),
Action::Mouse(action) => action.fmt(f),
_ => write!(f, "{:?}", self),
}
}
}
/// Vi mode specific actions.
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
#[derive(ConfigDeserialize, 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,
/// Jump to the beginning of the next match.
SearchNext,
/// Jump to the beginning of the previous match.
SearchPrevious,
/// 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,
/// Launch the URL below the vi mode cursor.
Open,
/// Centers the screen around the vi mode cursor.
CenterAroundViCursor,
}
2020-12-19 04:07:20 +00:00
/// Search mode specific actions.
2021-07-03 03:06:52 +00:00
#[allow(clippy::enum_variant_names)]
#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
2020-12-19 04:07:20 +00:00
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,
/// Go to the previous regex in the search history.
SearchHistoryPrevious,
/// Go to the next regex in the search history.
SearchHistoryNext,
}
/// Mouse binding specific actions.
#[derive(ConfigDeserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub enum MouseAction {
/// Expand the selection to the current mouse cursor position.
ExpandSelection,
}
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
);*
)
}};
(
$ty:ident;
$(
2020-01-06 22:06:09 +00:00
$key:expr
$(,$mods:expr)*
$(,+$mode:expr)*
$(,~$notmode:expr)*
;$action:expr
);*
$(;)*
) => {{
let mut v = Vec::new();
$(
let mut _mods = ModifiersState::empty();
$(_mods = $mods;)*
2020-12-19 04:07:20 +00:00
let mut _mode = BindingMode::empty();
$(_mode.insert($mode);)*
2020-12-19 04:07:20 +00:00
let mut _notmode = BindingMode::empty();
$(_notmode.insert($notmode);)*
v.push($ty {
trigger: $key,
mods: _mods,
mode: _mode,
notmode: _notmode,
action: $action.into(),
});
)*
v
2020-01-06 22:06:09 +00:00
}};
}
pub fn default_mouse_bindings() -> Vec<MouseBinding> {
bindings!(
MouseBinding;
MouseButton::Right; MouseAction::ExpandSelection;
MouseButton::Right, ModifiersState::CTRL; MouseAction::ExpandSelection;
2020-12-19 04:07:20 +00:00
MouseButton::Middle, ~BindingMode::VI; Action::PasteSelection;
)
}
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::SEARCH;
Action::ToggleViMode;
Space, ModifiersState::SHIFT | ModifiersState::CTRL, +BindingMode::VI, ~BindingMode::SEARCH;
Action::ScrollToBottom;
2020-12-19 04:07:20 +00:00
Escape, +BindingMode::VI, ~BindingMode::SEARCH;
Action::ClearSelection;
I, +BindingMode::VI, ~BindingMode::SEARCH;
Action::ToggleViMode;
I, +BindingMode::VI, ~BindingMode::SEARCH;
Action::ScrollToBottom;
2020-12-19 04:07:20 +00:00
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;
Z, +BindingMode::VI, ~BindingMode::SEARCH;
ViAction::CenterAroundViCursor;
2020-12-19 04:07:20 +00:00
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;
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;
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;
);
// 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
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,
];
for (index, mods) in modifiers.drain(..).enumerate() {
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));
));
2020-05-05 22:50:23 +00:00
// We're adding the following bindings with `Shift` manually above, so skipping them here.
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));
));
}
}
bindings.extend(platform_key_bindings());
bindings
}
#[cfg(not(any(target_os = "macos", test)))]
fn common_keybindings() -> Vec<KeyBinding> {
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;
Plus, ModifiersState::CTRL; Action::IncreaseFontSize;
NumpadAdd, ModifiersState::CTRL; Action::IncreaseFontSize;
Minus, ModifiersState::CTRL; Action::DecreaseFontSize;
NumpadSubtract, ModifiersState::CTRL; Action::DecreaseFontSize;
)
}
#[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;
);
bindings.extend(common_keybindings());
bindings
}
#[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;
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;
H, ModifiersState::LOGO | ModifiersState::ALT; Action::HideOtherApplications;
2020-01-06 22:06:09 +00:00
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;
)
}
2020-05-05 22:50:23 +00:00
// Don't return any bindings for tests since they are commented-out by default.
#[cfg(test)]
pub fn platform_key_bindings() -> Vec<KeyBinding> {
vec![]
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Key {
Scancode(u32),
2020-01-06 22:06:09 +00:00
Keycode(VirtualKeyCode),
}
impl<'a> Deserialize<'a> for Key {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let value = SerdeValue::deserialize(deserializer)?;
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))
},
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub 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
}
}
impl Default for ModeWrapper {
fn default() -> Self {
Self { mode: BindingMode::empty(), not_mode: BindingMode::empty() }
}
}
impl<'a> Deserialize<'a> for ModeWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
struct ModeVisitor;
impl<'a> Visitor<'a> for ModeVisitor {
type Value = ModeWrapper;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
"a combination of AppCursor | AppKeypad | Alt | Vi, possibly with negation (~)",
)
}
fn visit_str<E>(self, value: &str) -> Result<ModeWrapper, E>
where
E: de::Error,
{
2020-12-19 04:07:20 +00:00
let mut res =
ModeWrapper { mode: BindingMode::empty(), not_mode: BindingMode::empty() };
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,
_ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)),
}
}
Ok(res)
}
}
deserializer.deserialize_str(ModeVisitor)
}
}
struct MouseButtonWrapper(MouseButton);
impl MouseButtonWrapper {
fn into_inner(self) -> MouseButton {
self.0
}
}
impl<'a> Deserialize<'a> for MouseButtonWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
struct MouseButtonVisitor;
impl<'a> Visitor<'a> for MouseButtonVisitor {
type Value = MouseButtonWrapper;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("Left, Right, Middle, or a number from 0 to 65536")
}
fn visit_u64<E>(self, value: u64) -> Result<MouseButtonWrapper, E>
where
E: de::Error,
{
match value {
0..=65536 => Ok(MouseButtonWrapper(MouseButton::Other(value as u16))),
_ => Err(E::invalid_value(Unexpected::Unsigned(value), &self)),
}
}
fn visit_str<E>(self, value: &str) -> Result<MouseButtonWrapper, E>
where
E: de::Error,
{
match value {
"Left" => Ok(MouseButtonWrapper(MouseButton::Left)),
"Right" => Ok(MouseButtonWrapper(MouseButton::Right)),
"Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)),
_ => Err(E::invalid_value(Unexpected::Str(value), &self)),
}
}
}
deserializer.deserialize_any(MouseButtonVisitor)
}
}
/// 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,
action: Action,
}
impl RawBinding {
fn into_mouse_binding(self) -> Result<MouseBinding, Self> {
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)
}
}
fn into_key_binding(self) -> Result<KeyBinding, Self> {
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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];
enum Field {
Key,
Mods,
Mode,
Action,
Chars,
Mouse,
Command,
}
impl<'a> Deserialize<'a> for Field {
fn deserialize<D>(deserializer: D) -> Result<Field, D::Error>
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")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
match value.to_ascii_lowercase().as_str() {
"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")
}
fn visit_map<V>(self, mut map: V) -> Result<RawBinding, V::Error>
where
V: MapAccess<'a>,
{
let mut mods: Option<ModifiersState> = None;
let mut key: Option<Key> = None;
let mut chars: Option<String> = None;
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;
let mut mouse: Option<MouseButton> = None;
let mut command: Option<Program> = None;
use de::Error;
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"));
}
let val = map.next_value::<SerdeValue>()?;
if val.is_u64() {
let scancode = val.as_u64().unwrap();
2022-06-02 01:22:50 +00:00
if scancode > u64::from(u32::MAX) {
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"));
}
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())
} else if let Ok(mouse_action) = MouseAction::deserialize(value.clone())
{
Some(mouse_action.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() {
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"));
}
command = Some(map.next_value::<Program>()?);
},
}
}
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);
let mods = mods.unwrap_or_default();
let action = match (action, chars, command) {
(Some(action @ Action::ViMotion(_)), None, None)
2021-07-03 03:06:52 +00:00
| (Some(action @ Action::Vi(_)), None, None) => {
2020-12-19 04:07:20 +00:00
if !mode.intersects(BindingMode::VI) || not_mode.intersects(BindingMode::VI)
{
return Err(V::Error::custom(format!(
"action `{}` is only available in vi mode, try adding `mode: Vi`",
action,
)));
}
action
},
2021-07-03 03:06:52 +00:00
(Some(action @ Action::Search(_)), None, None) => {
2020-12-19 04:07:20 +00:00
if !mode.intersects(BindingMode::SEARCH) {
return Err(V::Error::custom(format!(
"action `{}` is only available in search mode, try adding `mode: \
Search`",
action,
)));
}
action
},
(Some(action @ Action::Mouse(_)), None, None) => {
if mouse.is_none() {
return Err(V::Error::custom(format!(
"action `{}` is only available for mouse bindings",
action,
)));
}
action
},
(Some(action), None, None) => action,
(None, Some(chars), None) => Action::Esc(chars),
(None, None, Some(cmd)) => Action::Command(cmd),
_ => {
return Err(V::Error::custom(
"must specify exactly one of chars, action or command",
));
},
};
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 {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let raw = RawBinding::deserialize(deserializer)?;
raw.into_mouse_binding()
.map_err(|_| D::Error::custom("expected mouse binding, got key binding"))
}
}
impl<'a> Deserialize<'a> for KeyBinding {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'a>,
{
let raw = RawBinding::deserialize(deserializer)?;
raw.into_key_binding()
.map_err(|_| D::Error::custom("expected key binding, got mouse binding"))
}
}
2020-05-05 22:50:23 +00:00
/// Newtype for implementing deserialize on glutin Mods.
///
/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the
/// impl below.
#[derive(SerdeReplace, Debug, Copy, Clone, Hash, Default, Eq, PartialEq)]
pub struct ModsWrapper(pub ModifiersState);
impl ModsWrapper {
pub fn into_inner(self) -> ModifiersState {
self.0
}
}
impl<'a> de::Deserialize<'a> for ModsWrapper {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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 {
f.write_str("None or a subset of Shift|Control|Super|Command|Alt|Option")
}
fn visit_str<E>(self, value: &str) -> Result<ModsWrapper, E>
where
E: de::Error,
{
let mut res = ModifiersState::empty();
for modifier in value.split('|') {
match modifier.trim().to_lowercase().as_str() {
"command" | "super" => res.insert(ModifiersState::LOGO),
"shift" => res.insert(ModifiersState::SHIFT),
"alt" | "option" => res.insert(ModifiersState::ALT),
"control" => res.insert(ModifiersState::CTRL),
"none" => (),
_ => return Err(E::invalid_value(Unexpected::Str(modifier), &self)),
}
}
Ok(ModsWrapper(res))
}
}
deserializer.deserialize_str(ModsVisitor)
}
}
#[cfg(test)]
2020-01-28 12:32:35 +00:00
mod tests {
2020-12-19 04:07:20 +00:00
use super::*;
2020-12-19 04:07:20 +00:00
use glutin::event::ModifiersState;
type MockBinding = Binding<usize>;
impl Default for MockBinding {
fn default() -> Self {
Self {
mods: Default::default(),
action: Action::None,
2020-12-19 04:07:20 +00:00
mode: BindingMode::empty(),
notmode: BindingMode::empty(),
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();
2021-01-01 05:07:39 +00:00
let different_action =
MockBinding { action: Action::ClearHistory, ..MockBinding::default() };
assert!(binding.triggers_match(&different_action));
assert!(different_action.triggers_match(&binding));
}
#[test]
fn mods_binding_requires_strict_match() {
2021-01-01 05:07:39 +00:00
let superset_mods = MockBinding { mods: ModifiersState::all(), ..MockBinding::default() };
let subset_mods = MockBinding { mods: ModifiersState::ALT, ..MockBinding::default() };
assert!(!superset_mods.triggers_match(&subset_mods));
assert!(!subset_mods.triggers_match(&superset_mods));
}
#[test]
fn binding_matches_identical_mode() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding { mode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
let b2 = MockBinding { mode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
assert!(b1.triggers_match(&b2));
assert!(b2.triggers_match(&b1));
}
#[test]
fn binding_without_mode_matches_any_mode() {
let b1 = MockBinding::default();
2021-01-01 05:07:39 +00:00
let b2 = MockBinding {
mode: BindingMode::APP_KEYPAD,
notmode: BindingMode::ALT_SCREEN,
..MockBinding::default()
};
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_with_mode_matches_empty_mode() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding {
mode: BindingMode::APP_KEYPAD,
notmode: BindingMode::ALT_SCREEN,
..MockBinding::default()
};
let b2 = MockBinding::default();
assert!(b1.triggers_match(&b2));
assert!(b2.triggers_match(&b1));
}
#[test]
fn binding_matches_modes() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding {
mode: BindingMode::ALT_SCREEN | BindingMode::APP_KEYPAD,
..MockBinding::default()
};
let b2 = MockBinding { mode: BindingMode::APP_KEYPAD, ..MockBinding::default() };
assert!(b1.triggers_match(&b2));
assert!(b2.triggers_match(&b1));
}
#[test]
fn binding_matches_partial_intersection() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding {
mode: BindingMode::ALT_SCREEN | BindingMode::APP_KEYPAD,
..MockBinding::default()
};
let b2 = MockBinding {
mode: BindingMode::APP_KEYPAD | BindingMode::APP_CURSOR,
..MockBinding::default()
};
assert!(b1.triggers_match(&b2));
assert!(b2.triggers_match(&b1));
}
#[test]
fn binding_mismatches_notmode() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding { mode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
let b2 = MockBinding { notmode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
assert!(!b1.triggers_match(&b2));
assert!(!b2.triggers_match(&b1));
}
#[test]
fn binding_mismatches_unrelated() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding { mode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
let b2 = MockBinding { mode: BindingMode::APP_KEYPAD, ..MockBinding::default() };
assert!(!b1.triggers_match(&b2));
assert!(!b2.triggers_match(&b1));
}
#[test]
fn binding_matches_notmodes() {
2021-01-01 05:07:39 +00:00
let subset_notmodes = MockBinding {
notmode: BindingMode::VI | BindingMode::APP_CURSOR,
..MockBinding::default()
};
let superset_notmodes =
MockBinding { notmode: BindingMode::APP_CURSOR, ..MockBinding::default() };
assert!(subset_notmodes.triggers_match(&superset_notmodes));
assert!(superset_notmodes.triggers_match(&subset_notmodes));
}
#[test]
fn binding_matches_mode_notmode() {
2021-01-01 05:07:39 +00:00
let b1 = MockBinding {
mode: BindingMode::VI,
notmode: BindingMode::APP_CURSOR,
..MockBinding::default()
};
let b2 = MockBinding { notmode: BindingMode::APP_CURSOR, ..MockBinding::default() };
assert!(b1.triggers_match(&b2));
assert!(b2.triggers_match(&b1));
}
#[test]
fn binding_trigger_input() {
2021-01-01 05:07:39 +00:00
let binding = MockBinding { trigger: 13, ..MockBinding::default() };
let mods = binding.mods;
let mode = binding.mode;
assert!(binding.is_triggered_by(mode, mods, &13));
assert!(!binding.is_triggered_by(mode, mods, &32));
}
#[test]
fn binding_trigger_mods() {
2021-01-01 05:07:39 +00:00
let binding = MockBinding {
mods: ModifiersState::ALT | ModifiersState::LOGO,
..MockBinding::default()
};
let superset_mods = ModifiersState::all();
let subset_mods = ModifiersState::empty();
let t = binding.trigger;
let mode = binding.mode;
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));
}
#[test]
fn binding_trigger_modes() {
2021-01-01 05:07:39 +00:00
let binding = MockBinding { mode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
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));
}
#[test]
fn binding_trigger_notmodes() {
2021-01-01 05:07:39 +00:00
let binding = MockBinding { notmode: BindingMode::ALT_SCREEN, ..MockBinding::default() };
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));
}
}