add option to copy all lines in view
All checks were successful
CI / build (push) Successful in 46s
CI / checks (push) Successful in 54s
CI / build-nixpkgs (push) Successful in 37s

This commit is contained in:
Robin Appelman 2025-11-04 20:05:25 +01:00
commit 668512a767
5 changed files with 57 additions and 19 deletions

View file

@ -5,6 +5,8 @@ use crate::logs::ParsedLogs;
use crate::matcher::{MatchResult, Matcher};
use crate::ui::run_ui;
use base64::prelude::*;
use clap::builder::styling::{AnsiColor, Effects};
use clap::builder::Styles;
use clap::Parser;
use either::Either;
use indicatif::{ParallelProgressIterator, ProgressStyle};
@ -20,6 +22,8 @@ use std::io::{read_to_string, stdin, BufReader, Stderr, Write};
use std::iter::once;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::RwLock;
use std::thread::sleep;
use std::time::Duration;
mod app;
mod error;
@ -39,8 +43,16 @@ use time::format_description::{parse_owned, parse_strftime_owned};
#[global_allocator]
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)]
#[command(version, about, long_about = None)]
#[command(version, about, long_about = None, styles = styles())]
struct Args {
/// Files to read from, or "-" for stdin
files: Vec<String>,
@ -199,7 +211,10 @@ fn main() -> MainResult {
}
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> {

View file

@ -58,10 +58,10 @@ fn help(page: UiPage) -> &'static str {
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::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 => {
"«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 => {
"«Q» Exit - «Esc» Back - «R» Toggle raw - «C» Copy log line - «R» Show logs for request"

View file

@ -17,6 +17,7 @@ pub enum UiEvent {
SelectAt(usize),
Enter(usize),
Copy,
CopyAll,
EnterFilterMode,
ClearFilter,
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::Enter | KeyCode::Right) => Some(UiEvent::Select),
(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('g')) => Some(UiEvent::GroupBy),
(Mode::Normal, KeyCode::F(4) | KeyCode::Char('f')) => {

View file

@ -56,7 +56,10 @@ impl StatefulWidget for SingleLog<'_> {
let layout = Layout::default()
.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);
par.render(layout[0], buf);
@ -65,7 +68,10 @@ impl StatefulWidget for SingleLog<'_> {
let (data_table, height) = render_data(line);
let layout = Layout::default()
.direction(Direction::Vertical)
.constraints(vec![Constraint::Min(height + 2), Constraint::Percentage(100)])
.constraints(vec![
Constraint::Min(height + 2),
Constraint::Percentage(100),
])
.split(layout[1]);
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) {
let header = [
Text::from("Key"),
Text::from("Value"),
]
let header = [Text::from("Key"), Text::from("Value")]
.into_iter()
.map(Cell::from)
.collect::<Row>()
.style(TABLE_HEADER_STYLE)
.height(1);
let max_key_width = log.data().map(|(key, _)| key).map(str::len).max().unwrap_or_default();
let max_key_width = log
.data()
.map(|(key, _)| key)
.map(str::len)
.max()
.unwrap_or_default();
let lines = log.data().count();
let rows = log.data()
let rows = log
.data()
.map(|(key, value)| Row::new([Cell::new(Text::from(key)), Cell::new(Text::from(value))]));
let widths = [
Constraint::Min(max_key_width as u16),
Constraint::Percentage(100),
];
(ScrollbarTable::new(rows, widths).header(header), lines as u16 + 1)
(
ScrollbarTable::new(rows, widths).header(header),
lines as u16 + 1,
)
}

View file

@ -622,6 +622,17 @@ impl<'a> UiState<'a> {
copy_osc(raw);
(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::Log(state), UiEvent::Copy) => {
let raw = app