diff --git a/asyncgit/src/diff.rs b/asyncgit/src/diff.rs index 1c45e05c..a375b743 100644 --- a/asyncgit/src/diff.rs +++ b/asyncgit/src/diff.rs @@ -3,7 +3,10 @@ use crossbeam_channel::Sender; use log::trace; use std::{ hash::Hash, - sync::{Arc, Mutex}, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, Mutex, + }, }; /// @@ -24,6 +27,7 @@ pub struct AsyncDiff { current: Arc>>, last: Arc>>>, sender: Sender, + pending: Arc, } impl AsyncDiff { @@ -33,6 +37,7 @@ impl AsyncDiff { current: Arc::new(Mutex::new(Request(0, None))), last: Arc::new(Mutex::new(None)), sender, + pending: Arc::new(AtomicUsize::new(0)), } } @@ -54,6 +59,11 @@ impl AsyncDiff { } } + /// + pub fn is_pending(&self) -> bool { + self.pending.load(Ordering::Relaxed) > 0 + } + /// pub fn request( &mut self, @@ -77,7 +87,10 @@ impl AsyncDiff { let arc_current = Arc::clone(&self.current); let arc_last = Arc::clone(&self.last); let sender = self.sender.clone(); + let arc_pending = Arc::clone(&self.pending); rayon_core::spawn(move || { + arc_pending.fetch_add(1, Ordering::Relaxed); + let res = sync::diff::get_diff(CWD, params.0.clone(), params.1); let mut notify = false; @@ -98,6 +111,8 @@ impl AsyncDiff { }); } + arc_pending.fetch_sub(1, Ordering::Relaxed); + if notify { sender .send(AsyncNotification::Diff) diff --git a/src/app.rs b/src/app.rs index 76303d9e..f40af11f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -229,12 +229,9 @@ impl App { self.do_quit } - fn can_focus_diff(&self) -> bool { - match self.focus { - Focus::WorkDir => self.index_wd.is_file_seleted(), - Focus::Stage => self.index.is_file_seleted(), - _ => false, - } + /// + pub fn any_work_pending(&self) -> bool { + self.git_diff.is_pending() } } @@ -278,6 +275,14 @@ impl App { None } + fn can_focus_diff(&self) -> bool { + match self.focus { + Focus::WorkDir => self.index_wd.is_file_seleted(), + Focus::Stage => self.index.is_file_seleted(), + _ => false, + } + } + fn update_commands(&mut self) { self.help.set_cmds(self.commands(true)); self.current_commands = self.commands(false); diff --git a/src/main.rs b/src/main.rs index 90b53d17..85d0a627 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod components; mod keys; mod poll; mod queue; +mod spinner; mod strings; mod ui; mod version; @@ -28,6 +29,7 @@ use log::error; use scopeguard::defer; use scopetime::scope_time; use simplelog::{Config, LevelFilter, WriteLogger}; +use spinner::Spinner; use std::{ env, fs, fs::File, @@ -40,6 +42,7 @@ use tui::{ }; static TICK_INTERVAL: Duration = Duration::from_secs(5); +static SPINNER_INTERVAL: Duration = Duration::from_millis(50); fn main() -> Result<()> { setup_logging(); @@ -65,28 +68,44 @@ fn main() -> Result<()> { set_panic_handlers(); let rx_input = poll::start_polling_thread(); - let ticker = tick(TICK_INTERVAL); + let spinner_ticker = tick(SPINNER_INTERVAL); app.update(); draw(&mut terminal, &mut app)?; + let mut spinner = Spinner::default(); + loop { - let events: Vec = - select_event(&rx_input, &rx_git, &ticker); + let events: Vec = select_event( + &rx_input, + &rx_git, + &ticker, + &spinner_ticker, + ); { scope_time!("loop"); + let mut needs_draw = true; + for e in events { match e { QueueEvent::InputEvent(ev) => app.event(ev), QueueEvent::Tick => app.update(), QueueEvent::GitEvent(ev) => app.update_git(ev), + QueueEvent::SpinnerUpdate => { + needs_draw = false; + spinner.update() + } } } - draw(&mut terminal, &mut app)?; + if needs_draw { + draw(&mut terminal, &mut app)?; + } + + spinner.draw(&mut terminal, app.any_work_pending())?; if app.is_quit() { break; @@ -112,6 +131,7 @@ fn select_event( rx_input: &Receiver>, rx_git: &Receiver, rx_ticker: &Receiver, + rx_spinner: &Receiver, ) -> Vec { let mut events: Vec = Vec::new(); @@ -120,6 +140,7 @@ fn select_event( sel.recv(rx_input); sel.recv(rx_git); sel.recv(rx_ticker); + sel.recv(rx_spinner); let oper = sel.select(); let index = oper.index(); @@ -132,7 +153,10 @@ fn select_event( 2 => oper .recv(rx_ticker) .map(|_| events.push(QueueEvent::Tick)), - _ => Ok(()), + 3 => oper + .recv(rx_spinner) + .map(|_| events.push(QueueEvent::SpinnerUpdate)), + _ => panic!("unknown select source"), } .unwrap(); diff --git a/src/poll.rs b/src/poll.rs index 031fdf27..dc13d0f3 100644 --- a/src/poll.rs +++ b/src/poll.rs @@ -7,6 +7,7 @@ use std::time::{Duration, Instant}; #[derive(Clone, Copy)] pub enum QueueEvent { Tick, + SpinnerUpdate, GitEvent(AsyncNotification), InputEvent(Event), } diff --git a/src/spinner.rs b/src/spinner.rs new file mode 100644 index 00000000..924aa977 --- /dev/null +++ b/src/spinner.rs @@ -0,0 +1,36 @@ +use std::io; +use tui::{backend::Backend, buffer::Cell, Terminal}; + +static SPINNER_CHARS: &[char] = &['|', '/', '-', '\\']; + +/// +#[derive(Default)] +pub struct Spinner { + idx: usize, +} + +impl Spinner { + /// + pub fn update(&mut self) { + self.idx += 1; + self.idx %= SPINNER_CHARS.len(); + } + + pub fn draw( + &self, + terminal: &mut Terminal, + pending: bool, + ) -> io::Result<()> { + let idx = self.idx; + + let c: Cell = Cell::default() + .set_char(if pending { SPINNER_CHARS[idx] } else { ' ' }) + .clone(); + terminal + .backend_mut() + .draw(vec![(0_u16, 0_u16, &c)].into_iter())?; + tui::backend::Backend::flush(terminal.backend_mut())?; + + Ok(()) + } +}