start working on tui

This commit is contained in:
Robin Appelman 2024-07-24 22:34:00 +02:00
commit f9a1aa1415
12 changed files with 709 additions and 58 deletions

50
src/ui/match_list.rs Normal file
View file

@ -0,0 +1,50 @@
use crate::app::{App, LogMatch};
use ratatui::prelude::*;
use ratatui::style::palette::tailwind;
use ratatui::widgets::{Cell, HighlightSpacing, Row, Table};
use std::fmt::Write;
pub fn match_list(app: &App) -> Table {
let header_style = Style::default()
.bg(tailwind::BLACK)
.fg(tailwind::GREEN.c600);
let selected_style = Style::default()
.add_modifier(Modifier::REVERSED)
.bg(tailwind::BLACK)
.fg(tailwind::GREEN.c600);
let header = ["Statement", "File", "Line", "Count"]
.into_iter()
.map(Cell::from)
.collect::<Row>()
.style(header_style)
.height(1);
let widths = [
Constraint::Percentage(60),
Constraint::Percentage(40),
Constraint::Min(10),
Constraint::Min(10),
];
let table = Table::new(
app.matches.iter().map(|result| log_row(result, app)),
widths,
)
.header(header)
.highlight_style(selected_style)
.highlight_spacing(HighlightSpacing::Always);
table
}
fn log_row<'a>(result: &LogMatch, app: &'a App) -> Row<'a> {
let mut message = String::new();
let mut paths = String::new();
let mut lines = String::new();
for index in result.result.iter() {
let statement = app.log_statements.get(index).expect("invalid match index");
writeln!(&mut message, "{}", statement.message()).unwrap();
writeln!(&mut paths, "{}", statement.path()).unwrap();
writeln!(&mut lines, "{}", statement.line).unwrap();
}
Row::new([message, paths, lines, result.count.to_string()]).height(result.result.len() as u16)
}

64
src/ui/mod.rs Normal file
View file

@ -0,0 +1,64 @@
use crate::app::App;
use crate::error::UiError;
use crate::ui::match_list::match_list;
use crate::ui::state::{UiEvent, UiState};
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
use ratatui::crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::crossterm::{event, ExecutableCommand};
use ratatui::prelude::*;
use ratatui::Terminal;
use std::io;
use std::io::stdout;
mod match_list;
mod state;
pub fn run_ui(app: App) -> Result<(), UiError> {
enable_raw_mode()?;
stdout().execute(EnterAlternateScreen)?;
let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
let mut ui_state = UiState::default();
while !matches!(ui_state, UiState::Quit) {
terminal.draw(|frame| ui(frame, &app, &mut ui_state))?;
if let Some(event) = handle_events()? {
ui_state = ui_state.process(event, &app);
}
}
disable_raw_mode()?;
stdout().execute(LeaveAlternateScreen)?;
Ok(())
}
fn handle_events() -> io::Result<Option<UiEvent>> {
if event::poll(std::time::Duration::from_millis(50))? {
if let Event::Key(key) = event::read()? {
if key.kind == event::KeyEventKind::Press {
return Ok(match key.code {
KeyCode::Char('c') if key.modifiers == KeyModifiers::CONTROL => {
Some(UiEvent::Quit)
}
KeyCode::Char('q') => Some(UiEvent::Quit),
KeyCode::Esc => Some(UiEvent::Back),
KeyCode::Down => Some(UiEvent::Down),
KeyCode::Up => Some(UiEvent::Up),
_ => None,
});
}
}
}
Ok(None)
}
fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
match state {
UiState::Quit => {}
UiState::MatchList { table_state } => {
frame.render_stateful_widget(match_list(app), frame.size(), table_state);
}
}
}

63
src/ui/state.rs Normal file
View file

@ -0,0 +1,63 @@
use crate::app::App;
use ratatui::widgets::TableState;
use table_state::TableStateExt;
#[derive(Clone, Debug)]
pub enum UiState {
MatchList { table_state: TableState },
Quit,
}
impl Default for UiState {
fn default() -> Self {
let mut table_state = TableState::default();
table_state.select(Some(0));
UiState::MatchList { table_state }
}
}
impl UiState {
pub fn process(self, event: UiEvent, app: &App) -> UiState {
match (self, event) {
(UiState::Quit, _) => UiState::Quit,
(_, UiEvent::Quit) => UiState::Quit,
(UiState::MatchList { .. }, UiEvent::Back) => UiState::Quit,
(UiState::MatchList { mut table_state }, UiEvent::Down) => {
table_state.down(app.matches.len());
UiState::MatchList { table_state }
}
(UiState::MatchList { mut table_state }, UiEvent::Up) => {
table_state.up(app.matches.len());
UiState::MatchList { table_state }
}
}
}
}
pub enum UiEvent {
Quit,
Back,
Up,
Down,
}
mod table_state {
use ratatui::widgets::TableState;
pub trait TableStateExt {
fn up(&mut self, count: usize);
fn down(&mut self, count: usize);
}
impl TableStateExt for TableState {
fn up(&mut self, count: usize) {
let current = self.selected().unwrap_or(0);
self.select(Some(if current == 0 { count - 1 } else { current - 1 }))
}
fn down(&mut self, count: usize) {
let current = self.selected().unwrap_or(0);
self.select(Some((current + 1).rem_euclid(count)))
}
}
}