project.rs

  1use std::{path::PathBuf, sync::Arc};
  2
  3use collections::{BTreeMap, HashMap};
  4use gpui::Rgba;
  5use schemars::JsonSchema;
  6use serde::{Deserialize, Serialize};
  7use settings_json::parse_json_with_comments;
  8use settings_macros::{MergeFrom, with_fallible_options};
  9use util::serde::default_true;
 10
 11use crate::{
 12    AllLanguageSettingsContent, DelayMs, ExtendingVec, ParseStatus, ProjectTerminalSettingsContent,
 13    RootUserSettings, SlashCommandSettings, fallible_options,
 14};
 15
 16#[with_fallible_options]
 17#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
 18pub struct LspSettingsMap(pub HashMap<Arc<str>, LspSettings>);
 19
 20impl IntoIterator for LspSettingsMap {
 21    type Item = (Arc<str>, LspSettings);
 22    type IntoIter = std::collections::hash_map::IntoIter<Arc<str>, LspSettings>;
 23
 24    fn into_iter(self) -> Self::IntoIter {
 25        self.0.into_iter()
 26    }
 27}
 28
 29impl RootUserSettings for ProjectSettingsContent {
 30    fn parse_json(json: &str) -> (Option<Self>, ParseStatus) {
 31        fallible_options::parse_json(json)
 32    }
 33    fn parse_json_with_comments(json: &str) -> anyhow::Result<Self> {
 34        parse_json_with_comments(json)
 35    }
 36}
 37
 38#[with_fallible_options]
 39#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
 40pub struct ProjectSettingsContent {
 41    #[serde(flatten)]
 42    pub all_languages: AllLanguageSettingsContent,
 43
 44    #[serde(flatten)]
 45    pub worktree: WorktreeSettingsContent,
 46
 47    /// Configuration for language servers.
 48    ///
 49    /// The following settings can be overridden for specific language servers:
 50    /// - initialization_options
 51    ///
 52    /// To override settings for a language, add an entry for that language server's
 53    /// name to the lsp value.
 54    /// Default: null
 55    #[serde(default)]
 56    pub lsp: LspSettingsMap,
 57
 58    pub terminal: Option<ProjectTerminalSettingsContent>,
 59
 60    /// Configuration for Debugger-related features
 61    #[serde(default)]
 62    pub dap: HashMap<Arc<str>, DapSettingsContent>,
 63
 64    /// Settings for context servers used for AI-related features.
 65    #[serde(default)]
 66    pub context_servers: HashMap<Arc<str>, ContextServerSettingsContent>,
 67
 68    /// Default timeout in seconds for context server tool calls.
 69    /// Can be overridden per-server in context_servers configuration.
 70    ///
 71    /// Default: 60
 72    pub context_server_timeout: Option<u64>,
 73
 74    /// Configuration for how direnv configuration should be loaded
 75    pub load_direnv: Option<DirenvSettings>,
 76
 77    /// Settings for slash commands.
 78    pub slash_commands: Option<SlashCommandSettings>,
 79
 80    /// The list of custom Git hosting providers.
 81    pub git_hosting_providers: Option<ExtendingVec<GitHostingProviderConfig>>,
 82}
 83
 84#[with_fallible_options]
 85#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
 86pub struct WorktreeSettingsContent {
 87    /// The displayed name of this project. If not set or null, the root directory name
 88    /// will be displayed.
 89    ///
 90    /// Default: null
 91    pub project_name: Option<String>,
 92
 93    /// Whether to prevent this project from being shared in public channels.
 94    ///
 95    /// Default: false
 96    #[serde(default)]
 97    pub prevent_sharing_in_public_channels: bool,
 98
 99    /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
100    /// `file_scan_inclusions`.
101    ///
102    /// Default: [
103    ///   "**/.git",
104    ///   "**/.svn",
105    ///   "**/.hg",
106    ///   "**/.jj",
107    ///   "**/CVS",
108    ///   "**/.DS_Store",
109    ///   "**/Thumbs.db",
110    ///   "**/.classpath",
111    ///   "**/.settings"
112    /// ]
113    pub file_scan_exclusions: Option<Vec<String>>,
114
115    /// Always include files that match these globs when scanning for files, even if they're
116    /// ignored by git. This setting is overridden by `file_scan_exclusions`.
117    /// Default: [
118    ///  ".env*",
119    ///  "docker-compose.*.yml",
120    /// ]
121    pub file_scan_inclusions: Option<Vec<String>>,
122
123    /// Treat the files matching these globs as `.env` files.
124    /// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"]
125    pub private_files: Option<ExtendingVec<String>>,
126
127    /// Treat the files matching these globs as hidden files. You can hide hidden files in the project panel.
128    /// Default: ["**/.*"]
129    pub hidden_files: Option<Vec<String>>,
130
131    /// Treat the files matching these globs as read-only. These files can be opened and viewed,
132    /// but cannot be edited. This is useful for generated files, build outputs, or files from
133    /// external dependencies that should not be modified directly.
134    /// Default: []
135    pub read_only_files: Option<Vec<String>>,
136}
137
138#[with_fallible_options]
139#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)]
140#[serde(rename_all = "snake_case")]
141pub struct LspSettings {
142    pub binary: Option<BinarySettings>,
143    /// Options passed to the language server at startup.
144    ///
145    /// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize
146    ///
147    /// Consult the documentation for the specific language server to see which settings are supported.
148    pub initialization_options: Option<serde_json::Value>,
149    /// Language server settings.
150    ///
151    /// Ref: https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#workspace_configuration
152    ///
153    /// Consult the documentation for the specific language server to see which settings are supported.
154    pub settings: Option<serde_json::Value>,
155    /// If the server supports sending tasks over LSP extensions,
156    /// this setting can be used to enable or disable them in Zed.
157    /// Default: true
158    #[serde(default = "default_true")]
159    pub enable_lsp_tasks: bool,
160    pub fetch: Option<FetchSettings>,
161}
162
163impl Default for LspSettings {
164    fn default() -> Self {
165        Self {
166            binary: None,
167            initialization_options: None,
168            settings: None,
169            enable_lsp_tasks: true,
170            fetch: None,
171        }
172    }
173}
174
175#[with_fallible_options]
176#[derive(
177    Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
178)]
179pub struct BinarySettings {
180    pub path: Option<String>,
181    pub arguments: Option<Vec<String>>,
182    pub env: Option<BTreeMap<String, String>>,
183    pub ignore_system_version: Option<bool>,
184}
185
186#[with_fallible_options]
187#[derive(
188    Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash,
189)]
190pub struct FetchSettings {
191    // Whether to consider pre-releases for fetching
192    pub pre_release: Option<bool>,
193}
194
195/// Common language server settings.
196#[with_fallible_options]
197#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
198pub struct GlobalLspSettingsContent {
199    /// Whether to show the LSP servers button in the status bar.
200    ///
201    /// Default: `true`
202    pub button: Option<bool>,
203    /// Settings for language server notifications
204    pub notifications: Option<LspNotificationSettingsContent>,
205    /// Rules for rendering LSP semantic tokens.
206    pub semantic_token_rules: Option<SemanticTokenRules>,
207}
208
209#[with_fallible_options]
210#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
211pub struct LspNotificationSettingsContent {
212    /// Timeout in milliseconds for automatically dismissing language server notifications.
213    /// Set to 0 to disable auto-dismiss.
214    ///
215    /// Default: 5000
216    pub dismiss_timeout_ms: Option<u64>,
217}
218
219/// Custom rules for rendering LSP semantic tokens.
220#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
221#[serde(transparent)]
222pub struct SemanticTokenRules {
223    pub rules: Vec<SemanticTokenRule>,
224}
225
226impl crate::merge_from::MergeFrom for SemanticTokenRules {
227    fn merge_from(&mut self, other: &Self) {
228        self.rules.splice(0..0, other.rules.iter().cloned());
229    }
230}
231
232#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)]
233#[serde(rename_all = "snake_case")]
234pub struct SemanticTokenRule {
235    pub token_type: Option<String>,
236    #[serde(default)]
237    pub token_modifiers: Vec<String>,
238    #[serde(default)]
239    pub style: Vec<String>,
240    pub foreground_color: Option<Rgba>,
241    pub background_color: Option<Rgba>,
242    pub underline: Option<SemanticTokenColorOverride>,
243    pub strikethrough: Option<SemanticTokenColorOverride>,
244    pub font_weight: Option<SemanticTokenFontWeight>,
245    pub font_style: Option<SemanticTokenFontStyle>,
246}
247
248#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)]
249#[serde(untagged)]
250pub enum SemanticTokenColorOverride {
251    InheritForeground(bool),
252    Replace(Rgba),
253}
254
255#[derive(
256    Copy,
257    Clone,
258    Debug,
259    Default,
260    Serialize,
261    Deserialize,
262    PartialEq,
263    Eq,
264    JsonSchema,
265    MergeFrom,
266    strum::VariantArray,
267    strum::VariantNames,
268)]
269#[serde(rename_all = "snake_case")]
270pub enum SemanticTokenFontWeight {
271    #[default]
272    Normal,
273    Bold,
274}
275
276#[derive(
277    Copy,
278    Clone,
279    Debug,
280    Default,
281    Serialize,
282    Deserialize,
283    PartialEq,
284    Eq,
285    JsonSchema,
286    MergeFrom,
287    strum::VariantArray,
288    strum::VariantNames,
289)]
290#[serde(rename_all = "snake_case")]
291pub enum SemanticTokenFontStyle {
292    #[default]
293    Normal,
294    Italic,
295}
296
297#[with_fallible_options]
298#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
299#[serde(rename_all = "snake_case")]
300pub struct DapSettingsContent {
301    pub binary: Option<String>,
302    pub args: Option<Vec<String>>,
303    pub env: Option<HashMap<String, String>>,
304}
305
306#[with_fallible_options]
307#[derive(
308    Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom,
309)]
310pub struct SessionSettingsContent {
311    /// Whether or not to restore unsaved buffers on restart.
312    ///
313    /// If this is true, user won't be prompted whether to save/discard
314    /// dirty files when closing the application.
315    ///
316    /// Default: true
317    pub restore_unsaved_buffers: Option<bool>,
318    /// Whether or not to skip worktree trust checks.
319    /// When trusted, project settings are synchronized automatically,
320    /// language and MCP servers are downloaded and started automatically.
321    ///
322    /// Default: false
323    pub trust_all_worktrees: Option<bool>,
324}
325
326#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom, Debug)]
327#[serde(untagged, rename_all = "snake_case")]
328pub enum ContextServerSettingsContent {
329    Stdio {
330        /// Whether the context server is enabled.
331        #[serde(default = "default_true")]
332        enabled: bool,
333        /// Whether to run the context server on the remote server when using remote development.
334        ///
335        /// If this is false, the context server will always run on the local machine.
336        ///
337        /// Default: false
338        #[serde(default)]
339        remote: bool,
340        #[serde(flatten)]
341        command: ContextServerCommand,
342    },
343    Http {
344        /// Whether the context server is enabled.
345        #[serde(default = "default_true")]
346        enabled: bool,
347        /// The URL of the remote context server.
348        url: String,
349        /// Optional headers to send.
350        #[serde(skip_serializing_if = "HashMap::is_empty", default)]
351        headers: HashMap<String, String>,
352        /// Timeout for tool calls in seconds. Defaults to global context_server_timeout if not specified.
353        timeout: Option<u64>,
354    },
355    Extension {
356        /// Whether the context server is enabled.
357        #[serde(default = "default_true")]
358        enabled: bool,
359        /// Whether to run the context server on the remote server when using remote development.
360        ///
361        /// If this is false, the context server will always run on the local machine.
362        ///
363        /// Default: false
364        #[serde(default)]
365        remote: bool,
366        /// The settings for this context server specified by the extension.
367        ///
368        /// Consult the documentation for the context server to see what settings
369        /// are supported.
370        settings: serde_json::Value,
371    },
372}
373
374impl ContextServerSettingsContent {
375    pub fn set_enabled(&mut self, enabled: bool) {
376        match self {
377            ContextServerSettingsContent::Stdio {
378                enabled: custom_enabled,
379                ..
380            } => {
381                *custom_enabled = enabled;
382            }
383            ContextServerSettingsContent::Extension {
384                enabled: ext_enabled,
385                ..
386            } => *ext_enabled = enabled,
387            ContextServerSettingsContent::Http {
388                enabled: remote_enabled,
389                ..
390            } => *remote_enabled = enabled,
391        }
392    }
393}
394
395#[with_fallible_options]
396#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)]
397pub struct ContextServerCommand {
398    #[serde(rename = "command")]
399    pub path: PathBuf,
400    pub args: Vec<String>,
401    pub env: Option<HashMap<String, String>>,
402    /// Timeout for tool calls in seconds. Defaults to 60 if not specified.
403    pub timeout: Option<u64>,
404}
405
406impl std::fmt::Debug for ContextServerCommand {
407    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408        let filtered_env = self.env.as_ref().map(|env| {
409            env.iter()
410                .map(|(k, v)| {
411                    (
412                        k,
413                        if util::redact::should_redact(k) {
414                            "[REDACTED]"
415                        } else {
416                            v
417                        },
418                    )
419                })
420                .collect::<Vec<_>>()
421        });
422
423        f.debug_struct("ContextServerCommand")
424            .field("path", &self.path)
425            .field("args", &self.args)
426            .field("env", &filtered_env)
427            .finish()
428    }
429}
430
431#[with_fallible_options]
432#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
433pub struct GitSettings {
434    /// Whether or not to enable git integration.
435    ///
436    /// Default: true
437    #[serde(flatten)]
438    pub enabled: Option<GitEnabledSettings>,
439    /// Whether or not to show the git gutter.
440    ///
441    /// Default: tracked_files
442    pub git_gutter: Option<GitGutterSetting>,
443    /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
444    ///
445    /// Default: 0
446    pub gutter_debounce: Option<u64>,
447    /// Whether or not to show git blame data inline in
448    /// the currently focused line.
449    ///
450    /// Default: on
451    pub inline_blame: Option<InlineBlameSettings>,
452    /// Git blame settings.
453    pub blame: Option<BlameSettings>,
454    /// Which information to show in the branch picker.
455    ///
456    /// Default: on
457    pub branch_picker: Option<BranchPickerSettingsContent>,
458    /// How hunks are displayed visually in the editor.
459    ///
460    /// Default: staged_hollow
461    pub hunk_style: Option<GitHunkStyleSetting>,
462    /// How file paths are displayed in the git gutter.
463    ///
464    /// Default: file_name_first
465    pub path_style: Option<GitPathStyle>,
466}
467
468#[with_fallible_options]
469#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
470#[serde(rename_all = "snake_case")]
471pub struct GitEnabledSettings {
472    pub disable_git: Option<bool>,
473    pub enable_status: Option<bool>,
474    pub enable_diff: Option<bool>,
475}
476
477impl GitEnabledSettings {
478    pub fn is_git_status_enabled(&self) -> bool {
479        !self.disable_git.unwrap_or(false) && self.enable_status.unwrap_or(true)
480    }
481
482    pub fn is_git_diff_enabled(&self) -> bool {
483        !self.disable_git.unwrap_or(false) && self.enable_diff.unwrap_or(true)
484    }
485}
486
487#[derive(
488    Clone,
489    Copy,
490    Debug,
491    PartialEq,
492    Default,
493    Serialize,
494    Deserialize,
495    JsonSchema,
496    MergeFrom,
497    strum::VariantArray,
498    strum::VariantNames,
499)]
500#[serde(rename_all = "snake_case")]
501pub enum GitGutterSetting {
502    /// Show git gutter in tracked files.
503    #[default]
504    TrackedFiles,
505    /// Hide git gutter
506    Hide,
507}
508
509#[with_fallible_options]
510#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
511#[serde(rename_all = "snake_case")]
512pub struct InlineBlameSettings {
513    /// Whether or not to show git blame data inline in
514    /// the currently focused line.
515    ///
516    /// Default: true
517    pub enabled: Option<bool>,
518    /// Whether to only show the inline blame information
519    /// after a delay once the cursor stops moving.
520    ///
521    /// Default: 0
522    pub delay_ms: Option<DelayMs>,
523    /// The amount of padding between the end of the source line and the start
524    /// of the inline blame in units of columns.
525    ///
526    /// Default: 7
527    pub padding: Option<u32>,
528    /// The minimum column number to show the inline blame information at
529    ///
530    /// Default: 0
531    pub min_column: Option<u32>,
532    /// Whether to show commit summary as part of the inline blame.
533    ///
534    /// Default: false
535    pub show_commit_summary: Option<bool>,
536}
537
538#[with_fallible_options]
539#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
540#[serde(rename_all = "snake_case")]
541pub struct BlameSettings {
542    /// Whether to show the avatar of the author of the commit.
543    ///
544    /// Default: true
545    pub show_avatar: Option<bool>,
546}
547
548#[with_fallible_options]
549#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
550#[serde(rename_all = "snake_case")]
551pub struct BranchPickerSettingsContent {
552    /// Whether to show author name as part of the commit information.
553    ///
554    /// Default: false
555    pub show_author_name: Option<bool>,
556}
557
558#[derive(
559    Clone,
560    Copy,
561    PartialEq,
562    Debug,
563    Default,
564    Serialize,
565    Deserialize,
566    JsonSchema,
567    MergeFrom,
568    strum::VariantArray,
569    strum::VariantNames,
570)]
571#[serde(rename_all = "snake_case")]
572pub enum GitHunkStyleSetting {
573    /// Show unstaged hunks with a filled background and staged hunks hollow.
574    #[default]
575    StagedHollow,
576    /// Show unstaged hunks hollow and staged hunks with a filled background.
577    UnstagedHollow,
578}
579
580#[with_fallible_options]
581#[derive(
582    Copy,
583    Clone,
584    Debug,
585    PartialEq,
586    Default,
587    Serialize,
588    Deserialize,
589    JsonSchema,
590    MergeFrom,
591    strum::VariantArray,
592    strum::VariantNames,
593)]
594#[serde(rename_all = "snake_case")]
595pub enum GitPathStyle {
596    /// Show file name first, then path
597    #[default]
598    FileNameFirst,
599    /// Show full path first
600    FilePathFirst,
601}
602
603#[with_fallible_options]
604#[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
605pub struct DiagnosticsSettingsContent {
606    /// Whether to show the project diagnostics button in the status bar.
607    pub button: Option<bool>,
608
609    /// Whether or not to include warning diagnostics.
610    ///
611    /// Default: true
612    pub include_warnings: Option<bool>,
613
614    /// Settings for using LSP pull diagnostics mechanism in Zed.
615    pub lsp_pull_diagnostics: Option<LspPullDiagnosticsSettingsContent>,
616
617    /// Settings for showing inline diagnostics.
618    pub inline: Option<InlineDiagnosticsSettingsContent>,
619}
620
621#[with_fallible_options]
622#[derive(
623    Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq,
624)]
625pub struct LspPullDiagnosticsSettingsContent {
626    /// Whether to pull for diagnostics or not.
627    ///
628    /// Default: true
629    pub enabled: Option<bool>,
630    /// Minimum time to wait before pulling diagnostics from the language server(s).
631    /// 0 turns the debounce off.
632    ///
633    /// Default: 50
634    pub debounce_ms: Option<DelayMs>,
635}
636
637#[with_fallible_options]
638#[derive(
639    Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Eq,
640)]
641pub struct InlineDiagnosticsSettingsContent {
642    /// Whether or not to show inline diagnostics
643    ///
644    /// Default: false
645    pub enabled: Option<bool>,
646    /// Whether to only show the inline diagnostics after a delay after the
647    /// last editor event.
648    ///
649    /// Default: 150
650    pub update_debounce_ms: Option<DelayMs>,
651    /// The amount of padding between the end of the source line and the start
652    /// of the inline diagnostic in units of columns.
653    ///
654    /// Default: 4
655    pub padding: Option<u32>,
656    /// The minimum column to display inline diagnostics. This setting can be
657    /// used to horizontally align inline diagnostics at some position. Lines
658    /// longer than this value will still push diagnostics further to the right.
659    ///
660    /// Default: 0
661    pub min_column: Option<u32>,
662
663    pub max_severity: Option<DiagnosticSeverityContent>,
664}
665
666#[with_fallible_options]
667#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
668pub struct NodeBinarySettings {
669    /// The path to the Node binary.
670    pub path: Option<String>,
671    /// The path to the npm binary Zed should use (defaults to `.path/../npm`).
672    pub npm_path: Option<String>,
673    /// If enabled, Zed will download its own copy of Node.
674    pub ignore_system_version: Option<bool>,
675}
676
677#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)]
678#[serde(rename_all = "snake_case")]
679pub enum DirenvSettings {
680    /// Load direnv configuration through a shell hook
681    ShellHook,
682    /// Load direnv configuration directly using `direnv export json`
683    #[default]
684    Direct,
685    /// Do not load direnv configuration
686    Disabled,
687}
688
689#[derive(
690    Clone,
691    Copy,
692    Debug,
693    Eq,
694    PartialEq,
695    Ord,
696    PartialOrd,
697    Serialize,
698    Deserialize,
699    JsonSchema,
700    MergeFrom,
701    strum::VariantArray,
702    strum::VariantNames,
703)]
704#[serde(rename_all = "snake_case")]
705pub enum DiagnosticSeverityContent {
706    // No diagnostics are shown.
707    Off,
708    Error,
709    Warning,
710    Info,
711    Hint,
712    All,
713}
714
715/// A custom Git hosting provider.
716#[with_fallible_options]
717#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)]
718pub struct GitHostingProviderConfig {
719    /// The type of the provider.
720    ///
721    /// Must be one of `github`, `gitlab`, `bitbucket`, `gitea`, `forgejo`, or `source_hut`.
722    pub provider: GitHostingProviderKind,
723
724    /// The base URL for the provider (e.g., "https://code.corp.big.com").
725    pub base_url: String,
726
727    /// The display name for the provider (e.g., "BigCorp GitHub").
728    pub name: String,
729}
730
731#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
732#[serde(rename_all = "snake_case")]
733pub enum GitHostingProviderKind {
734    Github,
735    Gitlab,
736    Bitbucket,
737    Gitea,
738    Forgejo,
739    SourceHut,
740}