648 lines · 21.9 KB · Rust
RawDownload
use anyhow::Context as _;use collections::HashMap;use fs::Fs;use gpui::{App, AsyncApp, BorrowAppContext, Context, Entity, EventEmitter};use lsp::LanguageServerName;use paths::{    local_settings_file_relative_path, local_tasks_file_relative_path,    local_vscode_tasks_file_relative_path, EDITORCONFIG_NAME,};use rpc::{    proto::{self, FromProto, ToProto},    AnyProtoClient, TypedEnvelope,};use schemars::JsonSchema;use serde::{Deserialize, Serialize};use settings::{    parse_json_with_comments, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation,    SettingsSources, SettingsStore,};use std::{path::Path, sync::Arc, time::Duration};use task::{TaskTemplates, VsCodeTaskFile};use util::ResultExt;use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::{    task_store::TaskStore,    worktree_store::{WorktreeStore, WorktreeStoreEvent},}; #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]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 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)]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 disabled, Zed will download its own copy of Node.    #[serde(default)]    pub ignore_system_version: Option<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(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]pub struct DiagnosticsSettings {    /// Whether or not to include warning diagnostics    #[serde(default = "true_value")]    pub include_warnings: bool,     /// Settings for showing inline diagnostics    #[serde(default)]    pub inline: InlineDiagnosticsSettings,} #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]pub struct InlineDiagnosticsSettings {    /// Whether or not to show inline diagnostics    ///    /// Default: false    #[serde(default)]    pub enabled: bool,    /// Whether to only show the inline diaganostics after a delay after the    /// last editor event.    ///    /// Default: 150    #[serde(default = "default_inline_diagnostics_debounce_ms")]    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    #[serde(default = "default_inline_diagnostics_padding")]    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    #[serde(default)]    pub min_column: u32,     #[serde(default)]    pub max_severity: Option<DiagnosticSeverity>,} #[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]#[serde(rename_all = "snake_case")]pub enum DiagnosticSeverity {    Error,    Warning,    Info,    Hint,} impl Default for InlineDiagnosticsSettings {    fn default() -> Self {        Self {            enabled: false,            update_debounce_ms: default_inline_diagnostics_debounce_ms(),            padding: default_inline_diagnostics_padding(),            min_column: 0,            max_severity: None,        }    }} fn default_inline_diagnostics_debounce_ms() -> u64 {    150} fn default_inline_diagnostics_padding() -> u32 {    4} #[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>,} 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 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 = "true_value")]    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,} const fn true_value() -> bool {    true} #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]pub struct BinarySettings {    pub path: Option<String>,    pub arguments: Option<Vec<String>>,    pub ignore_system_version: Option<bool>,} #[derive(Clone, Debug, Default, 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>,} #[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()    }} pub enum SettingsObserverMode {    Local(Arc<dyn Fs>),    Remote,} #[derive(Clone, Debug, PartialEq)]pub enum SettingsObserverEvent {    LocalSettingsUpdated(Result<(), 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>,} /// 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),            downstream_client: None,            project_id: 0,        }    }     pub fn new_remote(        worktree_store: Entity<WorktreeStore>,        task_store: Entity<TaskStore>,        _: &mut Context<Self>,    ) -> Self {        Self {            worktree_store,            task_store,            mode: SettingsObserverMode::Remote,            downstream_client: None,            project_id: 0,        }    }     pub fn shared(        &mut self,        project_id: u64,        downstream_client: AnyProtoClient,        cx: &mut Context<Self>,    ) {        self.project_id = project_id;        self.downstream_client = Some(downstream_client.clone());         let store = cx.global::<SettingsStore>();        for worktree in self.worktree_store.read(cx).worktrees() {            let worktree_id = worktree.read(cx).id().to_proto();            for (path, content) in store.local_settings(worktree.read(cx).id()) {                downstream_client                    .send(proto::UpdateWorktreeSettings {                        project_id,                        worktree_id,                        path: path.to_proto(),                        content: Some(content),                        kind: Some(                            local_settings_kind_to_proto(LocalSettingsKind::Settings).into(),                        ),                    })                    .log_err();            }            for (path, content, _) in store.local_editorconfig_settings(worktree.read(cx).id()) {                downstream_client                    .send(proto::UpdateWorktreeSettings {                        project_id,                        worktree_id,                        path: path.to_proto(),                        content: Some(content),                        kind: Some(                            local_settings_kind_to_proto(LocalSettingsKind::Editorconfig).into(),                        ),                    })                    .log_err();            }        }    }     pub fn unshared(&mut self, _: &mut Context<Self>) {        self.downstream_client = None;    }     async fn handle_update_worktree_settings(        this: Entity<Self>,        envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,        mut cx: AsyncApp,    ) -> anyhow::Result<()> {        let kind = match envelope.payload.kind {            Some(kind) => proto::LocalSettingsKind::from_i32(kind)                .with_context(|| format!("unknown kind {kind}"))?,            None => proto::LocalSettingsKind::Settings,        };        this.update(&mut cx, |this, cx| {            let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);            let Some(worktree) = this                .worktree_store                .read(cx)                .worktree_for_id(worktree_id, cx)            else {                return;            };             this.update_settings(                worktree,                [(                    Arc::<Path>::from_proto(envelope.payload.path.clone()),                    local_settings_kind_from_proto(kind),                    envelope.payload.content,                )],                cx,            );        })?;        Ok(())    }     fn on_worktree_store_event(        &mut self,        _: Entity<WorktreeStore>,        event: &WorktreeStoreEvent,        cx: &mut Context<Self>,    ) {        if let WorktreeStoreEvent::WorktreeAdded(worktree) = event {            cx.subscribe(worktree, |this, worktree, event, cx| {                if let worktree::Event::UpdatedEntries(changes) = event {                    this.update_local_worktree_settings(&worktree, changes, cx)                }            })            .detach()        }    }     fn update_local_worktree_settings(        &mut self,        worktree: &Entity<Worktree>,        changes: &UpdatedEntriesSet,        cx: &mut Context<Self>,    ) {        let SettingsObserverMode::Local(fs) = &self.mode else {            return;        };         let mut settings_contents = Vec::new();        for (path, _, change) in changes.iter() {            let (settings_dir, kind) = if path.ends_with(local_settings_file_relative_path()) {                let settings_dir = Arc::<Path>::from(                    path.ancestors()                        .nth(local_settings_file_relative_path().components().count())                        .unwrap(),                );                (settings_dir, LocalSettingsKind::Settings)            } else if path.ends_with(local_tasks_file_relative_path()) {                let settings_dir = Arc::<Path>::from(                    path.ancestors()                        .nth(                            local_tasks_file_relative_path()                                .components()                                .count()                                .saturating_sub(1),                        )                        .unwrap(),                );                (settings_dir, LocalSettingsKind::Tasks)            } else if path.ends_with(local_vscode_tasks_file_relative_path()) {                let settings_dir = Arc::<Path>::from(                    path.ancestors()                        .nth(                            local_vscode_tasks_file_relative_path()                                .components()                                .count()                                .saturating_sub(1),                        )                        .unwrap(),                );                (settings_dir, LocalSettingsKind::Tasks)            } else if path.ends_with(EDITORCONFIG_NAME) {                let Some(settings_dir) = path.parent().map(Arc::from) else {                    continue;                };                (settings_dir, LocalSettingsKind::Editorconfig)            } else {                continue;            };             let removed = change == &PathChange::Removed;            let fs = fs.clone();            let abs_path = match worktree.read(cx).absolutize(path) {                Ok(abs_path) => abs_path,                Err(e) => {                    log::warn!("Cannot absolutize {path:?} received as {change:?} FS change: {e}");                    continue;

Lines 1–500 of 648.