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