language_settings.rs

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