project_panel: Add auto_open settings (#40435)
- Based on #40234, and improvement of #40331 Release Notes: - Added granular settings to control when files auto-open in the project panel (project_panel.auto_open.on_create, on_paste, on_drop) <img width="662" height="367" alt="Screenshot_2025-10-16_17-28-31" src="https://github.com/user-attachments/assets/930a0a50-fc89-4c5d-8d05-b1fa2279de8b" /> --------- Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>
This commit is contained in:
co-authored by
Smit Barmase
parent
854c6873c7
commit
2ad7ecbcf0
@@ -1655,7 +1655,10 @@ impl ProjectPanel {
|
||||
}
|
||||
project_panel.update_visible_entries(None, false, false, window, cx);
|
||||
if is_new_entry && !is_dir {
|
||||
project_panel.open_entry(new_entry.id, true, false, cx);
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
if settings.auto_open.should_open_on_create() {
|
||||
project_panel.open_entry(new_entry.id, true, false, cx);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
})?;
|
||||
@@ -2709,15 +2712,16 @@ impl ProjectPanel {
|
||||
|
||||
if item_count == 1 {
|
||||
// open entry if not dir, setting is enabled, and only focus if rename is not pending
|
||||
if !entry.is_dir()
|
||||
&& ProjectPanelSettings::get_global(cx).open_file_on_paste
|
||||
{
|
||||
project_panel.open_entry(
|
||||
entry.id,
|
||||
disambiguation_range.is_none(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
if !entry.is_dir() {
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
if settings.auto_open.should_open_on_paste() {
|
||||
project_panel.open_entry(
|
||||
entry.id,
|
||||
disambiguation_range.is_none(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// if only one entry was pasted and it was disambiguated, open the rename editor
|
||||
@@ -3593,7 +3597,10 @@ impl ProjectPanel {
|
||||
let opened_entries = task.await.with_context(|| "failed to copy external paths")?;
|
||||
this.update(cx, |this, cx| {
|
||||
if open_file_after_drop && !opened_entries.is_empty() {
|
||||
this.open_entry(opened_entries[0], true, false, cx);
|
||||
let settings = ProjectPanelSettings::get_global(cx);
|
||||
if settings.auto_open.should_open_on_drop() {
|
||||
this.open_entry(opened_entries[0], true, false, cx);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct ProjectPanelSettings {
|
||||
pub hide_root: bool,
|
||||
pub hide_hidden: bool,
|
||||
pub drag_and_drop: bool,
|
||||
pub open_file_on_paste: bool,
|
||||
pub auto_open: AutoOpenSettings,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
@@ -48,6 +48,30 @@ pub struct ScrollbarSettings {
|
||||
pub show: Option<ShowScrollbar>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
pub struct AutoOpenSettings {
|
||||
pub on_create: bool,
|
||||
pub on_paste: bool,
|
||||
pub on_drop: bool,
|
||||
}
|
||||
|
||||
impl AutoOpenSettings {
|
||||
#[inline]
|
||||
pub fn should_open_on_create(self) -> bool {
|
||||
self.on_create
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn should_open_on_paste(self) -> bool {
|
||||
self.on_paste
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn should_open_on_drop(self) -> bool {
|
||||
self.on_drop
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollbarVisibility for ProjectPanelSettings {
|
||||
fn visibility(&self, cx: &ui::App) -> ShowScrollbar {
|
||||
self.scrollbar
|
||||
@@ -83,7 +107,14 @@ impl Settings for ProjectPanelSettings {
|
||||
hide_root: project_panel.hide_root.unwrap(),
|
||||
hide_hidden: project_panel.hide_hidden.unwrap(),
|
||||
drag_and_drop: project_panel.drag_and_drop.unwrap(),
|
||||
open_file_on_paste: project_panel.open_file_on_paste.unwrap(),
|
||||
auto_open: {
|
||||
let auto_open = project_panel.auto_open.unwrap();
|
||||
AutoOpenSettings {
|
||||
on_create: auto_open.on_create.unwrap(),
|
||||
on_paste: auto_open.on_paste.unwrap(),
|
||||
on_drop: auto_open.on_drop.unwrap(),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use gpui::{Empty, Entity, TestAppContext, VisualTestContext, WindowHandle};
|
||||
use pretty_assertions::assert_eq;
|
||||
use project::FakeFs;
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use settings::{ProjectPanelAutoOpenSettings, SettingsStore};
|
||||
use std::path::{Path, PathBuf};
|
||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||
use workspace::{
|
||||
@@ -1998,6 +1998,248 @@ async fn test_remove_opened_file(cx: &mut gpui::TestAppContext) {
|
||||
ensure_no_open_items_and_panes(&workspace, cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_new_file_when_enabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_create: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/root"), json!({})).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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
|
||||
cx.run_until_parked();
|
||||
panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("auto-open.rs", window, cx);
|
||||
});
|
||||
panel.confirm_edit(true, window, cx).unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
ensure_single_file_is_opened(&workspace, "auto-open.rs", cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_new_file_when_disabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_create: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/root"), json!({})).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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
panel.update_in(cx, |panel, window, cx| panel.new_file(&NewFile, window, cx));
|
||||
cx.run_until_parked();
|
||||
panel
|
||||
.update_in(cx, |panel, window, cx| {
|
||||
panel.filename_editor.update(cx, |editor, cx| {
|
||||
editor.set_text("manual-open.rs", window, cx);
|
||||
});
|
||||
panel.confirm_edit(true, window, cx).unwrap()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
ensure_no_open_items_and_panes(&workspace, cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_on_paste_when_enabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_paste: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"src": {
|
||||
"original.rs": ""
|
||||
},
|
||||
"target": {}
|
||||
}),
|
||||
)
|
||||
.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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
toggle_expand_dir(&panel, "root/src", cx);
|
||||
toggle_expand_dir(&panel, "root/target", cx);
|
||||
|
||||
select_path(&panel, "root/src/original.rs", cx);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.copy(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
select_path(&panel, "root/target", cx);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.paste(&Default::default(), window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
ensure_single_file_is_opened(&workspace, "target/original.rs", cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_on_paste_when_disabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_paste: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"src": {
|
||||
"original.rs": ""
|
||||
},
|
||||
"target": {}
|
||||
}),
|
||||
)
|
||||
.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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
toggle_expand_dir(&panel, "root/src", cx);
|
||||
toggle_expand_dir(&panel, "root/target", cx);
|
||||
|
||||
select_path(&panel, "root/src/original.rs", cx);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.copy(&Default::default(), window, cx);
|
||||
});
|
||||
|
||||
select_path(&panel, "root/target", cx);
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.paste(&Default::default(), window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
ensure_no_open_items_and_panes(&workspace, cx);
|
||||
assert!(
|
||||
find_project_entry(&panel, "root/target/original.rs", cx).is_some(),
|
||||
"Pasted entry should exist even when auto-open is disabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_on_drop_when_enabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_drop: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/root"), json!({})).await;
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let external_path = temp_dir.path().join("dropped.rs");
|
||||
std::fs::write(&external_path, "// dropped").unwrap();
|
||||
fs.insert_tree_from_real_fs(temp_dir.path(), temp_dir.path())
|
||||
.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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
let root_entry = find_project_entry(&panel, "root", cx).unwrap();
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.drop_external_files(std::slice::from_ref(&external_path), root_entry, window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
ensure_single_file_is_opened(&workspace, "dropped.rs", cx);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_auto_open_on_drop_when_disabled(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
set_auto_open_settings(
|
||||
cx,
|
||||
ProjectPanelAutoOpenSettings {
|
||||
on_drop: Some(false),
|
||||
..Default::default()
|
||||
},
|
||||
);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/root"), json!({})).await;
|
||||
|
||||
let temp_dir = tempfile::tempdir().unwrap();
|
||||
let external_path = temp_dir.path().join("manual.rs");
|
||||
std::fs::write(&external_path, "// dropped").unwrap();
|
||||
fs.insert_tree_from_real_fs(temp_dir.path(), temp_dir.path())
|
||||
.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);
|
||||
let panel = workspace.update(cx, ProjectPanel::new).unwrap();
|
||||
cx.run_until_parked();
|
||||
|
||||
let root_entry = find_project_entry(&panel, "root", cx).unwrap();
|
||||
panel.update_in(cx, |panel, window, cx| {
|
||||
panel.drop_external_files(std::slice::from_ref(&external_path), root_entry, window, cx);
|
||||
});
|
||||
cx.executor().run_until_parked();
|
||||
|
||||
ensure_no_open_items_and_panes(&workspace, cx);
|
||||
assert!(
|
||||
find_project_entry(&panel, "root/manual.rs", cx).is_some(),
|
||||
"Dropped entry should exist even when auto-open is disabled"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
|
||||
init_test_with_editor(cx);
|
||||
@@ -7368,6 +7610,19 @@ fn init_test_with_editor(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
fn set_auto_open_settings(
|
||||
cx: &mut TestAppContext,
|
||||
auto_open_settings: ProjectPanelAutoOpenSettings,
|
||||
) {
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings(cx, |settings| {
|
||||
settings.project_panel.get_or_insert_default().auto_open = Some(auto_open_settings);
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn ensure_single_file_is_opened(
|
||||
window: &WindowHandle<Workspace>,
|
||||
expected_path: &str,
|
||||
|
||||
Reference in New Issue
Block a user