aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoe Wilm <joe@jwilm.com>2016-11-15 09:38:50 -0800
committerJoe Wilm <joe@jwilm.com>2016-11-17 17:17:54 -0800
commitd97996e19de6856c23c51d05ec10f10db41e309d (patch)
tree80e43240687249f5062bcd16d64492ae32b0625d /src
parentcb2bc4eadd3d149fcae4d1b552be47bc5a9413d8 (diff)
downloadalacritty-d97996e19de6856c23c51d05ec10f10db41e309d.tar.gz
alacritty-d97996e19de6856c23c51d05ec10f10db41e309d.zip
Make bindings configurable from alacritty.yml
Bindings were previously hardcoded within input.rs; adding, removing, or changing a binding required a recompile! Now, bindings may be declared in alacritty.yml. Even better, bindings are live-reloaded when alacritty.yml is changed! One unexpected benefit of this change was that all of the special casing in input.rs has disappeared. Conversely, config.rs has gained complexity for all of the deserialization logic. Resolves #3.
Diffstat (limited to 'src')
-rw-r--r--src/config.rs726
-rw-r--r--src/event.rs32
-rw-r--r--src/input.rs497
-rw-r--r--src/main.rs4
4 files changed, 945 insertions, 314 deletions
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