project_settings.rs

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