use anyhow::Result; use collections::HashMap; use gpui::AppContext; use schemars::{ schema::{InstanceType, ObjectValidation, Schema, SchemaObject}, JsonSchema, }; use serde::{Deserialize, Serialize}; use std::{num::NonZeroU32, path::Path, sync::Arc}; pub fn init(cx: &mut AppContext) { settings::register_setting::(cx); } pub fn language_settings<'a>(language: Option<&str>, cx: &'a AppContext) -> &'a LanguageSettings { settings::get::(cx).language(language) } pub fn all_language_settings<'a>(cx: &'a AppContext) -> &'a AllLanguageSettings { settings::get::(cx) } #[derive(Debug, Clone)] pub struct AllLanguageSettings { pub copilot: CopilotSettings, defaults: LanguageSettings, languages: HashMap, LanguageSettings>, } #[derive(Debug, Clone, Deserialize)] pub struct LanguageSettings { pub tab_size: NonZeroU32, pub hard_tabs: bool, pub soft_wrap: SoftWrap, pub preferred_line_length: u32, pub format_on_save: FormatOnSave, pub remove_trailing_whitespace_on_save: bool, pub ensure_final_newline_on_save: bool, pub formatter: Formatter, pub enable_language_server: bool, pub show_copilot_suggestions: bool, pub show_whitespaces: ShowWhitespaceSetting, } #[derive(Clone, Debug, Default)] pub struct CopilotSettings { pub feature_enabled: bool, pub disabled_globs: Vec, } #[derive(Clone, Serialize, Deserialize, JsonSchema)] pub struct AllLanguageSettingsContent { #[serde(default)] pub features: Option, #[serde(default)] pub copilot: Option, #[serde(flatten)] pub defaults: LanguageSettingsContent, #[serde(default, alias = "language_overrides")] pub languages: HashMap, LanguageSettingsContent>, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] pub struct LanguageSettingsContent { #[serde(default)] pub tab_size: Option, #[serde(default)] pub hard_tabs: Option, #[serde(default)] pub soft_wrap: Option, #[serde(default)] pub preferred_line_length: Option, #[serde(default)] pub format_on_save: Option, #[serde(default)] pub remove_trailing_whitespace_on_save: Option, #[serde(default)] pub ensure_final_newline_on_save: Option, #[serde(default)] pub formatter: Option, #[serde(default)] pub enable_language_server: Option, #[serde(default)] pub show_copilot_suggestions: Option, #[serde(default)] pub show_whitespaces: Option, } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct CopilotSettingsContent { #[serde(default)] pub disabled_globs: Option>, } #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] #[serde(rename_all = "snake_case")] pub struct FeaturesContent { pub copilot: Option, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum SoftWrap { None, EditorWidth, PreferredLineLength, } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum FormatOnSave { On, Off, LanguageServer, External { command: Arc, arguments: Arc<[String]>, }, } #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum ShowWhitespaceSetting { Selection, None, All, } #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum Formatter { LanguageServer, External { command: Arc, arguments: Arc<[String]>, }, } impl AllLanguageSettings { pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings { if let Some(name) = language_name { if let Some(overrides) = self.languages.get(name) { return overrides; } } &self.defaults } pub fn copilot_enabled_for_path(&self, path: &Path) -> bool { !self .copilot .disabled_globs .iter() .any(|glob| glob.matches_path(path)) } pub fn copilot_enabled(&self, language_name: Option<&str>, path: Option<&Path>) -> bool { if !self.copilot.feature_enabled { return false; } if let Some(path) = path { if !self.copilot_enabled_for_path(path) { return false; } } self.language(language_name).show_copilot_suggestions } } impl settings::Setting for AllLanguageSettings { const KEY: Option<&'static str> = None; type FileContent = AllLanguageSettingsContent; fn load( default_value: &Self::FileContent, user_settings: &[&Self::FileContent], _: &AppContext, ) -> Result { // A default is provided for all settings. let mut defaults: LanguageSettings = serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?; let mut languages = HashMap::default(); for (language_name, settings) in &default_value.languages { let mut language_settings = defaults.clone(); merge_settings(&mut language_settings, &settings); languages.insert(language_name.clone(), language_settings); } let mut copilot_enabled = default_value .features .as_ref() .and_then(|f| f.copilot) .ok_or_else(Self::missing_default)?; let mut copilot_globs = default_value .copilot .as_ref() .and_then(|c| c.disabled_globs.as_ref()) .ok_or_else(Self::missing_default)?; for user_settings in user_settings { if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) { copilot_enabled = copilot; } if let Some(globs) = user_settings .copilot .as_ref() .and_then(|f| f.disabled_globs.as_ref()) { copilot_globs = globs; } // A user's global settings override the default global settings and // all default language-specific settings. merge_settings(&mut defaults, &user_settings.defaults); for language_settings in languages.values_mut() { merge_settings(language_settings, &user_settings.defaults); } // A user's language-specific settings override default language-specific settings. for (language_name, user_language_settings) in &user_settings.languages { merge_settings( languages .entry(language_name.clone()) .or_insert_with(|| defaults.clone()), &user_language_settings, ); } } Ok(Self { copilot: CopilotSettings { feature_enabled: copilot_enabled, disabled_globs: copilot_globs .iter() .filter_map(|pattern| glob::Pattern::new(pattern).ok()) .collect(), }, defaults, languages, }) } fn json_schema( generator: &mut schemars::gen::SchemaGenerator, params: &settings::SettingsJsonSchemaParams, _: &AppContext, ) -> schemars::schema::RootSchema { let mut root_schema = generator.root_schema_for::(); // Create a schema for a 'languages overrides' object, associating editor // settings with specific langauges. assert!(root_schema .definitions .contains_key("LanguageSettingsContent")); let languages_object_schema = SchemaObject { instance_type: Some(InstanceType::Object.into()), object: Some(Box::new(ObjectValidation { properties: params .language_names .iter() .map(|name| { ( name.clone(), Schema::new_ref("#/definitions/LanguageSettingsContent".into()), ) }) .collect(), ..Default::default() })), ..Default::default() }; root_schema .definitions .extend([("Languages".into(), languages_object_schema.into())]); root_schema .schema .object .as_mut() .unwrap() .properties .extend([ ( "languages".to_owned(), Schema::new_ref("#/definitions/Languages".into()), ), // For backward compatibility ( "language_overrides".to_owned(), Schema::new_ref("#/definitions/Languages".into()), ), ]); root_schema } } fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { merge(&mut settings.tab_size, src.tab_size); merge(&mut settings.hard_tabs, src.hard_tabs); merge(&mut settings.soft_wrap, src.soft_wrap); merge( &mut settings.preferred_line_length, src.preferred_line_length, ); merge(&mut settings.formatter, src.formatter.clone()); merge(&mut settings.format_on_save, src.format_on_save.clone()); merge( &mut settings.remove_trailing_whitespace_on_save, src.remove_trailing_whitespace_on_save, ); merge( &mut settings.ensure_final_newline_on_save, src.ensure_final_newline_on_save, ); merge( &mut settings.enable_language_server, src.enable_language_server, ); merge( &mut settings.show_copilot_suggestions, src.show_copilot_suggestions, ); merge(&mut settings.show_whitespaces, src.show_whitespaces); fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; } } }