diff options
author | Christian Duerr <contact@christianduerr.com> | 2019-10-05 02:29:26 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-05 02:29:26 +0200 |
commit | 729eef0c933831bccfeac6a355bdb410787fbe5f (patch) | |
tree | 35cdf2e6427ad18bc53efbab4cab34a0af2054d7 /alacritty/src/config/bindings.rs | |
parent | b0c6fdff763f7271506d26d7e768e6377fdc691b (diff) | |
download | r-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.gz r-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.tar.bz2 r-alacritty-729eef0c933831bccfeac6a355bdb410787fbe5f.zip |
Update to winit/glutin EventLoop 2.0
This takes the latest glutin master to port Alacritty to the EventLoop
2.0 rework.
This changes a big part of the event loop handling by pushing the event
loop in a separate thread from the renderer and running both in
parallel.
Fixes #2796.
Fixes #2694.
Fixes #2643.
Fixes #2625.
Fixes #2618.
Fixes #2601.
Fixes #2564.
Fixes #2456.
Fixes #2438.
Fixes #2334.
Fixes #2254.
Fixes #2217.
Fixes #1789.
Fixes #1750.
Fixes #1125.
Diffstat (limited to 'alacritty/src/config/bindings.rs')
-rw-r--r-- | alacritty/src/config/bindings.rs | 1409 |
1 files changed, 1409 insertions, 0 deletions
diff --git a/alacritty/src/config/bindings.rs b/alacritty/src/config/bindings.rs new file mode 100644 index 00000000..17a6d0b7 --- /dev/null +++ b/alacritty/src/config/bindings.rs @@ -0,0 +1,1409 @@ +// 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. + +use std::fmt; +use std::str::FromStr; + +use glutin::event::{ModifiersState, MouseButton}; +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::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, + + /// 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.logo || other.logo) + && (!self.alt || other.alt) + && (!self.ctrl || other.ctrl) + && (!self.shift || other.shift) + } +} + +macro_rules! bindings { + ( + $ty:ident; + $( + $key:path + $(,[$($mod:ident: $enabled:expr),*])* + $(,+$mode:expr)* + $(,~$notmode:expr)* + ;$action:expr + );* + $(;)* + ) => {{ + let mut v = Vec::new(); + + $( + let mut _mods = ModifiersState { + $($($mod: $enabled),*,)* + ..Default::default() + }; + 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; + Key::Paste; Action::Paste; + Key::Copy; Action::Copy; + Key::L, [ctrl: true]; Action::ClearLogNotice; + Key::L, [ctrl: true]; Action::Esc("\x0c".into()); + Key::PageUp, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageUp; + Key::PageDown, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollPageDown; + Key::Home, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToTop; + Key::End, [shift: true], ~TermMode::ALT_SCREEN; Action::ScrollToBottom; + Key::Home, +TermMode::APP_CURSOR; Action::Esc("\x1bOH".into()); + Key::Home, ~TermMode::APP_CURSOR; Action::Esc("\x1b[H".into()); + Key::Home, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2H".into()); + Key::End, +TermMode::APP_CURSOR; Action::Esc("\x1bOF".into()); + Key::End, ~TermMode::APP_CURSOR; Action::Esc("\x1b[F".into()); + Key::End, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[1;2F".into()); + Key::PageUp; Action::Esc("\x1b[5~".into()); + Key::PageUp, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[5;2~".into()); + Key::PageDown; Action::Esc("\x1b[6~".into()); + Key::PageDown, [shift: true], +TermMode::ALT_SCREEN; Action::Esc("\x1b[6;2~".into()); + Key::Tab, [shift: true]; Action::Esc("\x1b[Z".into()); + Key::Back; Action::Esc("\x7f".into()); + Key::Back, [alt: true]; Action::Esc("\x1b\x7f".into()); + Key::Insert; Action::Esc("\x1b[2~".into()); + Key::Delete; Action::Esc("\x1b[3~".into()); + Key::Up, +TermMode::APP_CURSOR; Action::Esc("\x1bOA".into()); + Key::Up, ~TermMode::APP_CURSOR; Action::Esc("\x1b[A".into()); + Key::Down, +TermMode::APP_CURSOR; Action::Esc("\x1bOB".into()); + Key::Down, ~TermMode::APP_CURSOR; Action::Esc("\x1b[B".into()); + Key::Right, +TermMode::APP_CURSOR; Action::Esc("\x1bOC".into()); + Key::Right, ~TermMode::APP_CURSOR; Action::Esc("\x1b[C".into()); + Key::Left, +TermMode::APP_CURSOR; Action::Esc("\x1bOD".into()); + Key::Left, ~TermMode::APP_CURSOR; Action::Esc("\x1b[D".into()); + Key::F1; Action::Esc("\x1bOP".into()); + Key::F2; Action::Esc("\x1bOQ".into()); + Key::F3; Action::Esc("\x1bOR".into()); + Key::F4; Action::Esc("\x1bOS".into()); + Key::F5; Action::Esc("\x1b[15~".into()); + Key::F6; Action::Esc("\x1b[17~".into()); + Key::F7; Action::Esc("\x1b[18~".into()); + Key::F8; Action::Esc("\x1b[19~".into()); + Key::F9; Action::Esc("\x1b[20~".into()); + Key::F10; Action::Esc("\x1b[21~".into()); + Key::F11; Action::Esc("\x1b[23~".into()); + Key::F12; Action::Esc("\x1b[24~".into()); + Key::F13; Action::Esc("\x1b[25~".into()); + Key::F14; Action::Esc("\x1b[26~".into()); + Key::F15; Action::Esc("\x1b[28~".into()); + Key::F16; Action::Esc("\x1b[29~".into()); + Key::F17; Action::Esc("\x1b[31~".into()); + Key::F18; Action::Esc("\x1b[32~".into()); + Key::F19; Action::Esc("\x1b[33~".into()); + Key::F20; Action::Esc("\x1b[34~".into()); + Key::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 modifiers = vec![ + ModifiersState { shift: true, ..ModifiersState::default() }, + ModifiersState { alt: true, ..ModifiersState::default() }, + ModifiersState { shift: true, alt: true, ..ModifiersState::default() }, + ModifiersState { ctrl: true, ..ModifiersState::default() }, + ModifiersState { shift: true, ctrl: true, ..ModifiersState::default() }, + ModifiersState { alt: true, ctrl: true, ..ModifiersState::default() }, + ModifiersState { shift: true, alt: true, ctrl: true, ..ModifiersState::default() }, + ]; + + for (index, mods) in modifiers.iter().enumerate() { + let modifiers_code = index + 2; + bindings.extend(bindings!( + KeyBinding; + Key::Up, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}A", modifiers_code)); + Key::Down, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}B", modifiers_code)); + Key::Right, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}C", modifiers_code)); + Key::Left, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}D", modifiers_code)); + Key::F1, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}P", modifiers_code)); + Key::F2, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}Q", modifiers_code)); + Key::F3, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}R", modifiers_code)); + Key::F4, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}S", modifiers_code)); + Key::F5, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[15;{}~", modifiers_code)); + Key::F6, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[17;{}~", modifiers_code)); + Key::F7, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[18;{}~", modifiers_code)); + Key::F8, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[19;{}~", modifiers_code)); + Key::F9, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[20;{}~", modifiers_code)); + Key::F10, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[21;{}~", modifiers_code)); + Key::F11, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[23;{}~", modifiers_code)); + Key::F12, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[24;{}~", modifiers_code)); + Key::F13, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[25;{}~", modifiers_code)); + Key::F14, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[26;{}~", modifiers_code)); + Key::F15, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[28;{}~", modifiers_code)); + Key::F16, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[29;{}~", modifiers_code)); + Key::F17, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[31;{}~", modifiers_code)); + Key::F18, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[32;{}~", modifiers_code)); + Key::F19, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[33;{}~", modifiers_code)); + Key::F20, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + 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; + Key::PageUp, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[5;{}~", modifiers_code)); + Key::PageDown, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[6;{}~", modifiers_code)); + Key::End, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + Action::Esc(format!("\x1b[1;{}F", modifiers_code)); + Key::Home, [shift: mods.shift, alt: mods.alt, ctrl: mods.ctrl]; + 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; + Key::V, [ctrl: true, shift: true]; Action::Paste; + Key::C, [ctrl: true, shift: true]; Action::Copy; + Key::Insert, [shift: true]; Action::PasteSelection; + Key::Key0, [ctrl: true]; Action::ResetFontSize; + Key::Equals, [ctrl: true]; Action::IncreaseFontSize; + Key::Add, [ctrl: true]; Action::IncreaseFontSize; + Key::Subtract, [ctrl: true]; Action::DecreaseFontSize; + Key::Minus, [ctrl: true]; 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; + Key::Return, [alt: true]; Action::ToggleFullscreen; + ); + bindings.extend(common_keybindings()); + bindings +} + +#[cfg(all(target_os = "macos", not(test)))] +pub fn platform_key_bindings() -> Vec<KeyBinding> { + bindings!( + KeyBinding; + Key::Key0, [logo: true]; Action::ResetFontSize; + Key::Equals, [logo: true]; Action::IncreaseFontSize; + Key::Add, [logo: true]; Action::IncreaseFontSize; + Key::Minus, [logo: true]; Action::DecreaseFontSize; + Key::F, [ctrl: true, logo: true]; Action::ToggleFullscreen; + Key::K, [logo: true]; Action::ClearHistory; + Key::K, [logo: true]; Action::Esc("\x0c".into()); + Key::V, [logo: true]; Action::Paste; + Key::C, [logo: true]; Action::Copy; + Key::H, [logo: true]; Action::Hide; + Key::Q, [logo: true]; Action::Quit; + Key::W, [logo: true]; 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), + Key1, + Key2, + Key3, + Key4, + Key5, + Key6, + Key7, + Key8, + Key9, + Key0, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + Escape, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + Snapshot, + Scroll, + Pause, + Insert, + Home, + Delete, + End, + PageDown, + PageUp, + Left, + Up, + Right, + Down, + Back, + Return, + Space, + Compose, + Numlock, + Numpad0, + Numpad1, + Numpad2, + Numpad3, + Numpad4, + Numpad5, + Numpad6, + Numpad7, + Numpad8, + Numpad9, + AbntC1, + AbntC2, + Add, + Apostrophe, + Apps, + At, + Ax, + Backslash, + Calculator, + Capital, + Colon, + Comma, + Convert, + Decimal, + Divide, + Equals, + Grave, + Kana, + Kanji, + LAlt, + LBracket, + LControl, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, + Caret, + Copy, + Paste, + Cut, +} + +impl Key { + pub fn from_glutin_input(key: glutin::event::VirtualKeyCode) -> Self { + use glutin::event::VirtualKeyCode::*; + // Thank you, vim macros and regex! + match key { + Key1 => Key::Key1, + Key2 => Key::Key2, + Key3 => Key::Key3, + Key4 => Key::Key4, + Key5 => Key::Key5, + Key6 => Key::Key6, + Key7 => Key::Key7, + Key8 => Key::Key8, + Key9 => Key::Key9, + Key0 => Key::Key0, + A => Key::A, + B => Key::B, + C => Key::C, + D => Key::D, + E => Key::E, + F => Key::F, + G => Key::G, + H => Key::H, + I => Key::I, + J => Key::J, + K => Key::K, + L => Key::L, + M => Key::M, + N => Key::N, + O => Key::O, + P => Key::P, + Q => Key::Q, + R => Key::R, + S => Key::S, + T => Key::T, + U => Key::U, + V => Key::V, + W => Key::W, + X => Key::X, + Y => Key::Y, + Z => Key::Z, + Escape => Key::Escape, + F1 => Key::F1, + F2 => Key::F2, + F3 => Key::F3, + F4 => Key::F4, + F5 => Key::F5, + F6 => Key::F6, + F7 => Key::F7, + F8 => Key::F8, + F9 => Key::F9, + F10 => Key::F10, + F11 => Key::F11, + F12 => Key::F12, + F13 => Key::F13, + F14 => Key::F14, + F15 => Key::F15, + F16 => Key::F16, + F17 => Key::F17, + F18 => Key::F18, + F19 => Key::F19, + F20 => Key::F20, + F21 => Key::F21, + F22 => Key::F22, + F23 => Key::F23, + F24 => Key::F24, + Snapshot => Key::Snapshot, + Scroll => Key::Scroll, + Pause => Key::Pause, + Insert => Key::Insert, + Home => Key::Home, + Delete => Key::Delete, + End => Key::End, + PageDown => Key::PageDown, + PageUp => Key::PageUp, + Left => Key::Left, + Up => Key::Up, + Right => Key::Right, + Down => Key::Down, + Back => Key::Back, + Return => Key::Return, + Space => Key::Space, + Compose => Key::Compose, + Numlock => Key::Numlock, + Numpad0 => Key::Numpad0, + Numpad1 => Key::Numpad1, + Numpad2 => Key::Numpad2, + Numpad3 => Key::Numpad3, + Numpad4 => Key::Numpad4, + Numpad5 => Key::Numpad5, + Numpad6 => Key::Numpad6, + Numpad7 => Key::Numpad7, + Numpad8 => Key::Numpad8, + Numpad9 => Key::Numpad9, + AbntC1 => Key::AbntC1, + AbntC2 => Key::AbntC2, + Add => Key::Add, + Apostrophe => Key::Apostrophe, + Apps => Key::Apps, + At => Key::At, + Ax => Key::Ax, + Backslash => Key::Backslash, + Calculator => Key::Calculator, + Capital => Key::Capital, + Colon => Key::Colon, + Comma => Key::Comma, + Convert => Key::Convert, + Decimal => Key::Decimal, + Divide => Key::Divide, + Equals => Key::Equals, + Grave => Key::Grave, + Kana => Key::Kana, + Kanji => Key::Kanji, + LAlt => Key::LAlt, + LBracket => Key::LBracket, + LControl => Key::LControl, + LShift => Key::LShift, + LWin => Key::LWin, + Mail => Key::Mail, + MediaSelect => Key::MediaSelect, + MediaStop => Key::MediaStop, + Minus => Key::Minus, + Multiply => Key::Multiply, + Mute => Key::Mute, + MyComputer => Key::MyComputer, + NavigateForward => Key::NavigateForward, + NavigateBackward => Key::NavigateBackward, + NextTrack => Key::NextTrack, + NoConvert => Key::NoConvert, + NumpadComma => Key::NumpadComma, + NumpadEnter => Key::NumpadEnter, + NumpadEquals => Key::NumpadEquals, + OEM102 => Key::OEM102, + Period => Key::Period, + PlayPause => Key::PlayPause, + Power => Key::Power, + PrevTrack => Key::PrevTrack, + RAlt => Key::RAlt, + RBracket => Key::RBracket, + RControl => Key::RControl, + RShift => Key::RShift, + RWin => Key::RWin, + Semicolon => Key::Semicolon, + Slash => Key::Slash, + Sleep => Key::Sleep, + Stop => Key::Stop, + Subtract => Key::Subtract, + Sysrq => Key::Sysrq, + Tab => Key::Tab, + Underline => Key::Underline, + Unlabeled => Key::Unlabeled, + VolumeDown => Key::VolumeDown, + VolumeUp => Key::VolumeUp, + Wake => Key::Wake, + WebBack => Key::WebBack, + WebFavorites => Key::WebFavorites, + WebForward => Key::WebForward, + WebHome => Key::WebHome, + WebRefresh => Key::WebRefresh, + WebSearch => Key::WebSearch, + WebStop => Key::WebStop, + Yen => Key::Yen, + Caret => Key::Caret, + Copy => Key::Copy, + Paste => Key::Paste, + Cut => Key::Cut, + } + } +} + +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::default(); + for modifier in value.split('|') { + match modifier.trim().to_lowercase().as_str() { + "command" | "super" => res.logo = true, + "shift" => res.shift = true, + "alt" | "option" => res.alt = true, + "control" => res.ctrl = true, + "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 { alt: true, logo: true, ctrl: true, shift: true }; + let mut subset_mods = MockBinding::default(); + subset_mods.mods = ModifiersState { alt: true, logo: false, ctrl: false, shift: false }; + + 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: true, logo: true, ctrl: false, shift: false }; + + let superset_mods = ModifiersState { alt: true, logo: true, ctrl: true, shift: true }; + let subset_mods = ModifiersState { alt: false, logo: false, ctrl: false, shift: false }; + + 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)); + } +} |