load only on click and first n items
This commit is contained in:
@@ -30,10 +30,11 @@ use git::{
|
||||
TrashUntrackedFiles, UnstageAll,
|
||||
};
|
||||
use gpui::{
|
||||
Action, AsyncApp, AsyncWindowContext, ClickEvent, Corner, DismissEvent, Entity, EventEmitter,
|
||||
FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior, ListSizingBehavior,
|
||||
MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy, Subscription, Task,
|
||||
UniformListScrollHandle, WeakEntity, actions, anchored, deferred, uniform_list,
|
||||
Action, AppContext, AsyncApp, AsyncWindowContext, ClickEvent, Corner, DismissEvent, Entity,
|
||||
EventEmitter, FocusHandle, Focusable, KeyContext, ListHorizontalSizingBehavior,
|
||||
ListSizingBehavior, MouseButton, MouseDownEvent, Point, PromptLevel, ScrollStrategy,
|
||||
Subscription, Task, UniformListScrollHandle, WeakEntity, actions, anchored, deferred,
|
||||
uniform_list,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use language::{Buffer, File};
|
||||
@@ -311,6 +312,9 @@ pub struct GitPanel {
|
||||
bulk_staging: Option<BulkStaging>,
|
||||
stash_entries: GitStash,
|
||||
_settings_subscription: Subscription,
|
||||
/// On clicking an entry in a the git_panel this will
|
||||
/// trigger loading it
|
||||
open_diff_task: Option<Task<()>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -471,6 +475,7 @@ impl GitPanel {
|
||||
bulk_staging: None,
|
||||
stash_entries: Default::default(),
|
||||
_settings_subscription,
|
||||
open_diff_task: None,
|
||||
};
|
||||
|
||||
this.schedule_update(window, cx);
|
||||
@@ -750,11 +755,25 @@ impl GitPanel {
|
||||
|
||||
fn open_diff(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
maybe!({
|
||||
let entry = self.entries.get(self.selected_entry?)?.status_entry()?;
|
||||
let entry = self
|
||||
.entries
|
||||
.get(self.selected_entry?)?
|
||||
.status_entry()?
|
||||
.clone();
|
||||
let workspace = self.workspace.upgrade()?;
|
||||
let git_repo = self.active_repository.as_ref()?;
|
||||
let git_repo = self.active_repository.as_ref()?.clone();
|
||||
let focus_handle = self.focus_handle.clone();
|
||||
|
||||
if let Some(project_diff) = workspace.read(cx).active_item_as::<ProjectDiff>(cx)
|
||||
// let panel = panel.upgrade().unwrap(); // TODO FIXME
|
||||
// cx.read_entity(&panel, |panel, cx| {
|
||||
// panel
|
||||
// })
|
||||
// .unwrap(); // TODO FIXME
|
||||
|
||||
// how do we get the projectdiff here?
|
||||
|
||||
let project_diff = if let Some(project_diff) =
|
||||
workspace.read(cx).active_item_as::<ProjectDiff>(cx)
|
||||
&& let Some(project_path) = project_diff.read(cx).active_path(cx)
|
||||
&& Some(&entry.repo_path)
|
||||
== git_repo
|
||||
@@ -764,16 +783,20 @@ impl GitPanel {
|
||||
{
|
||||
project_diff.focus_handle(cx).focus(window);
|
||||
project_diff.update(cx, |project_diff, cx| project_diff.autoscroll(cx));
|
||||
return None;
|
||||
};
|
||||
|
||||
self.workspace
|
||||
.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx);
|
||||
project_diff
|
||||
} else {
|
||||
workspace.update(cx, |workspace, cx| {
|
||||
ProjectDiff::deploy_at(workspace, Some(entry.clone()), window, cx)
|
||||
})
|
||||
.ok();
|
||||
self.focus_handle.focus(window);
|
||||
};
|
||||
focus_handle.focus(window); // TODO: should we focus before the file is loaded or wait for that?
|
||||
|
||||
let project_diff = project_diff.downgrade();
|
||||
self.open_diff_task = Some(cx.spawn_in(window, async move |_, cx| {
|
||||
ProjectDiff::refresh_one(project_diff, entry.repo_path, entry.status, cx)
|
||||
.await
|
||||
.unwrap(); // TODO FIXME
|
||||
}));
|
||||
Some(())
|
||||
});
|
||||
}
|
||||
@@ -807,11 +830,7 @@ impl GitPanel {
|
||||
.notify_async_err(&mut cx)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to open file"))?;
|
||||
if let Some(active_editor) = item.downcast::<Editor>() {
|
||||
// active_editor.update(cx, |editor, _cx| editor.load_diff())?;
|
||||
if let Some(diff_task) =
|
||||
// this needs to become load the diff
|
||||
// this just waits for the load_diff_task to be completed
|
||||
// later we will make all files load for small diffs
|
||||
active_editor.update(cx, |editor, _cx| editor.wait_for_diff_to_load())?
|
||||
{
|
||||
diff_task.await;
|
||||
|
||||
@@ -14,7 +14,7 @@ use editor::{
|
||||
multibuffer_context_lines,
|
||||
scroll::Autoscroll,
|
||||
};
|
||||
use futures::{FutureExt, stream::FuturesUnordered};
|
||||
use futures::stream::FuturesUnordered;
|
||||
use git::{
|
||||
Commit, StageAll, StageAndNext, ToggleStaged, UnstageAll, UnstageAndNext,
|
||||
repository::{Branch, RepoPath, Upstream, UpstreamTracking, UpstreamTrackingStatus},
|
||||
@@ -29,7 +29,7 @@ use multi_buffer::{MultiBuffer, PathKey};
|
||||
use project::{
|
||||
Project, ProjectPath,
|
||||
git_store::{
|
||||
self, Repository,
|
||||
self, Repository, StatusEntry,
|
||||
branch_diff::{self, BranchDiffEvent, DiffBase},
|
||||
},
|
||||
};
|
||||
@@ -97,7 +97,7 @@ impl ProjectDiff {
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
Self::deploy_at(workspace, None, window, cx)
|
||||
Self::deploy_at(workspace, None, window, cx);
|
||||
}
|
||||
|
||||
fn deploy_branch_diff(
|
||||
@@ -139,7 +139,7 @@ impl ProjectDiff {
|
||||
entry: Option<GitStatusEntry>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) {
|
||||
) -> Entity<ProjectDiff> {
|
||||
telemetry::event!(
|
||||
"Git Diff Opened",
|
||||
source = if entry.is_some() {
|
||||
@@ -171,7 +171,8 @@ impl ProjectDiff {
|
||||
project_diff.update(cx, |project_diff, cx| {
|
||||
project_diff.move_to_entry(entry, window, cx);
|
||||
})
|
||||
}
|
||||
};
|
||||
project_diff
|
||||
}
|
||||
|
||||
pub fn autoscroll(&self, cx: &mut Context<Self>) {
|
||||
@@ -277,12 +278,14 @@ impl ProjectDiff {
|
||||
window,
|
||||
move |this, _git_store, event, window, cx| match event {
|
||||
BranchDiffEvent::FileListChanged => {
|
||||
if this.number_of_paths(cx) < 100 {
|
||||
this._task = window.spawn(cx, {
|
||||
let this = cx.weak_entity();
|
||||
async |cx| Self::refresh(this, cx).await
|
||||
})
|
||||
}
|
||||
// TODO this does not account for size of paths
|
||||
// maybe a quick fs metadata could get us info on that?
|
||||
// would make number of paths async but thats fine here
|
||||
let entries = this.first_n_entries(cx, 100);
|
||||
this._task = window.spawn(cx, {
|
||||
let this = cx.weak_entity();
|
||||
async |cx| Self::refresh(this, entries, cx).await
|
||||
})
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -310,9 +313,18 @@ impl ProjectDiff {
|
||||
})
|
||||
.detach();
|
||||
|
||||
// let entries = cx.read_entity(&cx.entity(), |project_diff, cx| {
|
||||
// project_diff.first_n_entries(cx, 100)
|
||||
// });
|
||||
|
||||
let task = window.spawn(cx, {
|
||||
let this = cx.weak_entity();
|
||||
async |cx| Self::refresh(this, cx).await
|
||||
async |cx| {
|
||||
let entries = this
|
||||
.read_with(cx, |project_diff, cx| project_diff.first_n_entries(cx, 100))
|
||||
.unwrap();
|
||||
Self::refresh(this, entries, cx).await
|
||||
}
|
||||
});
|
||||
|
||||
Self {
|
||||
@@ -479,10 +491,11 @@ impl ProjectDiff {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let subscription = cx.subscribe_in(&diff, window, move |this, _, _, window, cx| {
|
||||
this._task = window.spawn(cx, {
|
||||
let this = cx.weak_entity();
|
||||
async |cx| Self::refresh(this, cx).await
|
||||
})
|
||||
// TODO fix this
|
||||
// this._task = window.spawn(cx, {
|
||||
// let this = cx.weak_entity();
|
||||
// async |cx| Self::refresh(this, cx).await
|
||||
// })
|
||||
});
|
||||
self.buffer_diff_subscriptions
|
||||
.insert(path_key.path.clone(), (diff.clone(), subscription));
|
||||
@@ -558,64 +571,21 @@ impl ProjectDiff {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn old_refresh(this: WeakEntity<Self>, cx: &mut AsyncWindowContext) -> Result<()> {
|
||||
let mut path_keys = Vec::new();
|
||||
let buffers_to_load = this.update(cx, |this, cx| {
|
||||
let (repo, buffers_to_load) = this.branch_diff.update(cx, |branch_diff, cx| {
|
||||
let load_buffers = branch_diff.load_buffers(cx);
|
||||
(branch_diff.repo().cloned(), load_buffers)
|
||||
});
|
||||
let mut previous_paths = this.multibuffer.read(cx).paths().collect::<HashSet<_>>();
|
||||
|
||||
if let Some(repo) = repo {
|
||||
let repo = repo.read(cx);
|
||||
|
||||
path_keys = Vec::with_capacity(buffers_to_load.len());
|
||||
for entry in buffers_to_load.iter() {
|
||||
let sort_prefix = sort_prefix(&repo, &entry.repo_path, entry.file_status, cx);
|
||||
let path_key =
|
||||
PathKey::with_sort_prefix(sort_prefix, entry.repo_path.as_ref().clone());
|
||||
previous_paths.remove(&path_key);
|
||||
path_keys.push(path_key)
|
||||
}
|
||||
}
|
||||
|
||||
this.multibuffer.update(cx, |multibuffer, cx| {
|
||||
for path in previous_paths {
|
||||
this.buffer_diff_subscriptions.remove(&path.path);
|
||||
multibuffer.remove_excerpts_for_path(path, cx);
|
||||
}
|
||||
});
|
||||
buffers_to_load
|
||||
})?;
|
||||
|
||||
for (entry, path_key) in buffers_to_load.into_iter().zip(path_keys.into_iter()) {
|
||||
if let Some((buffer, diff)) = entry.load.await.log_err() {
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.register_buffer(path_key, entry.file_status, buffer, diff, window, cx)
|
||||
})
|
||||
.ok();
|
||||
})?;
|
||||
}
|
||||
}
|
||||
this.update(cx, |this, cx| {
|
||||
this.pending_scroll.take();
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn number_of_paths(&self, cx: &App) -> usize {
|
||||
pub fn first_n_entries(&self, cx: &App, n: usize) -> Vec<StatusEntry> {
|
||||
let Some(ref repo) = self.branch_diff.read(cx).repo else {
|
||||
return 0;
|
||||
return Vec::new();
|
||||
};
|
||||
repo.read(cx).cached_status().count()
|
||||
repo.read(cx).cached_status().take(n).collect()
|
||||
}
|
||||
|
||||
pub async fn refresh(this: WeakEntity<Self>, cx: &mut AsyncWindowContext) -> Result<()> {
|
||||
pub async fn refresh_one(
|
||||
this: WeakEntity<Self>,
|
||||
repo_path: RepoPath,
|
||||
status: FileStatus,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
use git_store::branch_diff::BranchDiff;
|
||||
|
||||
let Some(this) = this.upgrade() else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -627,8 +597,90 @@ impl ProjectDiff {
|
||||
};
|
||||
let project = cx.read_entity(&branch_diff, |bd, _| bd.project.clone())?;
|
||||
|
||||
let cached_status: Vec<git_store::StatusEntry> =
|
||||
cx.read_entity(&repo, |repo: &Repository, _| repo.cached_status().collect())?;
|
||||
let mut previous_paths =
|
||||
cx.read_entity(&multibuffer, |mb, _| mb.paths().collect::<HashSet<_>>())?;
|
||||
|
||||
let tree_diff_status = cx.read_entity(&branch_diff, |branch_diff, _| {
|
||||
branch_diff
|
||||
.tree_diff
|
||||
.as_ref()
|
||||
.and_then(|t| t.entries.get(&repo_path))
|
||||
.cloned()
|
||||
})?;
|
||||
|
||||
let Some(status) = cx.read_entity(&branch_diff, |bd, _| {
|
||||
bd.merge_statuses(Some(status), tree_diff_status.as_ref())
|
||||
})?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
if !status.has_changes() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(project_path) = cx.read_entity(&repo, |repo, cx| {
|
||||
repo.repo_path_to_project_path(&repo_path, cx)
|
||||
})?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let sort_prefix =
|
||||
cx.read_entity(&repo, |repo, cx| sort_prefix(repo, &repo_path, status, cx))?;
|
||||
|
||||
let path_key = PathKey::with_sort_prefix(sort_prefix, repo_path.into_arc());
|
||||
previous_paths.remove(&path_key);
|
||||
|
||||
let repo = repo.clone();
|
||||
let Some((buffer, diff)) = BranchDiff::load_buffer(
|
||||
tree_diff_status,
|
||||
project_path,
|
||||
repo,
|
||||
project.downgrade(),
|
||||
&mut cx.to_app(),
|
||||
)
|
||||
.await
|
||||
.log_err() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
cx.update(|window, cx| {
|
||||
this.update(cx, |this, cx| {
|
||||
this.register_buffer(path_key, status, buffer, diff, window, cx)
|
||||
});
|
||||
})?;
|
||||
|
||||
// TODO LL clear multibuff on open?
|
||||
// // remove anything not part of the diff in the multibuffer
|
||||
// this.update(cx, |this, cx| {
|
||||
// multibuffer.update(cx, |multibuffer, cx| {
|
||||
// for path in previous_paths {
|
||||
// this.buffer_diff_subscriptions.remove(&path.path);
|
||||
// multibuffer.remove_excerpts_for_path(path, cx);
|
||||
// }
|
||||
// });
|
||||
// })?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn refresh(
|
||||
this: WeakEntity<Self>,
|
||||
cached_status: Vec<StatusEntry>,
|
||||
cx: &mut AsyncWindowContext,
|
||||
) -> Result<()> {
|
||||
dbg!("refreshing all");
|
||||
use git_store::branch_diff::BranchDiff;
|
||||
let Some(this) = this.upgrade() else {
|
||||
return Ok(());
|
||||
};
|
||||
let multibuffer = cx.read_entity(&this, |this, _| this.multibuffer.clone())?;
|
||||
let branch_diff = cx.read_entity(&this, |pd, _| pd.branch_diff.clone())?;
|
||||
|
||||
let Some(repo) = cx.read_entity(&branch_diff, |bd, _| bd.repo.clone())? else {
|
||||
return Ok(());
|
||||
};
|
||||
let project = cx.read_entity(&branch_diff, |bd, _| bd.project.clone())?;
|
||||
|
||||
let mut previous_paths =
|
||||
cx.read_entity(&multibuffer, |mb, _| mb.paths().collect::<HashSet<_>>())?;
|
||||
@@ -674,10 +726,18 @@ impl ProjectDiff {
|
||||
previous_paths.remove(&path_key);
|
||||
|
||||
let repo = repo.clone();
|
||||
let task = project.update(cx, move |_, cx| {
|
||||
BranchDiff::load_buffer(tree_diff_status, project_path, repo, cx)
|
||||
.map(move |res| (res, path_key, entry.status))
|
||||
})?;
|
||||
let project = project.downgrade();
|
||||
let task = cx.spawn(async move |cx| {
|
||||
let res = BranchDiff::load_buffer(
|
||||
tree_diff_status,
|
||||
project_path,
|
||||
repo,
|
||||
project,
|
||||
&mut cx.to_app(),
|
||||
)
|
||||
.await;
|
||||
(res, path_key, entry.status)
|
||||
});
|
||||
|
||||
tasks.push(task)
|
||||
}
|
||||
|
||||
@@ -310,6 +310,11 @@ impl AsyncWindowContext {
|
||||
.update(self, |_, window, cx| read(cx.global(), window, cx))
|
||||
}
|
||||
|
||||
/// Returns an `AsyncApp` by cloning the one used by Self
|
||||
pub fn to_app(&self) -> AsyncApp {
|
||||
self.app.clone()
|
||||
}
|
||||
|
||||
/// A convenience method for [`App::update_global`](BorrowAppContext::update_global).
|
||||
/// for updating the global state of the specified type.
|
||||
pub fn update_global<G, R>(
|
||||
|
||||
@@ -233,6 +233,9 @@ impl<'a, T: 'static> Context<'a, T> {
|
||||
/// Spawn the future returned by the given function.
|
||||
/// The function is provided a weak handle to the entity owned by this context and a context that can be held across await points.
|
||||
/// The returned task must be held or detached.
|
||||
///
|
||||
/// # Example
|
||||
/// `cx.spawn(async move |some_weak_entity, cx| ...)`
|
||||
#[track_caller]
|
||||
pub fn spawn<AsyncFn, R>(&self, f: AsyncFn) -> Task<R>
|
||||
where
|
||||
|
||||
@@ -283,7 +283,11 @@ impl BranchDiff {
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let task = Self::load_buffer(branch_diff, project_path, repo.clone(), cx);
|
||||
|
||||
let repo = repo.clone();
|
||||
let task = cx.spawn(async move |project, cx| {
|
||||
Self::load_buffer(branch_diff, project_path, repo.clone(), project, cx).await
|
||||
});
|
||||
|
||||
output.push(DiffBuffer {
|
||||
repo_path: item.repo_path.clone(),
|
||||
@@ -303,8 +307,11 @@ impl BranchDiff {
|
||||
let Some(project_path) = repo.read(cx).repo_path_to_project_path(&path, cx) else {
|
||||
continue;
|
||||
};
|
||||
let task =
|
||||
Self::load_buffer(Some(branch_diff.clone()), project_path, repo.clone(), cx);
|
||||
let repo = repo.clone();
|
||||
let branch_diff2 = Some(branch_diff.clone());
|
||||
let task = cx.spawn(async move |project, cx| {
|
||||
Self::load_buffer(branch_diff2, project_path, repo, project, cx).await
|
||||
});
|
||||
|
||||
let file_status = diff_status_to_file_status(branch_diff);
|
||||
|
||||
@@ -318,42 +325,40 @@ impl BranchDiff {
|
||||
output
|
||||
}
|
||||
|
||||
pub fn load_buffer(
|
||||
pub async fn load_buffer(
|
||||
branch_diff: Option<git::status::TreeDiffStatus>,
|
||||
project_path: crate::ProjectPath,
|
||||
repo: Entity<Repository>,
|
||||
cx: &Context<'_, Project>,
|
||||
) -> Task<Result<(Entity<Buffer>, Entity<BufferDiff>)>> {
|
||||
let task = cx.spawn(async move |project, cx| {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.await?;
|
||||
project: WeakEntity<Project>,
|
||||
cx: &mut gpui::AsyncApp, // making this generic over AppContext hangs the compiler
|
||||
) -> Result<(Entity<Buffer>, Entity<BufferDiff>)> {
|
||||
let buffer = project
|
||||
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
|
||||
.await?;
|
||||
|
||||
let languages = project.update(cx, |project, _cx| project.languages().clone())?;
|
||||
let languages = project.update(cx, |project, _cx| project.languages().clone())?;
|
||||
|
||||
let changes = if let Some(entry) = branch_diff {
|
||||
let oid = match entry {
|
||||
git::status::TreeDiffStatus::Added { .. } => None,
|
||||
git::status::TreeDiffStatus::Modified { old, .. }
|
||||
| git::status::TreeDiffStatus::Deleted { old } => Some(old),
|
||||
};
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.git_store().update(cx, |git_store, cx| {
|
||||
git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
|
||||
})
|
||||
})?
|
||||
.await?
|
||||
} else {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_uncommitted_diff(buffer.clone(), cx)
|
||||
})?
|
||||
.await?
|
||||
let changes = if let Some(entry) = branch_diff {
|
||||
let oid = match entry {
|
||||
git::status::TreeDiffStatus::Added { .. } => None,
|
||||
git::status::TreeDiffStatus::Modified { old, .. }
|
||||
| git::status::TreeDiffStatus::Deleted { old } => Some(old),
|
||||
};
|
||||
Ok((buffer, changes))
|
||||
});
|
||||
task
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.git_store().update(cx, |git_store, cx| {
|
||||
git_store.open_diff_since(oid, buffer.clone(), repo, languages, cx)
|
||||
})
|
||||
})?
|
||||
.await?
|
||||
} else {
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.open_uncommitted_diff(buffer.clone(), cx)
|
||||
})?
|
||||
.await?
|
||||
};
|
||||
Ok((buffer, changes))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user