Add support for excluding files based on .gitignore
(#26636)
Closes: #17543 Release Notes: - **New Feature:** Introduced the ability to automatically remove files and directories from the Zed project panel that are specified in `.gitignore`. - **Configuration Option:** This behavior can be controlled via the new `project_panel.hide_gitignore` setting. By setting it to `true`, files listed in `.gitignore` will be excluded from the project panel. - **Toggle:** Ability to toggle this setting using the action `ProjectPanel::ToggleHideGitIgnore` ```json "project_panel": { "hide_gitignore": true }, ``` This results in a cleaner and easier to browse project panel for projects that generate a lot of object files like `xv6-riscv` or `linux` without needing to tweak `file_scan_exclusions` on `settings.json` **Preview:** - With `"project_panel.hide_gitignore": false` (default, this is how zed currently looks)  - With `"project_panel.hide_gitignore": true`  - Action `ProjectPanel::ToggleHideGitIgnore` 
This commit is contained in:
parent
9eacac62a9
commit
82536f5243
4 changed files with 196 additions and 8 deletions
|
@ -427,6 +427,8 @@
|
|||
"project_panel": {
|
||||
// Whether to show the project panel button in the status bar
|
||||
"button": true,
|
||||
// Whether to hide the gitignore entries in the project panel.
|
||||
"hide_gitignore": false,
|
||||
// Default width of the project panel.
|
||||
"default_width": 240,
|
||||
// Where to dock the project panel. Can be 'left' or 'right'.
|
||||
|
|
|
@ -36,7 +36,7 @@ use project_panel_settings::{
|
|||
};
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{Settings, SettingsStore};
|
||||
use settings::{update_settings_file, Settings, SettingsStore};
|
||||
use smallvec::SmallVec;
|
||||
use std::any::TypeId;
|
||||
use std::{
|
||||
|
@ -197,6 +197,7 @@ actions!(
|
|||
Open,
|
||||
OpenPermanent,
|
||||
ToggleFocus,
|
||||
ToggleHideGitIgnore,
|
||||
NewSearchInDirectory,
|
||||
UnfoldDirectory,
|
||||
FoldDirectory,
|
||||
|
@ -233,6 +234,13 @@ pub fn init(cx: &mut App) {
|
|||
workspace.register_action(|workspace, _: &ToggleFocus, window, cx| {
|
||||
workspace.toggle_panel_focus::<ProjectPanel>(window, cx);
|
||||
});
|
||||
|
||||
workspace.register_action(|workspace, _: &ToggleHideGitIgnore, _, cx| {
|
||||
let fs = workspace.app_state().fs.clone();
|
||||
update_settings_file::<ProjectPanelSettings>(fs, cx, move |setting, _| {
|
||||
setting.hide_gitignore = Some(!setting.hide_gitignore.unwrap_or(false));
|
||||
})
|
||||
});
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
|
@ -414,6 +422,9 @@ impl ProjectPanel {
|
|||
cx.observe_global::<SettingsStore>(move |this, cx| {
|
||||
let new_settings = *ProjectPanelSettings::get_global(cx);
|
||||
if project_panel_settings != new_settings {
|
||||
if project_panel_settings.hide_gitignore != new_settings.hide_gitignore {
|
||||
this.update_visible_entries(None, cx);
|
||||
}
|
||||
project_panel_settings = new_settings;
|
||||
this.update_diagnostics(cx);
|
||||
cx.notify();
|
||||
|
@ -1536,7 +1547,6 @@ impl ProjectPanel {
|
|||
if sanitized_entries.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let project = self.project.read(cx);
|
||||
let (worktree_id, worktree) = sanitized_entries
|
||||
.iter()
|
||||
|
@ -1568,13 +1578,14 @@ impl ProjectPanel {
|
|||
|
||||
// Remove all siblings that are being deleted except the last marked entry
|
||||
let snapshot = worktree.snapshot();
|
||||
let hide_gitignore = ProjectPanelSettings::get_global(cx).hide_gitignore;
|
||||
let mut siblings: Vec<_> = ChildEntriesGitIter::new(&snapshot, parent_path)
|
||||
.filter(|sibling| {
|
||||
sibling.id == latest_entry.id
|
||||
|| !marked_entries_in_worktree.contains(&&SelectedEntry {
|
||||
(sibling.id == latest_entry.id)
|
||||
|| (!marked_entries_in_worktree.contains(&&SelectedEntry {
|
||||
worktree_id,
|
||||
entry_id: sibling.id,
|
||||
})
|
||||
}) && (!hide_gitignore || !sibling.is_ignored))
|
||||
})
|
||||
.map(|entry| entry.to_owned())
|
||||
.collect();
|
||||
|
@ -2590,7 +2601,9 @@ impl ProjectPanel {
|
|||
new_selected_entry: Option<(WorktreeId, ProjectEntryId)>,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let auto_collapse_dirs = ProjectPanelSettings::get_global(cx).auto_fold_dirs;
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
let auto_collapse_dirs = settings.auto_fold_dirs;
|
||||
let hide_gitignore = settings.hide_gitignore;
|
||||
let project = self.project.read(cx);
|
||||
self.last_worktree_root_id = project
|
||||
.visible_worktrees(cx)
|
||||
|
@ -2675,7 +2688,9 @@ impl ProjectPanel {
|
|||
}
|
||||
}
|
||||
auto_folded_ancestors.clear();
|
||||
if !hide_gitignore || !entry.is_ignored {
|
||||
visible_worktree_entries.push(entry.to_owned());
|
||||
}
|
||||
let precedes_new_entry = if let Some(new_entry_id) = new_entry_parent_id {
|
||||
entry.id == new_entry_id || {
|
||||
self.ancestors.get(&entry.id).map_or(false, |entries| {
|
||||
|
@ -2688,7 +2703,7 @@ impl ProjectPanel {
|
|||
} else {
|
||||
false
|
||||
};
|
||||
if precedes_new_entry {
|
||||
if precedes_new_entry && (!hide_gitignore || !entry.is_ignored) {
|
||||
visible_worktree_entries.push(GitEntry {
|
||||
entry: Entry {
|
||||
id: NEW_ENTRY_ID,
|
||||
|
|
|
@ -31,6 +31,7 @@ pub enum EntrySpacing {
|
|||
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ProjectPanelSettings {
|
||||
pub button: bool,
|
||||
pub hide_gitignore: bool,
|
||||
pub default_width: Pixels,
|
||||
pub dock: ProjectPanelDockPosition,
|
||||
pub entry_spacing: EntrySpacing,
|
||||
|
@ -93,6 +94,10 @@ pub struct ProjectPanelSettingsContent {
|
|||
///
|
||||
/// Default: true
|
||||
pub button: Option<bool>,
|
||||
/// Whether to hide gitignore files in the project panel.
|
||||
///
|
||||
/// Default: false
|
||||
pub hide_gitignore: Option<bool>,
|
||||
/// Customize default width (in pixels) taken by project panel
|
||||
///
|
||||
/// Default: 240
|
||||
|
|
|
@ -3735,6 +3735,172 @@ async fn test_basic_file_deletion_scenarios(cx: &mut gpui::TestAppContext) {
|
|||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_deletion_gitignored(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"aa": "// Testing 1",
|
||||
"bb": "// Testing 2",
|
||||
"cc": "// Testing 3",
|
||||
"dd": "// Testing 4",
|
||||
"ee": "// Testing 5",
|
||||
"ff": "// Testing 6",
|
||||
"gg": "// Testing 7",
|
||||
"hh": "// Testing 8",
|
||||
"ii": "// Testing 8",
|
||||
".gitignore": "bb\ndd\nee\nff\nii\n'",
|
||||
}),
|
||||
)
|
||||
.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);
|
||||
|
||||
// Test 1: Auto selection with one gitignored file next to the deleted file
|
||||
cx.update(|_, cx| {
|
||||
let settings = *ProjectPanelSettings::get_global(cx);
|
||||
ProjectPanelSettings::override_global(
|
||||
ProjectPanelSettings {
|
||||
hide_gitignore: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
select_path(&panel, "root/aa", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" .gitignore",
|
||||
" aa <== selected",
|
||||
" cc",
|
||||
" gg",
|
||||
" hh"
|
||||
],
|
||||
"Initial state should hide files on .gitignore"
|
||||
);
|
||||
|
||||
submit_deletion(&panel, cx);
|
||||
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" .gitignore",
|
||||
" cc <== selected",
|
||||
" gg",
|
||||
" hh"
|
||||
],
|
||||
"Should select next entry not on .gitignore"
|
||||
);
|
||||
|
||||
// Test 2: Auto selection with many gitignored files next to the deleted file
|
||||
submit_deletion(&panel, cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" .gitignore",
|
||||
" gg <== selected",
|
||||
" hh"
|
||||
],
|
||||
"Should select next entry not on .gitignore"
|
||||
);
|
||||
|
||||
// Test 3: Auto selection of entry before deleted file
|
||||
select_path(&panel, "root/hh", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" .gitignore",
|
||||
" gg",
|
||||
" hh <== selected"
|
||||
],
|
||||
"Should select next entry not on .gitignore"
|
||||
);
|
||||
submit_deletion(&panel, cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&["v root", " .gitignore", " gg <== selected"],
|
||||
"Should select next entry not on .gitignore"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_nested_deletion_gitignore(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor().clone());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"dir1": {
|
||||
"file1": "// Testing",
|
||||
"file2": "// Testing",
|
||||
"file3": "// Testing"
|
||||
},
|
||||
"aa": "// Testing",
|
||||
".gitignore": "file1\nfile3\n",
|
||||
}),
|
||||
)
|
||||
.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_gitignore: true,
|
||||
..settings
|
||||
},
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
|
||||
// Test 1: Visible items should exclude files on gitignore
|
||||
toggle_expand_dir(&panel, "root/dir1", cx);
|
||||
select_path(&panel, "root/dir1/file2", cx);
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" v dir1",
|
||||
" file2 <== selected",
|
||||
" .gitignore",
|
||||
" aa"
|
||||
],
|
||||
"Initial state should hide files on .gitignore"
|
||||
);
|
||||
submit_deletion(&panel, cx);
|
||||
|
||||
// Test 2: Auto selection should go to the parent
|
||||
assert_eq!(
|
||||
visible_entries_as_strings(&panel, 0..10, cx),
|
||||
&[
|
||||
"v root",
|
||||
" v dir1 <== selected",
|
||||
" .gitignore",
|
||||
" aa"
|
||||
],
|
||||
"Initial state should hide files on .gitignore"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_complex_selection_scenarios(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue