Settings refactor (#38367)

Co-Authored-By: Ben K <ben@zed.dev>
Co-Authored-By: Anthony <anthony@zed.dev>
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- settings: Major internal changes to settings. The primary user-facing
effect is that some settings which did not make sense in project
settings files are no-longer read from there. (For example the inline
blame settings)

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Conrad Irwin
2025-09-18 16:47:23 +00:00
committed by GitHub
co-authored by Ben Kunkle Mikayla Maki Anthony
parent 0a9023bce0
commit fcdab160f9
219 changed files with 11697 additions and 11894 deletions
+21 -4
View File
@@ -9,8 +9,6 @@ use gpui::{
Render, SharedString, StyleRefinement, Styled, Subscription, WeakEntity, Window, deferred, div,
px,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::SettingsStore;
use std::sync::Arc;
use ui::{ContextMenu, Divider, DividerColor, IconButton, Tooltip, h_flex};
@@ -210,14 +208,33 @@ impl Focusable for Dock {
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
#[serde(rename_all = "lowercase")]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DockPosition {
Left,
Bottom,
Right,
}
impl From<settings::DockPosition> for DockPosition {
fn from(value: settings::DockPosition) -> Self {
match value {
settings::DockPosition::Left => Self::Left,
settings::DockPosition::Bottom => Self::Bottom,
settings::DockPosition::Right => Self::Right,
}
}
}
impl Into<settings::DockPosition> for DockPosition {
fn into(self) -> settings::DockPosition {
match self {
Self::Left => settings::DockPosition::Left,
Self::Bottom => settings::DockPosition::Bottom,
Self::Right => settings::DockPosition::Right,
}
}
}
impl DockPosition {
fn label(&self) -> &'static str {
match self {
+89 -121
View File
@@ -15,9 +15,9 @@ use gpui::{
Focusable, Font, HighlightStyle, Pixels, Point, Render, SharedString, Task, WeakEntity, Window,
};
use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsUi};
pub use settings::{
ActivateOnClose, ClosePosition, Settings, SettingsLocation, ShowCloseButton, ShowDiagnostics,
};
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -30,7 +30,7 @@ use std::{
};
use theme::Theme;
use ui::{Color, Icon, IntoElement, Label, LabelCommon};
use util::ResultExt;
use util::{MergeFrom as _, ResultExt};
pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
@@ -49,7 +49,6 @@ impl Default for SaveOptions {
}
}
#[derive(Deserialize)]
pub struct ItemSettings {
pub git_status: bool,
pub close_position: ClosePosition,
@@ -59,150 +58,119 @@ pub struct ItemSettings {
pub show_close_button: ShowCloseButton,
}
#[derive(Deserialize)]
pub struct PreviewTabsSettings {
pub enabled: bool,
pub enable_preview_from_file_finder: bool,
pub enable_preview_from_code_navigation: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "lowercase")]
pub enum ClosePosition {
Left,
#[default]
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 {
#[default]
Off,
Errors,
All,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ActivateOnClose {
#[default]
History,
Neighbour,
LeftNeighbour,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "tabs")]
pub struct ItemSettingsContent {
/// Whether to show the Git file status on a tab item.
///
/// Default: false
git_status: Option<bool>,
/// Position of the close button in a tab.
///
/// Default: right
close_position: Option<ClosePosition>,
/// Whether to show the file icon for a tab.
///
/// Default: false
file_icons: Option<bool>,
/// What to do after closing the current tab.
///
/// Default: history
pub activate_on_close: Option<ActivateOnClose>,
/// Which files containing diagnostic errors/warnings to mark in the tabs.
/// This setting can take the following three values:
///
/// Default: off
show_diagnostics: Option<ShowDiagnostics>,
/// Whether to always show the close button on tabs.
///
/// Default: false
show_close_button: Option<ShowCloseButton>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "preview_tabs")]
pub struct PreviewTabsSettingsContent {
/// Whether to show opened editors as preview tabs.
/// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
///
/// Default: true
enabled: Option<bool>,
/// Whether to open tabs in preview mode when selected from the file finder.
///
/// Default: false
enable_preview_from_file_finder: Option<bool>,
/// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
///
/// Default: false
enable_preview_from_code_navigation: Option<bool>,
}
impl Settings for ItemSettings {
type FileContent = ItemSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let tabs = content.tabs.as_ref().unwrap();
Self {
git_status: tabs.git_status.unwrap(),
close_position: tabs.close_position.unwrap(),
activate_on_close: tabs.activate_on_close.unwrap(),
file_icons: tabs.file_icons.unwrap(),
show_diagnostics: tabs.show_diagnostics.unwrap(),
show_close_button: tabs.show_close_button.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
let Some(tabs) = content.tabs.as_ref() else {
return;
};
self.git_status.merge_from(&tabs.git_status);
self.close_position.merge_from(&tabs.close_position);
self.activate_on_close.merge_from(&tabs.activate_on_close);
self.file_icons.merge_from(&tabs.file_icons);
self.show_diagnostics.merge_from(&tabs.show_diagnostics);
self.show_close_button.merge_from(&tabs.show_close_button);
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(b) = vscode.read_bool("workbench.editor.tabActionCloseVisibility") {
current.show_close_button = Some(if b {
current.tabs.get_or_insert_default().show_close_button = Some(if b {
ShowCloseButton::Always
} else {
ShowCloseButton::Hidden
})
}
vscode.enum_setting(
"workbench.editor.tabActionLocation",
&mut current.close_position,
|s| match s {
"right" => Some(ClosePosition::Right),
"left" => Some(ClosePosition::Left),
_ => None,
},
);
if let Some(s) = vscode.read_enum("workbench.editor.tabActionLocation", |s| match s {
"right" => Some(ClosePosition::Right),
"left" => Some(ClosePosition::Left),
_ => None,
}) {
current.tabs.get_or_insert_default().close_position = Some(s)
}
if let Some(b) = vscode.read_bool("workbench.editor.focusRecentEditorAfterClose") {
current.activate_on_close = Some(if b {
current.tabs.get_or_insert_default().activate_on_close = Some(if b {
ActivateOnClose::History
} else {
ActivateOnClose::LeftNeighbour
})
}
vscode.bool_setting("workbench.editor.showIcons", &mut current.file_icons);
vscode.bool_setting("git.decorations.enabled", &mut current.git_status);
if let Some(b) = vscode.read_bool("workbench.editor.showIcons") {
current.tabs.get_or_insert_default().file_icons = Some(b);
};
if let Some(b) = vscode.read_bool("git.decorations.enabled") {
current.tabs.get_or_insert_default().git_status = Some(b);
}
}
}
impl Settings for PreviewTabsSettings {
type FileContent = PreviewTabsSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let preview_tabs = content.preview_tabs.as_ref().unwrap();
Self {
enabled: preview_tabs.enabled.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
.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
vscode.bool_setting("workbench.editor.enablePreview", &mut current.enabled);
vscode.bool_setting(
"workbench.editor.enablePreviewFromCodeNavigation",
&mut current.enable_preview_from_code_navigation,
);
vscode.bool_setting(
"workbench.editor.enablePreviewFromQuickOpen",
&mut current.enable_preview_from_file_finder,
);
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
let Some(preview_tabs) = content.preview_tabs.as_ref() else {
return;
};
self.enabled.merge_from(&preview_tabs.enabled);
self.enable_preview_from_file_finder
.merge_from(&preview_tabs.enable_preview_from_file_finder);
self.enable_preview_from_code_navigation
.merge_from(&preview_tabs.enable_preview_from_code_navigation);
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(enabled) = vscode.read_bool("workbench.editor.enablePreview") {
current.preview_tabs.get_or_insert_default().enabled = Some(enabled);
}
if let Some(enable_preview_from_code_navigation) =
vscode.read_bool("workbench.editor.enablePreviewFromCodeNavigation")
{
current
.preview_tabs
.get_or_insert_default()
.enable_preview_from_code_navigation = Some(enable_preview_from_code_navigation)
}
if let Some(enable_preview_from_file_finder) =
vscode.read_bool("workbench.editor.enablePreviewFromQuickOpen")
{
current
.preview_tabs
.get_or_insert_default()
.enable_preview_from_file_finder = Some(enable_preview_from_file_finder)
}
}
}
+7 -6
View File
@@ -5816,8 +5816,8 @@ mod tests {
async fn test_remove_item_ordering_neighbour(cx: &mut TestAppContext) {
init_test(cx);
cx.update_global::<SettingsStore, ()>(|s, cx| {
s.update_user_settings::<ItemSettings>(cx, |s| {
s.activate_on_close = Some(ActivateOnClose::Neighbour);
s.update_user_settings(cx, |s| {
s.tabs.get_or_insert_default().activate_on_close = Some(ActivateOnClose::Neighbour);
});
});
let fs = FakeFs::new(cx.executor());
@@ -5905,8 +5905,9 @@ mod tests {
async fn test_remove_item_ordering_left_neighbour(cx: &mut TestAppContext) {
init_test(cx);
cx.update_global::<SettingsStore, ()>(|s, cx| {
s.update_user_settings::<ItemSettings>(cx, |s| {
s.activate_on_close = Some(ActivateOnClose::LeftNeighbour);
s.update_user_settings(cx, |s| {
s.tabs.get_or_insert_default().activate_on_close =
Some(ActivateOnClose::LeftNeighbour);
});
});
let fs = FakeFs::new(cx.executor());
@@ -6558,8 +6559,8 @@ mod tests {
fn set_max_tabs(cx: &mut TestAppContext, value: Option<usize>) {
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.max_tabs = value.map(|v| NonZero::new(v).unwrap())
store.update_user_settings(cx, |settings| {
settings.workspace.max_tabs = value.map(|v| NonZero::new(v).unwrap())
});
});
}
+22 -25
View File
@@ -52,10 +52,7 @@ pub use item::{
ProjectItem, SerializableItem, SerializableItemHandle, WeakItemHandle,
};
use itertools::Itertools;
use language::{
Buffer, LanguageRegistry, Rope,
language_settings::{AllLanguageSettings, all_language_settings},
};
use language::{Buffer, LanguageRegistry, Rope, language_settings::all_language_settings};
pub use modal_layer::*;
use node_runtime::NodeRuntime;
use notifications::{
@@ -1695,8 +1692,8 @@ impl Workspace {
cx: &mut Context<Self>,
) {
let fs = self.project().read(cx).fs();
settings::update_settings_file::<WorkspaceSettings>(fs.clone(), cx, move |content, _cx| {
content.bottom_dock_layout = Some(layout);
settings::update_settings_file(fs.clone(), cx, move |content, _cx| {
content.workspace.bottom_dock_layout = Some(layout);
});
cx.notify();
@@ -6014,8 +6011,8 @@ impl Workspace {
) {
let fs = self.project().read(cx).fs().clone();
let show_edit_predictions = all_language_settings(None, cx).show_edit_predictions(None, cx);
update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
file.defaults.show_edit_predictions = Some(!show_edit_predictions)
update_settings_file(fs, cx, move |file, _| {
file.project.all_languages.defaults.show_edit_predictions = Some(!show_edit_predictions)
});
}
}
@@ -8678,8 +8675,8 @@ mod tests {
// Autosave on window change.
item.update(cx, |item, cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.autosave = Some(AutosaveSetting::OnWindowChange);
settings.update_user_settings(cx, |settings| {
settings.workspace.autosave = Some(AutosaveSetting::OnWindowChange);
})
});
item.is_dirty = true;
@@ -8698,13 +8695,12 @@ mod tests {
item.update_in(cx, |item, window, cx| {
cx.focus_self(window);
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.autosave = Some(AutosaveSetting::OnFocusChange);
settings.update_user_settings(cx, |settings| {
settings.workspace.autosave = Some(AutosaveSetting::OnFocusChange);
})
});
item.is_dirty = true;
});
// Blurring the item saves the file.
item.update_in(cx, |_, window, _| window.blur());
cx.executor().run_until_parked();
@@ -8721,8 +8717,9 @@ mod tests {
// Autosave after delay.
item.update(cx, |item, cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
settings.update_user_settings(cx, |settings| {
settings.workspace.autosave =
Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
})
});
item.is_dirty = true;
@@ -8770,8 +8767,8 @@ mod tests {
// Autosave on focus change, ensuring closing the tab counts as such.
item.update(cx, |item, cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.autosave = Some(AutosaveSetting::OnFocusChange);
settings.update_user_settings(cx, |settings| {
settings.workspace.autosave = Some(AutosaveSetting::OnFocusChange);
})
});
item.is_dirty = true;
@@ -9774,8 +9771,8 @@ mod tests {
// Enable the close_on_disk_deletion setting
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.close_on_file_delete = Some(true);
store.update_user_settings(cx, |settings| {
settings.workspace.close_on_file_delete = Some(true);
});
});
@@ -9842,8 +9839,8 @@ mod tests {
// Ensure close_on_disk_deletion is disabled (default)
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.close_on_file_delete = Some(false);
store.update_user_settings(cx, |settings| {
settings.workspace.close_on_file_delete = Some(false);
});
});
@@ -9919,8 +9916,8 @@ mod tests {
// Enable the close_on_file_delete setting
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.close_on_file_delete = Some(true);
store.update_user_settings(cx, |settings| {
settings.workspace.close_on_file_delete = Some(true);
});
});
@@ -9992,8 +9989,8 @@ mod tests {
// Enable the close_on_file_delete setting
cx.update_global(|store: &mut SettingsStore, cx| {
store.update_user_settings::<WorkspaceSettings>(cx, |settings| {
settings.close_on_file_delete = Some(true);
store.update_user_settings(cx, |settings| {
settings.workspace.close_on_file_delete = Some(true);
});
});
+177 -289
View File
@@ -1,65 +1,49 @@
use std::num::NonZeroUsize;
use crate::DockPosition;
use anyhow::Result;
use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use serde::Deserialize;
pub use settings::AutosaveSetting;
use settings::Settings;
pub use settings::{
BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical,
RestoreOnStartupBehavior,
};
use util::MergeFrom as _;
#[derive(Deserialize)]
pub struct WorkspaceSettings {
pub active_pane_modifiers: ActivePanelModifiers,
pub bottom_dock_layout: BottomDockLayout,
pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal,
pub pane_split_direction_vertical: PaneSplitDirectionVertical,
pub centered_layout: CenteredLayoutSettings,
pub bottom_dock_layout: settings::BottomDockLayout,
pub pane_split_direction_horizontal: settings::PaneSplitDirectionHorizontal,
pub pane_split_direction_vertical: settings::PaneSplitDirectionVertical,
pub centered_layout: settings::CenteredLayoutSettings,
pub confirm_quit: bool,
pub show_call_status_icon: bool,
pub autosave: AutosaveSetting,
pub restore_on_startup: RestoreOnStartupBehavior,
pub restore_on_startup: settings::RestoreOnStartupBehavior,
pub restore_on_file_reopen: bool,
pub drop_target_size: f32,
pub use_system_path_prompts: bool,
pub use_system_prompts: bool,
pub command_aliases: HashMap<String, String>,
pub max_tabs: Option<NonZeroUsize>,
pub when_closing_with_no_tabs: CloseWindowWhenNoItems,
pub on_last_window_closed: OnLastWindowClosed,
pub when_closing_with_no_tabs: settings::CloseWindowWhenNoItems,
pub on_last_window_closed: settings::OnLastWindowClosed,
pub resize_all_panels_in_dock: Vec<DockPosition>,
pub close_on_file_delete: bool,
pub use_system_window_tabs: bool,
pub zoomed_padding: bool,
}
#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum OnLastWindowClosed {
/// Match platform conventions by default, so don't quit on macOS, and quit on other platforms
#[default]
PlatformDefault,
/// Quit the application the last window is closed
QuitApp,
}
impl OnLastWindowClosed {
pub fn is_quit_app(&self) -> bool {
match self {
OnLastWindowClosed::PlatformDefault => false,
OnLastWindowClosed::QuitApp => true,
}
}
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
#[derive(Copy, Clone, PartialEq, Debug, Default)]
pub struct ActivePanelModifiers {
/// Size of the border surrounding the active pane.
/// When set to 0, the active pane doesn't have any border.
/// The border is drawn inset.
///
/// Default: `0.0`
// TODO: make this not an option, it is never None
pub border_size: Option<f32>,
/// Opacity of inactive panels.
/// When set to 1.0, the inactive panes have the same opacity as the active one.
@@ -67,156 +51,10 @@ pub struct ActivePanelModifiers {
/// Values are clamped to the [0.0, 1.0] range.
///
/// Default: `1.0`
// TODO: make this not an option, it is never None
pub inactive_opacity: Option<f32>,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum BottomDockLayout {
/// Contained between the left and right docks
#[default]
Contained,
/// Takes up the full width of the window
Full,
/// Extends under the left dock while snapping to the right dock
LeftAligned,
/// Extends under the right dock while snapping to the left dock
RightAligned,
}
#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CloseWindowWhenNoItems {
/// Match platform conventions by default, so "on" on macOS and "off" everywhere else
#[default]
PlatformDefault,
/// Close the window when there are no tabs
CloseWindow,
/// Leave the window open when there are no tabs
KeepWindowOpen,
}
impl CloseWindowWhenNoItems {
pub fn should_close(&self) -> bool {
match self {
CloseWindowWhenNoItems::PlatformDefault => cfg!(target_os = "macos"),
CloseWindowWhenNoItems::CloseWindow => true,
CloseWindowWhenNoItems::KeepWindowOpen => false,
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RestoreOnStartupBehavior {
/// Always start with an empty editor
None,
/// Restore the workspace that was closed last.
LastWorkspace,
/// Restore all workspaces that were open when quitting Zed.
#[default]
LastSession,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct WorkspaceSettingsContent {
/// Active pane styling settings.
pub active_pane_modifiers: Option<ActivePanelModifiers>,
/// Layout mode for the bottom dock
///
/// Default: contained
pub bottom_dock_layout: Option<BottomDockLayout>,
/// Direction to split horizontally.
///
/// Default: "up"
pub pane_split_direction_horizontal: Option<PaneSplitDirectionHorizontal>,
/// Direction to split vertically.
///
/// Default: "left"
pub pane_split_direction_vertical: Option<PaneSplitDirectionVertical>,
/// Centered layout related settings.
pub centered_layout: Option<CenteredLayoutSettings>,
/// Whether or not to prompt the user to confirm before closing the application.
///
/// Default: false
pub confirm_quit: Option<bool>,
/// Whether or not to show the call status icon in the status bar.
///
/// Default: true
pub show_call_status_icon: Option<bool>,
/// When to automatically save edited buffers.
///
/// Default: off
pub autosave: Option<AutosaveSetting>,
/// Controls previous session restoration in freshly launched Zed instance.
/// Values: none, last_workspace, last_session
/// Default: last_session
pub restore_on_startup: Option<RestoreOnStartupBehavior>,
/// Whether to attempt to restore previous file's state when opening it again.
/// The state is stored per pane.
/// When disabled, defaults are applied instead of the state restoration.
///
/// E.g. for editors, selections, folds and scroll positions are restored, if the same file is closed and, later, opened again in the same pane.
/// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default.
///
/// Default: true
pub restore_on_file_reopen: Option<bool>,
/// The size of the workspace split drop targets on the outer edges.
/// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
///
/// Default: `0.2` (20% of the smaller dimension of the workspace)
pub drop_target_size: Option<f32>,
/// Whether to close the window when using 'close active item' on a workspace with no tabs
///
/// Default: auto ("on" on macOS, "off" otherwise)
pub when_closing_with_no_tabs: Option<CloseWindowWhenNoItems>,
/// Whether to use the system provided dialogs for Open and Save As.
/// When set to false, Zed will use the built-in keyboard-first pickers.
///
/// Default: true
pub use_system_path_prompts: Option<bool>,
/// Whether to use the system provided prompts.
/// When set to false, Zed will use the built-in prompts.
/// Note that this setting has no effect on Linux, where Zed will always
/// use the built-in prompts.
///
/// Default: true
pub use_system_prompts: Option<bool>,
/// Aliases for the command palette. When you type a key in this map,
/// it will be assumed to equal the value.
///
/// Default: true
pub command_aliases: Option<HashMap<String, String>>,
/// Maximum open tabs in a pane. Will not close an unsaved
/// tab. Set to `None` for unlimited tabs.
///
/// Default: none
pub max_tabs: Option<NonZeroUsize>,
/// What to do when the last window is closed
///
/// Default: auto (nothing on macOS, "app quit" otherwise)
pub on_last_window_closed: Option<OnLastWindowClosed>,
/// Whether to resize all the panels in a dock when resizing the dock.
///
/// Default: ["left"]
pub resize_all_panels_in_dock: Option<Vec<DockPosition>>,
/// Whether to automatically close files that have been deleted on disk.
///
/// Default: false
pub close_on_file_delete: Option<bool>,
/// Whether to allow windows to tab together based on the users tabbing preference (macOS only).
///
/// Default: false
pub use_system_window_tabs: Option<bool>,
/// Whether to show padding for zoomed panels.
/// When enabled, zoomed bottom panels will have some top padding,
/// while zoomed left/right panels will have padding to the right/left (respectively).
///
/// Default: true
pub zoomed_padding: Option<bool>,
}
#[derive(Deserialize)]
pub struct TabBarSettings {
pub show: bool,
@@ -224,84 +62,118 @@ pub struct TabBarSettings {
pub show_tab_bar_buttons: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "tab_bar")]
pub struct TabBarSettingsContent {
/// Whether or not to show the tab bar in the editor.
///
/// Default: true
pub show: Option<bool>,
/// Whether or not to show the navigation history buttons in the tab bar.
///
/// Default: true
pub show_nav_history_buttons: Option<bool>,
/// Whether or not to show the tab bar buttons.
///
/// Default: true
pub show_tab_bar_buttons: Option<bool>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AutosaveSetting {
/// Disable autosave.
Off,
/// Save after inactivity period of `milliseconds`.
AfterDelay { milliseconds: u64 },
/// Autosave when focus changes.
OnFocusChange,
/// Autosave when the active window changes.
OnWindowChange,
}
impl AutosaveSetting {
pub fn should_save_on_close(&self) -> bool {
matches!(
&self,
AutosaveSetting::OnFocusChange
| AutosaveSetting::OnWindowChange
| AutosaveSetting::AfterDelay { .. }
)
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PaneSplitDirectionHorizontal {
Up,
Down,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum PaneSplitDirectionVertical {
Left,
Right,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[serde(rename_all = "snake_case")]
pub struct CenteredLayoutSettings {
/// The relative width of the left padding of the central pane from the
/// workspace when the centered layout is used.
///
/// Default: 0.2
pub left_padding: Option<f32>,
// The relative width of the right padding of the central pane from the
// workspace when the centered layout is used.
///
/// Default: 0.2
pub right_padding: Option<f32>,
}
impl Settings for WorkspaceSettings {
type FileContent = WorkspaceSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let workspace = &content.workspace;
Self {
active_pane_modifiers: ActivePanelModifiers {
border_size: Some(
workspace
.active_pane_modifiers
.unwrap()
.border_size
.unwrap(),
),
inactive_opacity: Some(
workspace
.active_pane_modifiers
.unwrap()
.inactive_opacity
.unwrap(),
),
},
bottom_dock_layout: workspace.bottom_dock_layout.unwrap(),
pane_split_direction_horizontal: workspace.pane_split_direction_horizontal.unwrap(),
pane_split_direction_vertical: workspace.pane_split_direction_vertical.unwrap(),
centered_layout: workspace.centered_layout.unwrap(),
confirm_quit: workspace.confirm_quit.unwrap(),
show_call_status_icon: workspace.show_call_status_icon.unwrap(),
autosave: workspace.autosave.unwrap(),
restore_on_startup: workspace.restore_on_startup.unwrap(),
restore_on_file_reopen: workspace.restore_on_file_reopen.unwrap(),
drop_target_size: workspace.drop_target_size.unwrap(),
use_system_path_prompts: workspace.use_system_path_prompts.unwrap(),
use_system_prompts: workspace.use_system_prompts.unwrap(),
command_aliases: workspace.command_aliases.clone(),
max_tabs: workspace.max_tabs,
when_closing_with_no_tabs: workspace.when_closing_with_no_tabs.unwrap(),
on_last_window_closed: workspace.on_last_window_closed.unwrap(),
resize_all_panels_in_dock: workspace
.resize_all_panels_in_dock
.clone()
.unwrap()
.into_iter()
.map(Into::into)
.collect(),
close_on_file_delete: workspace.close_on_file_delete.unwrap(),
use_system_window_tabs: workspace.use_system_window_tabs.unwrap(),
zoomed_padding: workspace.zoomed_padding.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
let workspace = &content.workspace;
if let Some(border_size) = workspace
.active_pane_modifiers
.and_then(|modifier| modifier.border_size)
{
self.active_pane_modifiers.border_size = Some(border_size);
}
if let Some(inactive_opacity) = workspace
.active_pane_modifiers
.and_then(|modifier| modifier.inactive_opacity)
{
self.active_pane_modifiers.inactive_opacity = Some(inactive_opacity);
}
self.bottom_dock_layout
.merge_from(&workspace.bottom_dock_layout);
self.pane_split_direction_horizontal
.merge_from(&workspace.pane_split_direction_horizontal);
self.pane_split_direction_vertical
.merge_from(&workspace.pane_split_direction_vertical);
self.centered_layout.merge_from(&workspace.centered_layout);
self.confirm_quit.merge_from(&workspace.confirm_quit);
self.show_call_status_icon
.merge_from(&workspace.show_call_status_icon);
self.autosave.merge_from(&workspace.autosave);
self.restore_on_startup
.merge_from(&workspace.restore_on_startup);
self.restore_on_file_reopen
.merge_from(&workspace.restore_on_file_reopen);
self.drop_target_size
.merge_from(&workspace.drop_target_size);
self.use_system_path_prompts
.merge_from(&workspace.use_system_path_prompts);
self.use_system_prompts
.merge_from(&workspace.use_system_prompts);
self.command_aliases
.extend(workspace.command_aliases.clone());
if let Some(max_tabs) = workspace.max_tabs {
self.max_tabs = Some(max_tabs);
}
self.when_closing_with_no_tabs
.merge_from(&workspace.when_closing_with_no_tabs);
self.on_last_window_closed
.merge_from(&workspace.on_last_window_closed);
self.resize_all_panels_in_dock.merge_from(
&workspace
.resize_all_panels_in_dock
.as_ref()
.map(|resize| resize.clone().into_iter().map(Into::into).collect()),
);
self.close_on_file_delete
.merge_from(&workspace.close_on_file_delete);
self.use_system_window_tabs
.merge_from(&workspace.use_system_window_tabs);
self.zoomed_padding.merge_from(&workspace.zoomed_padding);
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if vscode
.read_bool("accessibility.dimUnfocused.enabled")
.unwrap_or_default()
@@ -309,19 +181,16 @@ impl Settings for WorkspaceSettings {
.read_value("accessibility.dimUnfocused.opacity")
.and_then(|v| v.as_f64())
{
if let Some(settings) = current.active_pane_modifiers.as_mut() {
settings.inactive_opacity = Some(opacity as f32)
} else {
current.active_pane_modifiers = Some(ActivePanelModifiers {
inactive_opacity: Some(opacity as f32),
..Default::default()
})
}
current
.workspace
.active_pane_modifiers
.get_or_insert_default()
.inactive_opacity = Some(opacity as f32);
}
vscode.enum_setting(
"window.confirmBeforeClose",
&mut current.confirm_quit,
&mut current.workspace.confirm_quit,
|s| match s {
"always" | "keyboardOnly" => Some(true),
"never" => Some(false),
@@ -331,22 +200,22 @@ impl Settings for WorkspaceSettings {
vscode.bool_setting(
"workbench.editor.restoreViewState",
&mut current.restore_on_file_reopen,
&mut current.workspace.restore_on_file_reopen,
);
if let Some(b) = vscode.read_bool("window.closeWhenEmpty") {
current.when_closing_with_no_tabs = Some(if b {
CloseWindowWhenNoItems::CloseWindow
current.workspace.when_closing_with_no_tabs = Some(if b {
settings::CloseWindowWhenNoItems::CloseWindow
} else {
CloseWindowWhenNoItems::KeepWindowOpen
})
settings::CloseWindowWhenNoItems::KeepWindowOpen
});
}
if let Some(b) = vscode.read_bool("files.simpleDialog.enable") {
current.use_system_path_prompts = Some(!b);
current.workspace.use_system_path_prompts = Some(!b);
}
vscode.enum_setting("files.autoSave", &mut current.autosave, |s| match s {
if let Some(v) = vscode.read_enum("files.autoSave", |s| match s {
"off" => Some(AutosaveSetting::Off),
"afterDelay" => Some(AutosaveSetting::AfterDelay {
milliseconds: vscode
@@ -357,7 +226,9 @@ impl Settings for WorkspaceSettings {
"onFocusChange" => Some(AutosaveSetting::OnFocusChange),
"onWindowChange" => Some(AutosaveSetting::OnWindowChange),
_ => None,
});
}) {
current.workspace.autosave = Some(v);
}
// workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
// our semantics match if those are set to true, some N, and true respectively.
@@ -370,10 +241,12 @@ impl Settings for WorkspaceSettings {
.read_bool("workbench.editor.limit.enabled")
.unwrap_or_default()
{
current.max_tabs = Some(n)
current.workspace.max_tabs = Some(n)
}
vscode.bool_setting("window.nativeTabs", &mut current.use_system_window_tabs);
if let Some(b) = vscode.read_bool("window.nativeTabs") {
current.workspace.use_system_window_tabs = Some(b);
}
// some combination of "window.restoreWindows" and "workbench.startupEditor" might
// map to our "restore_on_startup"
@@ -384,24 +257,39 @@ impl Settings for WorkspaceSettings {
}
impl Settings for TabBarSettings {
type FileContent = TabBarSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let tab_bar = content.tab_bar.clone().unwrap();
TabBarSettings {
show: tab_bar.show.unwrap(),
show_nav_history_buttons: tab_bar.show_nav_history_buttons.unwrap(),
show_tab_bar_buttons: tab_bar.show_tab_bar_buttons.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
vscode.enum_setting(
"workbench.editor.showTabs",
&mut current.show,
|s| match s {
"multiple" => Some(true),
"single" | "none" => Some(false),
_ => None,
},
);
fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) {
let Some(tab_bar) = &content.tab_bar else {
return;
};
self.show.merge_from(&tab_bar.show);
self.show_nav_history_buttons
.merge_from(&tab_bar.show_nav_history_buttons);
self.show_tab_bar_buttons
.merge_from(&tab_bar.show_tab_bar_buttons);
}
fn import_from_vscode(
vscode: &settings::VsCodeSettings,
current: &mut settings::SettingsContent,
) {
if let Some(b) = vscode.read_enum("workbench.editor.showTabs", |s| match s {
"multiple" => Some(true),
"single" | "none" => Some(false),
_ => None,
}) {
current.tab_bar.get_or_insert_default().show = Some(b);
}
if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") {
current.show_tab_bar_buttons = Some(false)
current.tab_bar.get_or_insert_default().show_tab_bar_buttons = Some(false)
}
}
}