Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:35:04.028Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

settings_content.rs

1477 lines · 42.6 KB · rust
1mod action;
2mod agent;
3mod editor;
4mod extension;
5mod fallible_options;
6mod language;
7mod language_model;
8pub mod merge_from;
9mod project;
10mod serde_helper;
11mod terminal;
12mod theme;
13mod title_bar;
14mod workspace;
15
16pub use action::{ActionName, ActionWithArguments, CommandAliasTarget};
17pub use agent::*;
18pub use editor::*;
19pub use extension::*;
20pub use fallible_options::*;
21pub use language::*;
22pub use language_model::*;
23pub use merge_from::MergeFrom as MergeFromTrait;
24pub use project::*;
25use serde::de::DeserializeOwned;
26pub use serde_helper::{
27    serialize_f32_with_two_decimal_places, serialize_optional_f32_with_two_decimal_places,
28};
29use settings_json::parse_json_with_comments;
30pub use terminal::*;
31pub use theme::*;
32pub use title_bar::*;
33pub use workspace::*;
34
35use collections::{HashMap, IndexMap, IndexSet};
36use schemars::JsonSchema;
37use serde::{Deserialize, Serialize};
38use settings_macros::{MergeFrom, with_fallible_options};
39
40/// Defines a settings override struct where each field is
41/// `Option<Box<SettingsContent>>`, along with:
42/// - `OVERRIDE_KEYS`: a `&[&str]` of the field names (the JSON keys)
43/// - `get_by_key(&self, key) -> Option<&SettingsContent>`: accessor by key
44///
45/// The field list is the single source of truth for the override key strings.
46macro_rules! settings_overrides {
47    (
48        $(#[$attr:meta])*
49        pub struct $name:ident { $($field:ident),* $(,)? }
50    ) => {
51        $(#[$attr])*
52        pub struct $name {
53            $(pub $field: Option<Box<SettingsContent>>,)*
54        }
55
56        impl $name {
57            /// The JSON override keys, derived from the field names on this struct.
58            pub const OVERRIDE_KEYS: &[&str] = &[$(stringify!($field)),*];
59
60            /// Look up an override by its JSON key name.
61            pub fn get_by_key(&self, key: &str) -> Option<&SettingsContent> {
62                match key {
63                    $(stringify!($field) => self.$field.as_deref(),)*
64                    _ => None,
65                }
66            }
67        }
68    }
69}
70use std::collections::{BTreeMap, BTreeSet};
71use std::hash::Hash;
72use std::sync::Arc;
73pub use util::serde::default_true;
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum ParseStatus {
77    /// Settings were parsed successfully
78    Success,
79    /// Settings file was not changed, so no parsing was performed
80    Unchanged,
81    /// Settings failed to parse
82    Failed { error: String },
83}
84
85/// Determines when the mouse cursor should be hidden in response to keyboard
86/// input.
87///
88/// Default: on_typing_and_action
89#[derive(
90    Copy,
91    Clone,
92    Debug,
93    Default,
94    Serialize,
95    Deserialize,
96    PartialEq,
97    Eq,
98    JsonSchema,
99    MergeFrom,
100    strum::VariantArray,
101    strum::VariantNames,
102)]
103#[serde(rename_all = "snake_case")]
104pub enum HideMouseMode {
105    /// Never hide the mouse cursor
106    Never,
107    /// Hide only when typing
108    OnTyping,
109    /// Hide on typing and on key bindings that resolve to an action
110    #[default]
111    OnTypingAndAction,
112}
113
114/// Determines whether to reduce non-essential motion in the UI, such as
115/// loading spinners and pulsating labels, by rendering them in a static state.
116///
117/// Default: off
118#[derive(
119    Copy,
120    Clone,
121    Debug,
122    Default,
123    Serialize,
124    Deserialize,
125    PartialEq,
126    Eq,
127    JsonSchema,
128    MergeFrom,
129    strum::VariantArray,
130    strum::VariantNames,
131)]
132#[serde(rename_all = "snake_case")]
133pub enum ReduceMotionMode {
134    /// Always reduce motion
135    On,
136    /// Never reduce motion
137    #[default]
138    Off,
139}
140
141#[with_fallible_options]
142#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
143pub struct SettingsContent {
144    #[serde(flatten)]
145    pub project: ProjectSettingsContent,
146
147    #[serde(flatten)]
148    pub theme: Box<ThemeSettingsContent>,
149
150    #[serde(flatten)]
151    pub extension: ExtensionSettingsContent,
152
153    #[serde(flatten)]
154    pub workspace: WorkspaceSettingsContent,
155
156    #[serde(flatten)]
157    pub editor: EditorSettingsContent,
158
159    #[serde(flatten)]
160    pub remote: RemoteSettingsContent,
161
162    /// Settings related to the file finder.
163    pub file_finder: Option<FileFinderSettingsContent>,
164
165    pub git_panel: Option<GitPanelSettingsContent>,
166
167    pub tabs: Option<ItemSettingsContent>,
168    pub tab_bar: Option<TabBarSettingsContent>,
169    pub status_bar: Option<StatusBarSettingsContent>,
170
171    pub preview_tabs: Option<PreviewTabsSettingsContent>,
172
173    pub agent: Option<AgentSettingsContent>,
174    pub agent_servers: Option<AllAgentServersSettings>,
175
176    /// Configuration of audio in Omega.
177    pub audio: Option<AudioSettingsContent>,
178
179    /// Whether or not to automatically check for updates.
180    ///
181    /// Default: true
182    pub auto_update: Option<bool>,
183
184    /// This base keymap settings adjusts the default keybindings in Omega to be similar
185    /// to other common code editors. By default, Omega's keymap closely follows VSCode's
186    /// keymap, with minor adjustments, this corresponds to the "VSCode" setting.
187    ///
188    /// Default: VSCode
189    pub base_keymap: Option<BaseKeymapContent>,
190
191    /// Configuration for the collab panel visual settings.
192    pub collaboration_panel: Option<PanelSettingsContent>,
193
194    pub debugger: Option<DebuggerSettingsContent>,
195
196    /// Configuration for Diagnostics-related features.
197    pub diagnostics: Option<DiagnosticsSettingsContent>,
198
199    /// Configuration for Git-related features
200    pub git: Option<GitSettings>,
201
202    /// Common language server settings.
203    pub global_lsp_settings: Option<GlobalLspSettingsContent>,
204
205    /// The settings for the image viewer.
206    pub image_viewer: Option<ImageViewerSettingsContent>,
207
208    /// The settings for the markdown preview.
209    pub markdown_preview: Option<MarkdownPreviewSettingsContent>,
210
211    pub repl: Option<ReplSettingsContent>,
212
213    /// Whether or not to enable Helix mode.
214    ///
215    /// Default: false
216    pub helix_mode: Option<bool>,
217
218    /// Determines when the mouse cursor should be hidden in response to
219    /// keyboard input. Applies globally across all input surfaces (editors,
220    /// terminals, palettes, etc.).
221    ///
222    /// Default: on_typing_and_action
223    pub hide_mouse: Option<HideMouseMode>,
224
225    pub journal: Option<JournalSettingsContent>,
226
227    /// A map of log scopes to the desired log level.
228    /// Useful for filtering out noisy logs or enabling more verbose logging.
229    ///
230    /// Example: {"log": {"client": "warn"}}
231    pub log: Option<HashMap<String, String>>,
232
233    pub line_indicator_format: Option<LineIndicatorFormat>,
234
235    pub language_models: Option<AllLanguageModelSettingsContent>,
236
237    pub outline_panel: Option<OutlinePanelSettingsContent>,
238
239    pub project_panel: Option<ProjectPanelSettingsContent>,
240
241    /// Configuration for Node-related features
242    pub node: Option<NodeBinarySettings>,
243
244    pub proxy: Option<String>,
245
246    /// Whether to reduce non-essential motion in the UI, such as loading
247    /// spinners and pulsating labels, by rendering them in a static state.
248    ///
249    /// Default: off
250    pub reduce_motion: Option<ReduceMotionMode>,
251
252    /// The URL of the Zed server to connect to.
253    pub server_url: Option<String>,
254
255    /// The URL used as the key for credential storage.
256    ///
257    /// When set, credentials are stored under this URL instead of `server_url`.
258    /// This allows running multiple Omega instances side by side without them
259    /// overwriting each other's keychain entries.
260    pub credentials_url: Option<String>,
261
262    /// Configuration for session-related features
263    pub session: Option<SessionSettingsContent>,
264    /// Control what info is collected by Omega.
265    pub telemetry: Option<TelemetrySettingsContent>,
266
267    /// Configuration of the terminal in Omega.
268    pub terminal: Option<TerminalSettingsContent>,
269
270    pub title_bar: Option<TitleBarSettingsContent>,
271
272    /// Whether or not to enable Vim mode.
273    ///
274    /// Default: false
275    pub vim_mode: Option<bool>,
276
277    // Settings related to calls in Zed
278    pub calls: Option<CallSettingsContent>,
279
280    /// Settings for the which-key popup.
281    pub which_key: Option<WhichKeySettingsContent>,
282
283    /// Settings related to Vim mode in Omega.
284    pub vim: Option<VimSettingsContent>,
285
286    /// Number of lines to search for modelines at the beginning and end of files.
287    /// Modelines contain editor directives (e.g., vim/emacs settings) that configure
288    /// the editor behavior for specific files.
289    ///
290    /// Default: 5
291    pub modeline_lines: Option<usize>,
292
293    /// Local overrides for feature flags, keyed by flag name.
294    pub feature_flags: Option<FeatureFlagsMap>,
295
296    /// Settings for developer-oriented instrumentation tools (profilers,
297    /// tracers, etc.) that can be toggled at runtime.
298    pub instrumentation: Option<InstrumentationSettingsContent>,
299}
300
301/// Configuration for developer-oriented instrumentation tools that collect
302/// diagnostic data about a running Omega instance.
303#[with_fallible_options]
304#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
305pub struct InstrumentationSettingsContent {
306    /// Configuration for the performance profiler, accessed via the
307    /// `omega: open performance profiler` action.
308    pub performance_profiler: Option<PerformanceProfilerSettingsContent>,
309}
310
311/// Configuration for the performance profiler which collects timing data
312/// for foreground and background executor tasks.
313#[with_fallible_options]
314#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
315pub struct PerformanceProfilerSettingsContent {
316    /// Whether to collect timing data for foreground and background executor
317    /// tasks. Enabling this may lead to increased memory usage, hence it's
318    /// disabled by default for regular builds.
319    ///
320    /// Default: false
321    pub enabled: Option<bool>,
322}
323
324#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize, MergeFrom)]
325#[serde(transparent)]
326pub struct FeatureFlagsMap(pub HashMap<String, String>);
327
328// A manual `JsonSchema` impl keeps this type's schema registered under a
329// unique name. The derived impl on a `#[serde(transparent)]` newtype around
330// `HashMap<String, String>` would inline to the map's own schema name (`Map_of_string`),
331// which is shared with every other `HashMap<String, String>` setting field in
332// `SettingsContent`. A named placeholder lets `json_schema_store` find and
333// replace just this field's schema at runtime without clobbering the others.
334impl JsonSchema for FeatureFlagsMap {
335    fn schema_name() -> std::borrow::Cow<'static, str> {
336        "FeatureFlagsMap".into()
337    }
338
339    fn json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
340        schemars::json_schema!({
341            "type": "object",
342            "additionalProperties": { "type": "string" }
343        })
344    }
345}
346
347impl std::ops::Deref for FeatureFlagsMap {
348    type Target = HashMap<String, String>;
349    fn deref(&self) -> &Self::Target {
350        &self.0
351    }
352}
353
354impl std::ops::DerefMut for FeatureFlagsMap {
355    fn deref_mut(&mut self) -> &mut Self::Target {
356        &mut self.0
357    }
358}
359
360impl SettingsContent {
361    pub fn languages_mut(&mut self) -> &mut HashMap<String, LanguageSettingsContent> {
362        &mut self.project.all_languages.languages.0
363    }
364}
365
366// These impls are there to optimize builds by avoiding monomorphization downstream. Yes, they're repetitive, but using default impls
367// break the optimization, for whatever reason.
368pub trait RootUserSettings: Sized + DeserializeOwned {
369    fn parse_json(json: &str) -> (Option<Self>, ParseStatus);
370    fn parse_json_with_comments(json: &str) -> anyhow::Result<Self>;
371}
372
373impl RootUserSettings for SettingsContent {
374    fn parse_json(json: &str) -> (Option<Self>, ParseStatus) {
375        fallible_options::parse_json(json)
376    }
377    fn parse_json_with_comments(json: &str) -> anyhow::Result<Self> {
378        parse_json_with_comments(json)
379    }
380}
381// Explicit opt-in instead of blanket impl to avoid monomorphizing downstream. Just a hunch though.
382impl RootUserSettings for Option<SettingsContent> {
383    fn parse_json(json: &str) -> (Option<Self>, ParseStatus) {
384        fallible_options::parse_json(json)
385    }
386    fn parse_json_with_comments(json: &str) -> anyhow::Result<Self> {
387        parse_json_with_comments(json)
388    }
389}
390impl RootUserSettings for UserSettingsContent {
391    fn parse_json(json: &str) -> (Option<Self>, ParseStatus) {
392        fallible_options::parse_json(json)
393    }
394    fn parse_json_with_comments(json: &str) -> anyhow::Result<Self> {
395        parse_json_with_comments(json)
396    }
397}
398
399settings_overrides! {
400    #[with_fallible_options]
401    #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
402    pub struct ReleaseChannelOverrides { dev, nightly, preview, stable }
403}
404
405settings_overrides! {
406    #[with_fallible_options]
407    #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
408    pub struct PlatformOverrides { macos, linux, windows }
409}
410
411/// Determines what settings a profile starts from before applying its overrides.
412#[derive(
413    Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom,
414)]
415#[serde(rename_all = "snake_case")]
416pub enum ProfileBase {
417    /// Apply profile settings on top of the user's current settings.
418    #[default]
419    User,
420    /// Apply profile settings on top of Omega's default settings, ignoring user customizations.
421    Default,
422}
423
424/// A named settings profile that can temporarily override settings.
425#[with_fallible_options]
426#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
427pub struct SettingsProfile {
428    /// What base settings to start from before applying this profile's overrides.
429    ///
430    /// - `user`: Apply on top of user's settings (default)
431    /// - `default`: Apply on top of Omega's default settings, ignoring user customizations
432    #[serde(default)]
433    pub base: ProfileBase,
434
435    /// The settings overrides for this profile.
436    #[serde(default)]
437    pub settings: Box<SettingsContent>,
438}
439
440#[with_fallible_options]
441#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
442pub struct UserSettingsContent {
443    #[serde(flatten)]
444    pub content: Box<SettingsContent>,
445
446    #[serde(flatten)]
447    pub release_channel_overrides: ReleaseChannelOverrides,
448
449    #[serde(flatten)]
450    pub platform_overrides: PlatformOverrides,
451
452    #[serde(default)]
453    pub profiles: IndexMap<String, SettingsProfile>,
454}
455
456pub struct ExtensionsSettingsContent {
457    pub all_languages: AllLanguageSettingsContent,
458}
459
460/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
461///
462/// Default: Zed
463#[derive(
464    Copy,
465    Clone,
466    Debug,
467    Serialize,
468    Deserialize,
469    JsonSchema,
470    MergeFrom,
471    PartialEq,
472    Eq,
473    Default,
474    strum::VariantArray,
475)]
476pub enum BaseKeymapContent {
477    #[default]
478    Zed,
479    VSCode,
480    JetBrains,
481    SublimeText,
482    Atom,
483    TextMate,
484    Emacs,
485    Cursor,
486    None,
487}
488
489impl strum::VariantNames for BaseKeymapContent {
490    const VARIANTS: &'static [&'static str] = &[
491        "Zed",
492        "VSCode",
493        "JetBrains",
494        "Sublime Text",
495        "Atom",
496        "TextMate",
497        "Emacs",
498        "Cursor",
499        "None",
500    ];
501}
502
503/// Configuration of audio in Omega.
504#[with_fallible_options]
505#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
506pub struct AudioSettingsContent {
507    /// Select specific output audio device.
508    #[serde(rename = "experimental.output_audio_device")]
509    pub output_audio_device: Option<AudioOutputDeviceName>,
510    /// Select specific input audio device.
511    #[serde(rename = "experimental.input_audio_device")]
512    pub input_audio_device: Option<AudioInputDeviceName>,
513}
514
515#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
516#[serde(transparent)]
517pub struct AudioOutputDeviceName(pub Option<String>);
518
519impl AsRef<Option<String>> for AudioInputDeviceName {
520    fn as_ref(&self) -> &Option<String> {
521        &self.0
522    }
523}
524
525impl From<Option<String>> for AudioInputDeviceName {
526    fn from(value: Option<String>) -> Self {
527        Self(value)
528    }
529}
530
531#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
532#[serde(transparent)]
533pub struct AudioInputDeviceName(pub Option<String>);
534
535impl AsRef<Option<String>> for AudioOutputDeviceName {
536    fn as_ref(&self) -> &Option<String> {
537        &self.0
538    }
539}
540
541impl From<Option<String>> for AudioOutputDeviceName {
542    fn from(value: Option<String>) -> Self {
543        Self(value)
544    }
545}
546
547/// Control what info is collected by Omega.
548#[with_fallible_options]
549#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)]
550pub struct TelemetrySettingsContent {
551    /// Send debug info like crash reports.
552    ///
553    /// Default: true
554    pub diagnostics: Option<bool>,
555    /// Send anonymized usage data like what languages you're using Omega with.
556    ///
557    /// Default: true
558    pub metrics: Option<bool>,
559    /// Allow sending requests to Anthropic models that cannot be offered with
560    /// Zero Data Retention.
561    ///
562    /// Default: false
563    pub anthropic_retention: Option<bool>,
564}
565
566impl Default for TelemetrySettingsContent {
567    fn default() -> Self {
568        Self {
569            diagnostics: Some(true),
570            metrics: Some(true),
571            anthropic_retention: Some(false),
572        }
573    }
574}
575
576#[with_fallible_options]
577#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)]
578pub struct DebuggerSettingsContent {
579    /// Determines the stepping granularity.
580    ///
581    /// Default: line
582    pub stepping_granularity: Option<SteppingGranularity>,
583    /// Whether the breakpoints should be reused across Omega sessions.
584    ///
585    /// Default: true
586    pub save_breakpoints: Option<bool>,
587    /// Whether to show the debug button in the status bar.
588    ///
589    /// Default: true
590    pub button: Option<bool>,
591    /// Time in milliseconds until timeout error when connecting to a TCP debug adapter
592    ///
593    /// Default: 2000ms
594    pub timeout: Option<u64>,
595    /// Whether to log messages between active debug adapters and Omega
596    ///
597    /// Default: true
598    pub log_dap_communications: Option<bool>,
599    /// Whether to format dap messages in when adding them to debug adapter logger
600    ///
601    /// Default: true
602    pub format_dap_log_messages: Option<bool>,
603    /// The dock position of the debug panel
604    ///
605    /// Default: Bottom
606    pub dock: Option<DockPosition>,
607}
608
609/// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`.
610#[derive(
611    PartialEq,
612    Eq,
613    Debug,
614    Hash,
615    Clone,
616    Copy,
617    Deserialize,
618    Serialize,
619    JsonSchema,
620    MergeFrom,
621    strum::VariantArray,
622    strum::VariantNames,
623)]
624#[serde(rename_all = "snake_case")]
625pub enum SteppingGranularity {
626    /// The step should allow the program to run until the current statement has finished executing.
627    /// The meaning of a statement is determined by the adapter and it may be considered equivalent to a line.
628    /// For example 'for(int i = 0; i < 10; i++)' could be considered to have 3 statements 'int i = 0', 'i < 10', and 'i++'.
629    Statement,
630    /// The step should allow the program to run until the current source line has executed.
631    Line,
632    /// The step should allow one instruction to execute (e.g. one x86 instruction).
633    Instruction,
634}
635
636#[derive(
637    Copy,
638    Clone,
639    Debug,
640    Serialize,
641    Deserialize,
642    JsonSchema,
643    MergeFrom,
644    PartialEq,
645    Eq,
646    strum::VariantArray,
647    strum::VariantNames,
648)]
649#[serde(rename_all = "snake_case")]
650pub enum DockPosition {
651    Left,
652    Bottom,
653    Right,
654}
655
656/// Configuration of voice calls in Omega.
657#[with_fallible_options]
658#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
659pub struct CallSettingsContent {
660    /// Whether the microphone should be muted when joining a channel or a call.
661    ///
662    /// Default: false
663    pub mute_on_join: Option<bool>,
664
665    /// Whether your current project should be shared when joining an empty channel.
666    ///
667    /// Default: false
668    pub share_on_join: Option<bool>,
669}
670
671#[with_fallible_options]
672#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)]
673pub struct GitPanelSettingsContent {
674    /// Whether to show the panel button in the status bar.
675    ///
676    /// Default: true
677    pub button: Option<bool>,
678    /// Where to dock the panel.
679    ///
680    /// Default: right (Agentic layout), left (Classic layout)
681    pub dock: Option<DockPosition>,
682    /// Default width of the panel in pixels.
683    ///
684    /// Default: 360
685    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
686    pub default_width: Option<f32>,
687    /// How entry statuses are displayed.
688    ///
689    /// Default: icon
690    pub status_style: Option<StatusStyle>,
691
692    /// Whether to show file icons in the git panel.
693    ///
694    /// Default: false
695    pub file_icons: Option<bool>,
696
697    /// Whether to show folder icons or chevrons for directories in the git panel.
698    ///
699    /// Default: true
700    pub folder_icons: Option<bool>,
701
702    /// How and when the scrollbar should be displayed.
703    ///
704    /// Default: inherits editor scrollbar settings
705    pub scrollbar: Option<ScrollbarSettings>,
706
707    /// What the default branch name should be when
708    /// `init.defaultBranch` is not set in git
709    ///
710    /// Default: main
711    pub fallback_branch_name: Option<String>,
712
713    /// How to sort entries in the git panel.
714    ///
715    /// Default: path
716    pub sort_by: Option<GitPanelSortBy>,
717
718    /// How to group entries in the git panel.
719    ///
720    /// Default: status
721    pub group_by: Option<GitPanelGroupBy>,
722
723    /// Whether to collapse untracked files in the diff panel.
724    ///
725    /// Default: false
726    pub collapse_untracked_diff: Option<bool>,
727
728    /// Whether to show entries with tree or flat view in the panel
729    ///
730    /// Default: false
731    pub tree_view: Option<bool>,
732
733    /// Whether to show the addition/deletion change count next to each file in the Git panel.
734    ///
735    /// Default: true
736    pub diff_stats: Option<bool>,
737
738    /// Whether to show a badge on the git panel icon with the count of uncommitted changes.
739    ///
740    /// Default: false
741    pub show_count_badge: Option<bool>,
742
743    /// Whether the git panel should open on startup.
744    ///
745    /// Default: false
746    pub starts_open: Option<bool>,
747
748    /// Maximum length of the commit message title before a warning is shown.
749    /// Set to 0 to disable.
750    ///
751    /// Default: 0
752    pub commit_title_max_length: Option<usize>,
753
754    /// Default action when clicking a changed file in the Git panel.
755    ///
756    /// Default: project_diff
757    pub entry_primary_click_action: Option<GitPanelClickBehavior>,
758}
759
760#[derive(
761    Default,
762    Copy,
763    Clone,
764    Debug,
765    Serialize,
766    Deserialize,
767    JsonSchema,
768    MergeFrom,
769    PartialEq,
770    Eq,
771    strum::VariantArray,
772    strum::VariantNames,
773)]
774#[serde(rename_all = "snake_case")]
775pub enum GitPanelClickBehavior {
776    /// Open the project diff, showing all changed files.
777    #[default]
778    ProjectDiff,
779    /// Open a single-file diff view.
780    FileDiff,
781    /// Open the file in the editor without a diff view.
782    ViewFile,
783}
784
785#[derive(
786    Copy,
787    Clone,
788    Debug,
789    Default,
790    Serialize,
791    Deserialize,
792    JsonSchema,
793    MergeFrom,
794    PartialEq,
795    Eq,
796    strum::VariantArray,
797    strum::VariantNames,
798)]
799#[serde(rename_all = "snake_case")]
800pub enum GitPanelSortBy {
801    #[default]
802    Path,
803    Name,
804}
805
806#[derive(
807    Copy,
808    Clone,
809    Debug,
810    Default,
811    Serialize,
812    Deserialize,
813    JsonSchema,
814    MergeFrom,
815    PartialEq,
816    Eq,
817    strum::VariantArray,
818    strum::VariantNames,
819)]
820#[serde(rename_all = "snake_case")]
821pub enum GitPanelGroupBy {
822    None,
823    #[default]
824    Status,
825    Staging,
826}
827
828#[derive(
829    Default,
830    Copy,
831    Clone,
832    Debug,
833    Serialize,
834    Deserialize,
835    JsonSchema,
836    MergeFrom,
837    PartialEq,
838    Eq,
839    strum::VariantArray,
840    strum::VariantNames,
841)]
842#[serde(rename_all = "snake_case")]
843pub enum StatusStyle {
844    #[default]
845    Icon,
846    LabelColor,
847}
848
849#[with_fallible_options]
850#[derive(
851    Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
852)]
853pub struct ScrollbarSettings {
854    pub show: Option<ShowScrollbar>,
855}
856
857#[with_fallible_options]
858#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
859pub struct PanelSettingsContent {
860    /// Whether to show the panel button in the status bar.
861    ///
862    /// Default: true
863    pub button: Option<bool>,
864    /// Where to dock the panel.
865    ///
866    /// Default: right (Agentic layout), left (Classic layout)
867    pub dock: Option<DockPosition>,
868    /// Default width of the panel in pixels.
869    ///
870    /// Default: 240
871    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
872    pub default_width: Option<f32>,
873}
874
875#[with_fallible_options]
876#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
877pub struct FileFinderSettingsContent {
878    /// Whether to show file icons in the file finder.
879    ///
880    /// Default: true
881    pub file_icons: Option<bool>,
882    /// Determines how much space the file finder can take up in relation to the available window width.
883    ///
884    /// Default: small
885    pub modal_max_width: Option<FileFinderWidthContent>,
886    /// Determines whether the file finder should skip focus for the active file in search results.
887    ///
888    /// Default: true
889    pub skip_focus_for_active_in_search: Option<bool>,
890    /// Whether to use gitignored files when searching.
891    /// Only the file Omega had indexed will be used, not necessary all the gitignored files.
892    ///
893    /// Default: Smart
894    pub include_ignored: Option<IncludeIgnoredContent>,
895    /// Whether to include text channels in file finder results.
896    ///
897    /// Default: false
898    pub include_channels: Option<bool>,
899}
900
901#[derive(
902    Debug,
903    PartialEq,
904    Eq,
905    Clone,
906    Copy,
907    Default,
908    Serialize,
909    Deserialize,
910    JsonSchema,
911    MergeFrom,
912    strum::VariantArray,
913    strum::VariantNames,
914)]
915#[serde(rename_all = "snake_case")]
916pub enum IncludeIgnoredContent {
917    /// Use all gitignored files
918    All,
919    /// Use only the files Omega had indexed
920    Indexed,
921    /// Be smart and search for ignored when called from a gitignored worktree
922    #[default]
923    Smart,
924}
925
926#[derive(
927    Debug,
928    PartialEq,
929    Eq,
930    Clone,
931    Copy,
932    Default,
933    Serialize,
934    Deserialize,
935    JsonSchema,
936    MergeFrom,
937    strum::VariantArray,
938    strum::VariantNames,
939)]
940#[serde(rename_all = "lowercase")]
941pub enum FileFinderWidthContent {
942    #[default]
943    Small,
944    Medium,
945    Large,
946    XLarge,
947    Full,
948}
949
950#[with_fallible_options]
951#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)]
952pub struct VimSettingsContent {
953    pub default_mode: Option<ModeContent>,
954    pub toggle_relative_line_numbers: Option<bool>,
955    pub use_system_clipboard: Option<UseSystemClipboard>,
956    pub use_smartcase_find: Option<bool>,
957    pub use_regex_search: Option<bool>,
958    /// When enabled, the `:substitute` command replaces all matches in a line
959    /// by default. The 'g' flag then toggles this behavior.,
960    pub gdefault: Option<bool>,
961    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
962    pub highlight_on_yank_duration: Option<u64>,
963    pub cursor_shape: Option<CursorShapeSettings>,
964    /// When enabled, edit predictions are shown in Vim normal mode.
965    /// By default, edit predictions are only shown in insert and replace modes.
966    pub show_edit_predictions_in_normal_mode: Option<bool>,
967}
968
969#[derive(
970    Copy,
971    Clone,
972    Default,
973    Serialize,
974    Deserialize,
975    JsonSchema,
976    MergeFrom,
977    PartialEq,
978    Debug,
979    strum::VariantArray,
980    strum::VariantNames,
981)]
982#[serde(rename_all = "snake_case")]
983pub enum ModeContent {
984    #[default]
985    Normal,
986    Insert,
987}
988
989/// Controls when to use system clipboard.
990#[derive(
991    Copy,
992    Clone,
993    Debug,
994    Serialize,
995    Deserialize,
996    PartialEq,
997    Eq,
998    JsonSchema,
999    MergeFrom,
1000    strum::VariantArray,
1001    strum::VariantNames,
1002)]
1003#[serde(rename_all = "snake_case")]
1004pub enum UseSystemClipboard {
1005    /// Don't use system clipboard.
1006    Never,
1007    /// Use system clipboard.
1008    Always,
1009    /// Use system clipboard for yank operations.
1010    OnYank,
1011}
1012
1013/// Cursor shape configuration for insert mode in Vim.
1014#[derive(
1015    Copy,
1016    Clone,
1017    Debug,
1018    Serialize,
1019    Deserialize,
1020    PartialEq,
1021    Eq,
1022    JsonSchema,
1023    MergeFrom,
1024    strum::VariantArray,
1025    strum::VariantNames,
1026)]
1027#[serde(rename_all = "snake_case")]
1028pub enum VimInsertModeCursorShape {
1029    /// Inherit cursor shape from the editor's base cursor_shape setting.
1030    Inherit,
1031    /// Vertical bar cursor.
1032    Bar,
1033    /// Block cursor that surrounds the character.
1034    Block,
1035    /// Underline cursor.
1036    Underline,
1037    /// Hollow box cursor.
1038    Hollow,
1039}
1040
1041/// The settings for cursor shape.
1042#[with_fallible_options]
1043#[derive(
1044    Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom,
1045)]
1046pub struct CursorShapeSettings {
1047    /// Cursor shape for the normal mode.
1048    ///
1049    /// Default: block
1050    pub normal: Option<CursorShape>,
1051    /// Cursor shape for the replace mode.
1052    ///
1053    /// Default: underline
1054    pub replace: Option<CursorShape>,
1055    /// Cursor shape for the visual mode.
1056    ///
1057    /// Default: block
1058    pub visual: Option<CursorShape>,
1059    /// Cursor shape for the insert mode.
1060    ///
1061    /// The default value follows the primary cursor_shape.
1062    pub insert: Option<VimInsertModeCursorShape>,
1063}
1064
1065/// Settings specific to journaling
1066#[with_fallible_options]
1067#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
1068pub struct JournalSettingsContent {
1069    /// The path of the directory where journal entries are stored.
1070    ///
1071    /// Default: `~`
1072    pub path: Option<String>,
1073    /// What format to display the hours in.
1074    ///
1075    /// Default: hour12
1076    pub hour_format: Option<HourFormat>,
1077}
1078
1079#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
1080#[serde(rename_all = "snake_case")]
1081pub enum HourFormat {
1082    #[default]
1083    Hour12,
1084    Hour24,
1085}
1086
1087#[with_fallible_options]
1088#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)]
1089pub struct OutlinePanelSettingsContent {
1090    /// Whether to show the outline panel button in the status bar.
1091    ///
1092    /// Default: true
1093    pub button: Option<bool>,
1094    /// Customize default width (in pixels) taken by outline panel
1095    ///
1096    /// Default: 240
1097    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
1098    pub default_width: Option<f32>,
1099    /// The position of outline panel
1100    ///
1101    /// Default: right (Agentic layout), left (Classic layout)
1102    pub dock: Option<DockSide>,
1103    /// Whether to show file icons in the outline panel.
1104    ///
1105    /// Default: true
1106    pub file_icons: Option<bool>,
1107    /// Whether to show folder icons or chevrons for directories in the outline panel.
1108    ///
1109    /// Default: true
1110    pub folder_icons: Option<bool>,
1111    /// Whether to show the git status in the outline panel.
1112    ///
1113    /// Default: true
1114    pub git_status: Option<bool>,
1115    /// Amount of indentation (in pixels) for nested items.
1116    ///
1117    /// Default: 20
1118    #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")]
1119    pub indent_size: Option<f32>,
1120    /// Whether to reveal it in the outline panel automatically,
1121    /// when a corresponding project entry becomes active.
1122    /// Gitignored entries are never auto revealed.
1123    ///
1124    /// Default: true
1125    pub auto_reveal_entries: Option<bool>,
1126    /// Whether to fold directories automatically
1127    /// when directory has only one directory inside.
1128    ///
1129    /// Default: true
1130    pub auto_fold_dirs: Option<bool>,
1131    /// Settings related to indent guides in the outline panel.
1132    pub indent_guides: Option<IndentGuidesSettingsContent>,
1133    /// Scrollbar-related settings
1134    pub scrollbar: Option<ScrollbarSettingsContent>,
1135    /// Default depth to expand outline items in the current file.
1136    /// The default depth to which outline entries are expanded on reveal.
1137    /// - Set to 0 to collapse all items that have children
1138    /// - Set to 1 or higher to collapse items at that depth or deeper
1139    ///
1140    /// Default: 100
1141    pub expand_outlines_with_depth: Option<usize>,
1142}
1143
1144#[derive(
1145    Clone,
1146    Copy,
1147    Debug,
1148    PartialEq,
1149    Eq,
1150    Serialize,
1151    Deserialize,
1152    JsonSchema,
1153    MergeFrom,
1154    strum::VariantArray,
1155    strum::VariantNames,
1156)]
1157#[serde(rename_all = "snake_case")]
1158pub enum DockSide {
1159    Left,
1160    Right,
1161}
1162
1163#[derive(
1164    Copy,
1165    Clone,
1166    Debug,
1167    PartialEq,
1168    Eq,
1169    Deserialize,
1170    Serialize,
1171    JsonSchema,
1172    MergeFrom,
1173    strum::VariantArray,
1174    strum::VariantNames,
1175)]
1176#[serde(rename_all = "snake_case")]
1177pub enum ShowIndentGuides {
1178    Always,
1179    Never,
1180}
1181
1182#[with_fallible_options]
1183#[derive(
1184    Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default,
1185)]
1186pub struct IndentGuidesSettingsContent {
1187    /// When to show the scrollbar in the outline panel.
1188    pub show: Option<ShowIndentGuides>,
1189}
1190
1191#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)]
1192#[serde(rename_all = "snake_case")]
1193pub enum LineIndicatorFormat {
1194    Short,
1195    #[default]
1196    Long,
1197}
1198
1199/// The settings for the markdown preview.
1200#[with_fallible_options]
1201#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)]
1202pub struct MarkdownPreviewSettingsContent {
1203    /// Whether to limit the width of the rendered markdown content. When
1204    /// enabled, content is constrained to `max_width` and centered
1205    /// horizontally within the preview pane, for optimal readability.
1206    ///
1207    /// Default: true
1208    pub limit_content_width: Option<bool>,
1209    /// The maximum width, in pixels, of the rendered markdown content when
1210    /// `limit_content_width` is enabled.
1211    ///
1212    /// Default: 800
1213    pub max_width: Option<f32>,
1214}
1215
1216/// The settings for the image viewer.
1217#[with_fallible_options]
1218#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)]
1219pub struct ImageViewerSettingsContent {
1220    /// The unit to use for displaying image file sizes.
1221    ///
1222    /// Default: "binary"
1223    pub unit: Option<ImageFileSizeUnit>,
1224}
1225
1226#[with_fallible_options]
1227#[derive(
1228    Clone,
1229    Copy,
1230    Debug,
1231    Serialize,
1232    Deserialize,
1233    JsonSchema,
1234    MergeFrom,
1235    Default,
1236    PartialEq,
1237    strum::VariantArray,
1238    strum::VariantNames,
1239)]
1240#[serde(rename_all = "snake_case")]
1241pub enum ImageFileSizeUnit {
1242    /// Displays file size in binary units (e.g., KiB, MiB).
1243    #[default]
1244    Binary,
1245    /// Displays file size in decimal units (e.g., KB, MB).
1246    Decimal,
1247}
1248
1249#[with_fallible_options]
1250#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
1251pub struct RemoteSettingsContent {
1252    pub ssh_connections: Option<Vec<SshConnection>>,
1253    pub wsl_connections: Option<Vec<WslConnection>>,
1254    pub dev_container_connections: Option<Vec<DevContainerConnection>>,
1255    pub read_ssh_config: Option<bool>,
1256    pub use_podman: Option<bool>,
1257    /// Whether to build dev container images with BuildKit.
1258    ///
1259    /// When unset, Omega auto-detects BuildKit by probing for the `buildx` CLI
1260    /// plugin. Set to `false` to force the classic Docker builder, which is
1261    /// required for Docker-compatible engines that lack an integrated BuildKit
1262    /// (e.g. Apple Container via a Docker-API bridge), where BuildKit builds
1263    /// cannot resolve locally-built images.
1264    ///
1265    /// Default: null (auto-detect)
1266    pub dev_container_use_buildkit: Option<bool>,
1267}
1268
1269#[with_fallible_options]
1270#[derive(
1271    Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
1272)]
1273pub struct DevContainerConnection {
1274    pub name: String,
1275    pub remote_user: String,
1276    pub container_id: String,
1277    pub use_podman: bool,
1278    pub extension_ids: Vec<String>,
1279    pub remote_env: BTreeMap<String, String>,
1280}
1281
1282#[with_fallible_options]
1283#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
1284pub struct SshConnection {
1285    pub host: String,
1286    pub username: Option<String>,
1287    pub port: Option<u16>,
1288    #[serde(default)]
1289    pub args: Vec<String>,
1290    #[serde(default)]
1291    pub projects: collections::BTreeSet<RemoteProject>,
1292    /// Name to use for this server in UI.
1293    pub nickname: Option<String>,
1294    // By default Zed will download the binary to the host directly.
1295    // If this is set to true, Zed will download the binary to your local machine,
1296    // and then upload it over the SSH connection. Useful if your SSH server has
1297    // limited outbound internet access.
1298    pub upload_binary_over_ssh: Option<bool>,
1299
1300    pub port_forwards: Option<Vec<SshPortForwardOption>>,
1301    /// Timeout in seconds for SSH connection and downloading the remote server binary.
1302    /// Defaults to 10 seconds if not specified.
1303    pub connection_timeout: Option<u16>,
1304}
1305
1306#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)]
1307pub struct WslConnection {
1308    pub distro_name: String,
1309    pub user: Option<String>,
1310    #[serde(default)]
1311    pub projects: BTreeSet<RemoteProject>,
1312}
1313
1314#[with_fallible_options]
1315#[derive(
1316    Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema,
1317)]
1318pub struct RemoteProject {
1319    pub paths: Vec<String>,
1320}
1321
1322#[with_fallible_options]
1323#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)]
1324pub struct SshPortForwardOption {
1325    pub local_host: Option<String>,
1326    pub local_port: u16,
1327    pub remote_host: Option<String>,
1328    pub remote_port: u16,
1329}
1330
1331/// Settings for configuring REPL display and behavior.
1332#[with_fallible_options]
1333#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
1334pub struct ReplSettingsContent {
1335    /// Maximum number of lines to keep in REPL's scrollback buffer.
1336    /// Clamped with [4, 256] range.
1337    ///
1338    /// Default: 32
1339    pub max_lines: Option<usize>,
1340    /// Maximum number of columns to keep in REPL's scrollback buffer.
1341    /// Clamped with [20, 512] range.
1342    ///
1343    /// Default: 128
1344    pub max_columns: Option<usize>,
1345    /// Whether to show small single-line outputs inline instead of in a block.
1346    ///
1347    /// Default: true
1348    pub inline_output: Option<bool>,
1349    /// Maximum number of characters for an output to be shown inline.
1350    /// Only applies when `inline_output` is true.
1351    ///
1352    /// Default: 50
1353    pub inline_output_max_length: Option<usize>,
1354    /// Maximum number of lines of output to display before scrolling.
1355    /// Set to 0 to disable output height limits.
1356    ///
1357    /// Default: 0
1358    pub output_max_height_lines: Option<usize>,
1359}
1360
1361/// Settings for configuring the which-key popup behaviour.
1362#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
1363pub struct WhichKeySettingsContent {
1364    /// Whether to show the which-key popup when holding down key combinations
1365    ///
1366    /// Default: false
1367    pub enabled: Option<bool>,
1368    /// Delay in milliseconds before showing the which-key popup.
1369    ///
1370    /// Default: 700
1371    pub delay_ms: Option<u64>,
1372}
1373
1374// An ExtendingVec in the settings can only accumulate new values.
1375//
1376// This is useful for things like private files where you only want
1377// to allow new values to be added.
1378//
1379// Consider using a HashMap<String, bool> instead of this type
1380// (like auto_install_extensions) so that user settings files can both add
1381// and remove values from the set.
1382#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1383pub struct ExtendingVec<T>(pub Vec<T>);
1384
1385impl<T> Into<Vec<T>> for ExtendingVec<T> {
1386    fn into(self) -> Vec<T> {
1387        self.0
1388    }
1389}
1390impl<T> From<Vec<T>> for ExtendingVec<T> {
1391    fn from(vec: Vec<T>) -> Self {
1392        ExtendingVec(vec)
1393    }
1394}
1395
1396impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
1397    fn merge_from(&mut self, other: &Self) {
1398        self.0.extend_from_slice(other.0.as_slice());
1399    }
1400}
1401
1402// An ExtendingSet in the settings can only accumulate new values, and ignores
1403// values that are already present, so merging the same source more than once
1404// (e.g. re-importing VS Code settings) is idempotent.
1405//
1406// Insertion order is preserved, so it round-trips through the user's settings
1407// file without reordering their entries.
1408#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1409pub struct ExtendingSet<T: std::hash::Hash + Eq>(pub IndexSet<T>);
1410
1411impl<T: std::hash::Hash + Eq> From<Vec<T>> for ExtendingSet<T> {
1412    fn from(vec: Vec<T>) -> Self {
1413        ExtendingSet(vec.into_iter().collect())
1414    }
1415}
1416
1417impl<T: Clone + std::hash::Hash + Eq> merge_from::MergeFrom for ExtendingSet<T> {
1418    fn merge_from(&mut self, other: &Self) {
1419        self.0.extend(other.0.iter().cloned());
1420    }
1421}
1422
1423// A SaturatingBool in the settings can only ever be set to true,
1424// later attempts to set it to false will be ignored.
1425//
1426// Used by `disable_ai`.
1427#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
1428pub struct SaturatingBool(pub bool);
1429
1430impl From<bool> for SaturatingBool {
1431    fn from(value: bool) -> Self {
1432        SaturatingBool(value)
1433    }
1434}
1435
1436impl From<SaturatingBool> for bool {
1437    fn from(value: SaturatingBool) -> bool {
1438        value.0
1439    }
1440}
1441
1442impl merge_from::MergeFrom for SaturatingBool {
1443    fn merge_from(&mut self, other: &Self) {
1444        self.0 |= other.0
1445    }
1446}
1447
1448#[derive(
1449    Copy,
1450    Clone,
1451    Default,
1452    Debug,
1453    PartialEq,
1454    Eq,
1455    PartialOrd,
1456    Ord,
1457    Serialize,
1458    Deserialize,
1459    MergeFrom,
1460    JsonSchema,
1461    derive_more::FromStr,
1462)]
1463#[serde(transparent)]
1464pub struct DelayMs(pub u64);
1465
1466impl From<u64> for DelayMs {
1467    fn from(n: u64) -> Self {
1468        Self(n)
1469    }
1470}
1471
1472impl std::fmt::Display for DelayMs {
1473    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1474        write!(f, "{}ms", self.0)
1475    }
1476}
1477
Served at tenant.openagents/omega Member data and write actions are omitted.