git_graph: Polish UX (#50123)
This is a follow-up on #50027 I address my comments by adding a hash map look-up to find the selected pending commit. I also removed the limitation where we would only retry finding the pending commit 5 times. The pending selection is removed when the graph is fully loaded and doesn't contain the pending commit. This PR also cleans up some internal code structure and starts work to enable search and propagating git log error messages to the UI. UI wise I made the git graph item show the repository name instead of "Git Graph" in Zed. Before you mark this PR as ready for review, make sure that you have: - [ ] Added a solid test coverage and/or screenshots from doing manual testing - [x] Done a self-review taking into account security and performance aspects - [x] Aligned any UI changes with the [UI checklist](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist) Release Notes: - N/A --------- Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
1 parent
046b173b87
commit
b4fe63b097
2 files changed
+207
-107
No files matched your search
@@ -18,7 +18,10 @@ use language::line_diff;
|
||||
use menu::{Cancel, SelectNext, SelectPrevious};
|
||||
use project::{
|
||||
Project,
|
||||
git_store::{CommitDataState, GitStoreEvent, Repository, RepositoryEvent, RepositoryId},
|
||||
git_store::{
|
||||
CommitDataState, GitGraphEvent, GitStoreEvent, GraphDataResponse, Repository,
|
||||
RepositoryEvent, RepositoryId,
|
||||
},
|
||||
};
|
||||
use settings::Settings;
|
||||
use smallvec::{SmallVec, smallvec};
|
||||
@@ -48,7 +51,6 @@ const LANE_WIDTH: Pixels = px(16.0);
|
||||
const LEFT_PADDING: Pixels = px(12.0);
|
||||
const LINE_WIDTH: Pixels = px(1.5);
|
||||
const RESIZE_HANDLE_WIDTH: f32 = 8.0;
|
||||
const PENDING_SELECT_MAX_RETRIES: usize = 5;
|
||||
const COPIED_STATE_DURATION: Duration = Duration::from_secs(2);
|
||||
|
||||
struct CopiedState {
|
||||
@@ -853,7 +855,7 @@ pub struct GitGraph {
|
||||
commit_details_split_state: Entity<SplitState>,
|
||||
selected_repo_id: Option<RepositoryId>,
|
||||
changed_files_scroll_handle: UniformListScrollHandle,
|
||||
pending_select_sha: Option<(String, usize)>,
|
||||
pending_select_sha: Option<Oid>,
|
||||
}
|
||||
|
||||
impl GitGraph {
|
||||
@@ -965,20 +967,62 @@ impl GitGraph {
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
match event {
|
||||
RepositoryEvent::GitGraphCountUpdated((order, source), commit_count) => {
|
||||
if order != &self.log_order || source != &self.log_source {
|
||||
return;
|
||||
RepositoryEvent::GraphEvent((source, order), event)
|
||||
if source == &self.log_source && order == &self.log_order =>
|
||||
{
|
||||
match event {
|
||||
GitGraphEvent::FullyLoaded => {
|
||||
if let Some(pending_sha_index) =
|
||||
self.pending_select_sha.take().and_then(|oid| {
|
||||
repository
|
||||
.read(cx)
|
||||
.get_graph_data(source.clone(), *order)
|
||||
.and_then(|data| data.commit_oid_to_index.get(&oid).copied())
|
||||
})
|
||||
{
|
||||
self.select_entry(pending_sha_index, cx);
|
||||
}
|
||||
}
|
||||
GitGraphEvent::LoadingError => {
|
||||
// todo(git_graph): Wire this up with the UI
|
||||
}
|
||||
GitGraphEvent::CountUpdated(commit_count) => {
|
||||
let old_count = self.graph_data.commits.len();
|
||||
|
||||
if let Some(pending_selection_index) =
|
||||
repository.update(cx, |repository, cx| {
|
||||
let GraphDataResponse {
|
||||
commits,
|
||||
is_loading,
|
||||
error: _,
|
||||
} = repository.graph_data(
|
||||
source.clone(),
|
||||
*order,
|
||||
old_count..*commit_count,
|
||||
cx,
|
||||
);
|
||||
self.graph_data.add_commits(commits);
|
||||
|
||||
let pending_sha_index = self.pending_select_sha.and_then(|oid| {
|
||||
repository.get_graph_data(source.clone(), *order).and_then(
|
||||
|data| data.commit_oid_to_index.get(&oid).copied(),
|
||||
)
|
||||
});
|
||||
|
||||
if !is_loading && pending_sha_index.is_none() {
|
||||
self.pending_select_sha.take();
|
||||
}
|
||||
|
||||
pending_sha_index
|
||||
})
|
||||
{
|
||||
self.select_entry(pending_selection_index, cx);
|
||||
self.pending_select_sha.take();
|
||||
}
|
||||
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
|
||||
let old_count = self.graph_data.commits.len();
|
||||
|
||||
repository.update(cx, |repository, cx| {
|
||||
let (commits, _) =
|
||||
repository.graph_data(source.clone(), *order, old_count..*commit_count, cx);
|
||||
self.graph_data.add_commits(commits);
|
||||
});
|
||||
cx.notify();
|
||||
self.retry_pending_select(cx);
|
||||
}
|
||||
RepositoryEvent::BranchChanged | RepositoryEvent::MergeHeadsChanged => {
|
||||
self.pending_select_sha = None;
|
||||
@@ -990,6 +1034,7 @@ impl GitGraph {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
RepositoryEvent::GraphEvent(_, _) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -997,12 +1042,9 @@ impl GitGraph {
|
||||
fn fetch_initial_graph_data(&mut self, cx: &mut App) {
|
||||
if let Some(repository) = self.get_selected_repository(cx) {
|
||||
repository.update(cx, |repository, cx| {
|
||||
let (commits, _) = repository.graph_data(
|
||||
self.log_source.clone(),
|
||||
self.log_order,
|
||||
0..usize::MAX,
|
||||
cx,
|
||||
);
|
||||
let commits = repository
|
||||
.graph_data(self.log_source.clone(), self.log_order, 0..usize::MAX, cx)
|
||||
.commits;
|
||||
self.graph_data.add_commits(commits);
|
||||
});
|
||||
}
|
||||
@@ -1145,6 +1187,10 @@ impl GitGraph {
|
||||
}
|
||||
}
|
||||
|
||||
fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
|
||||
self.open_selected_commit_view(window, cx);
|
||||
}
|
||||
|
||||
fn select_entry(&mut self, idx: usize, cx: &mut Context<Self>) {
|
||||
if self.selected_entry_idx == Some(idx) {
|
||||
return;
|
||||
@@ -1193,31 +1239,21 @@ impl GitGraph {
|
||||
let Ok(oid) = sha.parse::<Oid>() else {
|
||||
return;
|
||||
};
|
||||
for (idx, commit) in self.graph_data.commits.iter().enumerate() {
|
||||
if commit.data.sha == oid {
|
||||
self.pending_select_sha = None;
|
||||
self.select_entry(idx, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.pending_select_sha = Some((sha.to_string(), PENDING_SELECT_MAX_RETRIES));
|
||||
}
|
||||
|
||||
fn retry_pending_select(&mut self, cx: &mut Context<Self>) {
|
||||
let Some((sha, retries_remaining)) = self.pending_select_sha.take() else {
|
||||
let Some(selected_repository) = self.get_selected_repository(cx) else {
|
||||
return;
|
||||
};
|
||||
if let Ok(oid) = sha.parse::<Oid>() {
|
||||
for (idx, commit) in self.graph_data.commits.iter().enumerate() {
|
||||
if commit.data.sha == oid {
|
||||
self.select_entry(idx, cx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if retries_remaining > 0 {
|
||||
self.pending_select_sha = Some((sha, retries_remaining - 1));
|
||||
}
|
||||
|
||||
let Some(index) = selected_repository
|
||||
.read(cx)
|
||||
.get_graph_data(self.log_source.clone(), self.log_order)
|
||||
.and_then(|data| data.commit_oid_to_index.get(&oid))
|
||||
.copied()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.select_entry(index, cx);
|
||||
}
|
||||
|
||||
fn open_selected_commit_view(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
@@ -2033,7 +2069,11 @@ impl Render for GitGraph {
|
||||
if let Some(repository) = self.get_selected_repository(cx) {
|
||||
repository.update(cx, |repository, cx| {
|
||||
// Start loading the graph data if we haven't started already
|
||||
let (commits, is_loading) = repository.graph_data(
|
||||
let GraphDataResponse {
|
||||
commits,
|
||||
is_loading,
|
||||
error: _,
|
||||
} = repository.graph_data(
|
||||
self.log_source.clone(),
|
||||
self.log_order,
|
||||
0..usize::MAX,
|
||||
@@ -2212,16 +2252,17 @@ impl Render for GitGraph {
|
||||
};
|
||||
|
||||
div()
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.key_context("GitGraph")
|
||||
.track_focus(&self.focus_handle)
|
||||
.size_full()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.on_action(cx.listener(|this, _: &OpenCommitView, window, cx| {
|
||||
this.open_selected_commit_view(window, cx);
|
||||
}))
|
||||
.on_action(cx.listener(Self::cancel))
|
||||
.on_action(cx.listener(Self::select_prev))
|
||||
.on_action(cx.listener(Self::select_next))
|
||||
.on_action(cx.listener(Self::confirm))
|
||||
.child(content)
|
||||
.children(self.context_menu.as_ref().map(|(menu, position, _)| {
|
||||
deferred(
|
||||
@@ -2270,8 +2311,15 @@ impl Item for GitGraph {
|
||||
}))))
|
||||
}
|
||||
|
||||
fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
|
||||
"Git Graph".into()
|
||||
fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
|
||||
self.get_selected_repository(cx)
|
||||
.and_then(|repo| {
|
||||
repo.read(cx)
|
||||
.work_directory_abs_path
|
||||
.file_name()
|
||||
.map(|name| name.to_string_lossy().to_string())
|
||||
})
|
||||
.map_or_else(|| "Git Graph".into(), |name| SharedString::from(name))
|
||||
}
|
||||
|
||||
fn show_toolbar(&self) -> bool {
|
||||
@@ -3049,7 +3097,7 @@ mod tests {
|
||||
0..usize::MAX,
|
||||
cx,
|
||||
)
|
||||
.0
|
||||
.commits
|
||||
.to_vec()
|
||||
});
|
||||
|
||||
@@ -3132,13 +3180,10 @@ mod tests {
|
||||
.any(|event| matches!(event, RepositoryEvent::MergeHeadsChanged)),
|
||||
"initial repository scan should emit MergeHeadsChanged"
|
||||
);
|
||||
|
||||
let graph_data_key = (crate::LogOrder::default(), crate::LogSource::default());
|
||||
let commit_count_after = repository.read_with(cx, |repo, _| {
|
||||
repo.initial_graph_data
|
||||
.get(&graph_data_key)
|
||||
.map(|(_, data)| data.len())
|
||||
.unwrap_or(0)
|
||||
repo.get_graph_data(crate::LogSource::default(), crate::LogOrder::default())
|
||||
.map(|data| data.commit_data.len())
|
||||
.unwrap()
|
||||
});
|
||||
assert_eq!(
|
||||
commits.len(),
|
||||
|
||||
Reference in new issue
Block a user