[feature] Add scroll wheel support to interactive history search (#435)
This commit is contained in:
parent
dcdde22511
commit
3f5350dee6
2 changed files with 36 additions and 25 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue