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
Generated
+1
@@ -13078,6 +13078,7 @@ dependencies = [
|
|||||||
"settings",
|
"settings",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"telemetry",
|
"telemetry",
|
||||||
|
"tempfile",
|
||||||
"theme",
|
"theme",
|
||||||
"ui",
|
"ui",
|
||||||
"util",
|
"util",
|
||||||
|
|||||||
@@ -748,8 +748,15 @@
|
|||||||
"hide_root": false,
|
"hide_root": false,
|
||||||
// Whether to hide the hidden entries in the project panel.
|
// Whether to hide the hidden entries in the project panel.
|
||||||
"hide_hidden": false,
|
"hide_hidden": false,
|
||||||
// Whether to automatically open files when pasting them in the project panel.
|
// Settings for automatically opening files.
|
||||||
"open_file_on_paste": true
|
"auto_open": {
|
||||||
|
// Whether to automatically open newly created files in the editor.
|
||||||
|
"on_create": true,
|
||||||
|
// Whether to automatically open files after pasting or duplicating them.
|
||||||
|
"on_paste": true,
|
||||||
|
// Whether to automatically open files dropped from external sources.
|
||||||
|
"on_drop": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"outline_panel": {
|
"outline_panel": {
|
||||||
// Whether to show the outline panel button in the status bar
|
// Whether to show the outline panel button in the status bar
|
||||||
|
|||||||
@@ -135,3 +135,9 @@ pub(crate) mod m_2025_10_21 {
|
|||||||
|
|
||||||
pub(crate) use settings::make_relative_line_numbers_an_enum;
|
pub(crate) use settings::make_relative_line_numbers_an_enum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) mod m_2025_11_12 {
|
||||||
|
mod settings;
|
||||||
|
|
||||||
|
pub(crate) use settings::SETTINGS_PATTERNS;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
use std::ops::Range;
|
||||||
|
use tree_sitter::{Query, QueryMatch};
|
||||||
|
|
||||||
|
use crate::MigrationPatterns;
|
||||||
|
use crate::patterns::SETTINGS_NESTED_KEY_VALUE_PATTERN;
|
||||||
|
|
||||||
|
pub const SETTINGS_PATTERNS: MigrationPatterns = &[
|
||||||
|
(
|
||||||
|
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||||
|
rename_open_file_on_paste_setting,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SETTINGS_NESTED_KEY_VALUE_PATTERN,
|
||||||
|
replace_open_file_on_paste_setting_value,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
fn rename_open_file_on_paste_setting(
|
||||||
|
contents: &str,
|
||||||
|
mat: &QueryMatch,
|
||||||
|
query: &Query,
|
||||||
|
) -> Option<(Range<usize>, String)> {
|
||||||
|
if !is_project_panel_open_file_on_paste(contents, mat, query) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting_name_ix = query.capture_index_for_name("setting_name")?;
|
||||||
|
let setting_name_range = mat
|
||||||
|
.nodes_for_capture_index(setting_name_ix)
|
||||||
|
.next()?
|
||||||
|
.byte_range();
|
||||||
|
|
||||||
|
Some((setting_name_range, "auto_open".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_open_file_on_paste_setting_value(
|
||||||
|
contents: &str,
|
||||||
|
mat: &QueryMatch,
|
||||||
|
query: &Query,
|
||||||
|
) -> Option<(Range<usize>, String)> {
|
||||||
|
if !is_project_panel_open_file_on_paste(contents, mat, query) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let value_ix = query.capture_index_for_name("setting_value")?;
|
||||||
|
let value_node = mat.nodes_for_capture_index(value_ix).next()?;
|
||||||
|
let value_range = value_node.byte_range();
|
||||||
|
let value_text = contents.get(value_range.clone())?.trim();
|
||||||
|
|
||||||
|
let normalized_value = match value_text {
|
||||||
|
"true" => "true",
|
||||||
|
"false" => "false",
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((
|
||||||
|
value_range,
|
||||||
|
format!("{{ \"on_paste\": {normalized_value} }}"),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_project_panel_open_file_on_paste(contents: &str, mat: &QueryMatch, query: &Query) -> bool {
|
||||||
|
let parent_key_ix = match query.capture_index_for_name("parent_key") {
|
||||||
|
Some(ix) => ix,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
let parent_range = match mat.nodes_for_capture_index(parent_key_ix).next() {
|
||||||
|
Some(node) => node.byte_range(),
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
if contents.get(parent_range) != Some("project_panel") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let setting_name_ix = match query.capture_index_for_name("setting_name") {
|
||||||
|
Some(ix) => ix,
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
let setting_name_range = match mat.nodes_for_capture_index(setting_name_ix).next() {
|
||||||
|
Some(node) => node.byte_range(),
|
||||||
|
None => return false,
|
||||||
|
};
|
||||||
|
contents.get(setting_name_range) == Some("open_file_on_paste")
|
||||||
|
}
|
||||||
@@ -215,6 +215,10 @@ pub fn migrate_settings(text: &str) -> Result<Option<String>> {
|
|||||||
MigrationType::Json(migrations::m_2025_10_16::restore_code_actions_on_format),
|
MigrationType::Json(migrations::m_2025_10_16::restore_code_actions_on_format),
|
||||||
MigrationType::Json(migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum),
|
MigrationType::Json(migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum),
|
||||||
MigrationType::Json(migrations::m_2025_10_21::make_relative_line_numbers_an_enum),
|
MigrationType::Json(migrations::m_2025_10_21::make_relative_line_numbers_an_enum),
|
||||||
|
MigrationType::TreeSitter(
|
||||||
|
migrations::m_2025_11_12::SETTINGS_PATTERNS,
|
||||||
|
&SETTINGS_QUERY_2025_11_12,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
run_migrations(text, migrations)
|
run_migrations(text, migrations)
|
||||||
}
|
}
|
||||||
@@ -333,6 +337,10 @@ define_query!(
|
|||||||
SETTINGS_QUERY_2025_10_03,
|
SETTINGS_QUERY_2025_10_03,
|
||||||
migrations::m_2025_10_03::SETTINGS_PATTERNS
|
migrations::m_2025_10_03::SETTINGS_PATTERNS
|
||||||
);
|
);
|
||||||
|
define_query!(
|
||||||
|
SETTINGS_QUERY_2025_11_12,
|
||||||
|
migrations::m_2025_11_12::SETTINGS_PATTERNS
|
||||||
|
);
|
||||||
|
|
||||||
// custom query
|
// custom query
|
||||||
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock<Query> = LazyLock::new(|| {
|
||||||
@@ -2193,4 +2201,49 @@ mod tests {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_panel_open_file_on_paste_migration() {
|
||||||
|
assert_migrate_settings(
|
||||||
|
&r#"
|
||||||
|
{
|
||||||
|
"project_panel": {
|
||||||
|
"open_file_on_paste": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
Some(
|
||||||
|
&r#"
|
||||||
|
{
|
||||||
|
"project_panel": {
|
||||||
|
"auto_open": { "on_paste": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_migrate_settings(
|
||||||
|
&r#"
|
||||||
|
{
|
||||||
|
"project_panel": {
|
||||||
|
"open_file_on_paste": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
Some(
|
||||||
|
&r#"
|
||||||
|
{
|
||||||
|
"project_panel": {
|
||||||
|
"auto_open": { "on_paste": false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#
|
||||||
|
.unindent(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,4 +53,5 @@ editor = { workspace = true, features = ["test-support"] }
|
|||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
|
tempfile.workspace = true
|
||||||
workspace = { workspace = true, features = ["test-support"] }
|
workspace = { workspace = true, features = ["test-support"] }
|
||||||
|
|||||||
@@ -1655,7 +1655,10 @@ impl ProjectPanel {
|
|||||||
}
|
}
|
||||||
project_panel.update_visible_entries(None, false, false, window, cx);
|
project_panel.update_visible_entries(None, false, false, window, cx);
|
||||||
if is_new_entry && !is_dir {
|
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();
|
cx.notify();
|
||||||
})?;
|
})?;
|
||||||
@@ -2709,15 +2712,16 @@ impl ProjectPanel {
|
|||||||
|
|
||||||
if item_count == 1 {
|
if item_count == 1 {
|
||||||
// open entry if not dir, setting is enabled, and only focus if rename is not pending
|
// open entry if not dir, setting is enabled, and only focus if rename is not pending
|
||||||
if !entry.is_dir()
|
if !entry.is_dir() {
|
||||||
&& ProjectPanelSettings::get_global(cx).open_file_on_paste
|
let settings = ProjectPanelSettings::get_global(cx);
|
||||||
{
|
if settings.auto_open.should_open_on_paste() {
|
||||||
project_panel.open_entry(
|
project_panel.open_entry(
|
||||||
entry.id,
|
entry.id,
|
||||||
disambiguation_range.is_none(),
|
disambiguation_range.is_none(),
|
||||||
false,
|
false,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if only one entry was pasted and it was disambiguated, open the rename editor
|
// 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")?;
|
let opened_entries = task.await.with_context(|| "failed to copy external paths")?;
|
||||||
this.update(cx, |this, cx| {
|
this.update(cx, |this, cx| {
|
||||||
if open_file_after_drop && !opened_entries.is_empty() {
|
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_root: bool,
|
||||||
pub hide_hidden: bool,
|
pub hide_hidden: bool,
|
||||||
pub drag_and_drop: 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)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
@@ -48,6 +48,30 @@ pub struct ScrollbarSettings {
|
|||||||
pub show: Option<ShowScrollbar>,
|
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 {
|
impl ScrollbarVisibility for ProjectPanelSettings {
|
||||||
fn visibility(&self, cx: &ui::App) -> ShowScrollbar {
|
fn visibility(&self, cx: &ui::App) -> ShowScrollbar {
|
||||||
self.scrollbar
|
self.scrollbar
|
||||||
@@ -83,7 +107,14 @@ impl Settings for ProjectPanelSettings {
|
|||||||
hide_root: project_panel.hide_root.unwrap(),
|
hide_root: project_panel.hide_root.unwrap(),
|
||||||
hide_hidden: project_panel.hide_hidden.unwrap(),
|
hide_hidden: project_panel.hide_hidden.unwrap(),
|
||||||
drag_and_drop: project_panel.drag_and_drop.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 pretty_assertions::assert_eq;
|
||||||
use project::FakeFs;
|
use project::FakeFs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use settings::SettingsStore;
|
use settings::{ProjectPanelAutoOpenSettings, SettingsStore};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use util::{path, paths::PathStyle, rel_path::rel_path};
|
use util::{path, paths::PathStyle, rel_path::rel_path};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
@@ -1998,6 +1998,248 @@ async fn test_remove_opened_file(cx: &mut gpui::TestAppContext) {
|
|||||||
ensure_no_open_items_and_panes(&workspace, cx);
|
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]
|
#[gpui::test]
|
||||||
async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
|
async fn test_create_duplicate_items(cx: &mut gpui::TestAppContext) {
|
||||||
init_test_with_editor(cx);
|
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(
|
fn ensure_single_file_is_opened(
|
||||||
window: &WindowHandle<Workspace>,
|
window: &WindowHandle<Workspace>,
|
||||||
expected_path: &str,
|
expected_path: &str,
|
||||||
|
|||||||
@@ -510,6 +510,23 @@ impl OnLastWindowClosed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[skip_serializing_none]
|
||||||
|
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
|
||||||
|
pub struct ProjectPanelAutoOpenSettings {
|
||||||
|
/// Whether to automatically open newly created files in the editor.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub on_create: Option<bool>,
|
||||||
|
/// Whether to automatically open files after pasting or duplicating them.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub on_paste: Option<bool>,
|
||||||
|
/// Whether to automatically open files dropped from external sources.
|
||||||
|
///
|
||||||
|
/// Default: true
|
||||||
|
pub on_drop: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[skip_serializing_none]
|
#[skip_serializing_none]
|
||||||
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
|
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
|
||||||
pub struct ProjectPanelSettingsContent {
|
pub struct ProjectPanelSettingsContent {
|
||||||
@@ -590,10 +607,8 @@ pub struct ProjectPanelSettingsContent {
|
|||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub drag_and_drop: Option<bool>,
|
pub drag_and_drop: Option<bool>,
|
||||||
/// Whether to automatically open files when pasting them in the project panel.
|
/// Settings for automatically opening files.
|
||||||
///
|
pub auto_open: Option<ProjectPanelAutoOpenSettings>,
|
||||||
/// Default: true
|
|
||||||
pub open_file_on_paste: Option<bool>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
|||||||
@@ -664,13 +664,13 @@ impl VsCodeSettings {
|
|||||||
hide_root: None,
|
hide_root: None,
|
||||||
indent_guides: None,
|
indent_guides: None,
|
||||||
indent_size: None,
|
indent_size: None,
|
||||||
open_file_on_paste: None,
|
|
||||||
scrollbar: None,
|
scrollbar: None,
|
||||||
show_diagnostics: self
|
show_diagnostics: self
|
||||||
.read_bool("problems.decorations.enabled")
|
.read_bool("problems.decorations.enabled")
|
||||||
.and_then(|b| if b { Some(ShowDiagnostics::Off) } else { None }),
|
.and_then(|b| if b { Some(ShowDiagnostics::Off) } else { None }),
|
||||||
starts_open: None,
|
starts_open: None,
|
||||||
sticky_scroll: None,
|
sticky_scroll: None,
|
||||||
|
auto_open: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let (Some(false), Some(false)) = (
|
if let (Some(false), Some(false)) = (
|
||||||
|
|||||||
@@ -3776,23 +3776,47 @@ pub(crate) fn settings_data(cx: &App) -> Vec<SettingsPage> {
|
|||||||
metadata: None,
|
metadata: None,
|
||||||
files: USER,
|
files: USER,
|
||||||
}),
|
}),
|
||||||
|
SettingsPageItem::SectionHeader("Auto Open Files"),
|
||||||
SettingsPageItem::SettingItem(SettingItem {
|
SettingsPageItem::SettingItem(SettingItem {
|
||||||
title: "Open File on Paste",
|
title: "On Create",
|
||||||
description: "Whether to automatically open files when pasting them in the project panel.",
|
description: "Whether to automatically open newly created files in the editor.",
|
||||||
field: Box::new(SettingField {
|
field: Box::new(SettingField {
|
||||||
json_path: Some("project_panel.open_file_on_paste"),
|
json_path: Some("project_panel.auto_open.on_create"),
|
||||||
pick: |settings_content| {
|
pick: |settings_content| {
|
||||||
settings_content
|
settings_content.project_panel.as_ref()?.auto_open.as_ref()?.on_create.as_ref()
|
||||||
.project_panel
|
|
||||||
.as_ref()?
|
|
||||||
.open_file_on_paste
|
|
||||||
.as_ref()
|
|
||||||
},
|
},
|
||||||
write: |settings_content, value| {
|
write: |settings_content, value| {
|
||||||
settings_content
|
settings_content.project_panel.get_or_insert_default().auto_open.get_or_insert_default().on_create = value;
|
||||||
.project_panel
|
},
|
||||||
.get_or_insert_default()
|
}),
|
||||||
.open_file_on_paste = value;
|
metadata: None,
|
||||||
|
files: USER,
|
||||||
|
}),
|
||||||
|
SettingsPageItem::SettingItem(SettingItem {
|
||||||
|
title: "On Paste",
|
||||||
|
description: "Whether to automatically open files after pasting or duplicating them.",
|
||||||
|
field: Box::new(SettingField {
|
||||||
|
json_path: Some("project_panel.auto_open.on_paste"),
|
||||||
|
pick: |settings_content| {
|
||||||
|
settings_content.project_panel.as_ref()?.auto_open.as_ref()?.on_paste.as_ref()
|
||||||
|
},
|
||||||
|
write: |settings_content, value| {
|
||||||
|
settings_content.project_panel.get_or_insert_default().auto_open.get_or_insert_default().on_paste = value;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
metadata: None,
|
||||||
|
files: USER,
|
||||||
|
}),
|
||||||
|
SettingsPageItem::SettingItem(SettingItem {
|
||||||
|
title: "On Drop",
|
||||||
|
description: "Whether to automatically open files dropped from external sources.",
|
||||||
|
field: Box::new(SettingField {
|
||||||
|
json_path: Some("project_panel.auto_open.on_drop"),
|
||||||
|
pick: |settings_content| {
|
||||||
|
settings_content.project_panel.as_ref()?.auto_open.as_ref()?.on_drop.as_ref()
|
||||||
|
},
|
||||||
|
write: |settings_content, value| {
|
||||||
|
settings_content.project_panel.get_or_insert_default().auto_open.get_or_insert_default().on_drop = value;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
metadata: None,
|
metadata: None,
|
||||||
|
|||||||
@@ -4280,7 +4280,11 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
|
|||||||
"hide_root": false,
|
"hide_root": false,
|
||||||
"hide_hidden": false,
|
"hide_hidden": false,
|
||||||
"starts_open": true,
|
"starts_open": true,
|
||||||
"open_file_on_paste": true
|
"auto_open": {
|
||||||
|
"on_create": true,
|
||||||
|
"on_paste": true,
|
||||||
|
"on_drop": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -4489,6 +4493,26 @@ Run the {#action theme_selector::Toggle} action in the command palette to see a
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Auto Open
|
||||||
|
|
||||||
|
- Description: Control whether files are opened automatically after different creation flows in the project panel.
|
||||||
|
- Setting: `auto_open`
|
||||||
|
- Default:
|
||||||
|
|
||||||
|
```json [settings]
|
||||||
|
"auto_open": {
|
||||||
|
"on_create": true,
|
||||||
|
"on_paste": true,
|
||||||
|
"on_drop": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options**
|
||||||
|
|
||||||
|
- `on_create`: Whether to automatically open newly created files in the editor.
|
||||||
|
- `on_paste`: Whether to automatically open files after pasting or duplicating them.
|
||||||
|
- `on_drop`: Whether to automatically open files dropped from external sources.
|
||||||
|
|
||||||
## Agent
|
## Agent
|
||||||
|
|
||||||
Visit [the Configuration page](./ai/configuration.md) under the AI section to learn more about all the agent-related settings.
|
Visit [the Configuration page](./ai/configuration.md) under the AI section to learn more about all the agent-related settings.
|
||||||
|
|||||||
Reference in New Issue
Block a user