From 2eff215934d0a3c6dda586f1a42bf5e048c315f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Tue, 21 May 2024 15:25:24 +0200 Subject: [PATCH] wip: style thread tree using crossterm --- Cargo.lock | 4 + Cargo.toml | 1 + src/email/envelope/command/thread.rs | 177 +++++++++++++++++++-------- 3 files changed, 134 insertions(+), 48 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fc00013..a6728b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1006,7 +1006,10 @@ dependencies = [ "bitflags 2.5.0", "crossterm_winapi", "libc", + "mio", "parking_lot 0.12.1", + "signal-hook", + "signal-hook-mio", "winapi", ] @@ -2063,6 +2066,7 @@ dependencies = [ "color-eyre", "comfy-table", "console", + "crossterm 0.27.0", "dirs 4.0.0", "email-lib", "email_address", diff --git a/Cargo.toml b/Cargo.toml index 95a07de..862deb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,7 @@ clap_mangen = "0.2" color-eyre = "0.6.3" comfy-table = "7.1.1" console = "0.15.2" +crossterm = "0.27" dirs = "4" email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "tracing"] } email_address = "0.2.4" diff --git a/src/email/envelope/command/thread.rs b/src/email/envelope/command/thread.rs index 8202ba7..cad69f0 100644 --- a/src/email/envelope/command/thread.rs +++ b/src/email/envelope/command/thread.rs @@ -1,10 +1,16 @@ -use ariadne::{Color, Label, Report, ReportKind, Source}; +use ariadne::{Label, Report, ReportKind, Source}; use clap::Parser; use color_eyre::Result; +use crossterm::{ + cursor::{self, MoveToColumn}, + style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor}, + terminal, ExecutableCommand, +}; use email::{ + account::config::AccountConfig, backend::feature::BackendFeatureSource, email::search_query, - envelope::list::ListEnvelopesOptions, + envelope::{list::ListEnvelopesOptions, ThreadedEnvelope}, search_query::{filter::SearchEmailsFilterQuery, SearchEmailsQuery}, }; use petgraph::{graphmap::DiGraphMap, visit::IntoNodeIdentifiers, Direction}; @@ -172,42 +178,55 @@ impl ThreadEnvelopesCommand { ) .await?; - // let query = self - // .query - // .map(|query| query.join(" ").parse::()); - // let query = match query { - // None => None, - // Some(Ok(query)) => Some(query), - // Some(Err(main_err)) => { - // let source = "query"; - // let search_query::error::Error::ParseError(errs, query) = &main_err; - // for err in errs { - // Report::build(ReportKind::Error, source, err.span().start) - // .with_message(main_err.to_string()) - // .with_label( - // Label::new((source, err.span().into_range())) - // .with_message(err.reason().to_string()) - // .with_color(Color::Red), - // ) - // .finish() - // .eprint((source, Source::from(&query))) - // .unwrap(); - // } + let query = self + .query + .map(|query| query.join(" ").parse::()); + let query = match query { + None => None, + Some(Ok(query)) => Some(query), + Some(Err(main_err)) => { + let source = "query"; + let search_query::error::Error::ParseError(errs, query) = &main_err; + for err in errs { + Report::build(ReportKind::Error, source, err.span().start) + .with_message(main_err.to_string()) + .with_label( + Label::new((source, err.span().into_range())) + .with_message(err.reason().to_string()) + .with_color(ariadne::Color::Red), + ) + .finish() + .eprint((source, Source::from(&query))) + .unwrap(); + } - // exit(0) - // } - // }; + exit(0) + } + }; let opts = ListEnvelopesOptions { page, page_size, - query: None, + query, }; let envelopes = backend.thread_envelopes(folder, opts).await?; let mut stdout = std::io::stdout(); - write_tree(&mut stdout, envelopes.graph(), "root", String::new(), 0)?; + write_tree( + &account_config, + &mut stdout, + envelopes.graph(), + ThreadedEnvelope { + id: "0", + message_id: "0", + from: "", + subject: "", + date: Default::default(), + }, + String::new(), + 0, + )?; stdout.flush()?; // printer.print_table(envelopes, self.table_max_width)?; @@ -217,9 +236,10 @@ impl ThreadEnvelopesCommand { } pub fn write_tree( + config: &AccountConfig, w: &mut impl std::io::Write, - graph: &DiGraphMap<&str, u8>, - parent: &str, + graph: &DiGraphMap, u8>, + parent: ThreadedEnvelope<'_>, pad: String, weight: u8, ) -> std::io::Result<()> { @@ -234,7 +254,50 @@ pub fn write_tree( }) .collect::>(); - writeln!(w, "{parent}")?; + if parent.id == "0" { + w.execute(Print("root"))?; + } else { + w.execute(SetForegroundColor(Color::Red))? + .execute(Print(parent.id))? + .execute(SetForegroundColor(Color::DarkGrey))? + .execute(Print(") "))? + .execute(ResetColor)?; + + if !parent.subject.is_empty() { + w.execute(SetForegroundColor(Color::Green))? + .execute(Print(parent.subject))? + .execute(ResetColor)? + .execute(Print(" "))?; + } + + if !parent.from.is_empty() { + w.execute(SetForegroundColor(Color::DarkGrey))? + .execute(Print("<"))? + .execute(SetForegroundColor(Color::Blue))? + .execute(Print(parent.from))? + .execute(SetForegroundColor(Color::DarkGrey))? + .execute(Print(">"))? + .execute(ResetColor)?; + } + + let date = parent.format_date(config); + let cursor_date_begin_col = terminal::size()?.0 - date.len() as u16; + + w.execute(Print(" "))? + .execute(SetForegroundColor(Color::DarkGrey))? + .execute(Print("ยท".repeat( + (cursor_date_begin_col - cursor::position()?.0 - 1) as usize, + )))? + .execute(ResetColor)? + .execute(Print(" "))?; + + w.execute(MoveToColumn(terminal::size()?.0 - date.len() as u16))? + .execute(SetForegroundColor(Color::DarkYellow))? + .execute(Print(date))? + .execute(ResetColor)?; + } + + writeln!(w)?; let edges_count = edges.len(); for (i, b) in edges.into_iter().enumerate() { @@ -244,9 +307,11 @@ pub fn write_tree( } else { ('โ”‚', 'โ”œ') }; + write!(w, "{pad}{y}โ”€ ")?; + let pad = format!("{pad}{x} "); - write_tree(w, graph, b, pad, weight + 1)?; + write_tree(config, w, graph, b, pad, weight + 1)?; } Ok(()) @@ -254,19 +319,33 @@ pub fn write_tree( #[cfg(test)] mod test { + use email::{account::config::AccountConfig, envelope::ThreadedEnvelope}; use petgraph::graphmap::DiGraphMap; use super::write_tree; + macro_rules! e { + ($id:literal) => { + ThreadedEnvelope { + id: $id, + message_id: $id, + from: "", + subject: "", + date: Default::default(), + } + }; + } + #[test] fn tree_1() { + let config = AccountConfig::default(); let mut buf = Vec::new(); let mut graph = DiGraphMap::new(); - graph.add_edge("0", "1", 0); - graph.add_edge("0", "2", 0); - graph.add_edge("0", "3", 0); + graph.add_edge(e!("0"), e!("1"), 0); + graph.add_edge(e!("0"), e!("2"), 0); + graph.add_edge(e!("0"), e!("3"), 0); - write_tree(&mut buf, &graph, "0", String::new(), 0).unwrap(); + write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); let buf = String::from_utf8_lossy(&buf); let expected = " @@ -280,13 +359,14 @@ mod test { #[test] fn tree_2() { + let config = AccountConfig::default(); let mut buf = Vec::new(); let mut graph = DiGraphMap::new(); - graph.add_edge("0", "1", 0); - graph.add_edge("1", "2", 1); - graph.add_edge("1", "3", 1); + graph.add_edge(e!("0"), e!("1"), 0); + graph.add_edge(e!("1"), e!("2"), 1); + graph.add_edge(e!("1"), e!("3"), 1); - write_tree(&mut buf, &graph, "0", String::new(), 0).unwrap(); + write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); let buf = String::from_utf8_lossy(&buf); let expected = " @@ -300,17 +380,18 @@ mod test { #[test] fn tree_3() { + let config = AccountConfig::default(); let mut buf = Vec::new(); let mut graph = DiGraphMap::new(); - graph.add_edge("0", "1", 0); - graph.add_edge("1", "2", 1); - graph.add_edge("2", "22", 2); - graph.add_edge("1", "3", 1); - graph.add_edge("0", "4", 0); - graph.add_edge("4", "5", 1); - graph.add_edge("5", "6", 2); + graph.add_edge(e!("0"), e!("1"), 0); + graph.add_edge(e!("1"), e!("2"), 1); + graph.add_edge(e!("2"), e!("22"), 2); + graph.add_edge(e!("1"), e!("3"), 1); + graph.add_edge(e!("0"), e!("4"), 0); + graph.add_edge(e!("4"), e!("5"), 1); + graph.add_edge(e!("5"), e!("6"), 2); - write_tree(&mut buf, &graph, "0", String::new(), 0).unwrap(); + write_tree(&config, &mut buf, &graph, e!("0"), String::new(), 0).unwrap(); let buf = String::from_utf8_lossy(&buf); let expected = "