ok replaced tui's closure-style read loop with a non-closure loop
This commit is contained in:
parent
a1e9576d07
commit
22f11544e0
12 changed files with 468 additions and 102 deletions
33
Cargo.lock
generated
33
Cargo.lock
generated
|
@ -49,9 +49,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.39"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
|
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "archery"
|
name = "archery"
|
||||||
|
@ -1525,7 +1525,7 @@ dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"inotify-sys",
|
"inotify-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2014,7 +2014,6 @@ dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-humanize",
|
"chrono-humanize",
|
||||||
"crossterm 0.19.0",
|
|
||||||
"downcast-rs",
|
"downcast-rs",
|
||||||
"fern",
|
"fern",
|
||||||
"format-bytes",
|
"format-bytes",
|
||||||
|
@ -2027,6 +2026,7 @@ dependencies = [
|
||||||
"notify-rust",
|
"notify-rust",
|
||||||
"panorama-imap",
|
"panorama-imap",
|
||||||
"panorama-smtp",
|
"panorama-smtp",
|
||||||
|
"panorama-tui",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"quoted_printable",
|
"quoted_printable",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -2034,12 +2034,11 @@ dependencies = [
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"structopt",
|
"structopt",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"toml",
|
"toml",
|
||||||
"tui",
|
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
@ -2060,7 +2059,7 @@ dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"quoted_printable",
|
"quoted_printable",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
|
@ -2073,6 +2072,14 @@ version = "0.1.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "panorama-tui"
|
name = "panorama-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"crossterm 0.19.0",
|
||||||
|
"futures 0.3.13",
|
||||||
|
"log",
|
||||||
|
"tokio 1.4.0",
|
||||||
|
"tui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking"
|
name = "parking"
|
||||||
|
@ -2869,7 +2876,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ce2e16b6774c671cc183e1d202386fdf9cde1e8468c1894a7f2a63eb671c4f4"
|
checksum = "4ce2e16b6774c671cc183e1d202386fdf9cde1e8468c1894a7f2a63eb671c4f4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3143,9 +3150,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8d56477f6ed99e10225f38f9f75f872f29b8b8bd8c0b946f63345bb144e9eeda"
|
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes 1.0.1",
|
"bytes 1.0.1",
|
||||||
|
@ -3179,7 +3186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
"webpki",
|
"webpki",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3191,7 +3198,7 @@ checksum = "c535f53c0cfa1acace62995a8994fc9cc1f12d202420da96ff306ee24d576469"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite 0.2.6",
|
"pin-project-lite 0.2.6",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3208,7 +3215,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite 0.2.6",
|
"pin-project-lite 0.2.6",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio 1.3.0",
|
"tokio 1.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
19
Cargo.toml
19
Cargo.toml
|
@ -16,38 +16,37 @@ members = [
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# tantivy = "0.14.0"
|
||||||
anyhow = "1.0.39"
|
anyhow = "1.0.39"
|
||||||
async-trait = "0.1.48"
|
async-trait = "0.1.48"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
chrono-humanize = "0.1.2"
|
chrono-humanize = "0.1.2"
|
||||||
crossterm = { version = "0.19.0", features = ["event-stream"] }
|
downcast-rs = "1.2.0"
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
format-bytes = "0.2.2"
|
format-bytes = "0.2.2"
|
||||||
futures = "0.3.13"
|
futures = "0.3.13"
|
||||||
gluon = "0.17.2"
|
gluon = "0.17.2"
|
||||||
|
hex = "0.4.3"
|
||||||
inotify = { version = "0.9.2", features = ["stream"] }
|
inotify = { version = "0.9.2", features = ["stream"] }
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
|
mailparse = "0.13.2"
|
||||||
notify-rust = { version = "4.3.0", default-features = false, features = ["z"] }
|
notify-rust = { version = "4.3.0", default-features = false, features = ["z"] }
|
||||||
|
panorama-tui = { path = "tui" }
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
|
quoted_printable = "0.4.2"
|
||||||
serde = { version = "1.0.124", features = ["derive"] }
|
serde = { version = "1.0.124", features = ["derive"] }
|
||||||
|
sha2 = "0.9.3"
|
||||||
|
shellexpand = "2.1.0"
|
||||||
|
sqlx = { version = "0.5.1", features = ["runtime-tokio-rustls", "sqlite"] }
|
||||||
structopt = "0.3.21"
|
structopt = "0.3.21"
|
||||||
tokio = { version = "1.3.0", features = ["full"] }
|
tokio = { version = "1.3.0", features = ["full"] }
|
||||||
tokio-rustls = "0.22.0"
|
tokio-rustls = "0.22.0"
|
||||||
tokio-stream = { version = "0.1.4", features = ["sync"] }
|
tokio-stream = { version = "0.1.4", features = ["sync"] }
|
||||||
tokio-util = { version = "0.6.4", features = ["full"] }
|
tokio-util = { version = "0.6.4", features = ["full"] }
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
tui = { version = "0.14.0", default-features = false, features = ["crossterm"] }
|
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
downcast-rs = "1.2.0"
|
|
||||||
quoted_printable = "0.4.2"
|
|
||||||
sqlx = { version = "0.5.1", features = ["runtime-tokio-rustls", "sqlite"] }
|
|
||||||
sha2 = "0.9.3"
|
|
||||||
hex = "0.4.3"
|
|
||||||
shellexpand = "2.1.0"
|
|
||||||
mailparse = "0.13.2"
|
|
||||||
# tantivy = "0.14.0"
|
|
||||||
|
|
||||||
[dependencies.panorama-imap]
|
[dependencies.panorama-imap]
|
||||||
path = "imap"
|
path = "imap"
|
||||||
|
|
|
@ -11,8 +11,6 @@ extern crate anyhow;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate async_trait;
|
extern crate async_trait;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate crossterm;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate format_bytes;
|
extern crate format_bytes;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
|
|
|
@ -154,7 +154,7 @@ impl MailStore {
|
||||||
.fetch_one(&inner.pool)
|
.fetch_one(&inner.pool)
|
||||||
.await,
|
.await,
|
||||||
)?;
|
)?;
|
||||||
mem::drop(inner);
|
mem::drop(read);
|
||||||
|
|
||||||
if let Some(existing) = existing {
|
if let Some(existing) = existing {
|
||||||
let rowid = existing.0;
|
let rowid = existing.0;
|
||||||
|
@ -272,7 +272,7 @@ impl MailStore {
|
||||||
.context("error inserting email into db")?
|
.context("error inserting email into db")?
|
||||||
.last_insert_rowid();
|
.last_insert_rowid();
|
||||||
}
|
}
|
||||||
mem::drop(inner);
|
mem::drop(read);
|
||||||
|
|
||||||
// self.email_events
|
// self.email_events
|
||||||
// .send(EmailUpdateInfo {})
|
// .send(EmailUpdateInfo {})
|
||||||
|
|
|
@ -74,7 +74,13 @@ async fn run(opt: Opt) -> Result<()> {
|
||||||
|
|
||||||
if !opt.headless {
|
if !opt.headless {
|
||||||
let config_update2 = config_update.clone();
|
let config_update2 = config_update.clone();
|
||||||
run_ui(config_update2, mail_store.clone(), exit_tx, mail2ui_rx, ui2vm_tx);
|
run_ui(
|
||||||
|
config_update2,
|
||||||
|
mail_store.clone(),
|
||||||
|
exit_tx,
|
||||||
|
mail2ui_rx,
|
||||||
|
ui2vm_tx,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
exit_rx.recv().await;
|
exit_rx.recv().await;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
use panorama_tui::crossterm::event::{KeyCode, KeyEvent};
|
||||||
|
|
||||||
use super::input::{HandlesInput, InputResult};
|
use super::input::{HandlesInput, InputResult};
|
||||||
use super::TermType;
|
use super::TermType;
|
||||||
|
|
|
@ -6,8 +6,8 @@ use std::sync::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEvent};
|
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
|
use panorama_tui::crossterm::event::{self, Event, KeyCode, KeyEvent};
|
||||||
|
|
||||||
use super::colon_prompt::ColonPrompt;
|
use super::colon_prompt::ColonPrompt;
|
||||||
use super::TermType;
|
use super::TermType;
|
||||||
|
|
|
@ -8,14 +8,16 @@ use std::sync::{
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{DateTime, Datelike, Duration, Local};
|
use chrono::{DateTime, Datelike, Duration, Local};
|
||||||
use chrono_humanize::HumanTime;
|
use chrono_humanize::HumanTime;
|
||||||
use crossterm::event::{KeyCode, KeyEvent};
|
|
||||||
use panorama_imap::response::Envelope;
|
use panorama_imap::response::Envelope;
|
||||||
use tui::{
|
use panorama_tui::{
|
||||||
|
crossterm::event::{KeyCode, KeyEvent},
|
||||||
|
tui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Span, Spans},
|
text::{Span, Spans},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::mail::EmailMetadata;
|
use crate::mail::EmailMetadata;
|
||||||
|
@ -54,36 +56,36 @@ impl Window for MailView {
|
||||||
String::from("email")
|
String::from("email")
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn draw(&self) {
|
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI) {
|
||||||
// let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
// .direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
// .margin(0)
|
.margin(0)
|
||||||
// .constraints([Constraint::Length(20), Constraint::Max(5000)])
|
.constraints([Constraint::Length(20), Constraint::Max(5000)])
|
||||||
// .split(area);
|
.split(area);
|
||||||
|
|
||||||
// let accts = self.mail_store.list_accounts().await;
|
let accts = self.mail_store.list_accounts().await;
|
||||||
|
|
||||||
// // folder list
|
// folder list
|
||||||
// let mut items = vec![];
|
let mut items = vec![];
|
||||||
// for (acct_name, acct_ref) in accts.iter() {
|
for (acct_name, acct_ref) in accts.iter() {
|
||||||
// let folders = acct_ref.folders().await;
|
let folders = acct_ref.folders().await;
|
||||||
|
|
||||||
// items.push(ListItem::new(acct_name.to_owned()));
|
items.push(ListItem::new(acct_name.to_owned()));
|
||||||
// for folder in folders {
|
for folder in folders {
|
||||||
// items.push(ListItem::new(format!(" {}", folder)));
|
items.push(ListItem::new(format!(" {}", folder)));
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// let dirlist = List::new(items)
|
let dirlist = List::new(items)
|
||||||
// .block(Block::default().borders(Borders::NONE).title(Span::styled(
|
.block(Block::default().borders(Borders::NONE).title(Span::styled(
|
||||||
// "hellosu",
|
"hellosu",
|
||||||
// Style::default().add_modifier(Modifier::BOLD),
|
Style::default().add_modifier(Modifier::BOLD),
|
||||||
// )))
|
)))
|
||||||
// .style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
// .highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||||
// .highlight_symbol(">>");
|
.highlight_symbol(">>");
|
||||||
|
|
||||||
// let mut rows = vec![];
|
let mut rows = vec![];
|
||||||
// for acct in accts.iter() {
|
// for acct in accts.iter() {
|
||||||
// // TODO: messages
|
// // TODO: messages
|
||||||
// let result: Option<Vec<EmailMetadata>> = None; // self.mail_store.messages_of(acct);
|
// let result: Option<Vec<EmailMetadata>> = None; // self.mail_store.messages_of(acct);
|
||||||
|
@ -108,23 +110,23 @@ impl Window for MailView {
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// let table = Table::new(rows)
|
let table = Table::new(rows)
|
||||||
// .style(Style::default().fg(Color::White))
|
.style(Style::default().fg(Color::White))
|
||||||
// .widths(&[
|
.widths(&[
|
||||||
// Constraint::Length(1),
|
Constraint::Length(1),
|
||||||
// Constraint::Max(3),
|
Constraint::Max(3),
|
||||||
// Constraint::Min(20),
|
Constraint::Min(20),
|
||||||
// Constraint::Min(35),
|
Constraint::Min(35),
|
||||||
// Constraint::Max(5000),
|
Constraint::Max(5000),
|
||||||
// ])
|
])
|
||||||
// .header(
|
.header(
|
||||||
// Row::new(vec!["", "UID", "Date", "From", "Subject"])
|
Row::new(vec!["", "UID", "Date", "From", "Subject"])
|
||||||
// .style(Style::default().add_modifier(Modifier::BOLD)),
|
.style(Style::default().add_modifier(Modifier::BOLD)),
|
||||||
// )
|
)
|
||||||
// .highlight_style(Style::default().bg(Color::DarkGray));
|
.highlight_style(Style::default().bg(Color::DarkGray));
|
||||||
|
|
||||||
// f.render_widget(dirlist, chunks[0]);
|
f.render_widget(dirlist, chunks[0]);
|
||||||
// f.render_widget(table, chunks[1]);
|
f.render_widget(table, chunks[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,23 +19,25 @@ use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use chrono::{Local, TimeZone};
|
use chrono::{Local, TimeZone};
|
||||||
use crossterm::{
|
|
||||||
cursor,
|
|
||||||
event::{self, Event, EventStream, KeyCode, KeyEvent},
|
|
||||||
style, terminal,
|
|
||||||
};
|
|
||||||
use downcast_rs::Downcast;
|
use downcast_rs::Downcast;
|
||||||
use futures::{future::FutureExt, select, stream::StreamExt};
|
use futures::{future::FutureExt, select, stream::StreamExt};
|
||||||
use panorama_imap::response::{AttributeValue, Envelope};
|
use panorama_imap::response::{AttributeValue, Envelope};
|
||||||
use tokio::{sync::mpsc, time};
|
use panorama_tui::{
|
||||||
use tui::{
|
crossterm::{
|
||||||
|
cursor,
|
||||||
|
event::{self, Event, EventStream, KeyCode, KeyEvent},
|
||||||
|
execute, queue, style, terminal,
|
||||||
|
},
|
||||||
|
tui::{
|
||||||
backend::CrosstermBackend,
|
backend::CrosstermBackend,
|
||||||
layout::{Constraint, Direction, Layout},
|
layout::{Constraint, Direction, Layout},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
text::{Span, Spans},
|
text::{Span, Spans},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
|
},
|
||||||
Frame, Terminal,
|
Frame, Terminal,
|
||||||
};
|
};
|
||||||
|
use tokio::{sync::mpsc, time};
|
||||||
|
|
||||||
use crate::config::ConfigWatcher;
|
use crate::config::ConfigWatcher;
|
||||||
use crate::mail::{EmailMetadata, MailEvent, MailStore};
|
use crate::mail::{EmailMetadata, MailEvent, MailStore};
|
||||||
|
@ -46,8 +48,9 @@ use self::mail_view::MailView;
|
||||||
pub(crate) use self::messages::*;
|
pub(crate) use self::messages::*;
|
||||||
use self::windows::*;
|
use self::windows::*;
|
||||||
|
|
||||||
pub(crate) type FrameType<'a, 'b, 'c> = &'c mut Frame<'a, CrosstermBackend<&'b mut Stdout>>;
|
pub(crate) type FrameType<'a, 'b> = Frame<'a, &'b mut Stdout>;
|
||||||
pub(crate) type TermType<'a, 'b> = &'b mut Terminal<CrosstermBackend<&'a mut Stdout>>;
|
// pub(crate) type FrameType<'a, 'b, 'c> = &'c mut Frame<'a, CrosstermBackend<&'b mut Stdout>>;
|
||||||
|
pub(crate) type TermType<'b> = &'b mut Terminal<Stdout>;
|
||||||
|
|
||||||
/// Parameters for passing to the UI thread
|
/// Parameters for passing to the UI thread
|
||||||
pub struct UiParams {
|
pub struct UiParams {
|
||||||
|
@ -76,8 +79,8 @@ pub async fn run_ui2(params: UiParams) -> Result<()> {
|
||||||
execute!(stdout, cursor::Hide, terminal::EnterAlternateScreen)?;
|
execute!(stdout, cursor::Hide, terminal::EnterAlternateScreen)?;
|
||||||
terminal::enable_raw_mode()?;
|
terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
let backend = CrosstermBackend::new(&mut stdout);
|
// let backend = CrosstermBackend::new(&mut stdout);
|
||||||
let mut term = Terminal::new(backend)?;
|
let mut term = Terminal::new(&mut stdout)?;
|
||||||
let mut ui_events = EventStream::new();
|
let mut ui_events = EventStream::new();
|
||||||
|
|
||||||
let should_exit = Arc::new(AtomicBool::new(false));
|
let should_exit = Arc::new(AtomicBool::new(false));
|
||||||
|
@ -96,9 +99,12 @@ pub async fn run_ui2(params: UiParams) -> Result<()> {
|
||||||
// let mut input_states: Vec<Box<dyn HandlesInput>> = vec![];
|
// let mut input_states: Vec<Box<dyn HandlesInput>> = vec![];
|
||||||
|
|
||||||
while !should_exit.load(Ordering::Relaxed) {
|
while !should_exit.load(Ordering::Relaxed) {
|
||||||
term.draw(|f| {
|
term.pre_draw()?;
|
||||||
ui.draw(f);
|
{
|
||||||
})?;
|
let mut frame = term.get_frame();
|
||||||
|
ui.draw(&mut frame).await;
|
||||||
|
}
|
||||||
|
term.post_draw()?;
|
||||||
|
|
||||||
select! {
|
select! {
|
||||||
// got an event from the mail thread
|
// got an event from the mail thread
|
||||||
|
@ -117,8 +123,8 @@ pub async fn run_ui2(params: UiParams) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mem::drop(term);
|
|
||||||
mem::drop(ui);
|
mem::drop(ui);
|
||||||
|
mem::drop(term);
|
||||||
|
|
||||||
execute!(
|
execute!(
|
||||||
stdout,
|
stdout,
|
||||||
|
@ -142,7 +148,7 @@ pub struct UI {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UI {
|
impl UI {
|
||||||
fn draw(&mut self, f: FrameType) {
|
async fn draw(&mut self, f: &mut FrameType<'_, '_>) {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
|
@ -167,12 +173,14 @@ impl UI {
|
||||||
.collect();
|
.collect();
|
||||||
let tabs = Tabs::new(titles).style(Style::default().bg(Color::DarkGray));
|
let tabs = Tabs::new(titles).style(Style::default().bg(Color::DarkGray));
|
||||||
f.render_widget(tabs, chunks[1]);
|
f.render_widget(tabs, chunks[1]);
|
||||||
|
debug!("drew chunks");
|
||||||
|
|
||||||
// render all other windows
|
// render all other windows
|
||||||
let visible = self.window_layout.visible_windows(chunks[0]);
|
let visible = self.window_layout.visible_windows(chunks[0]);
|
||||||
for (layout_id, area) in visible.into_iter() {
|
for (layout_id, area) in visible.into_iter() {
|
||||||
if let Some(window) = self.windows.get(&layout_id) {
|
if let Some(window) = self.windows.get(&layout_id) {
|
||||||
window.draw();
|
window.draw(f, area, self).await;
|
||||||
|
debug!("drew {:?} {:?}", layout_id, area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use tui::layout::Rect;
|
use panorama_tui::tui::layout::Rect;
|
||||||
|
|
||||||
use super::{FrameType, HandlesInput, UI};
|
use super::{FrameType, HandlesInput, UI};
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ pub trait Window: HandlesInput {
|
||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
|
|
||||||
/// Main draw function
|
/// Main draw function
|
||||||
async fn draw(&self);
|
async fn draw(&self, f: &mut FrameType<'_, '_>, area: Rect, ui: &UI);
|
||||||
// async fn draw(&self, f: FrameType, area: Rect, ui: Rc<UI>);
|
// async fn draw(&self, f: FrameType, area: Rect, ui: Rc<UI>);
|
||||||
|
|
||||||
/// Update function
|
/// Update function
|
||||||
|
|
|
@ -5,3 +5,9 @@ authors = ["Michael Zhang <mail@mzhang.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.40"
|
||||||
|
crossterm = { version = "0.19.0", features = ["event-stream"] }
|
||||||
|
futures = "0.3.13"
|
||||||
|
log = "0.4.14"
|
||||||
|
tokio = { version = "1.4.0", features = ["full"] }
|
||||||
|
tui = { version = "0.14.0", default-features = false, features = ["crossterm"] }
|
||||||
|
|
340
tui/src/lib.rs
340
tui/src/lib.rs
|
@ -0,0 +1,340 @@
|
||||||
|
//! messily modified version of `tui` (MIT licensed) with async support for events
|
||||||
|
//!
|
||||||
|
//! (note: still uses synchronous stdout api since crossterm doesn't have tokio support
|
||||||
|
//! and no way i'm porting crossterm to tokio)
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
|
pub extern crate crossterm;
|
||||||
|
pub extern crate tui;
|
||||||
|
|
||||||
|
use std::io::{Stdout, Write};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use crossterm::{
|
||||||
|
cursor::{MoveTo, Show},
|
||||||
|
execute, queue,
|
||||||
|
style::{
|
||||||
|
Attribute as CAttribute, Color as CColor, Print, SetAttribute, SetBackgroundColor,
|
||||||
|
SetForegroundColor,
|
||||||
|
},
|
||||||
|
terminal::{self, Clear, ClearType},
|
||||||
|
};
|
||||||
|
use futures::future::Future;
|
||||||
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
use tui::{
|
||||||
|
buffer::{Buffer, Cell},
|
||||||
|
layout::Rect,
|
||||||
|
style::{Color, Modifier},
|
||||||
|
widgets::Widget,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Terminal<W> {
|
||||||
|
stdout: W,
|
||||||
|
buffers: [Buffer; 2],
|
||||||
|
current: usize,
|
||||||
|
hidden_cursor: bool,
|
||||||
|
viewport: Viewport,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> Terminal<W> {
|
||||||
|
pub fn new(stdout: W) -> Result<Self> {
|
||||||
|
let (width, height) = terminal::size()?;
|
||||||
|
let size = Rect::new(0, 0, width, height);
|
||||||
|
Terminal::with_options(
|
||||||
|
stdout,
|
||||||
|
TerminalOptions {
|
||||||
|
viewport: Viewport {
|
||||||
|
area: size,
|
||||||
|
resize_behavior: ResizeBehavior::Auto,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_options(stdout: W, options: TerminalOptions) -> Result<Self> {
|
||||||
|
Ok(Terminal {
|
||||||
|
stdout,
|
||||||
|
buffers: [
|
||||||
|
Buffer::empty(options.viewport.area),
|
||||||
|
Buffer::empty(options.viewport.area),
|
||||||
|
],
|
||||||
|
current: 0,
|
||||||
|
hidden_cursor: false,
|
||||||
|
viewport: options.viewport,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pre_draw(&mut self) -> Result<()> {
|
||||||
|
self.autoresize()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn post_draw(&mut self) -> Result<()> {
|
||||||
|
self.flush()?;
|
||||||
|
|
||||||
|
self.buffers[1 - self.current].reset();
|
||||||
|
self.current = 1 - self.current;
|
||||||
|
|
||||||
|
self.stdout.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn draw<F, F2>(&mut self, f: F) -> Result<()>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Frame<W>) -> F2,
|
||||||
|
F2: Future<Output = ()>,
|
||||||
|
{
|
||||||
|
self.autoresize()?;
|
||||||
|
|
||||||
|
let mut frame = self.get_frame();
|
||||||
|
f(&mut frame).await;
|
||||||
|
self.flush()?;
|
||||||
|
|
||||||
|
self.buffers[1 - self.current].reset();
|
||||||
|
self.current = 1 - self.current;
|
||||||
|
|
||||||
|
self.stdout.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn autoresize(&mut self) -> Result<()> {
|
||||||
|
if self.viewport.resize_behavior == ResizeBehavior::Auto {
|
||||||
|
let size = self.size()?;
|
||||||
|
if size != self.viewport.area {
|
||||||
|
self.resize(size)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resize(&mut self, area: Rect) -> Result<()> {
|
||||||
|
self.buffers[self.current].resize(area);
|
||||||
|
self.buffers[1 - self.current].resize(area);
|
||||||
|
self.viewport.area = area;
|
||||||
|
self.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self) -> Result<()> {
|
||||||
|
execute!(self.stdout, Clear(ClearType::All))?;
|
||||||
|
// Reset the back buffer to make sure the next update will redraw everything.
|
||||||
|
self.buffers[1 - self.current].reset();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<()> {
|
||||||
|
let previous_buffer = &self.buffers[1 - self.current];
|
||||||
|
let current_buffer = &self.buffers[self.current];
|
||||||
|
let updates = previous_buffer
|
||||||
|
.diff(current_buffer)
|
||||||
|
.into_iter()
|
||||||
|
.map(|(a, b, c)| (a, b, c.clone()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
self.draw_backend(updates.into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_frame(&mut self) -> Frame<W> {
|
||||||
|
Frame {
|
||||||
|
terminal: self,
|
||||||
|
cursor_position: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn draw_backend<I>(&mut self, content: I) -> Result<()>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = (u16, u16, Cell)>,
|
||||||
|
{
|
||||||
|
let mut fg = Color::Reset;
|
||||||
|
let mut bg = Color::Reset;
|
||||||
|
let mut modifier = Modifier::empty();
|
||||||
|
let mut last_pos: Option<(u16, u16)> = None;
|
||||||
|
for (x, y, cell) in content {
|
||||||
|
// Move the cursor if the previous location was not (x - 1, y)
|
||||||
|
if !matches!(last_pos, Some(p) if x == p.0 + 1 && y == p.1) {
|
||||||
|
queue!(self.stdout, MoveTo(x, y))?;
|
||||||
|
}
|
||||||
|
last_pos = Some((x, y));
|
||||||
|
if cell.modifier != modifier {
|
||||||
|
let diff = ModifierDiff {
|
||||||
|
from: modifier,
|
||||||
|
to: cell.modifier,
|
||||||
|
};
|
||||||
|
diff.queue(&mut self.stdout)?;
|
||||||
|
modifier = cell.modifier;
|
||||||
|
}
|
||||||
|
if cell.fg != fg {
|
||||||
|
let color = color_conv(cell.fg);
|
||||||
|
queue!(self.stdout, SetForegroundColor(color))?;
|
||||||
|
fg = cell.fg;
|
||||||
|
}
|
||||||
|
if cell.bg != bg {
|
||||||
|
let color = color_conv(cell.bg);
|
||||||
|
queue!(self.stdout, SetBackgroundColor(color))?;
|
||||||
|
bg = cell.bg;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue!(self.stdout, Print(&cell.symbol))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue!(
|
||||||
|
self.stdout,
|
||||||
|
SetForegroundColor(CColor::Reset),
|
||||||
|
SetBackgroundColor(CColor::Reset),
|
||||||
|
SetAttribute(CAttribute::Reset)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_buffer_mut(&mut self) -> &mut Buffer {
|
||||||
|
&mut self.buffers[self.current]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show_cursor(&mut self) -> Result<()> {
|
||||||
|
execute!(self.stdout, Show)?;
|
||||||
|
self.hidden_cursor = false;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cursor(&mut self, x: u16, y: u16) -> Result<()> {
|
||||||
|
execute!(self.stdout, MoveTo(x, y))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Result<Rect> {
|
||||||
|
let (width, height) = terminal::size()?;
|
||||||
|
Ok(Rect::new(0, 0, width, height))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn color_conv(color: Color) -> CColor {
|
||||||
|
match color {
|
||||||
|
Color::Reset => CColor::Reset,
|
||||||
|
Color::Black => CColor::Black,
|
||||||
|
Color::Red => CColor::DarkRed,
|
||||||
|
Color::Green => CColor::DarkGreen,
|
||||||
|
Color::Yellow => CColor::DarkYellow,
|
||||||
|
Color::Blue => CColor::DarkBlue,
|
||||||
|
Color::Magenta => CColor::DarkMagenta,
|
||||||
|
Color::Cyan => CColor::DarkCyan,
|
||||||
|
Color::Gray => CColor::Grey,
|
||||||
|
Color::DarkGray => CColor::DarkGrey,
|
||||||
|
Color::LightRed => CColor::Red,
|
||||||
|
Color::LightGreen => CColor::Green,
|
||||||
|
Color::LightBlue => CColor::Blue,
|
||||||
|
Color::LightYellow => CColor::Yellow,
|
||||||
|
Color::LightMagenta => CColor::Magenta,
|
||||||
|
Color::LightCyan => CColor::Cyan,
|
||||||
|
Color::White => CColor::White,
|
||||||
|
Color::Indexed(i) => CColor::AnsiValue(i),
|
||||||
|
Color::Rgb(r, g, b) => CColor::Rgb { r, g, b },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Frame<'a, W> {
|
||||||
|
terminal: &'a mut Terminal<W>,
|
||||||
|
cursor_position: Option<(u16, u16)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, W: Write> Frame<'a, W> {
|
||||||
|
pub fn set_cursor(&mut self, x: u16, y: u16) {
|
||||||
|
self.cursor_position = Some((x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> Rect {
|
||||||
|
self.terminal.viewport.area
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render_widget<W2>(&mut self, widget: W2, area: Rect)
|
||||||
|
where
|
||||||
|
W2: Widget,
|
||||||
|
{
|
||||||
|
widget.render(area, self.terminal.current_buffer_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct ModifierDiff {
|
||||||
|
pub from: Modifier,
|
||||||
|
pub to: Modifier,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ModifierDiff {
|
||||||
|
fn queue<W: Write>(&self, w: &mut W) -> Result<()> {
|
||||||
|
//use crossterm::Attribute;
|
||||||
|
let removed = self.from - self.to;
|
||||||
|
if removed.contains(Modifier::REVERSED) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NoReverse))?;
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::BOLD) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
|
||||||
|
if self.to.contains(Modifier::DIM) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Dim))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::ITALIC) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NoItalic))?;
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::UNDERLINED) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NoUnderline))?;
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::DIM) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NormalIntensity))?;
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::CROSSED_OUT) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NotCrossedOut))?;
|
||||||
|
}
|
||||||
|
if removed.contains(Modifier::SLOW_BLINK) || removed.contains(Modifier::RAPID_BLINK) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::NoBlink))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let added = self.to - self.from;
|
||||||
|
if added.contains(Modifier::REVERSED) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Reverse))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::BOLD) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Bold))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::ITALIC) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Italic))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::UNDERLINED) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Underlined))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::DIM) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::Dim))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::CROSSED_OUT) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::CrossedOut))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::SLOW_BLINK) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::SlowBlink))?;
|
||||||
|
}
|
||||||
|
if added.contains(Modifier::RAPID_BLINK) {
|
||||||
|
queue!(w, SetAttribute(CAttribute::RapidBlink))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum ResizeBehavior {
|
||||||
|
Fixed,
|
||||||
|
Auto,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Viewport {
|
||||||
|
area: Rect,
|
||||||
|
resize_behavior: ResizeBehavior,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
/// Options to pass to [`Terminal::with_options`]
|
||||||
|
pub struct TerminalOptions {
|
||||||
|
/// Viewport used to draw to the terminal
|
||||||
|
pub viewport: Viewport,
|
||||||
|
}
|
Loading…
Reference in a new issue