wip: style thread tree using crossterm

This commit is contained in:
Clément DOUIN 2024-05-21 15:25:24 +02:00
parent 55ba892436
commit 2eff215934
No known key found for this signature in database
GPG key ID: 353E4A18EE0FAB72
3 changed files with 134 additions and 48 deletions

4
Cargo.lock generated
View file

@ -1006,7 +1006,10 @@ dependencies = [
"bitflags 2.5.0", "bitflags 2.5.0",
"crossterm_winapi", "crossterm_winapi",
"libc", "libc",
"mio",
"parking_lot 0.12.1", "parking_lot 0.12.1",
"signal-hook",
"signal-hook-mio",
"winapi", "winapi",
] ]
@ -2063,6 +2066,7 @@ dependencies = [
"color-eyre", "color-eyre",
"comfy-table", "comfy-table",
"console", "console",
"crossterm 0.27.0",
"dirs 4.0.0", "dirs 4.0.0",
"email-lib", "email-lib",
"email_address", "email_address",

View file

@ -54,6 +54,7 @@ clap_mangen = "0.2"
color-eyre = "0.6.3" color-eyre = "0.6.3"
comfy-table = "7.1.1" comfy-table = "7.1.1"
console = "0.15.2" console = "0.15.2"
crossterm = "0.27"
dirs = "4" dirs = "4"
email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "tracing"] } email-lib = { version = "=0.24.1", default-features = false, features = ["derive", "tracing"] }
email_address = "0.2.4" email_address = "0.2.4"

View file

@ -1,10 +1,16 @@
use ariadne::{Color, Label, Report, ReportKind, Source}; use ariadne::{Label, Report, ReportKind, Source};
use clap::Parser; use clap::Parser;
use color_eyre::Result; use color_eyre::Result;
use crossterm::{
cursor::{self, MoveToColumn},
style::{Attribute, Color, Print, ResetColor, SetAttribute, SetForegroundColor},
terminal, ExecutableCommand,
};
use email::{ use email::{
account::config::AccountConfig,
backend::feature::BackendFeatureSource, backend::feature::BackendFeatureSource,
email::search_query, email::search_query,
envelope::list::ListEnvelopesOptions, envelope::{list::ListEnvelopesOptions, ThreadedEnvelope},
search_query::{filter::SearchEmailsFilterQuery, SearchEmailsQuery}, search_query::{filter::SearchEmailsFilterQuery, SearchEmailsQuery},
}; };
use petgraph::{graphmap::DiGraphMap, visit::IntoNodeIdentifiers, Direction}; use petgraph::{graphmap::DiGraphMap, visit::IntoNodeIdentifiers, Direction};
@ -172,42 +178,55 @@ impl ThreadEnvelopesCommand {
) )
.await?; .await?;
// let query = self let query = self
// .query .query
// .map(|query| query.join(" ").parse::<SearchEmailsQuery>()); .map(|query| query.join(" ").parse::<SearchEmailsQuery>());
// let query = match query { let query = match query {
// None => None, None => None,
// Some(Ok(query)) => Some(query), Some(Ok(query)) => Some(query),
// Some(Err(main_err)) => { Some(Err(main_err)) => {
// let source = "query"; let source = "query";
// let search_query::error::Error::ParseError(errs, query) = &main_err; let search_query::error::Error::ParseError(errs, query) = &main_err;
// for err in errs { for err in errs {
// Report::build(ReportKind::Error, source, err.span().start) Report::build(ReportKind::Error, source, err.span().start)
// .with_message(main_err.to_string()) .with_message(main_err.to_string())
// .with_label( .with_label(
// Label::new((source, err.span().into_range())) Label::new((source, err.span().into_range()))
// .with_message(err.reason().to_string()) .with_message(err.reason().to_string())
// .with_color(Color::Red), .with_color(ariadne::Color::Red),
// ) )
// .finish() .finish()
// .eprint((source, Source::from(&query))) .eprint((source, Source::from(&query)))
// .unwrap(); .unwrap();
// } }
// exit(0) exit(0)
// } }
// }; };
let opts = ListEnvelopesOptions { let opts = ListEnvelopesOptions {
page, page,
page_size, page_size,
query: None, query,
}; };
let envelopes = backend.thread_envelopes(folder, opts).await?; let envelopes = backend.thread_envelopes(folder, opts).await?;
let mut stdout = std::io::stdout(); 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()?; stdout.flush()?;
// printer.print_table(envelopes, self.table_max_width)?; // printer.print_table(envelopes, self.table_max_width)?;
@ -217,9 +236,10 @@ impl ThreadEnvelopesCommand {
} }
pub fn write_tree( pub fn write_tree(
config: &AccountConfig,
w: &mut impl std::io::Write, w: &mut impl std::io::Write,
graph: &DiGraphMap<&str, u8>, graph: &DiGraphMap<ThreadedEnvelope<'_>, u8>,
parent: &str, parent: ThreadedEnvelope<'_>,
pad: String, pad: String,
weight: u8, weight: u8,
) -> std::io::Result<()> { ) -> std::io::Result<()> {
@ -234,7 +254,50 @@ pub fn write_tree(
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
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(); let edges_count = edges.len();
for (i, b) in edges.into_iter().enumerate() { for (i, b) in edges.into_iter().enumerate() {
@ -244,9 +307,11 @@ pub fn write_tree(
} else { } else {
('│', '├') ('│', '├')
}; };
write!(w, "{pad}{y}─ ")?; write!(w, "{pad}{y}─ ")?;
let pad = format!("{pad}{x} "); let pad = format!("{pad}{x} ");
write_tree(w, graph, b, pad, weight + 1)?; write_tree(config, w, graph, b, pad, weight + 1)?;
} }
Ok(()) Ok(())
@ -254,19 +319,33 @@ pub fn write_tree(
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use email::{account::config::AccountConfig, envelope::ThreadedEnvelope};
use petgraph::graphmap::DiGraphMap; use petgraph::graphmap::DiGraphMap;
use super::write_tree; use super::write_tree;
macro_rules! e {
($id:literal) => {
ThreadedEnvelope {
id: $id,
message_id: $id,
from: "",
subject: "",
date: Default::default(),
}
};
}
#[test] #[test]
fn tree_1() { fn tree_1() {
let config = AccountConfig::default();
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut graph = DiGraphMap::new(); let mut graph = DiGraphMap::new();
graph.add_edge("0", "1", 0); graph.add_edge(e!("0"), e!("1"), 0);
graph.add_edge("0", "2", 0); graph.add_edge(e!("0"), e!("2"), 0);
graph.add_edge("0", "3", 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 buf = String::from_utf8_lossy(&buf);
let expected = " let expected = "
@ -280,13 +359,14 @@ mod test {
#[test] #[test]
fn tree_2() { fn tree_2() {
let config = AccountConfig::default();
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut graph = DiGraphMap::new(); let mut graph = DiGraphMap::new();
graph.add_edge("0", "1", 0); graph.add_edge(e!("0"), e!("1"), 0);
graph.add_edge("1", "2", 1); graph.add_edge(e!("1"), e!("2"), 1);
graph.add_edge("1", "3", 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 buf = String::from_utf8_lossy(&buf);
let expected = " let expected = "
@ -300,17 +380,18 @@ mod test {
#[test] #[test]
fn tree_3() { fn tree_3() {
let config = AccountConfig::default();
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut graph = DiGraphMap::new(); let mut graph = DiGraphMap::new();
graph.add_edge("0", "1", 0); graph.add_edge(e!("0"), e!("1"), 0);
graph.add_edge("1", "2", 1); graph.add_edge(e!("1"), e!("2"), 1);
graph.add_edge("2", "22", 2); graph.add_edge(e!("2"), e!("22"), 2);
graph.add_edge("1", "3", 1); graph.add_edge(e!("1"), e!("3"), 1);
graph.add_edge("0", "4", 0); graph.add_edge(e!("0"), e!("4"), 0);
graph.add_edge("4", "5", 1); graph.add_edge(e!("4"), e!("5"), 1);
graph.add_edge("5", "6", 2); 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 buf = String::from_utf8_lossy(&buf);
let expected = " let expected = "