Detailed changes
@@ -311,7 +311,12 @@ pub fn init(languages: Arc<LanguageRegistry>, fs: Arc<dyn Fs>, node: NodeRuntime
cx.update(|cx| {
SettingsStore::update_global(cx, |settings, cx| {
settings
- .set_extension_settings(language_settings.clone(), cx)
+ .set_extension_settings(
+ ExtensionsSettingsContent {
+ languages: language_settings.clone(),
+ },
+ cx,
+ )
.log_err();
});
})?;
@@ -1,9 +1,12 @@
use std::fmt::{Display, Formatter};
-use crate::{self as settings};
+use crate::{
+ self as settings,
+ settings_content::{self, BaseKeymapContent, SettingsContent},
+};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources, VsCodeSettings};
+use settings::{Settings, VsCodeSettings};
use settings_ui_macros::{SettingsKey, SettingsUi};
/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
@@ -24,6 +27,21 @@ pub enum BaseKeymap {
None,
}
+impl From<BaseKeymapContent> for BaseKeymap {
+ fn from(value: BaseKeymapContent) -> Self {
+ match value {
+ BaseKeymapContent::VSCode => Self::VSCode,
+ BaseKeymapContent::JetBrains => Self::JetBrains,
+ BaseKeymapContent::SublimeText => Self::SublimeText,
+ BaseKeymapContent::Atom => Self::Atom,
+ BaseKeymapContent::TextMate => Self::TextMate,
+ BaseKeymapContent::Emacs => Self::Emacs,
+ BaseKeymapContent::Cursor => Self::Cursor,
+ BaseKeymapContent::None => Self::None,
+ }
+ }
+}
+
impl Display for BaseKeymap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
@@ -120,25 +138,17 @@ pub struct BaseKeymapSetting {
}
impl Settings for BaseKeymap {
- type FileContent = BaseKeymapSetting;
+ fn from_file(s: &crate::settings_content::SettingsContent) -> Option<Self> {
+ s.base_keymap.map(Into::into)
+ }
- fn load(
- sources: SettingsSources<Self::FileContent>,
- _: &mut gpui::App,
- ) -> anyhow::Result<Self> {
- if let Some(Some(user_value)) = sources.user.map(|setting| setting.base_keymap) {
- return Ok(user_value);
- }
- if let Some(Some(server_value)) = sources.server.map(|setting| setting.base_keymap) {
- return Ok(server_value);
- }
- sources
- .default
- .base_keymap
- .ok_or_else(Self::missing_default)
+ fn refine(&mut self, s: &settings_content::SettingsContent) {
+ if let Some(base_keymap) = s.base_keymap {
+ *self = base_keymap.into();
+ };
}
- fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut Self::FileContent) {
- current.base_keymap = Some(BaseKeymap::VSCode);
+ fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) {
+ current.base_keymap = Some(BaseKeymapContent::VSCode);
}
}
@@ -1,7 +1,7 @@
use fs::Fs;
use gpui::{App, RenderOnce, SharedString};
-use crate::{Settings, update_settings_file};
+use crate::{Settings, settings_content::SettingsContent, update_settings_file};
/// A UI control that can be used to edit a setting.
pub trait EditableSettingControl: RenderOnce {
@@ -20,17 +20,13 @@ pub trait EditableSettingControl: RenderOnce {
/// Applies the given setting file to the settings file contents.
///
/// This will be called when writing the setting value back to the settings file.
- fn apply(
- settings: &mut <Self::Settings as Settings>::FileContent,
- value: Self::Value,
- cx: &App,
- );
+ fn apply(settings: &mut SettingsContent, value: Self::Value, cx: &App);
/// Writes the given setting value to the settings files.
fn write(value: Self::Value, cx: &App) {
let fs = <dyn Fs>::global(cx);
- update_settings_file::<Self::Settings>(fs, cx, move |settings, cx| {
+ update_settings_file(fs, cx, move |settings, cx| {
Self::apply(settings, value, cx);
});
}
@@ -1,6 +1,7 @@
mod base_keymap_setting;
mod editable_setting_control;
mod keymap_file;
+mod settings_content;
mod settings_file;
mod settings_json;
mod settings_store;
@@ -0,0 +1,972 @@
+use std::borrow::Cow;
+use std::env;
+use std::num::NonZeroU32;
+use std::sync::Arc;
+
+use collections::{HashMap, HashSet};
+use gpui::{App, Modifiers, SharedString};
+use release_channel::ReleaseChannel;
+use schemars::{JsonSchema, json_schema};
+use serde::de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor};
+use serde::{Deserialize, Deserializer, Serialize};
+use util::schemars::replace_subschema;
+use util::serde::default_true;
+
+use crate::{ActiveSettingsProfileName, ParameterizedJsonSchema, Settings};
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
+pub struct SettingsContent {
+ #[serde(flatten)]
+ pub project: ProjectSettingsContent,
+
+ pub base_keymap: Option<BaseKeymapContent>,
+}
+
+impl SettingsContent {
+ pub fn languages_mut(&mut self) -> &mut HashMap<SharedString, LanguageSettingsContent> {
+ &mut self.project.all_languages.languages.0
+ }
+}
+
+// todo!() what should this be?
+#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ServerSettingsContent {
+ #[serde(flatten)]
+ project: ProjectSettingsContent,
+}
+
+#[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
+pub(crate) struct UserSettingsContent {
+ #[serde(flatten)]
+ pub(crate) content: SettingsContent,
+
+ pub(crate) dev: Option<SettingsContent>,
+ pub(crate) nightly: Option<SettingsContent>,
+ pub(crate) preview: Option<SettingsContent>,
+ pub(crate) stable: Option<SettingsContent>,
+
+ pub(crate) macos: Option<SettingsContent>,
+ pub(crate) windows: Option<SettingsContent>,
+ pub(crate) linux: Option<SettingsContent>,
+
+ #[serde(default)]
+ pub(crate) profiles: HashMap<String, SettingsContent>,
+}
+
+pub struct ExtensionsSettingsContent {
+ pub(crate) all_languages: AllLanguageSettingsContent,
+}
+
+impl UserSettingsContent {
+ pub(crate) fn for_release_channel(&self) -> Option<&SettingsContent> {
+ match *release_channel::RELEASE_CHANNEL {
+ ReleaseChannel::Dev => self.dev.as_ref(),
+ ReleaseChannel::Nightly => self.nightly.as_ref(),
+ ReleaseChannel::Preview => self.preview.as_ref(),
+ ReleaseChannel::Stable => self.stable.as_ref(),
+ }
+ }
+
+ pub(crate) fn for_os(&self) -> Option<&SettingsContent> {
+ match env::consts::OS {
+ "macos" => self.macos.as_ref(),
+ "linux" => self.linux.as_ref(),
+ "windows" => self.windows.as_ref(),
+ _ => None,
+ }
+ }
+
+ pub(crate) fn for_profile(&self, cx: &App) -> Option<&SettingsContent> {
+ let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>() else {
+ return None;
+ };
+ self.profiles.get(&active_profile.0)
+ }
+}
+
+/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
+///
+/// Default: VSCode
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
+pub enum BaseKeymapContent {
+ #[default]
+ VSCode,
+ JetBrains,
+ SublimeText,
+ Atom,
+ TextMate,
+ Emacs,
+ Cursor,
+ None,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct ProjectSettingsContent {
+ #[serde(flatten)]
+ pub(crate) all_languages: AllLanguageSettingsContent,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct AllLanguageSettingsContent {
+ /// The settings for enabling/disabling features.
+ #[serde(default)]
+ pub features: Option<FeaturesContent>,
+ /// The edit prediction settings.
+ #[serde(default)]
+ pub edit_predictions: Option<EditPredictionSettingsContent>,
+ /// The default language settings.
+ #[serde(flatten)]
+ pub defaults: LanguageSettingsContent,
+ /// The settings for individual languages.
+ #[serde(default)]
+ pub languages: LanguageToSettingsMap,
+ /// Settings for associating file extensions and filenames
+ /// with languages.
+ #[serde(default)]
+ pub file_types: HashMap<SharedString, Vec<String>>,
+}
+
+/// The settings for enabling/disabling features.
+#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct FeaturesContent {
+ /// Determines which edit prediction provider to use.
+ pub edit_prediction_provider: Option<EditPredictionProviderContent>,
+}
+
+/// The provider that supplies edit predictions.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum EditPredictionProviderContent {
+ None,
+ #[default]
+ Copilot,
+ Supermaven,
+ Zed,
+}
+
+/// The contents of the edit prediction settings.
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct EditPredictionSettingsContent {
+ /// A list of globs representing files that edit predictions should be disabled for.
+ /// This list adds to a pre-existing, sensible default set of globs.
+ /// Any additional ones you add are combined with them.
+ #[serde(default)]
+ pub disabled_globs: Option<Vec<String>>,
+ /// The mode used to display edit predictions in the buffer.
+ /// Provider support required.
+ #[serde(default)]
+ pub mode: EditPredictionsModeContent,
+ /// Settings specific to GitHub Copilot.
+ #[serde(default)]
+ pub copilot: CopilotSettingsContent,
+ /// Whether edit predictions are enabled in the assistant prompt editor.
+ /// This has no effect if globally disabled.
+ #[serde(default = "default_true")]
+ pub enabled_in_text_threads: bool,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct CopilotSettingsContent {
+ /// HTTP/HTTPS proxy to use for Copilot.
+ ///
+ /// Default: none
+ #[serde(default)]
+ pub proxy: Option<String>,
+ /// Disable certificate verification for the proxy (not recommended).
+ ///
+ /// Default: false
+ #[serde(default)]
+ pub proxy_no_verify: Option<bool>,
+ /// Enterprise URI for Copilot.
+ ///
+ /// Default: none
+ #[serde(default)]
+ pub enterprise_uri: Option<String>,
+}
+
+/// The mode in which edit predictions should be displayed.
+#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum EditPredictionsModeContent {
+ /// If provider supports it, display inline when holding modifier key (e.g., alt).
+ /// Otherwise, eager preview is used.
+ #[serde(alias = "auto")]
+ Subtle,
+ /// Display inline when there are no language server completions available.
+ #[default]
+ #[serde(alias = "eager_preview")]
+ Eager,
+}
+
+/// Controls the soft-wrapping behavior in the editor.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum SoftWrapContent {
+ /// Prefer a single line generally, unless an overly long line is encountered.
+ None,
+ /// Deprecated: use None instead. Left to avoid breaking existing users' configs.
+ /// Prefer a single line generally, unless an overly long line is encountered.
+ PreferLine,
+ /// Soft wrap lines that exceed the editor width.
+ EditorWidth,
+ /// Soft wrap lines at the preferred line length.
+ PreferredLineLength,
+ /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
+ Bounded,
+}
+
+/// The settings for a particular language.
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct LanguageSettingsContent {
+ /// How many columns a tab should occupy.
+ ///
+ /// Default: 4
+ #[serde(default)]
+ pub tab_size: Option<NonZeroU32>,
+ /// Whether to indent lines using tab characters, as opposed to multiple
+ /// spaces.
+ ///
+ /// Default: false
+ #[serde(default)]
+ pub hard_tabs: Option<bool>,
+ /// How to soft-wrap long lines of text.
+ ///
+ /// Default: none
+ #[serde(default)]
+ pub soft_wrap: Option<SoftWrapContent>,
+ /// The column at which to soft-wrap lines, for buffers where soft-wrap
+ /// is enabled.
+ ///
+ /// Default: 80
+ #[serde(default)]
+ pub preferred_line_length: Option<u32>,
+ /// Whether to show wrap guides in the editor. Setting this to true will
+ /// show a guide at the 'preferred_line_length' value if softwrap is set to
+ /// 'preferred_line_length', and will show any additional guides as specified
+ /// by the 'wrap_guides' setting.
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub show_wrap_guides: Option<bool>,
+ /// Character counts at which to show wrap guides in the editor.
+ ///
+ /// Default: []
+ #[serde(default)]
+ pub wrap_guides: Option<Vec<usize>>,
+ /// Indent guide related settings.
+ #[serde(default)]
+ pub indent_guides: Option<IndentGuideSettingsContent>,
+ /// Whether or not to perform a buffer format before saving.
+ ///
+ /// Default: on
+ #[serde(default)]
+ pub format_on_save: Option<FormatOnSave>,
+ /// Whether or not to remove any trailing whitespace from lines of a buffer
+ /// before saving it.
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub remove_trailing_whitespace_on_save: Option<bool>,
+ /// Whether or not to ensure there's a single newline at the end of a buffer
+ /// when saving it.
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub ensure_final_newline_on_save: Option<bool>,
+ /// How to perform a buffer format.
+ ///
+ /// Default: auto
+ #[serde(default)]
+ pub formatter: Option<SelectedFormatter>,
+ /// Zed's Prettier integration settings.
+ /// Allows to enable/disable formatting with Prettier
+ /// and configure default Prettier, used when no project-level Prettier installation is found.
+ ///
+ /// Default: off
+ #[serde(default)]
+ pub prettier: Option<PrettierSettings>,
+ /// Whether to automatically close JSX tags.
+ #[serde(default)]
+ pub jsx_tag_auto_close: Option<JsxTagAutoCloseSettings>,
+ /// Whether to use language servers to provide code intelligence.
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub enable_language_server: Option<bool>,
+ /// The list of language servers to use (or disable) for this language.
+ ///
+ /// This array should consist of language server IDs, as well as the following
+ /// special tokens:
+ /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
+ /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
+ ///
+ /// Default: ["..."]
+ #[serde(default)]
+ pub language_servers: Option<Vec<String>>,
+ /// Controls where the `editor::Rewrap` action is allowed for this language.
+ ///
+ /// Note: This setting has no effect in Vim mode, as rewrap is already
+ /// allowed everywhere.
+ ///
+ /// Default: "in_comments"
+ #[serde(default)]
+ pub allow_rewrap: Option<RewrapBehavior>,
+ /// Controls whether edit predictions are shown immediately (true)
+ /// or manually by triggering `editor::ShowEditPrediction` (false).
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub show_edit_predictions: Option<bool>,
+ /// Controls whether edit predictions are shown in the given language
+ /// scopes.
+ ///
+ /// Example: ["string", "comment"]
+ ///
+ /// Default: []
+ #[serde(default)]
+ pub edit_predictions_disabled_in: Option<Vec<String>>,
+ /// Whether to show tabs and spaces in the editor.
+ #[serde(default)]
+ pub show_whitespaces: Option<ShowWhitespaceSetting>,
+ /// Visible characters used to render whitespace when show_whitespaces is enabled.
+ ///
+ /// Default: "•" for spaces, "→" for tabs.
+ #[serde(default)]
+ pub whitespace_map: Option<WhitespaceMap>,
+ /// Whether to start a new line with a comment when a previous line is a comment as well.
+ ///
+ /// Default: true
+ #[serde(default)]
+ pub extend_comment_on_newline: Option<bool>,
+ /// Inlay hint related settings.
+ #[serde(default)]
+ pub inlay_hints: Option<InlayHintSettings>,
+ /// Whether to automatically type closing characters for you. For example,
+ /// when you type (, Zed will automatically add a closing ) at the correct position.
+ ///
+ /// Default: true
+ pub use_autoclose: Option<bool>,
+ /// Whether to automatically surround text with characters for you. For example,
+ /// when you select text and type (, Zed will automatically surround text with ().
+ ///
+ /// Default: true
+ pub use_auto_surround: Option<bool>,
+ /// Controls how the editor handles the autoclosed characters.
+ /// When set to `false`(default), skipping over and auto-removing of the closing characters
+ /// happen only for auto-inserted characters.
+ /// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
+ /// no matter how they were inserted.
+ ///
+ /// Default: false
+ pub always_treat_brackets_as_autoclosed: Option<bool>,
+ /// Whether to use additional LSP queries to format (and amend) the code after
+ /// every "trigger" symbol input, defined by LSP server capabilities.
+ ///
+ /// Default: true
+ pub use_on_type_format: Option<bool>,
+ /// Which code actions to run on save after the formatter.
+ /// These are not run if formatting is off.
+ ///
+ /// Default: {} (or {"source.organizeImports": true} for Go).
+ pub code_actions_on_format: Option<HashMap<String, bool>>,
+ /// Whether to perform linked edits of associated ranges, if the language server supports it.
+ /// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
+ ///
+ /// Default: true
+ pub linked_edits: Option<bool>,
+ /// Whether indentation should be adjusted based on the context whilst typing.
+ ///
+ /// Default: true
+ pub auto_indent: Option<bool>,
+ /// Whether indentation of pasted content should be adjusted based on the context.
+ ///
+ /// Default: true
+ pub auto_indent_on_paste: Option<bool>,
+ /// Task configuration for this language.
+ ///
+ /// Default: {}
+ pub tasks: Option<LanguageTaskConfig>,
+ /// Whether to pop the completions menu while typing in an editor without
+ /// explicitly requesting it.
+ ///
+ /// Default: true
+ pub show_completions_on_input: Option<bool>,
+ /// Whether to display inline and alongside documentation for items in the
+ /// completions menu.
+ ///
+ /// Default: true
+ pub show_completion_documentation: Option<bool>,
+ /// Controls how completions are processed for this language.
+ pub completions: Option<CompletionSettings>,
+ /// Preferred debuggers for this language.
+ ///
+ /// Default: []
+ pub debuggers: Option<Vec<String>>,
+}
+
+/// Controls how whitespace should be displayedin the editor.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ShowWhitespaceSetting {
+ /// Draw whitespace only for the selected text.
+ Selection,
+ /// Do not draw any tabs or spaces.
+ None,
+ /// Draw all invisible symbols.
+ All,
+ /// Draw whitespaces at boundaries only.
+ ///
+ /// For a whitespace to be on a boundary, any of the following conditions need to be met:
+ /// - It is a tab
+ /// - It is adjacent to an edge (start or end)
+ /// - It is adjacent to a whitespace (left or right)
+ Boundary,
+ /// Draw whitespaces only after non-whitespace characters.
+ Trailing,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct WhitespaceMap {
+ #[serde(default)]
+ pub space: Option<String>,
+ #[serde(default)]
+ pub tab: Option<String>,
+}
+
+impl WhitespaceMap {
+ pub fn space(&self) -> SharedString {
+ self.space
+ .as_ref()
+ .map_or_else(|| SharedString::from("•"), |s| SharedString::from(s))
+ }
+
+ pub fn tab(&self) -> SharedString {
+ self.tab
+ .as_ref()
+ .map_or_else(|| SharedString::from("→"), |s| SharedString::from(s))
+ }
+}
+
+/// The behavior of `editor::Rewrap`.
+#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum RewrapBehavior {
+ /// Only rewrap within comments.
+ #[default]
+ InComments,
+ /// Only rewrap within the current selection(s).
+ InSelections,
+ /// Allow rewrapping anywhere.
+ Anywhere,
+}
+
+#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+pub struct JsxTagAutoCloseSettings {
+ /// Enables or disables auto-closing of JSX tags.
+ #[serde(default)]
+ pub enabled: bool,
+}
+
+/// The settings for inlay hints.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+pub struct InlayHintSettings {
+ /// Global switch to toggle hints on and off.
+ ///
+ /// Default: false
+ #[serde(default)]
+ pub enabled: bool,
+ /// Global switch to toggle inline values on and off when debugging.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub show_value_hints: bool,
+ /// Whether type hints should be shown.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub show_type_hints: bool,
+ /// Whether parameter hints should be shown.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub show_parameter_hints: bool,
+ /// Whether other hints should be shown.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub show_other_hints: bool,
+ /// Whether to show a background for inlay hints.
+ ///
+ /// If set to `true`, the background will use the `hint.background` color
+ /// from the current theme.
+ ///
+ /// Default: false
+ #[serde(default)]
+ pub show_background: bool,
+ /// Whether or not to debounce inlay hints updates after buffer edits.
+ ///
+ /// Set to 0 to disable debouncing.
+ ///
+ /// Default: 700
+ #[serde(default = "edit_debounce_ms")]
+ pub edit_debounce_ms: u64,
+ /// Whether or not to debounce inlay hints updates after buffer scrolls.
+ ///
+ /// Set to 0 to disable debouncing.
+ ///
+ /// Default: 50
+ #[serde(default = "scroll_debounce_ms")]
+ pub scroll_debounce_ms: u64,
+ /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
+ /// If only a subset of the modifiers specified is pressed, hints are not toggled.
+ /// If no modifiers are specified, this is equivalent to `None`.
+ ///
+ /// Default: None
+ #[serde(default)]
+ pub toggle_on_modifiers_press: Option<Modifiers>,
+}
+
+fn edit_debounce_ms() -> u64 {
+ 700
+}
+
+fn scroll_debounce_ms() -> u64 {
+ 50
+}
+
+/// Controls how completions are processed for this language.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub struct CompletionSettings {
+ /// Controls how words are completed.
+ /// For large documents, not all words may be fetched for completion.
+ ///
+ /// Default: `fallback`
+ #[serde(default = "default_words_completion_mode")]
+ pub words: WordsCompletionMode,
+ /// How many characters has to be in the completions query to automatically show the words-based completions.
+ /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
+ ///
+ /// Default: 3
+ #[serde(default = "default_3")]
+ pub words_min_length: usize,
+ /// Whether to fetch LSP completions or not.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub lsp: bool,
+ /// When fetching LSP completions, determines how long to wait for a response of a particular server.
+ /// When set to 0, waits indefinitely.
+ ///
+ /// Default: 0
+ #[serde(default)]
+ pub lsp_fetch_timeout_ms: u64,
+ /// Controls how LSP completions are inserted.
+ ///
+ /// Default: "replace_suffix"
+ #[serde(default = "default_lsp_insert_mode")]
+ pub lsp_insert_mode: LspInsertMode,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum LspInsertMode {
+ /// Replaces text before the cursor, using the `insert` range described in the LSP specification.
+ Insert,
+ /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
+ Replace,
+ /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
+ /// and like `"insert"` otherwise.
+ ReplaceSubsequence,
+ /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
+ /// `"insert"` otherwise.
+ ReplaceSuffix,
+}
+
+/// Controls how document's words are completed.
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum WordsCompletionMode {
+ /// Always fetch document's words for completions along with LSP completions.
+ Enabled,
+ /// Only if LSP response errors or times out,
+ /// use document's words to show completions.
+ Fallback,
+ /// Never fetch or complete document's words for completions.
+ /// (Word-based completions can still be queried via a separate action)
+ Disabled,
+}
+
+fn default_words_completion_mode() -> WordsCompletionMode {
+ WordsCompletionMode::Fallback
+}
+
+fn default_lsp_insert_mode() -> LspInsertMode {
+ LspInsertMode::ReplaceSuffix
+}
+
+fn default_3() -> usize {
+ 3
+}
+
+/// Allows to enable/disable formatting with Prettier
+/// and configure default Prettier, used when no project-level Prettier installation is found.
+/// Prettier formatting is disabled by default.
+#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+pub struct PrettierSettings {
+ /// Enables or disables formatting with Prettier for a given language.
+ #[serde(default)]
+ pub allowed: bool,
+
+ /// Forces Prettier integration to use a specific parser name when formatting files with the language.
+ #[serde(default)]
+ pub parser: Option<String>,
+
+ /// Forces Prettier integration to use specific plugins when formatting files with the language.
+ /// The default Prettier will be installed with these plugins.
+ #[serde(default)]
+ pub plugins: HashSet<String>,
+
+ /// Default Prettier options, in the format as in package.json section for Prettier.
+ /// If project installs Prettier via its package.json, these options will be ignored.
+ #[serde(flatten)]
+ pub options: HashMap<String, serde_json::Value>,
+}
+/// Controls the behavior of formatting files when they are saved.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub enum FormatOnSave {
+ /// Files should be formatted on save.
+ On,
+ /// Files should not be formatted on save.
+ Off,
+ List(FormatterList),
+}
+
+impl JsonSchema for FormatOnSave {
+ fn schema_name() -> Cow<'static, str> {
+ "OnSaveFormatter".into()
+ }
+
+ fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
+ let formatter_schema = Formatter::json_schema(generator);
+
+ json_schema!({
+ "oneOf": [
+ {
+ "type": "array",
+ "items": formatter_schema
+ },
+ {
+ "type": "string",
+ "enum": ["on", "off", "language_server"]
+ },
+ formatter_schema
+ ]
+ })
+ }
+}
+
+impl Serialize for FormatOnSave {
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ Self::On => serializer.serialize_str("on"),
+ Self::Off => serializer.serialize_str("off"),
+ Self::List(list) => list.serialize(serializer),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for FormatOnSave {
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct FormatDeserializer;
+
+ impl<'d> Visitor<'d> for FormatDeserializer {
+ type Value = FormatOnSave;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a valid on-save formatter kind")
+ }
+ fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ if v == "on" {
+ Ok(Self::Value::On)
+ } else if v == "off" {
+ Ok(Self::Value::Off)
+ } else if v == "language_server" {
+ Ok(Self::Value::List(FormatterList::Single(
+ Formatter::LanguageServer { name: None },
+ )))
+ } else {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(v.into_deserializer());
+ ret.map(Self::Value::List)
+ }
+ }
+ fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
+ where
+ A: MapAccess<'d>,
+ {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
+ ret.map(Self::Value::List)
+ }
+ fn visit_seq<A>(self, map: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'d>,
+ {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
+ ret.map(Self::Value::List)
+ }
+ }
+ deserializer.deserialize_any(FormatDeserializer)
+ }
+}
+
+/// Controls which formatter should be used when formatting code.
+#[derive(Clone, Debug, Default, PartialEq, Eq)]
+pub enum SelectedFormatter {
+ /// Format files using Zed's Prettier integration (if applicable),
+ /// or falling back to formatting via language server.
+ #[default]
+ Auto,
+ List(FormatterList),
+}
+
+impl JsonSchema for SelectedFormatter {
+ fn schema_name() -> Cow<'static, str> {
+ "Formatter".into()
+ }
+
+ fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
+ let formatter_schema = Formatter::json_schema(generator);
+
+ json_schema!({
+ "oneOf": [
+ {
+ "type": "array",
+ "items": formatter_schema
+ },
+ {
+ "type": "string",
+ "enum": ["auto", "language_server"]
+ },
+ formatter_schema
+ ]
+ })
+ }
+}
+
+impl Serialize for SelectedFormatter {
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: serde::Serializer,
+ {
+ match self {
+ SelectedFormatter::Auto => serializer.serialize_str("auto"),
+ SelectedFormatter::List(list) => list.serialize(serializer),
+ }
+ }
+}
+
+impl<'de> Deserialize<'de> for SelectedFormatter {
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ struct FormatDeserializer;
+
+ impl<'d> Visitor<'d> for FormatDeserializer {
+ type Value = SelectedFormatter;
+
+ fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+ formatter.write_str("a valid formatter kind")
+ }
+ fn visit_str<E>(self, v: &str) -> std::result::Result<Self::Value, E>
+ where
+ E: serde::de::Error,
+ {
+ if v == "auto" {
+ Ok(Self::Value::Auto)
+ } else if v == "language_server" {
+ Ok(Self::Value::List(FormatterList::Single(
+ Formatter::LanguageServer { name: None },
+ )))
+ } else {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(v.into_deserializer());
+ ret.map(SelectedFormatter::List)
+ }
+ }
+ fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
+ where
+ A: MapAccess<'d>,
+ {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(de::value::MapAccessDeserializer::new(map));
+ ret.map(SelectedFormatter::List)
+ }
+ fn visit_seq<A>(self, map: A) -> Result<Self::Value, A::Error>
+ where
+ A: SeqAccess<'d>,
+ {
+ let ret: Result<FormatterList, _> =
+ Deserialize::deserialize(de::value::SeqAccessDeserializer::new(map));
+ ret.map(SelectedFormatter::List)
+ }
+ }
+ deserializer.deserialize_any(FormatDeserializer)
+ }
+}
+
+/// Controls which formatters should be used when formatting code.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(untagged)]
+pub enum FormatterList {
+ Single(Formatter),
+ Vec(Vec<Formatter>),
+}
+
+impl Default for FormatterList {
+ fn default() -> Self {
+ Self::Single(Formatter::default())
+ }
+}
+
+/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration.
+#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum Formatter {
+ /// Format code using the current language server.
+ LanguageServer { name: Option<String> },
+ /// Format code using Zed's Prettier integration.
+ #[default]
+ Prettier,
+ /// Format code using an external command.
+ External {
+ /// The external program to run.
+ command: Arc<str>,
+ /// The arguments to pass to the program.
+ arguments: Option<Arc<[String]>>,
+ },
+ /// Files should be formatted using code actions executed by language servers.
+ CodeActions(HashMap<String, bool>),
+}
+
+/// The settings for indent guides.
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+pub struct IndentGuideSettingsContent {
+ /// Whether to display indent guides in the editor.
+ ///
+ /// Default: true
+ #[serde(default = "default_true")]
+ pub enabled: bool,
+ /// The width of the indent guides in pixels, between 1 and 10.
+ ///
+ /// Default: 1
+ #[serde(default = "line_width")]
+ pub line_width: u32,
+ /// The width of the active indent guide in pixels, between 1 and 10.
+ ///
+ /// Default: 1
+ #[serde(default = "active_line_width")]
+ pub active_line_width: u32,
+ /// Determines how indent guides are colored.
+ ///
+ /// Default: Fixed
+ #[serde(default)]
+ pub coloring: IndentGuideColoring,
+ /// Determines how indent guide backgrounds are colored.
+ ///
+ /// Default: Disabled
+ #[serde(default)]
+ pub background_coloring: IndentGuideBackgroundColoring,
+}
+
+fn line_width() -> u32 {
+ 1
+}
+
+fn active_line_width() -> u32 {
+ line_width()
+}
+
+/// The task settings for a particular language.
+#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)]
+pub struct LanguageTaskConfig {
+ /// Extra task variables to set for a particular language.
+ #[serde(default)]
+ pub variables: HashMap<String, String>,
+ #[serde(default = "default_true")]
+ pub enabled: bool,
+ /// Use LSP tasks over Zed language extension ones.
+ /// If no LSP tasks are returned due to error/timeout or regular execution,
+ /// Zed language extension tasks will be used instead.
+ ///
+ /// Other Zed tasks will still be shown:
+ /// * Zed task from either of the task config file
+ /// * Zed task from history (e.g. one-off task was spawned before)
+ #[serde(default = "default_true")]
+ pub prefer_lsp: bool,
+}
+
+/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language
+/// names in the keys.
+#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
+pub struct LanguageToSettingsMap(pub HashMap<SharedString, LanguageSettingsContent>);
+
+inventory::submit! {
+ ParameterizedJsonSchema {
+ add_and_get_ref: |generator, params, _cx| {
+ let language_settings_content_ref = generator
+ .subschema_for::<LanguageSettingsContent>()
+ .to_value();
+ replace_subschema::<LanguageToSettingsMap>(generator, || json_schema!({
+ "type": "object",
+ "properties": params
+ .language_names
+ .iter()
+ .map(|name| {
+ (
+ name.clone(),
+ language_settings_content_ref.clone(),
+ )
+ })
+ .collect::<serde_json::Map<_, _>>()
+ }))
+ }
+ }
+}
+
+/// Determines how indent guides are colored.
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum IndentGuideColoring {
+ /// Do not render any lines for indent guides.
+ Disabled,
+ /// Use the same color for all indentation levels.
+ #[default]
+ Fixed,
+ /// Use a different color for each indentation level.
+ IndentAware,
+}
+
+/// Determines how indent guide backgrounds are colored.
+#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum IndentGuideBackgroundColoring {
+ /// Do not render any background for indent guides.
+ #[default]
+ Disabled,
+ /// Use a different color for each indentation level.
+ IndentAware,
+}
@@ -1,4 +1,4 @@
-use crate::{Settings, settings_store::SettingsStore};
+use crate::{Settings, settings_content::SettingsContent, settings_store::SettingsStore};
use collections::HashSet;
use fs::{Fs, PathEventKind};
use futures::{StreamExt, channel::mpsc};
@@ -126,10 +126,10 @@ pub fn watch_config_dir(
rx
}
-pub fn update_settings_file<T: Settings>(
+pub fn update_settings_file(
fs: Arc<dyn Fs>,
cx: &App,
- update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
+ update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
) {
- SettingsStore::global(cx).update_settings_file::<T>(fs, update);
+ SettingsStore::global(cx).update_settings_file(fs, update);
}
@@ -33,7 +33,11 @@ pub type EditorconfigProperties = ec4rs::Properties;
use crate::{
ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry,
VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text,
- settings_ui_core::SettingsUi, update_value_in_json_text,
+ settings_content::{
+ ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent,
+ UserSettingsContent,
+ },
+ update_value_in_json_text,
};
pub trait SettingsKey: 'static + Send + Sync {
@@ -48,7 +52,7 @@ pub trait SettingsKey: 'static + Send + Sync {
/// 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 {
+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.
@@ -58,30 +62,9 @@ pub trait Settings: 'static + Send + Sync {
/// user settings match the current version of the settings.
const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
- /// The type that is stored in an individual JSON file.
- type FileContent: Clone
- + Default
- + Serialize
- + DeserializeOwned
- + JsonSchema
- + SettingsUi
- + SettingsKey;
-
- /*
- * let path = Settings
- *
- *
- */
- /// The logic for combining together values from one or more JSON files into the
- /// final value for this setting.
- ///
- /// # Warning
- /// `Self::FileContent` deserialized field names should match with `Self` deserialized field names
- /// otherwise the field won't be deserialized properly and you will get the error:
- /// "A default setting must be added to the `default.json` file"
- fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self>
- where
- Self: Sized;
+ fn from_file(content: &SettingsContent) -> Option<Self>;
+
+ fn refine(&mut self, content: &SettingsContent);
fn missing_default() -> anyhow::Error {
anyhow::anyhow!("missing default for: {}", std::any::type_name::<Self>())
@@ -89,7 +72,7 @@ pub trait Settings: 'static + Send + Sync {
/// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
/// equivalent settings from a vscode config to our config
- fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent);
+ fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent);
#[track_caller]
fn register(cx: &mut App)
@@ -218,17 +201,15 @@ pub struct SettingsLocation<'a> {
/// A set of strongly-typed setting values defined via multiple config files.
pub struct SettingsStore {
setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
- raw_default_settings: Value,
- raw_global_settings: Option<Value>,
- raw_user_settings: Value,
- raw_server_settings: Option<Value>,
- raw_extension_settings: Value,
- raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), Value>,
+ default_settings: Option<SettingsContent>,
+ user_settings: Option<UserSettingsContent>,
+ global_settings: Option<SettingsContent>,
+
+ extension_settings: Option<SettingsContent>,
+ server_settings: Option<SettingsContent>,
+ local_settings: BTreeMap<(WorktreeId, Arc<Path>), SettingsContent>,
raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>,
- tab_size_callback: Option<(
- TypeId,
- Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
- )>,
+
_setting_file_updates: Task<()>,
setting_file_updates_tx:
mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>,
@@ -271,20 +252,11 @@ struct SettingValue<T> {
}
trait AnySettingValue: 'static + Send + Sync {
- fn key(&self) -> Option<&'static str>;
fn setting_type_name(&self) -> &'static str;
- fn deserialize_setting(&self, json: &Value) -> Result<DeserializedSetting> {
- self.deserialize_setting_with_key(json).1
- }
- fn deserialize_setting_with_key(
- &self,
- json: &Value,
- ) -> (Option<&'static str>, Result<DeserializedSetting>);
- fn load_setting(
- &self,
- sources: SettingsSources<DeserializedSetting>,
- cx: &mut App,
- ) -> Result<Box<dyn Any>>;
+
+ fn from_file(&self, s: &SettingsContent) -> Option<Box<dyn Any>>;
+ fn refine(&self, value: &mut dyn Any, s: &[&SettingsContent]);
+
fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any;
fn all_local_values(&self) -> Vec<(WorktreeId, Arc<Path>, &dyn Any)>;
fn set_global_value(&mut self, value: Box<dyn Any>);
@@ -308,14 +280,13 @@ impl SettingsStore {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
Self {
setting_values: Default::default(),
- raw_default_settings: json!({}),
- raw_global_settings: None,
- raw_user_settings: json!({}),
- raw_server_settings: None,
- raw_extension_settings: json!({}),
- raw_local_settings: Default::default(),
+ default_settings: Some(Default::default()), // todo!()
+ global_settings: None,
+ server_settings: None,
+ user_settings: Some(Default::default()), // todo!()
+ extension_settings: None,
+ local_settings: BTreeMap::default(),
raw_editorconfig_settings: BTreeMap::default(),
- tab_size_callback: Default::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 {
@@ -354,71 +325,39 @@ impl SettingsStore {
local_values: Vec::new(),
}));
- if let Some(default_settings) = setting_value
- .deserialize_setting(&self.raw_default_settings)
- .log_err()
- {
- let user_value = setting_value
- .deserialize_setting(&self.raw_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())
- {
- release_channel_value = setting_value
- .deserialize_setting(release_settings)
- .log_err();
- }
+ let mut refinements = Vec::default();
- let mut os_settings_value = None;
- if let Some(os_settings) = &self.raw_user_settings.get(env::consts::OS) {
- os_settings_value = setting_value.deserialize_setting(os_settings).log_err();
- }
+ if let Some(extension_settings) = self.extension_settings.as_ref() {
+ refinements.push(extension_settings)
+ }
- let mut profile_value = None;
- if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>()
- && let Some(profiles) = self.raw_user_settings.get("profiles")
- && let Some(profile_settings) = profiles.get(&active_profile.0)
- {
- profile_value = setting_value
- .deserialize_setting(profile_settings)
- .log_err();
+ if let Some(user_settings) = self.user_settings.as_ref() {
+ refinements.push(&user_settings.content);
+ if let Some(release_channel) = user_settings.for_release_channel() {
+ refinements.push(release_channel)
}
-
- let server_value = self
- .raw_server_settings
- .as_ref()
- .and_then(|server_setting| {
- setting_value.deserialize_setting(server_setting).log_err()
- });
-
- let extension_value = setting_value
- .deserialize_setting(&self.raw_extension_settings)
- .log_err();
-
- if let Some(setting) = setting_value
- .load_setting(
- SettingsSources {
- default: &default_settings,
- global: None,
- extensions: extension_value.as_ref(),
- user: user_value.as_ref(),
- release_channel: release_channel_value.as_ref(),
- operating_system: os_settings_value.as_ref(),
- profile: profile_value.as_ref(),
- server: server_value.as_ref(),
- project: &[],
- },
- cx,
- )
- .context("A default setting must be added to the `default.json` file")
- .log_err()
- {
- setting_value.set_global_value(setting);
+ if let Some(os) = user_settings.for_os() {
+ refinements.push(os)
}
+ if let Some(profile) = user_settings.for_profile(cx) {
+ refinements.push(profile)
+ }
+ }
+
+ if let Some(server_settings) = self.server_settings.as_ref() {
+ refinements.push(server_settings)
}
+ let default = self.default_settings.as_ref().unwrap();
+ // todo!() unwrap...
+ let mut value = T::from_file(default).unwrap();
+ for refinement in refinements {
+ value.refine(refinement)
+ }
+
+ setting_value.set_global_value(Box::new(value));
+
+ // todo!() local settings
+ // (they weren't handled before...)
}
/// Get the value of a setting.
@@ -476,35 +415,31 @@ impl SettingsStore {
///
/// For user-facing functionality use the typed setting interface.
/// (e.g. ProjectSettings::get_global(cx))
- pub fn raw_user_settings(&self) -> &Value {
- &self.raw_user_settings
+ pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> {
+ self.user_settings.as_ref()
}
/// Replaces current settings with the values from the given JSON.
- pub fn set_raw_user_settings(&mut self, new_settings: Value, cx: &mut App) -> Result<()> {
- self.raw_user_settings = new_settings;
+ pub fn set_raw_user_settings(
+ &mut self,
+ new_settings: UserSettingsContent,
+ cx: &mut App,
+ ) -> Result<()> {
+ self.user_settings = Some(new_settings);
self.recompute_values(None, cx)?;
Ok(())
}
/// Get the configured settings profile names.
pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> {
- self.raw_user_settings
- .get("profiles")
- .and_then(|v| v.as_object())
- .into_iter()
- .flat_map(|obj| obj.keys())
- .map(|s| s.as_str())
- }
-
- /// Access the raw JSON value of the global settings.
- pub fn raw_global_settings(&self) -> Option<&Value> {
- self.raw_global_settings.as_ref()
+ self.user_settings
+ .iter()
+ .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str()))
}
/// Access the raw JSON value of the default settings.
- pub fn raw_default_settings(&self) -> &Value {
- &self.raw_default_settings
+ pub fn raw_default_settings(&self) -> Option<&SettingsContent> {
+ self.default_settings.as_ref()
}
#[cfg(any(test, feature = "test-support"))]
@@ -521,13 +456,18 @@ impl SettingsStore {
/// This is only for tests. Normally, settings are only loaded from
/// JSON files.
#[cfg(any(test, feature = "test-support"))]
- pub fn update_user_settings<T: Settings>(
+ pub fn update_user_settings(
&mut self,
cx: &mut App,
- update: impl FnOnce(&mut T::FileContent),
+ update: impl FnOnce(&mut SettingsContent),
) {
- let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
- let new_text = self.new_text_for_update::<T>(old_text, update);
+ let mut content = self.user_settings.as_ref().unwrap().content.clone();
+ update(&mut content);
+ let new_text = serde_json::to_string(&UserSettingsContent {
+ content,
+ ..Default::default()
+ })
+ .unwrap();
self.set_user_settings(&new_text, cx).unwrap();
}
@@ -626,14 +566,14 @@ impl SettingsStore {
self.update_settings_file_inner(fs, update)
}
- pub fn update_settings_file<T: Settings>(
+ pub fn update_settings_file(
&self,
fs: Arc<dyn Fs>,
- update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
+ update: impl 'static + Send + FnOnce(&mut SettingsContent, &App),
) {
_ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
cx.read_global(|store: &SettingsStore, cx| {
- store.new_text_for_update::<T>(old_text, |content| update(content, cx))
+ store.new_text_for_update(old_text, |content| update(content, cx))
})
});
}
@@ -660,12 +600,12 @@ impl SettingsStore {
impl SettingsStore {
/// Updates the value of a setting in a JSON file, returning the new text
/// for that JSON file.
- pub fn new_text_for_update<T: Settings>(
+ pub fn new_text_for_update(
&self,
old_text: String,
- update: impl FnOnce(&mut T::FileContent),
+ update: impl FnOnce(&mut SettingsContent),
) -> String {
- let edits = self.edits_for_update::<T>(&old_text, update);
+ let edits = self.edits_for_update(&old_text, update);
let mut new_text = old_text;
for (range, replacement) in edits.into_iter() {
new_text.replace_range(range, &replacement);
@@ -689,36 +629,19 @@ impl SettingsStore {
/// Updates the value of a setting in a JSON file, returning a list
/// of edits to apply to the JSON file.
- pub fn edits_for_update<T: Settings>(
+ pub fn edits_for_update(
&self,
text: &str,
- update: impl FnOnce(&mut T::FileContent),
+ update: impl FnOnce(&mut SettingsContent),
) -> Vec<(Range<usize>, String)> {
- let setting_type_id = TypeId::of::<T>();
-
- let preserved_keys = T::PRESERVED_KEYS.unwrap_or_default();
-
- let setting = self
- .setting_values
- .get(&setting_type_id)
- .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>()));
- let raw_settings = parse_json_with_comments::<Value>(text).unwrap_or_default();
- let (key, deserialized_setting) = setting.deserialize_setting_with_key(&raw_settings);
- let old_content = match deserialized_setting {
- Ok(content) => content.0.downcast::<T::FileContent>().unwrap(),
- Err(_) => Box::<<T as Settings>::FileContent>::default(),
- };
+ let old_content: UserSettingsContent = serde_json::from_str(text).unwrap_or_default();
let mut new_content = old_content.clone();
- update(&mut new_content);
+ update(&mut new_content.content);
let old_value = serde_json::to_value(&old_content).unwrap();
let new_value = serde_json::to_value(new_content).unwrap();
let mut key_path = Vec::new();
- if let Some(key) = key {
- key_path.push(key);
- }
-
let mut edits = Vec::new();
let tab_size = self.json_tab_size();
let mut text = text.to_string();
@@ -728,35 +651,14 @@ impl SettingsStore {
tab_size,
&old_value,
&new_value,
- preserved_keys,
+ &[], // todo!() is this still needed?
&mut edits,
);
edits
}
- /// Configure the tab sized when updating JSON files.
- pub fn set_json_tab_size_callback<T: Settings>(
- &mut self,
- get_tab_size: fn(&T) -> Option<usize>,
- ) {
- self.tab_size_callback = Some((
- TypeId::of::<T>(),
- Box::new(move |value| get_tab_size(value.downcast_ref::<T>().unwrap())),
- ));
- }
-
pub fn json_tab_size(&self) -> usize {
- const DEFAULT_JSON_TAB_SIZE: usize = 2;
-
- if let Some((setting_type_id, callback)) = &self.tab_size_callback {
- let setting_value = self.setting_values.get(setting_type_id).unwrap();
- let value = setting_value.value_for_path(None);
- if let Some(value) = callback(value) {
- return value;
- }
- }
-
- DEFAULT_JSON_TAB_SIZE
+ 2
}
/// Sets the default settings via a JSON string.
@@ -767,29 +669,22 @@ impl SettingsStore {
default_settings_content: &str,
cx: &mut App,
) -> Result<()> {
- let settings: Value = parse_json_with_comments(default_settings_content)?;
- anyhow::ensure!(settings.is_object(), "settings must be an object");
- self.raw_default_settings = settings;
+ self.default_settings = parse_json_with_comments(default_settings_content)?;
self.recompute_values(None, cx)?;
Ok(())
}
/// Sets the user settings via a JSON string.
- pub fn set_user_settings(
- &mut self,
- user_settings_content: &str,
- cx: &mut App,
- ) -> Result<Value> {
- let settings: Value = if user_settings_content.is_empty() {
+ pub fn set_user_settings(&mut self, user_settings_content: &str, cx: &mut App) -> Result<()> {
+ let settings: UserSettingsContent = if user_settings_content.is_empty() {
parse_json_with_comments("{}")?
} else {
parse_json_with_comments(user_settings_content)?
};
- anyhow::ensure!(settings.is_object(), "settings must be an object");
- self.raw_user_settings = settings.clone();
+ self.user_settings = Some(settings);
self.recompute_values(None, cx)?;
- Ok(settings)
+ Ok(())
}
/// Sets the global settings via a JSON string.
@@ -797,17 +692,16 @@ impl SettingsStore {
&mut self,
global_settings_content: &str,
cx: &mut App,
- ) -> Result<Value> {
- let settings: Value = if global_settings_content.is_empty() {
+ ) -> Result<()> {
+ let settings: SettingsContent = if global_settings_content.is_empty() {
parse_json_with_comments("{}")?
} else {
parse_json_with_comments(global_settings_content)?
};
- anyhow::ensure!(settings.is_object(), "settings must be an object");
- self.raw_global_settings = Some(settings.clone());
+ self.global_settings = Some(settings);
self.recompute_values(None, cx)?;
- Ok(settings)
+ Ok(())
}
pub fn set_server_settings(
@@ -815,20 +709,14 @@ impl SettingsStore {
server_settings_content: &str,
cx: &mut App,
) -> Result<()> {
- let settings: Option<Value> = if server_settings_content.is_empty() {
+ let settings: Option<ServerSettingsContent> = if server_settings_content.is_empty() {
None
} else {
parse_json_with_comments(server_settings_content)?
};
- anyhow::ensure!(
- settings
- .as_ref()
- .map(|value| value.is_object())
- .unwrap_or(true),
- "settings must be an object"
- );
- self.raw_server_settings = settings;
+ todo!();
+ // self.server_settings = Some(settings);
self.recompute_values(None, cx)?;
Ok(())
}
@@ -864,7 +752,7 @@ impl SettingsStore {
}
(LocalSettingsKind::Settings, None) => {
zed_settings_changed = self
- .raw_local_settings
+ .local_settings
.remove(&(root_id, directory_path.clone()))
.is_some()
}
@@ -873,24 +761,27 @@ impl SettingsStore {
.remove(&(root_id, directory_path.clone()));
}
(LocalSettingsKind::Settings, Some(settings_contents)) => {
- let new_settings =
- parse_json_with_comments::<Value>(settings_contents).map_err(|e| {
- InvalidSettingsError::LocalSettings {
- path: directory_path.join(local_settings_file_relative_path()),
- message: e.to_string(),
- }
- })?;
- match self
- .raw_local_settings
- .entry((root_id, directory_path.clone()))
- {
+ let new_settings = parse_json_with_comments::<ProjectSettingsContent>(
+ settings_contents,
+ )
+ .map_err(|e| InvalidSettingsError::LocalSettings {
+ path: directory_path.join(local_settings_file_relative_path()),
+ message: e.to_string(),
+ })?;
+ match self.local_settings.entry((root_id, directory_path.clone())) {
btree_map::Entry::Vacant(v) => {
- v.insert(new_settings);
+ v.insert(SettingsContent {
+ project: new_settings,
+ ..Default::default()
+ });
zed_settings_changed = true;
}
btree_map::Entry::Occupied(mut o) => {
- if o.get() != &new_settings {
- o.insert(new_settings);
+ if &o.get().project != &new_settings {
+ o.insert(SettingsContent {
+ project: new_settings,
+ ..Default::default()
+ });
zed_settings_changed = true;
}
}
@@ -942,17 +833,24 @@ impl SettingsStore {
Ok(())
}
- pub fn set_extension_settings<T: Serialize>(&mut self, content: T, cx: &mut App) -> Result<()> {
- let settings: Value = serde_json::to_value(content)?;
- anyhow::ensure!(settings.is_object(), "settings must be an object");
- self.raw_extension_settings = settings;
+ pub fn set_extension_settings(
+ &mut self,
+ content: ExtensionsSettingsContent,
+ cx: &mut App,
+ ) -> Result<()> {
+ self.extension_settings = Some(SettingsContent {
+ project: ProjectSettingsContent {
+ all_languages: content.all_languages,
+ },
+ ..Default::default()
+ });
self.recompute_values(None, cx)?;
Ok(())
}
/// Add or remove a set of local settings via a JSON string.
pub fn clear_local_settings(&mut self, root_id: WorktreeId, cx: &mut App) -> Result<()> {
- self.raw_local_settings
+ self.local_settings
.retain(|(worktree_id, _), _| worktree_id != &root_id);
self.recompute_values(Some((root_id, "".as_ref())), cx)?;
Ok(())
@@ -962,7 +860,7 @@ impl SettingsStore {
&self,
root_id: WorktreeId,
) -> impl '_ + Iterator<Item = (Arc<Path>, String)> {
- self.raw_local_settings
+ self.local_settings
.range(
(root_id, Path::new("").into())
..(
@@ -991,193 +889,194 @@ impl SettingsStore {
}
pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value {
- let mut generator = schemars::generate::SchemaSettings::draft2019_09()
- .with_transform(DefaultDenyUnknownFields)
- .into_generator();
- let mut combined_schema = json!({
- "type": "object",
- "properties": {}
- });
-
- // Merge together settings schemas, similarly to json schema's "allOf". This merging is
- // recursive, though at time of writing this recursive nature isn't used very much. An
- // example of it is the schema for `jupyter` having contribution from both `EditorSettings`
- // and `JupyterSettings`.
- //
- // This logic could be removed in favor of "allOf", but then there isn't the opportunity to
- // validate and fully control the merge.
- for setting_value in self.setting_values.values() {
- let mut setting_schema = setting_value.json_schema(&mut generator);
-
- if let Some(key) = setting_value.key() {
- if let Some(properties) = combined_schema.get_mut("properties")
- && let Some(properties_obj) = properties.as_object_mut()
- {
- if let Some(target) = properties_obj.get_mut(key) {
- merge_schema(target, setting_schema.to_value());
- } else {
- properties_obj.insert(key.to_string(), setting_schema.to_value());
- }
- }
- } else {
- setting_schema.remove("description");
- setting_schema.remove("additionalProperties");
- merge_schema(&mut combined_schema, setting_schema.to_value());
- }
- }
-
- fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
- let (Some(target_obj), serde_json::Value::Object(source_obj)) =
- (target.as_object_mut(), source)
- else {
- return;
- };
-
- for (source_key, source_value) in source_obj {
- match source_key.as_str() {
- "properties" => {
- let serde_json::Value::Object(source_properties) = source_value else {
- log::error!(
- "bug: expected object for `{}` json schema field, but got: {}",
- source_key,
- source_value
- );
- continue;
- };
- let target_properties =
- target_obj.entry(source_key.clone()).or_insert(json!({}));
- let Some(target_properties) = target_properties.as_object_mut() else {
- log::error!(
- "bug: expected object for `{}` json schema field, but got: {}",
- source_key,
- target_properties
- );
- continue;
- };
- for (key, value) in source_properties {
- if let Some(existing) = target_properties.get_mut(&key) {
- merge_schema(existing, value);
- } else {
- target_properties.insert(key, value);
- }
- }
- }
- "allOf" | "anyOf" | "oneOf" => {
- let serde_json::Value::Array(source_array) = source_value else {
- log::error!(
- "bug: expected array for `{}` json schema field, but got: {}",
- source_key,
- source_value,
- );
- continue;
- };
- let target_array =
- target_obj.entry(source_key.clone()).or_insert(json!([]));
- let Some(target_array) = target_array.as_array_mut() else {
- log::error!(
- "bug: expected array for `{}` json schema field, but got: {}",
- source_key,
- target_array,
- );
- continue;
- };
- target_array.extend(source_array);
- }
- "type"
- | "$ref"
- | "enum"
- | "minimum"
- | "maximum"
- | "pattern"
- | "description"
- | "additionalProperties" => {
- if let Some(old_value) =
- target_obj.insert(source_key.clone(), source_value.clone())
- && old_value != source_value
- {
- log::error!(
- "bug: while merging JSON schemas, \
- mismatch `\"{}\": {}` (before was `{}`)",
- source_key,
- old_value,
- source_value
- );
- }
- }
- _ => {
- log::error!(
- "bug: while merging settings JSON schemas, \
- encountered unexpected `\"{}\": {}`",
- source_key,
- source_value
- );
- }
- }
- }
- }
-
- // add schemas which are determined at runtime
- for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
- (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
- }
-
- // add merged settings schema to the definitions
- const ZED_SETTINGS: &str = "ZedSettings";
- let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema);
-
- // add `ZedSettingsOverride` which is the same as `ZedSettings` except that unknown
- // fields are rejected. This is used for release stage settings and profiles.
- let mut zed_settings_override = zed_settings_ref.clone();
- zed_settings_override.insert("unevaluatedProperties".to_string(), false.into());
- let zed_settings_override_ref = add_new_subschema(
- &mut generator,
- "ZedSettingsOverride",
- zed_settings_override.to_value(),
- );
-
- // Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that
- // unknown fields can be handled by the root schema and `ZedSettingsOverride`.
- let mut definitions = generator.take_definitions(true);
- definitions
- .get_mut(ZED_SETTINGS)
- .unwrap()
- .as_object_mut()
- .unwrap()
- .remove("additionalProperties");
-
- let meta_schema = generator
- .settings()
- .meta_schema
- .as_ref()
- .expect("meta_schema should be present in schemars settings")
- .to_string();
-
- json!({
- "$schema": meta_schema,
- "title": "Zed Settings",
- "unevaluatedProperties": false,
- // ZedSettings + settings overrides for each release stage / OS / profiles
- "allOf": [
- zed_settings_ref,
- {
- "properties": {
- "dev": zed_settings_override_ref,
- "nightly": zed_settings_override_ref,
- "stable": zed_settings_override_ref,
- "preview": zed_settings_override_ref,
- "linux": zed_settings_override_ref,
- "macos": zed_settings_override_ref,
- "windows": zed_settings_override_ref,
- "profiles": {
- "type": "object",
- "description": "Configures any number of settings profiles.",
- "additionalProperties": zed_settings_override_ref
- }
- }
- }
- ],
- "$defs": definitions,
- })
+ todo!()
+ // let mut generator = schemars::generate::SchemaSettings::draft2019_09()
+ // .with_transform(DefaultDenyUnknownFields)
+ // .into_generator();
+ // let mut combined_schema = json!({
+ // "type": "object",
+ // "properties": {}
+ // });
+
+ // // Merge together settings schemas, similarly to json schema's "allOf". This merging is
+ // // recursive, though at time of writing this recursive nature isn't used very much. An
+ // // example of it is the schema for `jupyter` having contribution from both `EditorSettings`
+ // // and `JupyterSettings`.
+ // //
+ // // This logic could be removed in favor of "allOf", but then there isn't the opportunity to
+ // // validate and fully control the merge.
+ // for setting_value in self.setting_values.values() {
+ // let mut setting_schema = setting_value.json_schema(&mut generator);
+
+ // if let Some(key) = setting_value.key() {
+ // if let Some(properties) = combined_schema.get_mut("properties")
+ // && let Some(properties_obj) = properties.as_object_mut()
+ // {
+ // if let Some(target) = properties_obj.get_mut(key) {
+ // merge_schema(target, setting_schema.to_value());
+ // } else {
+ // properties_obj.insert(key.to_string(), setting_schema.to_value());
+ // }
+ // }
+ // } else {
+ // setting_schema.remove("description");
+ // setting_schema.remove("additionalProperties");
+ // merge_schema(&mut combined_schema, setting_schema.to_value());
+ // }
+ // }
+
+ // fn merge_schema(target: &mut serde_json::Value, source: serde_json::Value) {
+ // let (Some(target_obj), serde_json::Value::Object(source_obj)) =
+ // (target.as_object_mut(), source)
+ // else {
+ // return;
+ // };
+
+ // for (source_key, source_value) in source_obj {
+ // match source_key.as_str() {
+ // "properties" => {
+ // let serde_json::Value::Object(source_properties) = source_value else {
+ // log::error!(
+ // "bug: expected object for `{}` json schema field, but got: {}",
+ // source_key,
+ // source_value
+ // );
+ // continue;
+ // };
+ // let target_properties =
+ // target_obj.entry(source_key.clone()).or_insert(json!({}));
+ // let Some(target_properties) = target_properties.as_object_mut() else {
+ // log::error!(
+ // "bug: expected object for `{}` json schema field, but got: {}",
+ // source_key,
+ // target_properties
+ // );
+ // continue;
+ // };
+ // for (key, value) in source_properties {
+ // if let Some(existing) = target_properties.get_mut(&key) {
+ // merge_schema(existing, value);
+ // } else {
+ // target_properties.insert(key, value);
+ // }
+ // }
+ // }
+ // "allOf" | "anyOf" | "oneOf" => {
+ // let serde_json::Value::Array(source_array) = source_value else {
+ // log::error!(
+ // "bug: expected array for `{}` json schema field, but got: {}",
+ // source_key,
+ // source_value,
+ // );
+ // continue;
+ // };
+ // let target_array =
+ // target_obj.entry(source_key.clone()).or_insert(json!([]));
+ // let Some(target_array) = target_array.as_array_mut() else {
+ // log::error!(
+ // "bug: expected array for `{}` json schema field, but got: {}",
+ // source_key,
+ // target_array,
+ // );
+ // continue;
+ // };
+ // target_array.extend(source_array);
+ // }
+ // "type"
+ // | "$ref"
+ // | "enum"
+ // | "minimum"
+ // | "maximum"
+ // | "pattern"
+ // | "description"
+ // | "additionalProperties" => {
+ // if let Some(old_value) =
+ // target_obj.insert(source_key.clone(), source_value.clone())
+ // && old_value != source_value
+ // {
+ // log::error!(
+ // "bug: while merging JSON schemas, \
+ // mismatch `\"{}\": {}` (before was `{}`)",
+ // source_key,
+ // old_value,
+ // source_value
+ // );
+ // }
+ // }
+ // _ => {
+ // log::error!(
+ // "bug: while merging settings JSON schemas, \
+ // encountered unexpected `\"{}\": {}`",
+ // source_key,
+ // source_value
+ // );
+ // }
+ // }
+ // }
+ // }
+
+ // // add schemas which are determined at runtime
+ // for parameterized_json_schema in inventory::iter::<ParameterizedJsonSchema>() {
+ // (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx);
+ // }
+
+ // // add merged settings schema to the definitions
+ // const ZED_SETTINGS: &str = "ZedSettings";
+ // let zed_settings_ref = add_new_subschema(&mut generator, ZED_SETTINGS, combined_schema);
+
+ // // add `ZedSettingsOverride` which is the same as `ZedSettings` except that unknown
+ // // fields are rejected. This is used for release stage settings and profiles.
+ // let mut zed_settings_override = zed_settings_ref.clone();
+ // zed_settings_override.insert("unevaluatedProperties".to_string(), false.into());
+ // let zed_settings_override_ref = add_new_subschema(
+ // &mut generator,
+ // "ZedSettingsOverride",
+ // zed_settings_override.to_value(),
+ // );
+
+ // // Remove `"additionalProperties": false` added by `DefaultDenyUnknownFields` so that
+ // // unknown fields can be handled by the root schema and `ZedSettingsOverride`.
+ // let mut definitions = generator.take_definitions(true);
+ // definitions
+ // .get_mut(ZED_SETTINGS)
+ // .unwrap()
+ // .as_object_mut()
+ // .unwrap()
+ // .remove("additionalProperties");
+
+ // let meta_schema = generator
+ // .settings()
+ // .meta_schema
+ // .as_ref()
+ // .expect("meta_schema should be present in schemars settings")
+ // .to_string();
+
+ // json!({
+ // "$schema": meta_schema,
+ // "title": "Zed Settings",
+ // "unevaluatedProperties": false,
+ // // ZedSettings + settings overrides for each release stage / OS / profiles
+ // "allOf": [
+ // zed_settings_ref,
+ // {
+ // "properties": {
+ // "dev": zed_settings_override_ref,
+ // "nightly": zed_settings_override_ref,
+ // "stable": zed_settings_override_ref,
+ // "preview": zed_settings_override_ref,
+ // "linux": zed_settings_override_ref,
+ // "macos": zed_settings_override_ref,
+ // "windows": zed_settings_override_ref,
+ // "profiles": {
+ // "type": "object",
+ // "description": "Configures any number of settings profiles.",
+ // "additionalProperties": zed_settings_override_ref
+ // }
+ // }
+ // }
+ // ],
+ // "$defs": definitions,
+ // })
}
fn recompute_values(
@@ -1186,90 +1085,45 @@ impl SettingsStore {
cx: &mut App,
) -> std::result::Result<(), InvalidSettingsError> {
// Reload the global and local values for every setting.
- let mut project_settings_stack = Vec::<DeserializedSetting>::new();
+ let mut project_settings_stack = Vec::<&SettingsContent>::new();
let mut paths_stack = Vec::<Option<(WorktreeId, &Path)>>::new();
- for setting_value in self.setting_values.values_mut() {
- let default_settings = setting_value
- .deserialize_setting(&self.raw_default_settings)
- .map_err(|e| InvalidSettingsError::DefaultSettings {
- message: e.to_string(),
- })?;
- let global_settings = self
- .raw_global_settings
- .as_ref()
- .and_then(|setting| setting_value.deserialize_setting(setting).log_err());
-
- let extension_settings = setting_value
- .deserialize_setting(&self.raw_extension_settings)
- .log_err();
-
- let user_settings = match setting_value.deserialize_setting(&self.raw_user_settings) {
- Ok(settings) => Some(settings),
- Err(error) => {
- return Err(InvalidSettingsError::UserSettings {
- message: error.to_string(),
- });
- }
- };
-
- let server_settings = self
- .raw_server_settings
- .as_ref()
- .and_then(|setting| setting_value.deserialize_setting(setting).log_err());
-
- let mut release_channel_settings = None;
- if let Some(release_settings) = &self
- .raw_user_settings
- .get(release_channel::RELEASE_CHANNEL.dev_name())
- && let Some(release_settings) = setting_value
- .deserialize_setting(release_settings)
- .log_err()
- {
- release_channel_settings = Some(release_settings);
- }
+ let mut refinements = Vec::default();
- let mut os_settings = None;
- if let Some(settings) = &self.raw_user_settings.get(env::consts::OS)
- && let Some(settings) = setting_value.deserialize_setting(settings).log_err()
- {
- os_settings = Some(settings);
- }
+ if let Some(extension_settings) = self.extension_settings.as_ref() {
+ refinements.push(extension_settings)
+ }
- let mut profile_settings = None;
- if let Some(active_profile) = cx.try_global::<ActiveSettingsProfileName>()
- && let Some(profiles) = self.raw_user_settings.get("profiles")
- && let Some(profile_json) = profiles.get(&active_profile.0)
- {
- profile_settings = setting_value.deserialize_setting(profile_json).log_err();
+ if let Some(user_settings) = self.user_settings.as_ref() {
+ refinements.push(&user_settings.content);
+ if let Some(release_channel) = user_settings.for_release_channel() {
+ refinements.push(release_channel)
+ }
+ if let Some(os) = user_settings.for_os() {
+ refinements.push(os)
}
+ if let Some(profile) = user_settings.for_profile(cx) {
+ refinements.push(profile)
+ }
+ }
+
+ if let Some(server_settings) = self.server_settings.as_ref() {
+ refinements.push(server_settings)
+ }
+ let default = self.default_settings.as_ref().unwrap();
+ for setting_value in self.setting_values.values_mut() {
// If the global settings file changed, reload the global value for the field.
- if changed_local_path.is_none()
- && let Some(value) = setting_value
- .load_setting(
- SettingsSources {
- default: &default_settings,
- global: global_settings.as_ref(),
- extensions: extension_settings.as_ref(),
- user: user_settings.as_ref(),
- release_channel: release_channel_settings.as_ref(),
- operating_system: os_settings.as_ref(),
- profile: profile_settings.as_ref(),
- server: server_settings.as_ref(),
- project: &[],
- },
- cx,
- )
- .log_err()
- {
+ if changed_local_path.is_none() {
+ let mut value = setting_value.from_file(&default).unwrap();
+ setting_value.refine(&mut value, &refinements);
setting_value.set_global_value(value);
}
// Reload the local values for the setting.
paths_stack.clear();
project_settings_stack.clear();
- for ((root_id, directory_path), local_settings) in &self.raw_local_settings {
+ for ((root_id, directory_path), local_settings) in &self.local_settings {
// Build a stack of all of the local values for that setting.
while let Some(prev_entry) = paths_stack.last() {
if let Some((prev_root_id, prev_path)) = prev_entry