some UI updates
This commit is contained in:
parent
4e3d2e63f3
commit
ba5d07db91
9 changed files with 192 additions and 64 deletions
|
@ -199,13 +199,13 @@ impl ClientAuthenticated {
|
||||||
mailbox: mailbox.as_ref().to_owned(),
|
mailbox: mailbox.as_ref().to_owned(),
|
||||||
};
|
};
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
let (done, data) = stream.wait().await?;
|
let (_, data) = stream.wait().await?;
|
||||||
for resp in data {
|
for resp in data {
|
||||||
debug!("execute called returned: {:?}", resp);
|
debug!("execute called returned: {:?}", resp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// nuke the capabilities cache
|
// nuke the capabilities cache
|
||||||
self.nuke_capabilities();
|
// self.nuke_capabilities();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -272,12 +272,12 @@ impl ClientAuthenticated {
|
||||||
let sender = self.sender();
|
let sender = self.sender();
|
||||||
Ok(IdleToken { stream, 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(feature = "rfc2177-idle")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "rfc2177-idle")))]
|
||||||
pub struct IdleToken {
|
pub struct IdleToken {
|
||||||
|
|
|
@ -109,8 +109,12 @@ pub enum FetchItems {
|
||||||
All,
|
All,
|
||||||
Fast,
|
Fast,
|
||||||
Full,
|
Full,
|
||||||
|
Items(Vec<FetchAttr>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum FetchAttr {}
|
||||||
|
|
||||||
impl fmt::Display for FetchItems {
|
impl fmt::Display for FetchItems {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
use FetchItems::*;
|
use FetchItems::*;
|
||||||
|
@ -118,6 +122,7 @@ impl fmt::Display for FetchItems {
|
||||||
All => write!(f, "ALL"),
|
All => write!(f, "ALL"),
|
||||||
Fast => write!(f, "FAST"),
|
Fast => write!(f, "FAST"),
|
||||||
Full => write!(f, "FULL"),
|
Full => write!(f, "FULL"),
|
||||||
|
FetchAttr => write!(f, ""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,76 +45,83 @@ pub fn parse_response(s: impl AsRef<str>) -> ParseResult<Response> {
|
||||||
|
|
||||||
fn build_response(pair: Pair<Rule>) -> Response {
|
fn build_response(pair: Pair<Rule>) -> Response {
|
||||||
assert!(matches!(pair.as_rule(), 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 mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
match pair.as_rule() {
|
match pair.as_rule() {
|
||||||
Rule::response_done => {
|
Rule::response_tagged => {
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
match pair.as_rule() {
|
let tag = pair.as_str().to_owned();
|
||||||
Rule::response_tagged => {
|
|
||||||
let mut pairs = pair.into_inner();
|
|
||||||
let pair = pairs.next().unwrap();
|
|
||||||
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();
|
let pair = pairs.next().unwrap();
|
||||||
match pair.as_rule() {
|
let (status, code, information) = build_resp_cond_state(pair);
|
||||||
Rule::resp_cond_state => {
|
Response::Done(ResponseDone {
|
||||||
let (status, code, information) = build_resp_cond_state(pair);
|
tag,
|
||||||
Response::Data(ResponseData {
|
status,
|
||||||
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 {
|
|
||||||
code,
|
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),
|
_ => 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) {
|
fn build_resp_text(pair: Pair<Rule>) -> (Option<ResponseCode>, String) {
|
||||||
assert!(matches!(pair.as_rule(), Rule::resp_text));
|
assert!(matches!(pair.as_rule(), Rule::resp_text));
|
||||||
let mut pairs = pair.into_inner();
|
let mut pairs = pair.into_inner();
|
||||||
|
|
2
notes.md
2
notes.md
|
@ -18,6 +18,8 @@ design ideas
|
||||||
- for ex: v1 has `{ x: Int }`, v2 has `{ [deprecated] x: Int, x2: Float }` and v3 has `{ x2: Float }`
|
- 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
|
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
|
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
|
imap routine
|
||||||
---
|
---
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -59,6 +59,9 @@ async fn run(opt: Opt) -> Result<()> {
|
||||||
// send messages from the mail thread to the UI thread
|
// send messages from the mail thread to the UI thread
|
||||||
let (mail2ui_tx, mail2ui_rx) = mpsc::unbounded_channel();
|
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 {
|
tokio::spawn(async move {
|
||||||
let config_update = config_update.clone();
|
let config_update = config_update.clone();
|
||||||
mail::run_mail(config_update, ui2mail_rx, mail2ui_tx)
|
mail::run_mail(config_update, ui2mail_rx, mail2ui_tx)
|
||||||
|
@ -67,7 +70,7 @@ async fn run(opt: Opt) -> Result<()> {
|
||||||
});
|
});
|
||||||
|
|
||||||
if !opt.headless {
|
if !opt.headless {
|
||||||
run_ui(exit_tx, mail2ui_rx);
|
run_ui(exit_tx, mail2ui_rx, ui2vm_tx);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_rx.recv().await;
|
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
|
// 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 stdout = std::io::stdout();
|
||||||
|
|
||||||
let rt = RuntimeBuilder::new_current_thread()
|
let rt = RuntimeBuilder::new_current_thread()
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gluon::{import::add_extern_module, ThreadExt};
|
use gluon::{import::add_extern_module, ThreadExt};
|
||||||
|
|
||||||
|
use crate::ui::{UiCommand, UiUpdate};
|
||||||
|
|
||||||
/// Creates a VM for running scripts
|
/// Creates a VM for running scripts
|
||||||
pub async fn create_script_vm() -> Result<()> {
|
pub async fn create_script_vm() -> Result<()> {
|
||||||
let vm = gluon::new_vm_async().await;
|
let vm = gluon::new_vm_async().await;
|
||||||
|
|
9
src/ui/messages.rs
Normal file
9
src/ui/messages.rs
Normal 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 {}
|
|
@ -4,8 +4,11 @@ mod colon_prompt;
|
||||||
mod input;
|
mod input;
|
||||||
mod keybinds;
|
mod keybinds;
|
||||||
mod mail_tab;
|
mod mail_tab;
|
||||||
|
mod messages;
|
||||||
|
mod windows;
|
||||||
|
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
|
@ -39,6 +42,8 @@ use crate::mail::{EmailMetadata, MailEvent};
|
||||||
use self::colon_prompt::ColonPrompt;
|
use self::colon_prompt::ColonPrompt;
|
||||||
use self::input::{BaseInputHandler, HandlesInput, InputResult};
|
use self::input::{BaseInputHandler, HandlesInput, InputResult};
|
||||||
use self::mail_tab::MailTabState;
|
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 FrameType<'a, 'b> = Frame<'a, CrosstermBackend<&'b mut Stdout>>;
|
||||||
pub(crate) type TermType<'a, 'b> = &'b mut Terminal<CrosstermBackend<&'a 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(),
|
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) {
|
while !should_exit.load(Ordering::Relaxed) {
|
||||||
term.draw(|f| {
|
term.draw(|f| {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
|
@ -78,7 +90,13 @@ pub async fn run_ui(
|
||||||
.split(f.size());
|
.split(f.size());
|
||||||
|
|
||||||
// this is the title bar
|
// 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);
|
let tabs = Tabs::new(titles);
|
||||||
f.render_widget(tabs, chunks[0]);
|
f.render_widget(tabs, chunks[0]);
|
||||||
|
|
||||||
|
|
78
src/ui/windows.rs
Normal file
78
src/ui/windows.rs
Normal 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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue