project_panel: Add hide_root when only one folder in the project (#25289)

Closes #24188

Todo:
- [x] Hide root when only one worktree
- [x] Basic tests
- [x] Docs
- [x] Fix `select_first` + tests
- [x] Fix auto collapse dir + tests
- [x] Fix file / dir creation + tests
- [x] Fix root rename case

| Show root | Hide root |
|--------|--------|
| <img width="272" alt="Screenshot 2025-02-20 alle 22 35 55"
src="https://github.com/user-attachments/assets/361d93c7-e1ad-4419-a5f4-be62c9632807"
/> | <img width="269" alt="Screenshot 2025-02-20 alle 22 36 11"
src="https://github.com/user-attachments/assets/62011f76-a24b-4297-9734-f5c3b9f75760"
/> |
| <img width="275" alt="Screenshot 2025-02-20 alle 22 56 33"
src="https://github.com/user-attachments/assets/77e7e6e6-3dfe-4e88-b4b0-b620cb809d2b"
/> | <img width="267" alt="Screenshot 2025-02-20 alle 22 55 53"
src="https://github.com/user-attachments/assets/fa1099c8-7ed0-45ef-a7cf-aeb54b8283b1"
/> |


Release Notes:

- Added support to hide the root entry of the Project Panel when there’s
only one folder in the project. This can be enabled by setting
`hide_root` to `true` in the `project_panel` config.

---------

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
Angelk90 2025-06-09 13:16:31 +02:00 committed by GitHub
parent 72bcb0beb7
commit 387281fa5b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 605 additions and 35 deletions

View file

@ -464,6 +464,9 @@ impl ProjectPanel {
if project_panel_settings.hide_gitignore != new_settings.hide_gitignore {
this.update_visible_entries(None, cx);
}
if project_panel_settings.hide_root != new_settings.hide_root {
this.update_visible_entries(None, cx);
}
project_panel_settings = new_settings;
this.update_diagnostics(cx);
cx.notify();
@ -768,6 +771,12 @@ impl ProjectPanel {
let is_remote = project.is_via_collab();
let is_local = project.is_local();
let settings = ProjectPanelSettings::get_global(cx);
let visible_worktrees_count = project.visible_worktrees(cx).count();
let should_hide_rename = is_root
&& (cfg!(target_os = "windows")
|| (settings.hide_root && visible_worktrees_count == 1));
let context_menu = ContextMenu::build(window, cx, |menu, _, _| {
menu.context(self.focus_handle.clone()).map(|menu| {
if is_read_only {
@ -817,7 +826,7 @@ impl ProjectPanel {
Box::new(zed_actions::workspace::CopyRelativePath),
)
.separator()
.when(!is_root || !cfg!(target_os = "windows"), |menu| {
.when(!should_hide_rename, |menu| {
menu.action("Rename", Box::new(Rename))
})
.when(!is_root & !is_remote, |menu| {
@ -1538,6 +1547,16 @@ impl ProjectPanel {
if Some(entry) == worktree.read(cx).root_entry() {
return;
}
if Some(entry) == worktree.read(cx).root_entry() {
let settings = ProjectPanelSettings::get_global(cx);
let visible_worktrees_count =
self.project.read(cx).visible_worktrees(cx).count();
if settings.hide_root && visible_worktrees_count == 1 {
return;
}
}
self.edit_state = Some(EditState {
worktree_id,
entry_id: sub_entry_id,
@ -2106,19 +2125,11 @@ impl ProjectPanel {
}
fn select_first(&mut self, _: &SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
let worktree = self
.visible_entries
.first()
.and_then(|(worktree_id, _, _)| {
self.project.read(cx).worktree_for_id(*worktree_id, cx)
});
if let Some(worktree) = worktree {
let worktree = worktree.read(cx);
let worktree_id = worktree.id();
if let Some(root_entry) = worktree.root_entry() {
if let Some((worktree_id, visible_worktree_entries, _)) = self.visible_entries.first() {
if let Some(entry) = visible_worktree_entries.first() {
let selection = SelectedEntry {
worktree_id,
entry_id: root_entry.id,
worktree_id: *worktree_id,
entry_id: entry.id,
};
self.selection = Some(selection);
if window.modifiers().shift {
@ -2771,6 +2782,31 @@ impl ProjectPanel {
Some(())
}
fn create_new_git_entry(
parent_entry: &Entry,
git_summary: GitSummary,
new_entry_kind: EntryKind,
) -> GitEntry {
GitEntry {
entry: Entry {
id: NEW_ENTRY_ID,
kind: new_entry_kind,
path: parent_entry.path.join("\0").into(),
inode: 0,
mtime: parent_entry.mtime,
size: parent_entry.size,
is_ignored: parent_entry.is_ignored,
is_external: false,
is_private: false,
is_always_included: parent_entry.is_always_included,
canonical_path: parent_entry.canonical_path.clone(),
char_bag: parent_entry.char_bag,
is_fifo: parent_entry.is_fifo,
},
git_summary,
}
}
fn update_visible_entries(
&mut self,
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
@ -2790,7 +2826,10 @@ impl ProjectPanel {
let old_ancestors = std::mem::take(&mut self.ancestors);
self.visible_entries.clear();
let mut max_width_item = None;
for worktree in project.visible_worktrees(cx) {
let visible_worktrees: Vec<_> = project.visible_worktrees(cx).collect();
let hide_root = settings.hide_root && visible_worktrees.len() == 1;
for worktree in visible_worktrees {
let worktree_snapshot = worktree.read(cx).snapshot();
let worktree_id = worktree_snapshot.id();
@ -2825,6 +2864,18 @@ impl ProjectPanel {
GitTraversal::new(&repo_snapshots, worktree_snapshot.entries(true, 0));
let mut auto_folded_ancestors = vec![];
while let Some(entry) = entry_iter.entry() {
if hide_root && Some(entry.entry) == worktree.read(cx).root_entry() {
if new_entry_parent_id == Some(entry.id) {
visible_worktree_entries.push(Self::create_new_git_entry(
&entry.entry,
entry.git_summary,
new_entry_kind,
));
new_entry_parent_id = None;
}
entry_iter.advance();
continue;
}
if auto_collapse_dirs && entry.kind.is_dir() {
auto_folded_ancestors.push(entry.id);
if !self.unfolded_dir_ids.contains(&entry.id) {
@ -2878,24 +2929,11 @@ impl ProjectPanel {
false
};
if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) {
visible_worktree_entries.push(GitEntry {
entry: Entry {
id: NEW_ENTRY_ID,
kind: new_entry_kind,
path: entry.path.join("\0").into(),
inode: 0,
mtime: entry.mtime,
size: entry.size,
is_ignored: entry.is_ignored,
is_external: false,
is_private: false,
is_always_included: entry.is_always_included,
canonical_path: entry.canonical_path.clone(),
char_bag: entry.char_bag,
is_fifo: entry.is_fifo,
},
git_summary: entry.git_summary,
});
visible_worktree_entries.push(Self::create_new_git_entry(
&entry.entry,
entry.git_summary,
new_entry_kind,
));
}
let worktree_abs_path = worktree.read(cx).abs_path();
let (depth, path) = if Some(entry.entry) == worktree.read(cx).root_entry() {
@ -3729,7 +3767,7 @@ impl ProjectPanel {
None
}
})
.unwrap_or((0, 0));
.unwrap_or_else(|| (0, entry.path.components().count()));
(depth, difference)
}