634 lines · 22.0 KB · Rust
RawDownload
use anyhow::Context;use collections::HashMap;use fs::Fs;use gpui::{AppContext, AsyncAppContext, BorrowAppContext, EventEmitter, Model, ModelContext};use language::LanguageServerName;use paths::{    local_settings_file_relative_path, local_tasks_file_relative_path,    local_vscode_tasks_file_relative_path, EDITORCONFIG_NAME,};use rpc::{proto, 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, PathBuf},    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 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 GitSettings {    /// Whether or not to show the git gutter.    ///    /// Default: tracked_files    pub git_gutter: Option<GitGutterSetting>,    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,        }    }} #[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>,} 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 AppContext,    ) -> anyhow::Result<Self> {        sources.json_merge()    }} pub enum SettingsObserverMode {    Local(Arc<dyn Fs>),    Ssh(AnyProtoClient),    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: Model<WorktreeStore>,    project_id: u64,    task_store: Model<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_model_message_handler(Self::handle_update_worktree_settings);        client.add_model_message_handler(Self::handle_update_user_settings)    }     pub fn new_local(        fs: Arc<dyn Fs>,        worktree_store: Model<WorktreeStore>,        task_store: Model<TaskStore>,        cx: &mut ModelContext<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_ssh(        client: AnyProtoClient,        worktree_store: Model<WorktreeStore>,        task_store: Model<TaskStore>,        cx: &mut ModelContext<Self>,    ) -> Self {        let this = Self {            worktree_store,            task_store,            mode: SettingsObserverMode::Ssh(client.clone()),            downstream_client: None,            project_id: 0,        };        this.maintain_ssh_settings(client, cx);        this    }     pub fn new_remote(        worktree_store: Model<WorktreeStore>,        task_store: Model<TaskStore>,        _: &mut ModelContext<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 ModelContext<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_string_lossy().into(),                        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_string_lossy().into(),                        content: Some(content),                        kind: Some(                            local_settings_kind_to_proto(LocalSettingsKind::Editorconfig).into(),                        ),                    })                    .log_err();            }        }    }     pub fn unshared(&mut self, _: &mut ModelContext<Self>) {        self.downstream_client = None;    }     async fn handle_update_worktree_settings(        this: Model<Self>,        envelope: TypedEnvelope<proto::UpdateWorktreeSettings>,        mut cx: AsyncAppContext,    ) -> 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,                [(                    PathBuf::from(&envelope.payload.path).into(),                    local_settings_kind_from_proto(kind),                    envelope.payload.content,                )],                cx,            );        })?;        Ok(())    }     pub async fn handle_update_user_settings(        settings_observer: Model<Self>,        envelope: TypedEnvelope<proto::UpdateUserSettings>,        mut cx: AsyncAppContext,    ) -> anyhow::Result<()> {        match envelope.payload.kind() {            proto::update_user_settings::Kind::Settings => {                cx.update_global(move |settings_store: &mut SettingsStore, cx| {                    settings_store.set_user_settings(&envelope.payload.content, cx)                })            }            proto::update_user_settings::Kind::Tasks => {                settings_observer.update(&mut cx, |settings_observer, cx| {                    settings_observer.task_store.update(cx, |task_store, cx| {                        task_store.update_user_tasks(None, Some(&envelope.payload.content), cx)                    })                })            }        }??;         Ok(())    }     pub fn maintain_ssh_settings(&self, ssh: AnyProtoClient, cx: &mut ModelContext<Self>) {        let settings_store = cx.global::<SettingsStore>();         let mut settings = settings_store.raw_user_settings().clone();        if let Some(content) = serde_json::to_string(&settings).log_err() {            ssh.send(proto::UpdateUserSettings {                project_id: 0,                content,                kind: Some(proto::LocalSettingsKind::Settings.into()),            })            .log_err();        }         let weak_client = ssh.downgrade();        cx.observe_global::<SettingsStore>(move |_, cx| {            let new_settings = cx.global::<SettingsStore>().raw_user_settings();            if &settings != new_settings {                settings = new_settings.clone()            }            if let Some(content) = serde_json::to_string(&settings).log_err() {                if let Some(ssh) = weak_client.upgrade() {                    ssh.send(proto::UpdateUserSettings {                        project_id: 0,                        content,                        kind: Some(proto::LocalSettingsKind::Settings.into()),                    })                    .log_err();                }            }        })        .detach();    }     fn on_worktree_store_event(        &mut self,        _: Model<WorktreeStore>,        event: &WorktreeStoreEvent,        cx: &mut ModelContext<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: &Model<Worktree>,        changes: &UpdatedEntriesSet,        cx: &mut ModelContext<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;                }            };            settings_contents.push(async move {                (                    settings_dir,                    kind,                    if removed {                        None                    } else {                        Some(                            async move {                                let content = fs.load(&abs_path).await?;                                if abs_path.ends_with(local_vscode_tasks_file_relative_path()) {                                    let vscode_tasks =

Lines 1–500 of 634.