mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 09:28:21 +00:00
spinner showing pending background work
This commit is contained in:
parent
7c678a941a
commit
f502c81187
5 changed files with 93 additions and 12 deletions
|
|
@ -3,7 +3,10 @@ use crossbeam_channel::Sender;
|
||||||
use log::trace;
|
use log::trace;
|
||||||
use std::{
|
use std::{
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
sync::{Arc, Mutex},
|
sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc, Mutex,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|
@ -24,6 +27,7 @@ pub struct AsyncDiff {
|
||||||
current: Arc<Mutex<Request<u64, FileDiff>>>,
|
current: Arc<Mutex<Request<u64, FileDiff>>>,
|
||||||
last: Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
|
last: Arc<Mutex<Option<LastResult<DiffParams, FileDiff>>>>,
|
||||||
sender: Sender<AsyncNotification>,
|
sender: Sender<AsyncNotification>,
|
||||||
|
pending: Arc<AtomicUsize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncDiff {
|
impl AsyncDiff {
|
||||||
|
|
@ -33,6 +37,7 @@ impl AsyncDiff {
|
||||||
current: Arc::new(Mutex::new(Request(0, None))),
|
current: Arc::new(Mutex::new(Request(0, None))),
|
||||||
last: Arc::new(Mutex::new(None)),
|
last: Arc::new(Mutex::new(None)),
|
||||||
sender,
|
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(
|
pub fn request(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -77,7 +87,10 @@ impl AsyncDiff {
|
||||||
let arc_current = Arc::clone(&self.current);
|
let arc_current = Arc::clone(&self.current);
|
||||||
let arc_last = Arc::clone(&self.last);
|
let arc_last = Arc::clone(&self.last);
|
||||||
let sender = self.sender.clone();
|
let sender = self.sender.clone();
|
||||||
|
let arc_pending = Arc::clone(&self.pending);
|
||||||
rayon_core::spawn(move || {
|
rayon_core::spawn(move || {
|
||||||
|
arc_pending.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
||||||
let res =
|
let res =
|
||||||
sync::diff::get_diff(CWD, params.0.clone(), params.1);
|
sync::diff::get_diff(CWD, params.0.clone(), params.1);
|
||||||
let mut notify = false;
|
let mut notify = false;
|
||||||
|
|
@ -98,6 +111,8 @@ impl AsyncDiff {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
arc_pending.fetch_sub(1, Ordering::Relaxed);
|
||||||
|
|
||||||
if notify {
|
if notify {
|
||||||
sender
|
sender
|
||||||
.send(AsyncNotification::Diff)
|
.send(AsyncNotification::Diff)
|
||||||
|
|
|
||||||
17
src/app.rs
17
src/app.rs
|
|
@ -229,12 +229,9 @@ impl App {
|
||||||
self.do_quit
|
self.do_quit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn can_focus_diff(&self) -> bool {
|
///
|
||||||
match self.focus {
|
pub fn any_work_pending(&self) -> bool {
|
||||||
Focus::WorkDir => self.index_wd.is_file_seleted(),
|
self.git_diff.is_pending()
|
||||||
Focus::Stage => self.index.is_file_seleted(),
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -278,6 +275,14 @@ impl App {
|
||||||
None
|
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) {
|
fn update_commands(&mut self) {
|
||||||
self.help.set_cmds(self.commands(true));
|
self.help.set_cmds(self.commands(true));
|
||||||
self.current_commands = self.commands(false);
|
self.current_commands = self.commands(false);
|
||||||
|
|
|
||||||
34
src/main.rs
34
src/main.rs
|
|
@ -8,6 +8,7 @@ mod components;
|
||||||
mod keys;
|
mod keys;
|
||||||
mod poll;
|
mod poll;
|
||||||
mod queue;
|
mod queue;
|
||||||
|
mod spinner;
|
||||||
mod strings;
|
mod strings;
|
||||||
mod ui;
|
mod ui;
|
||||||
mod version;
|
mod version;
|
||||||
|
|
@ -28,6 +29,7 @@ use log::error;
|
||||||
use scopeguard::defer;
|
use scopeguard::defer;
|
||||||
use scopetime::scope_time;
|
use scopetime::scope_time;
|
||||||
use simplelog::{Config, LevelFilter, WriteLogger};
|
use simplelog::{Config, LevelFilter, WriteLogger};
|
||||||
|
use spinner::Spinner;
|
||||||
use std::{
|
use std::{
|
||||||
env, fs,
|
env, fs,
|
||||||
fs::File,
|
fs::File,
|
||||||
|
|
@ -40,6 +42,7 @@ use tui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
static TICK_INTERVAL: Duration = Duration::from_secs(5);
|
static TICK_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
|
static SPINNER_INTERVAL: Duration = Duration::from_millis(50);
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
setup_logging();
|
setup_logging();
|
||||||
|
|
@ -65,28 +68,44 @@ fn main() -> Result<()> {
|
||||||
set_panic_handlers();
|
set_panic_handlers();
|
||||||
|
|
||||||
let rx_input = poll::start_polling_thread();
|
let rx_input = poll::start_polling_thread();
|
||||||
|
|
||||||
let ticker = tick(TICK_INTERVAL);
|
let ticker = tick(TICK_INTERVAL);
|
||||||
|
let spinner_ticker = tick(SPINNER_INTERVAL);
|
||||||
|
|
||||||
app.update();
|
app.update();
|
||||||
draw(&mut terminal, &mut app)?;
|
draw(&mut terminal, &mut app)?;
|
||||||
|
|
||||||
|
let mut spinner = Spinner::default();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let events: Vec<QueueEvent> =
|
let events: Vec<QueueEvent> = select_event(
|
||||||
select_event(&rx_input, &rx_git, &ticker);
|
&rx_input,
|
||||||
|
&rx_git,
|
||||||
|
&ticker,
|
||||||
|
&spinner_ticker,
|
||||||
|
);
|
||||||
|
|
||||||
{
|
{
|
||||||
scope_time!("loop");
|
scope_time!("loop");
|
||||||
|
|
||||||
|
let mut needs_draw = true;
|
||||||
|
|
||||||
for e in events {
|
for e in events {
|
||||||
match e {
|
match e {
|
||||||
QueueEvent::InputEvent(ev) => app.event(ev),
|
QueueEvent::InputEvent(ev) => app.event(ev),
|
||||||
QueueEvent::Tick => app.update(),
|
QueueEvent::Tick => app.update(),
|
||||||
QueueEvent::GitEvent(ev) => app.update_git(ev),
|
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() {
|
if app.is_quit() {
|
||||||
break;
|
break;
|
||||||
|
|
@ -112,6 +131,7 @@ fn select_event(
|
||||||
rx_input: &Receiver<Vec<QueueEvent>>,
|
rx_input: &Receiver<Vec<QueueEvent>>,
|
||||||
rx_git: &Receiver<AsyncNotification>,
|
rx_git: &Receiver<AsyncNotification>,
|
||||||
rx_ticker: &Receiver<Instant>,
|
rx_ticker: &Receiver<Instant>,
|
||||||
|
rx_spinner: &Receiver<Instant>,
|
||||||
) -> Vec<QueueEvent> {
|
) -> Vec<QueueEvent> {
|
||||||
let mut events: Vec<QueueEvent> = Vec::new();
|
let mut events: Vec<QueueEvent> = Vec::new();
|
||||||
|
|
||||||
|
|
@ -120,6 +140,7 @@ fn select_event(
|
||||||
sel.recv(rx_input);
|
sel.recv(rx_input);
|
||||||
sel.recv(rx_git);
|
sel.recv(rx_git);
|
||||||
sel.recv(rx_ticker);
|
sel.recv(rx_ticker);
|
||||||
|
sel.recv(rx_spinner);
|
||||||
|
|
||||||
let oper = sel.select();
|
let oper = sel.select();
|
||||||
let index = oper.index();
|
let index = oper.index();
|
||||||
|
|
@ -132,7 +153,10 @@ fn select_event(
|
||||||
2 => oper
|
2 => oper
|
||||||
.recv(rx_ticker)
|
.recv(rx_ticker)
|
||||||
.map(|_| events.push(QueueEvent::Tick)),
|
.map(|_| events.push(QueueEvent::Tick)),
|
||||||
_ => Ok(()),
|
3 => oper
|
||||||
|
.recv(rx_spinner)
|
||||||
|
.map(|_| events.push(QueueEvent::SpinnerUpdate)),
|
||||||
|
_ => panic!("unknown select source"),
|
||||||
}
|
}
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use std::time::{Duration, Instant};
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub enum QueueEvent {
|
pub enum QueueEvent {
|
||||||
Tick,
|
Tick,
|
||||||
|
SpinnerUpdate,
|
||||||
GitEvent(AsyncNotification),
|
GitEvent(AsyncNotification),
|
||||||
InputEvent(Event),
|
InputEvent(Event),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
src/spinner.rs
Normal file
36
src/spinner.rs
Normal file
|
|
@ -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<B: Backend>(
|
||||||
|
&self,
|
||||||
|
terminal: &mut Terminal<B>,
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue