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