Refactor using async-std

This commit is contained in:
Dheepak Krishnamurthy 2021-03-30 18:48:15 -06:00
parent 6df2aba770
commit 979e886a29
7 changed files with 919 additions and 162 deletions

View file

@ -30,7 +30,16 @@ use chrono::{Datelike, Local, NaiveDate, NaiveDateTime, TimeZone};
use anyhow::Result;
use std::{sync::mpsc, thread, time::Duration};
use async_std::prelude::*;
use async_std::stream::StreamExt;
use async_std::task;
use futures::future::join_all;
use futures::join;
use futures::stream::FuturesOrdered;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout, Margin, Rect},
@ -140,6 +149,7 @@ pub enum AppMode {
pub struct TaskwarriorTuiApp {
pub should_quit: bool,
pub dirty: bool,
pub task_table_state: TableState,
pub context_table_state: TableState,
pub current_context_filter: String,
@ -173,8 +183,10 @@ impl TaskwarriorTuiApp {
let c = Config::default()?;
let mut kc = KeyConfig::default();
kc.update()?;
let (w, h) = crossterm::terminal::size()?;
let mut app = Self {
should_quit: false,
dirty: true,
task_table_state: TableState::default(),
context_table_state: TableState::default(),
tasks: vec![],
@ -198,8 +210,8 @@ impl TaskwarriorTuiApp {
contexts: vec![],
last_export: None,
keyconfig: kc,
terminal_width: 0,
terminal_height: 0,
terminal_width: w,
terminal_height: h,
};
for c in app.config.filter.chars() {
app.filter.insert(c, 1);
@ -223,6 +235,14 @@ impl TaskwarriorTuiApp {
Ok(())
}
pub fn render<B>(&mut self, terminal: &mut Terminal<B>) -> Result<()>
where
B: Backend,
{
terminal.draw(|f| self.draw(f))?;
Ok(())
}
pub fn draw(&mut self, f: &mut Frame<impl Backend>) {
let rect = f.size();
self.terminal_width = rect.width;
@ -280,7 +300,6 @@ impl TaskwarriorTuiApp {
let mut tasks_with_styles = vec![];
let tasks_is_empty = self.tasks.is_empty();
let tasks_len = self.tasks.len();
if !tasks_is_empty {
let tasks = &self.tasks;
@ -305,8 +324,7 @@ impl TaskwarriorTuiApp {
pub fn draw_task(&mut self, f: &mut Frame<impl Backend>) {
let tasks_is_empty = self.tasks.is_empty();
let tasks_len = self.tasks.len();
while !tasks_is_empty && self.current_selection >= tasks_len {
while !tasks_is_empty && self.current_selection >= self.tasks.len() {
self.task_report_previous();
}
let rects = Layout::default()
@ -333,7 +351,7 @@ impl TaskwarriorTuiApp {
self.draw_task_details(f, split_task_layout[1]);
}
let selected = self.current_selection;
let task_ids = if tasks_len == 0 {
let task_ids = if self.tasks.is_empty() {
vec!["0".to_string()]
} else {
match self.task_table_state.mode() {
@ -593,24 +611,14 @@ impl TaskwarriorTuiApp {
let task_id = self.tasks[selected].id().unwrap_or_default();
let task_uuid = *self.tasks[selected].uuid();
if !self.task_details.contains_key(&task_uuid) {
let output = Command::new("task")
.arg("rc.color=off")
.arg(format!("rc.defaultwidth={}", self.terminal_width - 2))
.arg(format!("{}", task_uuid))
.output();
let data = if let Ok(output) = output {
String::from_utf8_lossy(&output.stdout).to_string()
} else {
"".to_string()
};
let entry = self.task_details.entry(task_uuid).or_insert_with(|| "".to_string());
*entry = data;
}
let data = self.task_details[&task_uuid].clone();
let data = match self.task_details.get(&task_uuid) {
Some(s) => s.clone(),
None => "Loading task details ...".to_string(),
};
self.task_details_scroll = std::cmp::min(
(data.lines().count() as u16).saturating_sub(rect.height),
(data.lines().count() as u16)
.saturating_sub(rect.height)
.saturating_add(2),
self.task_details_scroll,
);
let p = Paragraph::new(Text::from(&data[..]))
@ -624,11 +632,11 @@ impl TaskwarriorTuiApp {
f.render_widget(p, rect);
}
fn task_details_scroll_down(&mut self) {
fn task_details_scroll_up(&mut self) {
self.task_details_scroll = self.task_details_scroll.saturating_sub(1);
}
fn task_details_scroll_up(&mut self) {
fn task_details_scroll_down(&mut self) {
self.task_details_scroll = self.task_details_scroll.saturating_add(1);
}
@ -856,13 +864,83 @@ impl TaskwarriorTuiApp {
}
pub fn update(&mut self, force: bool) -> Result<()> {
if force || self.tasks_changed_since(self.last_export)? {
if force || self.dirty || self.tasks_changed_since(self.last_export)? {
self.last_export = Some(std::time::SystemTime::now());
self.task_report_table.export_headers()?;
let _ = self.export_tasks();
self.export_contexts()?;
self.update_tags();
self.task_details.clear();
self.dirty = false;
}
if self.task_report_show_info {
task::block_on(self.update_task_details())?;
}
Ok(())
}
pub async fn update_task_details(&mut self) -> Result<()> {
if self.tasks.is_empty() {
return Ok(());
}
// remove task_details of tasks not in task report
let mut to_delete = vec![];
for k in self.task_details.keys() {
if !self.tasks.iter().map(|t| t.uuid()).any(|x| x == k) {
to_delete.push(*k);
}
}
for k in to_delete {
self.task_details.remove(&k);
}
let selected = self.current_selection;
if selected >= self.tasks.len() {
return Ok(());
}
let current_task_uuid = *self.tasks[selected].uuid();
let mut l = vec![selected];
for s in 1..=self.config.uda_task_detail_prefetch {
l.insert(0, std::cmp::min(selected.saturating_sub(s), self.tasks.len() - 1));
l.push(std::cmp::min(selected + s, self.tasks.len() - 1))
}
l.dedup();
let mut output_futs = FuturesOrdered::new();
for s in l.iter() {
if self.tasks.is_empty() {
return Ok(());
}
if s >= &self.tasks.len() {
break;
}
let task_uuid = *self.tasks[*s].uuid();
if !self.task_details.contains_key(&task_uuid) || task_uuid == current_task_uuid {
let output_fut = async_std::process::Command::new("task")
.arg("rc.color=off")
.arg(format!("rc.defaultwidth={}", self.terminal_width - 2))
.arg(format!("{}", task_uuid))
.output();
output_futs.push(output_fut);
}
}
for s in l.iter() {
if s >= &self.tasks.len() {
break;
}
let task_id = self.tasks[*s].id().unwrap_or_default();
let task_uuid = *self.tasks[*s].uuid();
if !self.task_details.contains_key(&task_uuid) || task_uuid == current_task_uuid {
if let Some(Ok(output)) = output_futs.next().await {
let data = String::from_utf8_lossy(&output.stdout).to_string();
self.task_details.insert(task_uuid, data);
}
}
}
Ok(())
}
@ -1708,9 +1786,9 @@ impl TaskwarriorTuiApp {
} else if input == Key::PageUp || input == self.keyconfig.page_up {
self.task_report_previous_page();
} else if input == Key::Ctrl('e') {
self.task_details_scroll_up();
} else if input == Key::Ctrl('y') {
self.task_details_scroll_down();
} else if input == Key::Ctrl('y') {
self.task_details_scroll_up();
} else if input == self.keyconfig.done {
match self.task_done() {
Ok(_) => self.update(true)?,
@ -1983,7 +2061,7 @@ impl TaskwarriorTuiApp {
}
_ => {
handle_movement(&mut self.filter, input);
// TODO: call self.update(true) here for instant filter updates
self.dirty = true;
}
},
AppMode::TaskError => self.mode = AppMode::TaskReport,
@ -2123,6 +2201,9 @@ mod tests {
test_draw_empty_task_report();
test_draw_calendar();
test_draw_help_popup();
setup();
let app = TaskwarriorTuiApp::new().unwrap();
@ -2141,6 +2222,7 @@ mod tests {
test_task_tomorrow();
test_task_earlier_today();
test_task_later_today();
teardown();
}
@ -2766,9 +2848,9 @@ mod tests {
"╰────────────────────────────────────────────────╯",
"╭Task 27─────────────────────────────────────────╮",
"│ │",
"│Name Value ",
"│----------- ------------------------------------│",
"│ID 27",
"│Name Value │",
"│------------- ----------------------------------│",
"│ID 27",
"╰────────────────────────────────────────────────╯",
"╭Filter Tasks────────────────────────────────────╮",
"│status:pending -private │",
@ -2824,7 +2906,6 @@ mod tests {
test_case(&expected);
}
#[test]
fn test_draw_calendar() {
let test_case = |expected: &Buffer| {
let mut app = TaskwarriorTuiApp::new().unwrap();
@ -2909,7 +2990,6 @@ mod tests {
test_case(&expected);
}
#[test]
fn test_draw_help_popup() {
let test_case = |expected: &Buffer| {
let mut app = TaskwarriorTuiApp::new().unwrap();

View file

@ -1,4 +1,6 @@
use anyhow::{Context, Result};
use async_std::task;
use futures::join;
use std::collections::HashMap;
use std::error::Error;
use std::process::Command;
@ -43,6 +45,7 @@ pub struct Config {
pub print_empty_columns: bool,
pub due: usize,
pub rule_precedence_color: Vec<String>,
pub uda_task_detail_prefetch: usize,
pub uda_task_report_show_info: bool,
pub uda_task_report_looping: bool,
pub uda_selection_indicator: String,
@ -61,28 +64,102 @@ pub struct Config {
impl Config {
pub fn default() -> Result<Self> {
let bool_collection = Self::get_bool_collection();
let enabled = true;
let obfuscate = bool_collection.get("obfuscate").cloned().unwrap_or(false);
let print_empty_columns = bool_collection.get("print_empty_columns").cloned().unwrap_or(false);
let color = Self::get_color_collection();
let filter = Self::get_filter();
let data_location = Self::get_data_location();
let due = Self::get_due();
let rule_precedence_color = Self::get_rule_precedence_color();
let uda_task_detail_prefetch = Self::get_uda_task_detail_prefetch();
let uda_task_report_show_info = Self::get_uda_task_report_show_info();
let uda_task_report_looping = Self::get_uda_task_report_looping();
let uda_selection_indicator = Self::get_uda_selection_indicator();
let uda_mark_indicator = Self::get_uda_mark_indicator();
let uda_unmark_indicator = Self::get_uda_unmark_indicator();
let uda_selection_bold = Self::get_uda_selection_bold();
let uda_selection_italic = Self::get_uda_selection_italic();
let uda_selection_dim = Self::get_uda_selection_dim();
let uda_selection_blink = Self::get_uda_selection_blink();
let uda_calendar_months_per_row = Self::get_uda_months_per_row();
let uda_style_calendar_title = Self::get_uda_style("calendar.title");
let uda_style_context_active = Self::get_uda_style("context.active");
let uda_shortcuts = Self::get_uda_shortcuts();
let (
color,
filter,
data_location,
due,
rule_precedence_color,
uda_task_detail_prefetch,
uda_task_report_show_info,
uda_task_report_looping,
uda_selection_indicator,
uda_mark_indicator,
uda_unmark_indicator,
uda_selection_bold,
uda_selection_italic,
uda_selection_dim,
uda_selection_blink,
uda_calendar_months_per_row,
uda_style_calendar_title,
uda_style_context_active,
uda_shortcuts,
) = task::block_on(async {
join!(
color,
filter,
data_location,
due,
rule_precedence_color,
uda_task_detail_prefetch,
uda_task_report_show_info,
uda_task_report_looping,
uda_selection_indicator,
uda_mark_indicator,
uda_unmark_indicator,
uda_selection_bold,
uda_selection_italic,
uda_selection_dim,
uda_selection_blink,
uda_calendar_months_per_row,
uda_style_calendar_title,
uda_style_context_active,
uda_shortcuts,
)
});
let color = color?;
let uda_style_calendar_title = uda_style_calendar_title.unwrap_or_default();
let uda_style_context_active = uda_style_context_active.unwrap_or_default();
Ok(Self {
enabled: true,
obfuscate: bool_collection.get("obfuscate").cloned().unwrap_or(false),
print_empty_columns: bool_collection.get("print_empty_columns").cloned().unwrap_or(false),
color: Self::get_color_collection()?,
filter: Self::get_filter(),
data_location: Self::get_data_location(),
due: Self::get_due(),
rule_precedence_color: Self::get_rule_precedence_color(),
uda_task_report_show_info: Self::get_uda_task_report_show_info(),
uda_task_report_looping: Self::get_uda_task_report_looping(),
uda_selection_indicator: Self::get_uda_selection_indicator(),
uda_mark_indicator: Self::get_uda_mark_indicator(),
uda_unmark_indicator: Self::get_uda_unmark_indicator(),
uda_selection_bold: Self::get_uda_selection_bold(),
uda_selection_italic: Self::get_uda_selection_italic(),
uda_selection_dim: Self::get_uda_selection_dim(),
uda_selection_blink: Self::get_uda_selection_blink(),
uda_calendar_months_per_row: Self::get_uda_months_per_row(),
uda_style_calendar_title: Self::get_uda_style("calendar.title").unwrap_or_default(),
uda_style_context_active: Self::get_uda_style("context.active").unwrap_or_default(),
uda_shortcuts: Self::get_uda_shortcuts(),
enabled,
color,
filter,
data_location,
obfuscate,
print_empty_columns,
due,
rule_precedence_color,
uda_task_detail_prefetch,
uda_task_report_show_info,
uda_task_report_looping,
uda_selection_indicator,
uda_mark_indicator,
uda_unmark_indicator,
uda_selection_bold,
uda_selection_italic,
uda_selection_dim,
uda_selection_blink,
uda_calendar_months_per_row,
uda_style_context_active,
uda_style_calendar_title,
uda_shortcuts,
})
}
@ -90,25 +167,29 @@ impl Config {
HashMap::new()
}
fn get_uda_shortcuts() -> Vec<String> {
async fn get_uda_shortcuts() -> Vec<String> {
let mut v = vec![];
for s in 0..=9 {
let c = format!("uda.taskwarrior-tui.shortcuts.{}", s);
let s = Self::get_config(&c).unwrap_or_default();
let s = Self::get_config(&c).await.unwrap_or_default();
v.push(s);
}
v
}
fn get_uda_style(config: &str) -> Option<Style> {
async fn get_uda_style(config: &str) -> Option<Style> {
let c = format!("uda.taskwarrior-tui.style.{}", config);
let s = Self::get_config(&c)?;
let s = Self::get_config(&c).await?;
Some(Self::get_tcolor(&s))
}
fn get_color_collection() -> Result<HashMap<String, Style>> {
async fn get_color_collection() -> Result<HashMap<String, Style>> {
let mut color_collection = HashMap::new();
let output = Command::new("task").arg("rc.color=off").arg("show").output()?;
let output = async_std::process::Command::new("task")
.arg("rc.color=off")
.arg("show")
.output()
.await?;
let data = String::from_utf8_lossy(&output.stdout);
for line in data.split('\n') {
@ -274,12 +355,13 @@ impl Config {
}
}
fn get_config(config: &str) -> Option<String> {
let output = Command::new("task")
async fn get_config(config: &str) -> Option<String> {
let output = async_std::process::Command::new("task")
.arg("rc.color=off")
.arg("show")
.arg(config)
.output()
.await
.with_context(|| format!("Unable to run `task show {}`.", config))
.unwrap();
@ -311,100 +393,119 @@ impl Config {
None
}
fn get_due() -> usize {
async fn get_due() -> usize {
Self::get_config("due")
.await
.unwrap_or_default()
.parse::<usize>()
.unwrap_or(7)
}
fn get_rule_precedence_color() -> Vec<String> {
async fn get_rule_precedence_color() -> Vec<String> {
let data = Self::get_config("rule.precedence.color")
.await
.context("Unable to parse `task show rule.precedence.color`.")
.unwrap();
data.split(',').map(|s| s.to_string()).collect::<Vec<_>>()
}
fn get_filter() -> String {
async fn get_filter() -> String {
Self::get_config("report.next.filter")
.await
.context("Unable to parse `task show report.next.filter`.")
.unwrap()
}
fn get_data_location() -> String {
async fn get_data_location() -> String {
Self::get_config("data.location")
.await
.context("Unable to parse `task show data.location`.")
.unwrap()
}
fn get_uda_task_report_show_info() -> bool {
async fn get_uda_task_detail_prefetch() -> usize {
Self::get_config("uda.taskwarrior-tui.task-report.task-detail-prefetch")
.await
.unwrap_or_default()
.parse::<usize>()
.unwrap_or(10)
}
async fn get_uda_task_report_show_info() -> bool {
Self::get_config("uda.taskwarrior-tui.task-report.show-info")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(true)
}
fn get_uda_task_report_looping() -> bool {
async fn get_uda_task_report_looping() -> bool {
Self::get_config("uda.taskwarrior-tui.task-report.looping")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(true)
}
fn get_uda_selection_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.selection.indicator");
async fn get_uda_selection_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.selection.indicator").await;
match indicator {
None => "".to_string(),
Some(indicator) => format!("{} ", indicator),
}
}
fn get_uda_mark_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.mark.indicator");
async fn get_uda_mark_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.mark.indicator").await;
match indicator {
None => "".to_string(),
Some(indicator) => format!("{} ", indicator),
}
}
fn get_uda_unmark_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.unmark.indicator");
async fn get_uda_unmark_indicator() -> String {
let indicator = Self::get_config("uda.taskwarrior-tui.unmark.indicator").await;
match indicator {
None => " ".to_string(),
Some(indicator) => format!("{} ", indicator),
}
}
fn get_uda_selection_bold() -> bool {
async fn get_uda_selection_bold() -> bool {
Self::get_config("uda.taskwarrior-tui.selection.bold")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(true)
}
fn get_uda_selection_italic() -> bool {
async fn get_uda_selection_italic() -> bool {
Self::get_config("uda.taskwarrior-tui.selection.italic")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(false)
}
fn get_uda_selection_dim() -> bool {
async fn get_uda_selection_dim() -> bool {
Self::get_config("uda.taskwarrior-tui.selection.dim")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(false)
}
fn get_uda_selection_blink() -> bool {
async fn get_uda_selection_blink() -> bool {
Self::get_config("uda.taskwarrior-tui.selection.blink")
.await
.unwrap_or_default()
.get_bool()
.unwrap_or(false)
}
fn get_uda_months_per_row() -> usize {
async fn get_uda_months_per_row() -> usize {
Self::get_config("uda.taskwarrior-tui.calendar.months-per-row")
.await
.unwrap_or_default()
.parse::<usize>()
.unwrap_or(4)
@ -416,7 +517,10 @@ mod tests {
use super::*;
#[test]
fn test_uda_configuration() {
assert_eq!(None, Config::get_config("uda.taskwarrior-tui.unmark.indicator"));
assert_eq!(
None,
task::block_on(Config::get_config("uda.taskwarrior-tui.unmark.indicator"))
);
}
#[test]

View file

@ -21,6 +21,11 @@ use std::io::Write;
use std::panic;
use std::time::Duration;
use async_std::prelude::*;
use async_std::sync::{Arc, Mutex};
use async_std::task;
use futures::stream::{FuturesUnordered, StreamExt};
use crate::util::Key;
use app::{AppMode, TaskwarriorTuiApp};
@ -44,28 +49,12 @@ fn main() -> Result<()> {
.get_matches();
let config = matches.value_of("config").unwrap_or("~/.taskrc");
let r = tui_main(config);
match r {
Ok(_) => std::process::exit(0),
Err(error) => {
if error.to_string().to_lowercase().contains("no such file or directory") {
eprintln!(
"[taskwarrior-tui error]: Unable to find executable `task`: {}. Check that taskwarrior is installed correctly and try again.", error
);
} else {
eprintln!(
"[taskwarrior-tui error]: {}. Please report as a github issue on https://github.com/kdheepak/taskwarrior-tui",
error
);
}
std::process::exit(1);
}
}
task::block_on(tui_main(config))
}
fn tui_main(_config: &str) -> Result<()> {
async fn tui_main(_config: &str) -> Result<()> {
// Terminal initialization
let mut terminal = setup_terminal();
let terminal = setup_terminal();
panic::set_hook(Box::new(|panic_info| {
destruct_terminal();
@ -79,21 +68,30 @@ fn tui_main(_config: &str) -> Result<()> {
let maybeapp = TaskwarriorTuiApp::new();
match maybeapp {
Ok(mut app) => {
Ok(app) => {
let app = Arc::new(Mutex::new(app));
let terminal = Arc::new(Mutex::new(terminal));
loop {
terminal.draw(|mut frame| app.draw(&mut frame)).unwrap();
let handle = {
let app = app.clone();
let terminal = terminal.clone();
task::spawn_local(async move {
let mut t = terminal.lock().await;
app.lock().await.render(&mut t).unwrap();
})
};
// Handle input
match events.next()? {
match events.next().await? {
Event::Input(input) => {
let r = app.handle_input(input, &mut terminal, &events);
let mut t = terminal.lock().await;
let r = app.lock().await.handle_input(input, &mut t, &events);
if r.is_err() {
destruct_terminal();
return r;
}
}
Event::Tick => {
let r = app.update(false);
let r = app.lock().await.update(false);
if r.is_err() {
destruct_terminal();
return r;
@ -101,7 +99,7 @@ fn tui_main(_config: &str) -> Result<()> {
}
}
if app.should_quit {
if app.lock().await.should_quit {
destruct_terminal();
break;
}

View file

@ -1,15 +1,20 @@
use crossterm::{
cursor,
event::{self, DisableMouseCapture, EnableMouseCapture},
event::{self, DisableMouseCapture, EnableMouseCapture, EventStream},
execute,
terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen},
};
use tui::{backend::CrosstermBackend, Terminal};
use async_std::channel::unbounded;
use async_std::sync::Arc;
use async_std::task;
use futures::prelude::*;
use futures::{future::FutureExt, select, StreamExt};
use futures_timer::Delay;
use std::io::{self, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::{sync::mpsc, thread, time::Duration, time::Instant};
use std::time::{Duration, Instant};
use serde::{Deserialize, Serialize};
@ -62,38 +67,36 @@ pub fn destruct_terminal() {
}
pub struct Events {
pub rx: mpsc::Receiver<Event<Key>>,
pub tx: mpsc::Sender<Event<Key>>,
pub rx: async_std::channel::Receiver<Event<Key>>,
pub pause_stdin: Arc<AtomicBool>,
pub handle: Option<thread::JoinHandle<()>>,
}
impl Events {
#[cfg(feature = "crossterm")]
pub fn with_config(config: EventConfig) -> Events {
use crossterm::event::{KeyCode::*, KeyModifiers};
let (tx, rx) = mpsc::channel();
let pause_stdin = Arc::new(AtomicBool::new(false));
let tick_rate = config.tick_rate;
let handle = Some({
let tx = tx.clone();
let pause_stdin = pause_stdin.clone();
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
if pause_stdin.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(250));
thread::park();
last_tick = Instant::now();
continue;
}
let (tx, rx) = unbounded::<Event<Key>>();
let ps = pause_stdin.clone();
task::spawn_local(async move {
let mut reader = EventStream::new();
let timeout = Duration::from_millis(10)
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_millis(5));
loop {
if ps.load(Ordering::SeqCst) {
task::sleep(Duration::from_millis(250)).await;
task::yield_now().await;
continue;
}
if event::poll(timeout).unwrap() {
if let event::Event::Key(key) = event::read().unwrap() {
let mut delay = Delay::new(Duration::from_millis(250)).fuse();
let mut event = reader.next().fuse();
select! {
_ = delay => {
tx.send(Event::Tick).await.ok();
},
maybe_event = event => {
if let Some(Ok(event::Event::Key(key))) = maybe_event {
let key = match key.code {
Backspace => Key::Backspace,
Enter => Key::Char('\n'),
@ -119,49 +122,39 @@ impl Events {
_ => Key::Null,
},
};
tx.send(Event::Input(key)).unwrap();
}
}
if last_tick.elapsed() >= tick_rate && tx.send(Event::Tick).is_ok() {
last_tick = Instant::now();
tx.send(Event::Input(key)).await.unwrap();
};
}
}
})
}
});
Events {
rx,
tx,
pause_stdin,
handle,
}
Events { rx, pause_stdin }
}
/// Attempts to read an event.
/// This function will block the current thread.
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
pub async fn next(&self) -> Result<Event<Key>, async_std::channel::RecvError> {
self.rx.recv().await
}
pub fn pause_event_loop(&self) {
pub async fn pause_event_loop(&self) {
self.pause_stdin.store(true, Ordering::SeqCst);
thread::yield_now();
task::yield_now().await;
while !self.pause_stdin.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(50));
task::sleep(Duration::from_millis(50)).await;
}
}
pub fn resume_event_loop(&self) {
pub async fn resume_event_loop(&self) {
self.pause_stdin.store(false, Ordering::SeqCst);
thread::yield_now();
task::yield_now().await;
while self.pause_stdin.load(Ordering::SeqCst) {
thread::sleep(Duration::from_millis(50));
task::sleep(Duration::from_millis(50)).await;
}
self.handle.as_ref().unwrap().thread().unpark();
}
pub fn pause_key_capture(&self, terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) {
self.pause_event_loop();
task::block_on(self.pause_event_loop());
disable_raw_mode().unwrap();
execute!(io::stdout(), LeaveAlternateScreen, DisableMouseCapture).unwrap();
terminal.show_cursor().unwrap();
@ -170,7 +163,7 @@ impl Events {
pub fn resume_key_capture(&self, terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) {
execute!(io::stdout(), EnterAlternateScreen, EnableMouseCapture).unwrap();
enable_raw_mode().unwrap();
self.resume_event_loop();
task::block_on(self.resume_event_loop());
terminal.resize(terminal.size().unwrap()).unwrap();
}
}