settings ui: Add numeric steppers to settings UI (#39491)
This PR adds the numeric stepper component to the settings ui and
implements some settings that rely on this component as well.
I also switched {buffer/ui}_font_weight to the `gpui::FontWeight` type
and added a manual implementation of the Schemars trait. This allows Zed
to send min, max, and default information to the JSON LSP when a user is
manually editing the settings file.
The numeric stepper elements added to the settings ui are below:
- ui font size
- ui font weight
- Buffer font size
- Buffer font weight
- Scroll sensitivity
- Fast scroll sensitivity
- Vertical scroll margin
- Horizontal scroll margin
- Inline blame padding
- Inline blame delay
- Inline blame min column
- Unnecessary code fade
- Tab Size
- Hover popover delay
Release Notes:
- N/A
This commit is contained in:
Generated
+2
@@ -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",
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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<f32> 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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,7 +57,8 @@ merge_from_overwrites!(
|
||||
gpui::SharedString,
|
||||
std::path::PathBuf,
|
||||
gpui::Modifiers,
|
||||
gpui::FontFeatures
|
||||
gpui::FontFeatures,
|
||||
gpui::FontWeight
|
||||
);
|
||||
|
||||
impl<T: Clone + MergeFrom> MergeFrom for Option<T> {
|
||||
|
||||
@@ -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<FontFeatures>,
|
||||
/// The weight of the UI font in CSS units from 100 to 900.
|
||||
#[serde(default)]
|
||||
pub ui_font_weight: Option<f32>,
|
||||
#[schemars(default = "default_buffer_font_weight")]
|
||||
pub ui_font_weight: Option<FontWeight>,
|
||||
/// The name of a font to use for rendering in text buffers.
|
||||
#[serde(default)]
|
||||
pub buffer_font_family: Option<FontFamilyName>,
|
||||
@@ -44,7 +45,8 @@ pub struct ThemeSettingsContent {
|
||||
pub buffer_font_size: Option<f32>,
|
||||
/// The weight of the editor font in CSS units from 100 to 900.
|
||||
#[serde(default)]
|
||||
pub buffer_font_weight: Option<f32>,
|
||||
#[schemars(default = "default_buffer_font_weight")]
|
||||
pub buffer_font_weight: Option<FontWeight>,
|
||||
/// The buffer's line height.
|
||||
#[serde(default)]
|
||||
pub buffer_line_height: Option<BufferLineHeight>,
|
||||
@@ -73,7 +75,8 @@ pub struct ThemeSettingsContent {
|
||||
|
||||
/// How much to fade out unused code.
|
||||
#[serde(default)]
|
||||
pub unnecessary_code_fade: Option<f32>,
|
||||
#[schemars(range(min = 0.0, max = 0.9))]
|
||||
pub unnecessary_code_fade: Option<CodeFade>,
|
||||
|
||||
/// EXPERIMENTAL: Overrides for the current theme.
|
||||
///
|
||||
@@ -88,6 +91,27 @@ pub struct ThemeSettingsContent {
|
||||
pub theme_overrides: HashMap<String, ThemeStyleContent>,
|
||||
}
|
||||
|
||||
#[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<FontFeatures> {
|
||||
Some(FontFeatures::default())
|
||||
}
|
||||
@@ -96,6 +120,10 @@ fn default_font_fallbacks() -> Option<FontFallbacks> {
|
||||
Some(FontFallbacks::default())
|
||||
}
|
||||
|
||||
fn default_buffer_font_weight() -> Option<FontWeight> {
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +125,12 @@ impl VsCodeSettings {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_f32_setting<T: From<f32>>(&self, key: &str, setting: &mut Option<T>) {
|
||||
if let Some(s) = self.content.get(key).and_then(Value::as_f64) {
|
||||
*setting = Some(T::from(s as f32))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enum_setting<T>(
|
||||
&self,
|
||||
key: &str,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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<SettingsPage> {
|
||||
}),
|
||||
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::ShowCloseButton>(|settings_field, file, _, window, cx| {
|
||||
render_dropdown(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<f32>(|settings_field, file, _, window, cx| {
|
||||
render_numeric_stepper(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<u32>(|settings_field, file, _, window, cx| {
|
||||
render_numeric_stepper(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<u64>(|settings_field, file, _, window, cx| {
|
||||
render_numeric_stepper(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<NonZeroU32>(|settings_field, file, _, window, cx| {
|
||||
render_numeric_stepper(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<CodeFade>(|settings_field, file, _, window, cx| {
|
||||
render_numeric_stepper(*settings_field, file, window, cx)
|
||||
})
|
||||
.add_renderer::<FontWeight>(|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<B: Into<bool> + From<bool> + Copy>(
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_numeric_stepper<T: NumericStepperType + Send + Sync>(
|
||||
field: SettingField<T>,
|
||||
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<T>(
|
||||
field: SettingField<T>,
|
||||
file: SettingsUiFile,
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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<Output = Self>
|
||||
+ Sub<Output = Self>
|
||||
+ 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<T = usize> {
|
||||
id: ElementId,
|
||||
@@ -281,7 +376,7 @@ impl<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
||||
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<T: NumericStepperType> RenderOnce for NumericStepper<T> {
|
||||
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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user