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