project_settings.rs

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