make the table look a bit nicer

This commit is contained in:
Michael Zhang 2021-03-08 18:26:18 -06:00
parent 9a58fe6d58
commit 46567c43e1
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
6 changed files with 101 additions and 54 deletions

View file

@ -45,7 +45,9 @@ use tokio_rustls::{
}; };
use crate::command::{Command, FetchItems, SearchCriteria}; use crate::command::{Command, FetchItems, SearchCriteria};
use crate::response::{AttributeValue, MailboxData, Response, ResponseData, ResponseDone}; use crate::response::{
AttributeValue, Envelope, MailboxData, Response, ResponseData, ResponseDone,
};
pub use self::inner::{Client, ResponseStream}; pub use self::inner::{Client, ResponseStream};
@ -199,7 +201,7 @@ impl ClientAuthenticated {
} }
/// Runs the UID FETCH command /// Runs the UID FETCH command
pub async fn uid_fetch(&mut self, uids: &[u32]) -> Result<Vec<(u32, Vec<AttributeValue>)>> { pub async fn uid_fetch(&mut self, uids: &[u32]) -> Result<Vec<Envelope>> {
let cmd = Command::UidFetch { let cmd = Command::UidFetch {
uids: uids.to_vec(), uids: uids.to_vec(),
items: FetchItems::All, items: FetchItems::All,
@ -210,7 +212,10 @@ impl ClientAuthenticated {
Ok(data Ok(data
.into_iter() .into_iter()
.filter_map(|resp| match resp { .filter_map(|resp| match resp {
Response::Fetch(n, attrs) => Some((n, attrs)), Response::Fetch(n, attrs) => attrs.into_iter().find_map(|attr| match attr {
AttributeValue::Envelope(envelope) => Some(envelope),
_ => None,
}),
_ => None, _ => None,
}) })
.collect()) .collect())

View file

@ -378,11 +378,15 @@ fn build_mailbox_list(pair: Pair<Rule>) -> (Vec<String>, Option<String>, String)
}; };
assert!(matches!(pair.as_rule(), Rule::mailbox_list_string)); assert!(matches!(pair.as_rule(), Rule::mailbox_list_string));
let s = build_nstring(pair); let s = build_nstring(unwrap1(pair));
pair = pairs.next().unwrap(); pair = pairs.next().unwrap();
assert!(matches!(pair.as_rule(), Rule::mailbox)); assert!(matches!(pair.as_rule(), Rule::mailbox));
let mailbox = build_string(pair); let mailbox = if pair.as_str().to_lowercase() == "inbox" {
pair.as_str().to_owned()
} else {
build_astring(unwrap1(pair))
};
(flags, s, mailbox) (flags, s, mailbox)
} }
@ -413,6 +417,19 @@ where
pair.as_str().parse::<T>().unwrap() pair.as_str().parse::<T>().unwrap()
} }
fn build_astring(pair: Pair<Rule>) -> String {
assert!(matches!(pair.as_rule(), Rule::astring));
let pair_str = pair.as_str().to_owned();
let mut pairs = pair.into_inner();
let rule = pairs.peek().map(|p| p.as_rule());
if let Some(Rule::string) = rule {
let pair = pairs.next().unwrap();
build_string(pair)
} else {
pair_str
}
}
/// Wrapper around [build_string][1], except return None for the `nil` case /// Wrapper around [build_string][1], except return None for the `nil` case
/// ///
/// [1]: self::build_string /// [1]: self::build_string
@ -434,7 +451,12 @@ fn build_string(pair: Pair<Rule>) -> String {
match pair.as_rule() { match pair.as_rule() {
Rule::literal => build_literal(pair), Rule::literal => build_literal(pair),
// TODO: escaping stuff? // TODO: escaping stuff?
Rule::quoted => pair.as_str().trim_start_matches("\"").trim_end_matches("\"").replace("\\\"", "\"").to_owned(), Rule::quoted => pair
.as_str()
.trim_start_matches("\"")
.trim_end_matches("\"")
.replace("\\\"", "\"")
.to_owned(),
_ => unreachable!(), _ => unreachable!(),
} }
} }

View file

@ -76,7 +76,9 @@ mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
mailbox_data_search = { ^"SEARCH" ~ (sp ~ nz_number)* } mailbox_data_search = { ^"SEARCH" ~ (sp ~ nz_number)* }
mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox } mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox }
mailbox_list_flags = { "(" ~ mbx_list_flags* ~ ")" } mailbox_list_flags = { "(" ~ mbx_list_flags* ~ ")" }
mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil } // TODO: technically this should only be 1 quoted char
// mailbox_list_string = { dquote ~ quoted_char ~ dquote | nil }
mailbox_list_string = ${ nstring }
mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* } mbx_list_flags = { (mbx_list_oflag ~ sp)* ~ mbx_list_sflag ~ (sp ~ mbx_list_oflag)* | mbx_list_oflag ~ (sp ~ mbx_list_oflag)* }
mbx_list_oflag = { "\\NoInferiors" | flag_extension } mbx_list_oflag = { "\\NoInferiors" | flag_extension }
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" } mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }

View file

@ -11,6 +11,7 @@ use panorama_imap::{
ClientBuilder, ClientConfig, ClientBuilder, ClientConfig,
}, },
command::Command as ImapCommand, command::Command as ImapCommand,
response::Envelope,
}; };
use tokio::{ use tokio::{
sync::mpsc::{UnboundedReceiver, UnboundedSender}, sync::mpsc::{UnboundedReceiver, UnboundedSender},
@ -37,7 +38,7 @@ pub enum MailEvent {
FolderList(Vec<String>), FolderList(Vec<String>),
/// Got the current list of messages /// Got the current list of messages
MessageList(Vec<String>), MessageList(Vec<Envelope>),
} }
/// Main entrypoint for the mail listener. /// Main entrypoint for the mail listener.
@ -144,11 +145,7 @@ async fn imap_main(acct: MailAccountConfig, mail2ui_tx: UnboundedSender<MailEven
let message_uids = authed.uid_search().await?; let message_uids = authed.uid_search().await?;
let message_list = authed.uid_fetch(&message_uids).await?; let message_list = authed.uid_fetch(&message_uids).await?;
let mut messages = Vec::new(); let _ = mail2ui_tx.send(MailEvent::MessageList(message_list));
for (_, attrs) in message_list {
messages.push(format!("{:?}", attrs));
}
let _ = mail2ui_tx.send(MailEvent::MessageList(messages));
let mut idle_stream = authed.idle().await?; let mut idle_stream = authed.idle().await?;

View file

@ -1,3 +1,4 @@
use panorama_imap::response::Envelope;
use tui::{ use tui::{
buffer::Buffer, buffer::Buffer,
layout::{Constraint, Direction, Layout, Rect}, layout::{Constraint, Direction, Layout, Rect},
@ -8,33 +9,48 @@ use tui::{
use super::FrameType; use super::FrameType;
pub fn render_mail_tab(f: &mut FrameType, area: Rect, folders: &[String], messages: &[String]) { #[derive(Default)]
let chunks = Layout::default() pub struct MailTabState {
.direction(Direction::Horizontal) pub folders: Vec<String>,
.margin(0) pub messages: Vec<Envelope>,
.constraints([Constraint::Length(20), Constraint::Max(5000)]) pub message_list: TableState,
.split(area); }
let items = folders impl MailTabState {
.iter() pub fn render(&mut self, f: &mut FrameType, area: Rect) {
.map(|s| ListItem::new(s.to_owned())) let chunks = Layout::default()
.collect::<Vec<_>>(); .direction(Direction::Horizontal)
.margin(0)
let dirlist = List::new(items) .constraints([Constraint::Length(20), Constraint::Max(5000)])
.block(Block::default().borders(Borders::NONE)) .split(area);
.style(Style::default().fg(Color::White))
.highlight_style(Style::default().add_modifier(Modifier::ITALIC)) // folder list
.highlight_symbol(">>"); let items = self
.folders
let rows = messages .iter()
.iter() .map(|s| ListItem::new(s.to_owned()))
.map(|s| Row::new(vec![s.as_ref()])) .collect::<Vec<_>>();
.collect::<Vec<_>>();
let table = Table::new(rows) let dirlist = List::new(items)
.style(Style::default().fg(Color::White)) .block(Block::default().borders(Borders::NONE))
.widths(&[Constraint::Max(5000)]) .style(Style::default().fg(Color::White))
.highlight_style(Style::default().add_modifier(Modifier::BOLD)); .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
.highlight_symbol(">>");
f.render_widget(dirlist, chunks[0]);
f.render_widget(table, chunks[1]); // message list table
let rows = self
.messages
.iter()
.map(|s| Row::new(vec![s.subject.clone().unwrap_or_default()]))
.collect::<Vec<_>>();
let table = Table::new(rows)
.style(Style::default().fg(Color::White))
.widths(&[Constraint::Max(5000)])
.header(Row::new(vec!["Subject"]).style(Style::default().add_modifier(Modifier::BOLD)))
.highlight_style(Style::default().fg(Color::Black).bg(Color::LightBlue));
f.render_widget(dirlist, chunks[0]);
f.render_stateful_widget(table, chunks[1], &mut self.message_list);
}
} }

View file

@ -25,7 +25,7 @@ use tui::{
use crate::mail::MailEvent; use crate::mail::MailEvent;
use self::mail_tab::render_mail_tab; use self::mail_tab::MailTabState;
pub(crate) type FrameType<'a, 'b> = Frame<'a, CrosstermBackend<&'b mut Stdout>>; pub(crate) type FrameType<'a, 'b> = Frame<'a, CrosstermBackend<&'b mut Stdout>>;
@ -43,8 +43,7 @@ pub async fn run_ui(
let backend = CrosstermBackend::new(&mut stdout); let backend = CrosstermBackend::new(&mut stdout);
let mut term = Terminal::new(backend)?; let mut term = Terminal::new(backend)?;
let mut folders = Vec::<String>::new(); let mut mail_tab = MailTabState::default();
let mut messages = Vec::<String>::new();
loop { loop {
term.draw(|f| { term.draw(|f| {
@ -59,19 +58,25 @@ pub async fn run_ui(
let tabs = Tabs::new(titles); let tabs = Tabs::new(titles);
f.render_widget(tabs, chunks[0]); f.render_widget(tabs, chunks[0]);
render_mail_tab(f, chunks[1], &folders, &messages); mail_tab.render(f, chunks[1]);
// render_mail_tab(f, chunks[1], &folders, &messages);
})?; })?;
let event = if event::poll(FRAME_DURATION)? { let event = if event::poll(FRAME_DURATION)? {
let event = event::read()?; let event = event::read()?;
// table.update(&event); // table.update(&event);
if let Event::Key(KeyEvent { if let Event::Key(KeyEvent { code, .. }) = event {
code: KeyCode::Char('q'), let selected = mail_tab.message_list.selected();
.. let len = mail_tab.messages.len();
}) = event let seln = selected.map(|x| if x < len - 1 { x + 1 } else { x }).unwrap_or(0);
{ let selp = selected.map(|x| if x > 0 { x - 1 } else { 0 }).unwrap_or(0);
break; match code {
KeyCode::Char('q') => break,
KeyCode::Char('j') => mail_tab.message_list.select(Some(seln)),
KeyCode::Char('k') => mail_tab.message_list.select(Some(selp)),
_ => {}
}
} }
Some(event) Some(event)
@ -87,10 +92,10 @@ pub async fn run_ui(
match mail_evt { match mail_evt {
MailEvent::FolderList(new_folders) => { MailEvent::FolderList(new_folders) => {
folders = new_folders; mail_tab.folders = new_folders;
} }
MailEvent::MessageList(new_messages) => { MailEvent::MessageList(new_messages) => {
messages = new_messages; mail_tab.messages = new_messages;
} }
} }
} }