From 1c72d4cd2433ecd48af2570b7c78c3da3c1646cb Mon Sep 17 00:00:00 2001 From: Thomas Eppers Date: Sun, 19 Feb 2023 11:40:40 +0100 Subject: [PATCH] refreshing the ui pnly on input or when new data is available --- src/ui/mod.rs | 18 ------- src/ui/no_yaml_found.rs | 92 +++++++++++++++++++++----------- src/ui/yaml_found.rs | 115 +++++++++++++++++++++++++++------------- 3 files changed, 139 insertions(+), 86 deletions(-) diff --git a/src/ui/mod.rs b/src/ui/mod.rs index ed59a03..eb3805b 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -2,14 +2,10 @@ mod no_yaml_found; mod yaml_found; use anyhow::Result; -use termion::input::TermRead; use crate::widget::service_switcher; use crate::Opt; -use std::sync::mpsc; -use std::{io, thread}; - pub fn create_ui(opt: &Opt) -> Result<()> { let service_result = service_switcher::ServiceSwitcher::new(&opt.file); match service_result { @@ -19,17 +15,3 @@ pub fn create_ui(opt: &Opt) -> Result<()> { Ok(()) } - -/// create a thread for catching input and send them to core loop -pub fn spawn_stdin_channel() -> mpsc::Receiver { - let (tx, rx) = mpsc::channel::(); - - thread::spawn(move || loop { - let stdin = io::stdin(); - for c in stdin.keys() { - tx.send(c.unwrap()).unwrap(); - } - }); - thread::sleep(std::time::Duration::from_millis(64)); - rx -} diff --git a/src/ui/no_yaml_found.rs b/src/ui/no_yaml_found.rs index d344443..b3210ed 100644 --- a/src/ui/no_yaml_found.rs +++ b/src/ui/no_yaml_found.rs @@ -1,10 +1,12 @@ use anyhow::Result; use termion::event::Key; +use termion::input::TermRead; use termion::raw::IntoRawMode; use tui::backend::TermionBackend; use tui::layout::{Constraint, Direction, Layout}; use tui::Terminal; +use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::{io, thread}; @@ -49,37 +51,58 @@ impl std::iter::Iterator for State { } pub enum UiEvent { + Input(Key), + RefreshOnNewData, +} + +pub enum DeferredEvent { + Quit, NewRepo(String), TagPrevious, TagNext, - Quit, } impl Ui { + /// create a thread for catching input and send them to core loop + pub fn spawn_input_channel(sender: mpsc::Sender) { + thread::spawn(move || loop { + let stdin = io::stdin(); + for c in stdin.keys() { + sender.send(UiEvent::Input(c.unwrap())).unwrap(); + } + }); + } + #[tokio::main] - pub async fn work_requests(ui: Arc>, event: std::sync::mpsc::Receiver) { + pub async fn work_requests( + ui: Arc>, + events: mpsc::Receiver, + sender: mpsc::Sender, + ) { loop { - match event.recv() { - Ok(UiEvent::Quit) => break, - Ok(UiEvent::NewRepo(name)) => { + match events.recv() { + Ok(DeferredEvent::Quit) => break, + Ok(DeferredEvent::NewRepo(name)) => { { let mut ui = ui.lock().unwrap(); ui.tags = TagList::with_status("fetching new tags..."); + sender.send(UiEvent::RefreshOnNewData).unwrap(); } let list = async_tag_list::TagList::with_repo_name(name).await; let mut ui = ui.lock().unwrap(); ui.tags = list; } - Ok(UiEvent::TagPrevious) => { + Ok(DeferredEvent::TagPrevious) => { let mut ui = ui.lock().unwrap(); ui.tags.previous(); ui.details = ui.tags.create_detail_widget(); } - Ok(UiEvent::TagNext) => { + Ok(DeferredEvent::TagNext) => { let (fetched_new_tags, mut tags) = { let mut ui = ui.lock().unwrap(); if ui.tags.at_end_of_list() { ui.info.set_text("Fetching more tags..."); + sender.send(UiEvent::RefreshOnNewData).unwrap(); (true, ui.tags.clone()) } else { (false, ui.tags.clone()) @@ -98,6 +121,7 @@ impl Ui { ui.info.set_info(&e); } }; + sender.send(UiEvent::RefreshOnNewData).unwrap(); } } @@ -113,10 +137,12 @@ impl Ui { })); // spawn new thread that fetches information async - let (sender, receiver) = std::sync::mpsc::channel(); + let (sender, receiver) = mpsc::channel(); + let (deferred_sender, deferred_receiver) = mpsc::channel(); let ui_clone = ui.clone(); + let sender2 = sender.clone(); std::thread::spawn(move || { - Self::work_requests(ui_clone, receiver); + Self::work_requests(ui_clone, deferred_receiver, sender2); }); //setup tui @@ -125,7 +151,7 @@ impl Ui { let mut terminal = Terminal::new(backend)?; //setup input thread - let receiver = super::spawn_stdin_channel(); + Self::spawn_input_channel(sender); //core interaction loop 'core: loop { @@ -157,50 +183,56 @@ impl Ui { rect.render_widget(ui_data.details.render(), more_chunks[1]); rect.render_widget(ui_data.info.render(), chunks[2]); })?; + drop(ui_data); //handle input - match receiver.try_recv() { - Ok(Key::Ctrl('q')) => { - sender.send(UiEvent::Quit)?; + //wait before locking + let event = receiver.recv(); + let mut ui_data = ui.lock().unwrap(); + match event { + Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { + deferred_sender.send(DeferredEvent::Quit)?; break 'core; //quit program without saving } - Ok(Key::Char('\t')) => { + Ok(UiEvent::Input(Key::Char('\t'))) => { ui_data.state.next(); let state = ui_data.state.clone(); ui_data.info.set_info(&state); } - Ok(Key::Ctrl('r')) => { + Ok(UiEvent::Input(Key::Ctrl('r'))) => { ui_data.repo.confirm(); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } - Ok(Key::Char('\n')) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { ui_data.repo.confirm(); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } - Ok(Key::Backspace) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { ui_data.info.set_text("Editing Repository"); ui_data.repo.handle_input(Key::Backspace); } - Ok(Key::Up) | Ok(Key::Char('k')) if ui_data.state == State::SelectTag => { - sender.send(UiEvent::TagPrevious).unwrap(); + Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) + if ui_data.state == State::SelectTag => + { + deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); ui_data.details = ui_data.tags.create_detail_widget(); } - Ok(Key::Down) | Ok(Key::Char('j')) if ui_data.state == State::SelectTag => { - sender.send(UiEvent::TagNext).unwrap(); + Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) + if ui_data.state == State::SelectTag => + { + deferred_sender.send(DeferredEvent::TagNext).unwrap(); ui_data.details = ui_data.tags.create_detail_widget(); } - Ok(Key::Char(key)) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { ui_data.info.set_text("Editing Repository"); ui_data.repo.handle_input(Key::Char(key)); } _ => {} } - - // release lock of ui for other threads - drop(ui_data); - - //sleep for 32ms (30 fps) - thread::sleep(std::time::Duration::from_millis(32)); } terminal.clear()?; diff --git a/src/ui/yaml_found.rs b/src/ui/yaml_found.rs index 498df55..fbd571b 100644 --- a/src/ui/yaml_found.rs +++ b/src/ui/yaml_found.rs @@ -1,10 +1,12 @@ use anyhow::Result; use termion::event::Key; +use termion::input::TermRead; use termion::raw::IntoRawMode; use tui::backend::TermionBackend; use tui::layout::{Constraint, Direction, Layout}; use tui::Terminal; +use std::sync::mpsc; use std::sync::{Arc, Mutex}; use std::{io, thread}; @@ -55,37 +57,58 @@ impl std::iter::Iterator for State { } pub enum UiEvent { + Input(Key), + RefreshOnNewData, +} + +pub enum DeferredEvent { + Quit, NewRepo(String), TagPrevious, TagNext, - Quit, } impl Ui { + /// create a thread for catching input and send them to core loop + pub fn spawn_input_channel(sender: mpsc::Sender) { + thread::spawn(move || loop { + let stdin = io::stdin(); + for c in stdin.keys() { + sender.send(UiEvent::Input(c.unwrap())).unwrap(); + } + }); + } + #[tokio::main] - pub async fn work_requests(ui: Arc>, event: std::sync::mpsc::Receiver) { + pub async fn work_requests( + ui: Arc>, + events: mpsc::Receiver, + sender: mpsc::Sender, + ) { loop { - match event.recv() { - Ok(UiEvent::Quit) => break, - Ok(UiEvent::NewRepo(name)) => { + match events.recv() { + Ok(DeferredEvent::Quit) => break, + Ok(DeferredEvent::NewRepo(name)) => { { let mut ui = ui.lock().unwrap(); ui.tags = TagList::with_status("Fetching new tags..."); + sender.send(UiEvent::RefreshOnNewData).unwrap(); } let list = TagList::with_repo_name(name).await; let mut ui = ui.lock().unwrap(); ui.tags = list; } - Ok(UiEvent::TagPrevious) => { + Ok(DeferredEvent::TagPrevious) => { let mut ui = ui.lock().unwrap(); ui.tags.previous(); ui.details = ui.tags.create_detail_widget(); } - Ok(UiEvent::TagNext) => { + Ok(DeferredEvent::TagNext) => { let (fetched_new_tags, mut tags) = { let mut ui = ui.lock().unwrap(); if ui.tags.at_end_of_list() { ui.info.set_text("Fetching more tags..."); + sender.send(UiEvent::RefreshOnNewData).unwrap(); (true, ui.tags.clone()) } else { (false, ui.tags.clone()) @@ -104,6 +127,7 @@ impl Ui { ui.info.set_info(&e); } }; + sender.send(UiEvent::RefreshOnNewData).unwrap(); } } @@ -120,10 +144,12 @@ impl Ui { })); // spawn new thread that fetches information async - let (sender, receiver) = std::sync::mpsc::channel(); + let (sender, receiver) = mpsc::channel(); + let (deferred_sender, deferred_receiver) = mpsc::channel(); let ui_clone = ui.clone(); + let sender2 = sender.clone(); std::thread::spawn(move || { - Self::work_requests(ui_clone, receiver); + Self::work_requests(ui_clone, deferred_receiver, sender2); }); //setup tui @@ -132,12 +158,12 @@ impl Ui { let mut terminal = Terminal::new(backend)?; //setup input thread - let receiver = super::spawn_stdin_channel(); + Self::spawn_input_channel(sender); //core interaction loop 'core: loop { - let mut ui_data = ui.lock().unwrap(); //draw + let mut ui_data = ui.lock().unwrap(); terminal.draw(|rect| { let chunks = Layout::default() .direction(Direction::Vertical) @@ -175,30 +201,36 @@ impl Ui { rect.render_widget(ui_data.details.render(), more_chunks[2]); rect.render_widget(ui_data.info.render(), chunks[2]); })?; + drop(ui_data); - //handle input - match receiver.try_recv() { - Ok(Key::Ctrl('q')) => { - sender.send(UiEvent::Quit)?; + //handle events + //wait before locking + let event = receiver.recv(); + let mut ui_data = ui.lock().unwrap(); + match event { + Ok(UiEvent::Input(Key::Ctrl('q'))) | Ok(UiEvent::Input(Key::Ctrl('c'))) => { + deferred_sender.send(DeferredEvent::Quit)?; break 'core; //quit program without saving } - Ok(Key::Char('\t')) => { + Ok(UiEvent::Input(Key::Char('\t'))) => { ui_data.state.next(); let state = ui_data.state.clone(); ui_data.info.set_info(&state); } - Ok(Key::Ctrl('s')) => match ui_data.services.save() { + Ok(UiEvent::Input(Key::Ctrl('s'))) => match ui_data.services.save() { Err(e) => { ui_data.info.set_info(&format!("{}", e)); continue; } Ok(_) => ui_data.info.set_text("Saved compose file"), }, - Ok(Key::Ctrl('r')) => { + Ok(UiEvent::Input(Key::Ctrl('r'))) => { ui_data.repo.confirm(); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } - Ok(Key::Char('\n')) if ui_data.state == State::SelectTag => { + Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::SelectTag => { let mut repo = ui_data.repo.get(); let tag = match ui_data.tags.get_selected() { Err(async_tag_list::Error::NextPageSelected) => continue, @@ -212,15 +244,17 @@ impl Ui { repo.push_str(&tag); ui_data.services.change_current_line(repo); } - Ok(Key::Char('\n')) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Char('\n'))) if ui_data.state == State::EditRepo => { ui_data.repo.confirm(); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } - Ok(Key::Backspace) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Backspace)) if ui_data.state == State::EditRepo => { ui_data.info.set_text("Editing Repository"); ui_data.repo.handle_input(Key::Backspace); } - Ok(Key::Up) | Ok(Key::Char('k')) + Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) if ui_data.state == State::SelectService && ui_data.services.find_previous_match() => { @@ -235,11 +269,13 @@ impl Ui { Ok(s) => s, }; ui_data.repo.set(repo.to_string()); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } } } - Ok(Key::Down) | Ok(Key::Char('j')) + Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) if ui_data.state == State::SelectService && ui_data.services.find_next_match() => { @@ -254,27 +290,30 @@ impl Ui { Ok(s) => s, }; ui_data.repo.set(repo.to_string()); - sender.send(UiEvent::NewRepo(ui_data.repo.get())).unwrap(); + deferred_sender + .send(DeferredEvent::NewRepo(ui_data.repo.get())) + .unwrap(); } } } - Ok(Key::Up) | Ok(Key::Char('k')) if ui_data.state == State::SelectTag => { - sender.send(UiEvent::TagPrevious).unwrap(); + Ok(UiEvent::Input(Key::Up)) | Ok(UiEvent::Input(Key::Char('k'))) + if ui_data.state == State::SelectTag => + { + deferred_sender.send(DeferredEvent::TagPrevious).unwrap(); } - Ok(Key::Down) | Ok(Key::Char('j')) if ui_data.state == State::SelectTag => { - sender.send(UiEvent::TagNext).unwrap(); + Ok(UiEvent::Input(Key::Down)) | Ok(UiEvent::Input(Key::Char('j'))) + if ui_data.state == State::SelectTag => + { + deferred_sender.send(DeferredEvent::TagNext).unwrap(); } - Ok(Key::Char(key)) if ui_data.state == State::EditRepo => { + Ok(UiEvent::Input(Key::Char(key))) if ui_data.state == State::EditRepo => { ui_data.info.set_text("Editing Repository"); ui_data.repo.handle_input(Key::Char(key)); } - _ => (), + Ok(UiEvent::Input(_)) => {} + Ok(UiEvent::RefreshOnNewData) => {} + Err(_) => {} } - - drop(ui_data); - - //sleep for 32ms (30 fps) - thread::sleep(std::time::Duration::from_millis(32)); } terminal.clear()?;