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::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())
|
||||||
|
|
|
@ -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!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue