@@ -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();
- visible_worktree_entries.push(entry.to_owned());
+ 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,
@@ -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);