diff --git a/src/command/client/event.rs b/src/command/client/event.rs index 189d9e7..f818d72 100644 --- a/src/command/client/event.rs +++ b/src/command/client/event.rs @@ -1,7 +1,7 @@ use std::{thread, time::Duration}; -use crossbeam_channel::unbounded; -use termion::{event::Key, input::TermRead}; +use crossbeam_channel::{bounded, TrySendError}; +use termion::{event::Event as TermEvent, event::Key, input::TermRead}; pub enum Event { Input(I), @@ -11,7 +11,7 @@ pub enum Event { /// A small event handler that wrap termion input and tick events. Each event /// type is handled in its own thread and returned to a common `Receiver` pub struct Events { - rx: crossbeam_channel::Receiver>, + rx: crossbeam_channel::Receiver>, } #[derive(Debug, Clone, Copy)] @@ -35,16 +35,22 @@ impl Events { } pub fn with_config(config: Config) -> Events { - let (tx, rx) = unbounded(); + // Keep channel small so scroll events don't stack for ages. + let (tx, rx) = bounded(1); { let tx = tx.clone(); thread::spawn(move || { let tty = termion::get_tty().expect("Could not find tty"); - for key in tty.keys().flatten() { - if let Err(err) = tx.send(Event::Input(key)) { - eprintln!("{}", err); - return; + for event in tty.events().flatten() { + if let Err(err) = tx.try_send(Event::Input(event)) { + if let TrySendError::Full(_) = err { + // Silently ignore send fails when buffer is full. + // This will most likely be scroll wheel spam and we can drop some events. + } else { + eprintln!("{}", err); + return; + } } } }) @@ -60,7 +66,7 @@ impl Events { Events { rx } } - pub fn next(&self) -> Result, crossbeam_channel::RecvError> { + pub fn next(&self) -> Result, crossbeam_channel::RecvError> { self.rx.recv() } } diff --git a/src/command/client/search.rs b/src/command/client/search.rs index e1ee55d..4e0de68 100644 --- a/src/command/client/search.rs +++ b/src/command/client/search.rs @@ -3,7 +3,10 @@ use std::{env, io::stdout, ops::Sub, time::Duration}; use chrono::Utc; use clap::Parser; use eyre::Result; -use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; +use termion::{ + event::Event as TermEvent, event::Key, event::MouseButton, event::MouseEvent, + input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen, +}; use tui::{ backend::{Backend, TermionBackend}, layout::{Alignment, Constraint, Corner, Direction, Layout}, @@ -305,14 +308,14 @@ fn remove_char_from_input(app: &mut State, i: usize) -> char { #[allow(clippy::too_many_lines)] async fn key_handler( - input: Key, + input: TermEvent, search_mode: SearchMode, db: &mut impl Database, app: &mut State, ) -> Option { match input { - Key::Esc | Key::Ctrl('c' | 'd' | 'g') => return Some(String::from("")), - Key::Char('\n') => { + TermEvent::Key(Key::Esc | Key::Ctrl('c' | 'd' | 'g')) => return Some(String::from("")), + TermEvent::Key(Key::Char('\n')) => { let i = app.results_state.selected().unwrap_or(0); return Some( @@ -321,7 +324,7 @@ async fn key_handler( .map_or(app.input.clone(), |h| h.command.clone()), ); } - Key::Alt(c) if ('1'..='9').contains(&c) => { + TermEvent::Key(Key::Alt(c)) if ('1'..='9').contains(&c) => { let c = c.to_digit(10)? as usize; let i = app.results_state.selected()? + c; @@ -331,28 +334,28 @@ async fn key_handler( .map_or(app.input.clone(), |h| h.command.clone()), ); } - Key::Left | Key::Ctrl('h') => { + TermEvent::Key(Key::Left | Key::Ctrl('h')) => { if app.cursor_index != 0 { app.cursor_index -= 1; } } - Key::Right | Key::Ctrl('l') => { + TermEvent::Key(Key::Right | Key::Ctrl('l')) => { if app.cursor_index < app.input.width() { app.cursor_index += 1; } } - Key::Ctrl('a') => { + TermEvent::Key(Key::Ctrl('a')) => { app.cursor_index = 0; } - Key::Ctrl('e') => { + TermEvent::Key(Key::Ctrl('e')) => { app.cursor_index = app.input.chars().count(); } - Key::Char(c) => { + TermEvent::Key(Key::Char(c)) => { insert_char_into_input(app, app.cursor_index, c); app.cursor_index += 1; query_results(app, search_mode, db).await.unwrap(); } - Key::Backspace => { + TermEvent::Key(Key::Backspace) => { if app.cursor_index == 0 { return None; } @@ -360,7 +363,7 @@ async fn key_handler( app.cursor_index -= 1; query_results(app, search_mode, db).await.unwrap(); } - Key::Ctrl('w') => { + TermEvent::Key(Key::Ctrl('w')) => { let mut stop_on_next_whitespace = false; loop { if app.cursor_index == 0 { @@ -378,12 +381,12 @@ async fn key_handler( } query_results(app, search_mode, db).await.unwrap(); } - Key::Ctrl('u') => { + TermEvent::Key(Key::Ctrl('u')) => { app.input = String::from(""); app.cursor_index = 0; query_results(app, search_mode, db).await.unwrap(); } - Key::Ctrl('r') => { + TermEvent::Key(Key::Ctrl('r')) => { app.filter_mode = match app.filter_mode { FilterMode::Global => FilterMode::Host, FilterMode::Host => FilterMode::Session, @@ -393,7 +396,8 @@ async fn key_handler( query_results(app, search_mode, db).await.unwrap(); } - Key::Down | Key::Ctrl('n' | 'j') => { + TermEvent::Key(Key::Down | Key::Ctrl('n' | 'j')) + | TermEvent::Mouse(MouseEvent::Press(MouseButton::WheelDown, _, _)) => { let i = match app.results_state.selected() { Some(i) => { if i == 0 { @@ -406,7 +410,8 @@ async fn key_handler( }; app.results_state.select(Some(i)); } - Key::Up | Key::Ctrl('p' | 'k') => { + TermEvent::Key(Key::Up | Key::Ctrl('p' | 'k')) + | TermEvent::Mouse(MouseEvent::Press(MouseButton::WheelUp, _, _)) => { let i = match app.results_state.selected() { Some(i) => { if i >= app.results.len() - 1 {