make the table look a bit nicer
This commit is contained in:
parent
9a58fe6d58
commit
46567c43e1
6 changed files with 101 additions and 54 deletions
|
@ -45,7 +45,9 @@ use tokio_rustls::{
|
|||
};
|
||||
|
||||
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};
|
||||
|
||||
|
@ -199,7 +201,7 @@ impl ClientAuthenticated {
|
|||
}
|
||||
|
||||
/// 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 {
|
||||
uids: uids.to_vec(),
|
||||
items: FetchItems::All,
|
||||
|
@ -210,7 +212,10 @@ impl ClientAuthenticated {
|
|||
Ok(data
|
||||
.into_iter()
|
||||
.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,
|
||||
})
|
||||
.collect())
|
||||
|
|
|
@ -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));
|
||||
let s = build_nstring(pair);
|
||||
let s = build_nstring(unwrap1(pair));
|
||||
|
||||
pair = pairs.next().unwrap();
|
||||
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)
|
||||
}
|
||||
|
@ -413,6 +417,19 @@ where
|
|||
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
|
||||
///
|
||||
/// [1]: self::build_string
|
||||
|
@ -434,7 +451,12 @@ fn build_string(pair: Pair<Rule>) -> String {
|
|||
match pair.as_rule() {
|
||||
Rule::literal => build_literal(pair),
|
||||
// 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!(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,7 +76,9 @@ mailbox_data_recent = { number ~ sp ~ ^"RECENT" }
|
|||
mailbox_data_search = { ^"SEARCH" ~ (sp ~ nz_number)* }
|
||||
mailbox_list = { mailbox_list_flags ~ sp ~ mailbox_list_string ~ sp ~ mailbox }
|
||||
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_oflag = { "\\NoInferiors" | flag_extension }
|
||||
mbx_list_sflag = { "\\NoSelect" | "\\Marked" | "\\Unmarked" }
|
||||
|
|
|
@ -11,6 +11,7 @@ use panorama_imap::{
|
|||
ClientBuilder, ClientConfig,
|
||||
},
|
||||
command::Command as ImapCommand,
|
||||
response::Envelope,
|
||||
};
|
||||
use tokio::{
|
||||
sync::mpsc::{UnboundedReceiver, UnboundedSender},
|
||||
|
@ -37,7 +38,7 @@ pub enum MailEvent {
|
|||
FolderList(Vec<String>),
|
||||
|
||||
/// Got the current list of messages
|
||||
MessageList(Vec<String>),
|
||||
MessageList(Vec<Envelope>),
|
||||
}
|
||||
|
||||
/// 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_list = authed.uid_fetch(&message_uids).await?;
|
||||
let mut messages = Vec::new();
|
||||
for (_, attrs) in message_list {
|
||||
messages.push(format!("{:?}", attrs));
|
||||
}
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageList(messages));
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageList(message_list));
|
||||
|
||||
let mut idle_stream = authed.idle().await?;
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use panorama_imap::response::Envelope;
|
||||
use tui::{
|
||||
buffer::Buffer,
|
||||
layout::{Constraint, Direction, Layout, Rect},
|
||||
|
@ -8,33 +9,48 @@ use tui::{
|
|||
|
||||
use super::FrameType;
|
||||
|
||||
pub fn render_mail_tab(f: &mut FrameType, area: Rect, folders: &[String], messages: &[String]) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.margin(0)
|
||||
.constraints([Constraint::Length(20), Constraint::Max(5000)])
|
||||
.split(area);
|
||||
|
||||
let items = folders
|
||||
.iter()
|
||||
.map(|s| ListItem::new(s.to_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let dirlist = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE))
|
||||
.style(Style::default().fg(Color::White))
|
||||
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
.highlight_symbol(">>");
|
||||
|
||||
let rows = messages
|
||||
.iter()
|
||||
.map(|s| Row::new(vec![s.as_ref()]))
|
||||
.collect::<Vec<_>>();
|
||||
let table = Table::new(rows)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.widths(&[Constraint::Max(5000)])
|
||||
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||
|
||||
f.render_widget(dirlist, chunks[0]);
|
||||
f.render_widget(table, chunks[1]);
|
||||
#[derive(Default)]
|
||||
pub struct MailTabState {
|
||||
pub folders: Vec<String>,
|
||||
pub messages: Vec<Envelope>,
|
||||
pub message_list: TableState,
|
||||
}
|
||||
|
||||
impl MailTabState {
|
||||
pub fn render(&mut self, f: &mut FrameType, area: Rect) {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Horizontal)
|
||||
.margin(0)
|
||||
.constraints([Constraint::Length(20), Constraint::Max(5000)])
|
||||
.split(area);
|
||||
|
||||
// folder list
|
||||
let items = self
|
||||
.folders
|
||||
.iter()
|
||||
.map(|s| ListItem::new(s.to_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let dirlist = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE))
|
||||
.style(Style::default().fg(Color::White))
|
||||
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||
.highlight_symbol(">>");
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use tui::{
|
|||
|
||||
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>>;
|
||||
|
||||
|
@ -43,8 +43,7 @@ pub async fn run_ui(
|
|||
let backend = CrosstermBackend::new(&mut stdout);
|
||||
let mut term = Terminal::new(backend)?;
|
||||
|
||||
let mut folders = Vec::<String>::new();
|
||||
let mut messages = Vec::<String>::new();
|
||||
let mut mail_tab = MailTabState::default();
|
||||
|
||||
loop {
|
||||
term.draw(|f| {
|
||||
|
@ -59,19 +58,25 @@ pub async fn run_ui(
|
|||
let tabs = Tabs::new(titles);
|
||||
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 = event::read()?;
|
||||
// table.update(&event);
|
||||
|
||||
if let Event::Key(KeyEvent {
|
||||
code: KeyCode::Char('q'),
|
||||
..
|
||||
}) = event
|
||||
{
|
||||
break;
|
||||
if let Event::Key(KeyEvent { code, .. }) = event {
|
||||
let selected = mail_tab.message_list.selected();
|
||||
let len = mail_tab.messages.len();
|
||||
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);
|
||||
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)
|
||||
|
@ -87,10 +92,10 @@ pub async fn run_ui(
|
|||
|
||||
match mail_evt {
|
||||
MailEvent::FolderList(new_folders) => {
|
||||
folders = new_folders;
|
||||
mail_tab.folders = new_folders;
|
||||
}
|
||||
MailEvent::MessageList(new_messages) => {
|
||||
messages = new_messages;
|
||||
mail_tab.messages = new_messages;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue