mirror of
https://github.com/gitui-org/gitui
synced 2026-05-23 17:08:21 +00:00
make fetching tree files async
This commit is contained in:
parent
bc15b5d550
commit
8e8c5fad55
7 changed files with 185 additions and 45 deletions
|
|
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Fixes
|
||||
* improve performance by requesting branches info asynchronous ([92f63d1](https://github.com/extrawurst/gitui/commit/92f63d107c1dca1f10139668ff5b3ca752261b0f))
|
||||
* fix app startup delay due to using file watcher ([#1436](https://github.com/extrawurst/gitui/issues/1436))
|
||||
* make git tree file fetch async ([#734](https://github.com/extrawurst/gitui/issues/734))
|
||||
|
||||
## [0.22.0] - 2022-11-19
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ mod revlog;
|
|||
mod status;
|
||||
pub mod sync;
|
||||
mod tags;
|
||||
mod treefiles;
|
||||
|
||||
pub use crate::{
|
||||
blame::{AsyncBlame, BlameParams},
|
||||
|
|
@ -61,6 +62,7 @@ pub use crate::{
|
|||
status::{StatusItem, StatusItemType},
|
||||
},
|
||||
tags::AsyncTags,
|
||||
treefiles::AsyncTreeFilesJob,
|
||||
};
|
||||
pub use git2::message_prettify;
|
||||
use std::{
|
||||
|
|
@ -99,6 +101,8 @@ pub enum AsyncGitNotification {
|
|||
Fetch,
|
||||
///
|
||||
Branches,
|
||||
///
|
||||
TreeFiles,
|
||||
}
|
||||
|
||||
/// helper function to calculate the hash of an arbitrary type that implements the `Hash` trait
|
||||
|
|
|
|||
73
asyncgit/src/treefiles.rs
Normal file
73
asyncgit/src/treefiles.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
use crate::{
|
||||
asyncjob::{AsyncJob, RunParams},
|
||||
error::Result,
|
||||
sync::{tree_files, CommitId, RepoPath, TreeFile},
|
||||
AsyncGitNotification,
|
||||
};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
enum JobState {
|
||||
Request { commit: CommitId, repo: RepoPath },
|
||||
Response(Result<Vec<TreeFile>>),
|
||||
}
|
||||
|
||||
///
|
||||
#[derive(Clone, Default)]
|
||||
pub struct AsyncTreeFilesJob {
|
||||
state: Arc<Mutex<Option<JobState>>>,
|
||||
}
|
||||
|
||||
///
|
||||
impl AsyncTreeFilesJob {
|
||||
///
|
||||
pub fn new(repo: RepoPath, commit: CommitId) -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(Some(JobState::Request {
|
||||
repo,
|
||||
commit,
|
||||
}))),
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
pub fn result(&self) -> Option<Result<Vec<TreeFile>>> {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
if let Some(state) = state.take() {
|
||||
return match state {
|
||||
JobState::Request { .. } => None,
|
||||
JobState::Response(result) => Some(result),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncJob for AsyncTreeFilesJob {
|
||||
type Notification = AsyncGitNotification;
|
||||
type Progress = ();
|
||||
|
||||
fn run(
|
||||
&mut self,
|
||||
_params: RunParams<Self::Notification, Self::Progress>,
|
||||
) -> Result<Self::Notification> {
|
||||
if let Ok(mut state) = self.state.lock() {
|
||||
*state = state.take().map(|state| match state {
|
||||
JobState::Request { commit, repo } => {
|
||||
let files = tree_files(&repo, commit);
|
||||
|
||||
std::thread::sleep(
|
||||
std::time::Duration::from_secs(2),
|
||||
);
|
||||
JobState::Response(files)
|
||||
}
|
||||
JobState::Response(result) => {
|
||||
JobState::Response(result)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Ok(AsyncGitNotification::TreeFiles)
|
||||
}
|
||||
}
|
||||
|
|
@ -162,6 +162,7 @@ impl App {
|
|||
repo.clone(),
|
||||
&queue,
|
||||
sender_app,
|
||||
sender.clone(),
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
),
|
||||
|
|
@ -306,6 +307,7 @@ impl App {
|
|||
files_tab: FilesTab::new(
|
||||
repo.clone(),
|
||||
sender_app,
|
||||
sender.clone(),
|
||||
&queue,
|
||||
theme.clone(),
|
||||
key_config.clone(),
|
||||
|
|
@ -508,8 +510,8 @@ impl App {
|
|||
self.select_branch_popup.update_git(ev)?;
|
||||
}
|
||||
|
||||
self.files_tab.update_async(ev);
|
||||
self.revision_files_popup.update(ev);
|
||||
self.files_tab.update_async(ev)?;
|
||||
self.revision_files_popup.update(ev)?;
|
||||
self.tags_popup.update(ev);
|
||||
|
||||
//TODO: better system for this
|
||||
|
|
|
|||
|
|
@ -11,14 +11,17 @@ use crate::{
|
|||
AsyncAppNotification, AsyncNotification,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::sync::{
|
||||
self, get_commit_info, CommitId, CommitInfo, RepoPathRef,
|
||||
TreeFile,
|
||||
use asyncgit::{
|
||||
asyncjob::AsyncSingleJob,
|
||||
sync::{
|
||||
get_commit_info, CommitId, CommitInfo, RepoPathRef, TreeFile,
|
||||
},
|
||||
AsyncGitNotification, AsyncTreeFilesJob,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use filetreelist::{FileTree, FileTreeItem};
|
||||
use std::fmt::Write;
|
||||
use std::{borrow::Cow, fmt::Write};
|
||||
use std::{
|
||||
collections::BTreeSet,
|
||||
convert::From,
|
||||
|
|
@ -44,7 +47,8 @@ pub struct RevisionFilesComponent {
|
|||
queue: Queue,
|
||||
theme: SharedTheme,
|
||||
//TODO: store TreeFiles in `tree`
|
||||
files: Vec<TreeFile>,
|
||||
files: Option<Vec<TreeFile>>,
|
||||
async_treefiles: AsyncSingleJob<AsyncTreeFilesJob>,
|
||||
current_file: SyntaxTextComponent,
|
||||
tree: FileTree,
|
||||
scroll: VerticalScroll,
|
||||
|
|
@ -60,6 +64,7 @@ impl RevisionFilesComponent {
|
|||
repo: RepoPathRef,
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncAppNotification>,
|
||||
sender_git: Sender<AsyncGitNotification>,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
) -> Self {
|
||||
|
|
@ -73,8 +78,9 @@ impl RevisionFilesComponent {
|
|||
key_config.clone(),
|
||||
theme.clone(),
|
||||
),
|
||||
async_treefiles: AsyncSingleJob::new(sender_git),
|
||||
theme,
|
||||
files: Vec::new(),
|
||||
files: None,
|
||||
revision: None,
|
||||
focus: Focus::Tree,
|
||||
key_config,
|
||||
|
|
@ -89,13 +95,15 @@ impl RevisionFilesComponent {
|
|||
|
||||
let same_id =
|
||||
self.revision.as_ref().map_or(false, |c| c.id == commit);
|
||||
|
||||
if !same_id {
|
||||
self.files =
|
||||
sync::tree_files(&self.repo.borrow(), commit)?;
|
||||
let filenames: Vec<&Path> =
|
||||
self.files.iter().map(|f| f.path.as_path()).collect();
|
||||
self.tree = FileTree::new(&filenames, &BTreeSet::new())?;
|
||||
self.tree.collapse_but_root();
|
||||
self.files = None;
|
||||
|
||||
self.async_treefiles.spawn(AsyncTreeFilesJob::new(
|
||||
self.repo.borrow().clone(),
|
||||
commit,
|
||||
));
|
||||
|
||||
self.revision =
|
||||
Some(get_commit_info(&self.repo.borrow(), &commit)?);
|
||||
}
|
||||
|
|
@ -114,13 +122,35 @@ impl RevisionFilesComponent {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self, ev: AsyncNotification) {
|
||||
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
|
||||
self.current_file.update(ev);
|
||||
|
||||
if matches!(
|
||||
ev,
|
||||
AsyncNotification::Git(AsyncGitNotification::TreeFiles)
|
||||
) {
|
||||
if let Some(last) = self.async_treefiles.take_last() {
|
||||
if let Some(Ok(last)) = last.result() {
|
||||
let filenames: Vec<&Path> = last
|
||||
.iter()
|
||||
.map(|f| f.path.as_path())
|
||||
.collect();
|
||||
self.tree =
|
||||
FileTree::new(&filenames, &BTreeSet::new())?;
|
||||
self.tree.collapse_but_root();
|
||||
|
||||
self.files = Some(last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
///
|
||||
pub fn any_work_pending(&self) -> bool {
|
||||
self.current_file.any_work_pending()
|
||||
|| self.async_treefiles.is_pending()
|
||||
}
|
||||
|
||||
fn tree_item_to_span<'a>(
|
||||
|
|
@ -190,8 +220,9 @@ impl RevisionFilesComponent {
|
|||
}
|
||||
|
||||
fn open_finder(&self) {
|
||||
self.queue
|
||||
.push(InternalEvent::OpenFileFinder(self.files.clone()));
|
||||
self.queue.push(InternalEvent::OpenFileFinder(
|
||||
self.files.clone().unwrap_or_default(),
|
||||
));
|
||||
}
|
||||
|
||||
pub fn find_file(&mut self, file: &Option<PathBuf>) {
|
||||
|
|
@ -221,18 +252,20 @@ impl RevisionFilesComponent {
|
|||
fn selection_changed(&mut self) {
|
||||
//TODO: retrieve TreeFile from tree datastructure
|
||||
if let Some(file) = self.selected_file_path_with_prefix() {
|
||||
let path = Path::new(&file);
|
||||
if let Some(item) =
|
||||
self.files.iter().find(|f| f.path == path)
|
||||
{
|
||||
if let Ok(path) = path.strip_prefix("./") {
|
||||
return self.current_file.load_file(
|
||||
path.to_string_lossy().to_string(),
|
||||
item,
|
||||
);
|
||||
if let Some(files) = &self.files {
|
||||
let path = Path::new(&file);
|
||||
if let Some(item) =
|
||||
files.iter().find(|f| f.path == path)
|
||||
{
|
||||
if let Ok(path) = path.strip_prefix("./") {
|
||||
return self.current_file.load_file(
|
||||
path.to_string_lossy().to_string(),
|
||||
item,
|
||||
);
|
||||
}
|
||||
}
|
||||
self.current_file.clear();
|
||||
}
|
||||
self.current_file.clear();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,18 +301,30 @@ impl RevisionFilesComponent {
|
|||
let is_tree_focused = matches!(self.focus, Focus::Tree);
|
||||
|
||||
let title = self.title_within(tree_width);
|
||||
ui::draw_list_block(
|
||||
f,
|
||||
area,
|
||||
Block::default()
|
||||
.title(Span::styled(
|
||||
title,
|
||||
self.theme.title(is_tree_focused),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(self.theme.block(is_tree_focused)),
|
||||
items,
|
||||
);
|
||||
let block = Block::default()
|
||||
.title(Span::styled(
|
||||
title,
|
||||
self.theme.title(is_tree_focused),
|
||||
))
|
||||
.borders(Borders::ALL)
|
||||
.border_style(self.theme.block(is_tree_focused));
|
||||
|
||||
if self.files.is_some() {
|
||||
ui::draw_list_block(f, area, block, items);
|
||||
} else {
|
||||
ui::draw_list_block(
|
||||
f,
|
||||
area,
|
||||
block,
|
||||
vec![Span::styled(
|
||||
Cow::from(strings::loading_text(
|
||||
&self.key_config,
|
||||
)),
|
||||
self.theme.text(false, false),
|
||||
)]
|
||||
.into_iter(),
|
||||
);
|
||||
}
|
||||
|
||||
if is_tree_focused {
|
||||
self.scroll.draw(f, area, &self.theme);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ use crate::{
|
|||
AsyncAppNotification, AsyncNotification,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::sync::{CommitId, RepoPathRef};
|
||||
use asyncgit::{
|
||||
sync::{CommitId, RepoPathRef},
|
||||
AsyncGitNotification,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
use crossterm::event::Event;
|
||||
use tui::{backend::Backend, layout::Rect, widgets::Clear, Frame};
|
||||
|
|
@ -47,6 +50,7 @@ impl RevisionFilesPopup {
|
|||
repo: RepoPathRef,
|
||||
queue: &Queue,
|
||||
sender: &Sender<AsyncAppNotification>,
|
||||
sender_git: Sender<AsyncGitNotification>,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
) -> Self {
|
||||
|
|
@ -55,6 +59,7 @@ impl RevisionFilesPopup {
|
|||
repo,
|
||||
queue,
|
||||
sender,
|
||||
sender_git,
|
||||
theme,
|
||||
key_config.clone(),
|
||||
),
|
||||
|
|
@ -75,8 +80,8 @@ impl RevisionFilesPopup {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn update(&mut self, ev: AsyncNotification) {
|
||||
self.files.update(ev);
|
||||
pub fn update(&mut self, ev: AsyncNotification) -> Result<()> {
|
||||
self.files.update(ev)
|
||||
}
|
||||
|
||||
///
|
||||
|
|
|
|||
|
|
@ -11,7 +11,10 @@ use crate::{
|
|||
AsyncAppNotification, AsyncNotification,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use asyncgit::sync::{self, RepoPathRef};
|
||||
use asyncgit::{
|
||||
sync::{self, RepoPathRef},
|
||||
AsyncGitNotification,
|
||||
};
|
||||
use crossbeam_channel::Sender;
|
||||
|
||||
pub struct FilesTab {
|
||||
|
|
@ -25,6 +28,7 @@ impl FilesTab {
|
|||
pub fn new(
|
||||
repo: RepoPathRef,
|
||||
sender: &Sender<AsyncAppNotification>,
|
||||
sender_git: Sender<AsyncGitNotification>,
|
||||
queue: &Queue,
|
||||
theme: SharedTheme,
|
||||
key_config: SharedKeyConfig,
|
||||
|
|
@ -35,6 +39,7 @@ impl FilesTab {
|
|||
repo.clone(),
|
||||
queue,
|
||||
sender,
|
||||
sender_git,
|
||||
theme,
|
||||
key_config,
|
||||
),
|
||||
|
|
@ -59,10 +64,15 @@ impl FilesTab {
|
|||
}
|
||||
|
||||
///
|
||||
pub fn update_async(&mut self, ev: AsyncNotification) {
|
||||
pub fn update_async(
|
||||
&mut self,
|
||||
ev: AsyncNotification,
|
||||
) -> Result<()> {
|
||||
if self.is_visible() {
|
||||
self.files.update(ev);
|
||||
self.files.update(ev)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn file_finder_update(&mut self, file: &Option<PathBuf>) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue