aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--alacritty.yml91
-rw-r--r--src/config.rs726
-rw-r--r--src/event.rs32
-rw-r--r--src/input.rs497
-rw-r--r--src/main.rs4
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