[feature] Add scroll wheel support to interactive history search (#435)

This commit is contained in:
Frank Hamand 2022-06-04 10:16:12 +01:00 committed by GitHub
parent dcdde22511
commit 3f5350dee6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 36 additions and 25 deletions

View file

@ -1,7 +1,7 @@
use std::{thread, time::Duration}; use std::{thread, time::Duration};
use crossbeam_channel::unbounded; use crossbeam_channel::{bounded, TrySendError};
use termion::{event::Key, input::TermRead}; use termion::{event::Event as TermEvent, event::Key, input::TermRead};
pub enum Event<I> { pub enum Event<I> {
Input(I), Input(I),
@ -11,7 +11,7 @@ pub enum Event<I> {
/// A small event handler that wrap termion input and tick events. Each 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` /// type is handled in its own thread and returned to a common `Receiver`
pub struct Events { pub struct Events {
rx: crossbeam_channel::Receiver<Event<Key>>, rx: crossbeam_channel::Receiver<Event<TermEvent>>,
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -35,16 +35,22 @@ impl Events {
} }
pub fn with_config(config: Config) -> 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(); let tx = tx.clone();
thread::spawn(move || { thread::spawn(move || {
let tty = termion::get_tty().expect("Could not find tty"); let tty = termion::get_tty().expect("Could not find tty");
for key in tty.keys().flatten() { for event in tty.events().flatten() {
if let Err(err) = tx.send(Event::Input(key)) { if let Err(err) = tx.try_send(Event::Input(event)) {
eprintln!("{}", err); if let TrySendError::Full(_) = err {
return; // 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 } Events { rx }
} }
pub fn next(&self) -> Result<Event<Key>, crossbeam_channel::RecvError> { pub fn next(&self) -> Result<Event<TermEvent>, crossbeam_channel::RecvError> {
self.rx.recv() self.rx.recv()
} }
} }

View file

@ -3,7 +3,10 @@ use std::{env, io::stdout, ops::Sub, time::Duration};
use chrono::Utc; use chrono::Utc;
use clap::Parser; use clap::Parser;
use eyre::Result; 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::{ use tui::{
backend::{Backend, TermionBackend}, backend::{Backend, TermionBackend},
layout::{Alignment, Constraint, Corner, Direction, Layout}, 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)] #[allow(clippy::too_many_lines)]
async fn key_handler( async fn key_handler(
input: Key, input: TermEvent,
search_mode: SearchMode, search_mode: SearchMode,
db: &mut impl Database, db: &mut impl Database,
app: &mut State, app: &mut State,
) -> Option<String> { ) -> Option<String> {
match input { match input {
Key::Esc | Key::Ctrl('c' | 'd' | 'g') => return Some(String::from("")), TermEvent::Key(Key::Esc | Key::Ctrl('c' | 'd' | 'g')) => return Some(String::from("")),
Key::Char('\n') => { TermEvent::Key(Key::Char('\n')) => {
let i = app.results_state.selected().unwrap_or(0); let i = app.results_state.selected().unwrap_or(0);
return Some( return Some(
@ -321,7 +324,7 @@ async fn key_handler(
.map_or(app.input.clone(), |h| h.command.clone()), .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 c = c.to_digit(10)? as usize;
let i = app.results_state.selected()? + c; let i = app.results_state.selected()? + c;
@ -331,28 +334,28 @@ async fn key_handler(
.map_or(app.input.clone(), |h| h.command.clone()), .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 { if app.cursor_index != 0 {
app.cursor_index -= 1; app.cursor_index -= 1;
} }
} }
Key::Right | Key::Ctrl('l') => { TermEvent::Key(Key::Right | Key::Ctrl('l')) => {
if app.cursor_index < app.input.width() { if app.cursor_index < app.input.width() {
app.cursor_index += 1; app.cursor_index += 1;
} }
} }
Key::Ctrl('a') => { TermEvent::Key(Key::Ctrl('a')) => {
app.cursor_index = 0; app.cursor_index = 0;
} }
Key::Ctrl('e') => { TermEvent::Key(Key::Ctrl('e')) => {
app.cursor_index = app.input.chars().count(); app.cursor_index = app.input.chars().count();
} }
Key::Char(c) => { TermEvent::Key(Key::Char(c)) => {
insert_char_into_input(app, app.cursor_index, c); insert_char_into_input(app, app.cursor_index, c);
app.cursor_index += 1; app.cursor_index += 1;
query_results(app, search_mode, db).await.unwrap(); query_results(app, search_mode, db).await.unwrap();
} }
Key::Backspace => { TermEvent::Key(Key::Backspace) => {
if app.cursor_index == 0 { if app.cursor_index == 0 {
return None; return None;
} }
@ -360,7 +363,7 @@ async fn key_handler(
app.cursor_index -= 1; app.cursor_index -= 1;
query_results(app, search_mode, db).await.unwrap(); query_results(app, search_mode, db).await.unwrap();
} }
Key::Ctrl('w') => { TermEvent::Key(Key::Ctrl('w')) => {
let mut stop_on_next_whitespace = false; let mut stop_on_next_whitespace = false;
loop { loop {
if app.cursor_index == 0 { if app.cursor_index == 0 {
@ -378,12 +381,12 @@ async fn key_handler(
} }
query_results(app, search_mode, db).await.unwrap(); query_results(app, search_mode, db).await.unwrap();
} }
Key::Ctrl('u') => { TermEvent::Key(Key::Ctrl('u')) => {
app.input = String::from(""); app.input = String::from("");
app.cursor_index = 0; app.cursor_index = 0;
query_results(app, search_mode, db).await.unwrap(); query_results(app, search_mode, db).await.unwrap();
} }
Key::Ctrl('r') => { TermEvent::Key(Key::Ctrl('r')) => {
app.filter_mode = match app.filter_mode { app.filter_mode = match app.filter_mode {
FilterMode::Global => FilterMode::Host, FilterMode::Global => FilterMode::Host,
FilterMode::Host => FilterMode::Session, FilterMode::Host => FilterMode::Session,
@ -393,7 +396,8 @@ async fn key_handler(
query_results(app, search_mode, db).await.unwrap(); 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() { let i = match app.results_state.selected() {
Some(i) => { Some(i) => {
if i == 0 { if i == 0 {
@ -406,7 +410,8 @@ async fn key_handler(
}; };
app.results_state.select(Some(i)); 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() { let i = match app.results_state.selected() {
Some(i) => { Some(i) => {
if i >= app.results.len() - 1 { if i >= app.results.len() - 1 {