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(),
|
||||
};
|
||||
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 {
|
||||
|
|
|
@ -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, ""),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
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 }`
|
||||
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
|
||||
---
|
||||
|
|
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
|
||||
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()
|
||||
|
|
|
@ -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
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 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
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