Add DelayMs type for settings (#40659)

Julia Ryan created

Closes #40610

Release Notes:

- N/A

Change summary

crates/diagnostics/src/diagnostics_tests.rs       |   2 
crates/editor/src/editor.rs                       |   4 
crates/editor/src/editor_settings.rs              |   8 
crates/editor/src/element.rs                      |  10 +
crates/editor/src/hover_popover.rs                |   6 
crates/project/src/project_settings.rs            |  12 
crates/settings/src/settings_content.rs           |  30 +++
crates/settings/src/settings_content/editor.rs    |  20 ++
crates/settings/src/settings_content/project.rs   |   9 
crates/settings/src/settings_content/theme.rs     |   6 
crates/settings/src/settings_content/workspace.rs |   6 
crates/settings/src/vscode_import.rs              |   5 
crates/settings_ui/src/settings_ui.rs             |   1 
crates/ui_input/src/number_field.rs               | 129 ++++------------
crates/workspace/src/item.rs                      |   2 
crates/workspace/src/workspace.rs                 |   5 
16 files changed, 130 insertions(+), 125 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics_tests.rs 🔗

@@ -1341,7 +1341,7 @@ async fn test_hover_diagnostic_and_info_popovers(cx: &mut gpui::TestAppContext)
             range: Some(range),
         }))
     });
-    let delay = cx.update(|_, cx| EditorSettings::get_global(cx).hover_popover_delay + 1);
+    let delay = cx.update(|_, cx| EditorSettings::get_global(cx).hover_popover_delay.0 + 1);
     cx.background_executor
         .advance_clock(Duration::from_millis(delay));
 

crates/editor/src/editor.rs 🔗

@@ -6772,7 +6772,7 @@ impl Editor {
         if let Some(state) = &mut self.inline_blame_popover {
             state.hide_task.take();
         } else {
-            let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
+            let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
             let blame_entry = blame_entry.clone();
             let show_task = cx.spawn(async move |editor, cx| {
                 if !ignore_timeout {
@@ -6863,7 +6863,7 @@ impl Editor {
             return None;
         }
 
-        let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce;
+        let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
         self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
             cx.background_executor()
                 .timer(Duration::from_millis(debounce))

crates/editor/src/editor_settings.rs 🔗

@@ -5,7 +5,7 @@ use language::CursorShape;
 use project::project_settings::DiagnosticSeverity;
 use settings::Settings;
 pub use settings::{
-    CurrentLineHighlight, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
+    CurrentLineHighlight, DelayMs, DisplayIn, DocumentColorsRenderMode, DoubleClickInMultibuffer,
     GoToDefinitionFallback, HideMouseMode, MinimapThumb, MinimapThumbBorder, MultiCursorModifier,
     ScrollBeyondLastLine, ScrollbarDiagnostics, SeedQuerySetting, ShowMinimap, SnippetSortOrder,
 };
@@ -20,9 +20,9 @@ pub struct EditorSettings {
     pub current_line_highlight: CurrentLineHighlight,
     pub selection_highlight: bool,
     pub rounded_selection: bool,
-    pub lsp_highlight_debounce: u64,
+    pub lsp_highlight_debounce: DelayMs,
     pub hover_popover_enabled: bool,
-    pub hover_popover_delay: u64,
+    pub hover_popover_delay: DelayMs,
     pub toolbar: Toolbar,
     pub scrollbar: Scrollbar,
     pub minimap: Minimap,
@@ -147,7 +147,7 @@ pub struct DragAndDropSelection {
     /// 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,
+    pub delay: DelayMs,
 }
 
 /// Default options for buffer and project search items.

crates/editor/src/element.rs 🔗

@@ -1070,7 +1070,10 @@ impl EditorElement {
                     ref mouse_down_time,
                 } => {
                     let drag_and_drop_delay = Duration::from_millis(
-                        EditorSettings::get_global(cx).drag_and_drop_selection.delay,
+                        EditorSettings::get_global(cx)
+                            .drag_and_drop_selection
+                            .delay
+                            .0,
                     );
                     if mouse_down_time.elapsed() >= drag_and_drop_delay {
                         let drop_cursor = Selection {
@@ -6172,7 +6175,10 @@ impl EditorElement {
                 } = &editor.selection_drag_state
                 {
                     let drag_and_drop_delay = Duration::from_millis(
-                        EditorSettings::get_global(cx).drag_and_drop_selection.delay,
+                        EditorSettings::get_global(cx)
+                            .drag_and_drop_selection
+                            .delay
+                            .0,
                     );
                     if mouse_down_time.elapsed() >= drag_and_drop_delay {
                         window.set_cursor_style(

crates/editor/src/hover_popover.rs 🔗

@@ -154,7 +154,7 @@ pub fn hover_at_inlay(
             hide_hover(editor, cx);
         }
 
-        let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
+        let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
 
         let task = cx.spawn_in(window, async move |this, cx| {
             async move {
@@ -275,7 +275,7 @@ fn show_hover(
         return None;
     }
 
-    let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay;
+    let hover_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
     let all_diagnostics_active = editor.active_diagnostics == ActiveDiagnostic::All;
     let active_group_id = if let ActiveDiagnostic::Group(group) = &editor.active_diagnostics {
         Some(group.group_id)
@@ -1004,7 +1004,7 @@ mod tests {
     use text::Bias;
 
     fn get_hover_popover_delay(cx: &gpui::TestAppContext) -> u64 {
-        cx.read(|cx: &App| -> u64 { EditorSettings::get_global(cx).hover_popover_delay })
+        cx.read(|cx: &App| -> u64 { EditorSettings::get_global(cx).hover_popover_delay.0 })
     }
 
     impl InfoPopover {

crates/project/src/project_settings.rs 🔗

@@ -331,7 +331,7 @@ pub struct InlineBlameSettings {
     /// after a delay once the cursor stops moving.
     ///
     /// Default: 0
-    pub delay_ms: std::time::Duration,
+    pub delay_ms: settings::DelayMs,
     /// The amount of padding between the end of the source line and the start
     /// of the inline blame in units of columns.
     ///
@@ -357,8 +357,8 @@ pub struct BlameSettings {
 
 impl GitSettings {
     pub fn inline_blame_delay(&self) -> Option<Duration> {
-        if self.inline_blame.delay_ms.as_millis() > 0 {
-            Some(self.inline_blame.delay_ms)
+        if self.inline_blame.delay_ms.0 > 0 {
+            Some(Duration::from_millis(self.inline_blame.delay_ms.0))
         } else {
             None
         }
@@ -452,7 +452,7 @@ impl Settings for ProjectSettings {
                 let inline = git.inline_blame.unwrap();
                 InlineBlameSettings {
                     enabled: inline.enabled.unwrap(),
-                    delay_ms: std::time::Duration::from_millis(inline.delay_ms.unwrap()),
+                    delay_ms: inline.delay_ms.unwrap(),
                     padding: inline.padding.unwrap(),
                     min_column: inline.min_column.unwrap(),
                     show_commit_summary: inline.show_commit_summary.unwrap(),
@@ -504,11 +504,11 @@ impl Settings for ProjectSettings {
                 include_warnings: diagnostics.include_warnings.unwrap(),
                 lsp_pull_diagnostics: LspPullDiagnosticsSettings {
                     enabled: lsp_pull_diagnostics.enabled.unwrap(),
-                    debounce_ms: lsp_pull_diagnostics.debounce_ms.unwrap(),
+                    debounce_ms: lsp_pull_diagnostics.debounce_ms.unwrap().0,
                 },
                 inline: InlineDiagnosticsSettings {
                     enabled: inline_diagnostics.enabled.unwrap(),
-                    update_debounce_ms: inline_diagnostics.update_debounce_ms.unwrap(),
+                    update_debounce_ms: inline_diagnostics.update_debounce_ms.unwrap().0,
                     padding: inline_diagnostics.padding.unwrap(),
                     min_column: inline_diagnostics.min_column.unwrap(),
                     max_severity: inline_diagnostics.max_severity.map(Into::into),

crates/settings/src/settings_content.rs 🔗

@@ -985,3 +985,33 @@ impl merge_from::MergeFrom for SaturatingBool {
         self.0 |= other.0
     }
 }
+
+#[derive(
+    Copy,
+    Clone,
+    Default,
+    Debug,
+    PartialEq,
+    Eq,
+    PartialOrd,
+    Ord,
+    Serialize,
+    Deserialize,
+    MergeFrom,
+    JsonSchema,
+    derive_more::FromStr,
+)]
+#[serde(transparent)]
+pub struct DelayMs(pub u64);
+
+impl From<u64> for DelayMs {
+    fn from(n: u64) -> Self {
+        Self(n)
+    }
+}
+
+impl std::fmt::Display for DelayMs {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}ms", self.0)
+    }
+}

crates/settings/src/settings_content/editor.rs 🔗

@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use settings_macros::MergeFrom;
 
-use crate::{DiagnosticSeverityContent, ShowScrollbar};
+use crate::{DelayMs, DiagnosticSeverityContent, ShowScrollbar};
 
 #[skip_serializing_none]
 #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
@@ -45,7 +45,7 @@ pub struct EditorSettingsContent {
     /// server based on the current cursor location.
     ///
     /// Default: 75
-    pub lsp_highlight_debounce: Option<u64>,
+    pub lsp_highlight_debounce: Option<DelayMs>,
     /// Whether to show the informational hover box when moving the mouse
     /// over symbols in the editor.
     ///
@@ -54,7 +54,7 @@ pub struct EditorSettingsContent {
     /// Time to wait in milliseconds before showing the informational hover box.
     ///
     /// Default: 300
-    pub hover_popover_delay: Option<u64>,
+    pub hover_popover_delay: Option<DelayMs>,
     /// Toolbar related settings
     pub toolbar: Option<ToolbarContent>,
     /// Scrollbar related settings
@@ -722,7 +722,7 @@ pub struct DragAndDropSelectionContent {
     /// The delay in milliseconds that must elapse before drag and drop is allowed. Otherwise, a new text selection is created.
     ///
     /// Default: 300
-    pub delay: Option<u64>,
+    pub delay: Option<DelayMs>,
 }
 
 /// When to show the minimap in the editor.
@@ -804,6 +804,12 @@ impl Display for MinimumContrast {
     }
 }
 
+impl From<f32> for MinimumContrast {
+    fn from(x: f32) -> Self {
+        Self(x)
+    }
+}
+
 /// Opacity of the inactive panes. 0 means transparent, 1 means opaque.
 ///
 /// Valid range: 0.0 to 1.0
@@ -828,3 +834,9 @@ impl Display for InactiveOpacity {
         write!(f, "{:.1}", self.0)
     }
 }
+
+impl From<f32> for InactiveOpacity {
+    fn from(x: f32) -> Self {
+        Self(x)
+    }
+}

crates/settings/src/settings_content/project.rs 🔗

@@ -8,7 +8,8 @@ use settings_macros::MergeFrom;
 use util::serde::default_true;
 
 use crate::{
-    AllLanguageSettingsContent, ExtendingVec, ProjectTerminalSettingsContent, SlashCommandSettings,
+    AllLanguageSettingsContent, DelayMs, ExtendingVec, ProjectTerminalSettingsContent,
+    SlashCommandSettings,
 };
 
 #[skip_serializing_none]
@@ -310,7 +311,7 @@ pub struct InlineBlameSettings {
     /// after a delay once the cursor stops moving.
     ///
     /// Default: 0
-    pub delay_ms: Option<u64>,
+    pub delay_ms: Option<DelayMs>,
     /// The amount of padding between the end of the source line and the start
     /// of the inline blame in units of columns.
     ///
@@ -397,7 +398,7 @@ pub struct LspPullDiagnosticsSettingsContent {
     /// 0 turns the debounce off.
     ///
     /// Default: 50
-    pub debounce_ms: Option<u64>,
+    pub debounce_ms: Option<DelayMs>,
 }
 
 #[skip_serializing_none]
@@ -413,7 +414,7 @@ pub struct InlineDiagnosticsSettingsContent {
     /// last editor event.
     ///
     /// Default: 150
-    pub update_debounce_ms: Option<u64>,
+    pub update_debounce_ms: Option<DelayMs>,
     /// The amount of padding between the end of the source line and the start
     /// of the inline diagnostic in units of columns.
     ///

crates/settings/src/settings_content/theme.rs 🔗

@@ -112,6 +112,12 @@ impl Display for CodeFade {
     }
 }
 
+impl From<f32> for CodeFade {
+    fn from(x: f32) -> Self {
+        Self(x)
+    }
+}
+
 fn default_font_features() -> Option<FontFeatures> {
     Some(FontFeatures::default())
 }

crates/settings/src/settings_content/workspace.rs 🔗

@@ -6,7 +6,9 @@ use serde::{Deserialize, Serialize};
 use serde_with::skip_serializing_none;
 use settings_macros::MergeFrom;
 
-use crate::{DockPosition, DockSide, InactiveOpacity, ScrollbarSettingsContent, ShowIndentGuides};
+use crate::{
+    DelayMs, DockPosition, DockSide, InactiveOpacity, ScrollbarSettingsContent, ShowIndentGuides,
+};
 
 #[skip_serializing_none]
 #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
@@ -386,7 +388,7 @@ pub enum AutosaveSetting {
     /// Disable autosave.
     Off,
     /// Save after inactivity period of `milliseconds`.
-    AfterDelay { milliseconds: u64 },
+    AfterDelay { milliseconds: DelayMs },
     /// Autosave when focus changes.
     OnFocusChange,
     /// Autosave when the active window changes.

crates/settings/src/vscode_import.rs 🔗

@@ -259,7 +259,7 @@ impl VsCodeSettings {
             gutter: self.gutter_content(),
             hide_mouse: None,
             horizontal_scroll_margin: None,
-            hover_popover_delay: self.read_u64("editor.hover.delay"),
+            hover_popover_delay: self.read_u64("editor.hover.delay").map(Into::into),
             hover_popover_enabled: self.read_bool("editor.hover.enabled"),
             inline_code_actions: None,
             jupyter: None,
@@ -791,7 +791,8 @@ impl VsCodeSettings {
                     milliseconds: self
                         .read_value("files.autoSaveDelay")
                         .and_then(|v| v.as_u64())
-                        .unwrap_or(1000),
+                        .unwrap_or(1000)
+                        .into(),
                 }),
                 "onFocusChange" => Some(AutosaveSetting::OnFocusChange),
                 "onWindowChange" => Some(AutosaveSetting::OnWindowChange),

crates/settings_ui/src/settings_ui.rs 🔗

@@ -417,6 +417,7 @@ fn init_renderers(cx: &mut App) {
         .add_basic_renderer::<NonZero<usize>>(render_number_field)
         .add_basic_renderer::<NonZeroU32>(render_number_field)
         .add_basic_renderer::<settings::CodeFade>(render_number_field)
+        .add_basic_renderer::<settings::DelayMs>(render_number_field)
         .add_basic_renderer::<gpui::FontWeight>(render_number_field)
         .add_basic_renderer::<settings::InactiveOpacity>(render_number_field)
         .add_basic_renderer::<settings::MinimumContrast>(render_number_field)

crates/ui_input/src/number_field.rs 🔗

@@ -8,7 +8,7 @@ use std::{
 use editor::{Editor, EditorStyle};
 use gpui::{ClickEvent, Entity, FocusHandle, Focusable, FontWeight, Modifiers};
 
-use settings::{CodeFade, InactiveOpacity, MinimumContrast};
+use settings::{CodeFade, DelayMs, InactiveOpacity, MinimumContrast};
 use ui::prelude::*;
 
 #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
@@ -31,102 +31,47 @@ pub trait NumberFieldType: Display + Copy + Clone + Sized + PartialOrd + FromStr
     fn saturating_sub(self, rhs: Self) -> Self;
 }
 
-impl NumberFieldType for gpui::FontWeight {
-    fn default_step() -> Self {
-        FontWeight(50.0)
-    }
-    fn large_step() -> Self {
-        FontWeight(100.0)
-    }
-    fn small_step() -> Self {
-        FontWeight(10.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))
-    }
-}
+macro_rules! impl_newtype_numeric_stepper {
+    ($type:ident, $default:expr, $large:expr, $small:expr, $min:expr, $max:expr) => {
+        impl NumberFieldType for $type {
+            fn default_step() -> Self {
+                $default.into()
+            }
 
-impl NumberFieldType 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))
-    }
-}
+            fn large_step() -> Self {
+                $large.into()
+            }
 
-impl NumberFieldType for settings::InactiveOpacity {
-    fn default_step() -> Self {
-        InactiveOpacity(0.10)
-    }
-    fn large_step() -> Self {
-        InactiveOpacity(0.20)
-    }
-    fn small_step() -> Self {
-        InactiveOpacity(0.05)
-    }
-    fn min_value() -> Self {
-        InactiveOpacity(0.0)
-    }
-    fn max_value() -> Self {
-        InactiveOpacity(1.0)
-    }
-    fn saturating_add(self, rhs: Self) -> Self {
-        InactiveOpacity((self.0 + rhs.0).min(Self::max_value().0))
-    }
-    fn saturating_sub(self, rhs: Self) -> Self {
-        InactiveOpacity((self.0 - rhs.0).max(Self::min_value().0))
-    }
-}
+            fn small_step() -> Self {
+                $small.into()
+            }
 
-impl NumberFieldType for settings::MinimumContrast {
-    fn default_step() -> Self {
-        MinimumContrast(1.0)
-    }
-    fn large_step() -> Self {
-        MinimumContrast(10.0)
-    }
-    fn small_step() -> Self {
-        MinimumContrast(0.5)
-    }
-    fn min_value() -> Self {
-        MinimumContrast(0.0)
-    }
-    fn max_value() -> Self {
-        MinimumContrast(106.0)
-    }
-    fn saturating_add(self, rhs: Self) -> Self {
-        MinimumContrast((self.0 + rhs.0).min(Self::max_value().0))
-    }
-    fn saturating_sub(self, rhs: Self) -> Self {
-        MinimumContrast((self.0 - rhs.0).max(Self::min_value().0))
-    }
+            fn min_value() -> Self {
+                $min.into()
+            }
+
+            fn max_value() -> Self {
+                $max.into()
+            }
+
+            fn saturating_add(self, rhs: Self) -> Self {
+                $type((self.0 + rhs.0).min(Self::max_value().0))
+            }
+
+            fn saturating_sub(self, rhs: Self) -> Self {
+                $type((self.0 - rhs.0).max(Self::min_value().0))
+            }
+        }
+    };
 }
 
+#[rustfmt::skip]
+impl_newtype_numeric_stepper!(FontWeight, 50., 100., 10., FontWeight::THIN, FontWeight::BLACK);
+impl_newtype_numeric_stepper!(CodeFade, 0.1, 0.2, 0.05, 0.0, 0.9);
+impl_newtype_numeric_stepper!(InactiveOpacity, 0.1, 0.2, 0.05, 0.0, 1.0);
+impl_newtype_numeric_stepper!(MinimumContrast, 1., 10., 0.5, 0.0, 106.0);
+impl_newtype_numeric_stepper!(DelayMs, 100, 500, 10, 0, 2000);
+
 macro_rules! impl_numeric_stepper_int {
     ($type:ident) => {
         impl NumberFieldType for $type {

crates/workspace/src/item.rs 🔗

@@ -811,7 +811,7 @@ impl<T: Item> ItemHandle for Entity<T> {
                             let autosave = item.workspace_settings(cx).autosave;
 
                             if let AutosaveSetting::AfterDelay { milliseconds } = autosave {
-                                let delay = Duration::from_millis(milliseconds);
+                                let delay = Duration::from_millis(milliseconds.0);
                                 let item = item.clone();
                                 pending_autosave.fire_new(
                                     delay,

crates/workspace/src/workspace.rs 🔗

@@ -8799,8 +8799,9 @@ mod tests {
         item.update(cx, |item, cx| {
             SettingsStore::update_global(cx, |settings, cx| {
                 settings.update_user_settings(cx, |settings| {
-                    settings.workspace.autosave =
-                        Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+                    settings.workspace.autosave = Some(AutosaveSetting::AfterDelay {
+                        milliseconds: 500.into(),
+                    });
                 })
             });
             item.is_dirty = true;