blob: bdbb7c07815febf49e42720d7b9d38b7fc99ab2d [file] [log] [blame]
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(clippy::enum_glob_use)]
use std::fmt;
use std::str::FromStr;
use glutin::event::VirtualKeyCode::*;
use glutin::event::{ModifiersState, MouseButton, VirtualKeyCode};
use log::error;
use serde::de::Error as SerdeError;
use serde::de::{self, MapAccess, Unexpected, Visitor};
use serde::{Deserialize, Deserializer};
use alacritty_terminal::config::LOG_TARGET_CONFIG;
use alacritty_terminal::term::TermMode;
/// Describes a state and action to take in that state
///
/// This is the shared component of `MouseBinding` and `KeyBinding`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Binding<T> {
/// Modifier keys required to activate binding
pub mods: ModifiersState,
/// String to send to pty if mods and mode match
pub action: Action,
/// Terminal mode required to activate binding
pub mode: TermMode,
/// excluded terminal modes where the binding won't be activated
pub notmode: TermMode,
/// 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,
}
/// Bindings that are triggered by a keyboard key
pub type KeyBinding = Binding<Key>;
/// Bindings that are triggered by a mouse button
pub type MouseBinding = Binding<MouseButton>;
impl Default for KeyBinding {
fn default() -> KeyBinding {
KeyBinding {
mods: Default::default(),
action: Action::Esc(String::new()),
mode: TermMode::NONE,
notmode: TermMode::NONE,
trigger: Key::Keycode(A),
}
}
}
impl Default for MouseBinding {
fn default() -> MouseBinding {
MouseBinding {
mods: Default::default(),
action: Action::Esc(String::new()),
mode: TermMode::NONE,
notmode: TermMode::NONE,
trigger: MouseButton::Left,
}
}
}
impl<T: Eq> Binding<T> {
#[inline]
pub fn is_triggered_by(
&self,
mode: TermMode,
mods: ModifiersState,
input: &T,
relaxed: bool,
) -> 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
&& mode.contains(self.mode)
&& !mode.intersects(self.notmode)
&& (self.mods == mods || (relaxed && self.mods.relaxed_eq(mods)))
}
#[inline]
pub fn triggers_match(&self, binding: &Binding<T>) -> bool {
// Check the binding's key and modifiers
if self.trigger != binding.trigger || self.mods != binding.mods {
return false;
}
// Completely empty modes match all modes
if (self.mode.is_empty() && self.notmode.is_empty())
|| (binding.mode.is_empty() && binding.notmode.is_empty())
{
return true;
}
// Check for intersection (equality is required since empty does not intersect itself)
(self.mode == binding.mode || self.mode.intersects(binding.mode))
&& (self.notmode == binding.notmode || self.notmode.intersects(binding.notmode))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub enum Action {
/// Write an escape sequence.
#[serde(skip)]
Esc(String),
/// Paste contents of system clipboard.
Paste,
/// Store current selection into clipboard.
Copy,
/// 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 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,
/// Run given command.
#[serde(skip)]
Command(String, Vec<String>),
/// Hide the Alacritty window.
Hide,
/// Minimize the Alacritty window.
Minimize,
/// Quit Alacritty.
Quit,
/// Clear warning and error notices.
ClearLogNotice,
/// Spawn a new instance of Alacritty.
SpawnNewInstance,
/// Toggle fullscreen.
ToggleFullscreen,
/// Toggle simple fullscreen on macos.
#[cfg(target_os = "macos")]
ToggleSimpleFullscreen,
/// Allow receiving char input.
ReceiveChar,
/// No action.
None,
}
impl Default for Action {
fn default() -> Action {
Action::None
}
}
impl From<&'static str> for Action {
fn from(s: &'static str) -> Action {
Action::Esc(s.into())
}
}
pub trait RelaxedEq<T: ?Sized = Self> {
fn relaxed_eq(&self, other: T) -> bool;
}
impl RelaxedEq for ModifiersState {
// Make sure that modifiers in the config are always present,
// but ignore surplus modifiers.
fn relaxed_eq(&self, other: Self) -> bool {
!*self | other == ModifiersState::all()
}
}
macro_rules! bindings {
(
KeyBinding;
$(
$key:ident
$(,$mods:expr)*
$(,+$mode:expr)*
$(,~$notmode:expr)*
;$action:expr
);*
$(;)*
) => {{
bindings!(
KeyBinding;
$(
Key::Keycode($key)
$(,$mods)*
$(,+$mode)*
$(,~$notmode)*
;$action
);*
)
}};
(
$ty:ident;
$(
$key:expr
$(,$mods:expr)*
$(,+$mode:expr)*
$(,~$notmode:expr)*
;$action:expr
);*
$(;)*
) => {{
let mut v = Vec::new();
$(
let mut _mods = ModifiersState::empty();
$(_mods = $mods;)*
let mut _mode = TermMode::empty();
$(_mode = $mode;)*
let mut _notmode = TermMode::empty();
$(_notmode = $notmode;)*
v.push($ty {
trigger: $key,
mods: _mods,
mode: _mode,
notmode: _notmode,
action: $action,
});
)*
v
}};
}
pub fn default_mouse_bindings() -> Vec<MouseBinding> {
bindings!(
MouseBinding;
MouseButton::Middle; Action::PasteSelection;
)
}
pub fn default_key_bindings() -> Vec<KeyBinding> {
let mut bindings = bindings!(
KeyBinding;
Paste; Action::Paste;
Copy; Action::Copy;
L, ModifiersState::CTRL; Action::ClearLogNotice;
L, ModifiersState::CTRL; Action::Esc("\x0c".into());
PageUp, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageUp;
PageDown, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollPageDown;
Home, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToTop;
End, ModifiersState::SHIFT, ~TermMode::ALT_SCREEN; Action::ScrollToBottom;
Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into());
Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into());
Home, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into());
End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into());
End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into());
End, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into());
PageUp; Action::Esc("\x1b[5~".into());
PageUp, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into());
PageDown; Action::Esc("\x1b[6~".into());
PageDown, ModifiersState::SHIFT, +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into());
Tab, ModifiersState::SHIFT; Action::Esc("\x1b[Z".into());
Back; Action::Esc("\x7f".into());
Back, ModifiersState::ALT; Action::Esc("\x1b\x7f".into());
Insert; Action::Esc("\x1b[2~".into());
Delete; Action::Esc("\x1b[3~".into());
Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into());
Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into());
Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into());
Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into());
Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into());
Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into());
Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into());
Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into());
F1; Action::Esc("\x1bOP".into());
F2; Action::Esc("\x1bOQ".into());
F3; Action::Esc("\x1bOR".into());
F4; Action::Esc("\x1bOS".into());
F5; Action::Esc("\x1b[15~".into());
F6; Action::Esc("\x1b[17~".into());
F7; Action::Esc("\x1b[18~".into());
F8; Action::Esc("\x1b[19~".into());
F9; Action::Esc("\x1b[20~".into());
F10; Action::Esc("\x1b[21~".into());
F11; Action::Esc("\x1b[23~".into());
F12; Action::Esc("\x1b[24~".into());
F13; Action::Esc("\x1b[25~".into());
F14; Action::Esc("\x1b[26~".into());
F15; Action::Esc("\x1b[28~".into());
F16; Action::Esc("\x1b[29~".into());
F17; Action::Esc("\x1b[31~".into());
F18; Action::Esc("\x1b[32~".into());
F19; Action::Esc("\x1b[33~".into());
F20; Action::Esc("\x1b[34~".into());
NumpadEnter; Action::Esc("\n".into());
);
// 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;
Delete, mods; Action::Esc(format!("\x1b[3;{}~", modifiers_code));
Up, mods; Action::Esc(format!("\x1b[1;{}A", modifiers_code));
Down, mods; Action::Esc(format!("\x1b[1;{}B", modifiers_code));
Right, mods; Action::Esc(format!("\x1b[1;{}C", modifiers_code));
Left, mods; Action::Esc(format!("\x1b[1;{}D", modifiers_code));
F1, mods; Action::Esc(format!("\x1b[1;{}P", modifiers_code));
F2, mods; Action::Esc(format!("\x1b[1;{}Q", modifiers_code));
F3, mods; Action::Esc(format!("\x1b[1;{}R", modifiers_code));
F4, mods; Action::Esc(format!("\x1b[1;{}S", modifiers_code));
F5, mods; Action::Esc(format!("\x1b[15;{}~", modifiers_code));
F6, mods; Action::Esc(format!("\x1b[17;{}~", modifiers_code));
F7, mods; Action::Esc(format!("\x1b[18;{}~", modifiers_code));
F8, mods; Action::Esc(format!("\x1b[19;{}~", modifiers_code));
F9, mods; Action::Esc(format!("\x1b[20;{}~", modifiers_code));
F10, mods; Action::Esc(format!("\x1b[21;{}~", modifiers_code));
F11, mods; Action::Esc(format!("\x1b[23;{}~", modifiers_code));
F12, mods; Action::Esc(format!("\x1b[24;{}~", modifiers_code));
F13, mods; Action::Esc(format!("\x1b[25;{}~", modifiers_code));
F14, mods; Action::Esc(format!("\x1b[26;{}~", modifiers_code));
F15, mods; Action::Esc(format!("\x1b[28;{}~", modifiers_code));
F16, mods; Action::Esc(format!("\x1b[29;{}~", modifiers_code));
F17, mods; Action::Esc(format!("\x1b[31;{}~", modifiers_code));
F18, mods; Action::Esc(format!("\x1b[32;{}~", modifiers_code));
F19, mods; Action::Esc(format!("\x1b[33;{}~", modifiers_code));
F20, mods; Action::Esc(format!("\x1b[34;{}~", modifiers_code));
));
// We're adding the following bindings with `Shift` manually above, so skipping them here
// modifiers_code != Shift
if modifiers_code != 2 {
bindings.extend(bindings!(
KeyBinding;
Insert, mods; Action::Esc(format!("\x1b[2;{}~", modifiers_code));
PageUp, mods; Action::Esc(format!("\x1b[5;{}~", modifiers_code));
PageDown, mods; Action::Esc(format!("\x1b[6;{}~", modifiers_code));
End, mods; Action::Esc(format!("\x1b[1;{}F", modifiers_code));
Home, mods; Action::Esc(format!("\x1b[1;{}H", modifiers_code));
));
}
}
bindings.extend(platform_key_bindings());
bindings
}
#[cfg(not(any(target_os = "macos", test)))]
fn common_keybindings() -> Vec<KeyBinding> {
bindings!(
KeyBinding;
V, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Paste;
C, ModifiersState::CTRL | ModifiersState::SHIFT; Action::Copy;
Insert, ModifiersState::SHIFT; Action::PasteSelection;
Key0, ModifiersState::CTRL; Action::ResetFontSize;
Equals, ModifiersState::CTRL; Action::IncreaseFontSize;
Add, ModifiersState::CTRL; Action::IncreaseFontSize;
Subtract, ModifiersState::CTRL; Action::DecreaseFontSize;
Minus, 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;
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;
Key0, ModifiersState::LOGO; Action::ResetFontSize;
Equals, ModifiersState::LOGO; Action::IncreaseFontSize;
Add, ModifiersState::LOGO; Action::IncreaseFontSize;
Minus, ModifiersState::LOGO; Action::DecreaseFontSize;
Insert, ModifiersState::SHIFT; Action::Esc("\x1b[2;2~".into());
F, ModifiersState::CTRL | ModifiersState::LOGO; Action::ToggleFullscreen;
K, ModifiersState::LOGO; Action::ClearHistory;
K, ModifiersState::LOGO; Action::Esc("\x0c".into());
V, ModifiersState::LOGO; Action::Paste;
C, ModifiersState::LOGO; Action::Copy;
H, ModifiersState::LOGO; Action::Hide;
M, ModifiersState::LOGO; Action::Minimize;
Q, ModifiersState::LOGO; Action::Quit;
W, ModifiersState::LOGO; Action::Quit;
)
}
// 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(Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub enum Key {
Scancode(u32),
Keycode(VirtualKeyCode),
}
struct ModeWrapper {
pub mode: TermMode,
pub not_mode: TermMode,
}
impl<'a> Deserialize<'a> for ModeWrapper {
fn deserialize<D>(deserializer: D) -> ::std::result::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("Combination of AppCursor | AppKeypad, possibly with negation (~)")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<ModeWrapper, E>
where
E: de::Error,
{
let mut res = ModeWrapper { mode: TermMode::empty(), not_mode: TermMode::empty() };
for modifier in value.split('|') {
match modifier.trim().to_lowercase().as_str() {
"appcursor" => res.mode |= TermMode::APP_CURSOR,
"~appcursor" => res.not_mode |= TermMode::APP_CURSOR,
"appkeypad" => res.mode |= TermMode::APP_KEYPAD,
"~appkeypad" => res.not_mode |= TermMode::APP_KEYPAD,
"~alt" => res.not_mode |= TermMode::ALT_SCREEN,
"alt" => res.mode |= TermMode::ALT_SCREEN,
_ => error!(target: LOG_TARGET_CONFIG, "Unknown mode {:?}", modifier),
}
}
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) -> ::std::result::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")
}
fn visit_str<E>(self, value: &str) -> ::std::result::Result<MouseButtonWrapper, E>
where
E: de::Error,
{
match value {
"Left" => Ok(MouseButtonWrapper(MouseButton::Left)),
"Right" => Ok(MouseButtonWrapper(MouseButton::Right)),
"Middle" => Ok(MouseButtonWrapper(MouseButton::Middle)),
_ => {
if let Ok(index) = u8::from_str(value) {
Ok(MouseButtonWrapper(MouseButton::Other(index)))
} else {
Err(E::invalid_value(Unexpected::Str(value), &self))
}
},
}
}
}
deserializer.deserialize_str(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,
mode: TermMode,
notmode: TermMode,
action: Action,
}
impl RawBinding {
fn into_mouse_binding(self) -> ::std::result::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) -> ::std::result::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) -> ::std::result::Result<Self, D::Error>
where
D: Deserializer<'a>,
{
enum Field {
Key,
Mods,
Mode,
Action,
Chars,
Mouse,
Command,
}
impl<'a> Deserialize<'a> for Field {
fn deserialize<D>(deserializer: D) -> ::std::result::Result<Field, D::Error>
where
D: Deserializer<'a>,
{
struct FieldVisitor;
static FIELDS: &[&str] =
&["key", "mods", "mode", "action", "chars", "mouse", "command"];
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) -> ::std::result::Result<Field, E>
where
E: de::Error,
{
match value {
"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) -> ::std::result::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;
let mut mode: Option<TermMode> = None;
let mut not_mode: Option<TermMode> = None;
let mut mouse: Option<MouseButton> = None;
let mut command: Option<CommandWrapper> = None;
use ::serde::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::<serde_yaml::Value>()?;
if val.is_u64() {
let scancode = val.as_u64().unwrap();
if scancode > u64::from(::std::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"));
}
action = Some(map.next_value::<Action>()?);
},
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::<CommandWrapper>()?);
},
}
}
let action = match (action, chars, command) {
(Some(action), None, None) => action,
(None, Some(chars), None) => Action::Esc(chars),
(None, None, Some(cmd)) => match cmd {
CommandWrapper::Just(program) => Action::Command(program, vec![]),
CommandWrapper::WithArgs { program, args } => {
Action::Command(program, args)
},
},
(None, None, None) => {
return Err(V::Error::custom("must specify chars, action or command"));
},
_ => {
return Err(V::Error::custom("must specify only chars, action or command"))
},
};
let mode = mode.unwrap_or_else(TermMode::empty);
let not_mode = not_mode.unwrap_or_else(TermMode::empty);
let mods = mods.unwrap_or_else(ModifiersState::default);
if mouse.is_none() && key.is_none() {
return Err(V::Error::custom("bindings require mouse button or key"));
}
Ok(RawBinding { mode, notmode: not_mode, action, key, mouse, mods })
}
}
const FIELDS: &[&str] = &["key", "mods", "mode", "action", "chars", "mouse", "command"];
deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor)
}
}
impl<'a> Deserialize<'a> for MouseBinding {
fn deserialize<D>(deserializer: D) -> ::std::result::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"))
}
}
impl<'a> Deserialize<'a> for KeyBinding {
fn deserialize<D>(deserializer: D) -> ::std::result::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"))
}
}
#[serde(untagged)]
#[derive(Debug, Deserialize, Clone, PartialEq, Eq)]
pub enum CommandWrapper {
Just(String),
WithArgs {
program: String,
#[serde(default)]
args: Vec<String>,
},
}
impl CommandWrapper {
pub fn program(&self) -> &str {
match self {
CommandWrapper::Just(program) => program,
CommandWrapper::WithArgs { program, .. } => program,
}
}
pub fn args(&self) -> &[String] {
match self {
CommandWrapper::Just(_) => &[],
CommandWrapper::WithArgs { args, .. } => args,
}
}
}
/// Newtype for implementing deserialize on glutin Mods
///
/// 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 {
fn deserialize<D>(deserializer: D) -> ::std::result::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("Some subset of Command|Shift|Super|Alt|Option|Control")
}
fn visit_str<E>(self, value: &str) -> ::std::result::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" => (),
_ => error!(target: LOG_TARGET_CONFIG, "Unknown modifier {:?}", modifier),
}
}
Ok(ModsWrapper(res))
}
}
deserializer.deserialize_str(ModsVisitor)
}
}
#[cfg(test)]
mod test {
use glutin::event::ModifiersState;
use alacritty_terminal::term::TermMode;
use crate::config::{Action, Binding};
type MockBinding = Binding<usize>;
impl Default for MockBinding {
fn default() -> Self {
Self {
mods: Default::default(),
action: Default::default(),
mode: TermMode::empty(),
notmode: TermMode::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();
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();
superset_mods.mods = ModifiersState::all();
let mut subset_mods = MockBinding::default();
subset_mods.mods = ModifiersState::ALT;
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();
b1.mode = TermMode::ALT_SCREEN;
let mut b2 = MockBinding::default();
b2.mode = TermMode::ALT_SCREEN;
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_without_mode_matches_any_mode() {
let b1 = MockBinding::default();
let mut b2 = MockBinding::default();
b2.mode = TermMode::APP_KEYPAD;
b2.notmode = TermMode::ALT_SCREEN;
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_with_mode_matches_empty_mode() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::APP_KEYPAD;
b1.notmode = TermMode::ALT_SCREEN;
let b2 = MockBinding::default();
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_matches_superset_mode() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::APP_KEYPAD;
let mut b2 = MockBinding::default();
b2.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_matches_subset_mode() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
let mut b2 = MockBinding::default();
b2.mode = TermMode::APP_KEYPAD;
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_matches_partial_intersection() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::ALT_SCREEN | TermMode::APP_KEYPAD;
let mut b2 = MockBinding::default();
b2.mode = TermMode::APP_KEYPAD | TermMode::APP_CURSOR;
assert!(b1.triggers_match(&b2));
}
#[test]
fn binding_mismatches_notmode() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::ALT_SCREEN;
let mut b2 = MockBinding::default();
b2.notmode = TermMode::ALT_SCREEN;
assert!(!b1.triggers_match(&b2));
}
#[test]
fn binding_mismatches_unrelated() {
let mut b1 = MockBinding::default();
b1.mode = TermMode::ALT_SCREEN;
let mut b2 = MockBinding::default();
b2.mode = TermMode::APP_KEYPAD;
assert!(!b1.triggers_match(&b2));
}
#[test]
fn binding_trigger_input() {
let mut binding = MockBinding::default();
binding.trigger = 13;
let mods = binding.mods;
let mode = binding.mode;
assert!(binding.is_triggered_by(mode, mods, &13, true));
assert!(!binding.is_triggered_by(mode, mods, &32, true));
}
#[test]
fn binding_trigger_mods() {
let mut binding = MockBinding::default();
binding.mods = ModifiersState::ALT | ModifiersState::LOGO;
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, true));
assert!(binding.is_triggered_by(mode, binding.mods, &t, false));
assert!(binding.is_triggered_by(mode, superset_mods, &t, true));
assert!(!binding.is_triggered_by(mode, superset_mods, &t, false));
assert!(!binding.is_triggered_by(mode, subset_mods, &t, true));
assert!(!binding.is_triggered_by(mode, subset_mods, &t, false));
}
#[test]
fn binding_trigger_modes() {
let mut binding = MockBinding::default();
binding.mode = TermMode::ALT_SCREEN;
let t = binding.trigger;
let mods = binding.mods;
assert!(!binding.is_triggered_by(TermMode::INSERT, mods, &t, true));
assert!(binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
assert!(binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
}
#[test]
fn binding_trigger_notmodes() {
let mut binding = MockBinding::default();
binding.notmode = TermMode::ALT_SCREEN;
let t = binding.trigger;
let mods = binding.mods;
assert!(binding.is_triggered_by(TermMode::INSERT, mods, &t, true));
assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN, mods, &t, true));
assert!(!binding.is_triggered_by(TermMode::ALT_SCREEN | TermMode::INSERT, mods, &t, true));
}
}