language_settings.rs

  1use crate::{File, Language};
  2use anyhow::Result;
  3use collections::HashMap;
  4use globset::GlobMatcher;
  5use gpui::AppContext;
  6use schemars::{
  7    schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
  8    JsonSchema,
  9};
 10use serde::{Deserialize, Serialize};
 11use std::{num::NonZeroU32, path::Path, sync::Arc};
 12
 13pub fn init(cx: &mut AppContext) {
 14    settings::register::<AllLanguageSettings>(cx);
 15}
 16
 17pub fn language_settings<'a>(
 18    language: Option<&Arc<Language>>,
 19    file: Option<&Arc<dyn File>>,
 20    cx: &'a AppContext,
 21) -> &'a LanguageSettings {
 22    settings::get_local::<AllLanguageSettings>(
 23        file.map(|f| (f.worktree_id(), f.path().as_ref())),
 24        cx,
 25    )
 26    .language(language.map(|l| l.name()).as_deref())
 27}
 28
 29pub fn all_language_settings<'a>(
 30    file: Option<&Arc<dyn File>>,
 31    cx: &'a AppContext,
 32) -> &'a AllLanguageSettings {
 33    settings::get_local::<AllLanguageSettings>(
 34        file.map(|f| (f.worktree_id(), f.path().as_ref())),
 35        cx,
 36    )
 37}
 38
 39#[derive(Debug, Clone)]
 40pub struct AllLanguageSettings {
 41    pub copilot: CopilotSettings,
 42    defaults: LanguageSettings,
 43    languages: HashMap<Arc<str>, LanguageSettings>,
 44}
 45
 46#[derive(Debug, Clone, Deserialize)]
 47pub struct LanguageSettings {
 48    pub tab_size: NonZeroU32,
 49    pub hard_tabs: bool,
 50    pub soft_wrap: SoftWrap,
 51    pub preferred_line_length: u32,
 52    pub format_on_save: FormatOnSave,
 53    pub remove_trailing_whitespace_on_save: bool,
 54    pub ensure_final_newline_on_save: bool,
 55    pub formatter: Formatter,
 56    pub enable_language_server: bool,
 57    pub show_copilot_suggestions: bool,
 58    pub show_whitespaces: ShowWhitespaceSetting,
 59}
 60
 61#[derive(Clone, Debug, Default)]
 62pub struct CopilotSettings {
 63    pub feature_enabled: bool,
 64    pub disabled_globs: Vec<GlobMatcher>,
 65}
 66
 67#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
 68pub struct AllLanguageSettingsContent {
 69    #[serde(default)]
 70    pub features: Option<FeaturesContent>,
 71    #[serde(default)]
 72    pub copilot: Option<CopilotSettingsContent>,
 73    #[serde(flatten)]
 74    pub defaults: LanguageSettingsContent,
 75    #[serde(default, alias = "language_overrides")]
 76    pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
 77}
 78
 79#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
 80pub struct LanguageSettingsContent {
 81    #[serde(default)]
 82    pub tab_size: Option<NonZeroU32>,
 83    #[serde(default)]
 84    pub hard_tabs: Option<bool>,
 85    #[serde(default)]
 86    pub soft_wrap: Option<SoftWrap>,
 87    #[serde(default)]
 88    pub preferred_line_length: Option<u32>,
 89    #[serde(default)]
 90    pub format_on_save: Option<FormatOnSave>,
 91    #[serde(default)]
 92    pub remove_trailing_whitespace_on_save: Option<bool>,
 93    #[serde(default)]
 94    pub ensure_final_newline_on_save: Option<bool>,
 95    #[serde(default)]
 96    pub formatter: Option<Formatter>,
 97    #[serde(default)]
 98    pub enable_language_server: Option<bool>,
 99    #[serde(default)]
100    pub show_copilot_suggestions: Option<bool>,
101    #[serde(default)]
102    pub show_whitespaces: Option<ShowWhitespaceSetting>,
103}
104
105#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
106pub struct CopilotSettingsContent {
107    #[serde(default)]
108    pub disabled_globs: Option<Vec<String>>,
109}
110
111#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
112#[serde(rename_all = "snake_case")]
113pub struct FeaturesContent {
114    pub copilot: Option<bool>,
115}
116
117#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
118#[serde(rename_all = "snake_case")]
119pub enum SoftWrap {
120    None,
121    EditorWidth,
122    PreferredLineLength,
123}
124
125#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
126#[serde(rename_all = "snake_case")]
127pub enum FormatOnSave {
128    On,
129    Off,
130    LanguageServer,
131    External {
132        command: Arc<str>,
133        arguments: Arc<[String]>,
134    },
135}
136
137#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
138#[serde(rename_all = "snake_case")]
139pub enum ShowWhitespaceSetting {
140    Selection,
141    None,
142    All,
143}
144
145#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
146#[serde(rename_all = "snake_case")]
147pub enum Formatter {
148    LanguageServer,
149    External {
150        command: Arc<str>,
151        arguments: Arc<[String]>,
152    },
153}
154
155impl AllLanguageSettings {
156    pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
157        if let Some(name) = language_name {
158            if let Some(overrides) = self.languages.get(name) {
159                return overrides;
160            }
161        }
162        &self.defaults
163    }
164
165    pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
166        !self
167            .copilot
168            .disabled_globs
169            .iter()
170            .any(|glob| glob.is_match(path))
171    }
172
173    pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
174        if !self.copilot.feature_enabled {
175            return false;
176        }
177
178        if let Some(path) = path {
179            if !self.copilot_enabled_for_path(path) {
180                return false;
181            }
182        }
183
184        self.language(language.map(|l| l.name()).as_deref())
185            .show_copilot_suggestions
186    }
187}
188
189impl settings::Setting for AllLanguageSettings {
190    const KEY: Option<&'static str> = None;
191
192    type FileContent = AllLanguageSettingsContent;
193
194    fn load(
195        default_value: &Self::FileContent,
196        user_settings: &[&Self::FileContent],
197        _: &AppContext,
198    ) -> Result<Self> {
199        // A default is provided for all settings.
200        let mut defaults: LanguageSettings =
201            serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
202
203        let mut languages = HashMap::default();
204        for (language_name, settings) in &default_value.languages {
205            let mut language_settings = defaults.clone();
206            merge_settings(&mut language_settings, &settings);
207            languages.insert(language_name.clone(), language_settings);
208        }
209
210        let mut copilot_enabled = default_value
211            .features
212            .as_ref()
213            .and_then(|f| f.copilot)
214            .ok_or_else(Self::missing_default)?;
215        let mut copilot_globs = default_value
216            .copilot
217            .as_ref()
218            .and_then(|c| c.disabled_globs.as_ref())
219            .ok_or_else(Self::missing_default)?;
220
221        for user_settings in user_settings {
222            if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
223                copilot_enabled = copilot;
224            }
225            if let Some(globs) = user_settings
226                .copilot
227                .as_ref()
228                .and_then(|f| f.disabled_globs.as_ref())
229            {
230                copilot_globs = globs;
231            }
232
233            // A user's global settings override the default global settings and
234            // all default language-specific settings.
235            merge_settings(&mut defaults, &user_settings.defaults);
236            for language_settings in languages.values_mut() {
237                merge_settings(language_settings, &user_settings.defaults);
238            }
239
240            // A user's language-specific settings override default language-specific settings.
241            for (language_name, user_language_settings) in &user_settings.languages {
242                merge_settings(
243                    languages
244                        .entry(language_name.clone())
245                        .or_insert_with(|| defaults.clone()),
246                    &user_language_settings,
247                );
248            }
249        }
250
251        Ok(Self {
252            copilot: CopilotSettings {
253                feature_enabled: copilot_enabled,
254                disabled_globs: copilot_globs
255                    .iter()
256                    .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
257                    .collect(),
258            },
259            defaults,
260            languages,
261        })
262    }
263
264    fn json_schema(
265        generator: &mut schemars::gen::SchemaGenerator,
266        params: &settings::SettingsJsonSchemaParams,
267        _: &AppContext,
268    ) -> schemars::schema::RootSchema {
269        let mut root_schema = generator.root_schema_for::<Self::FileContent>();
270
271        // Create a schema for a 'languages overrides' object, associating editor
272        // settings with specific langauges.
273        assert!(root_schema
274            .definitions
275            .contains_key("LanguageSettingsContent"));
276
277        let languages_object_schema = SchemaObject {
278            instance_type: Some(InstanceType::Object.into()),
279            object: Some(Box::new(ObjectValidation {
280                properties: params
281                    .language_names
282                    .iter()
283                    .map(|name| {
284                        (
285                            name.clone(),
286                            Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
287                        )
288                    })
289                    .collect(),
290                ..Default::default()
291            })),
292            ..Default::default()
293        };
294
295        root_schema
296            .definitions
297            .extend([("Languages".into(), languages_object_schema.into())]);
298
299        root_schema
300            .schema
301            .object
302            .as_mut()
303            .unwrap()
304            .properties
305            .extend([
306                (
307                    "languages".to_owned(),
308                    Schema::new_ref("#/definitions/Languages".into()),
309                ),
310                // For backward compatibility
311                (
312                    "language_overrides".to_owned(),
313                    Schema::new_ref("#/definitions/Languages".into()),
314                ),
315            ]);
316
317        root_schema
318    }
319}
320
321fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
322    merge(&mut settings.tab_size, src.tab_size);
323    merge(&mut settings.hard_tabs, src.hard_tabs);
324    merge(&mut settings.soft_wrap, src.soft_wrap);
325    merge(
326        &mut settings.preferred_line_length,
327        src.preferred_line_length,
328    );
329    merge(&mut settings.formatter, src.formatter.clone());
330    merge(&mut settings.format_on_save, src.format_on_save.clone());
331    merge(
332        &mut settings.remove_trailing_whitespace_on_save,
333        src.remove_trailing_whitespace_on_save,
334    );
335    merge(
336        &mut settings.ensure_final_newline_on_save,
337        src.ensure_final_newline_on_save,
338    );
339    merge(
340        &mut settings.enable_language_server,
341        src.enable_language_server,
342    );
343    merge(
344        &mut settings.show_copilot_suggestions,
345        src.show_copilot_suggestions,
346    );
347    merge(&mut settings.show_whitespaces, src.show_whitespaces);
348
349    fn merge<T>(target: &mut T, value: Option<T>) {
350        if let Some(value) = value {
351            *target = value;
352        }
353    }
354}