1use core::num;
  2use std::num::NonZeroU32;
  3
  4use gpui::App;
  5use language::CursorShape;
  6use project::project_settings::DiagnosticSeverity;
  7pub use settings::{
  8    CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
  9    GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
 10    ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
 11    VsCodeSettings,
 12};
 13use settings::{Settings, SettingsContent};
 14use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
 15
 16/// Imports from the VSCode settings at
 17/// https://code.visualstudio.com/docs/reference/default-settings
 18#[derive(Clone)]
 19pub struct EditorSettings {
 20    pub cursor_blink: bool,
 21    pub cursor_shape: Option<CursorShape>,
 22    pub current_line_highlight: CurrentLineHighlight,
 23    pub selection_highlight: bool,
 24    pub rounded_selection: bool,
 25    pub lsp_highlight_debounce: u64,
 26    pub hover_popover_enabled: bool,
 27    pub hover_popover_delay: u64,
 28    pub toolbar: Toolbar,
 29    pub scrollbar: Scrollbar,
 30    pub minimap: Minimap,
 31    pub gutter: Gutter,
 32    pub scroll_beyond_last_line: ScrollBeyondLastLine,
 33    pub vertical_scroll_margin: f64,
 34    pub autoscroll_on_clicks: bool,
 35    pub horizontal_scroll_margin: f32,
 36    pub scroll_sensitivity: f32,
 37    pub fast_scroll_sensitivity: f32,
 38    pub relative_line_numbers: bool,
 39    pub seed_search_query_from_cursor: SeedQuerySetting,
 40    pub use_smartcase_search: bool,
 41    pub multi_cursor_modifier: MultiCursorModifier,
 42    pub redact_private_values: bool,
 43    pub expand_excerpt_lines: u32,
 44    pub excerpt_context_lines: u32,
 45    pub middle_click_paste: bool,
 46    pub double_click_in_multibuffer: DoubleClickInMultibuffer,
 47    pub search_wrap: bool,
 48    pub search: SearchSettings,
 49    pub auto_signature_help: bool,
 50    pub show_signature_help_after_edits: bool,
 51    pub go_to_definition_fallback: GoToDefinitionFallback,
 52    pub jupyter: Jupyter,
 53    pub hide_mouse: Option<HideMouseMode>,
 54    pub snippet_sort_order: SnippetSortOrder,
 55    pub diagnostics_max_severity: Option<DiagnosticSeverity>,
 56    pub inline_code_actions: bool,
 57    pub drag_and_drop_selection: DragAndDropSelection,
 58    pub lsp_document_colors: DocumentColorsRenderMode,
 59    pub minimum_contrast_for_highlights: f32,
 60}
 61#[derive(Debug, Clone)]
 62pub struct Jupyter {
 63    /// Whether the Jupyter feature is enabled.
 64    ///
 65    /// Default: true
 66    pub enabled: bool,
 67}
 68
 69#[derive(Clone, Debug, PartialEq, Eq)]
 70pub struct Toolbar {
 71    pub breadcrumbs: bool,
 72    pub quick_actions: bool,
 73    pub selections_menu: bool,
 74    pub agent_review: bool,
 75    pub code_actions: bool,
 76}
 77
 78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
 79pub struct Scrollbar {
 80    pub show: ShowScrollbar,
 81    pub git_diff: bool,
 82    pub selected_text: bool,
 83    pub selected_symbol: bool,
 84    pub search_results: bool,
 85    pub diagnostics: ScrollbarDiagnostics,
 86    pub cursors: bool,
 87    pub axes: ScrollbarAxes,
 88}
 89
 90#[derive(Copy, Clone, Debug, PartialEq)]
 91pub struct Minimap {
 92    pub show: ShowMinimap,
 93    pub display_in: DisplayIn,
 94    pub thumb: MinimapThumb,
 95    pub thumb_border: MinimapThumbBorder,
 96    pub current_line_highlight: Option<CurrentLineHighlight>,
 97    pub max_width_columns: num::NonZeroU32,
 98}
 99
100impl Minimap {
101    pub fn minimap_enabled(&self) -> bool {
102        self.show != ShowMinimap::Never
103    }
104
105    #[inline]
106    pub fn on_active_editor(&self) -> bool {
107        self.display_in == DisplayIn::ActiveEditor
108    }
109
110    pub fn with_show_override(self) -> Self {
111        Self {
112            show: ShowMinimap::Always,
113            ..self
114        }
115    }
116}
117
118#[derive(Copy, Clone, Debug, PartialEq, Eq)]
119pub struct Gutter {
120    pub min_line_number_digits: usize,
121    pub line_numbers: bool,
122    pub runnables: bool,
123    pub breakpoints: bool,
124    pub folds: bool,
125}
126
127/// Forcefully enable or disable the scrollbar for each axis
128#[derive(Copy, Clone, Debug, PartialEq, Eq)]
129pub struct ScrollbarAxes {
130    /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
131    ///
132    /// Default: true
133    pub horizontal: bool,
134
135    /// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
136    ///
137    /// Default: true
138    pub vertical: bool,
139}
140
141/// Whether to allow drag and drop text selection in buffer.
142#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
143pub struct DragAndDropSelection {
144    /// When true, enables drag and drop text selection in buffer.
145    ///
146    /// Default: true
147    pub enabled: bool,
148
149    /// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
150    ///
151    /// Default: 300
152    pub delay: u64,
153}
154
155/// Default options for buffer and project search items.
156#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
157pub struct SearchSettings {
158    /// Whether to show the project search button in the status bar.
159    pub button: bool,
160    pub whole_word: bool,
161    pub case_sensitive: bool,
162    pub include_ignored: bool,
163    pub regex: bool,
164}
165
166impl EditorSettings {
167    pub fn jupyter_enabled(cx: &App) -> bool {
168        EditorSettings::get_global(cx).jupyter.enabled
169    }
170}
171
172impl ScrollbarVisibility for EditorSettings {
173    fn visibility(&self, _cx: &App) -> ShowScrollbar {
174        self.scrollbar.show
175    }
176}
177
178impl Settings for EditorSettings {
179    fn from_settings(content: &settings::SettingsContent) -> Self {
180        let editor = content.editor.clone();
181        let scrollbar = editor.scrollbar.unwrap();
182        let minimap = editor.minimap.unwrap();
183        let gutter = editor.gutter.unwrap();
184        let axes = scrollbar.axes.unwrap();
185        let toolbar = editor.toolbar.unwrap();
186        let search = editor.search.unwrap();
187        let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
188        Self {
189            cursor_blink: editor.cursor_blink.unwrap(),
190            cursor_shape: editor.cursor_shape.map(Into::into),
191            current_line_highlight: editor.current_line_highlight.unwrap(),
192            selection_highlight: editor.selection_highlight.unwrap(),
193            rounded_selection: editor.rounded_selection.unwrap(),
194            lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(),
195            hover_popover_enabled: editor.hover_popover_enabled.unwrap(),
196            hover_popover_delay: editor.hover_popover_delay.unwrap(),
197            toolbar: Toolbar {
198                breadcrumbs: toolbar.breadcrumbs.unwrap(),
199                quick_actions: toolbar.quick_actions.unwrap(),
200                selections_menu: toolbar.selections_menu.unwrap(),
201                agent_review: toolbar.agent_review.unwrap(),
202                code_actions: toolbar.code_actions.unwrap(),
203            },
204            scrollbar: Scrollbar {
205                show: scrollbar.show.map(Into::into).unwrap(),
206                git_diff: scrollbar.git_diff.unwrap(),
207                selected_text: scrollbar.selected_text.unwrap(),
208                selected_symbol: scrollbar.selected_symbol.unwrap(),
209                search_results: scrollbar.search_results.unwrap(),
210                diagnostics: scrollbar.diagnostics.unwrap(),
211                cursors: scrollbar.cursors.unwrap(),
212                axes: ScrollbarAxes {
213                    horizontal: axes.horizontal.unwrap(),
214                    vertical: axes.vertical.unwrap(),
215                },
216            },
217            minimap: Minimap {
218                show: minimap.show.unwrap(),
219                display_in: minimap.display_in.unwrap(),
220                thumb: minimap.thumb.unwrap(),
221                thumb_border: minimap.thumb_border.unwrap(),
222                current_line_highlight: minimap.current_line_highlight,
223                max_width_columns: minimap.max_width_columns.unwrap(),
224            },
225            gutter: Gutter {
226                min_line_number_digits: gutter.min_line_number_digits.unwrap(),
227                line_numbers: gutter.line_numbers.unwrap(),
228                runnables: gutter.runnables.unwrap(),
229                breakpoints: gutter.breakpoints.unwrap(),
230                folds: gutter.folds.unwrap(),
231            },
232            scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(),
233            vertical_scroll_margin: editor.vertical_scroll_margin.unwrap() as f64,
234            autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(),
235            horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(),
236            scroll_sensitivity: editor.scroll_sensitivity.unwrap(),
237            fast_scroll_sensitivity: editor.fast_scroll_sensitivity.unwrap(),
238            relative_line_numbers: editor.relative_line_numbers.unwrap(),
239            seed_search_query_from_cursor: editor.seed_search_query_from_cursor.unwrap(),
240            use_smartcase_search: editor.use_smartcase_search.unwrap(),
241            multi_cursor_modifier: editor.multi_cursor_modifier.unwrap(),
242            redact_private_values: editor.redact_private_values.unwrap(),
243            expand_excerpt_lines: editor.expand_excerpt_lines.unwrap(),
244            excerpt_context_lines: editor.excerpt_context_lines.unwrap(),
245            middle_click_paste: editor.middle_click_paste.unwrap(),
246            double_click_in_multibuffer: editor.double_click_in_multibuffer.unwrap(),
247            search_wrap: editor.search_wrap.unwrap(),
248            search: SearchSettings {
249                button: search.button.unwrap(),
250                whole_word: search.whole_word.unwrap(),
251                case_sensitive: search.case_sensitive.unwrap(),
252                include_ignored: search.include_ignored.unwrap(),
253                regex: search.regex.unwrap(),
254            },
255            auto_signature_help: editor.auto_signature_help.unwrap(),
256            show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(),
257            go_to_definition_fallback: editor.go_to_definition_fallback.unwrap(),
258            jupyter: Jupyter {
259                enabled: editor.jupyter.unwrap().enabled.unwrap(),
260            },
261            hide_mouse: editor.hide_mouse,
262            snippet_sort_order: editor.snippet_sort_order.unwrap(),
263            diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into),
264            inline_code_actions: editor.inline_code_actions.unwrap(),
265            drag_and_drop_selection: DragAndDropSelection {
266                enabled: drag_and_drop_selection.enabled.unwrap(),
267                delay: drag_and_drop_selection.delay.unwrap(),
268            },
269            lsp_document_colors: editor.lsp_document_colors.unwrap(),
270            minimum_contrast_for_highlights: editor.minimum_contrast_for_highlights.unwrap().0,
271        }
272    }
273
274    fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) {
275        vscode.enum_setting(
276            "editor.cursorBlinking",
277            &mut current.editor.cursor_blink,
278            |s| match s {
279                "blink" | "phase" | "expand" | "smooth" => Some(true),
280                "solid" => Some(false),
281                _ => None,
282            },
283        );
284        vscode.enum_setting(
285            "editor.cursorStyle",
286            &mut current.editor.cursor_shape,
287            |s| match s {
288                "block" => Some(settings::CursorShape::Block),
289                "block-outline" => Some(settings::CursorShape::Hollow),
290                "line" | "line-thin" => Some(settings::CursorShape::Bar),
291                "underline" | "underline-thin" => Some(settings::CursorShape::Underline),
292                _ => None,
293            },
294        );
295
296        vscode.enum_setting(
297            "editor.renderLineHighlight",
298            &mut current.editor.current_line_highlight,
299            |s| match s {
300                "gutter" => Some(CurrentLineHighlight::Gutter),
301                "line" => Some(CurrentLineHighlight::Line),
302                "all" => Some(CurrentLineHighlight::All),
303                _ => None,
304            },
305        );
306
307        vscode.bool_setting(
308            "editor.selectionHighlight",
309            &mut current.editor.selection_highlight,
310        );
311        vscode.bool_setting(
312            "editor.roundedSelection",
313            &mut current.editor.rounded_selection,
314        );
315        vscode.bool_setting(
316            "editor.hover.enabled",
317            &mut current.editor.hover_popover_enabled,
318        );
319        vscode.u64_setting(
320            "editor.hover.delay",
321            &mut current.editor.hover_popover_delay,
322        );
323
324        let mut gutter = settings::GutterContent::default();
325        vscode.enum_setting(
326            "editor.showFoldingControls",
327            &mut gutter.folds,
328            |s| match s {
329                "always" | "mouseover" => Some(true),
330                "never" => Some(false),
331                _ => None,
332            },
333        );
334        vscode.enum_setting(
335            "editor.lineNumbers",
336            &mut gutter.line_numbers,
337            |s| match s {
338                "on" | "relative" => Some(true),
339                "off" => Some(false),
340                _ => None,
341            },
342        );
343        if let Some(old_gutter) = current.editor.gutter.as_mut() {
344            if gutter.folds.is_some() {
345                old_gutter.folds = gutter.folds
346            }
347            if gutter.line_numbers.is_some() {
348                old_gutter.line_numbers = gutter.line_numbers
349            }
350        } else if gutter != settings::GutterContent::default() {
351            current.editor.gutter = Some(gutter)
352        }
353        if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") {
354            current.editor.scroll_beyond_last_line = Some(if b {
355                ScrollBeyondLastLine::OnePage
356            } else {
357                ScrollBeyondLastLine::Off
358            })
359        }
360
361        let mut scrollbar_axes = settings::ScrollbarAxesContent::default();
362        vscode.enum_setting(
363            "editor.scrollbar.horizontal",
364            &mut scrollbar_axes.horizontal,
365            |s| match s {
366                "auto" | "visible" => Some(true),
367                "hidden" => Some(false),
368                _ => None,
369            },
370        );
371        vscode.enum_setting(
372            "editor.scrollbar.vertical",
373            &mut scrollbar_axes.horizontal,
374            |s| match s {
375                "auto" | "visible" => Some(true),
376                "hidden" => Some(false),
377                _ => None,
378            },
379        );
380
381        if scrollbar_axes != settings::ScrollbarAxesContent::default() {
382            let scrollbar_settings = current.editor.scrollbar.get_or_insert_default();
383            let axes_settings = scrollbar_settings.axes.get_or_insert_default();
384
385            if let Some(vertical) = scrollbar_axes.vertical {
386                axes_settings.vertical = Some(vertical);
387            }
388            if let Some(horizontal) = scrollbar_axes.horizontal {
389                axes_settings.horizontal = Some(horizontal);
390            }
391        }
392
393        // TODO: check if this does the int->float conversion?
394        vscode.f32_setting(
395            "editor.cursorSurroundingLines",
396            &mut current.editor.vertical_scroll_margin,
397        );
398        vscode.f32_setting(
399            "editor.mouseWheelScrollSensitivity",
400            &mut current.editor.scroll_sensitivity,
401        );
402        vscode.f32_setting(
403            "editor.fastScrollSensitivity",
404            &mut current.editor.fast_scroll_sensitivity,
405        );
406        if Some("relative") == vscode.read_string("editor.lineNumbers") {
407            current.editor.relative_line_numbers = Some(true);
408        }
409
410        vscode.enum_setting(
411            "editor.find.seedSearchStringFromSelection",
412            &mut current.editor.seed_search_query_from_cursor,
413            |s| match s {
414                "always" => Some(SeedQuerySetting::Always),
415                "selection" => Some(SeedQuerySetting::Selection),
416                "never" => Some(SeedQuerySetting::Never),
417                _ => None,
418            },
419        );
420        vscode.bool_setting("search.smartCase", &mut current.editor.use_smartcase_search);
421        vscode.enum_setting(
422            "editor.multiCursorModifier",
423            &mut current.editor.multi_cursor_modifier,
424            |s| match s {
425                "ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl),
426                "alt" => Some(MultiCursorModifier::Alt),
427                _ => None,
428            },
429        );
430
431        vscode.bool_setting(
432            "editor.parameterHints.enabled",
433            &mut current.editor.auto_signature_help,
434        );
435        vscode.bool_setting(
436            "editor.parameterHints.enabled",
437            &mut current.editor.show_signature_help_after_edits,
438        );
439
440        if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") {
441            let search = current.editor.search.get_or_insert_default();
442            search.include_ignored = Some(use_ignored);
443        }
444
445        let mut minimap = settings::MinimapContent::default();
446        let minimap_enabled = vscode.read_bool("editor.minimap.enabled").unwrap_or(true);
447        let autohide = vscode.read_bool("editor.minimap.autohide");
448        let mut max_width_columns: Option<u32> = None;
449        vscode.u32_setting("editor.minimap.maxColumn", &mut max_width_columns);
450        if minimap_enabled {
451            if let Some(false) = autohide {
452                minimap.show = Some(ShowMinimap::Always);
453            } else {
454                minimap.show = Some(ShowMinimap::Auto);
455            }
456        } else {
457            minimap.show = Some(ShowMinimap::Never);
458        }
459        if let Some(max_width_columns) = max_width_columns {
460            minimap.max_width_columns = NonZeroU32::new(max_width_columns);
461        }
462
463        vscode.enum_setting(
464            "editor.minimap.showSlider",
465            &mut minimap.thumb,
466            |s| match s {
467                "always" => Some(MinimapThumb::Always),
468                "mouseover" => Some(MinimapThumb::Hover),
469                _ => None,
470            },
471        );
472
473        if minimap != settings::MinimapContent::default() {
474            current.editor.minimap = Some(minimap)
475        }
476    }
477}