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
co-authored by Max Max Brunsfeld
parent 4a3032c5e5
commit 7c5bc3c26f
42 changed files with 349 additions and 338 deletions
+18
View File
@@ -38,6 +38,7 @@ use schemars::{
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use smol::future::FutureExt as _;
use std::num::NonZeroU32;
use std::{
any::Any,
cell::RefCell,
@@ -73,6 +74,8 @@ pub use syntax_map::{OwnedSyntaxLayer, SyntaxLayer};
pub use text::LineEnding;
pub use tree_sitter::{Parser, Tree};
use crate::language_settings::SoftWrap;
/// Initializes the `language` crate.
///
/// 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(
LanguageConfig {
name: "Plain Text".into(),
soft_wrap: Some(SoftWrap::PreferredLineLength),
..Default::default()
},
None,
@@ -576,6 +580,17 @@ pub struct LanguageConfig {
/// The names of any Prettier plugins that should be used for this language.
#[serde(default)]
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)]
@@ -660,6 +675,9 @@ impl Default for LanguageConfig {
prettier_parser_name: None,
prettier_plugins: Default::default(),
collapsed_placeholder: Default::default(),
hard_tabs: Default::default(),
tab_size: Default::default(),
soft_wrap: Default::default(),
}
}
}
+17
View File
@@ -1,3 +1,4 @@
use crate::language_settings::{AllLanguageSettingsContent, LanguageSettingsContent};
use crate::{
language_settings::all_language_settings, task_context::ContextProvider, CachedLspAdapter,
File, Language, LanguageConfig, LanguageId, LanguageMatcher, LanguageServerName, LspAdapter,
@@ -38,6 +39,7 @@ pub struct LanguageRegistry {
struct LanguageRegistryState {
next_language_server_id: usize,
languages: Vec<Arc<Language>>,
language_settings: AllLanguageSettingsContent,
available_languages: Vec<AvailableLanguage>,
grammars: HashMap<Arc<str>, AvailableGrammar>,
lsp_adapters: HashMap<Arc<str>, Vec<Arc<CachedLspAdapter>>>,
@@ -145,6 +147,7 @@ impl LanguageRegistry {
languages: Vec::new(),
available_languages: Vec::new(),
grammars: Default::default(),
language_settings: Default::default(),
loading_languages: Default::default(),
lsp_adapters: Default::default(),
subscription: watch::channel(),
@@ -338,6 +341,10 @@ impl LanguageRegistry {
*state.subscription.0.borrow_mut() = ();
}
pub fn language_settings(&self) -> AllLanguageSettingsContent {
self.state.read().language_settings.clone()
}
pub fn language_names(&self) -> Vec<String> {
let state = self.state.read();
let mut result = state
@@ -854,6 +861,16 @@ impl LanguageRegistryState {
if let Some(theme) = self.theme.as_ref() {
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.version += 1;
*self.subscription.0.borrow_mut() = ();
+11 -15
View File
@@ -10,7 +10,7 @@ use schemars::{
JsonSchema,
};
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation};
use settings::{Settings, SettingsLocation, SettingsSources};
use std::{num::NonZeroU32, path::Path, sync::Arc};
impl<'a> Into<SettingsLocation<'a>> for &'a dyn File {
@@ -119,7 +119,7 @@ pub struct CopilotSettings {
}
/// The settings for all languages.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct AllLanguageSettingsContent {
/// The settings for enabling/disabling features.
#[serde(default)]
@@ -140,7 +140,7 @@ pub struct AllLanguageSettingsContent {
}
/// The settings for a particular language.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct LanguageSettingsContent {
/// How many columns a tab should occupy.
///
@@ -249,7 +249,7 @@ pub struct LanguageSettingsContent {
}
/// 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 {
/// A list of globs representing files that Copilot should be disabled for.
#[serde(default)]
@@ -257,7 +257,7 @@ pub struct CopilotSettingsContent {
}
/// The settings for enabling/disabling features.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub struct FeaturesContent {
/// Whether the GitHub Copilot feature is enabled.
@@ -473,11 +473,9 @@ impl settings::Settings for AllLanguageSettings {
type FileContent = AllLanguageSettingsContent;
fn load(
default_value: &Self::FileContent,
user_settings: &[&Self::FileContent],
_: &mut AppContext,
) -> Result<Self> {
fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
let default_value = sources.default;
// A default is provided for all settings.
let mut defaults: LanguageSettings =
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())
.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) {
copilot_enabled = copilot;
}
@@ -528,11 +527,8 @@ impl settings::Settings for AllLanguageSettings {
user_language_settings,
);
}
}
let mut file_types: HashMap<Arc<str>, Vec<String>> = HashMap::default();
for user_file_types in user_settings.iter().map(|s| &s.file_types) {
for (language, suffixes) in user_file_types {
for (language, suffixes) in &user_settings.file_types {
file_types
.entry(language.clone())
.or_default()