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