From 0a4ff2f47536c872ebd1ac3e672538a6251832e8 Mon Sep 17 00:00:00 2001 From: Morgan Metz <68765538+Morgandri1@users.noreply.github.com> Date: Sun, 2 Mar 2025 19:01:48 -0800 Subject: [PATCH] tab: Add setting to hide the close button entirely (#23880) Closes #23744 Release Notes: - Changed the `always_show_close_button` key to `show_close_button` and introduced a new `hidden` value, that allows never displaying the close button. --------- Co-authored-by: Peter Tripp Co-authored-by: Danilo Leal Co-authored-by: smit <0xtimsb@gmail.com> --- assets/settings/default.json | 12 ++++- crates/migrator/src/migrator.rs | 85 +++++++++++++++++++++++++++++++-- crates/workspace/src/item.rs | 13 ++++- crates/workspace/src/pane.rs | 31 ++++++------ docs/src/configuring-zed.md | 36 ++++++++++++-- 5 files changed, 149 insertions(+), 28 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 0bb82d9312..8e489dd0b5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -648,11 +648,19 @@ // Show git status colors in the editor tabs. "git_status": false, // Position of the close button on the editor tabs. + // One of: ["right", "left", "hidden"] "close_position": "right", // Whether to show the file icon for a tab. "file_icons": false, - // Whether to always show the close button on tabs. - "always_show_close_button": false, + // Controls the appearance behavior of the tab's close button. + // + // 1. Show it just upon hovering the tab. (default) + // "hover" + // 2. Show it persistently. + // "always" + // 3. Never show it, even if hovering it. + // "hidden" + "show_close_button": "hover", // What to do after closing the current tab. // // 1. Activate the tab that was open previously (default) diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index 7ab45a1c77..d3716cf01b 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -72,7 +72,7 @@ pub fn migrate_edit_prediction_provider_settings(text: &str) -> Result> = const SETTINGS_MIGRATION_PATTERNS: MigrationPatterns = &[ (SETTINGS_STRING_REPLACE_QUERY, replace_setting_name), ( - SETTINGS_REPLACE_NESTED_KEY, + SETTINGS_NESTED_KEY_VALUE_PATTERN, replace_edit_prediction_provider_setting, ), + ( + SETTINGS_NESTED_KEY_VALUE_PATTERN, + replace_tab_close_button_setting_key, + ), + ( + SETTINGS_NESTED_KEY_VALUE_PATTERN, + replace_tab_close_button_setting_value, + ), ( SETTINGS_REPLACE_IN_LANGUAGES_QUERY, replace_setting_in_languages, @@ -594,7 +602,7 @@ static SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new(|| { static EDIT_PREDICTION_SETTINGS_MIGRATION_QUERY: LazyLock = LazyLock::new(|| { Query::new( &tree_sitter_json::LANGUAGE.into(), - SETTINGS_REPLACE_NESTED_KEY, + SETTINGS_NESTED_KEY_VALUE_PATTERN, ) .unwrap() }); @@ -639,14 +647,14 @@ pub static SETTINGS_STRING_REPLACE: LazyLock ]) }); -const SETTINGS_REPLACE_NESTED_KEY: &str = r#" +const SETTINGS_NESTED_KEY_VALUE_PATTERN: &str = r#" (object (pair key: (string (string_content) @parent_key) value: (object (pair key: (string (string_content) @setting_name) - value: (_) @value + value: (_) @setting_value ) ) ) @@ -679,6 +687,73 @@ fn replace_edit_prediction_provider_setting( None } +fn replace_tab_close_button_setting_key( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; + let parent_object_range = mat + .nodes_for_capture_index(parent_object_capture_ix) + .next()? + .byte_range(); + let parent_object_name = contents.get(parent_object_range.clone())?; + + let setting_name_ix = query.capture_index_for_name("setting_name")?; + let setting_range = mat + .nodes_for_capture_index(setting_name_ix) + .next()? + .byte_range(); + let setting_name = contents.get(setting_range.clone())?; + + if parent_object_name == "tabs" && setting_name == "always_show_close_button" { + return Some((setting_range, "show_close_button".into())); + } + + None +} + +fn replace_tab_close_button_setting_value( + contents: &str, + mat: &QueryMatch, + query: &Query, +) -> Option<(Range, String)> { + let parent_object_capture_ix = query.capture_index_for_name("parent_key")?; + let parent_object_range = mat + .nodes_for_capture_index(parent_object_capture_ix) + .next()? + .byte_range(); + let parent_object_name = contents.get(parent_object_range.clone())?; + + 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(); + let setting_name = contents.get(setting_name_range.clone())?; + + let setting_value_ix = query.capture_index_for_name("setting_value")?; + let setting_value_range = mat + .nodes_for_capture_index(setting_value_ix) + .next()? + .byte_range(); + let setting_value = contents.get(setting_value_range.clone())?; + + if parent_object_name == "tabs" && setting_name == "always_show_close_button" { + match setting_value { + "true" => { + return Some((setting_value_range, "\"always\"".to_string())); + } + "false" => { + return Some((setting_value_range, "\"hover\"".to_string())); + } + _ => {} + } + } + + None +} + const SETTINGS_REPLACE_IN_LANGUAGES_QUERY: &str = r#" (object (pair diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 592f867c4a..de2f2382e1 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -42,7 +42,7 @@ pub struct ItemSettings { pub activate_on_close: ActivateOnClose, pub file_icons: bool, pub show_diagnostics: ShowDiagnostics, - pub always_show_close_button: bool, + pub show_close_button: ShowCloseButton, } #[derive(Deserialize)] @@ -60,6 +60,15 @@ pub enum ClosePosition { Right, } +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "lowercase")] +pub enum ShowCloseButton { + Always, + #[default] + Hover, + Hidden, +} + #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowDiagnostics { @@ -104,7 +113,7 @@ pub struct ItemSettingsContent { /// Whether to always show the close button on tabs. /// /// Default: false - always_show_close_button: Option, + show_close_button: Option, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 1c707b748e..a9ecd0d258 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,7 +1,7 @@ use crate::{ item::{ ActivateOnClose, ClosePosition, Item, ItemHandle, ItemSettings, PreviewTabsSettings, - ShowDiagnostics, TabContentParams, TabTooltipContent, WeakItemHandle, + ShowCloseButton, ShowDiagnostics, TabContentParams, TabTooltipContent, WeakItemHandle, }, move_item, notifications::NotifyResultExt, @@ -2269,7 +2269,7 @@ impl Pane { let settings = ItemSettings::get_global(cx); let close_side = &settings.close_position; - let always_show_close_button = settings.always_show_close_button; + let show_close_button = &settings.show_close_button; let indicator = render_item_indicator(item.boxed_clone(), cx); let item_id = item.item_id(); let is_first_item = ix == 0; @@ -2373,18 +2373,21 @@ impl Pane { close_pinned: false, }; end_slot_tooltip_text = "Close Tab"; - IconButton::new("close tab", IconName::Close) - .when(!always_show_close_button, |button| { - button.visible_on_hover("") - }) - .shape(IconButtonShape::Square) - .icon_color(Color::Muted) - .size(ButtonSize::None) - .icon_size(IconSize::XSmall) - .on_click(cx.listener(move |pane, _, window, cx| { - pane.close_item_by_id(item_id, SaveIntent::Close, window, cx) - .detach_and_log_err(cx); - })) + match show_close_button { + ShowCloseButton::Always => IconButton::new("close tab", IconName::Close), + ShowCloseButton::Hover => { + IconButton::new("close tab", IconName::Close).visible_on_hover("") + } + ShowCloseButton::Hidden => return this, + } + .shape(IconButtonShape::Square) + .icon_color(Color::Muted) + .size(ButtonSize::None) + .icon_size(IconSize::XSmall) + .on_click(cx.listener(move |pane, _, window, cx| { + pane.close_item_by_id(item_id, SaveIntent::Close, window, cx) + .detach_and_log_err(cx); + })) } .map(|this| { if is_active { diff --git a/docs/src/configuring-zed.md b/docs/src/configuring-zed.md index d1b9ca2bb2..ff8d6d4ac6 100644 --- a/docs/src/configuring-zed.md +++ b/docs/src/configuring-zed.md @@ -784,7 +784,7 @@ List of `string` values "file_icons": false, "git_status": false, "activate_on_close": "history", - "always_show_close_button": false + "show_close_button": "hover" }, ``` @@ -856,11 +856,37 @@ List of `string` values } ``` -### Always show the close button +### Show close button -- Description: Whether to always show the close button on tabs. -- Setting: `always_show_close_button` -- Default: `false` +- Description: Controls the appearance behavior of the tab's close button. +- Setting: `show_close_button` +- Default: `hover` + +**Options** + +1. Show it just upon hovering the tab: + +```json +{ + "show_close_button": "hover" +} +``` + +2. Show it persistently: + +```json +{ + "show_close_button": "always" +} +``` + +3. Never show it, even if hovering it: + +```json +{ + "show_close_button": "hidden" +} +``` ## Editor Toolbar