diff options
-rw-r--r-- | alacritty.yml | 91 | ||||
-rw-r--r-- | src/config.rs | 726 | ||||
-rw-r--r-- | src/event.rs | 32 | ||||
-rw-r--r-- | src/input.rs | 497 | ||||
-rw-r--r-- | src/main.rs | 4 |
5 files changed, 1036 insertions, 314 deletions
diff --git a/alacritty.yml b/alacritty.yml index 5f1f4359..97914968 100644 --- a/alacritty.yml +++ b/alacritty.yml @@ -85,3 +85,94 @@ colors: # magenta: '0x6c71c4' # cyan: '0x93a1a1' # white: '0xfdf6e3' + +# Key bindings +# +# Each binding is defined as an object with some properties. Most of the +# properties are optional. All of the alphabetical keys should have a letter for +# the `key` value such as `V`. Function keys are probably what you would expect +# as well (F1, F2, ..). The number keys above the main keyboard are encoded as +# `Key1`, `Key2`, etc. Keys on the number pad are encoded `Number1`, `Number2`, +# etc. These all match the glutin::VirtualKeyCode variants. +# +# Possible values for `mods` +# `Command`, `Super` refer to the super/command/windows key +# `Control` for the control key +# `Shift` for the Shift key +# `Alt` and `Option` refer to alt/option +# +# mods may be combined with a `|`. For example, requiring control and shift +# looks like: +# +# mods: Control|Shift +# +# The parser is currently quite sensitive to whitespace and capitalization - +# capitalization must match exactly, and piped items must not have whitespace +# around them. +# +# Either an `action` or `chars` field must be present. `chars` writes the +# specified string every time that binding is activated. These should generally +# be escape sequences, but they can be configured to send arbitrary strings of +# bytes. Possible values of `action` include `Paste` and `PasteSelection`. +key_bindings: + - { key: V, mods: Command, action: Paste } + - { key: Home, chars: "\x1b[H", mode: ~AppCursor } + - { key: Home, chars: "\x1b[1~", mode: AppCursor } + - { key: End, chars: "\x1b[F", mode: ~AppCursor } + - { key: End, chars: "\x1b[4~", mode: AppCursor } + - { key: PageUp, chars: "\x1b[5~" } + - { key: PageDown, chars: "\x1b[6~" } + - { key: Left, mods: Shift, chars: "\x1b[1;2D" } + - { key: Left, mods: Control, chars: "\x1b[1;5D" } + - { key: Left, mods: Alt, chars: "\x1b[1;3D" } + - { key: Left, chars: "\x1b[D", mode: ~AppCursor } + - { key: Left, chars: "\x1bOD", mode: AppCursor } + - { key: Right, mods: Shift, chars: "\x1b[1;2C" } + - { key: Right, mods: Control, chars: "\x1b[1;5C" } + - { key: Right, mods: Alt, chars: "\x1b[1;3C" } + - { key: Right, chars: "\x1b[C", mode: ~AppCursor } + - { key: Right, chars: "\x1bOC", mode: AppCursor } + - { key: Up, mods: Shift, chars: "\x1b[1;2A" } + - { key: Up, mods: Control, chars: "\x1b[1;5A" } + - { key: Up, mods: Alt, chars: "\x1b[1;3A" } + - { key: Up, chars: "\x1b[A", mode: ~AppCursor } + - { key: Up, chars: "\x1bOA", mode: AppCursor } + - { key: Down, mods: Shift, chars: "\x1b[1;2B" } + - { key: Down, mods: Control, chars: "\x1b[1;5B" } + - { key: Down, mods: Alt, chars: "\x1b[1;3B" } + - { key: Down, chars: "\x1b[B", mode: ~AppCursor } + - { key: Down, chars: "\x1bOB", mode: AppCursor } + - { key: F1, chars: "\x1bOP" } + - { key: F2, chars: "\x1bOQ" } + - { key: F3, chars: "\x1bOR" } + - { key: F4, chars: "\x1bOS" } + - { key: F5, chars: "\x1b[15~" } + - { key: F6, chars: "\x1b[17~" } + - { key: F7, chars: "\x1b[18~" } + - { key: F8, chars: "\x1b[19~" } + - { key: F9, chars: "\x1b[20~" } + - { key: F10, chars: "\x1b[21~" } + - { key: F11, chars: "\x1b[23~" } + - { key: F12, chars: "\x1b[24~" } + - { key: Back, chars: "\x7f" } + - { key: Delete, chars: "\x1b[3~", mode: AppKeypad } + - { key: Delete, chars: "\x1b[P", mode: ~AppKeypad } + +# Mouse bindings +# +# Currently doesn't support modifiers. Both the `mouse` and `action` fields must +# be specified. +# +# Values for `mouse`: +# - Middle +# - Left +# - Right +# - Numeric identifier such as `5` +# +# Values for `action`: +# - Paste +# - PasteSelection +# - Copy (TODO) +mouse: + - { mouse: Middle, action: PasteSelection } + diff --git a/src/config.rs b/src/config.rs index 33474ddb..b315fb86 100644 --- a/src/config.rs +++ b/src/config.rs @@ -7,14 +7,18 @@ use std::env; use std::fs; use std::io::{self, Read}; use std::path::{Path, PathBuf}; +use std::str::FromStr; use std::sync::mpsc; use ::Rgb; use font::Size; use serde_yaml; -use serde::{self, Error as SerdeError}; +use serde::{self, de, Error as SerdeError}; +use serde::de::{Visitor, MapVisitor}; use notify::{Watcher as WatcherApi, RecommendedWatcher as FileWatcher, op}; +use input::{Action, Binding, MouseBinding, KeyBinding}; + /// Function that returns true for serde default fn true_bool() -> bool { true @@ -42,6 +46,14 @@ pub struct Config { /// The standard ANSI colors to use #[serde(default)] colors: Colors, + + /// Keybindings + #[serde(default)] + key_bindings: Vec<KeyBinding>, + + /// Bindings for the mouse + #[serde(default)] + mouse_bindings: Vec<MouseBinding>, } impl Default for Config { @@ -52,7 +64,377 @@ impl Default for Config { font: Default::default(), render_timer: Default::default(), colors: Default::default(), + key_bindings: Vec::new(), + mouse_bindings: Vec::new(), + } + } +} + +/// Newtype for implementing deserialize on glutin Mods +/// +/// Our deserialize impl wouldn't be covered by a derive(Deserialize); see the +/// impl below. +struct ModsWrapper(::glutin::Mods); + +impl ModsWrapper { + fn into_inner(self) -> ::glutin::Mods { + self.0 + } +} + +impl de::Deserialize for ModsWrapper { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ModsVisitor; + + impl Visitor for ModsVisitor { + type Value = ModsWrapper; + + fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<ModsWrapper, E> + where E: de::Error, + { + use ::glutin::{mods, Mods}; + let mut res = Mods::empty(); + for modifier in value.split('|') { + match modifier { + "Command" | "Super" => res = res | mods::SUPER, + "Shift" => res = res | mods::SHIFT, + "Alt" | "Option" => res = res | mods::ALT, + "Control" => res = res | mods::CONTROL, + _ => err_println!("unknown modifier {:?}", modifier), + } + } + + Ok(ModsWrapper(res)) + } + } + + deserializer.deserialize_str(ModsVisitor) + } +} + +struct ActionWrapper(::input::Action); + +impl ActionWrapper { + fn into_inner(self) -> ::input::Action { + self.0 + } +} + +impl de::Deserialize for ActionWrapper { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ActionVisitor; + + impl Visitor for ActionVisitor { + type Value = ActionWrapper; + + fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<ActionWrapper, E> + where E: de::Error, + { + Ok(ActionWrapper(match value { + "Paste" => Action::Paste, + "PasteSelection" => Action::PasteSelection, + _ => return Err(E::invalid_value("invalid value for Action")), + })) + } } + deserializer.deserialize_str(ActionVisitor) + } +} + +use ::term::{mode, TermMode}; + +struct ModeWrapper { + pub mode: TermMode, + pub not_mode: TermMode, +} + +impl de::Deserialize for ModeWrapper { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct ModeVisitor; + + impl Visitor for ModeVisitor { + type Value = ModeWrapper; + + fn visit_str<E>(&mut 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 { + "AppCursor" => res.mode = res.mode | mode::APP_CURSOR, + "~AppCursor" => res.not_mode = res.not_mode | mode::APP_CURSOR, + "AppKeypad" => res.mode = res.mode | mode::APP_KEYPAD, + "~AppKeypad" => res.not_mode = res.not_mode | mode::APP_KEYPAD, + _ => err_println!("unknown omde {:?}", modifier), + } + } + + Ok(res) + } + } + deserializer.deserialize_str(ModeVisitor) + } +} + +struct MouseButton(::glutin::MouseButton); + +impl MouseButton { + fn into_inner(self) -> ::glutin::MouseButton { + self.0 + } +} + +impl de::Deserialize for MouseButton { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + struct MouseButtonVisitor; + + impl Visitor for MouseButtonVisitor { + type Value = MouseButton; + + fn visit_str<E>(&mut self, value: &str) -> ::std::result::Result<MouseButton, E> + where E: de::Error, + { + match value { + "Left" => Ok(MouseButton(::glutin::MouseButton::Left)), + "Right" => Ok(MouseButton(::glutin::MouseButton::Right)), + "Middle" => Ok(MouseButton(::glutin::MouseButton::Middle)), + _ => { + if let Ok(index) = u8::from_str(value) { + Ok(MouseButton(::glutin::MouseButton::Other(index))) + } else { + Err(E::invalid_value("mouse may be Left, Right, Middle, or u8")) + } + } + } + } + } + + deserializer.deserialize_str(MouseButtonVisitor) + } +} + +/// Bindings are deserialized into a RawBinding before being parsed as a +/// KeyBinding or MouseBinding. +struct RawBinding { + key: Option<::glutin::VirtualKeyCode>, + mouse: Option<::glutin::MouseButton>, + mods: ::glutin::Mods, + mode: TermMode, + notmode: TermMode, + action: Action, +} + +impl RawBinding { + fn into_mouse_binding(self) -> ::std::result::Result<MouseBinding, Self> { + if self.mouse.is_some() { + Ok(MouseBinding { + button: self.mouse.unwrap(), + binding: Binding { + 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 self.key.is_some() { + Ok(KeyBinding { + key: self.key.unwrap(), + binding: Binding { + mods: self.mods, + action: self.action, + mode: self.mode, + notmode: self.notmode, + } + }) + } else { + Err(self) + } + } +} + +impl de::Deserialize for RawBinding { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + enum Field { + Key, + Mods, + Mode, + Action, + Chars, + Mouse + } + + impl de::Deserialize for Field { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Field, D::Error> + where D: de::Deserializer + { + struct FieldVisitor; + + impl Visitor for FieldVisitor { + type Value = Field; + + fn visit_str<E>(&mut 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), + _ => Err(E::unknown_field(value)), + } + } + } + + deserializer.deserialize_struct_field(FieldVisitor) + } + } + + struct RawBindingVisitor; + impl Visitor for RawBindingVisitor { + type Value = RawBinding; + + fn visit_map<V>(&mut self, mut visitor: V) -> ::std::result::Result<RawBinding, V::Error> + where V: MapVisitor, + { + let mut mods: Option<::glutin::Mods> = None; + let mut key: Option<::glutin::VirtualKeyCode> = None; + let mut chars: Option<String> = None; + let mut action: Option<::input::Action> = None; + let mut mode: Option<TermMode> = None; + let mut not_mode: Option<TermMode> = None; + let mut mouse: Option<::glutin::MouseButton> = None; + + use ::serde::Error; + + while let Some(struct_key) = visitor.visit_key::<Field>()? { + match struct_key { + Field::Key => { + if key.is_some() { + return Err(<V::Error as Error>::duplicate_field("key")); + } + + let coherent_key = visitor.visit_value::<Key>()?; + key = Some(coherent_key.to_glutin_key()); + }, + Field::Mods => { + if mods.is_some() { + return Err(<V::Error as Error>::duplicate_field("mods")); + } + + mods = Some(visitor.visit_value::<ModsWrapper>()?.into_inner()); + }, + Field::Mode => { + if mode.is_some() { + return Err(<V::Error as Error>::duplicate_field("mode")); + } + + let mode_deserializer = visitor.visit_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(visitor.visit_value::<ActionWrapper>()?.into_inner()); + }, + Field::Chars => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("chars")); + } + + chars = Some(visitor.visit_value()?); + }, + Field::Mouse => { + if chars.is_some() { + return Err(<V::Error as Error>::duplicate_field("mouse")); + } + + mouse = Some(visitor.visit_value::<MouseButton>()?.into_inner()); + } + } + } + visitor.end()?; + if key.is_none() { + return Err(V::Error::missing_field("key")); + } + + let action = match (action, chars) { + (Some(_), Some(_)) => { + return Err(V::Error::custom("must specify only chars or action")); + }, + (Some(action), _) => action, + (_, Some(chars)) => Action::Esc(chars), + _ => return Err(V::Error::custom("must specify chars or action")) + }; + + let mode = mode.unwrap_or_else(TermMode::empty); + let not_mode = not_mode.unwrap_or_else(TermMode::empty); + let mods = mods.unwrap_or_else(::glutin::Mods::empty); + + if mouse.is_none() && key.is_none() { + return Err(V::Error::custom("bindings require mouse button or key")); + } + + Ok(RawBinding { + mode: mode, + notmode: not_mode, + action: action, + key: key, + mouse: mouse, + mods: mods, + }) + } + } + + const FIELDS: &'static [&'static str] = &[ + "key", "mods", "mode", "action", "chars", "mouse" + ]; + + deserializer.deserialize_struct("RawBinding", FIELDS, RawBindingVisitor) + } +} + +impl de::Deserialize for MouseBinding { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_mouse_binding() + .map_err(|_| D::Error::custom("expected mouse binding")) + } +} + +impl de::Deserialize for KeyBinding { + fn deserialize<D>(deserializer: &mut D) -> ::std::result::Result<Self, D::Error> + where D: de::Deserializer + { + let raw = RawBinding::deserialize(deserializer)?; + raw.into_key_binding() + .map_err(|_| D::Error::custom("expected key binding")) } } @@ -314,6 +696,14 @@ impl Config { ] } + pub fn key_bindings(&self) -> &[KeyBinding] { + &self.key_bindings[..] + } + + pub fn mouse_bindings(&self) -> &[MouseBinding] { + &self.mouse_bindings[..] + } + pub fn fg_color(&self) -> Rgb { self.colors.primary.foreground } @@ -605,3 +995,337 @@ impl Watcher { })) } } + +#[cfg(test)] +mod tests { + use super::Config; + + static ALACRITTY_YML: &'static str = + include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/alacritty.yml")); + + #[test] + fn parse_config() { + let config: Config = ::serde_yaml::from_str(ALACRITTY_YML) + .expect("deserialize config"); + + println!("config: {:#?}", config); + } +} + +#[derive(Deserialize, Copy, Clone)] +enum Key { + 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, + + 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, + LMenu, + LShift, + LWin, + Mail, + MediaSelect, + MediaStop, + Minus, + Multiply, + Mute, + MyComputer, + NavigateForward, + NavigateBackward, + NextTrack, + NoConvert, + NumpadComma, + NumpadEnter, + NumpadEquals, + OEM102, + Period, + PlayPause, + Power, + PrevTrack, + RAlt, + RBracket, + RControl, + RMenu, + RShift, + RWin, + Semicolon, + Slash, + Sleep, + Stop, + Subtract, + Sysrq, + Tab, + Underline, + Unlabeled, + VolumeDown, + VolumeUp, + Wake, + WebBack, + WebFavorites, + WebForward, + WebHome, + WebRefresh, + WebSearch, + WebStop, + Yen, +} + +impl Key { + fn to_glutin_key(&self) -> ::glutin::VirtualKeyCode { + // Thank you, vim macros! + match *self { + Key::Key1 => ::glutin::VirtualKeyCode::Key1, + Key::Key2 => ::glutin::VirtualKeyCode::Key2, + Key::Key3 => ::glutin::VirtualKeyCode::Key3, + Key::Key4 => ::glutin::VirtualKeyCode::Key4, + Key::Key5 => ::glutin::VirtualKeyCode::Key5, + Key::Key6 => ::glutin::VirtualKeyCode::Key6, + Key::Key7 => ::glutin::VirtualKeyCode::Key7, + Key::Key8 => ::glutin::VirtualKeyCode::Key8, + Key::Key9 => ::glutin::VirtualKeyCode::Key9, + Key::Key0 => ::glutin::VirtualKeyCode::Key0, + Key::A => ::glutin::VirtualKeyCode::A, + Key::B => ::glutin::VirtualKeyCode::B, + Key::C => ::glutin::VirtualKeyCode::C, + Key::D => ::glutin::VirtualKeyCode::D, + Key::E => ::glutin::VirtualKeyCode::E, + Key::F => ::glutin::VirtualKeyCode::F, + Key::G => ::glutin::VirtualKeyCode::G, + Key::H => ::glutin::VirtualKeyCode::H, + Key::I => ::glutin::VirtualKeyCode::I, + Key::J => ::glutin::VirtualKeyCode::J, + Key::K => ::glutin::VirtualKeyCode::K, + Key::L => ::glutin::VirtualKeyCode::L, + Key::M => ::glutin::VirtualKeyCode::M, + Key::N => ::glutin::VirtualKeyCode::N, + Key::O => ::glutin::VirtualKeyCode::O, + Key::P => ::glutin::VirtualKeyCode::P, + Key::Q => ::glutin::VirtualKeyCode::Q, + Key::R => ::glutin::VirtualKeyCode::R, + Key::S => ::glutin::VirtualKeyCode::S, + Key::T => ::glutin::VirtualKeyCode::T, + Key::U => ::glutin::VirtualKeyCode::U, + Key::V => ::glutin::VirtualKeyCode::V, + Key::W => ::glutin::VirtualKeyCode::W, + Key::X => ::glutin::VirtualKeyCode::X, + Key::Y => ::glutin::VirtualKeyCode::Y, + Key::Z => ::glutin::VirtualKeyCode::Z, + Key::Escape => ::glutin::VirtualKeyCode::Escape, + Key::F1 => ::glutin::VirtualKeyCode::F1, + Key::F2 => ::glutin::VirtualKeyCode::F2, + Key::F3 => ::glutin::VirtualKeyCode::F3, + Key::F4 => ::glutin::VirtualKeyCode::F4, + Key::F5 => ::glutin::VirtualKeyCode::F5, + Key::F6 => ::glutin::VirtualKeyCode::F6, + Key::F7 => ::glutin::VirtualKeyCode::F7, + Key::F8 => ::glutin::VirtualKeyCode::F8, + Key::F9 => ::glutin::VirtualKeyCode::F9, + Key::F10 => ::glutin::VirtualKeyCode::F10, + Key::F11 => ::glutin::VirtualKeyCode::F11, + Key::F12 => ::glutin::VirtualKeyCode::F12, + Key::F13 => ::glutin::VirtualKeyCode::F13, + Key::F14 => ::glutin::VirtualKeyCode::F14, + Key::F15 => ::glutin::VirtualKeyCode::F15, + Key::Snapshot => ::glutin::VirtualKeyCode::Snapshot, + Key::Scroll => ::glutin::VirtualKeyCode::Scroll, + Key::Pause => ::glutin::VirtualKeyCode::Pause, + Key::Insert => ::glutin::VirtualKeyCode::Insert, + Key::Home => ::glutin::VirtualKeyCode::Home, + Key::Delete => ::glutin::VirtualKeyCode::Delete, + Key::End => ::glutin::VirtualKeyCode::End, + Key::PageDown => ::glutin::VirtualKeyCode::PageDown, + Key::PageUp => ::glutin::VirtualKeyCode::PageUp, + Key::Left => ::glutin::VirtualKeyCode::Left, + Key::Up => ::glutin::VirtualKeyCode::Up, + Key::Right => ::glutin::VirtualKeyCode::Right, + Key::Down => ::glutin::VirtualKeyCode::Down, + Key::Back => ::glutin::VirtualKeyCode::Back, + Key::Return => ::glutin::VirtualKeyCode::Return, + Key::Space => ::glutin::VirtualKeyCode::Space, + Key::Compose => ::glutin::VirtualKeyCode::Compose, + Key::Numlock => ::glutin::VirtualKeyCode::Numlock, + Key::Numpad0 => ::glutin::VirtualKeyCode::Numpad0, + Key::Numpad1 => ::glutin::VirtualKeyCode::Numpad1, + Key::Numpad2 => ::glutin::VirtualKeyCode::Numpad2, + Key::Numpad3 => ::glutin::VirtualKeyCode::Numpad3, + Key::Numpad4 => ::glutin::VirtualKeyCode::Numpad4, + Key::Numpad5 => ::glutin::VirtualKeyCode::Numpad5, + Key::Numpad6 => ::glutin::VirtualKeyCode::Numpad6, + Key::Numpad7 => ::glutin::VirtualKeyCode::Numpad7, + Key::Numpad8 => ::glutin::VirtualKeyCode::Numpad8, + Key::Numpad9 => ::glutin::VirtualKeyCode::Numpad9, + Key::AbntC1 => ::glutin::VirtualKeyCode::AbntC1, + Key::AbntC2 => ::glutin::VirtualKeyCode::AbntC2, + Key::Add => ::glutin::VirtualKeyCode::Add, + Key::Apostrophe => ::glutin::VirtualKeyCode::Apostrophe, + Key::Apps => ::glutin::VirtualKeyCode::Apps, + Key::At => ::glutin::VirtualKeyCode::At, + Key::Ax => ::glutin::VirtualKeyCode::Ax, + Key::Backslash => ::glutin::VirtualKeyCode::Backslash, + Key::Calculator => ::glutin::VirtualKeyCode::Calculator, + Key::Capital => ::glutin::VirtualKeyCode::Capital, + Key::Colon => ::glutin::VirtualKeyCode::Colon, + Key::Comma => ::glutin::VirtualKeyCode::Comma, + Key::Convert => ::glutin::VirtualKeyCode::Convert, + Key::Decimal => ::glutin::VirtualKeyCode::Decimal, + Key::Divide => ::glutin::VirtualKeyCode::Divide, + Key::Equals => ::glutin::VirtualKeyCode::Equals, + Key::Grave => ::glutin::VirtualKeyCode::Grave, + Key::Kana => ::glutin::VirtualKeyCode::Kana, + Key::Kanji => ::glutin::VirtualKeyCode::Kanji, + Key::LAlt => ::glutin::VirtualKeyCode::LAlt, + Key::LBracket => ::glutin::VirtualKeyCode::LBracket, + Key::LControl => ::glutin::VirtualKeyCode::LControl, + Key::LMenu => ::glutin::VirtualKeyCode::LMenu, + Key::LShift => ::glutin::VirtualKeyCode::LShift, + Key::LWin => ::glutin::VirtualKeyCode::LWin, + Key::Mail => ::glutin::VirtualKeyCode::Mail, + Key::MediaSelect => ::glutin::VirtualKeyCode::MediaSelect, + Key::MediaStop => ::glutin::VirtualKeyCode::MediaStop, + Key::Minus => ::glutin::VirtualKeyCode::Minus, + Key::Multiply => ::glutin::VirtualKeyCode::Multiply, + Key::Mute => ::glutin::VirtualKeyCode::Mute, + Key::MyComputer => ::glutin::VirtualKeyCode::MyComputer, + Key::NavigateForward => ::glutin::VirtualKeyCode::NavigateForward, + Key::NavigateBackward => ::glutin::VirtualKeyCode::NavigateBackward, + Key::NextTrack => ::glutin::VirtualKeyCode::NextTrack, + Key::NoConvert => ::glutin::VirtualKeyCode::NoConvert, + Key::NumpadComma => ::glutin::VirtualKeyCode::NumpadComma, + Key::NumpadEnter => ::glutin::VirtualKeyCode::NumpadEnter, + Key::NumpadEquals => ::glutin::VirtualKeyCode::NumpadEquals, + Key::OEM102 => ::glutin::VirtualKeyCode::OEM102, + Key::Period => ::glutin::VirtualKeyCode::Period, + Key::PlayPause => ::glutin::VirtualKeyCode::PlayPause, + Key::Power => ::glutin::VirtualKeyCode::Power, + Key::PrevTrack => ::glutin::VirtualKeyCode::PrevTrack, + Key::RAlt => ::glutin::VirtualKeyCode::RAlt, + Key::RBracket => ::glutin::VirtualKeyCode::RBracket, + Key::RControl => ::glutin::VirtualKeyCode::RControl, + Key::RMenu => ::glutin::VirtualKeyCode::RMenu, + Key::RShift => ::glutin::VirtualKeyCode::RShift, + Key::RWin => ::glutin::VirtualKeyCode::RWin, + Key::Semicolon => ::glutin::VirtualKeyCode::Semicolon, + Key::Slash => ::glutin::VirtualKeyCode::Slash, + Key::Sleep => ::glutin::VirtualKeyCode::Sleep, + Key::Stop => ::glutin::VirtualKeyCode::Stop, + Key::Subtract => ::glutin::VirtualKeyCode::Subtract, + Key::Sysrq => ::glutin::VirtualKeyCode::Sysrq, + Key::Tab => ::glutin::VirtualKeyCode::Tab, + Key::Underline => ::glutin::VirtualKeyCode::Underline, + Key::Unlabeled => ::glutin::VirtualKeyCode::Unlabeled, + Key::VolumeDown => ::glutin::VirtualKeyCode::VolumeDown, + Key::VolumeUp => ::glutin::VirtualKeyCode::VolumeUp, + Key::Wake => ::glutin::VirtualKeyCode::Wake, + Key::WebBack => ::glutin::VirtualKeyCode::WebBack, + Key::WebFavorites => ::glutin::VirtualKeyCode::WebFavorites, + Key::WebForward => ::glutin::VirtualKeyCode::WebForward, + Key::WebHome => ::glutin::VirtualKeyCode::WebHome, + Key::WebRefresh => ::glutin::VirtualKeyCode::WebRefresh, + Key::WebSearch => ::glutin::VirtualKeyCode::WebSearch, + Key::WebStop => ::glutin::VirtualKeyCode::WebStop, + Key::Yen => ::glutin::VirtualKeyCode::Yen, + } + } +} diff --git a/src/event.rs b/src/event.rs index 46b96ab0..430671cb 100644 --- a/src/event.rs +++ b/src/event.rs @@ -7,6 +7,7 @@ use input; use sync::FairMutex; use term::Term; use util::encode_char; +use config::Config; /// The event processor pub struct Processor<N> { @@ -24,12 +25,13 @@ impl<N: input::Notify> Processor<N> { pub fn new( notifier: N, terminal: Arc<FairMutex<Term>>, - resize_tx: mpsc::Sender<(u32, u32)> + resize_tx: mpsc::Sender<(u32, u32)>, + config: &Config, ) -> Processor<N> { Processor { notifier: notifier, terminal: terminal, - input_processor: input::Processor::new(), + input_processor: input::Processor::new(config), resize_tx: resize_tx, } } @@ -37,28 +39,6 @@ impl<N: input::Notify> Processor<N> { fn handle_event(&mut self, event: glutin::Event) { match event { glutin::Event::Closed => panic!("window closed"), // TODO ... - // glutin::Event::ReceivedCharacter(c) => { - // match c { - // // Ignore BACKSPACE and DEL. These are handled specially. - // '\u{8}' | '\u{7f}' => (), - // // Extra thing on macOS delete? - // '\u{f728}' => (), - // // OSX arrow keys send invalid characters; ignore. - // '\u{f700}' | '\u{f701}' | '\u{f702}' | '\u{f703}' => (), - // // Same with home/end. Am I missing something? Would be - // // nice if glutin provided the received char in - // // KeyboardInput event so a choice could be made there - // // instead of having to special case everything. - // '\u{f72b}' | '\u{f729}' | '\u{f72c}' | '\u{f72d}' => (), - // // These letters are handled in the bindings system - // 'v' => (), - // _ => { - // println!("printing char {:?}", c); - // let buf = encode_char(c); - // self.notifier.notify(buf); - // } - // } - // }, glutin::Event::Resized(w, h) => { self.resize_tx.send((w, h)).expect("send new size"); // Acquire term lock @@ -99,4 +79,8 @@ impl<N: input::Notify> Processor<N> { self.handle_event(event); } } + + pub fn update_config(&mut self, config: &Config) { + self.input_processor.update_config(config); + } } diff --git a/src/input.rs b/src/input.rs index 667029f2..f99c7a45 100644 --- a/src/input.rs +++ b/src/input.rs @@ -29,8 +29,9 @@ use copypasta::{Clipboard, Load}; use glutin::{ElementState, VirtualKeyCode, MouseButton}; use glutin::{Mods, mods}; -use term::mode::{self, TermMode}; +use config::Config; use event_loop; +use term::mode::{self, TermMode}; use util::encode_char; /// Processes input from glutin. @@ -40,7 +41,10 @@ use util::encode_char; /// /// TODO also need terminal state when processing input #[derive(Default)] -pub struct Processor; +pub struct Processor { + key_bindings: Vec<KeyBinding>, + mouse_bindings: Vec<MouseBinding>, +} /// Types that are notified of escape sequences from the input::Processor. pub trait Notify { @@ -64,195 +68,146 @@ impl Notify for LoopNotifier { } } -/// Describes a key combination that should emit a control sequence +/// Describes a state and action to take in that state /// -/// The actual triggering key is omitted here since bindings are grouped by the trigger key. -#[derive(Debug)] +/// This is the shared component of MouseBinding and KeyBinding +#[derive(Debug, Clone)] pub struct Binding { /// Modifier keys required to activate binding - mods: Mods, + pub mods: Mods, + /// String to send to pty if mods and mode match - action: Action, + pub action: Action, + /// Terminal mode required to activate binding - mode: TermMode, + pub mode: TermMode, + /// excluded terminal modes where the binding won't be activated - notmode: TermMode, + pub notmode: TermMode, +} + +#[derive(Debug, Clone)] +pub struct KeyBinding { + pub key: VirtualKeyCode, + pub binding: Binding, +} + +#[derive(Debug, Clone)] +pub struct MouseBinding { + pub button: MouseButton, + pub binding: Binding, +} + +impl KeyBinding { + #[inline] + fn is_triggered_by( + &self, + mode: &TermMode, + mods: &Mods, + key: &VirtualKeyCode + ) -> bool { + // Check key 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.key == *key && self.binding.is_triggered_by(mode, mods) + } + + #[inline] + fn execute<N: Notify>(&self, notifier: &mut N) { + self.binding.action.execute(notifier) + } +} + +impl MouseBinding { + #[inline] + fn is_triggered_by( + &self, + mode: &TermMode, + mods: &Mods, + button: &MouseButton + ) -> bool { + // Check key 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.button == *button && self.binding.is_triggered_by(mode, mods) + } + + #[inline] + fn execute<N: Notify>(&self, notifier: &mut N) { + self.binding.action.execute(notifier) + } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Action { /// Write an escape sequence - Esc(&'static str), + Esc(String), /// Paste contents of system clipboard Paste, - /// Send a char to pty - Char(char) + /// Paste contents of selection buffer + PasteSelection, } -/// Bindings for the LEFT key. -static LEFT_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2D"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::CONTROL, action: Action::Esc("\x1b[1;5D"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ALT, action: Action::Esc("\x1b[1;3D"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[D"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for the RIGHT key -static RIGHT_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2C"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::CONTROL, action: Action::Esc("\x1b[1;5C"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ALT, action: Action::Esc("\x1b[1;3C"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[C"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1bOC"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for the UP key -static UP_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2A"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::CONTROL, action: Action::Esc("\x1b[1;5A"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ALT, action: Action::Esc("\x1b[1;3A"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[A"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1bOA"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for the DOWN key -static DOWN_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2B"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::CONTROL, action: Action::Esc("\x1b[1;5B"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ALT, action: Action::Esc("\x1b[1;3B"), mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[B"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1bOB"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for the F1 key -static F1_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1bOP"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F2 key -static F2_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1bOQ"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F3 key -static F3_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1bOR"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F4 key -static F4_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1bOS"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F5 key -static F5_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[15~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F6 key -static F6_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[17~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F7 key -static F7_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[18~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F8 key -static F8_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[19~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F9 key -static F9_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[20~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F10 key -static F10_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[21~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F11 key -static F11_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[23~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the F11 key -static F12_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[24~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for PageUp -static PAGEUP_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[5~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for PageDown -static PAGEDOWN_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[6~"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for Home -static HOME_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[H"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[1~"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for End -static END_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[F"), mode: mode::ANY, notmode: mode::APP_CURSOR }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[4~"), mode: mode::APP_CURSOR, notmode: mode::NONE }, -]; - -/// Bindings for the H key -/// -/// Control-H sends 0x08 normally, but we capture that in ReceivedCharacter -/// since DEL and BACKSPACE are inverted. This binding is a work around to that -/// capture. -static H_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::CONTROL, action: Action::Esc("\x08"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the V Key -/// -/// Cmd-V on macOS should trigger a paste -#[cfg(target_os="macos")] -static V_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::SUPER, action: Action::Paste, mode: mode::ANY, notmode: mode::NONE }, - Binding { mods: mods::NONE, action: Action::Char('v'), mode: mode::ANY, notmode: mode::NONE }, -]; - -#[cfg(not(target_os="macos"))] -static V_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::NONE, action: Action::Char('v'), mode: mode::ANY, notmode: mode::NONE }, -]; - -#[cfg(target_os="linux")] -static MOUSE_MIDDLE_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Paste, mode: mode::ANY, notmode: mode::NONE }, -]; - -#[cfg(not(target_os="linux"))] -static MOUSE_MIDDLE_BINDINGS: &'static [Binding] = &[]; - -static MOUSE_LEFT_BINDINGS: &'static [Binding] = &[]; -static MOUSE_RIGHT_BINDINGS: &'static [Binding] = &[]; - -/// Bindings for the Backspace key -static BACKSPACE_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x7f"), mode: mode::ANY, notmode: mode::NONE }, -]; - -/// Bindings for the Delete key -static DELETE_BINDINGS: &'static [Binding] = &[ - Binding { mods: mods::ANY, action: Action::Esc("\x1b[3~"), mode: mode::APP_KEYPAD, notmode: mode::NONE }, - Binding { mods: mods::ANY, action: Action::Esc("\x1b[P"), mode: mode::ANY, notmode: mode::APP_KEYPAD }, -]; +impl Action { + #[inline] + fn execute<N: Notify>(&self, notifier: &mut N) { + match *self { + Action::Esc(ref s) => notifier.notify(s.clone().into_bytes()), + Action::Paste | Action::PasteSelection => { + println!("paste request"); + let clip = Clipboard::new().expect("get clipboard"); + clip.load_selection() + .map(|contents| { + println!("got contents"); + notifier.notify(contents.into_bytes()) + }) + .unwrap_or_else(|err| { + err_println!("Error getting clipboard contents: {}", err); + }); + + println!("ok"); + }, + } + } +} + +impl From<&'static str> for Action { + fn from(s: &'static str) -> Action { + Action::Esc(s.into()) + } +} + +impl Binding { + /// Check if this binding is triggered by the current terminal mode, + /// modifier keys, and key pressed. + #[inline] + pub fn is_triggered_by( + &self, + mode: &TermMode, + mods: &Mods, + ) -> bool { + self.mode_matches(mode) && + self.not_mode_matches(mode) && + self.mods_match(mods) + } + + #[inline] + fn mode_matches(&self, mode: &TermMode) -> bool { + self.mode.is_empty() || mode.intersects(self.mode) + } + + #[inline] + fn not_mode_matches(&self, mode: &TermMode) -> bool { + self.notmode.is_empty() || !mode.intersects(self.notmode) + } + + #[inline] + fn mods_match(&self, mods: &Mods) -> bool { + self.mods.is_all() || *mods == self.mods + } +} // key mods escape appkey appcursor crlf // @@ -261,14 +216,17 @@ static DELETE_BINDINGS: &'static [Binding] = &[ // crlf = LNM (Linefeed/new line); wtf is this impl Processor { - pub fn new() -> Processor { - Default::default() + pub fn new(config: &Config) -> Processor { + Processor { + key_bindings: config.key_bindings().to_vec(), + mouse_bindings: config.mouse_bindings().to_vec(), + } } pub fn mouse_input<N: Notify>( &mut self, state: ElementState, - input: MouseButton, + button: MouseButton, notifier: &mut N, mode: TermMode ) { @@ -276,14 +234,13 @@ impl Processor { return; } - let bindings = match input { - MouseButton::Middle => MOUSE_MIDDLE_BINDINGS, - MouseButton::Left => MOUSE_LEFT_BINDINGS, - MouseButton::Right => MOUSE_RIGHT_BINDINGS, - MouseButton::Other(_index) => return, - }; - - self.process_bindings(bindings, mode, notifier, mods::NONE); + Processor::process_mouse_bindings( + &self.mouse_bindings[..], + mode, + notifier, + mods::NONE, + button + ); } pub fn process_key<N: Notify>( @@ -301,42 +258,8 @@ impl Processor { return; } - let bindings = match key { - // Arrows - VirtualKeyCode::Left => Some(LEFT_BINDINGS), - VirtualKeyCode::Up => Some(UP_BINDINGS), - VirtualKeyCode::Down => Some(DOWN_BINDINGS), - VirtualKeyCode::Right => Some(RIGHT_BINDINGS), - // Function keys - VirtualKeyCode::F1 => Some(F1_BINDINGS), - VirtualKeyCode::F2 => Some(F2_BINDINGS), - VirtualKeyCode::F3 => Some(F3_BINDINGS), - VirtualKeyCode::F4 => Some(F4_BINDINGS), - VirtualKeyCode::F5 => Some(F5_BINDINGS), - VirtualKeyCode::F6 => Some(F6_BINDINGS), - VirtualKeyCode::F7 => Some(F7_BINDINGS), - VirtualKeyCode::F8 => Some(F8_BINDINGS), - VirtualKeyCode::F9 => Some(F9_BINDINGS), - VirtualKeyCode::F10 => Some(F10_BINDINGS), - VirtualKeyCode::F11 => Some(F11_BINDINGS), - VirtualKeyCode::F12 => Some(F12_BINDINGS), - VirtualKeyCode::PageUp => Some(PAGEUP_BINDINGS), - VirtualKeyCode::PageDown => Some(PAGEDOWN_BINDINGS), - VirtualKeyCode::Home => Some(HOME_BINDINGS), - VirtualKeyCode::End => Some(END_BINDINGS), - VirtualKeyCode::Back => Some(BACKSPACE_BINDINGS), - VirtualKeyCode::Delete => Some(DELETE_BINDINGS), - VirtualKeyCode::H => Some(H_BINDINGS), - VirtualKeyCode::V => Some(V_BINDINGS), - _ => { - None - }, - }; - - if let Some(bindings) = bindings { - if self.process_bindings(bindings, mode, notifier, mods) { - return; - } + if Processor::process_key_bindings(&self.key_bindings[..], mode, notifier, mods, key) { + return; } // Didn't process a binding; print the provided character @@ -346,51 +269,61 @@ impl Processor { } } - fn process_bindings<N>(&self, - bindings: &[Binding], - mode: TermMode, - notifier: &mut N, - mods: Mods) -> bool + /// Attempts to find a binding and execute its action + /// + /// The provided mode, mods, and key must match what is allowed by a binding + /// for its action to be executed. + /// + /// Returns true if an action is executed. + fn process_key_bindings<N>( + bindings: &[KeyBinding], + mode: TermMode, + notifier: &mut N, + mods: Mods, + key: VirtualKeyCode + ) -> bool + where N: Notify + { + for binding in bindings { + if binding.is_triggered_by(&mode, &mods, &key) { + // binding was triggered; run the action + binding.execute(notifier); + return true; + } + } + + false + } + + /// Attempts to find a binding and execute its action + /// + /// The provided mode, mods, and key must match what is allowed by a binding + /// for its action to be executed. + /// + /// Returns true if an action is executed. + fn process_mouse_bindings<N>( + bindings: &[MouseBinding], + mode: TermMode, + notifier: &mut N, + mods: Mods, + button: MouseButton + ) -> bool where N: Notify { - // Check each binding for binding in bindings { - // TermMode positive - if binding.mode.is_all() || mode.intersects(binding.mode) { - // TermMode negative - if binding.notmode.is_empty() || !mode.intersects(binding.notmode) { - // Modifier keys - if binding.mods.is_all() || mods == binding.mods { - // everything matches; run the binding action - println!("{:?}", binding); - match binding.action { - Action::Esc(s) => notifier.notify(s.as_bytes()), - Action::Paste => { - println!("paste request"); - let clip = Clipboard::new().expect("get clipboard"); - clip.load_selection() - .map(|contents| { - println!("got contents"); - notifier.notify(contents.into_bytes()) - }) - .unwrap_or_else(|err| { - err_println!("Error getting clipboard contents: {}", err); - }); - - println!("ok"); - }, - Action::Char(c) => { - notifier.notify(encode_char(c)); - } - } - - return true; - } - } + if binding.is_triggered_by(&mode, &mods, &button) { + // binding was triggered; run the action + binding.execute(notifier); + return true; } } - return false; + false + } + + pub fn update_config(&mut self, config: &Config) { + self.key_bindings = config.key_bindings().to_vec(); + self.mouse_bindings = config.mouse_bindings().to_vec(); } } @@ -398,13 +331,11 @@ impl Processor { mod tests { use std::borrow::Cow; - use glutin::mods; + use glutin::{mods, VirtualKeyCode}; use term::mode; - use super::Action; - use super::Processor; - use super::Binding; + use super::{Action, Processor, Binding, KeyBinding}; /// Receiver that keeps a copy of any strings it is notified with #[derive(Default)] @@ -418,6 +349,8 @@ mod tests { } } + const KEY: VirtualKeyCode = VirtualKeyCode::Key0; + macro_rules! test_process_binding { { name: $name:ident, @@ -430,10 +363,11 @@ mod tests { fn $name() { let bindings = &[$binding]; - let processor = Processor::new(); let mut receiver = Receiver::default(); - processor.process_bindings(bindings, $mode, &mut receiver, $mods); + Processor::process_key_bindings( + bindings, $mode, &mut receiver, $mods, KEY + ); assert_eq!(receiver.got, $expect); } } @@ -441,7 +375,7 @@ mod tests { test_process_binding! { name: process_binding_nomode_shiftmod_require_shift, - binding: Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2D"), mode: mode::ANY, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE }}, expect: Some(String::from("\x1b[1;2D")), mode: mode::NONE, mods: mods::SHIFT @@ -449,7 +383,7 @@ mod tests { test_process_binding! { name: process_binding_nomode_nomod_require_shift, - binding: Binding { mods: mods::SHIFT, action: Action::Esc("\x1b[1;2D"), mode: mode::ANY, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE }}, expect: None, mode: mode::NONE, mods: mods::NONE @@ -457,7 +391,7 @@ mod tests { test_process_binding! { name: process_binding_nomode_controlmod, - binding: Binding { mods: mods::CONTROL, action: Action::Esc("\x1b[1;5D"), mode: mode::ANY, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::CONTROL, action: Action::from("\x1b[1;5D"), mode: mode::NONE, notmode: mode::NONE }}, expect: Some(String::from("\x1b[1;5D")), mode: mode::NONE, mods: mods::CONTROL @@ -465,7 +399,7 @@ mod tests { test_process_binding! { name: process_binding_nomode_nomod_require_not_appcursor, - binding: Binding { mods: mods::ANY, action: Action::Esc("\x1b[D"), mode: mode::ANY, notmode: mode::APP_CURSOR }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1b[D"), mode: mode::NONE, notmode: mode::APP_CURSOR }}, expect: Some(String::from("\x1b[D")), mode: mode::NONE, mods: mods::NONE @@ -473,7 +407,7 @@ mod tests { test_process_binding! { name: process_binding_appcursormode_nomod_require_appcursor, - binding: Binding { mods: mods::ANY, action: Action::Esc("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }}, expect: Some(String::from("\x1bOD")), mode: mode::APP_CURSOR, mods: mods::NONE @@ -481,7 +415,7 @@ mod tests { test_process_binding! { name: process_binding_nomode_nomod_require_appcursor, - binding: Binding { mods: mods::ANY, action: Action::Esc("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }}, expect: None, mode: mode::NONE, mods: mods::NONE @@ -489,7 +423,7 @@ mod tests { test_process_binding! { name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor, - binding: Binding { mods: mods::ANY, action: Action::Esc("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }}, expect: Some(String::from("\x1bOD")), mode: mode::APP_CURSOR | mode::APP_KEYPAD, mods: mods::NONE @@ -497,22 +431,9 @@ mod tests { test_process_binding! { name: process_binding_fail_with_extra_mods, - binding: Binding { mods: mods::SUPER, action: Action::Esc("arst"), mode: mode::ANY, notmode: mode::NONE }, + binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SUPER, action: Action::from("arst"), mode: mode::NONE, notmode: mode::NONE }}, expect: None, mode: mode::NONE, mods: mods::SUPER | mods::ALT } - - test_process_binding! { - name: process_binding_with_mods_none, - binding: Binding { mods: mods::NONE, action: Action::Char('v'), mode: mode::ANY, notmode: mode::NONE }, - expect: Some(String::from("v")), - mode: mode::NONE, - mods: mods::NONE - } - - #[test] - fn print_v_bindings() { - println!("{:#?}", super::V_BINDINGS); - } } diff --git a/src/main.rs b/src/main.rs index 38ecde39..ad8cfed9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -233,7 +233,8 @@ fn main() { let mut processor = event::Processor::new( input::LoopNotifier(loop_tx), terminal.clone(), - tx + tx, + &config ); let (config_tx, config_rx) = mpsc::channel(); @@ -253,6 +254,7 @@ fn main() { if let Ok(config) = config_rx.try_recv() { display.update_config(&config); + processor.update_config(&config); } // Maybe draw the terminal |