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