Files
oak-gpui/crates/editor/src/editor_settings.rs
T
Conrad Irwin b09764c54a settings: Use a derive macro for refine (#38451)
When we refactored settings to not pass JSON blobs around, we ended up
needing
to write *a lot* of code that just merged things (like json merge used
to do).

Use a derive macro to prevent typos in this logic.

Release Notes:

- N/A
2025-09-18 21:13:49 +00:00

496 lines
18 KiB
Rust

use core::num;
use std::num::NonZeroU32;
use gpui::App;
use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
pub use settings::{
CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
VsCodeSettings,
};
use settings::{Settings, SettingsContent};
use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
/// Imports from the VSCode settings at
/// https://code.visualstudio.com/docs/reference/default-settings
#[derive(Clone)]
pub struct EditorSettings {
pub cursor_blink: bool,
pub cursor_shape: Option<CursorShape>,
pub current_line_highlight: CurrentLineHighlight,
pub selection_highlight: bool,
pub rounded_selection: bool,
pub lsp_highlight_debounce: u64,
pub hover_popover_enabled: bool,
pub hover_popover_delay: u64,
pub status_bar: StatusBar,
pub toolbar: Toolbar,
pub scrollbar: Scrollbar,
pub minimap: Minimap,
pub gutter: Gutter,
pub scroll_beyond_last_line: ScrollBeyondLastLine,
pub vertical_scroll_margin: f32,
pub autoscroll_on_clicks: bool,
pub horizontal_scroll_margin: f32,
pub scroll_sensitivity: f32,
pub fast_scroll_sensitivity: f32,
pub relative_line_numbers: bool,
pub seed_search_query_from_cursor: SeedQuerySetting,
pub use_smartcase_search: bool,
pub multi_cursor_modifier: MultiCursorModifier,
pub redact_private_values: bool,
pub expand_excerpt_lines: u32,
pub excerpt_context_lines: u32,
pub middle_click_paste: bool,
pub double_click_in_multibuffer: DoubleClickInMultibuffer,
pub search_wrap: bool,
pub search: SearchSettings,
pub auto_signature_help: bool,
pub show_signature_help_after_edits: bool,
pub go_to_definition_fallback: GoToDefinitionFallback,
pub jupyter: Jupyter,
pub hide_mouse: Option<HideMouseMode>,
pub snippet_sort_order: SnippetSortOrder,
pub diagnostics_max_severity: Option<DiagnosticSeverity>,
pub inline_code_actions: bool,
pub drag_and_drop_selection: DragAndDropSelection,
pub lsp_document_colors: DocumentColorsRenderMode,
pub minimum_contrast_for_highlights: f32,
}
#[derive(Debug, Clone)]
pub struct Jupyter {
/// Whether the Jupyter feature is enabled.
///
/// Default: true
pub enabled: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct StatusBar {
/// Whether to display the active language button in the status bar.
///
/// Default: true
pub active_language_button: bool,
/// Whether to show the cursor position button in the status bar.
///
/// Default: true
pub cursor_position_button: bool,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Toolbar {
pub breadcrumbs: bool,
pub quick_actions: bool,
pub selections_menu: bool,
pub agent_review: bool,
pub code_actions: bool,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Scrollbar {
pub show: ShowScrollbar,
pub git_diff: bool,
pub selected_text: bool,
pub selected_symbol: bool,
pub search_results: bool,
pub diagnostics: ScrollbarDiagnostics,
pub cursors: bool,
pub axes: ScrollbarAxes,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Minimap {
pub show: ShowMinimap,
pub display_in: DisplayIn,
pub thumb: MinimapThumb,
pub thumb_border: MinimapThumbBorder,
pub current_line_highlight: Option<CurrentLineHighlight>,
pub max_width_columns: num::NonZeroU32,
}
impl Minimap {
pub fn minimap_enabled(&self) -> bool {
self.show != ShowMinimap::Never
}
#[inline]
pub fn on_active_editor(&self) -> bool {
self.display_in == DisplayIn::ActiveEditor
}
pub fn with_show_override(self) -> Self {
Self {
show: ShowMinimap::Always,
..self
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Gutter {
pub min_line_number_digits: usize,
pub line_numbers: bool,
pub runnables: bool,
pub breakpoints: bool,
pub folds: bool,
}
/// Forcefully enable or disable the scrollbar for each axis
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ScrollbarAxes {
/// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings.
///
/// Default: true
pub horizontal: bool,
/// When false, forcefully disables the vertical scrollbar. Otherwise, obey other settings.
///
/// Default: true
pub vertical: bool,
}
/// Whether to allow drag and drop text selection in buffer.
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct DragAndDropSelection {
/// When true, enables drag and drop text selection in buffer.
///
/// Default: true
pub enabled: bool,
/// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
///
/// Default: 300
pub delay: u64,
}
/// Default options for buffer and project search items.
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct SearchSettings {
/// Whether to show the project search button in the status bar.
pub button: bool,
pub whole_word: bool,
pub case_sensitive: bool,
pub include_ignored: bool,
pub regex: bool,
}
impl EditorSettings {
pub fn jupyter_enabled(cx: &App) -> bool {
EditorSettings::get_global(cx).jupyter.enabled
}
}
impl ScrollbarVisibility for EditorSettings {
fn visibility(&self, _cx: &App) -> ShowScrollbar {
self.scrollbar.show
}
}
impl Settings for EditorSettings {
fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let editor = content.editor.clone();
let scrollbar = editor.scrollbar.unwrap();
let minimap = editor.minimap.unwrap();
let gutter = editor.gutter.unwrap();
let axes = scrollbar.axes.unwrap();
let status_bar = editor.status_bar.unwrap();
let toolbar = editor.toolbar.unwrap();
let search = editor.search.unwrap();
let drag_and_drop_selection = editor.drag_and_drop_selection.unwrap();
Self {
cursor_blink: editor.cursor_blink.unwrap(),
cursor_shape: editor.cursor_shape.map(Into::into),
current_line_highlight: editor.current_line_highlight.unwrap(),
selection_highlight: editor.selection_highlight.unwrap(),
rounded_selection: editor.rounded_selection.unwrap(),
lsp_highlight_debounce: editor.lsp_highlight_debounce.unwrap(),
hover_popover_enabled: editor.hover_popover_enabled.unwrap(),
hover_popover_delay: editor.hover_popover_delay.unwrap(),
status_bar: StatusBar {
active_language_button: status_bar.active_language_button.unwrap(),
cursor_position_button: status_bar.cursor_position_button.unwrap(),
},
toolbar: Toolbar {
breadcrumbs: toolbar.breadcrumbs.unwrap(),
quick_actions: toolbar.quick_actions.unwrap(),
selections_menu: toolbar.selections_menu.unwrap(),
agent_review: toolbar.agent_review.unwrap(),
code_actions: toolbar.code_actions.unwrap(),
},
scrollbar: Scrollbar {
show: scrollbar.show.map(Into::into).unwrap(),
git_diff: scrollbar.git_diff.unwrap(),
selected_text: scrollbar.selected_text.unwrap(),
selected_symbol: scrollbar.selected_symbol.unwrap(),
search_results: scrollbar.search_results.unwrap(),
diagnostics: scrollbar.diagnostics.unwrap(),
cursors: scrollbar.cursors.unwrap(),
axes: ScrollbarAxes {
horizontal: axes.horizontal.unwrap(),
vertical: axes.vertical.unwrap(),
},
},
minimap: Minimap {
show: minimap.show.unwrap(),
display_in: minimap.display_in.unwrap(),
thumb: minimap.thumb.unwrap(),
thumb_border: minimap.thumb_border.unwrap(),
current_line_highlight: minimap.current_line_highlight,
max_width_columns: minimap.max_width_columns.unwrap(),
},
gutter: Gutter {
min_line_number_digits: gutter.min_line_number_digits.unwrap(),
line_numbers: gutter.line_numbers.unwrap(),
runnables: gutter.runnables.unwrap(),
breakpoints: gutter.breakpoints.unwrap(),
folds: gutter.folds.unwrap(),
},
scroll_beyond_last_line: editor.scroll_beyond_last_line.unwrap(),
vertical_scroll_margin: editor.vertical_scroll_margin.unwrap(),
autoscroll_on_clicks: editor.autoscroll_on_clicks.unwrap(),
horizontal_scroll_margin: editor.horizontal_scroll_margin.unwrap(),
scroll_sensitivity: editor.scroll_sensitivity.unwrap(),
fast_scroll_sensitivity: editor.fast_scroll_sensitivity.unwrap(),
relative_line_numbers: editor.relative_line_numbers.unwrap(),
seed_search_query_from_cursor: editor.seed_search_query_from_cursor.unwrap(),
use_smartcase_search: editor.use_smartcase_search.unwrap(),
multi_cursor_modifier: editor.multi_cursor_modifier.unwrap(),
redact_private_values: editor.redact_private_values.unwrap(),
expand_excerpt_lines: editor.expand_excerpt_lines.unwrap(),
excerpt_context_lines: editor.excerpt_context_lines.unwrap(),
middle_click_paste: editor.middle_click_paste.unwrap(),
double_click_in_multibuffer: editor.double_click_in_multibuffer.unwrap(),
search_wrap: editor.search_wrap.unwrap(),
search: SearchSettings {
button: search.button.unwrap(),
whole_word: search.whole_word.unwrap(),
case_sensitive: search.case_sensitive.unwrap(),
include_ignored: search.include_ignored.unwrap(),
regex: search.regex.unwrap(),
},
auto_signature_help: editor.auto_signature_help.unwrap(),
show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(),
go_to_definition_fallback: editor.go_to_definition_fallback.unwrap(),
jupyter: Jupyter {
enabled: editor.jupyter.unwrap().enabled.unwrap(),
},
hide_mouse: editor.hide_mouse,
snippet_sort_order: editor.snippet_sort_order.unwrap(),
diagnostics_max_severity: editor.diagnostics_max_severity.map(Into::into),
inline_code_actions: editor.inline_code_actions.unwrap(),
drag_and_drop_selection: DragAndDropSelection {
enabled: drag_and_drop_selection.enabled.unwrap(),
delay: drag_and_drop_selection.delay.unwrap(),
},
lsp_document_colors: editor.lsp_document_colors.unwrap(),
minimum_contrast_for_highlights: editor.minimum_contrast_for_highlights.unwrap(),
}
}
fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) {
vscode.enum_setting(
"editor.cursorBlinking",
&mut current.editor.cursor_blink,
|s| match s {
"blink" | "phase" | "expand" | "smooth" => Some(true),
"solid" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.cursorStyle",
&mut current.editor.cursor_shape,
|s| match s {
"block" => Some(settings::CursorShape::Block),
"block-outline" => Some(settings::CursorShape::Hollow),
"line" | "line-thin" => Some(settings::CursorShape::Bar),
"underline" | "underline-thin" => Some(settings::CursorShape::Underline),
_ => None,
},
);
vscode.enum_setting(
"editor.renderLineHighlight",
&mut current.editor.current_line_highlight,
|s| match s {
"gutter" => Some(CurrentLineHighlight::Gutter),
"line" => Some(CurrentLineHighlight::Line),
"all" => Some(CurrentLineHighlight::All),
_ => None,
},
);
vscode.bool_setting(
"editor.selectionHighlight",
&mut current.editor.selection_highlight,
);
vscode.bool_setting(
"editor.roundedSelection",
&mut current.editor.rounded_selection,
);
vscode.bool_setting(
"editor.hover.enabled",
&mut current.editor.hover_popover_enabled,
);
vscode.u64_setting(
"editor.hover.delay",
&mut current.editor.hover_popover_delay,
);
let mut gutter = settings::GutterContent::default();
vscode.enum_setting(
"editor.showFoldingControls",
&mut gutter.folds,
|s| match s {
"always" | "mouseover" => Some(true),
"never" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.lineNumbers",
&mut gutter.line_numbers,
|s| match s {
"on" | "relative" => Some(true),
"off" => Some(false),
_ => None,
},
);
if let Some(old_gutter) = current.editor.gutter.as_mut() {
if gutter.folds.is_some() {
old_gutter.folds = gutter.folds
}
if gutter.line_numbers.is_some() {
old_gutter.line_numbers = gutter.line_numbers
}
} else if gutter != settings::GutterContent::default() {
current.editor.gutter = Some(gutter)
}
if let Some(b) = vscode.read_bool("editor.scrollBeyondLastLine") {
current.editor.scroll_beyond_last_line = Some(if b {
ScrollBeyondLastLine::OnePage
} else {
ScrollBeyondLastLine::Off
})
}
let mut scrollbar_axes = settings::ScrollbarAxesContent::default();
vscode.enum_setting(
"editor.scrollbar.horizontal",
&mut scrollbar_axes.horizontal,
|s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
},
);
vscode.enum_setting(
"editor.scrollbar.vertical",
&mut scrollbar_axes.horizontal,
|s| match s {
"auto" | "visible" => Some(true),
"hidden" => Some(false),
_ => None,
},
);
if scrollbar_axes != settings::ScrollbarAxesContent::default() {
let scrollbar_settings = current.editor.scrollbar.get_or_insert_default();
let axes_settings = scrollbar_settings.axes.get_or_insert_default();
if let Some(vertical) = scrollbar_axes.vertical {
axes_settings.vertical = Some(vertical);
}
if let Some(horizontal) = scrollbar_axes.horizontal {
axes_settings.horizontal = Some(horizontal);
}
}
// TODO: check if this does the int->float conversion?
vscode.f32_setting(
"editor.cursorSurroundingLines",
&mut current.editor.vertical_scroll_margin,
);
vscode.f32_setting(
"editor.mouseWheelScrollSensitivity",
&mut current.editor.scroll_sensitivity,
);
vscode.f32_setting(
"editor.fastScrollSensitivity",
&mut current.editor.fast_scroll_sensitivity,
);
if Some("relative") == vscode.read_string("editor.lineNumbers") {
current.editor.relative_line_numbers = Some(true);
}
vscode.enum_setting(
"editor.find.seedSearchStringFromSelection",
&mut current.editor.seed_search_query_from_cursor,
|s| match s {
"always" => Some(SeedQuerySetting::Always),
"selection" => Some(SeedQuerySetting::Selection),
"never" => Some(SeedQuerySetting::Never),
_ => None,
},
);
vscode.bool_setting("search.smartCase", &mut current.editor.use_smartcase_search);
vscode.enum_setting(
"editor.multiCursorModifier",
&mut current.editor.multi_cursor_modifier,
|s| match s {
"ctrlCmd" => Some(MultiCursorModifier::CmdOrCtrl),
"alt" => Some(MultiCursorModifier::Alt),
_ => None,
},
);
vscode.bool_setting(
"editor.parameterHints.enabled",
&mut current.editor.auto_signature_help,
);
vscode.bool_setting(
"editor.parameterHints.enabled",
&mut current.editor.show_signature_help_after_edits,
);
if let Some(use_ignored) = vscode.read_bool("search.useIgnoreFiles") {
let search = current.editor.search.get_or_insert_default();
search.include_ignored = Some(use_ignored);
}
let mut minimap = settings::MinimapContent::default();
let minimap_enabled = vscode.read_bool("editor.minimap.enabled").unwrap_or(true);
let autohide = vscode.read_bool("editor.minimap.autohide");
let mut max_width_columns: Option<u32> = None;
vscode.u32_setting("editor.minimap.maxColumn", &mut max_width_columns);
if minimap_enabled {
if let Some(false) = autohide {
minimap.show = Some(ShowMinimap::Always);
} else {
minimap.show = Some(ShowMinimap::Auto);
}
} else {
minimap.show = Some(ShowMinimap::Never);
}
if let Some(max_width_columns) = max_width_columns {
minimap.max_width_columns = NonZeroU32::new(max_width_columns);
}
vscode.enum_setting(
"editor.minimap.showSlider",
&mut minimap.thumb,
|s| match s {
"always" => Some(MinimapThumb::Always),
"mouseover" => Some(MinimapThumb::Hover),
_ => None,
},
);
if minimap != settings::MinimapContent::default() {
current.editor.minimap = Some(minimap)
}
}
}