mirror of
https://codeberg.org/icewind/logsmash.git
synced 2026-06-03 10:04:12 +02:00
add option to copy all lines in view
This commit is contained in:
parent
dd4b6260a1
commit
668512a767
5 changed files with 57 additions and 19 deletions
19
src/main.rs
19
src/main.rs
|
|
@ -5,6 +5,8 @@ use crate::logs::ParsedLogs;
|
||||||
use crate::matcher::{MatchResult, Matcher};
|
use crate::matcher::{MatchResult, Matcher};
|
||||||
use crate::ui::run_ui;
|
use crate::ui::run_ui;
|
||||||
use base64::prelude::*;
|
use base64::prelude::*;
|
||||||
|
use clap::builder::styling::{AnsiColor, Effects};
|
||||||
|
use clap::builder::Styles;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use indicatif::{ParallelProgressIterator, ProgressStyle};
|
use indicatif::{ParallelProgressIterator, ProgressStyle};
|
||||||
|
|
@ -20,6 +22,8 @@ use std::io::{read_to_string, stdin, BufReader, Stderr, Write};
|
||||||
use std::iter::once;
|
use std::iter::once;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
use std::thread::sleep;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
@ -39,8 +43,16 @@ use time::format_description::{parse_owned, parse_strftime_owned};
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
static GLOBAL: Jemalloc = Jemalloc;
|
static GLOBAL: Jemalloc = Jemalloc;
|
||||||
|
|
||||||
|
fn styles() -> Styles {
|
||||||
|
Styles::styled()
|
||||||
|
.header(AnsiColor::Yellow.on_default() | Effects::BOLD)
|
||||||
|
.usage(AnsiColor::Yellow.on_default() | Effects::BOLD)
|
||||||
|
.literal(AnsiColor::Blue.on_default() | Effects::BOLD)
|
||||||
|
.placeholder(AnsiColor::Green.on_default())
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Parser)]
|
#[derive(Debug, Parser)]
|
||||||
#[command(version, about, long_about = None)]
|
#[command(version, about, long_about = None, styles = styles())]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Files to read from, or "-" for stdin
|
/// Files to read from, or "-" for stdin
|
||||||
files: Vec<String>,
|
files: Vec<String>,
|
||||||
|
|
@ -199,7 +211,10 @@ fn main() -> MainResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_osc(text: &str) {
|
fn copy_osc(text: &str) {
|
||||||
print!("\x1B]52;c;{}\x07", BASE64_STANDARD.encode(text))
|
print!("\x1B]52;c;{}\x07", BASE64_STANDARD.encode(text));
|
||||||
|
// seems to be very unreliable without doing it twice ¯\_(ツ)_/¯
|
||||||
|
sleep(Duration::from_millis(100));
|
||||||
|
print!("\x1B]52;c;{}\x07", BASE64_STANDARD.encode(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_line(mut line: &str) -> Result<LogLine, serde_json::Error> {
|
fn parse_line(mut line: &str) -> Result<LogLine, serde_json::Error> {
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,10 @@ fn help(page: UiPage) -> &'static str {
|
||||||
UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors",
|
UiPage::GroupList => "«Q» Exit - «Enter» Select - «F» Filter - «E» Show parse errors",
|
||||||
UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «G» Group By - «Esc» Back",
|
UiPage::Group => "«Q» Exit - «Enter» Select - «F» Filter - «G» Group By - «Esc» Back",
|
||||||
UiPage::DistinctLogs => {
|
UiPage::DistinctLogs => {
|
||||||
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «G» Group By - «R» Show logs for request"
|
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines - «G» Group By - «R» Show logs for request"
|
||||||
}
|
}
|
||||||
UiPage::ByRequest => {
|
UiPage::ByRequest => {
|
||||||
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line"
|
"«Q» Exit - «F» Filter - «Esc» Back - «C» Copy log line - «Shift» + «C» Copy all lines"
|
||||||
}
|
}
|
||||||
UiPage::Log => {
|
UiPage::Log => {
|
||||||
"«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request"
|
"«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request"
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ pub enum UiEvent {
|
||||||
SelectAt(usize),
|
SelectAt(usize),
|
||||||
Enter(usize),
|
Enter(usize),
|
||||||
Copy,
|
Copy,
|
||||||
|
CopyAll,
|
||||||
EnterFilterMode,
|
EnterFilterMode,
|
||||||
ClearFilter,
|
ClearFilter,
|
||||||
Text(char),
|
Text(char),
|
||||||
|
|
@ -53,6 +54,7 @@ pub fn handle_events(page: UiPage, ui_state: &UiState) -> io::Result<Option<UiEv
|
||||||
(_, KeyCode::Home) => Some(UiEvent::Up(usize::MAX, false)),
|
(_, KeyCode::Home) => Some(UiEvent::Up(usize::MAX, false)),
|
||||||
(_, KeyCode::Enter | KeyCode::Right) => Some(UiEvent::Select),
|
(_, KeyCode::Enter | KeyCode::Right) => Some(UiEvent::Select),
|
||||||
(Mode::Normal, KeyCode::Char('c')) => Some(UiEvent::Copy),
|
(Mode::Normal, KeyCode::Char('c')) => Some(UiEvent::Copy),
|
||||||
|
(Mode::Normal, KeyCode::Char('C')) => Some(UiEvent::CopyAll),
|
||||||
(Mode::Normal, KeyCode::Char('r')) => Some(UiEvent::ByRequest),
|
(Mode::Normal, KeyCode::Char('r')) => Some(UiEvent::ByRequest),
|
||||||
(Mode::Normal, KeyCode::Char('g')) => Some(UiEvent::GroupBy),
|
(Mode::Normal, KeyCode::Char('g')) => Some(UiEvent::GroupBy),
|
||||||
(Mode::Normal, KeyCode::F(4) | KeyCode::Char('f')) => {
|
(Mode::Normal, KeyCode::F(4) | KeyCode::Char('f')) => {
|
||||||
|
|
|
||||||
|
|
@ -56,16 +56,22 @@ impl StatefulWidget for SingleLog<'_> {
|
||||||
|
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![Constraint::Min(par.line_count(area.width) as u16 + 1), Constraint::Percentage(100)])
|
.constraints(vec![
|
||||||
|
Constraint::Min(par.line_count(area.width) as u16 + 1),
|
||||||
|
Constraint::Percentage(100),
|
||||||
|
])
|
||||||
.split(area);
|
.split(area);
|
||||||
|
|
||||||
par.render(layout[0], buf);
|
par.render(layout[0], buf);
|
||||||
|
|
||||||
let (exception_area, mut exception_state) = if line.has_data() {
|
let (exception_area, mut exception_state) = if line.has_data() {
|
||||||
let (data_table, height) = render_data(line);
|
let (data_table, height) = render_data(line);
|
||||||
let layout = Layout::default()
|
let layout = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints(vec![Constraint::Min(height + 2), Constraint::Percentage(100)])
|
.constraints(vec![
|
||||||
|
Constraint::Min(height + 2),
|
||||||
|
Constraint::Percentage(100),
|
||||||
|
])
|
||||||
.split(layout[1]);
|
.split(layout[1]);
|
||||||
|
|
||||||
let block = Block::new()
|
let block = Block::new()
|
||||||
|
|
@ -206,27 +212,31 @@ fn find_path_prefix_length<'a, I: Iterator<Item = &'a str>>(paths: I) -> usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_data(log: &FullLogLine) -> (ScrollbarTable, u16) {
|
pub fn render_data(log: &FullLogLine) -> (ScrollbarTable, u16) {
|
||||||
let header = [
|
let header = [Text::from("Key"), Text::from("Value")]
|
||||||
Text::from("Key"),
|
|
||||||
Text::from("Value"),
|
|
||||||
]
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(Cell::from)
|
.map(Cell::from)
|
||||||
.collect::<Row>()
|
.collect::<Row>()
|
||||||
.style(TABLE_HEADER_STYLE)
|
.style(TABLE_HEADER_STYLE)
|
||||||
.height(1);
|
.height(1);
|
||||||
|
|
||||||
|
let max_key_width = log
|
||||||
|
.data()
|
||||||
|
.map(|(key, _)| key)
|
||||||
|
.map(str::len)
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default();
|
||||||
|
let lines = log.data().count();
|
||||||
|
|
||||||
let max_key_width = log.data().map(|(key, _)| key).map(str::len).max().unwrap_or_default();
|
let rows = log
|
||||||
let lines =log.data().count();
|
.data()
|
||||||
|
|
||||||
let rows = log.data()
|
|
||||||
.map(|(key, value)| Row::new([Cell::new(Text::from(key)), Cell::new(Text::from(value))]));
|
.map(|(key, value)| Row::new([Cell::new(Text::from(key)), Cell::new(Text::from(value))]));
|
||||||
|
|
||||||
|
|
||||||
let widths = [
|
let widths = [
|
||||||
Constraint::Min(max_key_width as u16),
|
Constraint::Min(max_key_width as u16),
|
||||||
Constraint::Percentage(100),
|
Constraint::Percentage(100),
|
||||||
];
|
];
|
||||||
(ScrollbarTable::new(rows, widths).header(header), lines as u16 + 1)
|
(
|
||||||
}
|
ScrollbarTable::new(rows, widths).header(header),
|
||||||
|
lines as u16 + 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -284,7 +284,7 @@ impl<'a> DistinctLogsState<'a> {
|
||||||
} else {
|
} else {
|
||||||
0
|
0
|
||||||
};
|
};
|
||||||
|
|
||||||
let data_len = full_line.data().count();
|
let data_len = full_line.data().count();
|
||||||
|
|
||||||
let table_state = ScrollbarTableState::new(trace_len + data_len);
|
let table_state = ScrollbarTableState::new(trace_len + data_len);
|
||||||
|
|
@ -622,6 +622,17 @@ impl<'a> UiState<'a> {
|
||||||
copy_osc(raw);
|
copy_osc(raw);
|
||||||
(false, UiState::Distinct(state))
|
(false, UiState::Distinct(state))
|
||||||
}
|
}
|
||||||
|
(UiState::Distinct(state), UiEvent::CopyAll) => {
|
||||||
|
let mut lines = String::with_capacity(8 * 1024);
|
||||||
|
|
||||||
|
for line in &state.lines {
|
||||||
|
let raw = app.get_source_line(line.line_number).unwrap_or_default();
|
||||||
|
lines.push_str(raw);
|
||||||
|
lines.push('\n');
|
||||||
|
}
|
||||||
|
copy_osc(&lines);
|
||||||
|
(false, UiState::Distinct(state))
|
||||||
|
}
|
||||||
(UiState::Distinct(state), UiEvent::GroupBy) => (true, state.group_by_menu()),
|
(UiState::Distinct(state), UiEvent::GroupBy) => (true, state.group_by_menu()),
|
||||||
(UiState::Log(state), UiEvent::Copy) => {
|
(UiState::Log(state), UiEvent::Copy) => {
|
||||||
let raw = app
|
let raw = app
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue