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:
parent
4a3032c5e5
commit
7c5bc3c26f
42 changed files with 349 additions and 338 deletions
|
@ -8,7 +8,9 @@ use util::asset_str;
|
|||
|
||||
pub use keymap_file::KeymapFile;
|
||||
pub use settings_file::*;
|
||||
pub use settings_store::{Settings, SettingsJsonSchemaParams, SettingsLocation, SettingsStore};
|
||||
pub use settings_store::{
|
||||
Settings, SettingsJsonSchemaParams, SettingsLocation, SettingsSources, SettingsStore,
|
||||
};
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "../../assets"]
|
||||
|
|
|
@ -29,14 +29,7 @@ pub trait Settings: 'static + Send + Sync {
|
|||
|
||||
/// The logic for combining together values from one or more JSON files into the
|
||||
/// final value for this setting.
|
||||
///
|
||||
/// 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>
|
||||
fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
|
@ -48,31 +41,6 @@ pub trait Settings: 'static + Send + Sync {
|
|||
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 {
|
||||
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)]
|
||||
pub struct SettingsLocation<'a> {
|
||||
pub worktree_id: usize,
|
||||
|
@ -136,6 +146,7 @@ pub struct SettingsStore {
|
|||
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
|
||||
raw_default_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>,
|
||||
tab_size_callback: Option<(
|
||||
TypeId,
|
||||
|
@ -151,6 +162,7 @@ impl Default for SettingsStore {
|
|||
setting_values: Default::default(),
|
||||
raw_default_settings: serde_json::json!({}),
|
||||
raw_user_settings: serde_json::json!({}),
|
||||
raw_extension_settings: serde_json::json!({}),
|
||||
raw_local_settings: 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 load_setting(
|
||||
&self,
|
||||
default_value: &DeserializedSetting,
|
||||
custom: &[DeserializedSetting],
|
||||
sources: SettingsSources<DeserializedSetting>,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<Box<dyn Any>>;
|
||||
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
|
||||
|
@ -204,29 +215,35 @@ impl SettingsStore {
|
|||
.deserialize_setting(&self.raw_default_settings)
|
||||
.log_err()
|
||||
{
|
||||
let mut user_values_stack = Vec::new();
|
||||
|
||||
if let Some(user_settings) = setting_value
|
||||
let user_value = setting_value
|
||||
.deserialize_setting(&self.raw_user_settings)
|
||||
.log_err()
|
||||
{
|
||||
user_values_stack = vec![user_settings];
|
||||
}
|
||||
.log_err();
|
||||
|
||||
let mut release_channel_value = None;
|
||||
if let Some(release_settings) = &self
|
||||
.raw_user_settings
|
||||
.get(release_channel::RELEASE_CHANNEL.dev_name())
|
||||
{
|
||||
if let Some(release_settings) = setting_value
|
||||
release_channel_value = setting_value
|
||||
.deserialize_setting(release_settings)
|
||||
.log_err()
|
||||
{
|
||||
user_values_stack.push(release_settings);
|
||||
}
|
||||
.log_err();
|
||||
}
|
||||
|
||||
let extension_value = setting_value
|
||||
.deserialize_setting(&self.raw_extension_settings)
|
||||
.log_err();
|
||||
|
||||
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")
|
||||
.log_err()
|
||||
{
|
||||
|
@ -425,6 +442,21 @@ impl SettingsStore {
|
|||
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.
|
||||
pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> {
|
||||
self.raw_local_settings.retain(|k, _| k.0 != root_id);
|
||||
|
@ -551,22 +583,20 @@ impl SettingsStore {
|
|||
cx: &mut AppContext,
|
||||
) -> Result<()> {
|
||||
// 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();
|
||||
for setting_value in self.setting_values.values_mut() {
|
||||
let default_settings = setting_value.deserialize_setting(&self.raw_default_settings)?;
|
||||
|
||||
user_settings_stack.clear();
|
||||
paths_stack.clear();
|
||||
let extension_settings = setting_value
|
||||
.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)
|
||||
.log_err()
|
||||
{
|
||||
user_settings_stack.push(user_settings);
|
||||
paths_stack.push(None);
|
||||
}
|
||||
.log_err();
|
||||
|
||||
let mut release_channel_settings = None;
|
||||
if let Some(release_settings) = &self
|
||||
.raw_user_settings
|
||||
.get(release_channel::RELEASE_CHANNEL.dev_name())
|
||||
|
@ -575,15 +605,25 @@ impl SettingsStore {
|
|||
.deserialize_setting(release_settings)
|
||||
.log_err()
|
||||
{
|
||||
user_settings_stack.push(release_settings);
|
||||
paths_stack.push(None);
|
||||
release_channel_settings = Some(release_settings);
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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()
|
||||
{
|
||||
setting_value.set_global_value(value);
|
||||
|
@ -597,7 +637,7 @@ impl SettingsStore {
|
|||
if let Some((prev_root_id, prev_path)) = prev_entry {
|
||||
if root_id != prev_root_id || !path.starts_with(prev_path) {
|
||||
paths_stack.pop();
|
||||
user_settings_stack.pop();
|
||||
project_settings_stack.pop();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -608,7 +648,7 @@ impl SettingsStore {
|
|||
setting_value.deserialize_setting(local_settings).log_err()
|
||||
{
|
||||
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
|
||||
// settings for any path outside of that directory.
|
||||
|
@ -619,7 +659,16 @@ impl SettingsStore {
|
|||
}
|
||||
|
||||
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()
|
||||
{
|
||||
setting_value.set_local_value(*root_id, path.clone(), value);
|
||||
|
@ -660,16 +709,30 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
|
|||
|
||||
fn load_setting(
|
||||
&self,
|
||||
default_value: &DeserializedSetting,
|
||||
user_values: &[DeserializedSetting],
|
||||
values: SettingsSources<DeserializedSetting>,
|
||||
cx: &mut AppContext,
|
||||
) -> Result<Box<dyn Any>> {
|
||||
let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
|
||||
let values: SmallVec<[&T::FileContent; 6]> = user_values
|
||||
.iter()
|
||||
.map(|value| value.0.downcast_ref().unwrap())
|
||||
.collect();
|
||||
Ok(Box::new(T::load(default_value, &values, cx)?))
|
||||
Ok(Box::new(T::load(
|
||||
SettingsSources {
|
||||
default: values.default.0.downcast_ref::<T::FileContent>().unwrap(),
|
||||
extensions: values
|
||||
.extensions
|
||||
.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> {
|
||||
|
@ -1277,12 +1340,8 @@ mod tests {
|
|||
const KEY: Option<&'static str> = Some("user");
|
||||
type FileContent = UserSettingsJson;
|
||||
|
||||
fn load(
|
||||
default_value: &UserSettingsJson,
|
||||
user_values: &[&UserSettingsJson],
|
||||
_: &mut AppContext,
|
||||
) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1293,12 +1352,8 @@ mod tests {
|
|||
const KEY: Option<&'static str> = Some("turbo");
|
||||
type FileContent = Option<bool>;
|
||||
|
||||
fn load(
|
||||
default_value: &Option<bool>,
|
||||
user_values: &[&Option<bool>],
|
||||
_: &mut AppContext,
|
||||
) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1321,12 +1376,8 @@ mod tests {
|
|||
|
||||
type FileContent = MultiKeySettingsJson;
|
||||
|
||||
fn load(
|
||||
default_value: &MultiKeySettingsJson,
|
||||
user_values: &[&MultiKeySettingsJson],
|
||||
_: &mut AppContext,
|
||||
) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1354,12 +1405,8 @@ mod tests {
|
|||
|
||||
type FileContent = JournalSettingsJson;
|
||||
|
||||
fn load(
|
||||
default_value: &JournalSettingsJson,
|
||||
user_values: &[&JournalSettingsJson],
|
||||
_: &mut AppContext,
|
||||
) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1380,8 +1427,8 @@ mod tests {
|
|||
|
||||
type FileContent = Self;
|
||||
|
||||
fn load(default_value: &Self, user_values: &[&Self], _: &mut AppContext) -> Result<Self> {
|
||||
Self::load_via_json_merge(default_value, user_values)
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
|
||||
sources.json_merge()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue