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.
This commit is contained in:
Joe Wilm 2016-11-15 09:38:50 -08:00
parent cb2bc4eadd
commit d97996e19d
5 changed files with 1030 additions and 308 deletions

View File

@ -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 }

View File

@ -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,10 +64,380 @@ 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"))
}
}
/// Errors occurring during config loading
#[derive(Debug)]
pub enum Error {
@ -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,
}
}
}

View File

@ -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);
}
}

View File

@ -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)]
#[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, 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 },
];
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);
});
/// 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 },
];
println!("ok");
},
}
}
}
/// 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 },
];
impl From<&'static str> for Action {
fn from(s: &'static str) -> Action {
Action::Esc(s.into())
}
}
/// 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 },
];
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)
}
/// Bindings for the F1 key
static F1_BINDINGS: &'static [Binding] = &[
Binding { mods: mods::ANY, action: Action::Esc("\x1bOP"), mode: mode::ANY, notmode: mode::NONE },
];
#[inline]
fn mode_matches(&self, mode: &TermMode) -> bool {
self.mode.is_empty() || mode.intersects(self.mode)
}
/// Bindings for the F2 key
static F2_BINDINGS: &'static [Binding] = &[
Binding { mods: mods::ANY, action: Action::Esc("\x1bOQ"), mode: mode::ANY, notmode: mode::NONE },
];
#[inline]
fn not_mode_matches(&self, mode: &TermMode) -> bool {
self.notmode.is_empty() || !mode.intersects(self.notmode)
}
/// 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 },
];
#[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
{
// 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, &key) {
// binding was triggered; run the action
binding.execute(notifier);
return true;
}
}
return false;
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
{
for binding in bindings {
if binding.is_triggered_by(&mode, &mods, &button) {
// binding was triggered; run the action
binding.execute(notifier);
return true;
}
}
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);
}
}

View File

@ -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