2386 lines · 78.3 KB · Rust
RawDownload
use anyhow::{Context as _, Result};use collections::{BTreeMap, HashMap, btree_map, hash_map};use ec4rs::{ConfigParser, PropertiesSource, Section};use fs::Fs;use futures::{    FutureExt, StreamExt,    channel::{mpsc, oneshot},    future::LocalBoxFuture,};use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal}; use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};use schemars::{JsonSchema, json_schema};use serde_json::Value;use smallvec::SmallVec;use std::{    any::{Any, TypeId, type_name},    fmt::Debug,    ops::Range,    path::PathBuf,    rc::Rc,    str::{self, FromStr},    sync::Arc,};use util::{    ResultExt as _,    rel_path::RelPath,    schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema},}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{    ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent,    LanguageToSettingsMap, LspSettings, LspSettingsMap, ThemeName, VsCodeSettings, WorktreeId,    fallible_options,    merge_from::MergeFrom,    settings_content::{        ExtensionsSettingsContent, ProjectSettingsContent, SettingsContent, UserSettingsContent,    },}; use settings_json::{infer_json_indent_size, parse_json_with_comments, update_value_in_json_text}; pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/"; pub trait SettingsKey: 'static + Send + Sync {    /// The name of a key within the JSON file from which this setting should    /// be deserialized. If this is `None`, then the setting will be deserialized    /// from the root object.    const KEY: Option<&'static str>;     const FALLBACK_KEY: Option<&'static str> = None;} /// A value that can be defined as a user setting.////// Settings can be loaded from a combination of multiple JSON files.pub trait Settings: 'static + Send + Sync + Sized {    /// The name of the keys in the [`FileContent`](Self::FileContent) that should    /// always be written to a settings file, even if their value matches the default    /// value.    ///    /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag    /// is a "version" field that should always be persisted, even if the current    /// user settings match the current version of the settings.    const PRESERVED_KEYS: Option<&'static [&'static str]> = None;     /// Read the value from default.json.    ///    /// This function *should* panic if default values are missing,    /// and you should add a default to default.json for documentation.    fn from_settings(content: &SettingsContent) -> Self;     #[track_caller]    fn register(cx: &mut App)    where        Self: Sized,    {        SettingsStore::update_global(cx, |store, _| {            store.register_setting::<Self>();        });    }     #[track_caller]    fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self    where        Self: Sized,    {        cx.global::<SettingsStore>().get(path)    }     #[track_caller]    fn get_global(cx: &App) -> &Self    where        Self: Sized,    {        cx.global::<SettingsStore>().get(None)    }     #[track_caller]    fn try_get(cx: &App) -> Option<&Self>    where        Self: Sized,    {        if cx.has_global::<SettingsStore>() {            cx.global::<SettingsStore>().try_get(None)        } else {            None        }    }     #[track_caller]    fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R>    where        Self: Sized,    {        cx.try_read_global(|s: &SettingsStore, _| f(s.get(None)))    }     #[track_caller]    fn override_global(settings: Self, cx: &mut App)    where        Self: Sized,    {        cx.global_mut::<SettingsStore>().override_global(settings)    }} pub struct RegisteredSetting {    pub settings_value: fn() -> Box<dyn AnySettingValue>,    pub from_settings: fn(&SettingsContent) -> Box<dyn Any>,    pub id: fn() -> TypeId,} inventory::collect!(RegisteredSetting); #[derive(Clone, Copy, Debug)]pub struct SettingsLocation<'a> {    pub worktree_id: WorktreeId,    pub path: &'a RelPath,} pub struct SettingsStore {    setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,    default_settings: Rc<SettingsContent>,    user_settings: Option<UserSettingsContent>,    global_settings: Option<Box<SettingsContent>>,     extension_settings: Option<Box<SettingsContent>>,    server_settings: Option<Box<SettingsContent>>,     merged_settings: Rc<SettingsContent>,     local_settings: BTreeMap<(WorktreeId, Arc<RelPath>), SettingsContent>,    raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<RelPath>), (String, Option<Editorconfig>)>,     _setting_file_updates: Task<()>,    setting_file_updates_tx:        mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,    file_errors: BTreeMap<SettingsFile, SettingsParseResult>,} #[derive(Clone, PartialEq, Eq, Debug)]pub enum SettingsFile {    Default,    Global,    User,    Server,    /// Represents project settings in ssh projects as well as local projects    Project((WorktreeId, Arc<RelPath>)),} impl PartialOrd for SettingsFile {    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {        Some(self.cmp(other))    }} /// Sorted in order of precedenceimpl Ord for SettingsFile {    fn cmp(&self, other: &Self) -> std::cmp::Ordering {        use SettingsFile::*;        use std::cmp::Ordering;        match (self, other) {            (User, User) => Ordering::Equal,            (Server, Server) => Ordering::Equal,            (Default, Default) => Ordering::Equal,            (Project((id1, rel_path1)), Project((id2, rel_path2))) => id1                .cmp(id2)                .then_with(|| rel_path1.cmp(rel_path2).reverse()),            (Project(_), _) => Ordering::Less,            (_, Project(_)) => Ordering::Greater,            (Server, _) => Ordering::Less,            (_, Server) => Ordering::Greater,            (User, _) => Ordering::Less,            (_, User) => Ordering::Greater,            (Global, _) => Ordering::Less,            (_, Global) => Ordering::Greater,        }    }} #[derive(Clone)]pub struct Editorconfig {    pub is_root: bool,    pub sections: SmallVec<[Section; 5]>,} impl FromStr for Editorconfig {    type Err = anyhow::Error;     fn from_str(contents: &str) -> Result<Self, Self::Err> {        let parser = ConfigParser::new_buffered(contents.as_bytes())            .context("creating editorconfig parser")?;        let is_root = parser.is_root;        let sections = parser            .collect::<Result<SmallVec<_>, _>>()            .context("parsing editorconfig sections")?;        Ok(Self { is_root, sections })    }} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]pub enum LocalSettingsKind {    Settings,    Tasks,    Editorconfig,    Debug,} impl Global for SettingsStore {} #[doc(hidden)]#[derive(Debug)]pub struct SettingValue<T> {    #[doc(hidden)]    pub global_value: Option<T>,    #[doc(hidden)]    pub local_values: Vec<(WorktreeId, Arc<RelPath>, T)>,} #[doc(hidden)]pub trait AnySettingValue: 'static + Send + Sync {    fn setting_type_name(&self) -> &'static str;     fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any>;     fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;    fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>;    fn set_global_value(&mut self, value: Box<dyn Any>);    fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>);    fn clear_local_values(&mut self, root_id: WorktreeId);} /// Parameters that are used when generating some JSON schemas at runtime.pub struct SettingsJsonSchemaParams<'a> {    pub language_names: &'a [String],    pub font_names: &'a [String],    pub theme_names: &'a [SharedString],    pub icon_theme_names: &'a [SharedString],    pub lsp_adapter_names: &'a [String],} impl SettingsStore {    pub fn new(cx: &App, default_settings: &str) -> Self {        let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();        let default_settings: Rc<SettingsContent> =            parse_json_with_comments(default_settings).unwrap();        let mut this = Self {            setting_values: Default::default(),            default_settings: default_settings.clone(),            global_settings: None,            server_settings: None,            user_settings: None,            extension_settings: None,             merged_settings: default_settings,            local_settings: BTreeMap::default(),            raw_editorconfig_settings: BTreeMap::default(),            setting_file_updates_tx,            _setting_file_updates: cx.spawn(async move |cx| {                while let Some(setting_file_update) = setting_file_updates_rx.next().await {                    (setting_file_update)(cx.clone()).await.log_err();                }            }),            file_errors: BTreeMap::default(),        };         this.load_settings_types();         this    }     pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription {        cx.observe_global::<ActiveSettingsProfileName>(|cx| {            Self::update_global(cx, |store, cx| {                store.recompute_values(None, cx);            });        })    }     pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R    where        C: BorrowAppContext,    {        cx.update_global(f)    }     /// Add a new type of setting to the store.    pub fn register_setting<T: Settings>(&mut self) {        self.register_setting_internal(&RegisteredSetting {            settings_value: || {                Box::new(SettingValue::<T> {                    global_value: None,                    local_values: Vec::new(),                })            },            from_settings: |content| Box::new(T::from_settings(content)),            id: || TypeId::of::<T>(),        });    }     fn load_settings_types(&mut self) {        for registered_setting in inventory::iter::<RegisteredSetting>() {            self.register_setting_internal(registered_setting);        }    }     fn register_setting_internal(&mut self, registered_setting: &RegisteredSetting) {        let entry = self.setting_values.entry((registered_setting.id)());         if matches!(entry, hash_map::Entry::Occupied(_)) {            return;        }         let setting_value = entry.or_insert((registered_setting.settings_value)());        let value = (registered_setting.from_settings)(&self.merged_settings);        setting_value.set_global_value(value);    }     /// Get the value of a setting.    ///    /// Panics if the given setting type has not been registered, or if there is no    /// value for this setting.    pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T {        self.setting_values            .get(&TypeId::of::<T>())            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))            .value_for_path(path)            .downcast_ref::<T>()            .expect("no default value for setting type")    }     /// Get the value of a setting.    ///    /// Does not panic    pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> {        self.setting_values            .get(&TypeId::of::<T>())            .map(|value| value.value_for_path(path))            .and_then(|value| value.downcast_ref::<T>())    }     /// Get all values from project specific settings    pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<RelPath>, &T)> {        self.setting_values            .get(&TypeId::of::<T>())            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))            .all_local_values()            .into_iter()            .map(|(id, path, any)| {                (                    id,                    path,                    any.downcast_ref::<T>()                        .expect("wrong value type for setting"),                )            })            .collect()    }     /// Override the global value for a setting.    ///    /// The given value will be overwritten if the user settings file changes.    pub fn override_global<T: Settings>(&mut self, value: T) {        self.setting_values            .get_mut(&TypeId::of::<T>())            .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()))            .set_global_value(Box::new(value))    }     /// Get the user's settings content.    ///    /// For user-facing functionality use the typed setting interface.    /// (e.g. ProjectSettings::get_global(cx))    pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {        self.user_settings.as_ref()    }     /// Get the default settings content as a raw JSON value.    pub fn raw_default_settings(&self) -> &SettingsContent {        &self.default_settings    }     /// Get the configured settings profile names.    pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {        self.user_settings            .iter()            .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))    }     #[cfg(any(test, feature = "test-support"))]    pub fn test(cx: &mut App) -> Self {        Self::new(cx, &crate::test_settings())    }     /// Updates the value of a setting in the user's global configuration.    ///    /// This is only for tests. Normally, settings are only loaded from    /// JSON files.    #[cfg(any(test, feature = "test-support"))]    pub fn update_user_settings(        &mut self,        cx: &mut App,        update: impl FnOnce(&mut SettingsContent),    ) {        let mut content = self.user_settings.clone().unwrap_or_default().content;        update(&mut content);        let new_text = serde_json::to_string(&UserSettingsContent {            content,            ..Default::default()        })        .unwrap();        _ = self.set_user_settings(&new_text, cx);    }     pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {        match fs.load(paths::settings_file()).await {            result @ Ok(_) => result,            Err(err) => {                if let Some(e) = err.downcast_ref::<std::io::Error>()                    && e.kind() == std::io::ErrorKind::NotFound                {                    return Ok(crate::initial_user_settings_content().to_string());                }                Err(err)            }        }    }     fn update_settings_file_inner(        &self,        fs: Arc<dyn Fs>,        update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,    ) -> oneshot::Receiver<Result<()>> {        let (tx, rx) = oneshot::channel::<Result<()>>();        self.setting_file_updates_tx            .unbounded_send(Box::new(move |cx: AsyncApp| {                async move {                    let res = async move {                        let old_text = Self::load_settings(&fs).await?;                        let new_text = update(old_text, cx)?;                        let settings_path = paths::settings_file().as_path();                        if fs.is_file(settings_path).await {                            let resolved_path =                                fs.canonicalize(settings_path).await.with_context(|| {                                    format!(                                        "Failed to canonicalize settings path {:?}",                                        settings_path                                    )                                })?;                             fs.atomic_write(resolved_path.clone(), new_text)                                .await                                .with_context(|| {                                    format!("Failed to write settings to file {:?}", resolved_path)                                })?;                        } else {                            fs.atomic_write(settings_path.to_path_buf(), new_text)                                .await                                .with_context(|| {                                    format!("Failed to write settings to file {:?}", settings_path)                                })?;                        }                        anyhow::Ok(())                    }                    .await;                     let new_res = match &res {                        Ok(_) => anyhow::Ok(()),                        Err(e) => Err(anyhow::anyhow!("Failed to write settings to file {:?}", e)),                    };                     _ = tx.send(new_res);                    res                }                .boxed_local()            }))            .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))

Lines 1–500 of 2386.