diff --git a/Cargo.lock b/Cargo.lock index 2cd005b41e0c5f68fb21688a04412c3e49747209..6992c5f9ec6f83118870bc0a1e1220eb96bd9fd5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14292,6 +14292,7 @@ name = "settings" version = "0.1.0" dependencies = [ "anyhow", + "derive_more", "ec4rs", "fs", "futures 0.3.31", @@ -14379,6 +14380,7 @@ dependencies = [ "strum 0.27.1", "theme", "ui", + "ui_input", "workspace", "workspace-hack", "zed-util", diff --git a/assets/settings/default.json b/assets/settings/default.json index 8669623f46e9273f777163f377c24ce1a17a6abd..81c25ef54066f257d31dcb5273ea1dd2496a4cad 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -1231,6 +1231,10 @@ // 2. Hide the gutter // "git_gutter": "hide" "git_gutter": "tracked_files", + /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter. + /// + /// Default: null + "gutter_debounce": null, // Control whether the git blame information is shown inline, // in the currently focused line. "inline_blame": { diff --git a/crates/gpui/src/text_system.rs b/crates/gpui/src/text_system.rs index 83fb4993e0ad69cd0d2ae034f79fdd6feaeb9b77..efac0087387e394e0e43859ea1dabdeb087d6b34 100644 --- a/crates/gpui/src/text_system.rs +++ b/crates/gpui/src/text_system.rs @@ -19,7 +19,7 @@ use crate::{ use anyhow::{Context as _, anyhow}; use collections::FxHashMap; use core::fmt; -use derive_more::Deref; +use derive_more::{Add, Deref, FromStr, Sub}; use itertools::Itertools; use parking_lot::{Mutex, RwLock, RwLockUpgradableReadGuard}; use smallvec::{SmallVec, smallvec}; @@ -605,9 +605,22 @@ impl DerefMut for LineWrapperHandle { /// The degree of blackness or stroke thickness of a font. This value ranges from 100.0 to 900.0, /// with 400.0 as normal. -#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Serialize, Deserialize, Add, Sub, FromStr)] +#[serde(transparent)] pub struct FontWeight(pub f32); +impl Display for FontWeight { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for FontWeight { + fn from(weight: f32) -> Self { + FontWeight(weight) + } +} + impl Default for FontWeight { #[inline] fn default() -> FontWeight { @@ -657,6 +670,23 @@ impl FontWeight { ]; } +impl schemars::JsonSchema for FontWeight { + fn schema_name() -> std::borrow::Cow<'static, str> { + "FontWeight".into() + } + + fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema { + use schemars::json_schema; + json_schema!({ + "type": "number", + "minimum": Self::THIN, + "maximum": Self::BLACK, + "default": Self::default(), + "description": "Font weight value between 100 (thin) and 900 (black)" + }) + } +} + /// Allows italic or oblique faces to be selected. #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash, Default, Serialize, Deserialize, JsonSchema)] pub enum FontStyle { diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index b5cf8a0acfd036767a84a9ac41afc178447ec9f2..cc2a39ce230ea0a8e8f9774cc2d8ee33d4e13037 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -18,6 +18,7 @@ test-support = ["gpui/test-support", "fs/test-support"] [dependencies] anyhow.workspace = true collections.workspace = true +derive_more.workspace = true ec4rs.workspace = true fs.workspace = true futures.workspace = true diff --git a/crates/settings/src/merge_from.rs b/crates/settings/src/merge_from.rs index 141ae0ce6d2280c390db80d34949c0d62cbffd02..6347f231f8d00f0dbe10b98d1172c4b14b105e98 100644 --- a/crates/settings/src/merge_from.rs +++ b/crates/settings/src/merge_from.rs @@ -57,7 +57,8 @@ merge_from_overwrites!( gpui::SharedString, std::path::PathBuf, gpui::Modifiers, - gpui::FontFeatures + gpui::FontFeatures, + gpui::FontWeight ); impl MergeFrom for Option { diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 737de357009ff44e391784ae162ea3bfe782fec7..9e02f23a29fb0ed79dd73b13ac820bc0760bdf4b 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; use settings_macros::MergeFrom; -use std::sync::Arc; +use std::{fmt::Display, sync::Arc}; use serde_with::skip_serializing_none; @@ -31,7 +31,8 @@ pub struct ThemeSettingsContent { pub ui_font_features: Option, /// The weight of the UI font in CSS units from 100 to 900. #[serde(default)] - pub ui_font_weight: Option, + #[schemars(default = "default_buffer_font_weight")] + pub ui_font_weight: Option, /// The name of a font to use for rendering in text buffers. #[serde(default)] pub buffer_font_family: Option, @@ -44,7 +45,8 @@ pub struct ThemeSettingsContent { pub buffer_font_size: Option, /// The weight of the editor font in CSS units from 100 to 900. #[serde(default)] - pub buffer_font_weight: Option, + #[schemars(default = "default_buffer_font_weight")] + pub buffer_font_weight: Option, /// The buffer's line height. #[serde(default)] pub buffer_line_height: Option, @@ -73,7 +75,8 @@ pub struct ThemeSettingsContent { /// How much to fade out unused code. #[serde(default)] - pub unnecessary_code_fade: Option, + #[schemars(range(min = 0.0, max = 0.9))] + pub unnecessary_code_fade: Option, /// EXPERIMENTAL: Overrides for the current theme. /// @@ -88,6 +91,27 @@ pub struct ThemeSettingsContent { pub theme_overrides: HashMap, } +#[derive( + Clone, + Copy, + Debug, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, + PartialEq, + PartialOrd, + derive_more::FromStr, +)] +#[serde(transparent)] +pub struct CodeFade(pub f32); + +impl Display for CodeFade { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.2}", self.0) + } +} + fn default_font_features() -> Option { Some(FontFeatures::default()) } @@ -96,6 +120,10 @@ fn default_font_fallbacks() -> Option { Some(FontFallbacks::default()) } +fn default_buffer_font_weight() -> Option { + Some(FontWeight::default()) +} + /// Represents the selection of a theme, which can be either static or dynamic. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(untagged)] @@ -1113,4 +1141,46 @@ mod tests { .contains("buffer_line_height.custom must be at least 1.0") ); } + + #[test] + fn test_buffer_font_weight_schema_has_default() { + use schemars::schema_for; + + let schema = schema_for!(ThemeSettingsContent); + let schema_value = serde_json::to_value(&schema).unwrap(); + + let properties = &schema_value["properties"]; + let buffer_font_weight = &properties["buffer_font_weight"]; + + assert!( + buffer_font_weight.get("default").is_some(), + "buffer_font_weight should have a default value in the schema" + ); + + let default_value = &buffer_font_weight["default"]; + assert_eq!( + default_value.as_f64(), + Some(FontWeight::NORMAL.0 as f64), + "buffer_font_weight default should be 400.0 (FontWeight::NORMAL)" + ); + + let defs = &schema_value["$defs"]; + let font_weight_def = &defs["FontWeight"]; + + assert_eq!( + font_weight_def["minimum"].as_f64(), + Some(FontWeight::THIN.0 as f64), + "FontWeight should have minimum of 100.0" + ); + assert_eq!( + font_weight_def["maximum"].as_f64(), + Some(FontWeight::BLACK.0 as f64), + "FontWeight should have maximum of 900.0" + ); + assert_eq!( + font_weight_def["default"].as_f64(), + Some(FontWeight::NORMAL.0 as f64), + "FontWeight should have default of 400.0" + ); + } } diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index e6e64093a7fd255a582e86c47c7617f695e7b94f..f7ef48b0d536fc1245b8648e357f32eaf85cb3ad 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -125,6 +125,12 @@ impl VsCodeSettings { } } + pub fn from_f32_setting>(&self, key: &str, setting: &mut Option) { + if let Some(s) = self.content.get(key).and_then(Value::as_f64) { + *setting = Some(T::from(s as f32)) + } + } + pub fn enum_setting( &self, key: &str, diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index b6a741d4dbfe00d44ef685bfe5e226a771669382..2839fac954a35456189a7a09a1460efbeb7cdff3 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -31,6 +31,7 @@ settings.workspace = true strum.workspace = true theme.workspace = true ui.workspace = true +ui_input.workspace = true util.workspace = true workspace-hack.workspace = true workspace.workspace = true diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 959455f3b298cc493116d7de5bd857ac8bc6b12f..7e2fea900878483a43f4a4d5c7d2f14c7c15d95a 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -5,19 +5,20 @@ use editor::{Editor, EditorEvent}; use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use fuzzy::StringMatchCandidate; use gpui::{ - App, AppContext as _, Context, Div, Entity, Global, IntoElement, ReadGlobal as _, Render, - ScrollHandle, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle, + App, AppContext as _, Context, Div, Entity, FontWeight, Global, IntoElement, ReadGlobal as _, + Render, ScrollHandle, Task, TitlebarOptions, UniformListScrollHandle, Window, WindowHandle, WindowOptions, actions, div, point, px, size, uniform_list, }; use project::WorktreeId; use settings::{ - BottomDockLayout, CloseWindowWhenNoItems, CursorShape, OnLastWindowClosed, + BottomDockLayout, CloseWindowWhenNoItems, CodeFade, CursorShape, OnLastWindowClosed, RestoreOnStartupBehavior, SaturatingBool, SettingsContent, SettingsStore, }; use std::{ any::{Any, TypeId, type_name}, cell::RefCell, collections::HashMap, + num::NonZeroU32, ops::Range, rc::Rc, sync::{Arc, atomic::AtomicBool}, @@ -26,6 +27,7 @@ use ui::{ ContextMenu, Divider, DropdownMenu, DropdownStyle, Switch, SwitchColor, TreeViewItem, prelude::*, }; +use ui_input::{NumericStepper, NumericStepperType}; use util::{ResultExt as _, paths::PathStyle, rel_path::RelPath}; use crate::components::SettingsEditor; @@ -367,25 +369,24 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): We need to implement a numeric stepper for these - // SettingsPageItem::SettingItem(SettingItem { - // title: "Buffer Font Size", - // description: "Font size for editor text", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.theme.buffer_font_size, - // pick_mut: |settings_content| &mut settings_content.theme.buffer_font_size, - // }), - // metadata: None, - // }), - // SettingsPageItem::SettingItem(SettingItem { - // title: "Buffer Font Weight", - // description: "Font weight for editor text (100-900)", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.theme.buffer_font_weight, - // pick_mut: |settings_content| &mut settings_content.theme.buffer_font_weight, - // }), - // metadata: None, - // }), + SettingsPageItem::SettingItem(SettingItem { + title: "Buffer Font Size", + description: "Font size for editor text", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.theme.buffer_font_size, + pick_mut: |settings_content| &mut settings_content.theme.buffer_font_size, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Buffer Font Weight", + description: "Font weight for editor text (100-900)", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.theme.buffer_font_weight, + pick_mut: |settings_content| &mut settings_content.theme.buffer_font_weight, + }), + metadata: None, + }), SettingsPageItem::SettingItem(SettingItem { title: "Buffer Line Height", description: "Line height for editor text", @@ -404,25 +405,24 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): We need to implement a numeric stepper for these - // SettingsPageItem::SettingItem(SettingItem { - // title: "UI Font Size", - // description: "Font size for UI elements", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.theme.ui_font_size, - // pick_mut: |settings_content| &mut settings_content.theme.ui_font_size, - // }), - // metadata: None, - // }), - // SettingsPageItem::SettingItem(SettingItem { - // title: "UI Font Weight", - // description: "Font weight for UI elements (100-900)", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.theme.ui_font_weight, - // pick_mut: |settings_content| &mut settings_content.theme.ui_font_weight, - // }), - // metadata: None, - // }), + SettingsPageItem::SettingItem(SettingItem { + title: "UI Font Size", + description: "Font size for UI elements", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.theme.ui_font_size, + pick_mut: |settings_content| &mut settings_content.theme.ui_font_size, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "UI Font Weight", + description: "Font weight for UI elements (100-900)", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.theme.ui_font_weight, + pick_mut: |settings_content| &mut settings_content.theme.ui_font_weight, + }), + metadata: None, + }), SettingsPageItem::SectionHeader("Keymap"), SettingsPageItem::SettingItem(SettingItem { title: "Base Keymap", @@ -493,16 +493,17 @@ fn user_settings_data() -> Vec { metadata: None, }), SettingsPageItem::SectionHeader("Highlighting"), - // todo(settings_ui): numeric stepper and validator is needed for this - // SettingsPageItem::SettingItem(SettingItem { - // title: "Unnecessary Code Fade", - // description: "How much to fade out unused code (0.0 - 0.9)", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.theme.unnecessary_code_fade, - // pick_mut: |settings_content| &mut settings_content.theme.unnecessary_code_fade, - // }), - // metadata: None, - // }), + SettingsPageItem::SettingItem(SettingItem { + title: "Unnecessary Code Fade", + description: "How much to fade out unused code (0.0 - 0.9)", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.theme.unnecessary_code_fade, + pick_mut: |settings_content| { + &mut settings_content.theme.unnecessary_code_fade + }, + }), + metadata: None, + }), SettingsPageItem::SettingItem(SettingItem { title: "Current Line Highlight", description: "How to highlight the current line", @@ -623,25 +624,41 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): Needs numeric stepper + // todo(settings_ui): Needs numeric stepper + option within an option // SettingsPageItem::SettingItem(SettingItem { // title: "Centered Layout Left Padding", - // description: "Left padding for cenetered layout", + // description: "Left padding for centered layout", // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.workspace.bottom_dock_layout, + // pick: |settings_content| { + // &settings_content.workspace.centered_layout.left_padding + // }, // pick_mut: |settings_content| { - // &mut settings_content.workspace.bottom_dock_layout + // &mut settings_content.workspace.centered_layout.left_padding // }, // }), // metadata: None, // }), // SettingsPageItem::SettingItem(SettingItem { // title: "Centered Layout Right Padding", - // description: "Right padding for cenetered layout", + // description: "Right padding for centered layout", // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.workspace.bottom_dock_layout, + // pick: |settings_content| { + // if let Some(centered_layout) = + // &settings_content.workspace.centered_layout + // { + // ¢ered_layout.right_padding + // } else { + // &None + // } + // }, // pick_mut: |settings_content| { - // &mut settings_content.workspace.bottom_dock_layout + // if let Some(mut centered_layout) = + // settings_content.workspace.centered_layout + // { + // &mut centered_layout.right_padding + // } else { + // &mut None + // } // }, // }), // metadata: None, @@ -664,15 +681,19 @@ fn user_settings_data() -> Vec { items: vec![ SettingsPageItem::SectionHeader("Indentation"), // todo(settings_ui): Needs numeric stepper - // SettingsPageItem::SettingItem(SettingItem { - // title: "Tab Size", - // description: "How many columns a tab should occupy", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.project.all_languages.defaults.tab_size, - // pick_mut: |settings_content| &mut settings_content.project.all_languages.defaults.tab_size, - // }), - // metadata: None, - // }), + SettingsPageItem::SettingItem(SettingItem { + title: "Tab Size", + description: "How many columns a tab should occupy", + field: Box::new(SettingField { + pick: |settings_content| { + &settings_content.project.all_languages.defaults.tab_size + }, + pick_mut: |settings_content| { + &mut settings_content.project.all_languages.defaults.tab_size + }, + }), + metadata: None, + }), SettingsPageItem::SettingItem(SettingItem { title: "Hard Tabs", description: "Whether to indent lines using tab characters, as opposed to multiple spaces", @@ -837,36 +858,50 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): Needs numeric stepper - // SettingsPageItem::SettingItem(SettingItem { - // title: "Vertical Scroll Margin", - // description: "The number of lines to keep above/below the cursor when auto-scrolling", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.editor.vertical_scroll_margin, - // pick_mut: |settings_content| &mut settings_content.editor.vertical_scroll_margin, - // }), - // metadata: None, - // }), - // todo(settings_ui): Needs numeric stepper - // SettingsPageItem::SettingItem(SettingItem { - // title: "Horizontal Scroll Margin", - // description: "The number of characters to keep on either side when scrolling with the mouse", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.editor.horizontal_scroll_margin, - // pick_mut: |settings_content| &mut settings_content.editor.horizontal_scroll_margin, - // }), - // metadata: None, - // }), - // todo(settings_ui): Needs numeric stepper - // SettingsPageItem::SettingItem(SettingItem { - // title: "Scroll Sensitivity", - // description: "Scroll sensitivity multiplier", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.editor.scroll_sensitivity, - // pick_mut: |settings_content| &mut settings_content.editor.scroll_sensitivity, - // }), - // metadata: None, - // }), + SettingsPageItem::SettingItem(SettingItem { + title: "Vertical Scroll Margin", + description: "The number of lines to keep above/below the cursor when auto-scrolling", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.editor.vertical_scroll_margin, + pick_mut: |settings_content| { + &mut settings_content.editor.vertical_scroll_margin + }, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Horizontal Scroll Margin", + description: "The number of characters to keep on either side when scrolling with the mouse", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.editor.horizontal_scroll_margin, + pick_mut: |settings_content| { + &mut settings_content.editor.horizontal_scroll_margin + }, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Scroll Sensitivity", + description: "Scroll sensitivity multiplier for both horizontal and vertical scrolling", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.editor.scroll_sensitivity, + pick_mut: |settings_content| { + &mut settings_content.editor.scroll_sensitivity + }, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Fast Scroll Sensitivity", + description: "Fast Scroll sensitivity multiplier for both horizontal and vertical scrolling", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.editor.fast_scroll_sensitivity, + pick_mut: |settings_content| { + &mut settings_content.editor.fast_scroll_sensitivity + }, + }), + metadata: None, + }), SettingsPageItem::SettingItem(SettingItem { title: "Autoscroll On Clicks", description: "Whether to scroll when clicking near the edge of the visible text area", @@ -1117,16 +1152,18 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): Needs numeric stepper - // SettingsPageItem::SettingItem(SettingItem { - // title: "Hover Popover Delay", - // description: "Time to wait in milliseconds before showing the informational hover box", - // field: Box::new(SettingField { - // pick: |settings_content| &settings_content.editor.hover_popover_delay, - // pick_mut: |settings_content| &mut settings_content.editor.hover_popover_delay, - // }), - // metadata: None, - // }), + // todo(settings ui): add units to this numeric stepper + SettingsPageItem::SettingItem(SettingItem { + title: "Hover Popover Delay", + description: "Time to wait in milliseconds before showing the informational hover box", + field: Box::new(SettingField { + pick: |settings_content| &settings_content.editor.hover_popover_delay, + pick_mut: |settings_content| { + &mut settings_content.editor.hover_popover_delay + }, + }), + metadata: None, + }), SettingsPageItem::SectionHeader("Code Actions"), SettingsPageItem::SettingItem(SettingItem { title: "Inline Code Actions", @@ -1713,7 +1750,7 @@ fn user_settings_data() -> Vec { }), metadata: None, }), - // todo(settings_ui): Needs numeric stepper + // todo(settings_ui): Figure out the right default for this value in default.json // SettingsPageItem::SettingItem(SettingItem { // title: "Gutter Debounce", // description: "Debounce threshold in milliseconds after which changes are reflected in the git gutter", @@ -1757,6 +1794,84 @@ fn user_settings_data() -> Vec { }), metadata: None, }), + SettingsPageItem::SettingItem(SettingItem { + title: "Inline Blame Delay", + description: "The delay after which the inline blame information is shown", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(git) = &settings_content.git { + if let Some(inline_blame) = &git.inline_blame { + &inline_blame.delay_ms + } else { + &None + } + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .delay_ms + }, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Inline Blame Padding", + description: "Padding between the end of the source line and the start of the inline blame in columns", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(git) = &settings_content.git { + if let Some(inline_blame) = &git.inline_blame { + &inline_blame.padding + } else { + &None + } + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .padding + }, + }), + metadata: None, + }), + SettingsPageItem::SettingItem(SettingItem { + title: "Inline Blame Min Column", + description: "The minimum column number to show the inline blame information at", + field: Box::new(SettingField { + pick: |settings_content| { + if let Some(git) = &settings_content.git { + if let Some(inline_blame) = &git.inline_blame { + &inline_blame.min_column + } else { + &None + } + } else { + &None + } + }, + pick_mut: |settings_content| { + &mut settings_content + .git + .get_or_insert_default() + .inline_blame + .get_or_insert_default() + .min_column + }, + }), + metadata: None, + }), SettingsPageItem::SettingItem(SettingItem { title: "Show Commit Summary", description: "Whether to show commit summary as part of the inline blame", @@ -2722,6 +2837,24 @@ fn init_renderers(cx: &mut App) { }) .add_renderer::(|settings_field, file, _, window, cx| { render_dropdown(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) + }) + .add_renderer::(|settings_field, file, _, window, cx| { + render_numeric_stepper(*settings_field, file, window, cx) }); // todo(settings_ui): Figure out how we want to handle discriminant unions @@ -3571,6 +3704,27 @@ fn render_toggle_button + From + Copy>( .into_any_element() } +fn render_numeric_stepper( + field: SettingField, + file: SettingsUiFile, + window: &mut Window, + cx: &mut App, +) -> AnyElement { + let (_, &value) = SettingsStore::global(cx).get_value_from_file(file.to_settings(), field.pick); + + NumericStepper::new("numeric_stepper", value, window, cx) + .on_change({ + move |value, _window, cx| { + let value = *value; + update_settings_file(file.clone(), cx, move |settings, _cx| { + *(field.pick_mut)(settings) = Some(value); + }) + .log_err(); // todo(settings_ui) don't log err + } + }) + .into_any_element() +} + fn render_dropdown( field: SettingField, file: SettingsUiFile, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index c26fe0339a226a61a57365dfcee3cf360ccaf458..83cd7f9f2e55bc0a510d8653f397fbfc994c18e7 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -817,7 +817,7 @@ impl settings::Settings for ThemeSettings { family: content.ui_font_family.as_ref().unwrap().0.clone().into(), features: content.ui_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()), - weight: clamp_font_weight(content.ui_font_weight.unwrap()), + weight: clamp_font_weight(content.ui_font_weight.unwrap().0), style: Default::default(), }, buffer_font: Font { @@ -830,7 +830,7 @@ impl settings::Settings for ThemeSettings { .into(), features: content.buffer_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()), - weight: clamp_font_weight(content.buffer_font_weight.unwrap()), + weight: clamp_font_weight(content.buffer_font_weight.unwrap().0), style: FontStyle::default(), }, buffer_font_size: clamp_font_size(content.buffer_font_size.unwrap().into()), @@ -850,15 +850,15 @@ impl settings::Settings for ThemeSettings { .unwrap(), icon_theme_selection: Some(icon_theme_selection), ui_density: content.ui_density.unwrap_or_default().into(), - unnecessary_code_fade: content.unnecessary_code_fade.unwrap().clamp(0.0, 0.9), + unnecessary_code_fade: content.unnecessary_code_fade.unwrap().0.clamp(0.0, 0.9), }; this.apply_theme_overrides(); this } fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { - vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight); - vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size); + vscode.from_f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight); + vscode.from_f32_setting("editor.fontSize", &mut current.theme.buffer_font_size); if let Some(font) = vscode.read_string("editor.font") { current.theme.buffer_font_family = Some(FontFamilyName(font.into())); } diff --git a/crates/ui_input/src/numeric_stepper.rs b/crates/ui_input/src/numeric_stepper.rs index 7a5d66422fa0a6d74dbaa05ea96dedcdf3292111..fe50f7bc5944599e58da685bb07721f171da29cb 100644 --- a/crates/ui_input/src/numeric_stepper.rs +++ b/crates/ui_input/src/numeric_stepper.rs @@ -1,13 +1,14 @@ use std::{ fmt::Display, - ops::{Add, Sub}, + num::{NonZeroU32, NonZeroU64}, rc::Rc, str::FromStr, }; use editor::{Editor, EditorStyle}; -use gpui::{ClickEvent, Entity, FocusHandle, Focusable, Modifiers}; +use gpui::{ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers}; +use settings::CodeFade; use ui::{IconButtonShape, prelude::*}; #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] @@ -25,15 +26,7 @@ pub enum NumericStepperMode { } pub trait NumericStepperType: - Display - + Add - + Sub - + Copy - + Clone - + Sized - + PartialOrd - + FromStr - + 'static + Display + Copy + Clone + Sized + PartialOrd + FromStr + 'static { fn default_format(value: &Self) -> String { format!("{}", value) @@ -43,6 +36,56 @@ pub trait NumericStepperType: fn small_step() -> Self; fn min_value() -> Self; fn max_value() -> Self; + fn saturating_add(self, rhs: Self) -> Self; + fn saturating_sub(self, rhs: Self) -> Self; +} + +impl NumericStepperType for gpui::FontWeight { + fn default_step() -> Self { + FontWeight(10.0) + } + fn large_step() -> Self { + FontWeight(50.0) + } + fn small_step() -> Self { + FontWeight(5.0) + } + fn min_value() -> Self { + gpui::FontWeight::THIN + } + fn max_value() -> Self { + gpui::FontWeight::BLACK + } + fn saturating_add(self, rhs: Self) -> Self { + FontWeight((self.0 + rhs.0).min(Self::max_value().0)) + } + fn saturating_sub(self, rhs: Self) -> Self { + FontWeight((self.0 - rhs.0).max(Self::min_value().0)) + } +} + +impl NumericStepperType for settings::CodeFade { + fn default_step() -> Self { + CodeFade(0.10) + } + fn large_step() -> Self { + CodeFade(0.20) + } + fn small_step() -> Self { + CodeFade(0.05) + } + fn min_value() -> Self { + CodeFade(0.0) + } + fn max_value() -> Self { + CodeFade(0.9) + } + fn saturating_add(self, rhs: Self) -> Self { + CodeFade((self.0 + rhs.0).min(Self::max_value().0)) + } + fn saturating_sub(self, rhs: Self) -> Self { + CodeFade((self.0 - rhs.0).max(Self::min_value().0)) + } } macro_rules! impl_numeric_stepper_int { @@ -67,6 +110,50 @@ macro_rules! impl_numeric_stepper_int { fn max_value() -> Self { <$type>::MAX } + + fn saturating_add(self, rhs: Self) -> Self { + self.saturating_add(rhs) + } + + fn saturating_sub(self, rhs: Self) -> Self { + self.saturating_sub(rhs) + } + } + }; +} + +macro_rules! impl_numeric_stepper_nonzero_int { + ($nonzero:ty, $inner:ty) => { + impl NumericStepperType for $nonzero { + fn default_step() -> Self { + <$nonzero>::new(1).unwrap() + } + + fn large_step() -> Self { + <$nonzero>::new(10).unwrap() + } + + fn small_step() -> Self { + <$nonzero>::new(1).unwrap() + } + + fn min_value() -> Self { + <$nonzero>::MIN + } + + fn max_value() -> Self { + <$nonzero>::MAX + } + + fn saturating_add(self, rhs: Self) -> Self { + let result = self.get().saturating_add(rhs.get()); + <$nonzero>::new(result.max(1)).unwrap() + } + + fn saturating_sub(self, rhs: Self) -> Self { + let result = self.get().saturating_sub(rhs.get()).max(1); + <$nonzero>::new(result).unwrap() + } } }; } @@ -75,10 +162,7 @@ macro_rules! impl_numeric_stepper_float { ($type:ident) => { impl NumericStepperType for $type { fn default_format(value: &Self) -> String { - format!("{:^4}", value) - .trim_end_matches('0') - .trim_end_matches('.') - .to_string() + format!("{:.2}", value) } fn default_step() -> Self { @@ -100,6 +184,14 @@ macro_rules! impl_numeric_stepper_float { fn max_value() -> Self { <$type>::MAX } + + fn saturating_add(self, rhs: Self) -> Self { + (self + rhs).clamp(Self::min_value(), Self::max_value()) + } + + fn saturating_sub(self, rhs: Self) -> Self { + (self - rhs).clamp(Self::min_value(), Self::max_value()) + } } }; } @@ -113,6 +205,9 @@ impl_numeric_stepper_int!(u32); impl_numeric_stepper_int!(i64); impl_numeric_stepper_int!(u64); +impl_numeric_stepper_nonzero_int!(NonZeroU32, u32); +impl_numeric_stepper_nonzero_int!(NonZeroU64, u64); + #[derive(RegisterComponent)] pub struct NumericStepper { id: ElementId, @@ -281,7 +376,7 @@ impl RenderOnce for NumericStepper { let min = self.min_value; move |click: &ClickEvent, window: &mut Window, cx: &mut App| { let step = get_step(click.modifiers()); - let new_value = value - step; + let new_value = value.saturating_sub(step); let new_value = if new_value < min { min } else { new_value }; on_change(&new_value, window, cx); window.focus_prev(); @@ -410,7 +505,7 @@ impl RenderOnce for NumericStepper { let max = self.max_value; move |click: &ClickEvent, window: &mut Window, cx: &mut App| { let step = get_step(click.modifiers()); - let new_value = value + step; + let new_value = value.saturating_add(step); let new_value = if new_value > max { max } else { new_value }; on_change(&new_value, window, cx); }