some UI updates

This commit is contained in:
Michael Zhang 2021-03-11 04:57:47 -06:00
parent 4e3d2e63f3
commit ba5d07db91
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
9 changed files with 192 additions and 64 deletions

View file

@ -199,13 +199,13 @@ impl ClientAuthenticated {
mailbox: mailbox.as_ref().to_owned(),
};
let stream = self.execute(cmd).await?;
let (done, data) = stream.wait().await?;
let (_, data) = stream.wait().await?;
for resp in data {
debug!("execute called returned: {:?}", resp);
}
// nuke the capabilities cache
self.nuke_capabilities();
// self.nuke_capabilities();
Ok(())
}
@ -272,12 +272,12 @@ impl ClientAuthenticated {
let sender = self.sender();
Ok(IdleToken { stream, sender })
}
fn nuke_capabilities(&mut self) {
// TODO: do something here
}
}
/// A token that represents an idling connection.
///
/// Dropping this token indicates that the idling should be completed, and the DONE command will be
/// sent to the server as a result.
#[cfg(feature = "rfc2177-idle")]
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
pub struct IdleToken {

View file

@ -109,8 +109,12 @@ pub enum FetchItems {
All,
Fast,
Full,
Items(Vec<FetchAttr>),
}
#[derive(Clone, Debug)]
pub enum FetchAttr {}
impl fmt::Display for FetchItems {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use FetchItems::*;
@ -118,6 +122,7 @@ impl fmt::Display for FetchItems {
All => write!(f, "ALL"),
Fast => write!(f, "FAST"),
Full => write!(f, "FULL"),
FetchAttr => write!(f, ""),
}
}
}

View file

@ -45,76 +45,83 @@ pub fn parse_response(s: impl AsRef<str>) -> ParseResult<Response> {
fn build_response(pair: Pair<Rule>) -> Response {
assert!(matches!(pair.as_rule(), Rule::response));
let pair = unwrap1(pair);
match pair.as_rule() {
Rule::response_done => build_response_done(pair),
Rule::response_data => build_response_data(pair),
Rule::continue_req => build_continue_req(pair),
_ => unreachable!("{:#?}", pair),
}
}
fn build_response_done(pair: Pair<Rule>) -> Response {
assert!(matches!(pair.as_rule(), Rule::response_done));
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::response_done => {
Rule::response_tagged => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::response_tagged => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
let tag = pair.as_str().to_owned();
let tag = pair.as_str().to_owned();
let pair = pairs.next().unwrap();
let (status, code, information) = build_resp_cond_state(pair);
Response::Done(ResponseDone {
tag,
status,
code,
information,
})
}
_ => unreachable!("{:#?}", pair),
}
}
Rule::response_data => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::resp_cond_state => {
let (status, code, information) = build_resp_cond_state(pair);
Response::Data(ResponseData {
status,
code,
information,
})
}
Rule::mailbox_data => Response::MailboxData(build_mailbox_data(pair)),
Rule::capability_data => Response::Capabilities(build_capabilities(pair)),
Rule::message_data => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
let seq: u32 = build_number(pair);
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::message_data_expunge => Response::Expunge(seq),
Rule::message_data_fetch => {
let mut pairs = pair.into_inner();
let msg_att = pairs.next().unwrap();
let attrs = msg_att.into_inner().map(build_msg_att).collect();
Response::Fetch(seq, attrs)
}
_ => unreachable!("{:#?}", pair),
}
}
_ => unreachable!("{:#?}", pair),
}
}
Rule::continue_req => {
let (code, s) = build_resp_text(unwrap1(pair));
Response::Continue {
let (status, code, information) = build_resp_cond_state(pair);
Response::Done(ResponseDone {
tag,
status,
code,
information: Some(s),
information,
})
}
_ => unreachable!("{:#?}", pair),
}
}
fn build_response_data(pair: Pair<Rule>) -> Response {
assert!(matches!(pair.as_rule(), Rule::response_data));
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::resp_cond_state => {
let (status, code, information) = build_resp_cond_state(pair);
Response::Data(ResponseData {
status,
code,
information,
})
}
Rule::mailbox_data => Response::MailboxData(build_mailbox_data(pair)),
Rule::capability_data => Response::Capabilities(build_capabilities(pair)),
Rule::message_data => {
let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap();
let seq: u32 = build_number(pair);
let pair = pairs.next().unwrap();
match pair.as_rule() {
Rule::message_data_expunge => Response::Expunge(seq),
Rule::message_data_fetch => {
let mut pairs = pair.into_inner();
let msg_att = pairs.next().unwrap();
let attrs = msg_att.into_inner().map(build_msg_att).collect();
Response::Fetch(seq, attrs)
}
_ => unreachable!("{:#?}", pair),
}
}
_ => unreachable!("{:#?}", pair),
}
}
fn build_continue_req(pair: Pair<Rule>) -> Response {
assert!(matches!(pair.as_rule(), Rule::continue_req));
let (code, s) = build_resp_text(unwrap1(pair));
Response::Continue {
code,
information: Some(s),
}
}
fn build_resp_text(pair: Pair<Rule>) -> (Option<ResponseCode>, String) {
assert!(matches!(pair.as_rule(), Rule::resp_text));
let mut pairs = pair.into_inner();

View file

@ -18,6 +18,8 @@ design ideas
- for ex: v1 has `{ x: Int }`, v2 has `{ [deprecated] x: Int, x2: Float }` and v3 has `{ x2: Float }`
this means v1 -> v2 upgrade can be done automatically but because there are _any_ pending deprecated values being used
it's not allowed to automatically upgrade to v3
- imap repl? or more realistically gluon repl that has an imap interface
- basically lets me debug imap commands on-the-spot, with the current connection
imap routine
---

View file

@ -59,6 +59,9 @@ async fn run(opt: Opt) -> Result<()> {
// send messages from the mail thread to the UI thread
let (mail2ui_tx, mail2ui_rx) = mpsc::unbounded_channel();
// send messages from the UI thread to the vm thread
let (ui2vm_tx, ui2vm_rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
let config_update = config_update.clone();
mail::run_mail(config_update, ui2mail_rx, mail2ui_tx)
@ -67,7 +70,7 @@ async fn run(opt: Opt) -> Result<()> {
});
if !opt.headless {
run_ui(exit_tx, mail2ui_rx);
run_ui(exit_tx, mail2ui_rx, ui2vm_tx);
}
exit_rx.recv().await;
@ -80,7 +83,11 @@ async fn run(opt: Opt) -> Result<()> {
}
// Spawns the entire UI in a different thread, since it must be thread-local
fn run_ui(exit_tx: mpsc::Sender<()>, mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>) {
fn run_ui(
exit_tx: mpsc::Sender<()>,
mail2ui_rx: mpsc::UnboundedReceiver<MailEvent>,
ui2vm_tx: mpsc::UnboundedSender<()>,
) {
let stdout = std::io::stdout();
let rt = RuntimeBuilder::new_current_thread()

View file

@ -3,6 +3,8 @@
use anyhow::Result;
use gluon::{import::add_extern_module, ThreadExt};
use crate::ui::{UiCommand, UiUpdate};
/// Creates a VM for running scripts
pub async fn create_script_vm() -> Result<()> {
let vm = gluon::new_vm_async().await;

9
src/ui/messages.rs Normal file
View file

@ -0,0 +1,9 @@
/// Commands that are sent from the UI to the scripting VM
pub enum UiCommand {
HSplit,
VSplit,
OpenMessage,
}
/// Updates that are sent from the scripting VM to the UI
pub enum UiUpdate {}

View file

@ -4,8 +4,11 @@ mod colon_prompt;
mod input;
mod keybinds;
mod mail_tab;
mod messages;
mod windows;
use std::any::Any;
use std::collections::HashMap;
use std::io::Stdout;
use std::mem;
use std::sync::{
@ -39,6 +42,8 @@ use crate::mail::{EmailMetadata, MailEvent};
use self::colon_prompt::ColonPrompt;
use self::input::{BaseInputHandler, HandlesInput, InputResult};
use self::mail_tab::MailTabState;
pub(crate) use self::messages::*;
use self::windows::*;
pub(crate) type FrameType<'a, 'b> = Frame<'a, CrosstermBackend<&'b mut Stdout>>;
pub(crate) type TermType<'a, 'b> = &'b mut Terminal<CrosstermBackend<&'a mut Stdout>>;
@ -65,6 +70,13 @@ pub async fn run_ui(
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()
@ -78,7 +90,13 @@ pub async fn run_ui(
.split(f.size());
// this is the title bar
let titles = vec!["email"].into_iter().map(Spans::from).collect();
// 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]);

78
src/ui/windows.rs Normal file
View file

@ -0,0 +1,78 @@
use std::collections::{HashMap, HashSet, VecDeque};
use tui::layout::Rect;
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct LayoutId(usize);
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct PageId(usize);
#[derive(Default)]
pub struct WindowLayout {
ctr: usize,
currently_active: Option<LayoutId>,
ids: HashMap<LayoutId, PageId>,
page_order: Vec<PageId>,
pages: HashMap<PageId, PageGraph>,
layout_cache: HashMap<PageId, HashMap<LayoutId, Rect>>,
}
impl WindowLayout {
/// Adds a new page to the list
pub fn new_page(&mut self) -> (LayoutId, PageId) {
let id = LayoutId(self.ctr);
self.ctr += 1;
let pg = PageGraph::new(id);
let pid = PageId(self.ctr);
self.ctr += 1;
self.pages.insert(pid, pg);
self.page_order.push(pid);
(id, pid)
}
pub fn list_pages(&self) -> &[PageId] {
&self.page_order
}
/// Get a set of all windows visible on the current page
pub fn visible_windows(&self) -> HashMap<LayoutId, Rect> {
let mut map = HashMap::new();
if let Some(page) = self
.currently_active
.as_ref()
.and_then(|id| self.ids.get(id))
.and_then(|pid| self.pages.get(pid))
{
let mut q = VecDeque::new();
q.push_back(page.root);
while !q.is_empty() {
let front = q.pop_front().expect("not empty");
}
}
map
}
}
struct PageGraph {
root: LayoutId,
adj: HashMap<LayoutId, HashSet<(LayoutId, Dir)>>,
}
enum Dir {
H,
V,
}
impl PageGraph {
pub fn new(id: LayoutId) -> Self {
PageGraph {
root: id,
adj: HashMap::new(),
}
}
}