2016-06-30 03:56:12 +00:00
|
|
|
// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
//
|
2016-06-23 16:48:31 +00:00
|
|
|
//! Handle input from glutin
|
|
|
|
//!
|
|
|
|
//! Certain key combinations should send some escape sequence back to the pty.
|
|
|
|
//! In order to figure that out, state about which modifier keys are pressed
|
|
|
|
//! needs to be tracked. Additionally, we need a bit of a state machine to
|
|
|
|
//! determine what to do when a non-modifier key is pressed.
|
|
|
|
//!
|
|
|
|
//! TODO would be nice to generalize this so it could work with other windowing
|
|
|
|
//! APIs
|
|
|
|
//!
|
|
|
|
//! TODO handling xmodmap would be good
|
2016-09-24 23:11:50 +00:00
|
|
|
use std::borrow::Cow;
|
2016-12-12 06:02:03 +00:00
|
|
|
use std::mem;
|
2016-07-02 15:32:28 +00:00
|
|
|
|
2016-10-09 01:42:33 +00:00
|
|
|
use copypasta::{Clipboard, Load};
|
2016-09-27 05:22:28 +00:00
|
|
|
use glutin::{ElementState, VirtualKeyCode, MouseButton};
|
2016-07-30 19:47:22 +00:00
|
|
|
use glutin::{Mods, mods};
|
2016-11-25 03:50:34 +00:00
|
|
|
use glutin::{TouchPhase, MouseScrollDelta};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
use config::Config;
|
2016-09-26 02:49:44 +00:00
|
|
|
use event_loop;
|
2016-12-12 06:02:03 +00:00
|
|
|
use index::{Line, Column};
|
2016-11-24 04:25:37 +00:00
|
|
|
use term::mode::{self, TermMode};
|
2016-12-12 06:02:03 +00:00
|
|
|
use term::{self, Term};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
|
|
|
/// Processes input from glutin.
|
|
|
|
///
|
|
|
|
/// An escape sequence may be emitted in case specific keys or key combinations
|
|
|
|
/// are activated.
|
|
|
|
///
|
|
|
|
/// TODO also need terminal state when processing input
|
2016-11-15 17:38:50 +00:00
|
|
|
pub struct Processor {
|
|
|
|
key_bindings: Vec<KeyBinding>,
|
|
|
|
mouse_bindings: Vec<MouseBinding>,
|
2016-11-24 04:25:37 +00:00
|
|
|
mouse: Mouse,
|
2016-12-12 06:02:03 +00:00
|
|
|
size_info: term::SizeInfo,
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// State of the mouse
|
|
|
|
pub struct Mouse {
|
|
|
|
x: u32,
|
|
|
|
y: u32,
|
|
|
|
left_button_state: ElementState,
|
2016-11-25 03:50:34 +00:00
|
|
|
scroll_px: i32,
|
2016-12-12 06:02:03 +00:00
|
|
|
line: Line,
|
|
|
|
column: Column,
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for Mouse {
|
|
|
|
fn default() -> Mouse {
|
|
|
|
Mouse {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
2016-11-25 03:50:34 +00:00
|
|
|
left_button_state: ElementState::Pressed,
|
|
|
|
scroll_px: 0,
|
2016-12-12 06:02:03 +00:00
|
|
|
line: Line(0),
|
|
|
|
column: Column(0),
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2016-12-17 06:13:51 +00:00
|
|
|
/// Types that are notified of escape sequences from the `input::Processor`.
|
2016-06-23 16:48:31 +00:00
|
|
|
pub trait Notify {
|
|
|
|
/// Notify that an escape sequence should be written to the pty
|
2016-09-24 23:11:50 +00:00
|
|
|
///
|
|
|
|
/// TODO this needs to be able to error somehow
|
|
|
|
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, B);
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2016-09-26 02:49:44 +00:00
|
|
|
pub struct LoopNotifier(pub ::mio::channel::Sender<event_loop::Msg>);
|
2016-09-24 23:11:50 +00:00
|
|
|
|
|
|
|
impl Notify for LoopNotifier {
|
|
|
|
fn notify<B>(&mut self, bytes: B)
|
|
|
|
where B: Into<Cow<'static, [u8]>>
|
|
|
|
{
|
|
|
|
let bytes = bytes.into();
|
2016-09-26 02:49:44 +00:00
|
|
|
match self.0.send(event_loop::Msg::Input(bytes)) {
|
2016-09-24 23:11:50 +00:00
|
|
|
Ok(_) => (),
|
|
|
|
Err(_) => panic!("expected send event loop msg"),
|
|
|
|
}
|
2016-07-02 15:32:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// Describes a state and action to take in that state
|
2016-06-23 16:48:31 +00:00
|
|
|
///
|
2016-12-17 06:13:51 +00:00
|
|
|
/// This is the shared component of `MouseBinding` and `KeyBinding`
|
2016-11-15 17:38:50 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2016-06-23 16:48:31 +00:00
|
|
|
pub struct Binding {
|
|
|
|
/// Modifier keys required to activate binding
|
2016-11-15 17:38:50 +00:00
|
|
|
pub mods: Mods,
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// String to send to pty if mods and mode match
|
2016-11-15 17:38:50 +00:00
|
|
|
pub action: Action,
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// Terminal mode required to activate binding
|
2016-11-15 17:38:50 +00:00
|
|
|
pub mode: TermMode,
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
/// excluded terminal modes where the binding won't be activated
|
2016-11-15 17:38:50 +00:00
|
|
|
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]
|
2016-11-28 22:39:37 +00:00
|
|
|
fn execute<N: Notify>(&self, notifier: &mut N, mode: &TermMode) {
|
|
|
|
self.binding.action.execute(notifier, mode)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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]
|
2016-11-28 22:39:37 +00:00
|
|
|
fn execute<N: Notify>(&self, notifier: &mut N, mode: &TermMode) {
|
|
|
|
self.binding.action.execute(notifier, mode)
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
#[derive(Debug, Clone)]
|
2016-09-27 05:22:28 +00:00
|
|
|
pub enum Action {
|
|
|
|
/// Write an escape sequence
|
2016-11-15 17:38:50 +00:00
|
|
|
Esc(String),
|
2016-09-27 05:22:28 +00:00
|
|
|
|
|
|
|
/// Paste contents of system clipboard
|
|
|
|
Paste,
|
2016-09-27 15:27:39 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// Paste contents of selection buffer
|
|
|
|
PasteSelection,
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
impl Action {
|
|
|
|
#[inline]
|
2016-11-28 22:39:37 +00:00
|
|
|
fn execute<N: Notify>(&self, notifier: &mut N, mode: &TermMode) {
|
2016-11-15 17:38:50 +00:00
|
|
|
match *self {
|
|
|
|
Action::Esc(ref s) => notifier.notify(s.clone().into_bytes()),
|
|
|
|
Action::Paste | Action::PasteSelection => {
|
|
|
|
let clip = Clipboard::new().expect("get clipboard");
|
|
|
|
clip.load_selection()
|
|
|
|
.map(|contents| {
|
2016-11-28 22:39:37 +00:00
|
|
|
if mode.contains(mode::BRACKETED_PASTE) {
|
2016-12-17 06:13:51 +00:00
|
|
|
notifier.notify(&b"\x1b[200~"[..]);
|
2016-11-28 22:39:37 +00:00
|
|
|
notifier.notify(contents.into_bytes());
|
2016-12-17 06:13:51 +00:00
|
|
|
notifier.notify(&b"\x1b[201~"[..]);
|
2016-11-28 22:39:37 +00:00
|
|
|
} else {
|
|
|
|
notifier.notify(contents.into_bytes());
|
|
|
|
}
|
2016-11-15 17:38:50 +00:00
|
|
|
})
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
err_println!("Error getting clipboard contents: {}", err);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2016-07-04 16:24:51 +00:00
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
// key mods escape appkey appcursor crlf
|
|
|
|
//
|
|
|
|
// notes: appkey = DECPAM (application keypad mode); not enabled is "normal keypad"
|
|
|
|
// appcursor = DECCKM (application cursor mode);
|
|
|
|
// crlf = LNM (Linefeed/new line); wtf is this
|
|
|
|
|
|
|
|
impl Processor {
|
2016-12-12 06:02:03 +00:00
|
|
|
pub fn resize(&mut self, size_info: &term::SizeInfo) {
|
|
|
|
self.size_info = size_info.to_owned();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn new(config: &Config, size_info: &term::SizeInfo) -> Processor {
|
2016-11-15 17:38:50 +00:00
|
|
|
Processor {
|
|
|
|
key_bindings: config.key_bindings().to_vec(),
|
|
|
|
mouse_bindings: config.mouse_bindings().to_vec(),
|
2016-11-24 04:25:37 +00:00
|
|
|
mouse: Mouse::default(),
|
2016-12-12 06:02:03 +00:00
|
|
|
size_info: size_info.to_owned(),
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn mouse_moved(&mut self, x: u32, y: u32) {
|
|
|
|
// Record mouse position within window. Pixel coordinates are *not*
|
|
|
|
// translated to grid coordinates here since grid coordinates are rarely
|
|
|
|
// needed and the mouse position updates frequently.
|
|
|
|
self.mouse.x = x;
|
|
|
|
self.mouse.y = y;
|
2016-12-12 06:02:03 +00:00
|
|
|
|
|
|
|
if let Some((line, column)) = self.size_info.pixels_to_coords(x as usize, y as usize) {
|
|
|
|
// Swap values for following comparison
|
2016-12-12 17:31:29 +00:00
|
|
|
let _line = mem::replace(&mut self.mouse.line, line);
|
|
|
|
let _column = mem::replace(&mut self.mouse.column, column);
|
|
|
|
|
|
|
|
// TODO process changes
|
2016-12-12 06:02:03 +00:00
|
|
|
}
|
2016-11-24 04:25:37 +00:00
|
|
|
}
|
|
|
|
|
2016-12-12 06:02:03 +00:00
|
|
|
pub fn mouse_report<N: Notify>(
|
2016-11-24 04:25:37 +00:00
|
|
|
&mut self,
|
|
|
|
button: u8,
|
|
|
|
notifier: &mut N,
|
|
|
|
terminal: &Term
|
|
|
|
) {
|
|
|
|
if terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
|
|
|
|
let (line, column) = terminal.pixels_to_coords(
|
|
|
|
self.mouse.x as usize,
|
|
|
|
self.mouse.y as usize
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
if line < Line(223) && column < Column(223) {
|
|
|
|
let msg = vec![
|
|
|
|
'\x1b' as u8,
|
|
|
|
'[' as u8,
|
|
|
|
'M' as u8,
|
|
|
|
32 + button,
|
|
|
|
32 + 1 + column.0 as u8,
|
|
|
|
32 + 1 + line.0 as u8,
|
|
|
|
];
|
|
|
|
|
|
|
|
notifier.notify(msg);
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
2016-11-24 04:25:37 +00:00
|
|
|
pub fn on_mouse_press<N: Notify>(&mut self, notifier: &mut N, terminal: &Term) {
|
|
|
|
self.mouse_report(0, notifier, terminal);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn on_mouse_release<N: Notify>(&mut self, notifier: &mut N, terminal: &Term) {
|
|
|
|
self.mouse_report(3, notifier, terminal);
|
|
|
|
}
|
|
|
|
|
2016-11-25 03:50:34 +00:00
|
|
|
pub fn on_mouse_wheel<N: Notify>(
|
|
|
|
&mut self,
|
|
|
|
notifier: &mut N,
|
|
|
|
delta: MouseScrollDelta,
|
|
|
|
phase: TouchPhase,
|
|
|
|
terminal: &Term
|
|
|
|
) {
|
|
|
|
match delta {
|
2016-12-12 04:00:00 +00:00
|
|
|
MouseScrollDelta::LineDelta(_columns, lines) => {
|
|
|
|
let code = if lines > 0.0 {
|
|
|
|
64
|
|
|
|
} else {
|
|
|
|
65
|
|
|
|
};
|
|
|
|
|
|
|
|
for _ in 0..(lines.abs() as usize) {
|
|
|
|
self.mouse_report(code, notifier, terminal);
|
|
|
|
}
|
2016-11-25 03:50:34 +00:00
|
|
|
},
|
|
|
|
MouseScrollDelta::PixelDelta(_x, y) => {
|
|
|
|
match phase {
|
|
|
|
TouchPhase::Started => {
|
|
|
|
// Reset offset to zero
|
|
|
|
self.mouse.scroll_px = 0;
|
|
|
|
},
|
|
|
|
TouchPhase::Moved => {
|
|
|
|
self.mouse.scroll_px += y as i32;
|
|
|
|
let size = terminal.size_info();
|
|
|
|
let height = size.cell_height as i32;
|
|
|
|
|
|
|
|
while self.mouse.scroll_px.abs() >= height {
|
|
|
|
let button = if self.mouse.scroll_px > 0 {
|
|
|
|
self.mouse.scroll_px -= height;
|
|
|
|
64
|
|
|
|
} else {
|
|
|
|
self.mouse.scroll_px += height;
|
|
|
|
65
|
|
|
|
};
|
|
|
|
|
|
|
|
self.mouse_report(button, notifier, terminal);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 05:22:28 +00:00
|
|
|
pub fn mouse_input<N: Notify>(
|
|
|
|
&mut self,
|
|
|
|
state: ElementState,
|
2016-11-15 17:38:50 +00:00
|
|
|
button: MouseButton,
|
2016-09-27 05:22:28 +00:00
|
|
|
notifier: &mut N,
|
2016-11-24 04:25:37 +00:00
|
|
|
terminal: &Term
|
2016-09-27 05:22:28 +00:00
|
|
|
) {
|
2016-11-24 04:25:37 +00:00
|
|
|
if let MouseButton::Left = button {
|
|
|
|
// TODO handle state changes
|
|
|
|
if self.mouse.left_button_state != state {
|
|
|
|
self.mouse.left_button_state = state;
|
|
|
|
match state {
|
|
|
|
ElementState::Pressed => {
|
|
|
|
self.on_mouse_press(notifier, terminal);
|
|
|
|
},
|
|
|
|
ElementState::Released => {
|
|
|
|
self.on_mouse_release(notifier, terminal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 05:22:28 +00:00
|
|
|
if let ElementState::Released = state {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
Processor::process_mouse_bindings(
|
|
|
|
&self.mouse_bindings[..],
|
2016-11-24 04:25:37 +00:00
|
|
|
*terminal.mode(),
|
2016-11-15 17:38:50 +00:00
|
|
|
notifier,
|
|
|
|
mods::NONE,
|
|
|
|
button
|
|
|
|
);
|
2016-09-27 05:22:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn process_key<N: Notify>(
|
|
|
|
&mut self,
|
|
|
|
state: ElementState,
|
|
|
|
key: Option<VirtualKeyCode>,
|
|
|
|
mods: Mods,
|
|
|
|
notifier: &mut N,
|
2016-11-12 03:59:18 +00:00
|
|
|
mode: TermMode,
|
|
|
|
string: Option<String>,
|
2016-09-27 05:22:28 +00:00
|
|
|
) {
|
2016-06-23 16:48:31 +00:00
|
|
|
if let Some(key) = key {
|
|
|
|
// Ignore release events
|
|
|
|
if state == ElementState::Released {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
if Processor::process_key_bindings(&self.key_bindings[..], mode, notifier, mods, key) {
|
|
|
|
return;
|
2016-11-12 03:59:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Didn't process a binding; print the provided character
|
|
|
|
if let Some(string) = string {
|
|
|
|
notifier.notify(string.into_bytes());
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
/// 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
|
2016-11-28 22:39:37 +00:00
|
|
|
binding.execute(notifier, &mode);
|
2016-11-15 17:38:50 +00:00
|
|
|
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
|
2016-06-23 16:48:31 +00:00
|
|
|
where N: Notify
|
|
|
|
{
|
|
|
|
for binding in bindings {
|
2016-11-15 17:38:50 +00:00
|
|
|
if binding.is_triggered_by(&mode, &mods, &button) {
|
|
|
|
// binding was triggered; run the action
|
2016-11-28 22:39:37 +00:00
|
|
|
binding.execute(notifier, &mode);
|
2016-11-15 17:38:50 +00:00
|
|
|
return true;
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
2016-11-12 03:59:18 +00:00
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
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();
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2016-09-27 15:27:39 +00:00
|
|
|
use std::borrow::Cow;
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
use glutin::{mods, VirtualKeyCode};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
2016-09-27 15:27:39 +00:00
|
|
|
use term::mode;
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
use super::{Action, Processor, Binding, KeyBinding};
|
2016-06-23 16:48:31 +00:00
|
|
|
|
|
|
|
/// Receiver that keeps a copy of any strings it is notified with
|
|
|
|
#[derive(Default)]
|
|
|
|
struct Receiver {
|
|
|
|
pub got: Option<String>
|
|
|
|
}
|
|
|
|
|
|
|
|
impl super::Notify for Receiver {
|
2016-09-27 15:27:39 +00:00
|
|
|
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, item: B) {
|
|
|
|
self.got = Some(String::from_utf8(item.into().to_vec()).unwrap());
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
const KEY: VirtualKeyCode = VirtualKeyCode::Key0;
|
|
|
|
|
2016-06-23 16:48:31 +00:00
|
|
|
macro_rules! test_process_binding {
|
|
|
|
{
|
|
|
|
name: $name:ident,
|
|
|
|
binding: $binding:expr,
|
|
|
|
expect: $expect:expr,
|
|
|
|
mode: $mode:expr,
|
|
|
|
mods: $mods:expr
|
|
|
|
} => {
|
|
|
|
#[test]
|
|
|
|
fn $name() {
|
|
|
|
let bindings = &[$binding];
|
|
|
|
|
|
|
|
let mut receiver = Receiver::default();
|
|
|
|
|
2016-11-15 17:38:50 +00:00
|
|
|
Processor::process_key_bindings(
|
|
|
|
bindings, $mode, &mut receiver, $mods, KEY
|
|
|
|
);
|
2016-06-23 16:48:31 +00:00
|
|
|
assert_eq!(receiver.got, $expect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_shiftmod_require_shift,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: Some(String::from("\x1b[1;2D")),
|
|
|
|
mode: mode::NONE,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::SHIFT
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_shift,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SHIFT, action: Action::from("\x1b[1;2D"), mode: mode::NONE, notmode: mode::NONE }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: None,
|
|
|
|
mode: mode::NONE,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::NONE
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_controlmod,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::CONTROL, action: Action::from("\x1b[1;5D"), mode: mode::NONE, notmode: mode::NONE }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: Some(String::from("\x1b[1;5D")),
|
|
|
|
mode: mode::NONE,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::CONTROL
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_not_appcursor,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1b[D"), mode: mode::NONE, notmode: mode::APP_CURSOR }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: Some(String::from("\x1b[D")),
|
|
|
|
mode: mode::NONE,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::NONE
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_nomod_require_appcursor,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: Some(String::from("\x1bOD")),
|
|
|
|
mode: mode::APP_CURSOR,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::NONE
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_nomode_nomod_require_appcursor,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }},
|
2016-06-23 16:48:31 +00:00
|
|
|
expect: None,
|
|
|
|
mode: mode::NONE,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::NONE
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|
2016-07-30 01:27:45 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_appcursormode_appkeypadmode_nomod_require_appcursor,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::ANY, action: Action::from("\x1bOD"), mode: mode::APP_CURSOR, notmode: mode::NONE }},
|
2016-07-30 01:27:45 +00:00
|
|
|
expect: Some(String::from("\x1bOD")),
|
|
|
|
mode: mode::APP_CURSOR | mode::APP_KEYPAD,
|
2016-07-30 19:47:22 +00:00
|
|
|
mods: mods::NONE
|
2016-07-30 01:27:45 +00:00
|
|
|
}
|
2016-09-27 15:27:39 +00:00
|
|
|
|
|
|
|
test_process_binding! {
|
|
|
|
name: process_binding_fail_with_extra_mods,
|
2016-11-15 17:38:50 +00:00
|
|
|
binding: KeyBinding { key: KEY, binding: Binding { mods: mods::SUPER, action: Action::from("arst"), mode: mode::NONE, notmode: mode::NONE }},
|
2016-09-27 15:27:39 +00:00
|
|
|
expect: None,
|
|
|
|
mode: mode::NONE,
|
|
|
|
mods: mods::SUPER | mods::ALT
|
|
|
|
}
|
2016-06-23 16:48:31 +00:00
|
|
|
}
|