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