summaryrefslogtreecommitdiffstats
path: root/src/command/client/search/interactive.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/command/client/search/interactive.rs')
-rw-r--r--src/command/client/search/interactive.rs191
1 files changed, 128 insertions, 63 deletions
diff --git a/src/command/client/search/interactive.rs b/src/command/client/search/interactive.rs
index e0ceb091..c8ceab58 100644
--- a/src/command/client/search/interactive.rs
+++ b/src/command/client/search/interactive.rs
@@ -1,19 +1,22 @@
-use std::io::stdout;
-
-use eyre::Result;
-use semver::Version;
-use termion::{
- event::Event as TermEvent, event::Key, event::MouseButton, event::MouseEvent,
- input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen,
+use std::{
+ io::{stdout, Write},
+ time::Duration,
};
-use tui::{
- backend::{Backend, TermionBackend},
+
+use crate::tui::{
+ backend::{Backend, CrosstermBackend},
layout::{Alignment, Constraint, Direction, Layout},
style::{Color, Modifier, Style},
text::{Span, Spans, Text},
widgets::{Block, BorderType, Borders, Paragraph},
Frame, Terminal,
};
+use crossterm::{
+ event::{self, Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent},
+ execute, terminal,
+};
+use eyre::Result;
+use semver::Version;
use unicode_width::UnicodeWidthStr;
use atuin_client::{
@@ -26,7 +29,6 @@ use atuin_client::{
use super::{
cursor::Cursor,
- event::{Event, Events},
history_list::{HistoryList, ListState, PREFIX_LENGTH},
};
use crate::VERSION;
@@ -62,42 +64,69 @@ impl State {
Ok(results)
}
- fn handle_input(
+ fn handle_input(&mut self, settings: &Settings, input: &Event, len: usize) -> Option<usize> {
+ match input {
+ Event::Key(k) => self.handle_key_input(settings, k, len),
+ Event::Mouse(m) => self.handle_mouse_input(*m, len),
+ _ => None,
+ }
+ }
+
+ fn handle_mouse_input(&mut self, input: MouseEvent, len: usize) -> Option<usize> {
+ match input.kind {
+ event::MouseEventKind::ScrollDown => {
+ let i = self.results_state.selected().saturating_sub(1);
+ self.results_state.select(i);
+ }
+ event::MouseEventKind::ScrollUp => {
+ let i = self.results_state.selected() + 1;
+ self.results_state.select(i.min(len - 1));
+ }
+ _ => {}
+ }
+ None
+ }
+
+ fn handle_key_input(
&mut self,
settings: &Settings,
- input: &TermEvent,
+ input: &KeyEvent,
len: usize,
) -> Option<usize> {
- match input {
- TermEvent::Key(Key::Char('\t')) => {}
- TermEvent::Key(Key::Ctrl('c' | 'd' | 'g')) => return Some(RETURN_ORIGINAL),
- TermEvent::Key(Key::Esc) => {
+ let ctrl = input.modifiers.contains(KeyModifiers::CONTROL);
+ let alt = input.modifiers.contains(KeyModifiers::ALT);
+ match input.code {
+ KeyCode::Char('c' | 'd' | 'g') if ctrl => return Some(RETURN_ORIGINAL),
+ KeyCode::Esc => {
return Some(match settings.exit_mode {
ExitMode::ReturnOriginal => RETURN_ORIGINAL,
ExitMode::ReturnQuery => RETURN_QUERY,
})
}
- TermEvent::Key(Key::Char('\n')) => {
+ KeyCode::Enter => {
return Some(self.results_state.selected());
}
- TermEvent::Key(Key::Alt(c @ '1'..='9')) => {
+ KeyCode::Char(c @ '1'..='9') if alt => {
let c = c.to_digit(10)? as usize;
return Some(self.results_state.selected() + c);
}
- TermEvent::Key(Key::Left | Key::Ctrl('h')) => {
+ KeyCode::Left => {
self.input.left();
}
- TermEvent::Key(Key::Right | Key::Ctrl('l')) => self.input.right(),
- TermEvent::Key(Key::Ctrl('a') | Key::Home) => self.input.start(),
- TermEvent::Key(Key::Ctrl('e') | Key::End) => self.input.end(),
- TermEvent::Key(Key::Char(c)) => self.input.insert(*c),
- TermEvent::Key(Key::Backspace) => {
+ KeyCode::Char('h') if ctrl => {
+ self.input.left();
+ }
+ KeyCode::Right => self.input.right(),
+ KeyCode::Char('l') if ctrl => self.input.right(),
+ KeyCode::Char('a') if ctrl => self.input.start(),
+ KeyCode::Char('e') if ctrl => self.input.end(),
+ KeyCode::Backspace => {
self.input.back();
}
- TermEvent::Key(Key::Delete) => {
+ KeyCode::Delete => {
self.input.remove();
}
- TermEvent::Key(Key::Ctrl('w')) => {
+ KeyCode::Char('w') if ctrl => {
// remove the first batch of whitespace
while matches!(self.input.back(), Some(c) if c.is_whitespace()) {}
while self.input.left() {
@@ -108,8 +137,8 @@ impl State {
self.input.remove();
}
}
- TermEvent::Key(Key::Ctrl('u')) => self.input.clear(),
- TermEvent::Key(Key::Ctrl('r')) => {
+ KeyCode::Char('u') if ctrl => self.input.clear(),
+ KeyCode::Char('r') if ctrl => {
pub static FILTER_MODES: [FilterMode; 4] = [
FilterMode::Global,
FilterMode::Host,
@@ -120,19 +149,24 @@ impl State {
let i = (i + 1) % FILTER_MODES.len();
self.filter_mode = FILTER_MODES[i];
}
- TermEvent::Key(Key::Down | Key::Ctrl('n' | 'j'))
- | TermEvent::Mouse(MouseEvent::Press(MouseButton::WheelDown, _, _)) => {
- if self.results_state.selected() == 0 && input.eq(&TermEvent::Key(Key::Down)) {
- return Some(RETURN_ORIGINAL);
- }
+ KeyCode::Down if self.results_state.selected() == 0 => return Some(RETURN_ORIGINAL),
+ KeyCode::Down => {
+ let i = self.results_state.selected().saturating_sub(1);
+ self.results_state.select(i);
+ }
+ KeyCode::Char('n' | 'j') if ctrl => {
let i = self.results_state.selected().saturating_sub(1);
self.results_state.select(i);
}
- TermEvent::Key(Key::Up | Key::Ctrl('p' | 'k'))
- | TermEvent::Mouse(MouseEvent::Press(MouseButton::WheelUp, _, _)) => {
+ KeyCode::Up => {
+ let i = self.results_state.selected() + 1;
+ self.results_state.select(i.min(len - 1));
+ }
+ KeyCode::Char('p' | 'k') if ctrl => {
let i = self.results_state.selected() + 1;
self.results_state.select(i.min(len - 1));
}
+ KeyCode::Char(c) => self.input.insert(c),
_ => {}
};
@@ -303,6 +337,45 @@ impl State {
}
}
+struct Stdout {
+ stdout: std::io::Stdout,
+}
+
+impl Stdout {
+ pub fn new() -> std::io::Result<Self> {
+ terminal::enable_raw_mode()?;
+ let mut stdout = stdout();
+ execute!(
+ stdout,
+ terminal::EnterAlternateScreen,
+ event::EnableMouseCapture
+ )?;
+ Ok(Self { stdout })
+ }
+}
+
+impl Drop for Stdout {
+ fn drop(&mut self) {
+ execute!(
+ self.stdout,
+ terminal::LeaveAlternateScreen,
+ event::DisableMouseCapture
+ )
+ .unwrap();
+ terminal::disable_raw_mode().unwrap();
+ }
+}
+
+impl Write for Stdout {
+ fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+ self.stdout.write(buf)
+ }
+
+ fn flush(&mut self) -> std::io::Result<()> {
+ self.stdout.flush()
+ }
+}
+
// this is a big blob of horrible! clean it up!
// for now, it works. But it'd be great if it were more easily readable, and
// modular. I'd like to add some more stats and stuff at some point
@@ -312,15 +385,10 @@ pub async fn history(
settings: &Settings,
db: &mut impl Database,
) -> Result<String> {
- let stdout = stdout().into_raw_mode()?;
- let stdout = MouseTerminal::from(stdout);
- let stdout = AlternateScreen::from(stdout);
- let backend = TermionBackend::new(stdout);
+ let stdout = Stdout::new()?;
+ let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
- // Setup event handlers
- let events = Events::new();
-
let mut input = Cursor::from(query.join(" "));
// Put the cursor at the end of the query by default
input.end();
@@ -343,27 +411,6 @@ pub async fn history(
let mut results = app.query_results(settings.search_mode, db).await?;
let index = 'render: loop {
- let initial_input = app.input.as_str().to_owned();
- let initial_filter_mode = app.filter_mode;
-
- // Handle input
- if let Event::Input(input) = events.next()? {
- if let Some(i) = app.handle_input(settings, &input, results.len()) {
- break 'render i;
- }
- }
-
- // After we receive input process the whole event channel before query/render.
- while let Ok(Event::Input(input)) = events.try_next() {
- if let Some(i) = app.handle_input(settings, &input, results.len()) {
- break 'render i;
- }
- }
-
- if initial_input != app.input.as_str() || initial_filter_mode != app.filter_mode {
- results = app.query_results(settings.search_mode, db).await?;
- }
-
let compact = match settings.style {
atuin_client::settings::Style::Auto => {
terminal.size().map(|size| size.height < 14).unwrap_or(true)
@@ -376,6 +423,24 @@ pub async fn history(
} else {
terminal.draw(|f| app.draw(f, &results))?;
}
+
+ let initial_input = app.input.as_str().to_owned();
+ let initial_filter_mode = app.filter_mode;
+
+ if event::poll(Duration::from_millis(250))? {
+ loop {
+ if let Some(i) = app.handle_input(settings, &event::read()?, results.len()) {
+ break 'render i;
+ }
+ if !event::poll(Duration::ZERO)? {
+ break;
+ }
+ }
+ }
+
+ if initial_input != app.input.as_str() || initial_filter_mode != app.filter_mode {
+ results = app.query_results(settings.search_mode, db).await?;
+ }
};
if index < results.len() {