workspace_settings.rs

  1use std::num::NonZeroUsize;
  2
  3use crate::DockPosition;
  4use anyhow::Result;
  5use collections::HashMap;
  6use gpui::App;
  7use serde::Deserialize;
  8pub use settings::AutosaveSetting;
  9use settings::Settings;
 10pub use settings::{
 11    BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical,
 12    RestoreOnStartupBehavior,
 13};
 14use util::MergeFrom as _;
 15
 16pub struct WorkspaceSettings {
 17    pub active_pane_modifiers: ActivePanelModifiers,
 18    pub bottom_dock_layout: settings::BottomDockLayout,
 19    pub pane_split_direction_horizontal: settings::PaneSplitDirectionHorizontal,
 20    pub pane_split_direction_vertical: settings::PaneSplitDirectionVertical,
 21    pub centered_layout: settings::CenteredLayoutSettings, // <- This one is hard to describe, especially as it has
 22    pub confirm_quit: bool,
 23    pub show_call_status_icon: bool,
 24    pub autosave: AutosaveSetting,
 25    pub restore_on_startup: settings::RestoreOnStartupBehavior,
 26    pub restore_on_file_reopen: bool,
 27    pub drop_target_size: f32,
 28    pub use_system_path_prompts: bool,
 29    pub use_system_prompts: bool,
 30    pub command_aliases: HashMap<String, String>,
 31    pub max_tabs: Option<NonZeroUsize>,
 32    pub when_closing_with_no_tabs: settings::CloseWindowWhenNoItems,
 33    pub on_last_window_closed: settings::OnLastWindowClosed,
 34    pub resize_all_panels_in_dock: Vec<DockPosition>, // <- This one is not an overwrite merge, it is an extend merge
 35    pub close_on_file_delete: bool,
 36    pub use_system_window_tabs: bool,
 37    pub zoomed_padding: bool,
 38}
 39
 40#[derive(Copy, Clone, Debug, PartialEq)]
 41pub struct CenteredLayoutSettings {
 42    /// The relative width of the left padding of the central pane from the
 43    /// workspace when the centered layout is used.
 44    ///
 45    /// Default: 0.2
 46    pub left_padding: f32,
 47    // The relative width of the right padding of the central pane from the
 48    // workspace when the centered layout is used.
 49    ///
 50    /// Default: 0.2
 51    pub right_padding: f32,
 52}
 53
 54#[derive(Copy, Clone, PartialEq, Debug, Default)]
 55pub struct ActivePanelModifiers {
 56    /// Size of the border surrounding the active pane.
 57    /// When set to 0, the active pane doesn't have any border.
 58    /// The border is drawn inset.
 59    ///
 60    /// Default: `0.0`
 61    // TODO: make this not an option, it is never None
 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    // TODO: make this not an option, it is never None
 70    pub inactive_opacity: Option<f32>,
 71}
 72
 73#[derive(Deserialize)]
 74pub struct TabBarSettings {
 75    pub show: bool,
 76    pub show_nav_history_buttons: bool,
 77    pub show_tab_bar_buttons: bool,
 78}
 79
 80impl Settings for WorkspaceSettings {
 81    fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
 82        let workspace = &content.workspace;
 83        Self {
 84            active_pane_modifiers: ActivePanelModifiers {
 85                border_size: Some(
 86                    workspace
 87                        .active_pane_modifiers
 88                        .unwrap()
 89                        .border_size
 90                        .unwrap(),
 91                ),
 92                inactive_opacity: Some(
 93                    workspace
 94                        .active_pane_modifiers
 95                        .unwrap()
 96                        .inactive_opacity
 97                        .unwrap(),
 98                ),
 99            },
100            bottom_dock_layout: workspace.bottom_dock_layout.clone().unwrap(),
101            pane_split_direction_horizontal: workspace
102                .pane_split_direction_horizontal
103                .clone()
104                .unwrap(),
105            pane_split_direction_vertical: workspace.pane_split_direction_vertical.clone().unwrap(),
106            centered_layout: workspace.centered_layout.clone().unwrap(),
107            confirm_quit: workspace.confirm_quit.clone().unwrap(),
108            show_call_status_icon: workspace.show_call_status_icon.clone().unwrap(),
109            autosave: workspace.autosave.clone().unwrap(),
110            restore_on_startup: workspace.restore_on_startup.clone().unwrap(),
111            restore_on_file_reopen: workspace.restore_on_file_reopen.clone().unwrap(),
112            drop_target_size: workspace.drop_target_size.clone().unwrap(),
113            use_system_path_prompts: workspace.use_system_path_prompts.clone().unwrap(),
114            use_system_prompts: workspace.use_system_prompts.clone().unwrap(),
115            command_aliases: workspace.command_aliases.clone(),
116            max_tabs: workspace.max_tabs.clone(),
117            when_closing_with_no_tabs: workspace.when_closing_with_no_tabs.clone().unwrap(),
118            on_last_window_closed: workspace.on_last_window_closed.clone().unwrap(),
119            resize_all_panels_in_dock: workspace
120                .resize_all_panels_in_dock
121                .iter()
122                .copied()
123                .map(Into::into)
124                .collect(),
125            close_on_file_delete: workspace.close_on_file_delete.clone().unwrap(),
126            use_system_window_tabs: workspace.use_system_window_tabs.clone().unwrap(),
127            zoomed_padding: workspace.zoomed_padding.clone().unwrap(),
128        }
129    }
130
131    fn refine(&mut self, content: &settings::SettingsContent, cx: &mut App) {
132        let workspace = &content.workspace;
133        if let Some(border_size) = *&workspace
134            .active_pane_modifiers
135            .and_then(|modifier| modifier.border_size)
136        {
137            self.active_pane_modifiers.border_size = Some(border_size);
138        }
139
140        if let Some(inactive_opacity) = *&workspace
141            .active_pane_modifiers
142            .and_then(|modifier| modifier.inactive_opacity)
143        {
144            self.active_pane_modifiers.inactive_opacity = Some(inactive_opacity);
145        }
146
147        self.bottom_dock_layout
148            .merge_from(&workspace.bottom_dock_layout);
149        self.pane_split_direction_horizontal
150            .merge_from(&workspace.pane_split_direction_horizontal);
151        self.pane_split_direction_vertical
152            .merge_from(&workspace.pane_split_direction_vertical);
153        self.centered_layout.merge_from(&workspace.centered_layout);
154        self.confirm_quit.merge_from(&workspace.confirm_quit);
155        self.show_call_status_icon
156            .merge_from(&workspace.show_call_status_icon);
157        self.autosave.merge_from(&workspace.autosave);
158        self.restore_on_startup
159            .merge_from(&workspace.restore_on_startup);
160        self.restore_on_file_reopen
161            .merge_from(&workspace.restore_on_file_reopen);
162        self.drop_target_size
163            .merge_from(&workspace.drop_target_size);
164        self.use_system_path_prompts
165            .merge_from(&workspace.use_system_path_prompts);
166        self.use_system_prompts
167            .merge_from(&workspace.use_system_prompts);
168        self.command_aliases
169            .extend(workspace.command_aliases.clone());
170        if let Some(max_tabs) = workspace.max_tabs {
171            self.max_tabs = Some(max_tabs);
172        }
173        self.when_closing_with_no_tabs
174            .merge_from(&workspace.when_closing_with_no_tabs);
175        self.on_last_window_closed
176            .merge_from(&workspace.on_last_window_closed);
177        self.resize_all_panels_in_dock.extend(
178            workspace
179                .resize_all_panels_in_dock
180                .iter()
181                .copied()
182                .map(Into::<DockPosition>::into),
183        );
184        self.close_on_file_delete
185            .merge_from(&workspace.close_on_file_delete);
186        self.use_system_window_tabs
187            .merge_from(&workspace.use_system_window_tabs);
188        self.zoomed_padding.merge_from(&workspace.zoomed_padding);
189    }
190
191    fn import_from_vscode(
192        vscode: &settings::VsCodeSettings,
193        current: &mut settings::SettingsContent,
194    ) {
195        if vscode
196            .read_bool("accessibility.dimUnfocused.enabled")
197            .unwrap_or_default()
198            && let Some(opacity) = vscode
199                .read_value("accessibility.dimUnfocused.opacity")
200                .and_then(|v| v.as_f64())
201        {
202            current
203                .workspace
204                .active_pane_modifiers
205                .get_or_insert_default()
206                .inactive_opacity = Some(opacity as f32);
207        }
208
209        vscode.enum_setting(
210            "window.confirmBeforeClose",
211            &mut current.workspace.confirm_quit,
212            |s| match s {
213                "always" | "keyboardOnly" => Some(true),
214                "never" => Some(false),
215                _ => None,
216            },
217        );
218
219        vscode.bool_setting(
220            "workbench.editor.restoreViewState",
221            &mut current.workspace.restore_on_file_reopen,
222        );
223
224        if let Some(b) = vscode.read_bool("window.closeWhenEmpty") {
225            current.workspace.when_closing_with_no_tabs = Some(if b {
226                settings::CloseWindowWhenNoItems::CloseWindow
227            } else {
228                settings::CloseWindowWhenNoItems::KeepWindowOpen
229            });
230        }
231
232        if let Some(b) = vscode.read_bool("files.simpleDialog.enable") {
233            current.workspace.use_system_path_prompts = Some(!b);
234        }
235
236        if let Some(v) = vscode.read_enum("files.autoSave", |s| match s {
237            "off" => Some(AutosaveSetting::Off),
238            "afterDelay" => Some(AutosaveSetting::AfterDelay {
239                milliseconds: vscode
240                    .read_value("files.autoSaveDelay")
241                    .and_then(|v| v.as_u64())
242                    .unwrap_or(1000),
243            }),
244            "onFocusChange" => Some(AutosaveSetting::OnFocusChange),
245            "onWindowChange" => Some(AutosaveSetting::OnWindowChange),
246            _ => None,
247        }) {
248            current.workspace.autosave = Some(v);
249        }
250
251        // workbench.editor.limit contains "enabled", "value", and "perEditorGroup"
252        // our semantics match if those are set to true, some N, and true respectively.
253        // we'll ignore "perEditorGroup" for now since we only support a global max
254        if let Some(n) = vscode
255            .read_value("workbench.editor.limit.value")
256            .and_then(|v| v.as_u64())
257            .and_then(|n| NonZeroUsize::new(n as usize))
258            && vscode
259                .read_bool("workbench.editor.limit.enabled")
260                .unwrap_or_default()
261        {
262            current.workspace.max_tabs = Some(n)
263        }
264
265        if let Some(b) = vscode.read_bool("window.nativeTabs") {
266            current.workspace.use_system_window_tabs = Some(b);
267        }
268
269        // some combination of "window.restoreWindows" and "workbench.startupEditor" might
270        // map to our "restore_on_startup"
271
272        // there doesn't seem to be a way to read whether the bottom dock's "justified"
273        // setting is enabled in vscode. that'd be our equivalent to "bottom_dock_layout"
274    }
275}
276
277impl Settings for TabBarSettings {
278    fn import_from_vscode(
279        vscode: &settings::VsCodeSettings,
280        current: &mut settings::SettingsContent,
281    ) {
282        if let Some(b) = vscode.read_enum("workbench.editor.showTabs", |s| match s {
283            "multiple" => Some(true),
284            "single" | "none" => Some(false),
285            _ => None,
286        }) {
287            current.workspace.tab_bar.get_or_insert_default().show = Some(b);
288        }
289        if Some("hidden") == vscode.read_string("workbench.editor.editorActionsLocation") {
290            current
291                .workspace
292                .tab_bar
293                .get_or_insert_default()
294                .show_tab_bar_buttons = Some(false)
295        }
296    }
297}