Skip to content

Commit

Permalink
feat: responsive and buttery-smooth UI while scanning in interactive…
Browse files Browse the repository at this point in the history
…mode. (#209)

Using `dua i` the GUI would populate and is fully usable even while the scan
is in progress, which is fantastic when scanning big disks which can take several minutes.

However, previously is was quite janky as the refresh loop was bound to receiving
entries to process, which sometimes stalled for many seconds.

Now the GUI refresh is uncoupled from receiving traversal entries, and it will
update when the user presses a key or 250ms pass without any input, causing
it to respond immediately.

Thanks so much for contributing, [@unixzii](https://github /unixzii)!
  • Loading branch information
Byron committed Jan 5, 2024
2 parents ad7c77a + 0651cae commit3c8a31b
Show file tree
Hide file tree
Showing 6 changed files with 227 additions and 137 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ bstr = "1.8.0"
simplelog="0.12.1"
log="0.4.20"
log-panics= {version="2",features= ["with-backtrace"]}
crossbeam="0.8"

[[bin]]
name="dua"
Expand Down
77 changes: 43 additions & 34 deletions src/interactive/app/eventloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ use crate::interactive::{
SortMode,
};
useanyhow::Result;
usecrossbeam::channel::Receiver;
usecrosstermion::crossterm::event::{KeyCode,KeyEvent,KeyEventKind,KeyModifiers};
usecrosstermion::input::{input_channel,Event};
usecrosstermion::input::Event;
usedua::{
traverse::{EntryData,Traversal},
WalkOptions,WalkResult,
Expand All @@ -16,6 +17,7 @@ use std::path::PathBuf;
usetui::backend::Backend;
usetui_react::Terminal;

usesuper::input::input_channel;
usesuper::tree_view::TreeView;

#[derive(Default,Copy,Clone,PartialEq)]
Expand Down Expand Up @@ -343,7 +345,7 @@ pub struct TerminalApp {
pubwindow:MainWindow,
}

typeKeyboardInputAndApp=(std::sync::mpsc::Receiver<Event>,TerminalApp);
typeKeyboardInputAndApp=(crossbeam::channel::Receiver<Event>,TerminalApp);

implTerminalApp{
pubfnrefresh_view<B>(&mutself,terminal:&mutTerminal<B>)
Expand Down Expand Up @@ -397,56 +399,63 @@ impl TerminalApp {
letmutwindow =MainWindow::default();
letkeys_rx =matchmode{
Interaction::None=>{
let(_,keys_rx)=std::sync::mpsc::channel();
let(_,keys_rx)=crossbeam::channel::unbounded();
keys_rx
}
Interaction::Full=>input_channel(),
};

letfetch_buffered_key_events = ||{
#[inline]
fnfetch_buffered_key_events(keys_rx:&Receiver<Event>)->Vec<Event>{
letmutkeys =Vec::new();
whileletOk(key)= keys_rx.try_recv(){
keys.push(key);
}
keys
};
}

letmutstate =AppState{
is_scanning:true,
..Default::default()
};
letmutreceived_events =false;
lettraversal =Traversal::from_walk(options,input_paths,|traversal|{
if!received_events{
state.navigation_mut().view_root= traversal.root_index;
}
state.entries=sorted_entries(
&traversal.tree,
state.navigation().view_root,
state.sorting,
state.glob_root(),
);
if!received_events{
state.navigation_mut().selected= state.entries.first().map(|b| b.index);
}
state.reset_message();// force "scanning" to appear

letevents =fetch_buffered_key_events();
received_events |=!events.is_empty();
lettraversal =
Traversal::from_walk(options,input_paths,&keys_rx,|traversal,event|{
if!received_events{
state.navigation_mut().view_root= traversal.root_index;
}
state.entries=sorted_entries(
&traversal.tree,
state.navigation().view_root,
state.sorting,
state.glob_root(),
);
if!received_events{
state.navigation_mut().selected= state.entries.first().map(|b| b.index);
}
state.reset_message();// force "scanning" to appear

letshould_exit =matchstate.process_events(
&mutwindow,
traversal,
&mutdisplay,
terminal,
events.into_iter(),
)?{
ProcessingResult::ExitRequested(_)=>true,
ProcessingResult::Finished(_)=>false,
};
letmutevents =fetch_buffered_key_events(&keys_rx);
ifletSome(event)= event{
// This update is triggered by a user event, insert it
// before any events fetched later.
events.insert(0,event);
}
received_events |=!events.is_empty();

letshould_exit =matchstate.process_events(
&mutwindow,
traversal,
&mutdisplay,
terminal,
events.into_iter(),
)?{
ProcessingResult::ExitRequested(_)=>true,
ProcessingResult::Finished(_)=>false,
};

Ok(should_exit)
})?;
Ok(should_exit)
})?;

lettraversal =matchtraversal{
Some(t)=> t,
Expand Down
32 changes: 32 additions & 0 deletions src/interactive/app/input.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
usecrossbeam::channel::Receiver;
pubusecrosstermion::crossterm::event::Event;

enumAction<T>{
Continue,
Result(Result<T,std::io::Error>),
}

fncontinue_on_interrupt<T>(result:Result<T,std::io::Error>)->Action<T>{
matchresult{
Ok(v)=>Action::Result(Ok(v)),
Err(err)iferr.kind()== std::io::ErrorKind::Interrupted=>Action::Continue,
Err(err)=>Action::Result(Err(err)),
}
}

pubfninput_channel()->Receiver<Event>{
let(key_send,key_receive)= crossbeam::channel::bounded(0);
std::thread::spawn(move|| ->Result<(),std::io::Error>{
loop{
letevent =matchcontinue_on_interrupt(crosstermion::crossterm::event::read()){
Action::Continue=>continue,
Action::Result(res)=> res?,
};
ifkey_send.send(event).is_err(){
break;
}
}
Ok(())
});
key_receive
}
1 change: 1 addition & 0 deletions src/interactive/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod bytevis;
modcommon;
modeventloop;
modhandlers;
modinput;
modnavigation;
pubmodtree_view;

Expand Down
Loading

0 comments on commit3c8a31b

Please sign into comment.