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",
"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",

View file

@ -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"

View file

@ -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::<SearchEmailsQuery>());
// 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::<SearchEmailsQuery>());
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<ThreadedEnvelope<'_>, u8>,
parent: ThreadedEnvelope<'_>,
pad: String,
weight: u8,
) -> std::io::Result<()> {
@ -234,7 +254,50 @@ pub fn write_tree(
})
.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();
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 = "