git: Add tree view support to Git Panel (#44089)

Closes #35803

This PR adds tree view support to the git panel UI as an additional
setting and moves git entry checkboxes to the right. Tree view only
supports sorting by paths behavior since sorting by status can become
noisy, due to having to duplicate directories that have entries with
different statuses.

### Tree vs Flat View
<img width="358" height="250" alt="image"
src="https://github.com/user-attachments/assets/c6b95d57-12fc-4c5e-8537-ee129963e50c"
/>
<img width="362" height="152" alt="image"
src="https://github.com/user-attachments/assets/0a69e00f-3878-4807-ae45-65e2d54174fc"
/>


#### Architecture changes

Before this PR, `GitPanel::entries` represented all entries and all
visible entries because both sets were equal to one another. However,
this equality isn't true for tree view, because entries can be
collapsed. To fix this, `TreeState` was added as a logical indices field
that is used to filter out non-visible entries. A benefit of this field
is that it could be used in the future to implement searching in the
GitPanel.

Another significant thing this PR changed was adding a HashMap field
`entries_by_indices` on `GitPanel`. We did this because `entry_by_path`
used binary search, which becomes overly complicated to implement for
tree view. The performance of this function matters because it's a hot
code path, so a linear search wasn't ideal either. The solution was
using a hash map to improve time complexity from O(log n) to O(1), where
n is the count of entries.

#### Follow-ups
In the future, we could use `ui::ListItem` to render entries in the tree
view to improve UI consistency.
 
Release Notes:

- Added tree view for Git panel. Users are able to switch between Flat
and Tree view in Git panel.

---------

Co-authored-by: Anthony Eid <anthony@zed.dev>
Co-authored-by: Remco Smits <djsmits12@gmail.com>
This commit is contained in:
Xipeng Jin 2025-12-10 15:11:36 -05:00 committed by GitHub
parent 3a84ec38ac
commit 9e628505f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 966 additions and 216 deletions

View file

@ -870,6 +870,10 @@
//
// Default: false
"collapse_untracked_diff": false,
/// Whether to show entries with tree or flat view in the panel
///
/// Default: false
"tree_view": false,
"scrollbar": {
// When to show the scrollbar in the git panel.
//

File diff suppressed because it is too large Load diff

View file

@ -24,6 +24,7 @@ pub struct GitPanelSettings {
pub fallback_branch_name: String,
pub sort_by_path: bool,
pub collapse_untracked_diff: bool,
pub tree_view: bool,
}
impl ScrollbarVisibility for GitPanelSettings {
@ -56,6 +57,7 @@ impl Settings for GitPanelSettings {
fallback_branch_name: git_panel.fallback_branch_name.unwrap(),
sort_by_path: git_panel.sort_by_path.unwrap(),
collapse_untracked_diff: git_panel.collapse_untracked_diff.unwrap(),
tree_view: git_panel.tree_view.unwrap(),
}
}
}

View file

@ -644,7 +644,10 @@ impl ProjectDiff {
}
fn sort_prefix(repo: &Repository, repo_path: &RepoPath, status: FileStatus, cx: &App) -> u64 {
if GitPanelSettings::get_global(cx).sort_by_path {
let settings = GitPanelSettings::get_global(cx);
// Tree view can only sort by path
if settings.sort_by_path || settings.tree_view {
TRACKED_SORT_PREFIX
} else if repo.had_conflict_on_last_merge_head_change(repo_path) {
CONFLICT_SORT_PREFIX

View file

@ -511,6 +511,11 @@ pub struct GitPanelSettingsContent {
///
/// Default: false
pub collapse_untracked_diff: Option<bool>,
/// Whether to show entries with tree or flat view in the panel
///
/// Default: false
pub tree_view: Option<bool>,
}
#[derive(

View file

@ -4314,6 +4314,24 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Tree View",
description: "Enable to show entries in tree view list, disable to show in flat view list.",
field: Box::new(SettingField {
json_path: Some("git_panel.tree_view"),
pick: |settings_content| {
settings_content.git_panel.as_ref()?.tree_view.as_ref()
},
write: |settings_content, value| {
settings_content
.git_panel
.get_or_insert_default()
.tree_view = value;
},
}),
metadata: None,
files: USER,
}),
SettingsPageItem::SettingItem(SettingItem {
title: "Scroll Bar",
description: "How and when the scrollbar should be displayed.",