diff --git a/assets/settings/default.json b/assets/settings/default.json index f53019744e72daa253e3ddfa96f48a0541186b61..f687778d7bd7fc0f6d66404199c34fac8d77e7a8 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1100,13 +1100,22 @@ "preview_tabs": { // Whether preview tabs should be enabled. // Preview tabs allow you to open files in preview mode, where they close automatically - // when you switch to another file unless you explicitly pin them. + // when you open another preview tab. // This is useful for quickly viewing files without cluttering your workspace. "enabled": true, + // Whether to open tabs in preview mode when opened from the project panel with a single click. + "enable_preview_from_project_panel": true, // Whether to open tabs in preview mode when selected from the file finder. "enable_preview_from_file_finder": false, - // Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. - "enable_preview_from_code_navigation": false + // Whether to open tabs in preview mode when opened from a multibuffer. + "enable_preview_from_multibuffer": true, + // Whether to open tabs in preview mode when code navigation is used to open a multibuffer. + "enable_preview_multibuffer_from_code_navigation": false, + // Whether to open tabs in preview mode when code navigation is used to open a single file. + "enable_preview_file_from_code_navigation": true, + // Whether to keep tabs in preview mode when code navigation is used to navigate away from them. + // If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one. + "enable_keep_preview_on_code_navigation": false }, // Settings related to the file finder. "file_finder": { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index f6489c8ffece51d581e3fb73d3f683ff1283c433..f2d6e168fc9ed47cd3c490f3449bc856f90e79fd 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -17012,7 +17012,9 @@ impl Editor { }) .collect(); - let workspace = self.workspace(); + let Some(workspace) = self.workspace() else { + return Task::ready(Ok(Navigated::No)); + }; cx.spawn_in(window, async move |editor, cx| { let locations: Vec = future::join_all(definitions) @@ -17038,10 +17040,6 @@ impl Editor { } if num_locations > 1 { - let Some(workspace) = workspace else { - return Ok(Navigated::No); - }; - let tab_kind = match kind { Some(GotoDefinitionKind::Implementation) => "Implementations", Some(GotoDefinitionKind::Symbol) | None => "Definitions", @@ -17073,11 +17071,14 @@ impl Editor { let opened = workspace .update_in(cx, |workspace, window, cx| { + let allow_preview = PreviewTabsSettings::get_global(cx) + .enable_preview_multibuffer_from_code_navigation; Self::open_locations_in_multibuffer( workspace, locations, title, split, + allow_preview, MultibufferSelectionMode::First, window, cx, @@ -17094,10 +17095,9 @@ impl Editor { Ok(Navigated::Yes) } Some(Either::Right(path)) => { - let Some(workspace) = workspace else { - return Ok(Navigated::No); - }; - + // TODO(andrew): respect preview tab settings + // `enable_keep_preview_on_code_navigation` and + // `enable_preview_file_from_code_navigation` workspace .update_in(cx, |workspace, window, cx| { workspace.open_resolved_path(path, window, cx) @@ -17108,10 +17108,6 @@ impl Editor { None => Ok(Navigated::No), } } else { - let Some(workspace) = workspace else { - return Ok(Navigated::No); - }; - let (target_buffer, target_ranges) = locations.into_iter().next().unwrap(); let target_range = target_ranges.first().unwrap().clone(); @@ -17135,11 +17131,19 @@ impl Editor { workspace.active_pane().clone() }; + let preview_tabs_settings = PreviewTabsSettings::get_global(cx); + let keep_old_preview = preview_tabs_settings + .enable_keep_preview_on_code_navigation; + let allow_new_preview = preview_tabs_settings + .enable_preview_file_from_code_navigation; + workspace.open_project_item( pane, target_buffer.clone(), true, true, + keep_old_preview, + allow_new_preview, window, cx, ) @@ -17416,11 +17420,14 @@ impl Editor { } else { format!("References to {target}") }; + let allow_preview = PreviewTabsSettings::get_global(cx) + .enable_preview_multibuffer_from_code_navigation; Self::open_locations_in_multibuffer( workspace, locations, title, false, + allow_preview, MultibufferSelectionMode::First, window, cx, @@ -17436,6 +17443,7 @@ impl Editor { locations: std::collections::HashMap, Vec>>, title: String, split: bool, + allow_preview: bool, multibuffer_selection_mode: MultibufferSelectionMode, window: &mut Window, cx: &mut Context, @@ -17483,6 +17491,7 @@ impl Editor { .is_some_and(|it| *it == key) }) }); + let was_existing = existing.is_some(); let editor = existing.unwrap_or_else(|| { cx.new(|cx| { let mut editor = Editor::for_multibuffer( @@ -17523,29 +17532,23 @@ impl Editor { }); let item = Box::new(editor); - let item_id = item.item_id(); - - if split { - let pane = workspace.adjacent_pane(window, cx); - workspace.add_item(pane, item, None, true, true, window, cx); - } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation { - let (preview_item_id, preview_item_idx) = - workspace.active_pane().read_with(cx, |pane, _| { - (pane.preview_item_id(), pane.preview_item_idx()) - }); - workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx); + let pane = if split { + workspace.adjacent_pane(window, cx) + } else { + workspace.active_pane().clone() + }; + let activate_pane = split; - if let Some(preview_item_id) = preview_item_id { - workspace.active_pane().update(cx, |pane, cx| { - pane.remove_item(preview_item_id, false, false, window, cx); - }); + let mut destination_index = None; + pane.update(cx, |pane, cx| { + if allow_preview && !was_existing { + destination_index = pane.replace_preview_item_id(item.item_id(), window, cx); } - } else { - workspace.add_item_to_active_pane(item, None, true, window, cx); - } - workspace.active_pane().update(cx, |pane, cx| { - pane.set_preview_item_id(Some(item_id), cx); + if was_existing && !allow_preview { + pane.unpreview_item_if_preview(item.item_id()); + } + pane.add_item(item, activate_pane, true, destination_index, window, cx); }); } @@ -20783,6 +20786,7 @@ impl Editor { locations, format!("Selections for '{title}'"), false, + false, MultibufferSelectionMode::All, window, cx, @@ -22002,29 +22006,40 @@ impl Editor { // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id, // so `workspace.open_project_item` will never find them, always opening a new editor. // Instead, we try to activate the existing editor in the pane first. - let (editor, pane_item_index) = + let (editor, pane_item_index, pane_item_id) = pane.read(cx).items().enumerate().find_map(|(i, item)| { let editor = item.downcast::()?; let singleton_buffer = editor.read(cx).buffer().read(cx).as_singleton()?; if singleton_buffer == buffer { - Some((editor, i)) + Some((editor, i, item.item_id())) } else { None } })?; pane.update(cx, |pane, cx| { - pane.activate_item(pane_item_index, true, true, window, cx) + pane.activate_item(pane_item_index, true, true, window, cx); + if !PreviewTabsSettings::get_global(cx) + .enable_preview_from_multibuffer + { + pane.unpreview_item_if_preview(pane_item_id); + } }); Some(editor) }) .flatten() .unwrap_or_else(|| { + let keep_old_preview = PreviewTabsSettings::get_global(cx) + .enable_keep_preview_on_code_navigation; + let allow_new_preview = + PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer; workspace.open_project_item::( pane.clone(), buffer, true, true, + keep_old_preview, + allow_new_preview, window, cx, ) diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 07b7d3f0afb141d4dde77b883ca97f4df67cdd6c..398d5aaf9405d34e8d8a4e93d5c9b9045ee49118 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -153,3 +153,9 @@ pub(crate) mod m_2025_11_25 { pub(crate) use settings::remove_context_server_source; } + +pub(crate) mod m_2025_12_01 { + mod settings; + + pub(crate) use settings::SETTINGS_PATTERNS; +} diff --git a/crates/migrator/src/migrations/m_2025_12_01/settings.rs b/crates/migrator/src/migrations/m_2025_12_01/settings.rs new file mode 100644 index 0000000000000000000000000000000000000000..2c3816dab3446b483f197575e9f602986eee7e47 --- /dev/null +++ b/crates/migrator/src/migrations/m_2025_12_01/settings.rs @@ -0,0 +1,55 @@ +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_enable_preview_from_code_navigation_setting, +)]; + +fn rename_enable_preview_from_code_navigation_setting( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + if !is_enable_preview_from_code_navigation(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, + "enable_keep_preview_on_code_navigation".to_string(), + )) +} + +fn is_enable_preview_from_code_navigation(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("preview_tabs") { + 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("enable_preview_from_code_navigation") +} diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index 444ebadfb615628e91422ed62c351722d8cb9300..9fb6d8a1151719f350ea7877bfe2492d6b443c23 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -219,6 +219,10 @@ pub fn migrate_settings(text: &str) -> Result> { migrations::m_2025_11_12::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_11_12, ), + MigrationType::TreeSitter( + migrations::m_2025_12_01::SETTINGS_PATTERNS, + &SETTINGS_QUERY_2025_12_01, + ), MigrationType::TreeSitter( migrations::m_2025_11_20::SETTINGS_PATTERNS, &SETTINGS_QUERY_2025_11_20, @@ -346,6 +350,10 @@ define_query!( SETTINGS_QUERY_2025_11_12, migrations::m_2025_11_12::SETTINGS_PATTERNS ); +define_query!( + SETTINGS_QUERY_2025_12_01, + migrations::m_2025_12_01::SETTINGS_PATTERNS +); define_query!( SETTINGS_QUERY_2025_11_20, migrations::m_2025_11_20::SETTINGS_PATTERNS @@ -2262,6 +2270,54 @@ mod tests { ); } + #[test] + fn test_remove_context_server_source() { + assert_migrate_settings( + &r#" + { + "context_servers": { + "extension_server": { + "source": "extension", + "settings": { + "foo": "bar" + } + }, + "custom_server": { + "source": "custom", + "command": "foo", + "args": ["bar"], + "env": { + "FOO": "BAR" + } + }, + } + } + "# + .unindent(), + Some( + &r#" + { + "context_servers": { + "extension_server": { + "settings": { + "foo": "bar" + } + }, + "custom_server": { + "command": "foo", + "args": ["bar"], + "env": { + "FOO": "BAR" + } + }, + } + } + "# + .unindent(), + ), + ); + } + #[test] fn test_project_panel_open_file_on_paste_migration() { assert_migrate_settings( @@ -2308,25 +2364,14 @@ mod tests { } #[test] - fn test_remove_context_server_source() { + fn test_enable_preview_from_code_navigation_migration() { assert_migrate_settings( &r#" { - "context_servers": { - "extension_server": { - "source": "extension", - "settings": { - "foo": "bar" - } - }, - "custom_server": { - "source": "custom", - "command": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, + "other_setting_1": 1, + "preview_tabs": { + "other_setting_2": 2, + "enable_preview_from_code_navigation": false } } "# @@ -2334,19 +2379,35 @@ mod tests { Some( &r#" { - "context_servers": { - "extension_server": { - "settings": { - "foo": "bar" - } - }, - "custom_server": { - "command": "foo", - "args": ["bar"], - "env": { - "FOO": "BAR" - } - }, + "other_setting_1": 1, + "preview_tabs": { + "other_setting_2": 2, + "enable_keep_preview_on_code_navigation": false + } + } + "# + .unindent(), + ), + ); + + assert_migrate_settings( + &r#" + { + "other_setting_1": 1, + "preview_tabs": { + "other_setting_2": 2, + "enable_preview_from_code_navigation": true + } + } + "# + .unindent(), + Some( + &r#" + { + "other_setting_1": 1, + "preview_tabs": { + "other_setting_2": 2, + "enable_keep_preview_on_code_navigation": true } } "# diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index d191b9f3fea5a7183bbcc89b751a71b00c1a31b7..e53be8cd33fa265dfadb201b2bcd613c54ffb9dd 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1529,7 +1529,8 @@ impl ProjectPanel { } fn open(&mut self, _: &Open, window: &mut Window, cx: &mut Context) { - let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enabled; + let preview_tabs_enabled = + PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel; self.open_internal(true, !preview_tabs_enabled, None, window, cx); } @@ -4819,7 +4820,7 @@ impl ProjectPanel { project_panel.toggle_expanded(entry_id, window, cx); } } else { - let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enabled; + let preview_tabs_enabled = PreviewTabsSettings::get_global(cx).enable_preview_from_project_panel; let click_count = event.click_count(); let focus_opened_item = click_count > 1; let allow_preview = preview_tabs_enabled && click_count == 1; diff --git a/crates/project_symbols/src/project_symbols.rs b/crates/project_symbols/src/project_symbols.rs index 61ed715ffd639c532257319d2165d530ae5c0513..d96de4b876030deb5a6083b1474a167f8cba81ad 100644 --- a/crates/project_symbols/src/project_symbols.rs +++ b/crates/project_symbols/src/project_symbols.rs @@ -133,8 +133,9 @@ impl PickerDelegate for ProjectSymbolsDelegate { workspace.active_pane().clone() }; - let editor = - workspace.open_project_item::(pane, buffer, true, true, window, cx); + let editor = workspace.open_project_item::( + pane, buffer, true, true, true, true, window, cx, + ); editor.update(cx, |editor, cx| { editor.change_selections( diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs index 088d478e464bd0f4e9a92419440c16576005fc95..b809a8fa85a9b27da3f3af5242e99b280466a4bb 100644 --- a/crates/settings/src/settings_content/workspace.rs +++ b/crates/settings/src/settings_content/workspace.rs @@ -152,14 +152,31 @@ pub struct PreviewTabsSettingsContent { /// /// Default: true pub enabled: Option, + /// Whether to open tabs in preview mode when opened from the project panel with a single click. + /// + /// Default: true + pub enable_preview_from_project_panel: Option, /// Whether to open tabs in preview mode when selected from the file finder. /// /// Default: false pub enable_preview_from_file_finder: Option, - /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab. + /// Whether to open tabs in preview mode when opened from a multibuffer. + /// + /// Default: true + pub enable_preview_from_multibuffer: Option, + /// Whether to open tabs in preview mode when code navigation is used to open a multibuffer. + /// + /// Default: false + pub enable_preview_multibuffer_from_code_navigation: Option, + /// Whether to open tabs in preview mode when code navigation is used to open a single file. + /// + /// Default: true + pub enable_preview_file_from_code_navigation: Option, + /// Whether to keep tabs in preview mode when code navigation is used to navigate away from them. + /// If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one. /// /// Default: false - pub enable_preview_from_code_navigation: Option, + pub enable_keep_preview_on_code_navigation: Option, } #[derive( diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 0a4e249d60c6888d9a950dcc5be4600d0047ce00..587850303f13649fcc4adf8cf4ddbb8dc7181dcb 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -619,9 +619,13 @@ impl VsCodeSettings { fn preview_tabs_settings_content(&self) -> Option { skip_default(PreviewTabsSettingsContent { enabled: self.read_bool("workbench.editor.enablePreview"), + enable_preview_from_project_panel: None, enable_preview_from_file_finder: self .read_bool("workbench.editor.enablePreviewFromQuickOpen"), - enable_preview_from_code_navigation: self + enable_preview_from_multibuffer: None, + enable_preview_multibuffer_from_code_navigation: None, + enable_preview_file_from_code_navigation: None, + enable_keep_preview_on_code_navigation: self .read_bool("workbench.editor.enablePreviewFromCodeNavigation"), }) } diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index 1525271a39776f4b8b456244f40e3dfbc43cbaac..0c383970c990c3ba19eab7aa5d3b7c699f8a195e 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -3145,7 +3145,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec { SettingsPageItem::SectionHeader("Preview Tabs"), SettingsPageItem::SettingItem(SettingItem { title: "Preview Tabs Enabled", - description: "Show opened editors as Preview tabs.", + description: "Show opened editors as preview tabs.", field: Box::new(SettingField { json_path: Some("preview_tabs.enabled"), pick: |settings_content| { @@ -3161,9 +3161,31 @@ pub(crate) fn settings_data(cx: &App) -> Vec { metadata: None, files: USER, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Enable Preview From Project Panel", + description: "Whether to open tabs in preview mode when opened from the project panel with a single click.", + field: Box::new(SettingField { + json_path: Some("preview_tabs.enable_preview_from_project_panel"), + pick: |settings_content| { + settings_content + .preview_tabs + .as_ref()? + .enable_preview_from_project_panel + .as_ref() + }, + write: |settings_content, value| { + settings_content + .preview_tabs + .get_or_insert_default() + .enable_preview_from_project_panel = value; + }, + }), + metadata: None, + files: USER, + }), SettingsPageItem::SettingItem(SettingItem { title: "Enable Preview From File Finder", - description: "Whether to open tabs in Preview mode when selected from the file finder.", + description: "Whether to open tabs in preview mode when selected from the file finder.", field: Box::new(SettingField { json_path: Some("preview_tabs.enable_preview_from_file_finder"), pick: |settings_content| { @@ -3184,22 +3206,88 @@ pub(crate) fn settings_data(cx: &App) -> Vec { files: USER, }), SettingsPageItem::SettingItem(SettingItem { - title: "Enable Preview From Code Navigation", - description: "Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.", + title: "Enable Preview From Multibuffer", + description: "Whether to open tabs in preview mode when opened from a multibuffer.", + field: Box::new(SettingField { + json_path: Some("preview_tabs.enable_preview_from_multibuffer"), + pick: |settings_content| { + settings_content + .preview_tabs + .as_ref()? + .enable_preview_from_multibuffer + .as_ref() + }, + write: |settings_content, value| { + settings_content + .preview_tabs + .get_or_insert_default() + .enable_preview_from_multibuffer = value; + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Enable Preview Multibuffer From Code Navigation", + description: "Whether to open tabs in preview mode when code navigation is used to open a multibuffer.", + field: Box::new(SettingField { + json_path: Some("preview_tabs.enable_preview_multibuffer_from_code_navigation"), + pick: |settings_content| { + settings_content + .preview_tabs + .as_ref()? + .enable_preview_multibuffer_from_code_navigation + .as_ref() + }, + write: |settings_content, value| { + settings_content + .preview_tabs + .get_or_insert_default() + .enable_preview_multibuffer_from_code_navigation = value; + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Enable Preview File From Code Navigation", + description: "Whether to open tabs in preview mode when code navigation is used to open a single file.", + field: Box::new(SettingField { + json_path: Some("preview_tabs.enable_preview_file_from_code_navigation"), + pick: |settings_content| { + settings_content + .preview_tabs + .as_ref()? + .enable_preview_file_from_code_navigation + .as_ref() + }, + write: |settings_content, value| { + settings_content + .preview_tabs + .get_or_insert_default() + .enable_preview_file_from_code_navigation = value; + }, + }), + metadata: None, + files: USER, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Enable Keep Preview On Code Navigation", + description: "Whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one.", field: Box::new(SettingField { - json_path: Some("preview_tabs.enable_preview_from_code_navigation"), + json_path: Some("preview_tabs.enable_keep_preview_on_code_navigation"), pick: |settings_content| { settings_content .preview_tabs .as_ref()? - .enable_preview_from_code_navigation + .enable_keep_preview_on_code_navigation .as_ref() }, write: |settings_content, value| { settings_content .preview_tabs .get_or_insert_default() - .enable_preview_from_code_navigation = value; + .enable_keep_preview_on_code_navigation = value; }, }), metadata: None, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 8f459557270e7b4595e26e15f2aad3c33aea4cd8..42eb754c21347e7dced792f3e56cb9901bc70bd1 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -64,8 +64,12 @@ pub struct ItemSettings { #[derive(RegisterSetting)] pub struct PreviewTabsSettings { pub enabled: bool, + pub enable_preview_from_project_panel: bool, pub enable_preview_from_file_finder: bool, - pub enable_preview_from_code_navigation: bool, + pub enable_preview_from_multibuffer: bool, + pub enable_preview_multibuffer_from_code_navigation: bool, + pub enable_preview_file_from_code_navigation: bool, + pub enable_keep_preview_on_code_navigation: bool, } impl Settings for ItemSettings { @@ -87,9 +91,19 @@ impl Settings for PreviewTabsSettings { let preview_tabs = content.preview_tabs.as_ref().unwrap(); Self { enabled: preview_tabs.enabled.unwrap(), + enable_preview_from_project_panel: preview_tabs + .enable_preview_from_project_panel + .unwrap(), enable_preview_from_file_finder: preview_tabs.enable_preview_from_file_finder.unwrap(), - enable_preview_from_code_navigation: preview_tabs - .enable_preview_from_code_navigation + enable_preview_from_multibuffer: preview_tabs.enable_preview_from_multibuffer.unwrap(), + enable_preview_multibuffer_from_code_navigation: preview_tabs + .enable_preview_multibuffer_from_code_navigation + .unwrap(), + enable_preview_file_from_code_navigation: preview_tabs + .enable_preview_file_from_code_navigation + .unwrap(), + enable_keep_preview_on_code_navigation: preview_tabs + .enable_keep_preview_on_code_navigation .unwrap(), } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 5f0fb8ba9647f969b3bea4a83194dd600e1f84aa..e99f8d1dc959def06deebae7c4acc454c9210933 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -873,10 +873,35 @@ impl Pane { self.preview_item_id == Some(item_id) } + /// Promotes the item with the given ID to not be a preview item. + /// This does nothing if it wasn't already a preview item. + pub fn unpreview_item_if_preview(&mut self, item_id: EntityId) { + if self.is_active_preview_item(item_id) { + self.preview_item_id = None; + } + } + + /// Marks the item with the given ID as the preview item. + /// This will be ignored if the global setting `preview_tabs` is disabled. + /// + /// The old preview item (if there was one) is closed and its index is returned. + pub fn replace_preview_item_id( + &mut self, + item_id: EntityId, + window: &mut Window, + cx: &mut Context, + ) -> Option { + let idx = self.close_current_preview_item(window, cx); + self.set_preview_item_id(Some(item_id), cx); + idx + } + /// Marks the item with the given ID as the preview item. /// This will be ignored if the global setting `preview_tabs` is disabled. - pub fn set_preview_item_id(&mut self, item_id: Option, cx: &App) { - if PreviewTabsSettings::get_global(cx).enabled { + /// + /// This is a low-level method. Prefer `unpreview_item_if_preview()` or `set_new_preview_item()`. + pub(crate) fn set_preview_item_id(&mut self, item_id: Option, cx: &App) { + if item_id.is_none() || PreviewTabsSettings::get_global(cx).enabled { self.preview_item_id = item_id; } } @@ -895,7 +920,7 @@ impl Pane { && preview_item.item_id() == item_id && !preview_item.preserve_preview(cx) { - self.set_preview_item_id(None, cx); + self.unpreview_item_if_preview(item_id); } } @@ -936,14 +961,8 @@ impl Pane { let set_up_existing_item = |index: usize, pane: &mut Self, window: &mut Window, cx: &mut Context| { - // If the item is already open, and the item is a preview item - // and we are not allowing items to open as preview, mark the item as persistent. - if let Some(preview_item_id) = pane.preview_item_id - && let Some(tab) = pane.items.get(index) - && tab.item_id() == preview_item_id - && !allow_preview - { - pane.set_preview_item_id(None, cx); + if !allow_preview && let Some(item) = pane.items.get(index) { + pane.unpreview_item_if_preview(item.item_id()); } if activate { pane.activate_item(index, focus_item, focus_item, window, cx); @@ -955,7 +974,7 @@ impl Pane { window: &mut Window, cx: &mut Context| { if allow_preview { - pane.set_preview_item_id(Some(new_item.item_id()), cx); + pane.replace_preview_item_id(new_item.item_id(), window, cx); } if let Some(text) = new_item.telemetry_event_text(cx) { @@ -1036,6 +1055,7 @@ impl Pane { ) -> Option { let item_idx = self.preview_item_idx()?; let id = self.preview_item_id()?; + self.set_preview_item_id(None, cx); let prev_active_item_index = self.active_item_index; self.remove_item(id, false, false, window, cx); @@ -1981,9 +2001,7 @@ impl Pane { item.on_removed(cx); self.nav_history.set_mode(mode); - if self.is_active_preview_item(item.item_id()) { - self.set_preview_item_id(None, cx); - } + self.unpreview_item_if_preview(item.item_id()); if let Some(path) = item.project_path(cx) { let abs_path = self @@ -2194,9 +2212,7 @@ impl Pane { if can_save { pane.update_in(cx, |pane, window, cx| { - if pane.is_active_preview_item(item.item_id()) { - pane.set_preview_item_id(None, cx); - } + pane.unpreview_item_if_preview(item.item_id()); item.save( SaveOptions { format: should_format, @@ -2450,8 +2466,8 @@ impl Pane { let id = self.item_for_index(ix)?.item_id(); let should_activate = ix == self.active_item_index; - if matches!(operation, PinOperation::Pin) && self.is_active_preview_item(id) { - self.set_preview_item_id(None, cx); + if matches!(operation, PinOperation::Pin) { + self.unpreview_item_if_preview(id); } match operation { @@ -2624,12 +2640,9 @@ impl Pane { ) .on_mouse_down( MouseButton::Left, - cx.listener(move |pane, event: &MouseDownEvent, _, cx| { - if let Some(id) = pane.preview_item_id - && id == item_id - && event.click_count > 1 - { - pane.set_preview_item_id(None, cx); + cx.listener(move |pane, event: &MouseDownEvent, _, _| { + if event.click_count > 1 { + pane.unpreview_item_if_preview(item_id); } }), ) @@ -3272,11 +3285,7 @@ impl Pane { let mut to_pane = cx.entity(); let split_direction = self.drag_split_direction; let item_id = dragged_tab.item.item_id(); - if let Some(preview_item_id) = self.preview_item_id - && item_id == preview_item_id - { - self.set_preview_item_id(None, cx); - } + self.unpreview_item_if_preview(item_id); let is_clone = cfg!(target_os = "macos") && window.modifiers().alt || cfg!(not(target_os = "macos")) && window.modifiers().control; @@ -3788,15 +3797,17 @@ impl Render for Pane { .on_action(cx.listener(Self::toggle_pin_tab)) .on_action(cx.listener(Self::unpin_all_tabs)) .when(PreviewTabsSettings::get_global(cx).enabled, |this| { - this.on_action(cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, _, cx| { - if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) { - if pane.is_active_preview_item(active_item_id) { - pane.set_preview_item_id(None, cx); - } else { - pane.set_preview_item_id(Some(active_item_id), cx); + this.on_action( + cx.listener(|pane: &mut Pane, _: &TogglePreviewTab, window, cx| { + if let Some(active_item_id) = pane.active_item().map(|i| i.item_id()) { + if pane.is_active_preview_item(active_item_id) { + pane.unpreview_item_if_preview(active_item_id); + } else { + pane.replace_preview_item_id(active_item_id, window, cx); + } } - } - })) + }), + ) }) .on_action( cx.listener(|pane: &mut Self, action: &CloseActiveItem, window, cx| { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index d5a1c3a291c8e337695b30c1e6e1f3b3b76a3a62..b1ad520493b4869d646a76df4a0e576646253117 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -3636,14 +3636,33 @@ impl Workspace { project_item: Entity, activate_pane: bool, focus_item: bool, + keep_old_preview: bool, + allow_new_preview: bool, window: &mut Window, cx: &mut Context, ) -> Entity where T: ProjectItem, { + let old_item_id = pane.read(cx).active_item().map(|item| item.item_id()); + if let Some(item) = self.find_project_item(&pane, &project_item, cx) { + if !keep_old_preview + && let Some(old_id) = old_item_id + && old_id != item.item_id() + { + // switching to a different item, so unpreview old active item + pane.update(cx, |pane, _| { + pane.unpreview_item_if_preview(old_id); + }); + } + self.activate_item(&item, activate_pane, focus_item, window, cx); + if !allow_new_preview { + pane.update(cx, |pane, _| { + pane.unpreview_item_if_preview(item.item_id()); + }); + } return item; } @@ -3652,16 +3671,14 @@ impl Workspace { T::for_project_item(self.project().clone(), Some(pane), project_item, window, cx) }) }); - let item_id = item.item_id(); let mut destination_index = None; pane.update(cx, |pane, cx| { - if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation - && let Some(preview_item_id) = pane.preview_item_id() - && preview_item_id != item_id - { - destination_index = pane.close_current_preview_item(window, cx); + if !keep_old_preview && let Some(old_id) = old_item_id { + pane.unpreview_item_if_preview(old_id); + } + if allow_new_preview { + destination_index = pane.replace_preview_item_id(item.item_id(), window, cx); } - pane.set_preview_item_id(Some(item.item_id()), cx) }); self.add_item( diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index 3b90120407fe56643e4b3f279d88443b9740e154..477885a4537580aaf562aa596c1a06cae1c65bc8 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -2861,11 +2861,25 @@ Configuration object for defining settings profiles. Example: ```json [settings] "preview_tabs": { "enabled": true, + "enable_preview_from_project_panel": true, "enable_preview_from_file_finder": false, - "enable_preview_from_code_navigation": false, + "enable_preview_from_multibuffer": true, + "enable_preview_multibuffer_from_code_navigation": false, + "enable_preview_file_from_code_navigation": true, + "enable_keep_preview_on_code_navigation": false, } ``` +### Enable preview from project panel + +- Description: Determines whether to open files in preview mode when opened from the project panel with a single click. +- Setting: `enable_preview_from_project_panel` +- Default: `true` + +**Options** + +`boolean` values + ### Enable preview from file finder - Description: Determines whether to open files in preview mode when selected from the file finder. @@ -2876,10 +2890,40 @@ Configuration object for defining settings profiles. Example: `boolean` values -### Enable preview from code navigation +### Enable preview from multibuffer + +- Description: Determines whether to open files in preview mode when opened from a multibuffer. +- Setting: `enable_preview_from_multibuffer` +- Default: `true` + +**Options** + +`boolean` values + +### Enable preview multibuffer from code navigation + +- Description: Determines whether to open tabs in preview mode when code navigation is used to open a multibuffer. +- Setting: `enable_preview_multibuffer_from_code_navigation` +- Default: `false` + +**Options** + +`boolean` values + +### Enable preview file from code navigation + +- Description: Determines whether to open tabs in preview mode when code navigation is used to open a single file. +- Setting: `enable_preview_file_from_code_navigation` +- Default: `true` + +**Options** + +`boolean` values + +### Enable keep preview on code navigation -- Description: Determines whether a preview tab gets replaced when code navigation is used to navigate away from the tab. -- Setting: `enable_preview_from_code_navigation` +- Description: Determines whether to keep tabs in preview mode when code navigation is used to navigate away from them. If `enable_preview_file_from_code_navigation` or `enable_preview_multibuffer_from_code_navigation` is also true, the new tab may replace the existing one. +- Setting: `enable_keep_preview_on_code_navigation` - Default: `false` **Options**