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:
parent
72bcb0beb7
commit
387281fa5b
5 changed files with 605 additions and 35 deletions
|
@ -604,7 +604,9 @@
|
|||
// 2. Never show indent guides:
|
||||
// "never"
|
||||
"show": "always"
|
||||
}
|
||||
},
|
||||
// Whether to hide the root entry when only one folder is open in the window.
|
||||
"hide_root": false
|
||||
},
|
||||
"outline_panel": {
|
||||
// Whether to show the outline panel button in the status bar
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ pub struct ProjectPanelSettings {
|
|||
pub auto_fold_dirs: bool,
|
||||
pub scrollbar: ScrollbarSettings,
|
||||
pub show_diagnostics: ShowDiagnostics,
|
||||
pub hide_root: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
|
@ -145,6 +146,10 @@ pub struct ProjectPanelSettingsContent {
|
|||
pub show_diagnostics: Option<ShowDiagnostics>,
|
||||
/// Settings related to indent guides in the project panel.
|
||||
pub indent_guides: Option<IndentGuidesSettingsContent>,
|
||||
/// Whether to hide the root entry when only one folder is open in the window.
|
||||
///
|
||||
/// Default: false
|
||||
pub hide_root: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for ProjectPanelSettings {
|
||||
|
|
|
@ -309,6 +309,7 @@ async fn test_auto_collapse_dir_paths(cx: &mut gpui::TestAppContext) {
|
|||
)
|
||||
.await;
|
||||
|
||||
// Test 1: Multiple worktrees with auto_fold_dirs = true
|
||||
let project = Project::test(
|
||||
fs.clone(),
|
||||
[path!("/root1").as_ref(), path!("/root2").as_ref()],
|
||||
|
@ -392,6 +393,66 @@ async fn test_auto_collapse_dir_paths(cx: &mut gpui::TestAppContext) {
|
|||
separator!(" file_1.java"),
|
||||
]
|
||||
);
|
||||
|
||||
// Test 2: Single worktree with auto_fold_dirs = true and hide_root = true
|
||||
{
|
||||
let project = Project::test(fs.clone(), [path!("/root1").as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
auto_fold_dirs: true,
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[separator!("> dir_1/nested_dir_1/nested_dir_2/nested_dir_3")],
|
||||
"Single worktree with hide_root=true should hide root and show auto-folded paths"
|
||||
);
|
||||
|
||||
toggle_expand_dir(
|
||||
&panel,
|
||||
"root1/dir_1/nested_dir_1/nested_dir_2/nested_dir_3",
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
separator!("v dir_1/nested_dir_1/nested_dir_2/nested_dir_3 <== selected"),
|
||||
separator!(" > nested_dir_4/nested_dir_5"),
|
||||
separator!(" file_a.java"),
|
||||
separator!(" file_b.java"),
|
||||
separator!(" file_c.java"),
|
||||
],
|
||||
"Expanded auto-folded path with hidden root should show contents without root prefix"
|
||||
);
|
||||
|
||||
toggle_expand_dir(
|
||||
&panel,
|
||||
"root1/dir_1/nested_dir_1/nested_dir_2/nested_dir_3/nested_dir_4/nested_dir_5",
|
||||
cx,
|
||||
);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
separator!("v dir_1/nested_dir_1/nested_dir_2/nested_dir_3"),
|
||||
separator!(" v nested_dir_4/nested_dir_5 <== selected"),
|
||||
separator!(" file_d.java"),
|
||||
separator!(" file_a.java"),
|
||||
separator!(" file_b.java"),
|
||||
separator!(" file_c.java"),
|
||||
],
|
||||
"Nested expansion with hidden root should maintain proper indentation"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test(iterations = 30)]
|
||||
|
@ -2475,6 +2536,7 @@ async fn test_select_directory(cx: &mut gpui::TestAppContext) {
|
|||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_select_first_last(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
@ -2543,6 +2605,46 @@ async fn test_select_first_last(cx: &mut gpui::TestAppContext) {
|
|||
" file_2.py <== selected",
|
||||
]
|
||||
);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"> dir_1",
|
||||
"> zdir_2",
|
||||
" file_1.py",
|
||||
" file_2.py",
|
||||
],
|
||||
"With hide_root=true, root should be hidden"
|
||||
);
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.select_first(&SelectFirst, window, cx)
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"> dir_1 <== selected",
|
||||
"> zdir_2",
|
||||
" file_1.py",
|
||||
" file_2.py",
|
||||
],
|
||||
"With hide_root=true, first entry should be dir_1, not the hidden root"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -2789,6 +2891,101 @@ async fn test_rename_root_of_worktree(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_rename_with_hide_root(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
json!({
|
||||
"dir1": { "file1.txt": "content" },
|
||||
"file2.txt": "content",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
fs.insert_tree("/root2", json!({ "file3.txt": "content" }))
|
||||
.await;
|
||||
|
||||
// Test 1: Single worktree, hide_root=true - rename should be blocked
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
panel.update(cx, |panel, cx| {
|
||||
let project = panel.project.read(cx);
|
||||
let worktree = project.visible_worktrees(cx).next().unwrap();
|
||||
let root_entry = worktree.read(cx).root_entry().unwrap();
|
||||
panel.selection = Some(SelectedEntry {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
entry_id: root_entry.id,
|
||||
});
|
||||
});
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx));
|
||||
|
||||
assert!(
|
||||
panel.read_with(cx, |panel, _| panel.edit_state.is_none()),
|
||||
"Rename should be blocked when hide_root=true with single worktree"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 2: Multiple worktrees, hide_root=true - rename should work
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
select_path(&panel, "root1", cx);
|
||||
panel.update_in(cx, |panel, window, cx| panel.rename(&Rename, window, cx));
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
assert!(
|
||||
panel.read_with(cx, |panel, _| panel.edit_state.is_none()),
|
||||
"Rename should be blocked on Windows even with multiple worktrees"
|
||||
);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
assert!(
|
||||
panel.read_with(cx, |panel, _| panel.edit_state.is_some()),
|
||||
"Rename should work with multiple worktrees on non-Windows when hide_root=true"
|
||||
);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.cancel(&menu::Cancel, window, cx)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_multiple_marked_entries(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
@ -5098,6 +5295,155 @@ async fn test_create_entries_without_selection(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_entries_without_selection_hide_root(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"existing_dir": {
|
||||
"existing_file.txt": "",
|
||||
},
|
||||
"existing_file.txt": "",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace
|
||||
.update(cx, |workspace, window, cx| {
|
||||
let panel = ProjectPanel::new(workspace, window, cx);
|
||||
workspace.add_panel(panel.clone(), window, cx);
|
||||
panel
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"> existing_dir",
|
||||
" existing_file.txt",
|
||||
],
|
||||
"Initial state with hide_root=true, root should be hidden and nothing selected"
|
||||
);
|
||||
|
||||
panel.update(cx, |panel, _| {
|
||||
assert!(
|
||||
panel.selection.is_none(),
|
||||
"Should have no selection initially"
|
||||
);
|
||||
});
|
||||
|
||||
// Test 1: Create new file when no entry is selected
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.new_file(&NewFile, window, cx);
|
||||
});
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
assert!(panel.filename_editor.read(cx).is_focused(window));
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"> existing_dir",
|
||||
" [EDITOR: ''] <== selected",
|
||||
" existing_file.txt",
|
||||
],
|
||||
"Editor should appear at root level when hide_root=true and no selection"
|
||||
);
|
||||
|
||||
let confirm = panel.update_in(cx, |panel, window, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("new_file_at_root.txt", window, cx)
|
||||
});
|
||||
panel.confirm_edit(window, cx).unwrap()
|
||||
});
|
||||
confirm.await.unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"> existing_dir",
|
||||
" existing_file.txt",
|
||||
" new_file_at_root.txt <== selected <== marked",
|
||||
],
|
||||
"New file should be created at root level and visible without root prefix"
|
||||
);
|
||||
|
||||
assert!(
|
||||
fs.is_file(Path::new("/root/new_file_at_root.txt")).await,
|
||||
"File should be created in the actual root directory"
|
||||
);
|
||||
|
||||
// Test 2: Create new directory when no entry is selected
|
||||
panel.update(cx, |panel, _| {
|
||||
panel.selection = None;
|
||||
});
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.new_directory(&NewDirectory, window, cx);
|
||||
});
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
assert!(panel.filename_editor.read(cx).is_focused(window));
|
||||
});
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"> [EDITOR: ''] <== selected",
|
||||
"> existing_dir",
|
||||
" existing_file.txt",
|
||||
" new_file_at_root.txt",
|
||||
],
|
||||
"Directory editor should appear at root level when hide_root=true and no selection"
|
||||
);
|
||||
|
||||
let confirm = panel.update_in(cx, |panel, window, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("new_dir_at_root", window, cx)
|
||||
});
|
||||
panel.confirm_edit(window, cx).unwrap()
|
||||
});
|
||||
confirm.await.unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..20, cx),
|
||||
&[
|
||||
"> existing_dir",
|
||||
"v new_dir_at_root <== selected",
|
||||
" existing_file.txt",
|
||||
" new_file_at_root.txt",
|
||||
],
|
||||
"New directory should be created at root level and visible without root prefix"
|
||||
);
|
||||
|
||||
assert!(
|
||||
fs.is_dir(Path::new("/root/new_dir_at_root")).await,
|
||||
"Directory should be created in the actual root directory"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_highlight_entry_for_external_drag(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -5297,6 +5643,184 @@ async fn test_highlight_entry_for_selection_drag(cx: &mut gpui::TestAppContext)
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_hide_root(cx: &mut gpui::TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
"/root1",
|
||||
json!({
|
||||
"dir1": {
|
||||
"file1.txt": "content",
|
||||
"file2.txt": "content",
|
||||
},
|
||||
"dir2": {
|
||||
"file3.txt": "content",
|
||||
},
|
||||
"file4.txt": "content",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
fs.insert_tree(
|
||||
"/root2",
|
||||
json!({
|
||||
"dir3": {
|
||||
"file5.txt": "content",
|
||||
},
|
||||
"file6.txt": "content",
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Test 1: Single worktree with hide_root = false
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: false,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
#[rustfmt::skip]
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > dir1",
|
||||
" > dir2",
|
||||
" file4.txt",
|
||||
],
|
||||
"With hide_root=false and single worktree, root should be visible"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 2: Single worktree with hide_root = true
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
// Set hide_root to true
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["> dir1", "> dir2", " file4.txt",],
|
||||
"With hide_root=true and single worktree, root should be hidden"
|
||||
);
|
||||
|
||||
// Test expanding directories still works without root
|
||||
toggle_expand_dir(&panel, "root1/dir1", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v dir1 <== selected",
|
||||
" file1.txt",
|
||||
" file2.txt",
|
||||
"> dir2",
|
||||
" file4.txt",
|
||||
],
|
||||
"Should be able to expand directories even when root is hidden"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 3: Multiple worktrees with hide_root = true
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
// Set hide_root to true
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > dir1",
|
||||
" > dir2",
|
||||
" file4.txt",
|
||||
"v root2",
|
||||
" > dir3",
|
||||
" file6.txt",
|
||||
],
|
||||
"With hide_root=true and multiple worktrees, roots should still be visible"
|
||||
);
|
||||
}
|
||||
|
||||
// Test 4: Multiple worktrees with hide_root = false
|
||||
{
|
||||
let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await;
|
||||
let workspace =
|
||||
cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
|
||||
let cx = &mut VisualTestContext::from_window(*workspace, cx);
|
||||
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_root: false,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root1",
|
||||
" > dir1",
|
||||
" > dir2",
|
||||
" file4.txt",
|
||||
"v root2",
|
||||
" > dir3",
|
||||
" file6.txt",
|
||||
],
|
||||
"With hide_root=false and multiple worktrees, roots should be visible"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn select_path(panel: &Entity<ProjectPanel>, path: impl AsRef<Path>, cx: &mut VisualTestContext) {
|
||||
let path = path.as_ref();
|
||||
panel.update(cx, |panel, cx| {
|
||||
|
|
|
@ -3098,7 +3098,8 @@ Run the `theme selector: toggle` action in the command palette to see a current
|
|||
"show_diagnostics": "all",
|
||||
"indent_guides": {
|
||||
"show": "always"
|
||||
}
|
||||
},
|
||||
"hide_root": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue