displaying messages again
This commit is contained in:
parent
9379d06450
commit
e706a252f1
11 changed files with 257 additions and 242 deletions
|
@ -303,9 +303,9 @@ where
|
|||
} else if let Some((tag, cmd, cmd_tx)) = curr_cmd.as_mut() {
|
||||
// we got a response from the server for this command, so send it over the
|
||||
// channel
|
||||
debug!("sending {:?} to tag {}", resp, tag);
|
||||
// debug!("sending {:?} to tag {}", resp, tag);
|
||||
let res = cmd_tx.send(resp);
|
||||
debug!("res1: {:?}", res);
|
||||
// debug!("res1: {:?}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ impl Decoder for ImapCodec {
|
|||
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
let s = std::str::from_utf8(src)?;
|
||||
trace!("codec parsing {:?}", s);
|
||||
// trace!("codec parsing {:?}", s);
|
||||
match parse_streamed_response(s) {
|
||||
Ok((resp, len)) => {
|
||||
src.advance(len);
|
||||
|
|
|
@ -4,6 +4,9 @@ use anyhow::Result;
|
|||
use tempfile::NamedTempFile;
|
||||
use tokio::fs::{self, File, OpenOptions};
|
||||
|
||||
/// A Maildir, as defined by [Daniel J. Bernstein][1].
|
||||
///
|
||||
/// [1]: https://cr.yp.to/proto/maildir.html
|
||||
pub struct Maildir {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
|
|
@ -23,9 +23,12 @@ use super::{MailCommand, MailEvent};
|
|||
|
||||
/// The main sequence of steps for the IMAP thread to follow
|
||||
pub async fn imap_main(
|
||||
acct_name: impl AsRef<str>,
|
||||
acct: MailAccountConfig,
|
||||
mail2ui_tx: UnboundedSender<MailEvent>,
|
||||
) -> Result<()> {
|
||||
let acct_name = acct_name.as_ref().to_owned();
|
||||
|
||||
// loop ensures that the connection is retried after it dies
|
||||
loop {
|
||||
let builder: ClientConfig = ClientBuilder::default()
|
||||
|
@ -68,17 +71,20 @@ pub async fn imap_main(
|
|||
loop {
|
||||
let folder_list = authed.list().await?;
|
||||
debug!("mailbox list: {:?}", folder_list);
|
||||
let _ = mail2ui_tx.send(MailEvent::FolderList(folder_list));
|
||||
let _ = mail2ui_tx.send(MailEvent::FolderList(acct_name.clone(), folder_list));
|
||||
|
||||
let message_uids = authed.uid_search().await?;
|
||||
let message_uids = message_uids.into_iter().take(30).collect::<Vec<_>>();
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageUids(message_uids.clone()));
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageUids(
|
||||
acct_name.clone(),
|
||||
message_uids.clone(),
|
||||
));
|
||||
|
||||
// TODO: make this happen concurrently with the main loop?
|
||||
let mut message_list = authed.uid_fetch(&message_uids).await.unwrap();
|
||||
while let Some((uid, attrs)) = message_list.next().await {
|
||||
let evt = MailEvent::UpdateUid(uid, attrs);
|
||||
debug!("sent {:?}", evt);
|
||||
let evt = MailEvent::UpdateUid(acct_name.clone(), uid, attrs);
|
||||
// debug!("sent {:?}", evt);
|
||||
mail2ui_tx.send(evt);
|
||||
}
|
||||
|
||||
|
@ -110,13 +116,16 @@ pub async fn imap_main(
|
|||
let message_uids = authed.uid_search().await?;
|
||||
let message_uids =
|
||||
message_uids.into_iter().take(20).collect::<Vec<_>>();
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageUids(message_uids.clone()));
|
||||
let _ = mail2ui_tx.send(MailEvent::MessageUids(
|
||||
acct_name.clone(),
|
||||
message_uids.clone(),
|
||||
));
|
||||
|
||||
// TODO: make this happen concurrently with the main loop?
|
||||
let mut message_list = authed.uid_fetch(&message_uids).await.unwrap();
|
||||
while let Some((uid, attrs)) = message_list.next().await {
|
||||
let evt = MailEvent::UpdateUid(uid, attrs);
|
||||
debug!("sent {:?}", evt);
|
||||
let evt = MailEvent::UpdateUid(acct_name.clone(), uid, attrs);
|
||||
// debug!("sent {:?}", evt);
|
||||
mail2ui_tx.send(evt);
|
||||
}
|
||||
|
||||
|
|
31
src/mail/event.rs
Normal file
31
src/mail/event.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use panorama_imap::response::{AttributeValue, Envelope};
|
||||
|
||||
/// Possible events returned from the server that should be sent to the UI
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MailEvent {
|
||||
/// Got the list of folders
|
||||
FolderList(String, Vec<String>),
|
||||
|
||||
/// A list of the UIDs in the current mail view
|
||||
MessageUids(String, Vec<u32>),
|
||||
|
||||
/// Update the given UID with the given attribute list
|
||||
UpdateUid(String, u32, Vec<AttributeValue>),
|
||||
|
||||
/// New message came in with given UID
|
||||
NewUid(String, u32),
|
||||
}
|
||||
|
||||
impl MailEvent {
|
||||
/// Retrieves the account name that this event is associated with
|
||||
pub fn acct_name(&self) -> &str {
|
||||
use MailEvent::*;
|
||||
match self {
|
||||
FolderList(name, _)
|
||||
| MessageUids(name, _)
|
||||
| UpdateUid(name, _, _)
|
||||
| NewUid(name, _) => name,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,7 +4,7 @@ use chrono::{DateTime, Local};
|
|||
use panorama_imap::response::*;
|
||||
|
||||
/// A record that describes the metadata of an email as it appears in the UI list
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct EmailMetadata {
|
||||
/// UID if the message has one
|
||||
pub uid: Option<u32>,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
//! Mail
|
||||
|
||||
mod client;
|
||||
mod event;
|
||||
mod metadata;
|
||||
|
||||
use anyhow::Result;
|
||||
|
@ -25,6 +26,7 @@ use tokio_stream::wrappers::WatchStream;
|
|||
|
||||
use crate::config::{Config, ConfigWatcher, ImapAuth, MailAccountConfig, TlsMethod};
|
||||
|
||||
pub use self::event::MailEvent;
|
||||
pub use self::metadata::EmailMetadata;
|
||||
|
||||
/// Command sent to the mail thread by something else (i.e. UI)
|
||||
|
@ -38,26 +40,6 @@ pub enum MailCommand {
|
|||
Raw(ImapCommand),
|
||||
}
|
||||
|
||||
/// Possible events returned from the server that should be sent to the UI
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum MailEvent {
|
||||
/// Got the list of folders
|
||||
FolderList(Vec<String>),
|
||||
|
||||
/// Got the current list of messages
|
||||
MessageList(Vec<Envelope>),
|
||||
|
||||
/// A list of the UIDs in the current mail view
|
||||
MessageUids(Vec<u32>),
|
||||
|
||||
/// Update the given UID with the given attribute list
|
||||
UpdateUid(u32, Vec<AttributeValue>),
|
||||
|
||||
/// New message came in with given UID
|
||||
NewUid(u32),
|
||||
}
|
||||
|
||||
/// Main entrypoint for the mail listener.
|
||||
pub async fn run_mail(
|
||||
mut config_watcher: ConfigWatcher,
|
||||
|
@ -90,7 +72,7 @@ pub async fn run_mail(
|
|||
|
||||
// this loop is to make sure accounts are restarted on error
|
||||
loop {
|
||||
match client::imap_main(acct.clone(), mail2ui_tx.clone()).await {
|
||||
match client::imap_main(&acct_name, acct.clone(), mail2ui_tx.clone()).await {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
error!("IMAP Error: {}", err);
|
||||
|
|
90
src/ui/mail_store.rs
Normal file
90
src/ui/mail_store.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use crate::mail::{EmailMetadata, MailEvent};
|
||||
|
||||
/// UI's view of the currently-known mail-related state of all accounts.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct MailStore {
|
||||
accounts: Arc<RwLock<HashMap<String, Arc<RwLock<MailAccountState>>>>>,
|
||||
}
|
||||
|
||||
impl MailStore {
|
||||
pub fn handle_mail_event(&self, evt: MailEvent) {
|
||||
let acct_name = evt.acct_name().to_owned();
|
||||
|
||||
{
|
||||
let accounts = self.accounts.read();
|
||||
let contains_key = accounts.contains_key(&acct_name);
|
||||
std::mem::drop(accounts);
|
||||
|
||||
if !contains_key {
|
||||
let mut accounts = self.accounts.write();
|
||||
accounts.insert(
|
||||
acct_name.clone(),
|
||||
Arc::new(RwLock::new(MailAccountState::default())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let accounts = self.accounts.read();
|
||||
if let Some(lock) = accounts.get(&acct_name) {
|
||||
let mut state = lock.write();
|
||||
state.update(evt);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_accts(&self) -> Vec<String> {
|
||||
self.accounts.read().keys().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn folders_of(&self, acct_name: impl AsRef<str>) -> Option<Vec<String>> {
|
||||
let accounts = self.accounts.read();
|
||||
let lock = accounts.get(acct_name.as_ref())?;
|
||||
let state = lock.read();
|
||||
Some(state.folders.clone())
|
||||
}
|
||||
|
||||
pub fn messages_of(&self, acct_name: impl AsRef<str>) -> Option<Vec<EmailMetadata>> {
|
||||
let accounts = self.accounts.read();
|
||||
let lock = accounts.get(acct_name.as_ref())?;
|
||||
let state = lock.read();
|
||||
let mut msgs = Vec::new();
|
||||
for uid in state.message_uids.iter() {
|
||||
if let Some(meta) = state.message_map.get(uid) {
|
||||
msgs.push(meta.clone());
|
||||
}
|
||||
}
|
||||
Some(msgs)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MailAccountState {
|
||||
pub folders: Vec<String>,
|
||||
pub message_uids: Vec<u32>,
|
||||
pub message_map: HashMap<u32, EmailMetadata>,
|
||||
}
|
||||
|
||||
impl MailAccountState {
|
||||
pub fn update(&mut self, evt: MailEvent) {
|
||||
match evt {
|
||||
MailEvent::FolderList(_, new_folders) => self.folders = new_folders,
|
||||
MailEvent::MessageUids(_, new_uids) => self.message_uids = new_uids,
|
||||
|
||||
MailEvent::UpdateUid(_, uid, attrs) => {
|
||||
let meta = EmailMetadata::from_attrs(attrs);
|
||||
let uid = meta.uid.unwrap_or(uid);
|
||||
self.message_map.insert(uid, meta);
|
||||
}
|
||||
MailEvent::NewUid(_, uid) => {
|
||||
debug!("new msg!");
|
||||
self.message_uids.push(uid);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// debug!("mail store updated! {:?}", self);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{
|
||||
atomic::{AtomicI8, Ordering},
|
||||
atomic::{AtomicI8, AtomicU32, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
|
@ -19,15 +19,13 @@ use tui::{
|
|||
|
||||
use crate::mail::EmailMetadata;
|
||||
|
||||
use super::{FrameType, HandlesInput, InputResult, TermType, Window, UI};
|
||||
use super::{FrameType, HandlesInput, InputResult, MailStore, TermType, Window, UI};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct MailView {
|
||||
pub folders: Vec<String>,
|
||||
pub message_uids: Vec<u32>,
|
||||
pub message_map: HashMap<u32, EmailMetadata>,
|
||||
pub messages: Vec<Envelope>,
|
||||
pub mail_store: MailStore,
|
||||
pub message_list: TableState,
|
||||
pub selected: Arc<AtomicU32>,
|
||||
pub change: Arc<AtomicI8>,
|
||||
}
|
||||
|
||||
|
@ -61,16 +59,23 @@ impl Window for MailView {
|
|||
.constraints([Constraint::Length(20), Constraint::Max(5000)])
|
||||
.split(area);
|
||||
|
||||
let accts = self.mail_store.iter_accts();
|
||||
|
||||
// folder list
|
||||
let items = self
|
||||
.folders
|
||||
.iter()
|
||||
.map(|s| ListItem::new(s.to_owned()))
|
||||
.collect::<Vec<_>>();
|
||||
let mut items = vec![];
|
||||
for acct in accts.iter() {
|
||||
let result = self.mail_store.folders_of(acct);
|
||||
if let Some(folders) = result {
|
||||
items.push(ListItem::new(acct.to_owned()));
|
||||
for folder in folders {
|
||||
items.push(ListItem::new(format!(" {}", folder)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let dirlist = List::new(items)
|
||||
.block(Block::default().borders(Borders::NONE).title(Span::styled(
|
||||
"ur mom",
|
||||
"hellosu",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)))
|
||||
.style(Style::default().fg(Color::White))
|
||||
|
@ -78,33 +83,57 @@ impl Window for MailView {
|
|||
.highlight_symbol(">>");
|
||||
|
||||
// message list table
|
||||
let mut metas = self
|
||||
.message_uids
|
||||
.iter()
|
||||
.filter_map(|id| self.message_map.get(id))
|
||||
.collect::<Vec<_>>();
|
||||
metas.sort_by_key(|m| m.date);
|
||||
let rows = metas
|
||||
.iter()
|
||||
.rev()
|
||||
.map(|meta| {
|
||||
let mut row = Row::new(vec![
|
||||
String::from(if meta.unread { "\u{2b24}" } else { "" }),
|
||||
meta.uid.map(|u| u.to_string()).unwrap_or_default(),
|
||||
meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(),
|
||||
meta.from.clone(),
|
||||
meta.subject.clone(),
|
||||
]);
|
||||
if meta.unread {
|
||||
row = row.style(
|
||||
Style::default()
|
||||
.fg(Color::LightCyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
// let mut metas = self
|
||||
// .message_uids
|
||||
// .iter()
|
||||
// .filter_map(|id| self.message_map.get(id))
|
||||
// .collect::<Vec<_>>();
|
||||
// metas.sort_by_key(|m| m.date);
|
||||
// let rows = metas
|
||||
// .iter()
|
||||
// .rev()
|
||||
// .map(|meta| {
|
||||
// let mut row = Row::new(vec![
|
||||
// String::from(if meta.unread { "\u{2b24}" } else { "" }),
|
||||
// meta.uid.map(|u| u.to_string()).unwrap_or_default(),
|
||||
// meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(),
|
||||
// meta.from.clone(),
|
||||
// meta.subject.clone(),
|
||||
// ]);
|
||||
// if meta.unread {
|
||||
// row = row.style(
|
||||
// Style::default()
|
||||
// .fg(Color::LightCyan)
|
||||
// .add_modifier(Modifier::BOLD),
|
||||
// );
|
||||
// }
|
||||
// row
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
let mut rows = vec![];
|
||||
for acct in accts.iter() {
|
||||
let result = self.mail_store.messages_of(acct);
|
||||
if let Some(messages) = result {
|
||||
for meta in messages {
|
||||
let mut row = Row::new(vec![
|
||||
String::from(if meta.unread { "\u{2b24}" } else { "" }),
|
||||
meta.uid.map(|u| u.to_string()).unwrap_or_default(),
|
||||
meta.date.map(|d| humanize_timestamp(d)).unwrap_or_default(),
|
||||
meta.from.clone(),
|
||||
meta.subject.clone(),
|
||||
]);
|
||||
if meta.unread {
|
||||
row = row.style(
|
||||
Style::default()
|
||||
.fg(Color::LightCyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
);
|
||||
}
|
||||
rows.push(row);
|
||||
}
|
||||
row
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
}
|
||||
}
|
||||
let table = Table::new(rows)
|
||||
.style(Style::default().fg(Color::White))
|
||||
.widths(&[
|
||||
|
@ -144,32 +173,41 @@ fn humanize_timestamp(date: DateTime<Local>) -> String {
|
|||
}
|
||||
|
||||
impl MailView {
|
||||
pub fn move_down(&mut self) {
|
||||
if self.message_uids.is_empty() {
|
||||
return;
|
||||
}
|
||||
let len = self.message_uids.len();
|
||||
if let Some(selected) = self.message_list.selected() {
|
||||
if selected + 1 < len {
|
||||
self.message_list.select(Some(selected + 1));
|
||||
}
|
||||
} else {
|
||||
self.message_list.select(Some(0));
|
||||
pub fn new(mail_store: MailStore) -> Self {
|
||||
MailView {
|
||||
mail_store,
|
||||
message_list: TableState::default(),
|
||||
selected: Arc::new(AtomicU32::default()),
|
||||
change: Arc::new(AtomicI8::default()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_down(&mut self) {
|
||||
// if self.message_uids.is_empty() {
|
||||
// return;
|
||||
// }
|
||||
// let len = self.message_uids.len();
|
||||
// if let Some(selected) = self.message_list.selected() {
|
||||
// if selected + 1 < len {
|
||||
// self.message_list.select(Some(selected + 1));
|
||||
// }
|
||||
// } else {
|
||||
// self.message_list.select(Some(0));
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn move_up(&mut self) {
|
||||
if self.message_uids.is_empty() {
|
||||
return;
|
||||
}
|
||||
let len = self.message_uids.len();
|
||||
if let Some(selected) = self.message_list.selected() {
|
||||
if selected >= 1 {
|
||||
self.message_list.select(Some(selected - 1));
|
||||
}
|
||||
} else {
|
||||
self.message_list.select(Some(len - 1));
|
||||
}
|
||||
// if self.message_uids.is_empty() {
|
||||
// return;
|
||||
// }
|
||||
// let len = self.message_uids.len();
|
||||
// if let Some(selected) = self.message_list.selected() {
|
||||
// if selected >= 1 {
|
||||
// self.message_list.select(Some(selected - 1));
|
||||
// }
|
||||
// } else {
|
||||
// self.message_list.select(Some(len - 1));
|
||||
// }
|
||||
}
|
||||
|
||||
pub fn update(&mut self) {
|
||||
|
|
157
src/ui/mod.rs
157
src/ui/mod.rs
|
@ -3,6 +3,7 @@
|
|||
mod colon_prompt;
|
||||
mod input;
|
||||
mod keybinds;
|
||||
mod mail_store;
|
||||
mod mail_view;
|
||||
mod messages;
|
||||
mod windows;
|
||||
|
@ -41,6 +42,7 @@ use crate::mail::{EmailMetadata, MailEvent};
|
|||
|
||||
use self::colon_prompt::ColonPrompt;
|
||||
use self::input::{BaseInputHandler, HandlesInput, InputResult};
|
||||
use self::mail_store::MailStore;
|
||||
use self::mail_view::MailView;
|
||||
pub(crate) use self::messages::*;
|
||||
use self::windows::*;
|
||||
|
@ -64,14 +66,17 @@ pub async fn run_ui2(
|
|||
|
||||
let should_exit = Arc::new(AtomicBool::new(false));
|
||||
|
||||
let mail_store = MailStore::default();
|
||||
|
||||
let mut ui = UI {
|
||||
should_exit: should_exit.clone(),
|
||||
window_layout: WindowLayout::default(),
|
||||
windows: HashMap::new(),
|
||||
page_names: HashMap::new(),
|
||||
mail_store: mail_store.clone(),
|
||||
};
|
||||
|
||||
ui.open_window(MailView::default());
|
||||
ui.open_window(MailView::new(mail_store));
|
||||
|
||||
// let mut input_states: Vec<Box<dyn HandlesInput>> = vec![];
|
||||
|
||||
|
@ -118,6 +123,7 @@ pub struct UI {
|
|||
window_layout: WindowLayout,
|
||||
windows: HashMap<LayoutId, Box<dyn Window>>,
|
||||
page_names: HashMap<PageId, String>,
|
||||
mail_store: MailStore,
|
||||
}
|
||||
|
||||
impl UI {
|
||||
|
@ -201,153 +207,6 @@ impl UI {
|
|||
}
|
||||
|
||||
fn process_mail_event(&mut self, evt: MailEvent) {
|
||||
debug!("received mail event: {:?}", evt);
|
||||
self.mail_store.handle_mail_event(evt);
|
||||
}
|
||||
}
|
||||
|
||||
/// Main entrypoint for the UI
|
||||
pub async fn run_ui(
|
||||
mut stdout: Stdout,
|
||||
exit_tx: mpsc::Sender<()>,
|
||||
mut mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>,
|
||||
) -> Result<()> {
|
||||
execute!(stdout, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||
terminal::enable_raw_mode()?;
|
||||
|
||||
let backend = CrosstermBackend::new(&mut stdout);
|
||||
let mut term = Terminal::new(backend)?;
|
||||
let mut mail_tab = MailView::default();
|
||||
|
||||
// state stack for handling inputs
|
||||
let should_exit = Arc::new(AtomicBool::new(false));
|
||||
let mut input_states: Vec<Box<dyn HandlesInput>> = vec![Box::new(BaseInputHandler(
|
||||
should_exit.clone(),
|
||||
mail_tab.change.clone(),
|
||||
))];
|
||||
|
||||
let mut window_layout = WindowLayout::default();
|
||||
let mut page_names = HashMap::new();
|
||||
|
||||
// TODO: have this be configured thru the settings?
|
||||
let (mail_id, mail_page) = window_layout.new_page();
|
||||
page_names.insert(mail_page, "Email");
|
||||
|
||||
while !should_exit.load(Ordering::Relaxed) {
|
||||
term.draw(|f| {
|
||||
let chunks = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
.margin(0)
|
||||
.constraints([
|
||||
Constraint::Length(1),
|
||||
Constraint::Max(5000),
|
||||
Constraint::Length(1),
|
||||
])
|
||||
.split(f.size());
|
||||
|
||||
// this is the title bar
|
||||
// let titles = vec!["email"].into_iter().map(Spans::from).collect();
|
||||
let titles = window_layout
|
||||
.list_pages()
|
||||
.iter()
|
||||
.filter_map(|id| page_names.get(id))
|
||||
.map(|s| Spans::from(*s))
|
||||
.collect();
|
||||
let tabs = Tabs::new(titles);
|
||||
f.render_widget(tabs, chunks[0]);
|
||||
|
||||
// this is the main mail tab
|
||||
// mail_tab.render(f, chunks[1]);
|
||||
|
||||
// this is the status bar
|
||||
if let Some(last_state) = input_states.last() {
|
||||
let downcasted = last_state.downcast_ref::<ColonPrompt>();
|
||||
match downcasted {
|
||||
Some(colon_prompt) => {
|
||||
let status = Block::default().title(vec![
|
||||
Span::styled(":", Style::default().fg(Color::Gray)),
|
||||
Span::raw(&colon_prompt.value),
|
||||
]);
|
||||
f.render_widget(status, chunks[2]);
|
||||
f.set_cursor(colon_prompt.value.len() as u16 + 1, chunks[2].y);
|
||||
}
|
||||
None => {
|
||||
let status = Paragraph::new("hellosu");
|
||||
f.render_widget(status, chunks[2]);
|
||||
}
|
||||
};
|
||||
}
|
||||
})?;
|
||||
|
||||
let event = if event::poll(FRAME_DURATION)? {
|
||||
let event = event::read()?;
|
||||
// table.update(&event);
|
||||
|
||||
if let Event::Key(evt) = event {
|
||||
// handle states in the state stack
|
||||
// although this is written in a for loop, every case except one should break
|
||||
let mut should_pop = false;
|
||||
if let Some(input_state) = input_states.last_mut() {
|
||||
match input_state.handle_key(&mut term, evt)? {
|
||||
InputResult::Ok => {}
|
||||
InputResult::Push(state) => {
|
||||
input_states.push(state);
|
||||
}
|
||||
InputResult::Pop => {
|
||||
should_pop = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if should_pop {
|
||||
input_states.pop();
|
||||
}
|
||||
}
|
||||
|
||||
Some(event)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
select! {
|
||||
mail_evt = mail2ui_rx.recv().fuse() => {
|
||||
debug!("received mail event: {:?}", mail_evt);
|
||||
// TODO: handle case that channel is closed later
|
||||
let mail_evt = mail_evt.unwrap();
|
||||
|
||||
match mail_evt {
|
||||
MailEvent::FolderList(new_folders) => mail_tab.folders = new_folders,
|
||||
MailEvent::MessageList(new_messages) => mail_tab.messages = new_messages,
|
||||
MailEvent::MessageUids(new_uids) => mail_tab.message_uids = new_uids,
|
||||
|
||||
MailEvent::UpdateUid(uid, attrs) => {
|
||||
let meta = EmailMetadata::from_attrs(attrs);
|
||||
let uid = meta.uid.unwrap_or(uid);
|
||||
mail_tab.message_map.insert(uid, meta);
|
||||
}
|
||||
MailEvent::NewUid(uid) => {
|
||||
debug!("new msg!");
|
||||
mail_tab.message_uids.push(uid);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// approx 60fps
|
||||
_ = time::sleep(FRAME_DURATION).fuse() => {}
|
||||
}
|
||||
}
|
||||
|
||||
mem::drop(term);
|
||||
|
||||
execute!(
|
||||
stdout,
|
||||
style::ResetColor,
|
||||
cursor::Show,
|
||||
terminal::LeaveAlternateScreen
|
||||
)?;
|
||||
terminal::disable_raw_mode()?;
|
||||
|
||||
exit_tx.send(()).await?;
|
||||
debug!("sent exit");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -18,6 +18,9 @@ pub trait Window: HandlesInput {
|
|||
fn draw_inactive(&mut self, f: FrameType, area: Rect, ui: &UI) {
|
||||
self.draw(f, area, ui);
|
||||
}
|
||||
|
||||
/// Update function
|
||||
fn update(&mut self) {}
|
||||
}
|
||||
|
||||
downcast_rs::impl_downcast!(Window);
|
||||
|
|
Loading…
Reference in a new issue