Add the ability for extensions to provide language settings (#10296)

This PR adds the ability for extensions to provide certain language
settings via the language `config.toml`.

These settings are then merged in with the rest of the settings when the
language is loaded from the extension.

The language settings that are available are:

- `tab_size`
- `hard_tabs`
- `soft_wrap`

Additionally, for bundled languages we moved these settings out of the
`settings/default.json` and into their respective `config.toml`s .

For languages currently provided by extensions, we are leaving the
values in the `settings/default.json` temporarily until all released
versions of Zed are able to load these settings from the extension.

---

Along the way we ended up refactoring the `Settings::load` method
slightly, introducing a new `SettingsSources` struct to better convey
where the settings are being loaded from.

This makes it easier to load settings from specific locations/sets of
locations in an explicit way.

Release Notes:

- N/A

---------

Co-authored-by: Max <max@zed.dev>
Co-authored-by: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Marshall Bowers 2024-04-08 19:17:12 -04:00 committed by GitHub
parent 4a3032c5e5
commit 7c5bc3c26f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 349 additions and 338 deletions

View file

@ -556,18 +556,10 @@
"C": { "C": {
"format_on_save": "off" "format_on_save": "off"
}, },
"Plain Text": {
"soft_wrap": "preferred_line_length"
},
"Elixir": {
"tab_size": 2
},
"Gleam": { "Gleam": {
"tab_size": 2 "tab_size": 2
}, },
"Go": { "Go": {
"tab_size": 4,
"hard_tabs": true,
"code_actions_on_format": { "code_actions_on_format": {
"source.organizeImports": true "source.organizeImports": true
} }
@ -575,34 +567,6 @@
"Make": { "Make": {
"hard_tabs": true "hard_tabs": true
}, },
"Markdown": {
"tab_size": 2,
"soft_wrap": "preferred_line_length"
},
"JavaScript": {
"tab_size": 2
},
"Terraform": {
"tab_size": 2
},
"TypeScript": {
"tab_size": 2
},
"TSX": {
"tab_size": 2
},
"YAML": {
"tab_size": 2
},
"JSON": {
"tab_size": 2
},
"OCaml": {
"tab_size": 2
},
"OCaml Interface": {
"tab_size": 2
},
"Prisma": { "Prisma": {
"tab_size": 2 "tab_size": 2
} }

View file

@ -10,7 +10,7 @@ use serde::{
de::{self, Visitor}, de::{self, Visitor},
Deserialize, Deserializer, Serialize, Serializer, Deserialize, Deserializer, Serialize, Serializer,
}; };
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Clone, Debug, Default, PartialEq)] #[derive(Clone, Debug, Default, PartialEq)]
pub enum ZedDotDevModel { pub enum ZedDotDevModel {
@ -332,13 +332,12 @@ impl Settings for AssistantSettings {
type FileContent = AssistantSettingsContent; type FileContent = AssistantSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
let mut settings = AssistantSettings::default(); let mut settings = AssistantSettings::default();
for value in [default_value].iter().chain(user_values) { for value in sources.defaults_and_customizations() {
let value = value.upgrade(); let value = value.upgrade();
merge(&mut settings.enabled, value.enabled); merge(&mut settings.enabled, value.enabled);
merge(&mut settings.button, value.button); merge(&mut settings.button, value.button);

View file

@ -17,7 +17,7 @@ use serde::Deserialize;
use serde_derive::Serialize; use serde_derive::Serialize;
use smol::io::AsyncReadExt; use smol::io::AsyncReadExt;
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsSources, SettingsStore};
use smol::{fs::File, process::Command}; use smol::{fs::File, process::Command};
use release_channel::{AppCommitSha, AppVersion, ReleaseChannel}; use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
@ -91,13 +91,12 @@ impl Settings for AutoUpdateSetting {
type FileContent = AutoUpdateSettingOverride; type FileContent = AutoUpdateSettingOverride;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Ok(Self( Ok(Self(
Self::json_merge(default_value, user_values)? sources
.release_channel
.or(sources.user)
.unwrap_or(sources.default)
.0 .0
.ok_or_else(Self::missing_default)?, .ok_or_else(Self::missing_default)?,
)) ))

View file

@ -2,7 +2,7 @@ use anyhow::Result;
use gpui::AppContext; use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct CallSettings { pub struct CallSettings {
@ -29,14 +29,7 @@ impl Settings for CallSettings {
type FileContent = CallSettingsContent; type FileContent = CallSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_cx: &mut AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -28,7 +28,7 @@ use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage}; use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsSources, SettingsStore};
use std::fmt; use std::fmt;
use std::{ use std::{
any::TypeId, any::TypeId,
@ -97,15 +97,11 @@ impl Settings for ClientSettings {
type FileContent = ClientSettingsContent; type FileContent = ClientSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self>
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self>
where where
Self: Sized, Self: Sized,
{ {
let mut result = Self::load_via_json_merge(default_value, user_values)?; let mut result = sources.json_merge::<Self>()?;
if let Some(server_url) = &*ZED_SERVER_URL { if let Some(server_url) = &*ZED_SERVER_URL {
result.server_url = server_url.clone() result.server_url = server_url.clone()
} }
@ -427,21 +423,19 @@ impl settings::Settings for TelemetrySettings {
type FileContent = TelemetrySettingsContent; type FileContent = TelemetrySettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Ok(Self { Ok(Self {
diagnostics: user_values.first().and_then(|v| v.diagnostics).unwrap_or( diagnostics: sources.user.as_ref().and_then(|v| v.diagnostics).unwrap_or(
default_value sources
.default
.diagnostics .diagnostics
.ok_or_else(Self::missing_default)?, .ok_or_else(Self::missing_default)?,
), ),
metrics: user_values metrics: sources
.first() .user
.as_ref()
.and_then(|v| v.metrics) .and_then(|v| v.metrics)
.unwrap_or(default_value.metrics.ok_or_else(Self::missing_default)?), .unwrap_or(sources.default.metrics.ok_or_else(Self::missing_default)?),
}) })
} }
} }

View file

@ -2,7 +2,7 @@ use anyhow;
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
use workspace::dock::DockPosition; use workspace::dock::DockPosition;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -53,48 +53,52 @@ pub struct MessageEditorSettings {
impl Settings for CollaborationPanelSettings { impl Settings for CollaborationPanelSettings {
const KEY: Option<&'static str> = Some("collaboration_panel"); const KEY: Option<&'static str> = Some("collaboration_panel");
type FileContent = PanelSettingsContent; type FileContent = PanelSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }
impl Settings for ChatPanelSettings { impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel"); const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = PanelSettingsContent; type FileContent = PanelSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }
impl Settings for NotificationPanelSettings { impl Settings for NotificationPanelSettings {
const KEY: Option<&'static str> = Some("notification_panel"); const KEY: Option<&'static str> = Some("notification_panel");
type FileContent = PanelSettingsContent; type FileContent = PanelSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }
impl Settings for MessageEditorSettings { impl Settings for MessageEditorSettings {
const KEY: Option<&'static str> = Some("message_editor"); const KEY: Option<&'static str> = Some("message_editor");
type FileContent = MessageEditorSettings; type FileContent = MessageEditorSettings;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }

View file

@ -1,5 +1,8 @@
use anyhow::Result;
use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct ProjectDiagnosticsSettings { pub struct ProjectDiagnosticsSettings {
@ -15,18 +18,11 @@ pub struct ProjectDiagnosticsSettingsContent {
include_warnings: Option<bool>, include_warnings: Option<bool>,
} }
impl settings::Settings for ProjectDiagnosticsSettings { impl Settings for ProjectDiagnosticsSettings {
const KEY: Option<&'static str> = Some("diagnostics"); const KEY: Option<&'static str> = Some("diagnostics");
type FileContent = ProjectDiagnosticsSettingsContent; type FileContent = ProjectDiagnosticsSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_cx: &mut gpui::AppContext,
) -> anyhow::Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -1,6 +1,7 @@
use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone)]
pub struct EditorSettings { pub struct EditorSettings {
@ -224,10 +225,9 @@ impl Settings for EditorSettings {
type FileContent = EditorSettingsContent; type FileContent = EditorSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent], _: &mut AppContext,
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }

View file

@ -3,7 +3,7 @@ use collections::HashMap;
use gpui::AppContext; use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
use std::sync::Arc; use std::sync::Arc;
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
@ -26,14 +26,7 @@ impl Settings for ExtensionSettings {
type FileContent = Self; type FileContent = Self;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut AppContext) -> Result<Self> {
_default_value: &Self::FileContent, Ok(sources.user.cloned().unwrap_or_default())
user_values: &[&Self::FileContent],
_cx: &mut AppContext,
) -> Result<Self>
where
Self: Sized,
{
Ok(user_values.get(0).copied().cloned().unwrap_or_default())
} }
} }

View file

@ -5,7 +5,7 @@ use editor::Editor;
use gpui::{actions, AppContext, ViewContext, WindowContext}; use gpui::{actions, AppContext, ViewContext, WindowContext};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -50,12 +50,8 @@ impl settings::Settings for JournalSettings {
type FileContent = Self; type FileContent = Self;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
defaults: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(defaults, user_values)
} }
} }

View file

@ -38,6 +38,7 @@ use schemars::{
use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value; use serde_json::Value;
use smol::future::FutureExt as _; use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{ use std::{
any::Any, any::Any,
cell::RefCell, cell::RefCell,
@ -73,6 +74,8 @@ pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
pub use text::LineEnding; pub use text::LineEnding;
pub use tree_sitter::{Parser, Tree}; pub use tree_sitter::{Parser, Tree};
use crate::language_settings::SoftWrap;
/// Initializes the `language` crate. /// Initializes the `language` crate.
/// ///
/// This should be called before making use of items from the create. /// This should be called before making use of items from the create.
@ -99,6 +102,7 @@ lazy_static! {
pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new( pub static ref PLAIN_TEXT: Arc<Language> = Arc::new(Language::new(
LanguageConfig { LanguageConfig {
name: "Plain Text".into(), name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::PreferredLineLength),
..Default::default() ..Default::default()
}, },
None, None,
@ -576,6 +580,17 @@ pub struct LanguageConfig {
/// The names of any Prettier plugins that should be used for this language. /// The names of any Prettier plugins that should be used for this language.
#[serde(default)] #[serde(default)]
pub prettier_plugins: Vec<Arc<str>>, pub prettier_plugins: Vec<Arc<str>>,
/// Whether to indent lines using tab characters, as opposed to multiple
/// spaces.
#[serde(default)]
pub hard_tabs: Option<bool>,
/// How many columns a tab should occupy.
#[serde(default)]
pub tab_size: Option<NonZeroU32>,
/// How to soft-wrap long lines of text.
#[serde(default)]
pub soft_wrap: Option<SoftWrap>,
} }
#[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, Default, JsonSchema)]
@ -660,6 +675,9 @@ impl Default for LanguageConfig {
prettier_parser_name: None, prettier_parser_name: None,
prettier_plugins: Default::default(), prettier_plugins: Default::default(),
collapsed_placeholder: Default::default(), collapsed_placeholder: Default::default(),
hard_tabs: Default::default(),
tab_size: Default::default(),
soft_wrap: Default::default(),
} }
} }
} }

View file

@ -1,3 +1,4 @@
use crate::language_settings::{AllLanguageSettingsContent, LanguageSettingsContent};
use crate::{ use crate::{
language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter, language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter,
File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter, File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
@ -38,6 +39,7 @@ pub struct LanguageRegistry {
struct LanguageRegistryState { struct LanguageRegistryState {
next_language_server_id: usize, next_language_server_id: usize,
languages: Vec<Arc<Language>>, languages: Vec<Arc<Language>>,
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>, available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>, grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>, lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
@ -145,6 +147,7 @@ impl LanguageRegistry {
languages: Vec::new(), languages: Vec::new(),
available_languages: Vec::new(), available_languages: Vec::new(),
grammars: Default::default(), grammars: Default::default(),
language_settings: Default::default(),
loading_languages: Default::default(), loading_languages: Default::default(),
lsp_adapters: Default::default(), lsp_adapters: Default::default(),
subscription: watch::channel(), subscription: watch::channel(),
@ -338,6 +341,10 @@ impl LanguageRegistry {
*state.subscription.0.borrow_mut() = (); *state.subscription.0.borrow_mut() = ();
} }
pub fn language_settings(&self) -> AllLanguageSettingsContent {
self.state.read().language_settings.clone()
}
pub fn language_names(&self) -> Vec<String> { pub fn language_names(&self) -> Vec<String> {
let state = self.state.read(); let state = self.state.read();
let mut result = state let mut result = state
@ -854,6 +861,16 @@ impl LanguageRegistryState {
if let Some(theme) = self.theme.as_ref() { if let Some(theme) = self.theme.as_ref() {
language.set_theme(theme.syntax()); language.set_theme(theme.syntax());
} }
self.language_settings.languages.insert(
language.name(),
LanguageSettingsContent {
tab_size: language.config.tab_size,
hard_tabs: language.config.hard_tabs,
soft_wrap: language.config.soft_wrap,
..Default::default()
}
.clone(),
);
self.languages.push(language); self.languages.push(language);
self.version += 1; self.version += 1;
*self.subscription.0.borrow_mut() = (); *self.subscription.0.borrow_mut() = ();

View file

@ -10,7 +10,7 @@ use schemars::{
JsonSchema, JsonSchema,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation}; use settings::{Settings, SettingsLocation, SettingsSources};
use std::{num::NonZeroU32, path::Path, sync::Arc}; use std::{num::NonZeroU32, path::Path, sync::Arc};
impl<'a> Into<SettingsLocation<'a>> for &'a dyn File { impl<'a> Into<SettingsLocation<'a>> for &'a dyn File {
@ -119,7 +119,7 @@ pub struct CopilotSettings {
} }
/// The settings for all languages. /// The settings for all languages.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent { pub struct AllLanguageSettingsContent {
/// The settings for enabling/disabling features. /// The settings for enabling/disabling features.
#[serde(default)] #[serde(default)]
@ -140,7 +140,7 @@ pub struct AllLanguageSettingsContent {
} }
/// The settings for a particular language. /// The settings for a particular language.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct LanguageSettingsContent { pub struct LanguageSettingsContent {
/// How many columns a tab should occupy. /// How many columns a tab should occupy.
/// ///
@ -249,7 +249,7 @@ pub struct LanguageSettingsContent {
} }
/// The contents of the GitHub Copilot settings. /// The contents of the GitHub Copilot settings.
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
pub struct CopilotSettingsContent { pub struct CopilotSettingsContent {
/// A list of globs representing files that Copilot should be disabled for. /// A list of globs representing files that Copilot should be disabled for.
#[serde(default)] #[serde(default)]
@ -257,7 +257,7 @@ pub struct CopilotSettingsContent {
} }
/// The settings for enabling/disabling features. /// The settings for enabling/disabling features.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct FeaturesContent { pub struct FeaturesContent {
/// Whether the GitHub Copilot feature is enabled. /// Whether the GitHub Copilot feature is enabled.
@ -473,11 +473,9 @@ impl settings::Settings for AllLanguageSettings {
type FileContent = AllLanguageSettingsContent; type FileContent = AllLanguageSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, let default_value = sources.default;
user_settings: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
// A default is provided for all settings. // A default is provided for all settings.
let mut defaults: LanguageSettings = let mut defaults: LanguageSettings =
serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
@ -500,7 +498,8 @@ impl settings::Settings for AllLanguageSettings {
.and_then(|c| c.disabled_globs.as_ref()) .and_then(|c| c.disabled_globs.as_ref())
.ok_or_else(Self::missing_default)?; .ok_or_else(Self::missing_default)?;
for user_settings in user_settings { let mut file_types: HashMap<Arc<str>, Vec<String>> = HashMap::default();
for user_settings in sources.customizations() {
if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
copilot_enabled = copilot; copilot_enabled = copilot;
} }
@ -528,11 +527,8 @@ impl settings::Settings for AllLanguageSettings {
user_language_settings, user_language_settings,
); );
} }
}
let mut file_types: HashMap<Arc<str>, Vec<String>> = HashMap::default(); for (language, suffixes) in &user_settings.file_types {
for user_file_types in user_settings.iter().map(|s| &s.file_types) {
for (language, suffixes) in user_file_types {
file_types file_types
.entry(language.clone()) .entry(language.clone())
.or_default() .or_default()

View file

@ -2,12 +2,13 @@ use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use collections::HashMap; use collections::HashMap;
use futures::StreamExt; use futures::StreamExt;
use gpui::AppContext;
use language::{LanguageServerName, LspAdapter, LspAdapterDelegate}; use language::{LanguageServerName, LspAdapter, LspAdapterDelegate};
use lsp::{CodeActionKind, LanguageServerBinary}; use lsp::{CodeActionKind, LanguageServerBinary};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::json; use serde_json::json;
use settings::Settings; use settings::{Settings, SettingsSources};
use smol::{fs, fs::File}; use smol::{fs, fs::File};
use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc}; use std::{any::Any, env::consts, ffi::OsString, path::PathBuf, sync::Arc};
use util::{ use util::{
@ -31,15 +32,8 @@ impl Settings for DenoSettings {
type FileContent = DenoSettingsContent; type FileContent = DenoSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -1,14 +1,14 @@
use anyhow::{anyhow, bail, Context, Result}; use anyhow::{anyhow, bail, Context, Result};
use async_trait::async_trait; use async_trait::async_trait;
use futures::StreamExt; use futures::StreamExt;
use gpui::{AsyncAppContext, Task}; use gpui::{AppContext, AsyncAppContext, Task};
pub use language::*; pub use language::*;
use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind}; use lsp::{CompletionItemKind, LanguageServerBinary, SymbolKind};
use project::project_settings::ProjectSettings; use project::project_settings::ProjectSettings;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use settings::Settings; use settings::{Settings, SettingsSources};
use smol::fs::{self, File}; use smol::fs::{self, File};
use std::{ use std::{
any::Any, any::Any,
@ -56,15 +56,8 @@ impl Settings for ElixirSettings {
type FileContent = ElixirSettingsContent; type FileContent = ElixirSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext,
) -> Result<Self>
where
Self: Sized,
{
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -10,6 +10,7 @@ brackets = [
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] }, { start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
{ start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] }, { start = "'", end = "'", close = true, newline = false, not_in = ["string", "comment"] },
] ]
tab_size = 2
scope_opt_in_language_servers = ["tailwindcss-language-server"] scope_opt_in_language_servers = ["tailwindcss-language-server"]
[overrides.string] [overrides.string]

View file

@ -11,3 +11,5 @@ brackets = [
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] }, { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
] ]
tab_size = 4
hard_tabs = true

View file

@ -15,6 +15,7 @@ brackets = [
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
] ]
word_characters = ["$", "#"] word_characters = ["$", "#"]
tab_size = 2
scope_opt_in_language_servers = ["tailwindcss-language-server"] scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "babel" prettier_parser_name = "babel"

View file

@ -9,3 +9,4 @@ brackets = [
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }, { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] },
] ]
prettier_parser_name = "json" prettier_parser_name = "json"
tab_size = 2

View file

@ -1,11 +1,12 @@
use anyhow::Context; use anyhow::Context;
use gpui::AppContext; use gpui::{AppContext, BorrowAppContext};
pub use language::*; pub use language::*;
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use settings::Settings; use settings::{Settings, SettingsStore};
use smol::stream::StreamExt;
use std::{str, sync::Arc}; use std::{str, sync::Arc};
use util::asset_str; use util::{asset_str, ResultExt};
use crate::{elixir::elixir_task_context, rust::RustContextProvider}; use crate::{elixir::elixir_task_context, rust::RustContextProvider};
@ -327,6 +328,27 @@ pub fn init(
"Svelte".into(), "Svelte".into(),
Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())), Arc::new(tailwind::TailwindLspAdapter::new(node_runtime.clone())),
); );
let mut subscription = languages.subscribe();
let mut prev_language_settings = languages.language_settings();
cx.spawn(|cx| async move {
while subscription.next().await.is_some() {
let language_settings = languages.language_settings();
if language_settings != prev_language_settings {
cx.update(|cx| {
cx.update_global(|settings: &mut SettingsStore, cx| {
settings
.set_extension_settings(language_settings.clone(), cx)
.log_err();
});
})?;
prev_language_settings = language_settings;
}
}
anyhow::Ok(())
})
.detach();
} }
#[cfg(any(test, feature = "test-support"))] #[cfg(any(test, feature = "test-support"))]

View file

@ -12,3 +12,6 @@ brackets = [
{ start = "`", end = "`", close = false, newline = false }, { start = "`", end = "`", close = false, newline = false },
] ]
prettier_parser_name = "markdown" prettier_parser_name = "markdown"
tab_size = 2
soft_wrap = "preferred_line_length"

View file

@ -10,3 +10,4 @@ brackets = [
{ start = "[", end = "]", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true } { start = "(", end = ")", close = true, newline = true }
] ]
tab_size = 2

View file

@ -11,3 +11,4 @@ brackets = [
{ start = "(", end = ")", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] } { start = "\"", end = "\"", close = true, newline = false, not_in = ["string"] }
] ]
tab_size = 2

View file

@ -12,3 +12,4 @@ brackets = [
{ start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] }, { start = "'", end = "'", close = true, newline = false, not_in = ["comment", "string"] },
{ start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] }, { start = "/*", end = " */", close = true, newline = false, not_in = ["comment", "string"] },
] ]
tab_size = 2

View file

@ -16,6 +16,7 @@ brackets = [
word_characters = ["#", "$"] word_characters = ["#", "$"]
scope_opt_in_language_servers = ["tailwindcss-language-server"] scope_opt_in_language_servers = ["tailwindcss-language-server"]
prettier_parser_name = "typescript" prettier_parser_name = "typescript"
tab_size = 2
[overrides.element] [overrides.element]
line_comments = { remove = true } line_comments = { remove = true }

View file

@ -15,3 +15,4 @@ brackets = [
] ]
word_characters = ["#", "$"] word_characters = ["#", "$"]
prettier_parser_name = "typescript" prettier_parser_name = "typescript"
tab_size = 2

View file

@ -11,3 +11,4 @@ brackets = [
increase_indent_pattern = ":\\s*[|>]?\\s*$" increase_indent_pattern = ":\\s*[|>]?\\s*$"
prettier_parser_name = "yaml" prettier_parser_name = "yaml"
tab_size = 2

View file

@ -2,7 +2,7 @@ use collections::HashMap;
use gpui::AppContext; use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
use std::sync::Arc; use std::sync::Arc;
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
@ -61,10 +61,9 @@ impl Settings for ProjectSettings {
type FileContent = Self; type FileContent = Self;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut AppContext, _: &mut AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }

View file

@ -2,7 +2,7 @@ use anyhow;
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -62,10 +62,9 @@ impl Settings for ProjectPanelSettings {
type FileContent = ProjectPanelSettingsContent; type FileContent = ProjectPanelSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }

View file

@ -8,7 +8,9 @@ use util::asset_str;
pub use keymap_file::KeymapFile; pub use keymap_file::KeymapFile;
pub use settings_file::*; pub use settings_file::*;
pub use settings_store::{Settings, SettingsJsonSchemaParams, SettingsLocation, SettingsStore}; pub use settings_store::{
Settings, SettingsJsonSchemaParams, SettingsLocation, SettingsSources, SettingsStore,
};
#[derive(RustEmbed)] #[derive(RustEmbed)]
#[folder = "../../assets"] #[folder = "../../assets"]

View file

@ -29,14 +29,7 @@ pub trait Settings: 'static + Send + Sync {
/// The logic for combining together values from one or more JSON files into the /// The logic for combining together values from one or more JSON files into the
/// final value for this setting. /// final value for this setting.
/// fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self>
/// The user values are ordered from least specific (the global settings file)
/// to most specific (the innermost local settings file).
fn load(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
cx: &mut AppContext,
) -> Result<Self>
where where
Self: Sized; Self: Sized;
@ -48,31 +41,6 @@ pub trait Settings: 'static + Send + Sync {
generator.root_schema_for::<Self::FileContent>() generator.root_schema_for::<Self::FileContent>()
} }
fn json_merge(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
) -> Result<Self::FileContent> {
let mut merged = serde_json::Value::Null;
for value in [default_value].iter().chain(user_values) {
merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
}
Ok(serde_json::from_value(merged)?)
}
fn load_via_json_merge(
default_value: &Self::FileContent,
user_values: &[&Self::FileContent],
) -> Result<Self>
where
Self: DeserializeOwned,
{
let mut merged = serde_json::Value::Null;
for value in [default_value].iter().chain(user_values) {
merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
}
Ok(serde_json::from_value(merged)?)
}
fn missing_default() -> anyhow::Error { fn missing_default() -> anyhow::Error {
anyhow::anyhow!("missing default") anyhow::anyhow!("missing default")
} }
@ -119,6 +87,48 @@ pub trait Settings: 'static + Send + Sync {
} }
} }
#[derive(Clone, Copy, Debug)]
pub struct SettingsSources<'a, T> {
/// The default Zed settings.
pub default: &'a T,
/// Settings provided by extensions.
pub extensions: Option<&'a T>,
/// The user settings.
pub user: Option<&'a T>,
/// The user settings for the current release channel.
pub release_channel: Option<&'a T>,
/// The project settings, ordered from least specific to most specific.
pub project: &'a [&'a T],
}
impl<'a, T: Serialize> SettingsSources<'a, T> {
/// Returns an iterator over the default settings as well as all settings customizations.
pub fn defaults_and_customizations(&self) -> impl Iterator<Item = &T> {
[self.default].into_iter().chain(self.customizations())
}
/// Returns an iterator over all of the settings customizations.
pub fn customizations(&self) -> impl Iterator<Item = &T> {
self.extensions
.into_iter()
.chain(self.user)
.chain(self.release_channel)
.chain(self.project.iter().copied())
}
/// Returns the settings after performing a JSON merge of the customizations into the
/// default settings.
///
/// More-specific customizations win out over the less-specific ones.
pub fn json_merge<O: DeserializeOwned>(&self) -> Result<O> {
let mut merged = serde_json::Value::Null;
for value in self.defaults_and_customizations() {
merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged);
}
Ok(serde_json::from_value(merged)?)
}
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct SettingsLocation<'a> { pub struct SettingsLocation<'a> {
pub worktree_id: usize, pub worktree_id: usize,
@ -136,6 +146,7 @@ pub struct SettingsStore {
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
raw_default_settings: serde_json::Value, raw_default_settings: serde_json::Value,
raw_user_settings: serde_json::Value, raw_user_settings: serde_json::Value,
raw_extension_settings: serde_json::Value,
raw_local_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>, raw_local_settings: BTreeMap<(usize, Arc<Path>), serde_json::Value>,
tab_size_callback: Option<( tab_size_callback: Option<(
TypeId, TypeId,
@ -151,6 +162,7 @@ impl Default for SettingsStore {
setting_values: Default::default(), setting_values: Default::default(),
raw_default_settings: serde_json::json!({}), raw_default_settings: serde_json::json!({}),
raw_user_settings: serde_json::json!({}), raw_user_settings: serde_json::json!({}),
raw_extension_settings: serde_json::json!({}),
raw_local_settings: Default::default(), raw_local_settings: Default::default(),
tab_size_callback: Default::default(), tab_size_callback: Default::default(),
} }
@ -169,8 +181,7 @@ trait AnySettingValue: 'static + Send + Sync {
fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>; fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>;
fn load_setting( fn load_setting(
&self, &self,
default_value: &DeserializedSetting, sources: SettingsSources<DeserializedSetting>,
custom: &[DeserializedSetting],
cx: &mut AppContext, cx: &mut AppContext,
) -> Result<Box<dyn Any>>; ) -> Result<Box<dyn Any>>;
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any; fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
@ -204,29 +215,35 @@ impl SettingsStore {
.deserialize_setting(&self.raw_default_settings) .deserialize_setting(&self.raw_default_settings)
.log_err() .log_err()
{ {
let mut user_values_stack = Vec::new(); let user_value = setting_value
if let Some(user_settings) = setting_value
.deserialize_setting(&self.raw_user_settings) .deserialize_setting(&self.raw_user_settings)
.log_err() .log_err();
{
user_values_stack = vec![user_settings];
}
let mut release_channel_value = None;
if let Some(release_settings) = &self if let Some(release_settings) = &self
.raw_user_settings .raw_user_settings
.get(release_channel::RELEASE_CHANNEL.dev_name()) .get(release_channel::RELEASE_CHANNEL.dev_name())
{ {
if let Some(release_settings) = setting_value release_channel_value = setting_value
.deserialize_setting(release_settings) .deserialize_setting(release_settings)
.log_err() .log_err();
{
user_values_stack.push(release_settings);
}
} }
let extension_value = setting_value
.deserialize_setting(&self.raw_extension_settings)
.log_err();
if let Some(setting) = setting_value if let Some(setting) = setting_value
.load_setting(&default_settings, &user_values_stack, cx) .load_setting(
SettingsSources {
default: &default_settings,
release_channel: release_channel_value.as_ref(),
extensions: extension_value.as_ref(),
user: user_value.as_ref(),
project: &[],
},
cx,
)
.context("A default setting must be added to the `default.json` file") .context("A default setting must be added to the `default.json` file")
.log_err() .log_err()
{ {
@ -425,6 +442,21 @@ impl SettingsStore {
Ok(()) Ok(())
} }
pub fn set_extension_settings<T: Serialize>(
&mut self,
content: T,
cx: &mut AppContext,
) -> Result<()> {
let settings: serde_json::Value = serde_json::to_value(content)?;
if settings.is_object() {
self.raw_extension_settings = settings;
self.recompute_values(None, cx)?;
Ok(())
} else {
Err(anyhow!("settings must be an object"))
}
}
/// Add or remove a set of local settings via a JSON string. /// Add or remove a set of local settings via a JSON string.
pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> { pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> {
self.raw_local_settings.retain(|k, _| k.0 != root_id); self.raw_local_settings.retain(|k, _| k.0 != root_id);
@ -551,22 +583,20 @@ impl SettingsStore {
cx: &mut AppContext, cx: &mut AppContext,
) -> Result<()> { ) -> Result<()> {
// Reload the global and local values for every setting. // Reload the global and local values for every setting.
let mut user_settings_stack = Vec::<DeserializedSetting>::new(); let mut project_settings_stack = Vec::<DeserializedSetting>::new();
let mut paths_stack = Vec::<Option<(usize, &Path)>>::new(); let mut paths_stack = Vec::<Option<(usize, &Path)>>::new();
for setting_value in self.setting_values.values_mut() { for setting_value in self.setting_values.values_mut() {
let default_settings = setting_value.deserialize_setting(&self.raw_default_settings)?; let default_settings = setting_value.deserialize_setting(&self.raw_default_settings)?;
user_settings_stack.clear(); let extension_settings = setting_value
paths_stack.clear(); .deserialize_setting(&self.raw_extension_settings)
.log_err();
if let Some(user_settings) = setting_value let user_settings = setting_value
.deserialize_setting(&self.raw_user_settings) .deserialize_setting(&self.raw_user_settings)
.log_err() .log_err();
{
user_settings_stack.push(user_settings);
paths_stack.push(None);
}
let mut release_channel_settings = None;
if let Some(release_settings) = &self if let Some(release_settings) = &self
.raw_user_settings .raw_user_settings
.get(release_channel::RELEASE_CHANNEL.dev_name()) .get(release_channel::RELEASE_CHANNEL.dev_name())
@ -575,15 +605,25 @@ impl SettingsStore {
.deserialize_setting(release_settings) .deserialize_setting(release_settings)
.log_err() .log_err()
{ {
user_settings_stack.push(release_settings); release_channel_settings = Some(release_settings);
paths_stack.push(None);
} }
} }
// If the global settings file changed, reload the global value for the field. // If the global settings file changed, reload the global value for the field.
project_settings_stack.clear();
paths_stack.clear();
if changed_local_path.is_none() { if changed_local_path.is_none() {
if let Some(value) = setting_value if let Some(value) = setting_value
.load_setting(&default_settings, &user_settings_stack, cx) .load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
project: &[],
},
cx,
)
.log_err() .log_err()
{ {
setting_value.set_global_value(value); setting_value.set_global_value(value);
@ -597,7 +637,7 @@ impl SettingsStore {
if let Some((prev_root_id, prev_path)) = prev_entry { if let Some((prev_root_id, prev_path)) = prev_entry {
if root_id != prev_root_id || !path.starts_with(prev_path) { if root_id != prev_root_id || !path.starts_with(prev_path) {
paths_stack.pop(); paths_stack.pop();
user_settings_stack.pop(); project_settings_stack.pop();
continue; continue;
} }
} }
@ -608,7 +648,7 @@ impl SettingsStore {
setting_value.deserialize_setting(local_settings).log_err() setting_value.deserialize_setting(local_settings).log_err()
{ {
paths_stack.push(Some((*root_id, path.as_ref()))); paths_stack.push(Some((*root_id, path.as_ref())));
user_settings_stack.push(local_settings); project_settings_stack.push(local_settings);
// If a local settings file changed, then avoid recomputing local // If a local settings file changed, then avoid recomputing local
// settings for any path outside of that directory. // settings for any path outside of that directory.
@ -619,7 +659,16 @@ impl SettingsStore {
} }
if let Some(value) = setting_value if let Some(value) = setting_value
.load_setting(&default_settings, &user_settings_stack, cx) .load_setting(
SettingsSources {
default: &default_settings,
extensions: extension_settings.as_ref(),
user: user_settings.as_ref(),
release_channel: release_channel_settings.as_ref(),
project: &project_settings_stack.iter().collect::<Vec<_>>(),
},
cx,
)
.log_err() .log_err()
{ {
setting_value.set_local_value(*root_id, path.clone(), value); setting_value.set_local_value(*root_id, path.clone(), value);
@ -660,16 +709,30 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
fn load_setting( fn load_setting(
&self, &self,
default_value: &DeserializedSetting, values: SettingsSources<DeserializedSetting>,
user_values: &[DeserializedSetting],
cx: &mut AppContext, cx: &mut AppContext,
) -> Result<Box<dyn Any>> { ) -> Result<Box<dyn Any>> {
let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap(); Ok(Box::new(T::load(
let values: SmallVec<[&T::FileContent; 6]> = user_values SettingsSources {
.iter() default: values.default.0.downcast_ref::<T::FileContent>().unwrap(),
.map(|value| value.0.downcast_ref().unwrap()) extensions: values
.collect(); .extensions
Ok(Box::new(T::load(default_value, &values, cx)?)) .map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
user: values
.user
.map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
release_channel: values
.release_channel
.map(|value| value.0.downcast_ref::<T::FileContent>().unwrap()),
project: values
.project
.iter()
.map(|value| value.0.downcast_ref().unwrap())
.collect::<SmallVec<[_; 3]>>()
.as_slice(),
},
cx,
)?))
} }
fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> { fn deserialize_setting(&self, mut json: &serde_json::Value) -> Result<DeserializedSetting> {
@ -1277,12 +1340,8 @@ mod tests {
const KEY: Option<&'static str> = Some("user"); const KEY: Option<&'static str> = Some("user");
type FileContent = UserSettingsJson; type FileContent = UserSettingsJson;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &UserSettingsJson, sources.json_merge()
user_values: &[&UserSettingsJson],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }
@ -1293,12 +1352,8 @@ mod tests {
const KEY: Option<&'static str> = Some("turbo"); const KEY: Option<&'static str> = Some("turbo");
type FileContent = Option<bool>; type FileContent = Option<bool>;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Option<bool>, sources.json_merge()
user_values: &[&Option<bool>],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }
@ -1321,12 +1376,8 @@ mod tests {
type FileContent = MultiKeySettingsJson; type FileContent = MultiKeySettingsJson;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &MultiKeySettingsJson, sources.json_merge()
user_values: &[&MultiKeySettingsJson],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }
@ -1354,12 +1405,8 @@ mod tests {
type FileContent = JournalSettingsJson; type FileContent = JournalSettingsJson;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &JournalSettingsJson, sources.json_merge()
user_values: &[&JournalSettingsJson],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }
@ -1380,8 +1427,8 @@ mod tests {
type FileContent = Self; type FileContent = Self;
fn load(default_value: &Self, user_values: &[&Self], _: &mut AppContext) -> Result<Self> { fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }
} }

View file

@ -1,5 +1,6 @@
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources};
#[derive(Serialize, Deserialize, PartialEq, Default)] #[derive(Serialize, Deserialize, PartialEq, Default)]
pub(crate) struct TaskSettings { pub(crate) struct TaskSettings {
@ -13,22 +14,15 @@ pub(crate) struct TaskSettingsContent {
show_status_indicator: Option<bool>, show_status_indicator: Option<bool>,
} }
impl settings::Settings for TaskSettings { impl Settings for TaskSettings {
const KEY: Option<&'static str> = Some("task"); const KEY: Option<&'static str> = Some("task");
type FileContent = TaskSettingsContent; type FileContent = TaskSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> gpui::Result<Self> ) -> gpui::Result<Self> {
where sources.json_merge()
Self: Sized,
{
let this = Self::json_merge(default_value, user_values)?;
Ok(Self {
show_status_indicator: this.show_status_indicator.unwrap_or(true),
})
} }
} }

View file

@ -7,7 +7,7 @@ use schemars::{
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use settings::SettingsJsonSchemaParams; use settings::{SettingsJsonSchemaParams, SettingsSources};
use std::path::PathBuf; use std::path::PathBuf;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -171,12 +171,12 @@ impl settings::Settings for TerminalSettings {
type FileContent = TerminalSettingsContent; type FileContent = TerminalSettingsContent;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut AppContext, _: &mut AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
fn json_schema( fn json_schema(
generator: &mut SchemaGenerator, generator: &mut SchemaGenerator,
params: &SettingsJsonSchemaParams, params: &SettingsJsonSchemaParams,

View file

@ -14,7 +14,7 @@ use schemars::{
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use settings::{Settings, SettingsJsonSchemaParams}; use settings::{Settings, SettingsJsonSchemaParams, SettingsSources};
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt as _; use util::ResultExt as _;
@ -316,14 +316,11 @@ impl settings::Settings for ThemeSettings {
type FileContent = ThemeSettingsContent; type FileContent = ThemeSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self> {
defaults: &Self::FileContent,
user_values: &[&Self::FileContent],
cx: &mut AppContext,
) -> Result<Self> {
let themes = ThemeRegistry::default_global(cx); let themes = ThemeRegistry::default_global(cx);
let system_appearance = SystemAppearance::default_global(cx); let system_appearance = SystemAppearance::default_global(cx);
let defaults = sources.default;
let mut this = Self { let mut this = Self {
ui_font_size: defaults.ui_font_size.unwrap().into(), ui_font_size: defaults.ui_font_size.unwrap().into(),
ui_font: Font { ui_font: Font {
@ -348,15 +345,15 @@ impl settings::Settings for ThemeSettings {
theme_overrides: None, theme_overrides: None,
}; };
for value in user_values.iter().copied().cloned() { for value in sources.user.into_iter().chain(sources.release_channel) {
if let Some(value) = value.buffer_font_family { if let Some(value) = value.buffer_font_family.clone() {
this.buffer_font.family = value.into(); this.buffer_font.family = value.into();
} }
if let Some(value) = value.buffer_font_features { if let Some(value) = value.buffer_font_features {
this.buffer_font.features = value; this.buffer_font.features = value;
} }
if let Some(value) = value.ui_font_family { if let Some(value) = value.ui_font_family.clone() {
this.ui_font.family = value.into(); this.ui_font.family = value.into();
} }
if let Some(value) = value.ui_font_features { if let Some(value) = value.ui_font_features {
@ -373,7 +370,7 @@ impl settings::Settings for ThemeSettings {
} }
} }
this.theme_overrides = value.theme_overrides; this.theme_overrides = value.theme_overrides.clone();
this.apply_theme_overrides(); this.apply_theme_overrides();
merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into)); merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));

View file

@ -35,7 +35,7 @@ use replace::multi_replace;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use serde_derive::Serialize; use serde_derive::Serialize;
use settings::{update_settings_file, Settings, SettingsStore}; use settings::{update_settings_file, Settings, SettingsSources, SettingsStore};
use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState}; use state::{EditorState, Mode, Operator, RecordedSelection, WorkspaceState};
use std::{ops::Range, sync::Arc}; use std::{ops::Range, sync::Arc};
use surrounds::{add_surrounds, change_surrounds, delete_surrounds}; use surrounds::{add_surrounds, change_surrounds, delete_surrounds};
@ -779,13 +779,9 @@ impl Settings for VimModeSetting {
type FileContent = Option<bool>; type FileContent = Option<bool>;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, Ok(Self(sources.user.copied().flatten().unwrap_or(
user_values: &[&Self::FileContent], sources.default.ok_or_else(Self::missing_default)?,
_: &mut AppContext,
) -> Result<Self> {
Ok(Self(user_values.iter().rev().find_map(|v| **v).unwrap_or(
default_value.ok_or_else(Self::missing_default)?,
))) )))
} }
} }
@ -824,11 +820,7 @@ impl Settings for VimSettings {
type FileContent = VimSettingsContent; type FileContent = VimSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -2,7 +2,7 @@ use std::fmt::{Display, Formatter};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
/// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
/// ///
@ -70,16 +70,12 @@ impl Settings for BaseKeymap {
type FileContent = Option<Self>; type FileContent = Option<Self>;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext, _: &mut gpui::AppContext,
) -> anyhow::Result<Self> ) -> anyhow::Result<Self> {
where if let Some(Some(user_value)) = sources.user.copied() {
Self: Sized, return Ok(user_value);
{ }
Ok(user_values sources.default.ok_or_else(Self::missing_default)
.first()
.and_then(|v| **v)
.unwrap_or(default_value.unwrap()))
} }
} }

View file

@ -20,7 +20,7 @@ use gpui::{
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
@ -76,12 +76,8 @@ impl Settings for ItemSettings {
type FileContent = ItemSettingsContent; type FileContent = ItemSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -1,6 +1,8 @@
use anyhow::Result;
use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct WorkspaceSettings { pub struct WorkspaceSettings {
@ -63,12 +65,8 @@ impl Settings for WorkspaceSettings {
type FileContent = WorkspaceSettingsContent; type FileContent = WorkspaceSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }
@ -77,11 +75,7 @@ impl Settings for TabBarSettings {
type FileContent = TabBarSettingsContent; type FileContent = TabBarSettingsContent;
fn load( fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
default_value: &Self::FileContent, sources.json_merge()
user_values: &[&Self::FileContent],
_: &mut gpui::AppContext,
) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values)
} }
} }

View file

@ -1,7 +1,7 @@
use gpui::AppContext; use gpui::AppContext;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::Settings; use settings::{Settings, SettingsSources};
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct WorktreeSettings { pub struct WorktreeSettings {
@ -31,10 +31,9 @@ impl Settings for WorktreeSettings {
type FileContent = Self; type FileContent = Self;
fn load( fn load(
default_value: &Self::FileContent, sources: SettingsSources<Self::FileContent>,
user_values: &[&Self::FileContent],
_: &mut AppContext, _: &mut AppContext,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
Self::load_via_json_merge(default_value, user_values) sources.json_merge()
} }
} }

View file

@ -607,6 +607,7 @@ pub fn handle_keymap_file_changes(
cx.observe_global::<SettingsStore>(move |cx| { cx.observe_global::<SettingsStore>(move |cx| {
let new_base_keymap = *BaseKeymap::get_global(cx); let new_base_keymap = *BaseKeymap::get_global(cx);
let new_vim_enabled = VimModeSetting::get_global(cx).0; let new_vim_enabled = VimModeSetting::get_global(cx).0;
if new_base_keymap != old_base_keymap || new_vim_enabled != old_vim_enabled { if new_base_keymap != old_base_keymap || new_vim_enabled != old_vim_enabled {
old_base_keymap = new_base_keymap; old_base_keymap = new_base_keymap;
old_vim_enabled = new_vim_enabled; old_vim_enabled = new_vim_enabled;
@ -3062,6 +3063,7 @@ mod tests {
let mut app_state = AppState::test(cx); let mut app_state = AppState::test(cx);
let state = Arc::get_mut(&mut app_state).unwrap(); let state = Arc::get_mut(&mut app_state).unwrap();
env_logger::try_init().ok();
state.build_window_options = build_window_options; state.build_window_options = build_window_options;
theme::init(theme::LoadThemes::JustBase, cx); theme::init(theme::LoadThemes::JustBase, cx);

View file

@ -9,3 +9,4 @@ brackets = [
{ start = "(", end = ")", close = true, newline = true }, { start = "(", end = ")", close = true, newline = true },
{ start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] }, { start = "\"", end = "\"", close = true, newline = false, not_in = ["string", "comment"] },
] ]
tab_size = 2

View file

@ -7,3 +7,4 @@ brackets = [
{ start = "[", end = "]", close = true, newline = true }, { start = "[", end = "]", close = true, newline = true },
{ start = "(", end = ")", close = true, newline = true } { start = "(", end = ")", close = true, newline = true }
] ]
tab_size = 2