workspace_settings.rs

  1use std::num::NonZeroUsize;
  2
  3use crate::DockPosition;
  4use anyhow::Result;
  5use collections::HashMap;
  6use gpui::App;
  7use schemars::JsonSchema;
  8use serde::{Deserialize, Serialize};
  9use settings::{Settings, SettingsSources};
 10
 11#[derive(Deserialize)]
 12pub struct WorkspaceSettings {
 13    pub active_pane_modifiers: ActivePanelModifiers,
 14    pub bottom_dock_layout: BottomDockLayout,
 15    pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal,
 16    pub pane_split_direction_vertical: PaneSplitDirectionVertical,
 17    pub centered_layout: CenteredLayoutSettings,
 18    pub confirm_quit: bool,
 19    pub show_call_status_icon: bool,
 20    pub autosave: AutosaveSetting,
 21    pub restore_on_startup: RestoreOnStartupBehavior,
 22    pub restore_on_file_reopen: bool,
 23    pub drop_target_size: f32,
 24    pub use_system_path_prompts: bool,
 25    pub use_system_prompts: bool,
 26    pub command_aliases: HashMap<String, String>,
 27    pub max_tabs: Option<NonZeroUsize>,
 28    pub when_closing_with_no_tabs: CloseWindowWhenNoItems,
 29    pub on_last_window_closed: OnLastWindowClosed,
 30    pub resize_all_panels_in_dock: Vec<DockPosition>,
 31    pub close_on_file_delete: bool,
 32    pub zoomed_padding: bool,
 33}
 34
 35#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
 36#[serde(rename_all = "snake_case")]
 37pub enum OnLastWindowClosed {
 38    /// Match platform conventions by default, so don't quit on macOS, and quit on other platforms
 39    #[default]
 40    PlatformDefault,
 41    /// Quit the application the last window is closed
 42    QuitApp,
 43}
 44
 45impl OnLastWindowClosed {
 46    pub fn is_quit_app(&self) -> bool {
 47        match self {
 48            OnLastWindowClosed::PlatformDefault => false,
 49            OnLastWindowClosed::QuitApp => true,
 50        }
 51    }
 52}
 53
 54#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 55#[serde(rename_all = "snake_case")]
 56pub struct ActivePanelModifiers {
 57    /// Size of the border surrounding the active pane.
 58    /// When set to 0, the active pane doesn't have any border.
 59    /// The border is drawn inset.
 60    ///
 61    /// Default: `0.0`
 62    pub border_size: Option<f32>,
 63    /// Opacity of inactive panels.
 64    /// When set to 1.0, the inactive panes have the same opacity as the active one.
 65    /// If set to 0, the inactive panes content will not be visible at all.
 66    /// Values are clamped to the [0.0, 1.0] range.
 67    ///
 68    /// Default: `1.0`
 69    pub inactive_opacity: Option<f32>,
 70}
 71
 72#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
 73#[serde(rename_all = "snake_case")]
 74pub enum BottomDockLayout {
 75    /// Contained between the left and right docks
 76    #[default]
 77    Contained,
 78    /// Takes up the full width of the window
 79    Full,
 80    /// Extends under the left dock while snapping to the right dock
 81    LeftAligned,
 82    /// Extends under the right dock while snapping to the left dock
 83    RightAligned,
 84}
 85
 86#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
 87#[serde(rename_all = "snake_case")]
 88pub enum CloseWindowWhenNoItems {
 89    /// Match platform conventions by default, so "on" on macOS and "off" everywhere else
 90    #[default]
 91    PlatformDefault,
 92    /// Close the window when there are no tabs
 93    CloseWindow,
 94    /// Leave the window open when there are no tabs
 95    KeepWindowOpen,
 96}
 97
 98impl CloseWindowWhenNoItems {
 99    pub fn should_close(&self) -> bool {
100        match self {
101            CloseWindowWhenNoItems::PlatformDefault => cfg!(target_os = "macos"),
102            CloseWindowWhenNoItems::CloseWindow => true,
103            CloseWindowWhenNoItems::KeepWindowOpen => false,
104        }
105    }
106}
107
108#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
109#[serde(rename_all = "snake_case")]
110pub enum RestoreOnStartupBehavior {
111    /// Always start with an empty editor
112    None,
113    /// Restore the workspace that was closed last.
114    LastWorkspace,
115    /// Restore all workspaces that were open when quitting Zed.
116    #[default]
117    LastSession,
118}
119
120#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
121pub struct WorkspaceSettingsContent {
122    /// Active pane styling settings.
123    pub active_pane_modifiers: Option<ActivePanelModifiers>,
124    /// Layout mode for the bottom dock
125    ///
126    /// Default: contained
127    pub bottom_dock_layout: Option<BottomDockLayout>,
128    /// Direction to split horizontally.
129    ///
130    /// Default: "up"
131    pub pane_split_direction_horizontal: Option<PaneSplitDirectionHorizontal>,
132    /// Direction to split vertically.
133    ///
134    /// Default: "left"
135    pub pane_split_direction_vertical: Option<PaneSplitDirectionVertical>,
136    /// Centered layout related settings.
137    pub centered_layout: Option<CenteredLayoutSettings>,
138    /// Whether or not to prompt the user to confirm before closing the application.
139    ///
140    /// Default: false
141    pub confirm_quit: Option<bool>,
142    /// Whether or not to show the call status icon in the status bar.
143    ///
144    /// Default: true
145    pub show_call_status_icon: Option<bool>,
146    /// When to automatically save edited buffers.
147    ///
148    /// Default: off
149    pub autosave: Option<AutosaveSetting>,
150    /// Controls previous session restoration in freshly launched Zed instance.
151    /// Values: none, last_workspace, last_session
152    /// Default: last_session
153    pub restore_on_startup: Option<RestoreOnStartupBehavior>,
154    /// Whether to attempt to restore previous file's state when opening it again.
155    /// The state is stored per pane.
156    /// When disabled, defaults are applied instead of the state restoration.
157    ///
158    /// 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.
159    /// When disabled, a single selection in the very beginning of the file, zero scroll position and no folds state is used as a default.
160    ///
161    /// Default: true
162    pub restore_on_file_reopen: Option<bool>,
163    /// The size of the workspace split drop targets on the outer edges.
164    /// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
165    ///
166    /// Default: `0.2` (20% of the smaller dimension of the workspace)
167    pub drop_target_size: Option<f32>,
168    /// Whether to close the window when using 'close active item' on a workspace with no tabs
169    ///
170    /// Default: auto ("on" on macOS, "off" otherwise)
171    pub when_closing_with_no_tabs: Option<CloseWindowWhenNoItems>,
172    /// Whether to use the system provided dialogs for Open and Save As.
173    /// When set to false, Zed will use the built-in keyboard-first pickers.
174    ///
175    /// Default: true
176    pub use_system_path_prompts: Option<bool>,
177    /// Whether to use the system provided prompts.
178    /// When set to false, Zed will use the built-in prompts.
179    /// Note that this setting has no effect on Linux, where Zed will always
180    /// use the built-in prompts.
181    ///
182    /// Default: true
183    pub use_system_prompts: Option<bool>,
184    /// Aliases for the command palette. When you type a key in this map,
185    /// it will be assumed to equal the value.
186    ///
187    /// Default: true
188    pub command_aliases: Option<HashMap<String, String>>,
189    /// Maximum open tabs in a pane. Will not close an unsaved
190    /// tab. Set to `None` for unlimited tabs.
191    ///
192    /// Default: none
193    pub max_tabs: Option<NonZeroUsize>,
194    /// What to do when the last window is closed
195    ///
196    /// Default: auto (nothing on macOS, "app quit" otherwise)
197    pub on_last_window_closed: Option<OnLastWindowClosed>,
198    /// Whether to resize all the panels in a dock when resizing the dock.
199    ///
200    /// Default: ["left"]
201    pub resize_all_panels_in_dock: Option<Vec<DockPosition>>,
202    /// Whether to automatically close files that have been deleted on disk.
203    ///
204    /// Default: false
205    pub close_on_file_delete: Option<bool>,
206    /// Whether to show padding for zoomed panels.
207    /// When enabled, zoomed bottom panels will have some top padding,
208    /// while zoomed left/right panels will have padding to the right/left (respectively).
209    ///
210    /// Default: true
211    pub zoomed_padding: Option<bool>,
212}
213
214#[derive(Deserialize)]
215pub struct TabBarSettings {
216    pub show: bool,
217    pub show_nav_history_buttons: bool,
218    pub show_tab_bar_buttons: bool,
219}
220
221#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
222pub struct TabBarSettingsContent {
223    /// Whether or not to show the tab bar in the editor.
224    ///
225    /// Default: true
226    pub show: Option<bool>,
227    /// Whether or not to show the navigation history buttons in the tab bar.
228    ///
229    /// Default: true
230    pub show_nav_history_buttons: Option<bool>,
231    /// Whether or not to show the tab bar buttons.
232    ///
233    /// Default: true
234    pub show_tab_bar_buttons: Option<bool>,
235}
236
237#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
238#[serde(rename_all = "snake_case")]
239pub enum AutosaveSetting {
240    /// Disable autosave.
241    Off,
242    /// Save after inactivity period of `milliseconds`.
243    AfterDelay { milliseconds: u64 },
244    /// Autosave when focus changes.
245    OnFocusChange,
246    /// Autosave when the active window changes.
247    OnWindowChange,
248}
249
250#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
251#[serde(rename_all = "snake_case")]
252pub enum PaneSplitDirectionHorizontal {
253    Up,
254    Down,
255}
256
257#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
258#[serde(rename_all = "snake_case")]
259pub enum PaneSplitDirectionVertical {
260    Left,
261    Right,
262}
263
264#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
265#[serde(rename_all = "snake_case")]
266pub struct CenteredLayoutSettings {
267    /// The relative width of the left padding of the central pane from the
268    /// workspace when the centered layout is used.
269    ///
270    /// Default: 0.2
271    pub left_padding: Option<f32>,
272    // The relative width of the right padding of the central pane from the
273    // workspace when the centered layout is used.
274    ///
275    /// Default: 0.2
276    pub right_padding: Option<f32>,
277}
278
279impl Settings for WorkspaceSettings {
280    const KEY: Option<&'static str> = None;
281
282    type FileContent = WorkspaceSettingsContent;
283
284    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
285        sources.json_merge()
286    }
287
288    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
289        if vscode
290            .read_bool("accessibility.dimUnfocused.enabled")
291            .unwrap_or_default()
292            && let Some(opacity) = vscode
293                .read_value("accessibility.dimUnfocused.opacity")
294                .and_then(|v| v.as_f64())
295        {
296            if let Some(settings) = current.active_pane_modifiers.as_mut() {
297                settings.inactive_opacity = Some(opacity as f32)
298            } else {
299                current.active_pane_modifiers = Some(ActivePanelModifiers {
300                    inactive_opacity: Some(opacity as f32),
301                    ..Default::default()
302                })
303            }
304        }
305
306        vscode.enum_setting(
307            "window.confirmBeforeClose",
308            &mut current.confirm_quit,
309            |s| match s {
310                "always" | "keyboardOnly" => Some(true),
311                "never" => Some(false),
312                _ => None,
313            },
314        );
315
316        vscode.bool_setting(
317            "workbench.editor.restoreViewState",
318            &mut current.restore_on_file_reopen,
319        );
320
321        if let Some(b) = vscode.read_bool("window.closeWhenEmpty") {
322            current.when_closing_with_no_tabs = Some(if b {
323                CloseWindowWhenNoItems::CloseWindow
324            } else {
325                CloseWindowWhenNoItems::KeepWindowOpen
326            })
327        }
328
329        if let Some(b) = vscode.read_bool("files.simpleDialog.enable") {
330            current.use_system_path_prompts = Some(!b);
331        }
332
333        vscode.enum_setting("files.autoSave", &mut current.autosave, |s| match s {
334            "off" => Some(AutosaveSetting::Off),
335            "afterDelay" => Some(AutosaveSetting::AfterDelay {
336                milliseconds: vscode
337                    .read_value("files.autoSaveDelay")
338                    .and_then(|v| v.as_u64())
339                    .unwrap_or(1000),
340            }),
341            "onFocusChange" => Some(AutosaveSetting::OnFocusChange),
342            "onWindowChange" => Some(AutosaveSetting::OnWindowChange),
343            _ => None,
344        });
345
346        // workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
347        // our semantics match if those are set to true, some N, and true respectively.
348        // we'll ignore "perEditorGroup" for now since we only support a global max
349        if let Some(n) = vscode
350            .read_value("workbench.editor.limit.value")
351            .and_then(|v| v.as_u64())
352            .and_then(|n| NonZeroUsize::new(n as usize))
353            && vscode
354                .read_bool("workbench.editor.limit.enabled")
355                .unwrap_or_default()
356        {
357            current.max_tabs = Some(n)
358        }
359
360        // some combination of "window.restoreWindows" and "workbench.startupEditor" might
361        // map to our "restore_on_startup"
362
363        // there doesn't seem to be a way to read whether the bottom dock's "justified"
364        // setting is enabled in vscode. that'd be our equivalent to "bottom_dock_layout"
365    }
366}
367
368impl Settings for TabBarSettings {
369    const KEY: Option<&'static str> = Some("tab_bar");
370
371    type FileContent = TabBarSettingsContent;
372
373    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
374        sources.json_merge()
375    }
376
377    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
378        vscode.enum_setting(
379            "workbench.editor.showTabs",
380            &mut current.show,
381            |s| match s {
382                "multiple" => Some(true),
383                "single" | "none" => Some(false),
384                _ => None,
385            },
386        );
387        if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") {
388            current.show_tab_bar_buttons = Some(false)
389        }
390    }
391}