add copy keybind

This commit is contained in:
Robin Appelman 2024-07-27 14:57:00 +02:00
commit 5b86279b46
11 changed files with 231 additions and 101 deletions

View file

@ -31,6 +31,7 @@ fn help(page: UiPage) -> &'static str {
match page {
UiPage::MatchList => "«Q» Exit - «Enter» Select",
UiPage::Match => "«Q» Exit - «Enter» Select - «Esc» Back",
UiPage::Logs => "«Q» Exit - «Esc» Back",
UiPage::Logs => "«Q» Exit - «Esc» Back - «C» Copy log line",
UiPage::Log => "«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line",
}
}

View file

@ -4,8 +4,9 @@ use crate::ui::footer::footer;
use crate::ui::histogram::UiHistogram;
use crate::ui::match_list::match_list;
use crate::ui::raw_logs::raw_logs;
use crate::ui::single_log::single_log;
use crate::ui::single_match::grouped_lines;
use crate::ui::state::{UiEvent, UiState};
use crate::ui::state::{LogState, LogsState, MatchListState, MatchState, UiEvent, UiState};
use ratatui::crossterm::event::{Event, KeyCode, KeyModifiers};
use ratatui::crossterm::terminal::{
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
@ -21,6 +22,7 @@ mod footer;
mod histogram;
mod match_list;
mod raw_logs;
mod single_log;
mod single_match;
mod state;
pub mod style;
@ -58,7 +60,10 @@ fn handle_events() -> io::Result<Option<UiEvent>> {
KeyCode::Up => Some(UiEvent::Up(1)),
KeyCode::PageDown => Some(UiEvent::Down(10)),
KeyCode::PageUp => Some(UiEvent::Up(10)),
KeyCode::End => Some(UiEvent::Down(usize::MAX)),
KeyCode::Home => Some(UiEvent::Up(usize::MAX)),
KeyCode::Enter | KeyCode::Right => Some(UiEvent::Select),
KeyCode::Char('c') => Some(UiEvent::Copy),
_ => None,
});
}
@ -80,10 +85,10 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
match state {
UiState::Quit => {}
UiState::MatchList {
UiState::MatchList(MatchListState {
table_state,
scroll_state,
} => {
}) => {
let selected = table_state.selected().unwrap_or(0);
let histogram = if selected == 0 {
&app.all.histogram
@ -113,12 +118,12 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
);
frame.render_widget(footer(app, page), layout[2]);
}
UiState::Match {
UiState::Match(MatchState {
result,
table_state,
scroll_state,
..
} => {
}) => {
let selected_group = &result.grouped[table_state.selected().unwrap_or_default()];
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some(""))
@ -140,12 +145,12 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
);
frame.render_widget(footer(app, page), layout[2]);
}
UiState::Logs {
UiState::Logs(LogsState {
lines,
table_state,
scroll_state,
..
} => {
}) => {
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
.begin_symbol(Some(""))
.end_symbol(Some(""));
@ -164,5 +169,9 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
);
frame.render_widget(footer(app, page), layout[2]);
}
UiState::Log(LogState { log, .. }) => {
frame.render_widget(single_log(app, log), layout[0].union(layout[1]));
frame.render_widget(footer(app, page), layout[2]);
}
}
}

7
src/ui/single_log.rs Normal file
View file

@ -0,0 +1,7 @@
use crate::app::App;
use crate::logline::LogLine;
use ratatui::widgets::{Paragraph, Wrap};
pub fn single_log<'a>(_app: &App, line: &'a LogLine) -> Paragraph<'a> {
Paragraph::new(line.display()).wrap(Wrap::default())
}

View file

@ -1,70 +1,108 @@
use crate::app::{App, LogMatch};
use crate::copy_osc;
use crate::logline::LogLine;
use derive_more::From;
use ratatui::widgets::{ScrollbarState, TableState};
use table_state::TableStateExt;
#[derive(Clone)]
#[derive(Clone, From)]
pub enum UiState<'a> {
MatchList {
table_state: TableState,
scroll_state: ScrollbarState,
},
Match {
result: &'a LogMatch,
table_state: TableState,
scroll_state: ScrollbarState,
previous: Box<UiState<'a>>,
},
Logs {
lines: &'a [usize],
table_state: TableState,
scroll_state: ScrollbarState,
previous: Box<UiState<'a>>,
},
MatchList(MatchListState),
Match(MatchState<'a>),
Logs(LogsState<'a>),
Log(LogState<'a>),
Quit,
}
#[derive(Clone)]
pub struct MatchListState {
pub table_state: TableState,
pub scroll_state: ScrollbarState,
}
impl MatchListState {
fn selected(&self) -> usize {
self.table_state.selected().unwrap()
}
}
#[derive(Clone)]
pub struct MatchState<'a> {
pub result: &'a LogMatch,
pub table_state: TableState,
pub scroll_state: ScrollbarState,
pub previous: Box<UiState<'a>>,
}
impl<'a> MatchState<'a> {
fn selected(&self) -> usize {
self.table_state.selected().unwrap()
}
}
#[derive(Clone)]
pub struct LogsState<'a> {
pub lines: &'a [usize],
pub table_state: TableState,
pub scroll_state: ScrollbarState,
pub previous: Box<UiState<'a>>,
}
impl<'a> LogsState<'a> {
fn selected(&self) -> usize {
self.table_state.selected().unwrap()
}
}
#[derive(Clone)]
pub struct LogState<'a> {
pub log: &'a LogLine,
pub previous: Box<UiState<'a>>,
}
impl<'a> UiState<'a> {
pub fn new(app: &App) -> Self {
let mut table_state = TableState::default();
table_state.select(Some(0));
UiState::MatchList {
UiState::MatchList(MatchListState {
table_state,
scroll_state: ScrollbarState::new(app.match_lines()),
}
})
}
pub fn page(&self) -> UiPage {
match self {
UiState::Quit | UiState::MatchList { .. } => UiPage::MatchList,
UiState::Match { .. } => UiPage::Match,
UiState::Logs { .. } => UiPage::Logs,
UiState::Quit | UiState::MatchList(_) => UiPage::MatchList,
UiState::Match(_) => UiPage::Match,
UiState::Logs(_) => UiPage::Logs,
UiState::Log(_) => UiPage::Log,
}
}
fn table_state(&mut self) -> Option<&mut TableState> {
match self {
UiState::MatchList { table_state, .. } => Some(table_state),
UiState::Match { table_state, .. } => Some(table_state),
UiState::Logs { table_state, .. } => Some(table_state),
UiState::Quit => None,
UiState::MatchList(state) => Some(&mut state.table_state),
UiState::Match(state) => Some(&mut state.table_state),
UiState::Logs(state) => Some(&mut state.table_state),
_ => None,
}
}
fn scroll_state(&mut self) -> Option<&mut ScrollbarState> {
match self {
UiState::MatchList { scroll_state, .. } => Some(scroll_state),
UiState::Match { scroll_state, .. } => Some(scroll_state),
UiState::Logs { scroll_state, .. } => Some(scroll_state),
UiState::Quit => None,
UiState::MatchList(state) => Some(&mut state.scroll_state),
UiState::Match(state) => Some(&mut state.scroll_state),
UiState::Logs(state) => Some(&mut state.scroll_state),
_ => None,
}
}
fn table_count(&self, app: &App) -> usize {
fn row_count(&self, app: &App) -> usize {
match self {
UiState::MatchList { .. } => app.match_lines(),
UiState::Match { result, .. } => result.grouped.len(),
UiState::Logs { lines, .. } => lines.len(),
UiState::Quit => 0,
UiState::MatchList(_) => app.match_lines(),
UiState::Match(state) => state.result.grouped.len(),
UiState::Logs(state) => state.lines.len(),
_ => 0,
}
}
@ -72,9 +110,9 @@ impl<'a> UiState<'a> {
match (self, event) {
(UiState::Quit, _) => UiState::Quit,
(_, UiEvent::Quit) => UiState::Quit,
(UiState::MatchList { .. }, UiEvent::Back) => UiState::Quit,
(UiState::MatchList(_), UiEvent::Back) => UiState::Quit,
(mut state, UiEvent::Down(step)) => {
let count = state.table_count(app);
let count = state.row_count(app);
if let Some(table_state) = state.table_state() {
let pos = table_state.down(count, step);
let scroll_state = state.scroll_state().unwrap();
@ -83,7 +121,7 @@ impl<'a> UiState<'a> {
state
}
(mut state, UiEvent::Up(step)) => {
let count = state.table_count(app);
let count = state.row_count(app);
if let Some(table_state) = state.table_state() {
let pos = table_state.up(count, step);
let scroll_state = state.scroll_state().unwrap();
@ -91,14 +129,8 @@ impl<'a> UiState<'a> {
}
state
}
(
UiState::MatchList {
table_state: prev_state,
scroll_state: prev_scroll,
},
UiEvent::Select,
) => {
let selected = prev_state.selected().unwrap_or(0);
(UiState::MatchList(state), UiEvent::Select) => {
let selected = state.selected();
let mut table_state = TableState::default();
table_state.select(Some(0));
@ -109,45 +141,59 @@ impl<'a> UiState<'a> {
} else {
&app.matches[selected - 1]
};
UiState::Match {
UiState::Match(MatchState {
result,
table_state,
scroll_state: ScrollbarState::new(result.count()),
previous: Box::new(UiState::MatchList {
table_state: prev_state,
scroll_state: prev_scroll,
}),
}
previous: Box::new(state.into()),
})
}
(
UiState::Match {
table_state: prev_state,
scroll_state: prev_scroll,
previous,
result,
},
UiEvent::Select,
) => {
let selected = prev_state.selected().unwrap_or(0);
(UiState::Match(state), UiEvent::Select) => {
let selected = state.selected();
let mut table_state = TableState::default();
table_state.select(Some(0));
let lines = result.grouped[selected].lines.as_slice();
UiState::Logs {
let lines = state.result.grouped[selected].lines.as_slice();
UiState::Logs(LogsState {
lines,
table_state,
scroll_state: ScrollbarState::new(lines.len()),
previous: Box::new(UiState::Match {
table_state: prev_state,
scroll_state: prev_scroll,
previous,
result,
}),
}
previous: Box::new(state.into()),
})
}
(UiState::Match { previous, .. } | UiState::Logs { previous, .. }, UiEvent::Back) => {
*previous
(UiState::Logs(state), UiEvent::Select) => {
let selected = state.selected();
let mut table_state = TableState::default();
table_state.select(Some(0));
let line = state.lines[selected];
let log = &app.lines[line];
UiState::Log(LogState {
log,
previous: Box::new(state.into()),
})
}
(UiState::Logs(state), UiEvent::Copy) => {
let selected = state.selected();
let mut table_state = TableState::default();
table_state.select(Some(0));
let line = &app.lines[state.lines[selected]];
let raw = app.get_line(line.index).unwrap_or_default();
copy_osc(&raw);
UiState::Logs(state)
}
(UiState::Log(state), UiEvent::Copy) => {
let raw = app.get_line(state.log.index).unwrap_or_default();
copy_osc(&raw);
UiState::Log(state)
}
(
UiState::Match(MatchState { previous, .. })
| UiState::Logs(LogsState { previous, .. })
| UiState::Log(LogState { previous, .. }),
UiEvent::Back,
) => *previous,
(state, _) => state,
}
}
@ -159,12 +205,14 @@ pub enum UiEvent {
Up(usize),
Down(usize),
Select,
Copy,
}
pub enum UiPage {
MatchList,
Match,
Logs,
Log,
}
mod table_state {