mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 18:14:11 +02:00
add copy keybind
This commit is contained in:
parent
c56a77f3d0
commit
5b86279b46
11 changed files with 231 additions and 101 deletions
22
Cargo.lock
generated
22
Cargo.lock
generated
|
|
@ -329,6 +329,26 @@ dependencies = [
|
||||||
"syn 2.0.71",
|
"syn 2.0.71",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "1.0.0-beta.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7abbfc297053be59290e3152f8cbcd52c8642e0728b69ee187d991d4c1af08d"
|
||||||
|
dependencies = [
|
||||||
|
"derive_more-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more-impl"
|
||||||
|
version = "1.0.0-beta.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2bba3e9872d7c58ce7ef0fcf1844fcc3e23ef2a58377b50df35dd98e42a5726e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.71",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
|
|
@ -532,7 +552,9 @@ name = "logsmash"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash",
|
"ahash",
|
||||||
|
"base64",
|
||||||
"clap",
|
"clap",
|
||||||
|
"derive_more",
|
||||||
"hdrhistogram",
|
"hdrhistogram",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ tinystr = { version = "0.7.6", features = ["serde"] }
|
||||||
time = { version = "0.3.36", features = ["serde", "serde-well-known"] }
|
time = { version = "0.3.36", features = ["serde", "serde-well-known"] }
|
||||||
hdrhistogram = "7.5.4"
|
hdrhistogram = "7.5.4"
|
||||||
ahash = "0.8.11"
|
ahash = "0.8.11"
|
||||||
|
base64 = "0.21.7"
|
||||||
|
derive_more = { version = "1.0.0-beta.6", features = ["from"] }
|
||||||
|
|
||||||
[profile.dev.package."*"]
|
[profile.dev.package."*"]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
|
use crate::logfile::LogFile;
|
||||||
use crate::logline::LogLine;
|
use crate::logline::LogLine;
|
||||||
use crate::matcher::MatchResult;
|
use crate::matcher::MatchResult;
|
||||||
use crate::timegraph::TimeGraph;
|
use crate::timegraph::TimeGraph;
|
||||||
use logsmash_data::StatementList;
|
use logsmash_data::StatementList;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::sync::Mutex;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
|
|
@ -14,6 +16,7 @@ pub struct App {
|
||||||
pub error_count: usize,
|
pub error_count: usize,
|
||||||
pub all: LogMatch,
|
pub all: LogMatch,
|
||||||
pub unmatched: LogMatch,
|
pub unmatched: LogMatch,
|
||||||
|
pub log_file: Mutex<LogFile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
|
|
@ -25,6 +28,10 @@ impl App {
|
||||||
};
|
};
|
||||||
self.matches.len() + 1 + unmatched_line_count
|
self.matches.len() + 1 + unmatched_line_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_line(&self, index: usize) -> Option<String> {
|
||||||
|
self.log_file.lock().unwrap().nth(index)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LogMatch {
|
pub struct LogMatch {
|
||||||
|
|
@ -63,7 +70,7 @@ fn group_lines<I: Iterator<Item = usize>>(all_lines: &[LogLine], indices: I) ->
|
||||||
let mut map: BTreeMap<u64, Vec<usize>> = BTreeMap::new();
|
let mut map: BTreeMap<u64, Vec<usize>> = BTreeMap::new();
|
||||||
|
|
||||||
for (i, line) in indices.map(|i| (i, &all_lines[i])) {
|
for (i, line) in indices.map(|i| (i, &all_lines[i])) {
|
||||||
map.entry(line.index()).or_default().push(i);
|
map.entry(line.identity()).or_default().push(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut list: Vec<_> = map
|
let mut list: Vec<_> = map
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::error::ReadError;
|
use crate::error::ReadError;
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::{BufRead, BufReader};
|
use std::io::{BufRead, BufReader, Seek};
|
||||||
use zip::ZipArchive;
|
use zip::ZipArchive;
|
||||||
|
|
||||||
pub enum LogFile {
|
pub enum LogFile {
|
||||||
|
|
@ -37,4 +37,22 @@ impl LogFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn nth(&mut self, index: usize) -> Option<String> {
|
||||||
|
match self {
|
||||||
|
LogFile::Plain(file) => {
|
||||||
|
file.rewind().unwrap();
|
||||||
|
file.lines().nth(index).transpose().ok().flatten()
|
||||||
|
}
|
||||||
|
LogFile::Zip(zip) => {
|
||||||
|
let file = zip.by_index(0).expect("failed to open zip content again");
|
||||||
|
BufReader::new(file)
|
||||||
|
.lines()
|
||||||
|
.nth(index)
|
||||||
|
.transpose()
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use tinystr::TinyAsciiStr;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct LogLine {
|
pub struct LogLine {
|
||||||
|
#[serde(default)]
|
||||||
|
pub index: usize,
|
||||||
pub version: TinyAsciiStr<16>,
|
pub version: TinyAsciiStr<16>,
|
||||||
pub level: LogLevel,
|
pub level: LogLevel,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
|
|
@ -17,20 +19,11 @@ pub struct LogLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LogLine {
|
impl LogLine {
|
||||||
pub fn index(&self) -> u64 {
|
pub fn identity(&self) -> u64 {
|
||||||
let mut hasher = AHasher::default();
|
let mut hasher = AHasher::default();
|
||||||
self.message.hash(&mut hasher);
|
self.message.hash(&mut hasher);
|
||||||
self.level.hash(&mut hasher);
|
self.level.hash(&mut hasher);
|
||||||
self.exception
|
self.exception.hash(&mut hasher);
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.exception.as_str())
|
|
||||||
.hash(&mut hasher);
|
|
||||||
self.exception
|
|
||||||
.as_ref()
|
|
||||||
.map(|e| e.file.as_str())
|
|
||||||
.hash(&mut hasher);
|
|
||||||
self.app.hash(&mut hasher);
|
|
||||||
self.exception.as_ref().map(|e| e.line).hash(&mut hasher);
|
|
||||||
self.app.hash(&mut hasher);
|
self.app.hash(&mut hasher);
|
||||||
hasher.finish()
|
hasher.finish()
|
||||||
}
|
}
|
||||||
|
|
@ -71,3 +64,12 @@ pub struct Exception {
|
||||||
pub line: usize,
|
pub line: usize,
|
||||||
pub previous: Option<Box<Exception>>,
|
pub previous: Option<Box<Exception>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Hash for Exception {
|
||||||
|
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||||
|
self.message.hash(state);
|
||||||
|
self.exception.hash(state);
|
||||||
|
self.file.hash(state);
|
||||||
|
self.line.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
22
src/main.rs
22
src/main.rs
|
|
@ -4,12 +4,14 @@ use crate::logfile::LogFile;
|
||||||
use crate::logline::LogLine;
|
use crate::logline::LogLine;
|
||||||
use crate::matcher::{MatchResult, Matcher};
|
use crate::matcher::{MatchResult, Matcher};
|
||||||
use crate::ui::run_ui;
|
use crate::ui::run_ui;
|
||||||
|
use base64::prelude::*;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use logsmash_data::{default_apps, get_statements, SourceDefinition};
|
use logsmash_data::{default_apps, get_statements, SourceDefinition};
|
||||||
use main_error::MainResult;
|
use main_error::MainResult;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
@ -58,25 +60,26 @@ fn main() -> MainResult {
|
||||||
let mut unmatched_counts: HashMap<String, Vec<usize>> = HashMap::new();
|
let mut unmatched_counts: HashMap<String, Vec<usize>> = HashMap::new();
|
||||||
let mut parsed_lines = Vec::with_capacity(1024);
|
let mut parsed_lines = Vec::with_capacity(1024);
|
||||||
let mut unmatched_lines = Vec::with_capacity(256);
|
let mut unmatched_lines = Vec::with_capacity(256);
|
||||||
let mut i = 0;
|
let mut parsed_index = 0;
|
||||||
for line in lines {
|
for (index, line) in lines.enumerate() {
|
||||||
if line.starts_with('{') {
|
if line.starts_with('{') {
|
||||||
let parsed = match serde_json::from_str::<LogLine>(&line) {
|
let mut parsed = match serde_json::from_str::<LogLine>(&line) {
|
||||||
Ok(parsed) => parsed,
|
Ok(parsed) => parsed,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error_count += 1;
|
error_count += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
parsed.index = index;
|
||||||
if let Some(index) = matcher.match_log(&parsed) {
|
if let Some(index) = matcher.match_log(&parsed) {
|
||||||
counts.entry(index).or_default().push(i);
|
counts.entry(index).or_default().push(parsed_index);
|
||||||
} else if let Some(entry) = unmatched_counts.get_mut(parsed.app.as_str()) {
|
} else if let Some(entry) = unmatched_counts.get_mut(parsed.app.as_str()) {
|
||||||
entry.push(i)
|
entry.push(parsed_index)
|
||||||
} else {
|
} else {
|
||||||
unmatched_lines.push(i);
|
unmatched_lines.push(parsed_index);
|
||||||
}
|
}
|
||||||
parsed_lines.push(parsed);
|
parsed_lines.push(parsed);
|
||||||
i += 1;
|
parsed_index += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,9 +108,14 @@ fn main() -> MainResult {
|
||||||
unmatched,
|
unmatched,
|
||||||
all,
|
all,
|
||||||
error_count,
|
error_count,
|
||||||
|
log_file: Mutex::new(log_file),
|
||||||
};
|
};
|
||||||
|
|
||||||
run_ui(app)?;
|
run_ui(app)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn copy_osc(text: &str) {
|
||||||
|
print!("\x1B]52;c;{}\x07", BASE64_STANDARD.encode(text))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -163,6 +163,7 @@ fn test_matcher() {
|
||||||
message: "Not allowed to rename a shared album".into(),
|
message: "Not allowed to rename a shared album".into(),
|
||||||
exception: None,
|
exception: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -174,6 +175,7 @@ fn test_matcher() {
|
||||||
message: "Not allowed to rename an album".into(),
|
message: "Not allowed to rename an album".into(),
|
||||||
exception: None,
|
exception: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -185,6 +187,7 @@ fn test_matcher() {
|
||||||
message: "You are not allowed to edit link shares that you don't own".into(),
|
message: "You are not allowed to edit link shares that you don't own".into(),
|
||||||
exception: None,
|
exception: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -196,6 +199,7 @@ fn test_matcher() {
|
||||||
message: "You are not allowed to edit link shares that you don't own".into(),
|
message: "You are not allowed to edit link shares that you don't own".into(),
|
||||||
exception: None,
|
exception: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
@ -208,6 +212,7 @@ fn test_matcher() {
|
||||||
message: "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported".into(),
|
message: "Unsupported query value for mimetype: %/text, only values in the format \"mime/type\" or \"mime/%\" are supported".into(),
|
||||||
exception: None,
|
exception: None,
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
@ -227,6 +232,7 @@ fn test_matcher() {
|
||||||
previous: None,
|
previous: None,
|
||||||
}),
|
}),
|
||||||
time: OffsetDateTime::now_utc(),
|
time: OffsetDateTime::now_utc(),
|
||||||
|
index: 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ fn help(page: UiPage) -> &'static str {
|
||||||
match page {
|
match page {
|
||||||
UiPage::MatchList => "«Q» Exit - «Enter» Select",
|
UiPage::MatchList => "«Q» Exit - «Enter» Select",
|
||||||
UiPage::Match => "«Q» Exit - «Enter» Select - «Esc» Back",
|
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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,9 @@ use crate::ui::footer::footer;
|
||||||
use crate::ui::histogram::UiHistogram;
|
use crate::ui::histogram::UiHistogram;
|
||||||
use crate::ui::match_list::match_list;
|
use crate::ui::match_list::match_list;
|
||||||
use crate::ui::raw_logs::raw_logs;
|
use crate::ui::raw_logs::raw_logs;
|
||||||
|
use crate::ui::single_log::single_log;
|
||||||
use crate::ui::single_match::grouped_lines;
|
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::event::{Event, KeyCode, KeyModifiers};
|
||||||
use ratatui::crossterm::terminal::{
|
use ratatui::crossterm::terminal::{
|
||||||
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
|
||||||
|
|
@ -21,6 +22,7 @@ mod footer;
|
||||||
mod histogram;
|
mod histogram;
|
||||||
mod match_list;
|
mod match_list;
|
||||||
mod raw_logs;
|
mod raw_logs;
|
||||||
|
mod single_log;
|
||||||
mod single_match;
|
mod single_match;
|
||||||
mod state;
|
mod state;
|
||||||
pub mod style;
|
pub mod style;
|
||||||
|
|
@ -58,7 +60,10 @@ fn handle_events() -> io::Result<Option<UiEvent>> {
|
||||||
KeyCode::Up => Some(UiEvent::Up(1)),
|
KeyCode::Up => Some(UiEvent::Up(1)),
|
||||||
KeyCode::PageDown => Some(UiEvent::Down(10)),
|
KeyCode::PageDown => Some(UiEvent::Down(10)),
|
||||||
KeyCode::PageUp => Some(UiEvent::Up(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::Enter | KeyCode::Right => Some(UiEvent::Select),
|
||||||
|
KeyCode::Char('c') => Some(UiEvent::Copy),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -80,10 +85,10 @@ fn ui(frame: &mut Frame, app: &App, state: &mut UiState) {
|
||||||
|
|
||||||
match state {
|
match state {
|
||||||
UiState::Quit => {}
|
UiState::Quit => {}
|
||||||
UiState::MatchList {
|
UiState::MatchList(MatchListState {
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
} => {
|
}) => {
|
||||||
let selected = table_state.selected().unwrap_or(0);
|
let selected = table_state.selected().unwrap_or(0);
|
||||||
let histogram = if selected == 0 {
|
let histogram = if selected == 0 {
|
||||||
&app.all.histogram
|
&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]);
|
frame.render_widget(footer(app, page), layout[2]);
|
||||||
}
|
}
|
||||||
UiState::Match {
|
UiState::Match(MatchState {
|
||||||
result,
|
result,
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
let selected_group = &result.grouped[table_state.selected().unwrap_or_default()];
|
let selected_group = &result.grouped[table_state.selected().unwrap_or_default()];
|
||||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||||
.begin_symbol(Some("↑"))
|
.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]);
|
frame.render_widget(footer(app, page), layout[2]);
|
||||||
}
|
}
|
||||||
UiState::Logs {
|
UiState::Logs(LogsState {
|
||||||
lines,
|
lines,
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state,
|
scroll_state,
|
||||||
..
|
..
|
||||||
} => {
|
}) => {
|
||||||
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
|
||||||
.begin_symbol(Some("↑"))
|
.begin_symbol(Some("↑"))
|
||||||
.end_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]);
|
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
7
src/ui/single_log.rs
Normal 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())
|
||||||
|
}
|
||||||
192
src/ui/state.rs
192
src/ui/state.rs
|
|
@ -1,70 +1,108 @@
|
||||||
use crate::app::{App, LogMatch};
|
use crate::app::{App, LogMatch};
|
||||||
|
use crate::copy_osc;
|
||||||
|
use crate::logline::LogLine;
|
||||||
|
use derive_more::From;
|
||||||
use ratatui::widgets::{ScrollbarState, TableState};
|
use ratatui::widgets::{ScrollbarState, TableState};
|
||||||
use table_state::TableStateExt;
|
use table_state::TableStateExt;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, From)]
|
||||||
pub enum UiState<'a> {
|
pub enum UiState<'a> {
|
||||||
MatchList {
|
MatchList(MatchListState),
|
||||||
table_state: TableState,
|
Match(MatchState<'a>),
|
||||||
scroll_state: ScrollbarState,
|
Logs(LogsState<'a>),
|
||||||
},
|
Log(LogState<'a>),
|
||||||
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>>,
|
|
||||||
},
|
|
||||||
Quit,
|
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> {
|
impl<'a> UiState<'a> {
|
||||||
pub fn new(app: &App) -> Self {
|
pub fn new(app: &App) -> Self {
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
UiState::MatchList {
|
UiState::MatchList(MatchListState {
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state: ScrollbarState::new(app.match_lines()),
|
scroll_state: ScrollbarState::new(app.match_lines()),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn page(&self) -> UiPage {
|
pub fn page(&self) -> UiPage {
|
||||||
match self {
|
match self {
|
||||||
UiState::Quit | UiState::MatchList { .. } => UiPage::MatchList,
|
UiState::Quit | UiState::MatchList(_) => UiPage::MatchList,
|
||||||
UiState::Match { .. } => UiPage::Match,
|
UiState::Match(_) => UiPage::Match,
|
||||||
UiState::Logs { .. } => UiPage::Logs,
|
UiState::Logs(_) => UiPage::Logs,
|
||||||
|
UiState::Log(_) => UiPage::Log,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_state(&mut self) -> Option<&mut TableState> {
|
fn table_state(&mut self) -> Option<&mut TableState> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList { table_state, .. } => Some(table_state),
|
UiState::MatchList(state) => Some(&mut state.table_state),
|
||||||
UiState::Match { table_state, .. } => Some(table_state),
|
UiState::Match(state) => Some(&mut state.table_state),
|
||||||
UiState::Logs { table_state, .. } => Some(table_state),
|
UiState::Logs(state) => Some(&mut state.table_state),
|
||||||
UiState::Quit => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_state(&mut self) -> Option<&mut ScrollbarState> {
|
fn scroll_state(&mut self) -> Option<&mut ScrollbarState> {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList { scroll_state, .. } => Some(scroll_state),
|
UiState::MatchList(state) => Some(&mut state.scroll_state),
|
||||||
UiState::Match { scroll_state, .. } => Some(scroll_state),
|
UiState::Match(state) => Some(&mut state.scroll_state),
|
||||||
UiState::Logs { scroll_state, .. } => Some(scroll_state),
|
UiState::Logs(state) => Some(&mut state.scroll_state),
|
||||||
UiState::Quit => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn table_count(&self, app: &App) -> usize {
|
fn row_count(&self, app: &App) -> usize {
|
||||||
match self {
|
match self {
|
||||||
UiState::MatchList { .. } => app.match_lines(),
|
UiState::MatchList(_) => app.match_lines(),
|
||||||
UiState::Match { result, .. } => result.grouped.len(),
|
UiState::Match(state) => state.result.grouped.len(),
|
||||||
UiState::Logs { lines, .. } => lines.len(),
|
UiState::Logs(state) => state.lines.len(),
|
||||||
UiState::Quit => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,9 +110,9 @@ impl<'a> UiState<'a> {
|
||||||
match (self, event) {
|
match (self, event) {
|
||||||
(UiState::Quit, _) => UiState::Quit,
|
(UiState::Quit, _) => UiState::Quit,
|
||||||
(_, UiEvent::Quit) => UiState::Quit,
|
(_, UiEvent::Quit) => UiState::Quit,
|
||||||
(UiState::MatchList { .. }, UiEvent::Back) => UiState::Quit,
|
(UiState::MatchList(_), UiEvent::Back) => UiState::Quit,
|
||||||
(mut state, UiEvent::Down(step)) => {
|
(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() {
|
if let Some(table_state) = state.table_state() {
|
||||||
let pos = table_state.down(count, step);
|
let pos = table_state.down(count, step);
|
||||||
let scroll_state = state.scroll_state().unwrap();
|
let scroll_state = state.scroll_state().unwrap();
|
||||||
|
|
@ -83,7 +121,7 @@ impl<'a> UiState<'a> {
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
(mut state, UiEvent::Up(step)) => {
|
(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() {
|
if let Some(table_state) = state.table_state() {
|
||||||
let pos = table_state.up(count, step);
|
let pos = table_state.up(count, step);
|
||||||
let scroll_state = state.scroll_state().unwrap();
|
let scroll_state = state.scroll_state().unwrap();
|
||||||
|
|
@ -91,14 +129,8 @@ impl<'a> UiState<'a> {
|
||||||
}
|
}
|
||||||
state
|
state
|
||||||
}
|
}
|
||||||
(
|
(UiState::MatchList(state), UiEvent::Select) => {
|
||||||
UiState::MatchList {
|
let selected = state.selected();
|
||||||
table_state: prev_state,
|
|
||||||
scroll_state: prev_scroll,
|
|
||||||
},
|
|
||||||
UiEvent::Select,
|
|
||||||
) => {
|
|
||||||
let selected = prev_state.selected().unwrap_or(0);
|
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
|
|
||||||
|
|
@ -109,45 +141,59 @@ impl<'a> UiState<'a> {
|
||||||
} else {
|
} else {
|
||||||
&app.matches[selected - 1]
|
&app.matches[selected - 1]
|
||||||
};
|
};
|
||||||
UiState::Match {
|
UiState::Match(MatchState {
|
||||||
result,
|
result,
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state: ScrollbarState::new(result.count()),
|
scroll_state: ScrollbarState::new(result.count()),
|
||||||
previous: Box::new(UiState::MatchList {
|
previous: Box::new(state.into()),
|
||||||
table_state: prev_state,
|
})
|
||||||
scroll_state: prev_scroll,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
(UiState::Match(state), UiEvent::Select) => {
|
||||||
(
|
let selected = state.selected();
|
||||||
UiState::Match {
|
|
||||||
table_state: prev_state,
|
|
||||||
scroll_state: prev_scroll,
|
|
||||||
previous,
|
|
||||||
result,
|
|
||||||
},
|
|
||||||
UiEvent::Select,
|
|
||||||
) => {
|
|
||||||
let selected = prev_state.selected().unwrap_or(0);
|
|
||||||
let mut table_state = TableState::default();
|
let mut table_state = TableState::default();
|
||||||
table_state.select(Some(0));
|
table_state.select(Some(0));
|
||||||
|
|
||||||
let lines = result.grouped[selected].lines.as_slice();
|
let lines = state.result.grouped[selected].lines.as_slice();
|
||||||
UiState::Logs {
|
UiState::Logs(LogsState {
|
||||||
lines,
|
lines,
|
||||||
table_state,
|
table_state,
|
||||||
scroll_state: ScrollbarState::new(lines.len()),
|
scroll_state: ScrollbarState::new(lines.len()),
|
||||||
previous: Box::new(UiState::Match {
|
previous: Box::new(state.into()),
|
||||||
table_state: prev_state,
|
})
|
||||||
scroll_state: prev_scroll,
|
|
||||||
previous,
|
|
||||||
result,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
|
(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::Match { previous, .. } | UiState::Logs { previous, .. }, UiEvent::Back) => {
|
(UiState::Logs(state), UiEvent::Copy) => {
|
||||||
*previous
|
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,
|
(state, _) => state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -159,12 +205,14 @@ pub enum UiEvent {
|
||||||
Up(usize),
|
Up(usize),
|
||||||
Down(usize),
|
Down(usize),
|
||||||
Select,
|
Select,
|
||||||
|
Copy,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum UiPage {
|
pub enum UiPage {
|
||||||
MatchList,
|
MatchList,
|
||||||
Match,
|
Match,
|
||||||
Logs,
|
Logs,
|
||||||
|
Log,
|
||||||
}
|
}
|
||||||
|
|
||||||
mod table_state {
|
mod table_state {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue