Major cleanup for event handling

The event handling code grew organically over time, and with that came a
number of warts. The primary issue was with passing some random
selection of arguments to the input::Processor based on what the input
was. There was the issue that if multiple events were drained from a
single PollEventsIterator, the terminal mutex was potentially locked and
unlocked many times. Finally, and perhaps most importantly, there was no
good way to pass necessary state to the Action executor without going
through several API layers.

To fix all that, the input::ActionContext and input::Processor are now
generated once per call to the event::Processor. The input::Processor
holds onto the ActionContext so that it doesn't need to be passed
through layers of function calls. When a binding is activated, the
ActionContext is simply passed to the handler.

This did have the effect of breaking the input::Processor tests
(specifically, those relating to bindings). The issue was not addressed
in this commit since a larger refactor of the bindings is planned which
should also improve testability.
This commit is contained in:
Joe Wilm 2016-12-26 18:33:27 -05:00
parent 3b7c7377c9
commit 358c9fa17d
4 changed files with 209 additions and 238 deletions

View File

@ -1,26 +1,67 @@
//! Process window events //! Process window events
use std::borrow::Cow;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::sync::{Arc, mpsc}; use std::sync::{Arc, mpsc};
use serde_json as json; use serde_json as json;
use glutin; use glutin::{self, ElementState};
use index::{Line, Column, Side};
use config::Config; use config::Config;
use display::OnResize; use display::OnResize;
use input::{self, ActionContext}; use input::{self, ActionContext, MouseBinding, KeyBinding};
use selection::Selection; use selection::Selection;
use sync::FairMutex; use sync::FairMutex;
use term::{Term, SizeInfo}; use term::{Term, SizeInfo};
use window::Window; use window::Window;
/// Byte sequences are sent to a `Notify` in response to some events
pub trait Notify {
/// Notify that an escape sequence should be written to the pty
///
/// TODO this needs to be able to error somehow
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, B);
}
/// State of the mouse
pub struct Mouse {
pub x: u32,
pub y: u32,
pub left_button_state: ElementState,
pub scroll_px: i32,
pub line: Line,
pub column: Column,
pub cell_side: Side
}
impl Default for Mouse {
fn default() -> Mouse {
Mouse {
x: 0,
y: 0,
left_button_state: ElementState::Released,
scroll_px: 0,
line: Line(0),
column: Column(0),
cell_side: Side::Left,
}
}
}
/// The event processor /// The event processor
///
/// Stores some state from received events and dispatches actions when they are
/// triggered.
pub struct Processor<N> { pub struct Processor<N> {
key_bindings: Vec<KeyBinding>,
mouse_bindings: Vec<MouseBinding>,
notifier: N, notifier: N,
input_processor: input::Processor, mouse: Mouse,
terminal: Arc<FairMutex<Term>>, terminal: Arc<FairMutex<Term>>,
resize_tx: mpsc::Sender<(u32, u32)>, resize_tx: mpsc::Sender<(u32, u32)>,
ref_test: bool, ref_test: bool,
size_info: SizeInfo,
pub selection: Selection, pub selection: Selection,
} }
@ -29,11 +70,11 @@ pub struct Processor<N> {
/// Currently this just forwards the notice to the input processor. /// Currently this just forwards the notice to the input processor.
impl<N> OnResize for Processor<N> { impl<N> OnResize for Processor<N> {
fn on_resize(&mut self, size: &SizeInfo) { fn on_resize(&mut self, size: &SizeInfo) {
self.input_processor.resize(size); self.size_info = size.to_owned();
} }
} }
impl<N: input::Notify> Processor<N> { impl<N: Notify> Processor<N> {
/// Create a new event processor /// Create a new event processor
/// ///
/// Takes a writer which is expected to be hooked up to the write end of a /// Takes a writer which is expected to be hooked up to the write end of a
@ -45,33 +86,44 @@ impl<N: input::Notify> Processor<N> {
config: &Config, config: &Config,
ref_test: bool, ref_test: bool,
) -> Processor<N> { ) -> Processor<N> {
let input_processor = { let size_info = {
let terminal = terminal.lock(); let terminal = terminal.lock();
input::Processor::new(config, terminal.size_info()) terminal.size_info().to_owned()
}; };
Processor { Processor {
key_bindings: config.key_bindings().to_vec(),
mouse_bindings: config.mouse_bindings().to_vec(),
notifier: notifier, notifier: notifier,
terminal: terminal, terminal: terminal,
input_processor: input_processor,
resize_tx: resize_tx, resize_tx: resize_tx,
ref_test: ref_test, ref_test: ref_test,
mouse: Default::default(),
selection: Default::default(), selection: Default::default(),
size_info: size_info,
} }
} }
fn handle_event(&mut self, event: glutin::Event, wakeup_request: &mut bool) { /// Handle events from glutin
///
/// Doesn't take self mutably due to borrow checking. Kinda uggo but w/e.
fn handle_event<'a>(
processor: &mut input::Processor<'a, N>,
event: glutin::Event,
wakeup_request: &mut bool,
ref_test: bool,
resize_tx: &mpsc::Sender<(u32, u32)>,
) {
match event { match event {
glutin::Event::Closed => { glutin::Event::Closed => {
if self.ref_test { if ref_test {
// dump grid state // dump grid state
let terminal = self.terminal.lock(); let grid = processor.ctx.terminal.grid();
let grid = terminal.grid();
let serialized_grid = json::to_string(&grid) let serialized_grid = json::to_string(&grid)
.expect("serialize grid"); .expect("serialize grid");
let serialized_size = json::to_string(terminal.size_info()) let serialized_size = json::to_string(processor.ctx.terminal.size_info())
.expect("serialize size"); .expect("serialize size");
File::create("./grid.json") File::create("./grid.json")
@ -87,7 +139,7 @@ impl<N: input::Notify> Processor<N> {
panic!("window closed"); panic!("window closed");
}, },
glutin::Event::Resized(w, h) => { glutin::Event::Resized(w, h) => {
self.resize_tx.send((w, h)).expect("send new size"); resize_tx.send((w, h)).expect("send new size");
// Previously, this marked the terminal state as "dirty", but // Previously, this marked the terminal state as "dirty", but
// now the wakeup_request controls whether a display update is // now the wakeup_request controls whether a display update is
@ -95,67 +147,27 @@ impl<N: input::Notify> Processor<N> {
*wakeup_request = true; *wakeup_request = true;
}, },
glutin::Event::KeyboardInput(state, _code, key, mods, string) => { glutin::Event::KeyboardInput(state, _code, key, mods, string) => {
// Acquire term lock processor.process_key(state, key, mods, string);
let terminal = self.terminal.lock(); processor.ctx.selection.clear();
let processor = &mut self.input_processor;
{
let mut context = ActionContext {
terminal: &terminal,
notifier: &mut self.notifier,
selection: &mut self.selection,
};
processor.process_key(&mut context, state, key, mods, string);
}
self.selection.clear();
}, },
glutin::Event::MouseInput(state, button) => { glutin::Event::MouseInput(state, button) => {
let terminal = self.terminal.lock(); processor.mouse_input(state, button);
let processor = &mut self.input_processor;
let mut context = ActionContext {
terminal: &terminal,
notifier: &mut self.notifier,
selection: &mut self.selection,
};
processor.mouse_input(&mut context, state, button);
*wakeup_request = true; *wakeup_request = true;
}, },
glutin::Event::MouseMoved(x, y) => { glutin::Event::MouseMoved(x, y) => {
if x > 0 && y > 0 { if x > 0 && y > 0 {
let terminal = self.terminal.lock(); processor.mouse_moved(x as u32, y as u32);
self.input_processor.mouse_moved(
&mut self.selection, if !processor.ctx.selection.is_empty() {
*terminal.mode(),
x as u32,
y as u32
);
if !self.selection.is_empty() {
*wakeup_request = true; *wakeup_request = true;
} }
} }
}, },
glutin::Event::Focused(true) => { glutin::Event::Focused(true) => {
let mut terminal = self.terminal.lock(); *wakeup_request = true;
terminal.dirty = true;
}, },
glutin::Event::MouseWheel(scroll_delta, touch_phase) => { glutin::Event::MouseWheel(scroll_delta, touch_phase) => {
let mut terminal = self.terminal.lock(); processor.on_mouse_wheel(scroll_delta, touch_phase);
let processor = &mut self.input_processor;
let mut context = ActionContext {
terminal: &terminal,
notifier: &mut self.notifier,
selection: &mut self.selection,
};
processor.on_mouse_wheel(
&mut context,
scroll_delta,
touch_phase,
);
}, },
glutin::Event::Awakened => { glutin::Event::Awakened => {
*wakeup_request = true; *wakeup_request = true;
@ -167,19 +179,59 @@ impl<N: input::Notify> Processor<N> {
/// Process at least one event and handle any additional queued events. /// Process at least one event and handle any additional queued events.
pub fn process_events(&mut self, window: &Window) -> bool { pub fn process_events(&mut self, window: &Window) -> bool {
let mut wakeup_request = false; let mut wakeup_request = false;
for event in window.wait_events() {
self.handle_event(event, &mut wakeup_request); // These are lazily initialized the first time an event is returned
break; // from the blocking WaitEventsIterator. Otherwise, the pty reader
// would be blocked the entire time we wait for input!
let terminal;
let context;
let mut processor: input::Processor<N>;
// Convenience macro which curries most arguments to handle_event.
macro_rules! process {
($event:expr) => {
Processor::handle_event(
&mut processor,
$event,
&mut wakeup_request,
self.ref_test,
&self.resize_tx,
)
}
}
match window.wait_events().next() {
Some(event) => {
terminal = self.terminal.lock();
context = ActionContext {
terminal: &terminal,
notifier: &mut self.notifier,
selection: &mut self.selection,
mouse: &mut self.mouse,
size_info: &self.size_info,
};
processor = input::Processor {
ctx: context,
key_bindings: &self.key_bindings[..],
mouse_bindings: &self.mouse_bindings[..]
};
process!(event);
},
// Glutin guarantees the WaitEventsIterator never returns None.
None => unreachable!(),
} }
for event in window.poll_events() { for event in window.poll_events() {
self.handle_event(event, &mut wakeup_request); process!(event);
} }
wakeup_request wakeup_request
} }
pub fn update_config(&mut self, config: &Config) { pub fn update_config(&mut self, config: &Config) {
self.input_processor.update_config(config); self.key_bindings = config.key_bindings().to_vec();
self.mouse_bindings = config.mouse_bindings().to_vec();
} }
} }

View File

@ -11,6 +11,7 @@ use mio::unix::EventedFd;
use ansi; use ansi;
use display; use display;
use event;
use term::Term; use term::Term;
use util::thread; use util::thread;
use sync::FairMutex; use sync::FairMutex;
@ -52,6 +53,21 @@ pub struct State {
parser: ansi::Processor, parser: ansi::Processor,
} }
pub struct Notifier(pub ::mio::channel::Sender<Msg>);
impl event::Notify for Notifier {
fn notify<B>(&mut self, bytes: B)
where B: Into<Cow<'static, [u8]>>
{
let bytes = bytes.into();
match self.0.send(Msg::Input(bytes)) {
Ok(_) => (),
Err(_) => panic!("expected send event loop msg"),
}
}
}
impl Default for State { impl Default for State {
fn default() -> State { fn default() -> State {
State { State {

View File

@ -23,19 +23,17 @@
//! APIs //! APIs
//! //!
//! TODO handling xmodmap would be good //! TODO handling xmodmap would be good
use std::borrow::Cow;
use copypasta::{Clipboard, Load, Store}; use copypasta::{Clipboard, Load, Store};
use glutin::{ElementState, VirtualKeyCode, MouseButton}; use glutin::{ElementState, VirtualKeyCode, MouseButton};
use glutin::{Mods, mods}; use glutin::{Mods, mods};
use glutin::{TouchPhase, MouseScrollDelta}; use glutin::{TouchPhase, MouseScrollDelta};
use config::Config; use event::Notify;
use event_loop;
use index::{Line, Column, Side, Location}; use index::{Line, Column, Side, Location};
use selection::Selection; use selection::Selection;
use term::mode::{self, TermMode}; use term::mode::{self, TermMode};
use term::{self, Term}; use term::{self, Term};
use event::Mouse;
/// Processes input from glutin. /// Processes input from glutin.
/// ///
@ -43,58 +41,18 @@ use term::{self, Term};
/// are activated. /// are activated.
/// ///
/// TODO also need terminal state when processing input /// TODO also need terminal state when processing input
pub struct Processor { pub struct Processor<'a, N: 'a> {
key_bindings: Vec<KeyBinding>, pub key_bindings: &'a [KeyBinding],
mouse_bindings: Vec<MouseBinding>, pub mouse_bindings: &'a [MouseBinding],
mouse: Mouse, pub ctx: ActionContext<'a, N>,
size_info: term::SizeInfo,
} }
/// State of the mouse pub struct ActionContext<'a, N: 'a> {
pub struct Mouse { pub notifier: &'a mut N,
x: u32, pub terminal: &'a Term,
y: u32, pub selection: &'a mut Selection,
left_button_state: ElementState, pub mouse: &'a mut Mouse,
scroll_px: i32, pub size_info: &'a term::SizeInfo,
line: Line,
column: Column,
cell_side: Side
}
impl Default for Mouse {
fn default() -> Mouse {
Mouse {
x: 0,
y: 0,
left_button_state: ElementState::Released,
scroll_px: 0,
line: Line(0),
column: Column(0),
cell_side: Side::Left,
}
}
}
/// Types that are notified of escape sequences from the `input::Processor`.
pub trait Notify {
/// Notify that an escape sequence should be written to the pty
///
/// TODO this needs to be able to error somehow
fn notify<B: Into<Cow<'static, [u8]>>>(&mut self, B);
}
pub struct LoopNotifier(pub ::mio::channel::Sender<event_loop::Msg>);
impl Notify for LoopNotifier {
fn notify<B>(&mut self, bytes: B)
where B: Into<Cow<'static, [u8]>>
{
let bytes = bytes.into();
match self.0.send(event_loop::Msg::Input(bytes)) {
Ok(_) => (),
Err(_) => panic!("expected send event loop msg"),
}
}
} }
/// Describes a state and action to take in that state /// Describes a state and action to take in that state
@ -142,8 +100,8 @@ impl KeyBinding {
} }
#[inline] #[inline]
fn execute<'a, N: Notify>(&self, context: &mut ActionContext<'a, N>) { fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) {
self.binding.action.execute(context) self.binding.action.execute(ctx)
} }
} }
@ -162,8 +120,8 @@ impl MouseBinding {
} }
#[inline] #[inline]
fn execute<'a, N: Notify>(&self, context: &mut ActionContext<'a, N>) { fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) {
self.binding.action.execute(context) self.binding.action.execute(ctx)
} }
} }
@ -186,12 +144,15 @@ impl Action {
#[inline] #[inline]
fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) { fn execute<'a, N: Notify>(&self, ctx: &mut ActionContext<'a, N>) {
match *self { match *self {
Action::Esc(ref s) => ctx.notifier.notify(s.clone().into_bytes()), Action::Esc(ref s) => {
ctx.notifier.notify(s.clone().into_bytes())
},
Action::Copy => { Action::Copy => {
// so... need access to terminal state. and the selection. // so... need access to terminal state. and the selection.
unimplemented!(); unimplemented!();
}, },
Action::Paste | Action::PasteSelection => { Action::Paste |
Action::PasteSelection => {
let clip = Clipboard::new().expect("get clipboard"); let clip = Clipboard::new().expect("get clipboard");
clip.load_selection() clip.load_selection()
.map(|contents| { .map(|contents| {
@ -247,64 +208,41 @@ impl Binding {
} }
} }
pub struct ActionContext<'a, N: 'a> { impl<'a, N: Notify + 'a> Processor<'a, N> {
pub notifier: &'a mut N,
pub terminal: &'a Term,
pub selection: &'a mut Selection
}
impl Processor {
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 {
Processor {
key_bindings: config.key_bindings().to_vec(),
mouse_bindings: config.mouse_bindings().to_vec(),
mouse: Mouse::default(),
size_info: size_info.to_owned(),
}
}
#[inline] #[inline]
pub fn mouse_moved(&mut self, selection: &mut Selection, mode: TermMode, x: u32, y: u32) { pub fn mouse_moved(&mut self, x: u32, y: u32) {
// Record mouse position within window. Pixel coordinates are *not* // Record mouse position within window. Pixel coordinates are *not*
// translated to grid coordinates here since grid coordinates are rarely // translated to grid coordinates here since grid coordinates are rarely
// needed and the mouse position updates frequently. // needed and the mouse position updates frequently.
self.mouse.x = x; self.ctx.mouse.x = x;
self.mouse.y = y; self.ctx.mouse.y = y;
if let Some((line, column)) = self.size_info.pixels_to_coords(x as usize, y as usize) { if let Some((line, column)) = self.ctx.size_info.pixels_to_coords(x as usize, y as usize) {
self.mouse.line = line; self.ctx.mouse.line = line;
self.mouse.column = column; self.ctx.mouse.column = column;
let cell_x = x as usize % self.size_info.cell_width as usize; let cell_x = x as usize % self.ctx.size_info.cell_width as usize;
let half_cell_width = (self.size_info.cell_width / 2.0) as usize; let half_cell_width = (self.ctx.size_info.cell_width / 2.0) as usize;
self.mouse.cell_side = if cell_x > half_cell_width { self.ctx.mouse.cell_side = if cell_x > half_cell_width {
Side::Right Side::Right
} else { } else {
Side::Left Side::Left
}; };
if self.mouse.left_button_state == ElementState::Pressed && if self.ctx.mouse.left_button_state == ElementState::Pressed &&
!mode.contains(mode::MOUSE_REPORT_CLICK) !self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK)
{ {
selection.update(Location { self.ctx.selection.update(Location {
line: line, line: line,
col: column col: column
}, self.mouse.cell_side); }, self.ctx.mouse.cell_side);
} }
} }
} }
pub fn mouse_report<'a, N: Notify>( pub fn mouse_report(&mut self, button: u8) {
&mut self, let (line, column) = (self.ctx.mouse.line, self.ctx.mouse.column);
button: u8,
context: &mut ActionContext<'a, N>
) {
let (line, column) = (self.mouse.line, self.mouse.column);
if line < Line(223) && column < Column(223) { if line < Line(223) && column < Column(223) {
let msg = vec![ let msg = vec![
@ -316,35 +254,27 @@ impl Processor {
32 + 1 + line.0 as u8, 32 + 1 + line.0 as u8,
]; ];
context.notifier.notify(msg); self.ctx.notifier.notify(msg);
} }
} }
pub fn on_mouse_press<'a, N: Notify>( pub fn on_mouse_press(&mut self) {
&mut self, if self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
context: &mut ActionContext<'a, N> self.mouse_report(0);
) {
if context.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
self.mouse_report(0, context);
return; return;
} }
context.selection.clear(); self.ctx.selection.clear();
} }
pub fn on_mouse_release<'a, N: Notify>(&mut self, context: &mut ActionContext<'a, N>) { pub fn on_mouse_release(&mut self) {
if context.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) { if self.ctx.terminal.mode().contains(mode::MOUSE_REPORT_CLICK) {
self.mouse_report(3, context); self.mouse_report(3);
return; return;
} }
} }
pub fn on_mouse_wheel<'a, N: Notify>( pub fn on_mouse_wheel(&mut self, delta: MouseScrollDelta, phase: TouchPhase) {
&mut self,
context: &mut ActionContext<'a, N>,
delta: MouseScrollDelta,
phase: TouchPhase,
) {
match delta { match delta {
MouseScrollDelta::LineDelta(_columns, lines) => { MouseScrollDelta::LineDelta(_columns, lines) => {
let code = if lines > 0.0 { let code = if lines > 0.0 {
@ -354,29 +284,29 @@ impl Processor {
}; };
for _ in 0..(lines.abs() as usize) { for _ in 0..(lines.abs() as usize) {
self.mouse_report(code, context); self.mouse_report(code);
} }
}, },
MouseScrollDelta::PixelDelta(_x, y) => { MouseScrollDelta::PixelDelta(_x, y) => {
match phase { match phase {
TouchPhase::Started => { TouchPhase::Started => {
// Reset offset to zero // Reset offset to zero
self.mouse.scroll_px = 0; self.ctx.mouse.scroll_px = 0;
}, },
TouchPhase::Moved => { TouchPhase::Moved => {
self.mouse.scroll_px += y as i32; self.ctx.mouse.scroll_px += y as i32;
let height = self.size_info.cell_height as i32; let height = self.ctx.size_info.cell_height as i32;
while self.mouse.scroll_px.abs() >= height { while self.ctx.mouse.scroll_px.abs() >= height {
let button = if self.mouse.scroll_px > 0 { let button = if self.ctx.mouse.scroll_px > 0 {
self.mouse.scroll_px -= height; self.ctx.mouse.scroll_px -= height;
64 64
} else { } else {
self.mouse.scroll_px += height; self.ctx.mouse.scroll_px += height;
65 65
}; };
self.mouse_report(button, context); self.mouse_report(button);
} }
}, },
_ => (), _ => (),
@ -385,22 +315,17 @@ impl Processor {
} }
} }
pub fn mouse_input<'a, N: Notify>( pub fn mouse_input(&mut self, state: ElementState, button: MouseButton) {
&mut self,
context: &mut ActionContext<'a, N>,
state: ElementState,
button: MouseButton,
) {
if let MouseButton::Left = button { if let MouseButton::Left = button {
// TODO handle state changes // TODO handle state changes
if self.mouse.left_button_state != state { if self.ctx.mouse.left_button_state != state {
self.mouse.left_button_state = state; self.ctx.mouse.left_button_state = state;
match state { match state {
ElementState::Pressed => { ElementState::Pressed => {
self.on_mouse_press(context); self.on_mouse_press();
}, },
ElementState::Released => { ElementState::Released => {
self.on_mouse_release(context); self.on_mouse_release();
} }
} }
} }
@ -410,17 +335,11 @@ impl Processor {
return; return;
} }
Processor::process_mouse_bindings( self.process_mouse_bindings(mods::NONE, button);
context,
&self.mouse_bindings[..],
mods::NONE,
button
);
} }
pub fn process_key<'a, N: Notify>( pub fn process_key(
&mut self, &mut self,
context: &mut ActionContext<'a, N>,
state: ElementState, state: ElementState,
key: Option<VirtualKeyCode>, key: Option<VirtualKeyCode>,
mods: Mods, mods: Mods,
@ -432,13 +351,13 @@ impl Processor {
return; return;
} }
if Processor::process_key_bindings(context, &self.key_bindings[..], mods, key) { if self.process_key_bindings(mods, key) {
return; return;
} }
// Didn't process a binding; print the provided character // Didn't process a binding; print the provided character
if let Some(string) = string { if let Some(string) = string {
context.notifier.notify(string.into_bytes()); self.ctx.notifier.notify(string.into_bytes());
} }
} }
} }
@ -449,16 +368,11 @@ impl Processor {
/// for its action to be executed. /// for its action to be executed.
/// ///
/// Returns true if an action is executed. /// Returns true if an action is executed.
fn process_key_bindings<'a, N: Notify>( fn process_key_bindings(&mut self, mods: Mods, key: VirtualKeyCode) -> bool {
context: &mut ActionContext<'a, N>, for binding in self.key_bindings {
bindings: &[KeyBinding], if binding.is_triggered_by(self.ctx.terminal.mode(), &mods, &key) {
mods: Mods,
key: VirtualKeyCode
) -> bool {
for binding in bindings {
if binding.is_triggered_by(context.terminal.mode(), &mods, &key) {
// binding was triggered; run the action // binding was triggered; run the action
binding.execute(context); binding.execute(&mut self.ctx);
return true; return true;
} }
} }
@ -472,27 +386,17 @@ impl Processor {
/// for its action to be executed. /// for its action to be executed.
/// ///
/// Returns true if an action is executed. /// Returns true if an action is executed.
fn process_mouse_bindings<'a, N: Notify>( fn process_mouse_bindings(&mut self, mods: Mods, button: MouseButton) -> bool {
context: &mut ActionContext<'a, N>, for binding in self.mouse_bindings {
bindings: &[MouseBinding], if binding.is_triggered_by(self.ctx.terminal.mode(), &mods, &button) {
mods: Mods,
button: MouseButton
) -> bool {
for binding in bindings {
if binding.is_triggered_by(context.terminal.mode(), &mods, &button) {
// binding was triggered; run the action // binding was triggered; run the action
binding.execute(context); binding.execute(&mut self.ctx);
return true; return true;
} }
} }
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();
}
} }
#[cfg(test)] #[cfg(test)]

View File

@ -26,8 +26,7 @@ use alacritty::cli;
use alacritty::config::{self, Config}; use alacritty::config::{self, Config};
use alacritty::display::Display; use alacritty::display::Display;
use alacritty::event; use alacritty::event;
use alacritty::event_loop::EventLoop; use alacritty::event_loop::{self, EventLoop};
use alacritty::input;
use alacritty::sync::FairMutex; use alacritty::sync::FairMutex;
use alacritty::term::{Term}; use alacritty::term::{Term};
use alacritty::tty::{self, process_should_exit}; use alacritty::tty::{self, process_should_exit};
@ -108,7 +107,7 @@ fn run(mut config: Config, options: cli::Options) -> Result<(), Box<Error>> {
// //
// Need the Rc<RefCell<_>> here since a ref is shared in the resize callback // Need the Rc<RefCell<_>> here since a ref is shared in the resize callback
let mut processor = event::Processor::new( let mut processor = event::Processor::new(
input::LoopNotifier(loop_tx), event_loop::Notifier(loop_tx),
terminal.clone(), terminal.clone(),
display.resize_channel(), display.resize_channel(),
&config, &config,