project_settings.rs

   1use anyhow::Context as _;
   2use collections::HashMap;
   3use context_server::ContextServerCommand;
   4use dap::adapters::DebugAdapterName;
   5use fs::Fs;
   6use futures::StreamExt as _;
   7use gpui::{AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Subscription, Task};
   8use lsp::LanguageServerName;
   9use paths::{
  10    EDITORCONFIG_NAME, local_debug_file_relative_path, local_settings_file_relative_path,
  11    local_tasks_file_relative_path, local_vscode_launch_file_relative_path,
  12    local_vscode_tasks_file_relative_path, task_file_name,
  13};
  14use rpc::{
  15    AnyProtoClient, TypedEnvelope,
  16    proto::{self, REMOTE_SERVER_PROJECT_ID},
  17};
  18use schemars::JsonSchema;
  19use serde::{Deserialize, Serialize};
  20pub use settings::DirenvSettings;
  21pub use settings::LspSettings;
  22use settings::{
  23    DapSettingsContent, InvalidSettingsError, LocalSettingsKind, RegisterSetting, Settings,
  24    SettingsLocation, SettingsStore, parse_json_with_comments, watch_config_file,
  25};
  26use std::{cell::OnceCell, collections::BTreeMap, path::PathBuf, sync::Arc, time::Duration};
  27use task::{DebugTaskFile, TaskTemplates, VsCodeDebugTaskFile, VsCodeTaskFile};
  28use util::{ResultExt, rel_path::RelPath, serde::default_true};
  29use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
  30
  31use crate::{
  32    task_store::{TaskSettingsLocation, TaskStore},
  33    trusted_worktrees::{PathTrust, TrustedWorktrees, TrustedWorktreesEvent},
  34    worktree_store::{WorktreeStore, WorktreeStoreEvent},
  35};
  36
  37#[derive(Debug, Clone, RegisterSetting)]
  38pub struct ProjectSettings {
  39    /// Configuration for language servers.
  40    ///
  41    /// The following settings can be overridden for specific language servers:
  42    /// - initialization_options
  43    ///
  44    /// To override settings for a language, add an entry for that language server's
  45    /// name to the lsp value.
  46    /// Default: null
  47    // todo(settings-follow-up)
  48    // We should change to use a non content type (settings::LspSettings is a content type)
  49    // Note: Will either require merging with defaults, which also requires deciding where the defaults come from,
  50    //       or case by case deciding which fields are optional and which are actually required.
  51    pub lsp: HashMap<LanguageServerName, settings::LspSettings>,
  52
  53    /// Common language server settings.
  54    pub global_lsp_settings: GlobalLspSettings,
  55
  56    /// Configuration for Debugger-related features
  57    pub dap: HashMap<DebugAdapterName, DapSettings>,
  58
  59    /// Settings for context servers used for AI-related features.
  60    pub context_servers: HashMap<Arc<str>, ContextServerSettings>,
  61
  62    /// Configuration for Diagnostics-related features.
  63    pub diagnostics: DiagnosticsSettings,
  64
  65    /// Configuration for Git-related features
  66    pub git: GitSettings,
  67
  68    /// Configuration for Node-related features
  69    pub node: NodeBinarySettings,
  70
  71    /// Configuration for how direnv configuration should be loaded
  72    pub load_direnv: DirenvSettings,
  73
  74    /// Configuration for session-related features
  75    pub session: SessionSettings,
  76}
  77
  78#[derive(Copy, Clone, Debug)]
  79pub struct SessionSettings {
  80    /// Whether or not to restore unsaved buffers on restart.
  81    ///
  82    /// If this is true, user won't be prompted whether to save/discard
  83    /// dirty files when closing the application.
  84    ///
  85    /// Default: true
  86    pub restore_unsaved_buffers: bool,
  87    /// Whether or not to skip worktree trust checks.
  88    /// When trusted, project settings are synchronized automatically,
  89    /// language and MCP servers are downloaded and started automatically.
  90    ///
  91    /// Default: false
  92    pub trust_all_worktrees: bool,
  93}
  94
  95#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
  96pub struct NodeBinarySettings {
  97    /// The path to the Node binary.
  98    pub path: Option<String>,
  99    /// The path to the npm binary Zed should use (defaults to `.path/../npm`).
 100    pub npm_path: Option<String>,
 101    /// If enabled, Zed will download its own copy of Node.
 102    pub ignore_system_version: bool,
 103}
 104
 105impl From<settings::NodeBinarySettings> for NodeBinarySettings {
 106    fn from(settings: settings::NodeBinarySettings) -> Self {
 107        Self {
 108            path: settings.path,
 109            npm_path: settings.npm_path,
 110            ignore_system_version: settings.ignore_system_version.unwrap_or(false),
 111        }
 112    }
 113}
 114
 115/// Common language server settings.
 116#[derive(Debug, Clone, PartialEq)]
 117pub struct GlobalLspSettings {
 118    /// Whether to show the LSP servers button in the status bar.
 119    ///
 120    /// Default: `true`
 121    pub button: bool,
 122}
 123
 124#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)]
 125#[serde(tag = "source", rename_all = "snake_case")]
 126pub enum ContextServerSettings {
 127    Stdio {
 128        /// Whether the context server is enabled.
 129        #[serde(default = "default_true")]
 130        enabled: bool,
 131
 132        #[serde(flatten)]
 133        command: ContextServerCommand,
 134    },
 135    Http {
 136        /// Whether the context server is enabled.
 137        #[serde(default = "default_true")]
 138        enabled: bool,
 139        /// The URL of the remote context server.
 140        url: String,
 141        /// Optional authentication configuration for the remote server.
 142        #[serde(skip_serializing_if = "HashMap::is_empty", default)]
 143        headers: HashMap<String, String>,
 144    },
 145    Extension {
 146        /// Whether the context server is enabled.
 147        #[serde(default = "default_true")]
 148        enabled: bool,
 149        /// The settings for this context server specified by the extension.
 150        ///
 151        /// Consult the documentation for the context server to see what settings
 152        /// are supported.
 153        settings: serde_json::Value,
 154    },
 155}
 156
 157impl From<settings::ContextServerSettingsContent> for ContextServerSettings {
 158    fn from(value: settings::ContextServerSettingsContent) -> Self {
 159        match value {
 160            settings::ContextServerSettingsContent::Stdio { enabled, command } => {
 161                ContextServerSettings::Stdio { enabled, command }
 162            }
 163            settings::ContextServerSettingsContent::Extension { enabled, settings } => {
 164                ContextServerSettings::Extension { enabled, settings }
 165            }
 166            settings::ContextServerSettingsContent::Http {
 167                enabled,
 168                url,
 169                headers,
 170            } => ContextServerSettings::Http {
 171                enabled,
 172                url,
 173                headers,
 174            },
 175        }
 176    }
 177}
 178impl Into<settings::ContextServerSettingsContent> for ContextServerSettings {
 179    fn into(self) -> settings::ContextServerSettingsContent {
 180        match self {
 181            ContextServerSettings::Stdio { enabled, command } => {
 182                settings::ContextServerSettingsContent::Stdio { enabled, command }
 183            }
 184            ContextServerSettings::Extension { enabled, settings } => {
 185                settings::ContextServerSettingsContent::Extension { enabled, settings }
 186            }
 187            ContextServerSettings::Http {
 188                enabled,
 189                url,
 190                headers,
 191            } => settings::ContextServerSettingsContent::Http {
 192                enabled,
 193                url,
 194                headers,
 195            },
 196        }
 197    }
 198}
 199
 200impl ContextServerSettings {
 201    pub fn default_extension() -> Self {
 202        Self::Extension {
 203            enabled: true,
 204            settings: serde_json::json!({}),
 205        }
 206    }
 207
 208    pub fn enabled(&self) -> bool {
 209        match self {
 210            ContextServerSettings::Stdio { enabled, .. } => *enabled,
 211            ContextServerSettings::Http { enabled, .. } => *enabled,
 212            ContextServerSettings::Extension { enabled, .. } => *enabled,
 213        }
 214    }
 215
 216    pub fn set_enabled(&mut self, enabled: bool) {
 217        match self {
 218            ContextServerSettings::Stdio { enabled: e, .. } => *e = enabled,
 219            ContextServerSettings::Http { enabled: e, .. } => *e = enabled,
 220            ContextServerSettings::Extension { enabled: e, .. } => *e = enabled,
 221        }
 222    }
 223}
 224
 225#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
 226pub enum DiagnosticSeverity {
 227    // No diagnostics are shown.
 228    Off,
 229    Error,
 230    Warning,
 231    Info,
 232    Hint,
 233}
 234
 235impl DiagnosticSeverity {
 236    pub fn into_lsp(self) -> Option<lsp::DiagnosticSeverity> {
 237        match self {
 238            DiagnosticSeverity::Off => None,
 239            DiagnosticSeverity::Error => Some(lsp::DiagnosticSeverity::ERROR),
 240            DiagnosticSeverity::Warning => Some(lsp::DiagnosticSeverity::WARNING),
 241            DiagnosticSeverity::Info => Some(lsp::DiagnosticSeverity::INFORMATION),
 242            DiagnosticSeverity::Hint => Some(lsp::DiagnosticSeverity::HINT),
 243        }
 244    }
 245}
 246
 247impl From<settings::DiagnosticSeverityContent> for DiagnosticSeverity {
 248    fn from(severity: settings::DiagnosticSeverityContent) -> Self {
 249        match severity {
 250            settings::DiagnosticSeverityContent::Off => DiagnosticSeverity::Off,
 251            settings::DiagnosticSeverityContent::Error => DiagnosticSeverity::Error,
 252            settings::DiagnosticSeverityContent::Warning => DiagnosticSeverity::Warning,
 253            settings::DiagnosticSeverityContent::Info => DiagnosticSeverity::Info,
 254            settings::DiagnosticSeverityContent::Hint => DiagnosticSeverity::Hint,
 255            settings::DiagnosticSeverityContent::All => DiagnosticSeverity::Hint,
 256        }
 257    }
 258}
 259
 260/// Determines the severity of the diagnostic that should be moved to.
 261#[derive(PartialEq, PartialOrd, Clone, Copy, Debug, Eq, Deserialize, JsonSchema)]
 262#[serde(rename_all = "snake_case")]
 263pub enum GoToDiagnosticSeverity {
 264    /// Errors
 265    Error = 3,
 266    /// Warnings
 267    Warning = 2,
 268    /// Information
 269    Information = 1,
 270    /// Hints
 271    Hint = 0,
 272}
 273
 274impl From<lsp::DiagnosticSeverity> for GoToDiagnosticSeverity {
 275    fn from(severity: lsp::DiagnosticSeverity) -> Self {
 276        match severity {
 277            lsp::DiagnosticSeverity::ERROR => Self::Error,
 278            lsp::DiagnosticSeverity::WARNING => Self::Warning,
 279            lsp::DiagnosticSeverity::INFORMATION => Self::Information,
 280            lsp::DiagnosticSeverity::HINT => Self::Hint,
 281            _ => Self::Error,
 282        }
 283    }
 284}
 285
 286impl GoToDiagnosticSeverity {
 287    pub fn min() -> Self {
 288        Self::Hint
 289    }
 290
 291    pub fn max() -> Self {
 292        Self::Error
 293    }
 294}
 295
 296/// Allows filtering diagnostics that should be moved to.
 297#[derive(PartialEq, Clone, Copy, Debug, Deserialize, JsonSchema)]
 298#[serde(untagged)]
 299pub enum GoToDiagnosticSeverityFilter {
 300    /// Move to diagnostics of a specific severity.
 301    Only(GoToDiagnosticSeverity),
 302
 303    /// Specify a range of severities to include.
 304    Range {
 305        /// Minimum severity to move to. Defaults no "error".
 306        #[serde(default = "GoToDiagnosticSeverity::min")]
 307        min: GoToDiagnosticSeverity,
 308        /// Maximum severity to move to. Defaults to "hint".
 309        #[serde(default = "GoToDiagnosticSeverity::max")]
 310        max: GoToDiagnosticSeverity,
 311    },
 312}
 313
 314impl Default for GoToDiagnosticSeverityFilter {
 315    fn default() -> Self {
 316        Self::Range {
 317            min: GoToDiagnosticSeverity::min(),
 318            max: GoToDiagnosticSeverity::max(),
 319        }
 320    }
 321}
 322
 323impl GoToDiagnosticSeverityFilter {
 324    pub fn matches(&self, severity: lsp::DiagnosticSeverity) -> bool {
 325        let severity: GoToDiagnosticSeverity = severity.into();
 326        match self {
 327            Self::Only(target) => *target == severity,
 328            Self::Range { min, max } => severity >= *min && severity <= *max,
 329        }
 330    }
 331}
 332
 333#[derive(Copy, Clone, Debug)]
 334pub struct GitSettings {
 335    /// Whether or not to show the git gutter.
 336    ///
 337    /// Default: tracked_files
 338    pub git_gutter: settings::GitGutterSetting,
 339    /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.
 340    ///
 341    /// Default: 0
 342    pub gutter_debounce: u64,
 343    /// Whether or not to show git blame data inline in
 344    /// the currently focused line.
 345    ///
 346    /// Default: on
 347    pub inline_blame: InlineBlameSettings,
 348    /// Git blame settings.
 349    pub blame: BlameSettings,
 350    /// Which information to show in the branch picker.
 351    ///
 352    /// Default: on
 353    pub branch_picker: BranchPickerSettings,
 354    /// How hunks are displayed visually in the editor.
 355    ///
 356    /// Default: staged_hollow
 357    pub hunk_style: settings::GitHunkStyleSetting,
 358    /// How file paths are displayed in the git gutter.
 359    ///
 360    /// Default: file_name_first
 361    pub path_style: GitPathStyle,
 362}
 363
 364#[derive(Clone, Copy, Debug, PartialEq, Default)]
 365pub enum GitPathStyle {
 366    #[default]
 367    FileNameFirst,
 368    FilePathFirst,
 369}
 370
 371impl From<settings::GitPathStyle> for GitPathStyle {
 372    fn from(style: settings::GitPathStyle) -> Self {
 373        match style {
 374            settings::GitPathStyle::FileNameFirst => GitPathStyle::FileNameFirst,
 375            settings::GitPathStyle::FilePathFirst => GitPathStyle::FilePathFirst,
 376        }
 377    }
 378}
 379
 380#[derive(Clone, Copy, Debug)]
 381pub struct InlineBlameSettings {
 382    /// Whether or not to show git blame data inline in
 383    /// the currently focused line.
 384    ///
 385    /// Default: true
 386    pub enabled: bool,
 387    /// Whether to only show the inline blame information
 388    /// after a delay once the cursor stops moving.
 389    ///
 390    /// Default: 0
 391    pub delay_ms: settings::DelayMs,
 392    /// The amount of padding between the end of the source line and the start
 393    /// of the inline blame in units of columns.
 394    ///
 395    /// Default: 7
 396    pub padding: u32,
 397    /// The minimum column number to show the inline blame information at
 398    ///
 399    /// Default: 0
 400    pub min_column: u32,
 401    /// Whether to show commit summary as part of the inline blame.
 402    ///
 403    /// Default: false
 404    pub show_commit_summary: bool,
 405}
 406
 407#[derive(Clone, Copy, Debug)]
 408pub struct BlameSettings {
 409    /// Whether to show the avatar of the author of the commit.
 410    ///
 411    /// Default: true
 412    pub show_avatar: bool,
 413}
 414
 415impl GitSettings {
 416    pub fn inline_blame_delay(&self) -> Option<Duration> {
 417        if self.inline_blame.delay_ms.0 > 0 {
 418            Some(Duration::from_millis(self.inline_blame.delay_ms.0))
 419        } else {
 420            None
 421        }
 422    }
 423}
 424
 425#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
 426#[serde(rename_all = "snake_case")]
 427pub struct BranchPickerSettings {
 428    /// Whether to show author name as part of the commit information.
 429    ///
 430    /// Default: false
 431    #[serde(default)]
 432    pub show_author_name: bool,
 433}
 434
 435impl Default for BranchPickerSettings {
 436    fn default() -> Self {
 437        Self {
 438            show_author_name: true,
 439        }
 440    }
 441}
 442
 443#[derive(Clone, Debug)]
 444pub struct DiagnosticsSettings {
 445    /// Whether to show the project diagnostics button in the status bar.
 446    pub button: bool,
 447
 448    /// Whether or not to include warning diagnostics.
 449    pub include_warnings: bool,
 450
 451    /// Settings for using LSP pull diagnostics mechanism in Zed.
 452    pub lsp_pull_diagnostics: LspPullDiagnosticsSettings,
 453
 454    /// Settings for showing inline diagnostics.
 455    pub inline: InlineDiagnosticsSettings,
 456}
 457
 458#[derive(Clone, Copy, Debug, PartialEq, Eq)]
 459pub struct InlineDiagnosticsSettings {
 460    /// Whether or not to show inline diagnostics
 461    ///
 462    /// Default: false
 463    pub enabled: bool,
 464    /// Whether to only show the inline diagnostics after a delay after the
 465    /// last editor event.
 466    ///
 467    /// Default: 150
 468    pub update_debounce_ms: u64,
 469    /// The amount of padding between the end of the source line and the start
 470    /// of the inline diagnostic in units of columns.
 471    ///
 472    /// Default: 4
 473    pub padding: u32,
 474    /// The minimum column to display inline diagnostics. This setting can be
 475    /// used to horizontally align inline diagnostics at some position. Lines
 476    /// longer than this value will still push diagnostics further to the right.
 477    ///
 478    /// Default: 0
 479    pub min_column: u32,
 480
 481    pub max_severity: Option<DiagnosticSeverity>,
 482}
 483
 484#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 485pub struct LspPullDiagnosticsSettings {
 486    /// Whether to pull for diagnostics or not.
 487    ///
 488    /// Default: true
 489    pub enabled: bool,
 490    /// Minimum time to wait before pulling diagnostics from the language server(s).
 491    /// 0 turns the debounce off.
 492    ///
 493    /// Default: 50
 494    pub debounce_ms: u64,
 495}
 496
 497impl Settings for ProjectSettings {
 498    fn from_settings(content: &settings::SettingsContent) -> Self {
 499        let project = &content.project.clone();
 500        let diagnostics = content.diagnostics.as_ref().unwrap();
 501        let lsp_pull_diagnostics = diagnostics.lsp_pull_diagnostics.as_ref().unwrap();
 502        let inline_diagnostics = diagnostics.inline.as_ref().unwrap();
 503
 504        let git = content.git.as_ref().unwrap();
 505        let git_settings = GitSettings {
 506            git_gutter: git.git_gutter.unwrap(),
 507            gutter_debounce: git.gutter_debounce.unwrap_or_default(),
 508            inline_blame: {
 509                let inline = git.inline_blame.unwrap();
 510                InlineBlameSettings {
 511                    enabled: inline.enabled.unwrap(),
 512                    delay_ms: inline.delay_ms.unwrap(),
 513                    padding: inline.padding.unwrap(),
 514                    min_column: inline.min_column.unwrap(),
 515                    show_commit_summary: inline.show_commit_summary.unwrap(),
 516                }
 517            },
 518            blame: {
 519                let blame = git.blame.unwrap();
 520                BlameSettings {
 521                    show_avatar: blame.show_avatar.unwrap(),
 522                }
 523            },
 524            branch_picker: {
 525                let branch_picker = git.branch_picker.unwrap();
 526                BranchPickerSettings {
 527                    show_author_name: branch_picker.show_author_name.unwrap(),
 528                }
 529            },
 530            hunk_style: git.hunk_style.unwrap(),
 531            path_style: git.path_style.unwrap().into(),
 532        };
 533        Self {
 534            context_servers: project
 535                .context_servers
 536                .clone()
 537                .into_iter()
 538                .map(|(key, value)| (key, value.into()))
 539                .collect(),
 540            lsp: project
 541                .lsp
 542                .clone()
 543                .into_iter()
 544                .map(|(key, value)| (LanguageServerName(key.into()), value))
 545                .collect(),
 546            global_lsp_settings: GlobalLspSettings {
 547                button: content
 548                    .global_lsp_settings
 549                    .as_ref()
 550                    .unwrap()
 551                    .button
 552                    .unwrap(),
 553            },
 554            dap: project
 555                .dap
 556                .clone()
 557                .into_iter()
 558                .map(|(key, value)| (DebugAdapterName(key.into()), DapSettings::from(value)))
 559                .collect(),
 560            diagnostics: DiagnosticsSettings {
 561                button: diagnostics.button.unwrap(),
 562                include_warnings: diagnostics.include_warnings.unwrap(),
 563                lsp_pull_diagnostics: LspPullDiagnosticsSettings {
 564                    enabled: lsp_pull_diagnostics.enabled.unwrap(),
 565                    debounce_ms: lsp_pull_diagnostics.debounce_ms.unwrap().0,
 566                },
 567                inline: InlineDiagnosticsSettings {
 568                    enabled: inline_diagnostics.enabled.unwrap(),
 569                    update_debounce_ms: inline_diagnostics.update_debounce_ms.unwrap().0,
 570                    padding: inline_diagnostics.padding.unwrap(),
 571                    min_column: inline_diagnostics.min_column.unwrap(),
 572                    max_severity: inline_diagnostics.max_severity.map(Into::into),
 573                },
 574            },
 575            git: git_settings,
 576            node: content.node.clone().unwrap().into(),
 577            load_direnv: project.load_direnv.clone().unwrap(),
 578            session: SessionSettings {
 579                restore_unsaved_buffers: content.session.unwrap().restore_unsaved_buffers.unwrap(),
 580                trust_all_worktrees: content.session.unwrap().trust_all_worktrees.unwrap(),
 581            },
 582        }
 583    }
 584}
 585
 586pub enum SettingsObserverMode {
 587    Local(Arc<dyn Fs>),
 588    Remote,
 589}
 590
 591#[derive(Clone, Debug, PartialEq)]
 592pub enum SettingsObserverEvent {
 593    LocalSettingsUpdated(Result<PathBuf, InvalidSettingsError>),
 594    LocalTasksUpdated(Result<PathBuf, InvalidSettingsError>),
 595    LocalDebugScenariosUpdated(Result<PathBuf, InvalidSettingsError>),
 596}
 597
 598impl EventEmitter<SettingsObserverEvent> for SettingsObserver {}
 599
 600pub struct SettingsObserver {
 601    mode: SettingsObserverMode,
 602    downstream_client: Option<AnyProtoClient>,
 603    worktree_store: Entity<WorktreeStore>,
 604    project_id: u64,
 605    task_store: Entity<TaskStore>,
 606    pending_local_settings:
 607        HashMap<PathTrust, BTreeMap<(WorktreeId, Arc<RelPath>), Option<String>>>,
 608    _trusted_worktrees_watcher: Option<Subscription>,
 609    _user_settings_watcher: Option<Subscription>,
 610    _global_task_config_watcher: Task<()>,
 611    _global_debug_config_watcher: Task<()>,
 612}
 613
 614/// SettingsObserver observers changes to .zed/{settings, task}.json files in local worktrees
 615/// (or the equivalent protobuf messages from upstream) and updates local settings
 616/// and sends notifications downstream.
 617/// In ssh mode it also monitors ~/.config/zed/{settings, task}.json and sends the content
 618/// upstream.
 619impl SettingsObserver {
 620    pub fn init(client: &AnyProtoClient) {
 621        client.add_entity_message_handler(Self::handle_update_worktree_settings);
 622        client.add_entity_message_handler(Self::handle_update_user_settings);
 623    }
 624
 625    pub fn new_local(
 626        fs: Arc<dyn Fs>,
 627        worktree_store: Entity<WorktreeStore>,
 628        task_store: Entity<TaskStore>,
 629        cx: &mut Context<Self>,
 630    ) -> Self {
 631        cx.subscribe(&worktree_store, Self::on_worktree_store_event)
 632            .detach();
 633
 634        let _trusted_worktrees_watcher =
 635            TrustedWorktrees::try_get_global(cx).map(|trusted_worktrees| {
 636                cx.subscribe(
 637                    &trusted_worktrees,
 638                    move |settings_observer, _, e, cx| match e {
 639                        TrustedWorktreesEvent::Trusted(_, trusted_paths) => {
 640                            for trusted_path in trusted_paths {
 641                                if let Some(pending_local_settings) = settings_observer
 642                                    .pending_local_settings
 643                                    .remove(trusted_path)
 644                                {
 645                                    for ((worktree_id, directory_path), settings_contents) in
 646                                        pending_local_settings
 647                                    {
 648                                        apply_local_settings(
 649                                            worktree_id,
 650                                            &directory_path,
 651                                            LocalSettingsKind::Settings,
 652                                            &settings_contents,
 653                                            cx,
 654                                        );
 655                                        if let Some(downstream_client) =
 656                                            &settings_observer.downstream_client
 657                                        {
 658                                            downstream_client
 659                                                .send(proto::UpdateWorktreeSettings {
 660                                                    project_id: settings_observer.project_id,
 661                                                    worktree_id: worktree_id.to_proto(),
 662                                                    path: directory_path.to_proto(),
 663                                                    content: settings_contents,
 664                                                    kind: Some(
 665                                                        local_settings_kind_to_proto(
 666                                                            LocalSettingsKind::Settings,
 667                                                        )
 668                                                        .into(),
 669                                                    ),
 670                                                })
 671                                                .log_err();
 672                                        }
 673                                    }
 674                                }
 675                            }
 676                        }
 677                        TrustedWorktreesEvent::Restricted(..) => {}
 678                    },
 679                )
 680            });
 681
 682        Self {
 683            worktree_store,
 684            task_store,
 685            mode: SettingsObserverMode::Local(fs.clone()),
 686            downstream_client: None,
 687            _trusted_worktrees_watcher,
 688            pending_local_settings: HashMap::default(),
 689            _user_settings_watcher: None,
 690            project_id: REMOTE_SERVER_PROJECT_ID,
 691            _global_task_config_watcher: Self::subscribe_to_global_task_file_changes(
 692                fs.clone(),
 693                paths::tasks_file().clone(),
 694                cx,
 695            ),
 696            _global_debug_config_watcher: Self::subscribe_to_global_debug_scenarios_changes(
 697                fs.clone(),
 698                paths::debug_scenarios_file().clone(),
 699                cx,
 700            ),
 701        }
 702    }
 703
 704    pub fn new_remote(
 705        fs: Arc<dyn Fs>,
 706        worktree_store: Entity<WorktreeStore>,
 707        task_store: Entity<TaskStore>,
 708        upstream_client: Option<AnyProtoClient>,
 709        cx: &mut Context<Self>,
 710    ) -> Self {
 711        let mut user_settings_watcher = None;
 712        if cx.try_global::<SettingsStore>().is_some() {
 713            if let Some(upstream_client) = upstream_client {
 714                let mut user_settings = None;
 715                user_settings_watcher = Some(cx.observe_global::<SettingsStore>(move |_, cx| {
 716                    if let Some(new_settings) = cx.global::<SettingsStore>().raw_user_settings() {
 717                        if Some(new_settings) != user_settings.as_ref() {
 718                            if let Some(new_settings_string) =
 719                                serde_json::to_string(new_settings).ok()
 720                            {
 721                                user_settings = Some(new_settings.clone());
 722                                upstream_client
 723                                    .send(proto::UpdateUserSettings {
 724                                        project_id: REMOTE_SERVER_PROJECT_ID,
 725                                        contents: new_settings_string,
 726                                    })
 727                                    .log_err();
 728                            }
 729                        }
 730                    }
 731                }));
 732            }
 733        };
 734
 735        Self {
 736            worktree_store,
 737            task_store,
 738            mode: SettingsObserverMode::Remote,
 739            downstream_client: None,
 740            project_id: REMOTE_SERVER_PROJECT_ID,
 741            _trusted_worktrees_watcher: None,
 742            pending_local_settings: HashMap::default(),
 743            _user_settings_watcher: user_settings_watcher,
 744            _global_task_config_watcher: Self::subscribe_to_global_task_file_changes(
 745                fs.clone(),
 746                paths::tasks_file().clone(),
 747                cx,
 748            ),
 749            _global_debug_config_watcher: Self::subscribe_to_global_debug_scenarios_changes(
 750                fs.clone(),
 751                paths::debug_scenarios_file().clone(),
 752                cx,
 753            ),
 754        }
 755    }
 756
 757    pub fn shared(
 758        &mut self,
 759        project_id: u64,
 760        downstream_client: AnyProtoClient,
 761        cx: &mut Context<Self>,
 762    ) {
 763        self.project_id = project_id;
 764        self.downstream_client = Some(downstream_client.clone());
 765
 766        let store = cx.global::<SettingsStore>();
 767        for worktree in self.worktree_store.read(cx).worktrees() {
 768            let worktree_id = worktree.read(cx).id().to_proto();
 769            for (path, content) in store.local_settings(worktree.read(cx).id()) {
 770                let content = serde_json::to_string(&content).unwrap();
 771                downstream_client
 772                    .send(proto::UpdateWorktreeSettings {
 773                        project_id,
 774                        worktree_id,
 775                        path: path.to_proto(),
 776                        content: Some(content),
 777                        kind: Some(
 778                            local_settings_kind_to_proto(LocalSettingsKind::Settings).into(),
 779                        ),
 780                    })
 781                    .log_err();
 782            }
 783            for (path, content, _) in store.local_editorconfig_settings(worktree.read(cx).id()) {
 784                downstream_client
 785                    .send(proto::UpdateWorktreeSettings {
 786                        project_id,
 787                        worktree_id,
 788                        path: path.to_proto(),
 789                        content: Some(content),
 790                        kind: Some(
 791                            local_settings_kind_to_proto(LocalSettingsKind::Editorconfig).into(),
 792                        ),
 793                    })
 794                    .log_err();
 795            }
 796        }
 797    }
 798
 799    pub fn unshared(&mut self, _: &mut Context<Self>) {
 800        self.downstream_client = None;
 801    }
 802
 803    async fn handle_update_worktree_settings(
 804        this: Entity<Self>,
 805        envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,
 806        mut cx: AsyncApp,
 807    ) -> anyhow::Result<()> {
 808        let kind = match envelope.payload.kind {
 809            Some(kind) => proto::LocalSettingsKind::from_i32(kind)
 810                .with_context(|| format!("unknown kind {kind}"))?,
 811            None => proto::LocalSettingsKind::Settings,
 812        };
 813        let path = RelPath::from_proto(&envelope.payload.path)?;
 814        this.update(&mut cx, |this, cx| {
 815            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
 816            let Some(worktree) = this
 817                .worktree_store
 818                .read(cx)
 819                .worktree_for_id(worktree_id, cx)
 820            else {
 821                return;
 822            };
 823
 824            this.update_settings(
 825                worktree,
 826                [(
 827                    path,
 828                    local_settings_kind_from_proto(kind),
 829                    envelope.payload.content,
 830                )],
 831                cx,
 832            );
 833        })?;
 834        Ok(())
 835    }
 836
 837    async fn handle_update_user_settings(
 838        _: Entity<Self>,
 839        envelope: TypedEnvelope<proto::UpdateUserSettings>,
 840        cx: AsyncApp,
 841    ) -> anyhow::Result<()> {
 842        cx.update_global(|settings_store: &mut SettingsStore, cx| {
 843            settings_store
 844                .set_user_settings(&envelope.payload.contents, cx)
 845                .result()
 846                .context("setting new user settings")?;
 847            anyhow::Ok(())
 848        })??;
 849        Ok(())
 850    }
 851
 852    fn on_worktree_store_event(
 853        &mut self,
 854        _: Entity<WorktreeStore>,
 855        event: &WorktreeStoreEvent,
 856        cx: &mut Context<Self>,
 857    ) {
 858        match event {
 859            WorktreeStoreEvent::WorktreeAdded(worktree) => cx
 860                .subscribe(worktree, |this, worktree, event, cx| {
 861                    if let worktree::Event::UpdatedEntries(changes) = event {
 862                        this.update_local_worktree_settings(&worktree, changes, cx)
 863                    }
 864                })
 865                .detach(),
 866            WorktreeStoreEvent::WorktreeRemoved(_, worktree_id) => {
 867                cx.update_global::<SettingsStore, _>(|store, cx| {
 868                    store.clear_local_settings(*worktree_id, cx).log_err();
 869                });
 870            }
 871            _ => {}
 872        }
 873    }
 874
 875    fn update_local_worktree_settings(
 876        &mut self,
 877        worktree: &Entity<Worktree>,
 878        changes: &UpdatedEntriesSet,
 879        cx: &mut Context<Self>,
 880    ) {
 881        let SettingsObserverMode::Local(fs) = &self.mode else {
 882            return;
 883        };
 884
 885        let mut settings_contents = Vec::new();
 886        for (path, _, change) in changes.iter() {
 887            let (settings_dir, kind) = if path.ends_with(local_settings_file_relative_path()) {
 888                let settings_dir = path
 889                    .ancestors()
 890                    .nth(local_settings_file_relative_path().components().count())
 891                    .unwrap()
 892                    .into();
 893                (settings_dir, LocalSettingsKind::Settings)
 894            } else if path.ends_with(local_tasks_file_relative_path()) {
 895                let settings_dir = path
 896                    .ancestors()
 897                    .nth(
 898                        local_tasks_file_relative_path()
 899                            .components()
 900                            .count()
 901                            .saturating_sub(1),
 902                    )
 903                    .unwrap()
 904                    .into();
 905                (settings_dir, LocalSettingsKind::Tasks)
 906            } else if path.ends_with(local_vscode_tasks_file_relative_path()) {
 907                let settings_dir = path
 908                    .ancestors()
 909                    .nth(
 910                        local_vscode_tasks_file_relative_path()
 911                            .components()
 912                            .count()
 913                            .saturating_sub(1),
 914                    )
 915                    .unwrap()
 916                    .into();
 917                (settings_dir, LocalSettingsKind::Tasks)
 918            } else if path.ends_with(local_debug_file_relative_path()) {
 919                let settings_dir = path
 920                    .ancestors()
 921                    .nth(
 922                        local_debug_file_relative_path()
 923                            .components()
 924                            .count()
 925                            .saturating_sub(1),
 926                    )
 927                    .unwrap()
 928                    .into();
 929                (settings_dir, LocalSettingsKind::Debug)
 930            } else if path.ends_with(local_vscode_launch_file_relative_path()) {
 931                let settings_dir = path
 932                    .ancestors()
 933                    .nth(
 934                        local_vscode_tasks_file_relative_path()
 935                            .components()
 936                            .count()
 937                            .saturating_sub(1),
 938                    )
 939                    .unwrap()
 940                    .into();
 941                (settings_dir, LocalSettingsKind::Debug)
 942            } else if path.ends_with(RelPath::unix(EDITORCONFIG_NAME).unwrap()) {
 943                let Some(settings_dir) = path.parent().map(Arc::from) else {
 944                    continue;
 945                };
 946                (settings_dir, LocalSettingsKind::Editorconfig)
 947            } else {
 948                continue;
 949            };
 950
 951            let removed = change == &PathChange::Removed;
 952            let fs = fs.clone();
 953            let abs_path = worktree.read(cx).absolutize(path);
 954            settings_contents.push(async move {
 955                (
 956                    settings_dir,
 957                    kind,
 958                    if removed {
 959                        None
 960                    } else {
 961                        Some(
 962                            async move {
 963                                let content = fs.load(&abs_path).await?;
 964                                if abs_path.ends_with(local_vscode_tasks_file_relative_path().as_std_path()) {
 965                                    let vscode_tasks =
 966                                        parse_json_with_comments::<VsCodeTaskFile>(&content)
 967                                            .with_context(|| {
 968                                                format!("parsing VSCode tasks, file {abs_path:?}")
 969                                            })?;
 970                                    let zed_tasks = TaskTemplates::try_from(vscode_tasks)
 971                                        .with_context(|| {
 972                                            format!(
 973                                        "converting VSCode tasks into Zed ones, file {abs_path:?}"
 974                                    )
 975                                        })?;
 976                                    serde_json::to_string(&zed_tasks).with_context(|| {
 977                                        format!(
 978                                            "serializing Zed tasks into JSON, file {abs_path:?}"
 979                                        )
 980                                    })
 981                                } else if abs_path.ends_with(local_vscode_launch_file_relative_path().as_std_path()) {
 982                                    let vscode_tasks =
 983                                        parse_json_with_comments::<VsCodeDebugTaskFile>(&content)
 984                                            .with_context(|| {
 985                                                format!("parsing VSCode debug tasks, file {abs_path:?}")
 986                                            })?;
 987                                    let zed_tasks = DebugTaskFile::try_from(vscode_tasks)
 988                                        .with_context(|| {
 989                                            format!(
 990                                        "converting VSCode debug tasks into Zed ones, file {abs_path:?}"
 991                                    )
 992                                        })?;
 993                                    serde_json::to_string(&zed_tasks).with_context(|| {
 994                                        format!(
 995                                            "serializing Zed tasks into JSON, file {abs_path:?}"
 996                                        )
 997                                    })
 998                                } else {
 999                                    Ok(content)
1000                                }
1001                            }
1002                            .await,
1003                        )
1004                    },
1005                )
1006            });
1007        }
1008
1009        if settings_contents.is_empty() {
1010            return;
1011        }
1012
1013        let worktree = worktree.clone();
1014        cx.spawn(async move |this, cx| {
1015            let settings_contents: Vec<(Arc<RelPath>, _, _)> =
1016                futures::future::join_all(settings_contents).await;
1017            cx.update(|cx| {
1018                this.update(cx, |this, cx| {
1019                    this.update_settings(
1020                        worktree,
1021                        settings_contents.into_iter().map(|(path, kind, content)| {
1022                            (path, kind, content.and_then(|c| c.log_err()))
1023                        }),
1024                        cx,
1025                    )
1026                })
1027            })
1028        })
1029        .detach();
1030    }
1031
1032    fn update_settings(
1033        &mut self,
1034        worktree: Entity<Worktree>,
1035        settings_contents: impl IntoIterator<Item = (Arc<RelPath>, LocalSettingsKind, Option<String>)>,
1036        cx: &mut Context<Self>,
1037    ) {
1038        let worktree_id = worktree.read(cx).id();
1039        let remote_worktree_id = worktree.read(cx).id();
1040        let task_store = self.task_store.clone();
1041        let can_trust_worktree = OnceCell::new();
1042        for (directory, kind, file_content) in settings_contents {
1043            let mut applied = true;
1044            match kind {
1045                LocalSettingsKind::Settings => {
1046                    if *can_trust_worktree.get_or_init(|| {
1047                        if let Some(trusted_worktrees) = TrustedWorktrees::try_get_global(cx) {
1048                            trusted_worktrees.update(cx, |trusted_worktrees, cx| {
1049                                trusted_worktrees.can_trust(&self.worktree_store, worktree_id, cx)
1050                            })
1051                        } else {
1052                            true
1053                        }
1054                    }) {
1055                        apply_local_settings(worktree_id, &directory, kind, &file_content, cx)
1056                    } else {
1057                        applied = false;
1058                        self.pending_local_settings
1059                            .entry(PathTrust::Worktree(worktree_id))
1060                            .or_default()
1061                            .insert((worktree_id, directory.clone()), file_content.clone());
1062                    }
1063                }
1064                LocalSettingsKind::Editorconfig => {
1065                    apply_local_settings(worktree_id, &directory, kind, &file_content, cx)
1066                }
1067                LocalSettingsKind::Tasks => {
1068                    let result = task_store.update(cx, |task_store, cx| {
1069                        task_store.update_user_tasks(
1070                            TaskSettingsLocation::Worktree(SettingsLocation {
1071                                worktree_id,
1072                                path: directory.as_ref(),
1073                            }),
1074                            file_content.as_deref(),
1075                            cx,
1076                        )
1077                    });
1078
1079                    match result {
1080                        Err(InvalidSettingsError::Tasks { path, message }) => {
1081                            log::error!("Failed to set local tasks in {path:?}: {message:?}");
1082                            cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
1083                                InvalidSettingsError::Tasks { path, message },
1084                            )));
1085                        }
1086                        Err(e) => {
1087                            log::error!("Failed to set local tasks: {e}");
1088                        }
1089                        Ok(()) => {
1090                            cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(directory
1091                                .as_std_path()
1092                                .join(task_file_name()))));
1093                        }
1094                    }
1095                }
1096                LocalSettingsKind::Debug => {
1097                    let result = task_store.update(cx, |task_store, cx| {
1098                        task_store.update_user_debug_scenarios(
1099                            TaskSettingsLocation::Worktree(SettingsLocation {
1100                                worktree_id,
1101                                path: directory.as_ref(),
1102                            }),
1103                            file_content.as_deref(),
1104                            cx,
1105                        )
1106                    });
1107
1108                    match result {
1109                        Err(InvalidSettingsError::Debug { path, message }) => {
1110                            log::error!(
1111                                "Failed to set local debug scenarios in {path:?}: {message:?}"
1112                            );
1113                            cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
1114                                InvalidSettingsError::Debug { path, message },
1115                            )));
1116                        }
1117                        Err(e) => {
1118                            log::error!("Failed to set local tasks: {e}");
1119                        }
1120                        Ok(()) => {
1121                            cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(directory
1122                                .as_std_path()
1123                                .join(task_file_name()))));
1124                        }
1125                    }
1126                }
1127            };
1128
1129            if applied {
1130                if let Some(downstream_client) = &self.downstream_client {
1131                    downstream_client
1132                        .send(proto::UpdateWorktreeSettings {
1133                            project_id: self.project_id,
1134                            worktree_id: remote_worktree_id.to_proto(),
1135                            path: directory.to_proto(),
1136                            content: file_content.clone(),
1137                            kind: Some(local_settings_kind_to_proto(kind).into()),
1138                        })
1139                        .log_err();
1140                }
1141            }
1142        }
1143    }
1144
1145    fn subscribe_to_global_task_file_changes(
1146        fs: Arc<dyn Fs>,
1147        file_path: PathBuf,
1148        cx: &mut Context<Self>,
1149    ) -> Task<()> {
1150        let mut user_tasks_file_rx =
1151            watch_config_file(cx.background_executor(), fs, file_path.clone());
1152        let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
1153        let weak_entry = cx.weak_entity();
1154        cx.spawn(async move |settings_observer, cx| {
1155            let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
1156                settings_observer.task_store.clone()
1157            }) else {
1158                return;
1159            };
1160            if let Some(user_tasks_content) = user_tasks_content {
1161                let Ok(()) = task_store.update(cx, |task_store, cx| {
1162                    task_store
1163                        .update_user_tasks(
1164                            TaskSettingsLocation::Global(&file_path),
1165                            Some(&user_tasks_content),
1166                            cx,
1167                        )
1168                        .log_err();
1169                }) else {
1170                    return;
1171                };
1172            }
1173            while let Some(user_tasks_content) = user_tasks_file_rx.next().await {
1174                let Ok(result) = task_store.update(cx, |task_store, cx| {
1175                    task_store.update_user_tasks(
1176                        TaskSettingsLocation::Global(&file_path),
1177                        Some(&user_tasks_content),
1178                        cx,
1179                    )
1180                }) else {
1181                    break;
1182                };
1183
1184                weak_entry
1185                    .update(cx, |_, cx| match result {
1186                        Ok(()) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Ok(
1187                            file_path.clone()
1188                        ))),
1189                        Err(err) => cx.emit(SettingsObserverEvent::LocalTasksUpdated(Err(
1190                            InvalidSettingsError::Tasks {
1191                                path: file_path.clone(),
1192                                message: err.to_string(),
1193                            },
1194                        ))),
1195                    })
1196                    .ok();
1197            }
1198        })
1199    }
1200    fn subscribe_to_global_debug_scenarios_changes(
1201        fs: Arc<dyn Fs>,
1202        file_path: PathBuf,
1203        cx: &mut Context<Self>,
1204    ) -> Task<()> {
1205        let mut user_tasks_file_rx =
1206            watch_config_file(cx.background_executor(), fs, file_path.clone());
1207        let user_tasks_content = cx.background_executor().block(user_tasks_file_rx.next());
1208        let weak_entry = cx.weak_entity();
1209        cx.spawn(async move |settings_observer, cx| {
1210            let Ok(task_store) = settings_observer.read_with(cx, |settings_observer, _| {
1211                settings_observer.task_store.clone()
1212            }) else {
1213                return;
1214            };
1215            if let Some(user_tasks_content) = user_tasks_content {
1216                let Ok(()) = task_store.update(cx, |task_store, cx| {
1217                    task_store
1218                        .update_user_debug_scenarios(
1219                            TaskSettingsLocation::Global(&file_path),
1220                            Some(&user_tasks_content),
1221                            cx,
1222                        )
1223                        .log_err();
1224                }) else {
1225                    return;
1226                };
1227            }
1228            while let Some(user_tasks_content) = user_tasks_file_rx.next().await {
1229                let Ok(result) = task_store.update(cx, |task_store, cx| {
1230                    task_store.update_user_debug_scenarios(
1231                        TaskSettingsLocation::Global(&file_path),
1232                        Some(&user_tasks_content),
1233                        cx,
1234                    )
1235                }) else {
1236                    break;
1237                };
1238
1239                weak_entry
1240                    .update(cx, |_, cx| match result {
1241                        Ok(()) => cx.emit(SettingsObserverEvent::LocalDebugScenariosUpdated(Ok(
1242                            file_path.clone(),
1243                        ))),
1244                        Err(err) => cx.emit(SettingsObserverEvent::LocalDebugScenariosUpdated(
1245                            Err(InvalidSettingsError::Tasks {
1246                                path: file_path.clone(),
1247                                message: err.to_string(),
1248                            }),
1249                        )),
1250                    })
1251                    .ok();
1252            }
1253        })
1254    }
1255}
1256
1257fn apply_local_settings(
1258    worktree_id: WorktreeId,
1259    directory: &Arc<RelPath>,
1260    kind: LocalSettingsKind,
1261    file_content: &Option<String>,
1262    cx: &mut Context<'_, SettingsObserver>,
1263) {
1264    cx.update_global::<SettingsStore, _>(|store, cx| {
1265        let result = store.set_local_settings(
1266            worktree_id,
1267            directory.clone(),
1268            kind,
1269            file_content.as_deref(),
1270            cx,
1271        );
1272
1273        match result {
1274            Err(InvalidSettingsError::LocalSettings { path, message }) => {
1275                log::error!("Failed to set local settings in {path:?}: {message}");
1276                cx.emit(SettingsObserverEvent::LocalSettingsUpdated(Err(
1277                    InvalidSettingsError::LocalSettings { path, message },
1278                )));
1279            }
1280            Err(e) => log::error!("Failed to set local settings: {e}"),
1281            Ok(()) => cx.emit(SettingsObserverEvent::LocalSettingsUpdated(Ok(directory
1282                .as_std_path()
1283                .join(local_settings_file_relative_path().as_std_path())))),
1284        }
1285    })
1286}
1287
1288pub fn local_settings_kind_from_proto(kind: proto::LocalSettingsKind) -> LocalSettingsKind {
1289    match kind {
1290        proto::LocalSettingsKind::Settings => LocalSettingsKind::Settings,
1291        proto::LocalSettingsKind::Tasks => LocalSettingsKind::Tasks,
1292        proto::LocalSettingsKind::Editorconfig => LocalSettingsKind::Editorconfig,
1293        proto::LocalSettingsKind::Debug => LocalSettingsKind::Debug,
1294    }
1295}
1296
1297pub fn local_settings_kind_to_proto(kind: LocalSettingsKind) -> proto::LocalSettingsKind {
1298    match kind {
1299        LocalSettingsKind::Settings => proto::LocalSettingsKind::Settings,
1300        LocalSettingsKind::Tasks => proto::LocalSettingsKind::Tasks,
1301        LocalSettingsKind::Editorconfig => proto::LocalSettingsKind::Editorconfig,
1302        LocalSettingsKind::Debug => proto::LocalSettingsKind::Debug,
1303    }
1304}
1305
1306#[derive(Debug, Clone)]
1307pub struct DapSettings {
1308    pub binary: DapBinary,
1309    pub args: Vec<String>,
1310    pub env: HashMap<String, String>,
1311}
1312
1313impl From<DapSettingsContent> for DapSettings {
1314    fn from(content: DapSettingsContent) -> Self {
1315        DapSettings {
1316            binary: content
1317                .binary
1318                .map_or_else(|| DapBinary::Default, |binary| DapBinary::Custom(binary)),
1319            args: content.args.unwrap_or_default(),
1320            env: content.env.unwrap_or_default(),
1321        }
1322    }
1323}
1324
1325#[derive(Debug, Clone)]
1326pub enum DapBinary {
1327    Default,
1328    Custom(String),
1329}