968 lines · 34.8 KB · Rust
RawDownload
use anyhow::Context as _;use collections::HashMap;use context_server::ContextServerCommand;use dap::adapters::DebugAdapterName;use fs::Fs;use futures::StreamExt as _;use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter, Task};use lsp::LanguageServerName;use paths::{    EDITORCONFIG_NAME, local_debug_file_relative_path, local_settings_file_relative_path,    local_tasks_file_relative_path, local_vscode_launch_file_relative_path,    local_vscode_tasks_file_relative_path, task_file_name,};use rpc::{    AnyProtoClient, TypedEnvelope,    proto::{self, FromProto, ToProto},};use schemars::JsonSchema;use serde::{Deserialize, Serialize};use settings::{    InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,    SettingsStore, parse_json_with_comments, watch_config_file,};use std::{    path::{Path, PathBuf},    sync::Arc,    time::Duration,};use task::{DebugTaskFile, TaskTemplates, VsCodeDebugTaskFile, VsCodeTaskFile};use util::{ResultExt, serde::default_true};use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::{    task_store::{TaskSettingsLocation, TaskStore},    worktree_store::{WorktreeStore, WorktreeStoreEvent},}; #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]#[schemars(deny_unknown_fields)]pub struct ProjectSettings {    /// Configuration for language servers.    ///    /// The following settings can be overridden for specific language servers:    /// - initialization_options    ///    /// To override settings for a language, add an entry for that language server's    /// name to the lsp value.    /// Default: null    #[serde(default)]    pub lsp: HashMap<LanguageServerName, LspSettings>,     /// Configuration for Debugger-related features    #[serde(default)]    pub dap: HashMap<DebugAdapterName, DapSettings>,     /// Settings for context servers used for AI-related features.    #[serde(default)]    pub context_servers: HashMap<Arc<str>, ContextServerConfiguration>,     /// Configuration for Diagnostics-related features.    #[serde(default)]    pub diagnostics: DiagnosticsSettings,     /// Configuration for Git-related features    #[serde(default)]    pub git: GitSettings,     /// Configuration for Node-related features    #[serde(default)]    pub node: NodeBinarySettings,     /// Configuration for how direnv configuration should be loaded    #[serde(default)]    pub load_direnv: DirenvSettings,     /// Configuration for session-related features    #[serde(default)]    pub session: SessionSettings,} #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub struct DapSettings {    pub binary: Option<String>,} #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug, Default)]pub struct ContextServerConfiguration {    /// The command to run this context server.    ///    /// This will override the command set by an extension.    pub command: Option<ContextServerCommand>,    /// The settings for this context server.    ///    /// Consult the documentation for the context server to see what settings    /// are supported.    pub settings: Option<serde_json::Value>,} #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]pub struct NodeBinarySettings {    /// The path to the Node binary.    pub path: Option<String>,    /// The path to the npm binary Zed should use (defaults to `.path/../npm`).    pub npm_path: Option<String>,    /// If enabled, Zed will download its own copy of Node.    #[serde(default)]    pub ignore_system_version: bool,} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum DirenvSettings {    /// Load direnv configuration through a shell hook    ShellHook,    /// Load direnv configuration directly using `direnv export json`    #[default]    Direct,} #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]#[serde(default)]pub struct DiagnosticsSettings {    /// Whether to show the project diagnostics button in the status bar.    pub button: bool,     /// Whether or not to include warning diagnostics.    pub include_warnings: bool,     /// Settings for showing inline diagnostics.    pub inline: InlineDiagnosticsSettings,     /// Configuration, related to Rust language diagnostics.    pub cargo: Option<CargoDiagnosticsSettings>,} impl DiagnosticsSettings {    pub fn fetch_cargo_diagnostics(&self) -> bool {        self.cargo.as_ref().map_or(false, |cargo_diagnostics| {            cargo_diagnostics.fetch_cargo_diagnostics        })    }} #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]#[serde(default)]pub struct InlineDiagnosticsSettings {    /// Whether or not to show inline diagnostics    ///    /// Default: false    pub enabled: bool,    /// Whether to only show the inline diagnostics after a delay after the    /// last editor event.    ///    /// Default: 150    pub update_debounce_ms: u64,    /// The amount of padding between the end of the source line and the start    /// of the inline diagnostic in units of columns.    ///    /// Default: 4    pub padding: u32,    /// The minimum column to display inline diagnostics. This setting can be    /// used to horizontally align inline diagnostics at some position. Lines    /// longer than this value will still push diagnostics further to the right.    ///    /// Default: 0    pub min_column: u32,     pub max_severity: Option<DiagnosticSeverity>,} #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]pub struct CargoDiagnosticsSettings {    /// When enabled, Zed disables rust-analyzer's check on save and starts to query    /// Cargo diagnostics separately.    ///    /// Default: false    #[serde(default)]    pub fetch_cargo_diagnostics: bool,} #[derive(    Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema,)]#[serde(rename_all = "snake_case")]pub enum DiagnosticSeverity {    // No diagnostics are shown.    Off,    Error,    Warning,    Info,    Hint,} impl DiagnosticSeverity {    pub fn into_lsp(self) -> Option<lsp::DiagnosticSeverity> {        match self {            DiagnosticSeverity::Off => None,            DiagnosticSeverity::Error => Some(lsp::DiagnosticSeverity::ERROR),            DiagnosticSeverity::Warning => Some(lsp::DiagnosticSeverity::WARNING),            DiagnosticSeverity::Info => Some(lsp::DiagnosticSeverity::INFORMATION),            DiagnosticSeverity::Hint => Some(lsp::DiagnosticSeverity::HINT),        }    }} impl Default for DiagnosticsSettings {    fn default() -> Self {        Self {            button: true,            include_warnings: true,            inline: Default::default(),            cargo: Default::default(),        }    }} impl Default for InlineDiagnosticsSettings {    fn default() -> Self {        Self {            enabled: false,            update_debounce_ms: 150,            padding: 4,            min_column: 0,            max_severity: None,        }    }} #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]pub struct GitSettings {    /// Whether or not to show the git gutter.    ///    /// Default: tracked_files    pub git_gutter: Option<GitGutterSetting>,    /// Sets the debounce threshold (in milliseconds) after which changes are reflected in the git gutter.    ///    /// Default: null    pub gutter_debounce: Option<u64>,    /// Whether or not to show git blame data inline in    /// the currently focused line.    ///    /// Default: on    pub inline_blame: Option<InlineBlameSettings>,    /// How hunks are displayed visually in the editor.    ///    /// Default: staged_hollow    pub hunk_style: Option<GitHunkStyleSetting>,} impl GitSettings {    pub fn inline_blame_enabled(&self) -> bool {        #[allow(unknown_lints, clippy::manual_unwrap_or_default)]        match self.inline_blame {            Some(InlineBlameSettings { enabled, .. }) => enabled,            _ => false,        }    }     pub fn inline_blame_delay(&self) -> Option<Duration> {        match self.inline_blame {            Some(InlineBlameSettings {                delay_ms: Some(delay_ms),                ..            }) if delay_ms > 0 => Some(Duration::from_millis(delay_ms)),            _ => None,        }    }     pub fn show_inline_commit_summary(&self) -> bool {        match self.inline_blame {            Some(InlineBlameSettings {                show_commit_summary,                ..            }) => show_commit_summary,            _ => false,        }    }} #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum GitHunkStyleSetting {    /// Show unstaged hunks with a filled background and staged hunks hollow.    #[default]    StagedHollow,    /// Show unstaged hunks hollow and staged hunks with a filled background.    UnstagedHollow,} #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum GitGutterSetting {    /// Show git gutter in tracked files.    #[default]    TrackedFiles,    /// Hide git gutter    Hide,} #[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub struct InlineBlameSettings {    /// Whether or not to show git blame data inline in    /// the currently focused line.    ///    /// Default: true    #[serde(default = "default_true")]    pub enabled: bool,    /// Whether to only show the inline blame information    /// after a delay once the cursor stops moving.    ///    /// Default: 0    pub delay_ms: Option<u64>,    /// The minimum column number to show the inline blame information at    ///    /// Default: 0    pub min_column: Option<u32>,    /// Whether to show commit summary as part of the inline blame.    ///    /// Default: false    #[serde(default)]    pub show_commit_summary: bool,} #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]pub struct BinarySettings {    pub path: Option<String>,    pub arguments: Option<Vec<String>>,    // this can't be an FxHashMap because the extension APIs require the default SipHash    pub env: Option<std::collections::HashMap<String, String>>,    pub ignore_system_version: Option<bool>,} #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]#[serde(rename_all = "snake_case")]pub struct LspSettings {    pub binary: Option<BinarySettings>,    pub initialization_options: Option<serde_json::Value>,    pub settings: Option<serde_json::Value>,    /// If the server supports sending tasks over LSP extensions,    /// this setting can be used to enable or disable them in Zed.    /// Default: true    #[serde(default = "default_true")]    pub enable_lsp_tasks: bool,} impl Default for LspSettings {    fn default() -> Self {        Self {            binary: None,            initialization_options: None,            settings: None,            enable_lsp_tasks: true,        }    }} #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]pub struct SessionSettings {    /// Whether or not to restore unsaved buffers on restart.    ///    /// If this is true, user won't be prompted whether to save/discard    /// dirty files when closing the application.    ///    /// Default: true    pub restore_unsaved_buffers: bool,} impl Default for SessionSettings {    fn default() -> Self {        Self {            restore_unsaved_buffers: true,        }    }} impl Settings for ProjectSettings {    const KEY: Option<&'static str> = None;     type FileContent = Self;     fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {        sources.json_merge()    }     fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {        // this just sets the binary name instead of a full path so it relies on path lookup        // resolving to the one you want        vscode.enum_setting(            "npm.packageManager",            &mut current.node.npm_path,            |s| match s {                v @ ("npm" | "yarn" | "bun" | "pnpm") => Some(v.to_owned()),                _ => None,            },        );         if let Some(b) = vscode.read_bool("git.blame.editorDecoration.enabled") {            if let Some(blame) = current.git.inline_blame.as_mut() {                blame.enabled = b            } else {                current.git.inline_blame = Some(InlineBlameSettings {                    enabled: b,                    ..Default::default()                })            }        }         #[derive(Deserialize)]        struct VsCodeContextServerCommand {            command: String,            args: Option<Vec<String>>,            env: Option<HashMap<String, String>>,            // note: we don't support envFile and type        }        impl From<VsCodeContextServerCommand> for ContextServerCommand {            fn from(cmd: VsCodeContextServerCommand) -> Self {                Self {                    path: cmd.command,                    args: cmd.args.unwrap_or_default(),                    env: cmd.env,                }            }        }        if let Some(mcp) = vscode.read_value("mcp").and_then(|v| v.as_object()) {            current                .context_servers                .extend(mcp.iter().filter_map(|(k, v)| {                    Some((                        k.clone().into(),                        ContextServerConfiguration {                            command: Some(                                serde_json::from_value::<VsCodeContextServerCommand>(v.clone())                                    .ok()?                                    .into(),                            ),                            settings: None,                        },                    ))                }));        }         // TODO: translate lsp settings for rust-analyzer and other popular ones to old.lsp    }} pub enum SettingsObserverMode {    Local(Arc<dyn Fs>),    Remote,} #[derive(Clone, Debug, PartialEq)]pub enum SettingsObserverEvent {    LocalSettingsUpdated(Result<PathBuf, InvalidSettingsError>),    LocalTasksUpdated(Result<PathBuf, InvalidSettingsError>),} impl EventEmitter<SettingsObserverEvent> for SettingsObserver {} pub struct SettingsObserver {    mode: SettingsObserverMode,    downstream_client: Option<AnyProtoClient>,    worktree_store: Entity<WorktreeStore>,    project_id: u64,    task_store: Entity<TaskStore>,    _global_task_config_watcher: Task<()>,} /// SettingsObserver observers changes to .zed/{settings, task}.json files in local worktrees/// (or the equivalent protobuf messages from upstream) and updates local settings/// and sends notifications downstream./// In ssh mode it also monitors ~/.config/zed/{settings, task}.json and sends the content/// upstream.impl SettingsObserver {    pub fn init(client: &AnyProtoClient) {        client.add_entity_message_handler(Self::handle_update_worktree_settings);    }     pub fn new_local(        fs: Arc<dyn Fs>,        worktree_store: Entity<WorktreeStore>,        task_store: Entity<TaskStore>,        cx: &mut Context<Self>,    ) -> Self {        cx.subscribe(&worktree_store, Self::on_worktree_store_event)            .detach();         Self {            worktree_store,            task_store,            mode: SettingsObserverMode::Local(fs.clone()),            downstream_client: None,            project_id: 0,            _global_task_config_watcher: Self::subscribe_to_global_task_file_changes(                fs.clone(),                paths::tasks_file().clone(),                cx,            ),        }

Lines 1–500 of 968.