mirror of
https://github.com/gitui-org/gitui
synced 2026-05-24 01:18:21 +00:00
allow input polling to be paused (#114)
This commit is contained in:
parent
598b98b2bd
commit
4bcfa61087
3 changed files with 149 additions and 92 deletions
95
src/app.rs
95
src/app.rs
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
StashMsgComponent,
|
||||
},
|
||||
keys,
|
||||
poll::InputEvent,
|
||||
queue::{Action, InternalEvent, NeedsUpdate, Queue},
|
||||
strings,
|
||||
tabs::{Revlog, StashList, Stashing, Status},
|
||||
|
|
@ -117,61 +118,63 @@ impl App {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn event(&mut self, ev: Event) -> Result<()> {
|
||||
pub fn event(&mut self, ev: InputEvent) -> Result<()> {
|
||||
log::trace!("event: {:?}", ev);
|
||||
|
||||
if self.check_quit_key(ev) {
|
||||
return Ok(());
|
||||
}
|
||||
if let InputEvent::Input(ev) = ev {
|
||||
if self.check_quit_key(ev) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut flags = NeedsUpdate::empty();
|
||||
let mut flags = NeedsUpdate::empty();
|
||||
|
||||
if event_pump(ev, self.components_mut().as_mut_slice())? {
|
||||
flags.insert(NeedsUpdate::COMMANDS);
|
||||
} else if let Event::Key(k) = ev {
|
||||
let new_flags = match k {
|
||||
keys::TAB_TOGGLE => {
|
||||
self.toggle_tabs(false)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
keys::TAB_TOGGLE_REVERSE
|
||||
| keys::TAB_TOGGLE_REVERSE_WINDOWS => {
|
||||
self.toggle_tabs(true)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
keys::TAB_1
|
||||
| keys::TAB_2
|
||||
| keys::TAB_3
|
||||
| keys::TAB_4 => {
|
||||
self.switch_tab(k)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
if event_pump(ev, self.components_mut().as_mut_slice())? {
|
||||
flags.insert(NeedsUpdate::COMMANDS);
|
||||
} else if let Event::Key(k) = ev {
|
||||
let new_flags = match k {
|
||||
keys::TAB_TOGGLE => {
|
||||
self.toggle_tabs(false)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
keys::TAB_TOGGLE_REVERSE
|
||||
| keys::TAB_TOGGLE_REVERSE_WINDOWS => {
|
||||
self.toggle_tabs(true)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
keys::TAB_1
|
||||
| keys::TAB_2
|
||||
| keys::TAB_3
|
||||
| keys::TAB_4 => {
|
||||
self.switch_tab(k)?;
|
||||
NeedsUpdate::COMMANDS
|
||||
}
|
||||
|
||||
keys::CMD_BAR_TOGGLE => {
|
||||
self.cmdbar.toggle_more();
|
||||
NeedsUpdate::empty()
|
||||
}
|
||||
keys::CMD_BAR_TOGGLE => {
|
||||
self.cmdbar.toggle_more();
|
||||
NeedsUpdate::empty()
|
||||
}
|
||||
|
||||
_ => NeedsUpdate::empty(),
|
||||
};
|
||||
_ => NeedsUpdate::empty(),
|
||||
};
|
||||
|
||||
flags.insert(new_flags);
|
||||
}
|
||||
|
||||
let new_flags = self.process_queue()?;
|
||||
flags.insert(new_flags);
|
||||
}
|
||||
|
||||
let new_flags = self.process_queue()?;
|
||||
flags.insert(new_flags);
|
||||
|
||||
if flags.contains(NeedsUpdate::ALL) {
|
||||
self.update()?;
|
||||
}
|
||||
//TODO: make this a queue event?
|
||||
//NOTE: set when any tree component changed selection
|
||||
if flags.contains(NeedsUpdate::DIFF) {
|
||||
self.status_tab.update_diff()?;
|
||||
self.inspect_commit_popup.update_diff()?;
|
||||
}
|
||||
if flags.contains(NeedsUpdate::COMMANDS) {
|
||||
self.update_commands();
|
||||
if flags.contains(NeedsUpdate::ALL) {
|
||||
self.update()?;
|
||||
}
|
||||
//TODO: make this a queue event?
|
||||
//NOTE: set when any tree component changed selection
|
||||
if flags.contains(NeedsUpdate::DIFF) {
|
||||
self.status_tab.update_diff()?;
|
||||
self.inspect_commit_popup.update_diff()?;
|
||||
}
|
||||
if flags.contains(NeedsUpdate::COMMANDS) {
|
||||
self.update_commands();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
24
src/main.rs
24
src/main.rs
|
|
@ -21,7 +21,7 @@ mod tabs;
|
|||
mod ui;
|
||||
mod version;
|
||||
|
||||
use crate::{app::App, poll::QueueEvent};
|
||||
use crate::app::App;
|
||||
use anyhow::{anyhow, Result};
|
||||
use asyncgit::AsyncNotification;
|
||||
use backtrace::Backtrace;
|
||||
|
|
@ -37,6 +37,7 @@ use crossterm::{
|
|||
},
|
||||
ExecutableCommand,
|
||||
};
|
||||
use poll::{Input, InputEvent};
|
||||
use scopeguard::defer;
|
||||
use scopetime::scope_time;
|
||||
use simplelog::{Config, LevelFilter, WriteLogger};
|
||||
|
|
@ -58,6 +59,15 @@ use tui::{
|
|||
static TICK_INTERVAL: Duration = Duration::from_secs(5);
|
||||
static SPINNER_INTERVAL: Duration = Duration::from_millis(50);
|
||||
|
||||
///
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum QueueEvent {
|
||||
Tick,
|
||||
SpinnerUpdate,
|
||||
GitEvent(AsyncNotification),
|
||||
InputEvent(InputEvent),
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
process_cmdline()?;
|
||||
|
||||
|
|
@ -82,7 +92,8 @@ fn main() -> Result<()> {
|
|||
|
||||
let mut app = App::new(&tx_git);
|
||||
|
||||
let rx_input = poll::start_polling_thread();
|
||||
let mut input = Input::new();
|
||||
let rx_input = input.receiver();
|
||||
let ticker = tick(TICK_INTERVAL);
|
||||
let spinner_ticker = tick(SPINNER_INTERVAL);
|
||||
|
||||
|
|
@ -116,6 +127,9 @@ fn main() -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
//TODO: disable input polling while external editor open
|
||||
input.set_polling(!app.any_work_pending());
|
||||
|
||||
if needs_draw {
|
||||
draw(&mut terminal, &mut app)?;
|
||||
}
|
||||
|
|
@ -160,7 +174,7 @@ fn valid_path() -> Result<bool> {
|
|||
}
|
||||
|
||||
fn select_event(
|
||||
rx_input: &Receiver<Vec<QueueEvent>>,
|
||||
rx_input: &Receiver<InputEvent>,
|
||||
rx_git: &Receiver<AsyncNotification>,
|
||||
rx_ticker: &Receiver<Instant>,
|
||||
rx_spinner: &Receiver<Instant>,
|
||||
|
|
@ -178,7 +192,9 @@ fn select_event(
|
|||
let index = oper.index();
|
||||
|
||||
match index {
|
||||
0 => oper.recv(rx_input).map(|inputs| events.extend(inputs)),
|
||||
0 => oper
|
||||
.recv(rx_input)
|
||||
.map(|input| events.push(QueueEvent::InputEvent(input))),
|
||||
1 => oper
|
||||
.recv(rx_git)
|
||||
.map(|ev| events.push(QueueEvent::GitEvent(ev))),
|
||||
|
|
|
|||
122
src/poll.rs
122
src/poll.rs
|
|
@ -1,59 +1,97 @@
|
|||
use asyncgit::AsyncNotification;
|
||||
use crossbeam_channel::{unbounded, Receiver};
|
||||
use crossterm::event::{self, Event};
|
||||
use std::time::{Duration, Instant};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
static POLL_DURATION: Duration = Duration::from_millis(1000);
|
||||
|
||||
///
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum QueueEvent {
|
||||
Tick,
|
||||
SpinnerUpdate,
|
||||
GitEvent(AsyncNotification),
|
||||
InputEvent(Event),
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum InputState {
|
||||
Paused,
|
||||
Polling,
|
||||
}
|
||||
|
||||
static MAX_POLL_DURATION: Duration = Duration::from_secs(2);
|
||||
static MIN_POLL_DURATION: Duration = Duration::from_millis(5);
|
||||
static MAX_BATCHING_DURATION: Duration = Duration::from_millis(25);
|
||||
///
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum InputEvent {
|
||||
Input(Event),
|
||||
State(InputState),
|
||||
}
|
||||
|
||||
///
|
||||
pub fn start_polling_thread() -> Receiver<Vec<QueueEvent>> {
|
||||
let (tx, rx) = unbounded();
|
||||
pub struct Input {
|
||||
desired_state: Arc<AtomicBool>,
|
||||
receiver: Receiver<InputEvent>,
|
||||
}
|
||||
|
||||
rayon_core::spawn(move || {
|
||||
let mut last_send = Instant::now();
|
||||
let mut batch = Vec::new();
|
||||
impl Input {
|
||||
///
|
||||
pub fn new() -> Self {
|
||||
let (tx, rx) = unbounded();
|
||||
|
||||
loop {
|
||||
let timeout = if batch.is_empty() {
|
||||
MAX_POLL_DURATION
|
||||
} else {
|
||||
MIN_POLL_DURATION
|
||||
};
|
||||
if let Some(e) =
|
||||
poll(timeout).expect("failed to pull events.")
|
||||
{
|
||||
batch.push(QueueEvent::InputEvent(e));
|
||||
let desired_state = Arc::new(AtomicBool::new(true));
|
||||
|
||||
let arc_desired = Arc::clone(&desired_state);
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut current_state = true;
|
||||
loop {
|
||||
//TODO: use condvar to not busy wait
|
||||
if arc_desired.load(Ordering::Relaxed) {
|
||||
if !current_state {
|
||||
tx.send(InputEvent::State(
|
||||
InputState::Polling,
|
||||
))
|
||||
.expect("send failed");
|
||||
}
|
||||
current_state = true;
|
||||
|
||||
if let Some(e) = Self::poll(POLL_DURATION)
|
||||
.expect("failed to pull events.")
|
||||
{
|
||||
tx.send(InputEvent::Input(e))
|
||||
.expect("send input event failed");
|
||||
}
|
||||
} else {
|
||||
if current_state {
|
||||
tx.send(InputEvent::State(
|
||||
InputState::Paused,
|
||||
))
|
||||
.expect("send failed");
|
||||
}
|
||||
current_state = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if !batch.is_empty()
|
||||
&& last_send.elapsed() > MAX_BATCHING_DURATION
|
||||
{
|
||||
tx.send(batch).expect("send input event failed");
|
||||
batch = Vec::new();
|
||||
last_send = Instant::now();
|
||||
}
|
||||
Self {
|
||||
receiver: rx,
|
||||
desired_state,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
rx
|
||||
}
|
||||
///
|
||||
pub fn receiver(&self) -> Receiver<InputEvent> {
|
||||
self.receiver.clone()
|
||||
}
|
||||
|
||||
///
|
||||
fn poll(dur: Duration) -> anyhow::Result<Option<Event>> {
|
||||
if event::poll(dur)? {
|
||||
Ok(Some(event::read()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
///
|
||||
pub fn set_polling(&mut self, enabled: bool) {
|
||||
self.desired_state.store(enabled, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn poll(dur: Duration) -> anyhow::Result<Option<Event>> {
|
||||
if event::poll(dur)? {
|
||||
Ok(Some(event::read()?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue