1use crate::{File, Language};
2use anyhow::Result;
3use collections::{HashMap, HashSet};
4use globset::GlobMatcher;
5use gpui::AppContext;
6use schemars::{
7 schema::{InstanceType, ObjectValidation, Schema, SchemaObject},
8 JsonSchema,
9};
10use serde::{Deserialize, Serialize};
11use settings::Settings;
12use std::{num::NonZeroU32, path::Path, sync::Arc};
13
14pub fn init(cx: &mut AppContext) {
15 AllLanguageSettings::register(cx);
16}
17
18pub fn language_settings<'a>(
19 language: Option<&Arc<Language>>,
20 file: Option<&Arc<dyn File>>,
21 cx: &'a AppContext,
22) -> &'a LanguageSettings {
23 let language_name = language.map(|l| l.name());
24 all_language_settings(file, cx).language(language_name.as_deref())
25}
26
27pub fn all_language_settings<'a>(
28 file: Option<&Arc<dyn File>>,
29 cx: &'a AppContext,
30) -> &'a AllLanguageSettings {
31 let location = file.map(|f| (f.worktree_id(), f.path().as_ref()));
32 AllLanguageSettings::get(location, cx)
33}
34
35#[derive(Debug, Clone)]
36pub struct AllLanguageSettings {
37 pub copilot: CopilotSettings,
38 defaults: LanguageSettings,
39 languages: HashMap<Arc<str>, LanguageSettings>,
40}
41
42#[derive(Debug, Clone, Deserialize)]
43pub struct LanguageSettings {
44 pub tab_size: NonZeroU32,
45 pub hard_tabs: bool,
46 pub soft_wrap: SoftWrap,
47 pub preferred_line_length: u32,
48 pub show_wrap_guides: bool,
49 pub wrap_guides: Vec<usize>,
50 pub format_on_save: FormatOnSave,
51 pub remove_trailing_whitespace_on_save: bool,
52 pub ensure_final_newline_on_save: bool,
53 pub formatter: Formatter,
54 pub prettier: HashMap<String, serde_json::Value>,
55 pub enable_language_server: bool,
56 pub show_copilot_suggestions: bool,
57 pub show_whitespaces: ShowWhitespaceSetting,
58 pub extend_comment_on_newline: bool,
59 pub inlay_hints: InlayHintSettings,
60}
61
62#[derive(Clone, Debug, Default)]
63pub struct CopilotSettings {
64 pub feature_enabled: bool,
65 pub disabled_globs: Vec<GlobMatcher>,
66}
67
68#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
69pub struct AllLanguageSettingsContent {
70 #[serde(default)]
71 pub features: Option<FeaturesContent>,
72 #[serde(default)]
73 pub copilot: Option<CopilotSettingsContent>,
74 #[serde(flatten)]
75 pub defaults: LanguageSettingsContent,
76 #[serde(default, alias = "language_overrides")]
77 pub languages: HashMap<Arc<str>, LanguageSettingsContent>,
78}
79
80#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
81pub struct LanguageSettingsContent {
82 /// How many columns a tab should occupy.
83 ///
84 /// Default: 4
85 #[serde(default)]
86 pub tab_size: Option<NonZeroU32>,
87 /// Whether to indent lines using tab characters, as opposed to multiple
88 /// spaces.
89 ///
90 /// Default: false
91 #[serde(default)]
92 pub hard_tabs: Option<bool>,
93 /// How to soft-wrap long lines of text.
94 ///
95 /// Default: none
96 #[serde(default)]
97 pub soft_wrap: Option<SoftWrap>,
98 /// The column at which to soft-wrap lines, for buffers where soft-wrap
99 /// is enabled.
100 ///
101 /// Default: 80
102 #[serde(default)]
103 pub preferred_line_length: Option<u32>,
104 /// Whether to show wrap guides in the editor. Setting this to true will
105 /// show a guide at the 'preferred_line_length' value if softwrap is set to
106 /// 'preferred_line_length', and will show any additional guides as specified
107 /// by the 'wrap_guides' setting.
108 ///
109 /// Default: true
110 #[serde(default)]
111 pub show_wrap_guides: Option<bool>,
112 /// Character counts at which to show wrap guides in the editor.
113 ///
114 /// Default: []
115 #[serde(default)]
116 pub wrap_guides: Option<Vec<usize>>,
117 /// Whether or not to perform a buffer format before saving.
118 ///
119 /// Default: on
120 #[serde(default)]
121 pub format_on_save: Option<FormatOnSave>,
122 /// Whether or not to remove any trailing whitespace from lines of a buffer
123 /// before saving it.
124 ///
125 /// Default: true
126 #[serde(default)]
127 pub remove_trailing_whitespace_on_save: Option<bool>,
128 /// Whether or not to ensure there's a single newline at the end of a buffer
129 /// when saving it.
130 ///
131 /// Default: true
132 #[serde(default)]
133 pub ensure_final_newline_on_save: Option<bool>,
134 /// How to perform a buffer format.
135 ///
136 /// Default: auto
137 #[serde(default)]
138 pub formatter: Option<Formatter>,
139 /// Zed's Prettier integration settings.
140 /// If Prettier is enabled, Zed will use this its Prettier instance for any applicable file, if
141 /// project has no other Prettier installed.
142 ///
143 /// Default: {}
144 #[serde(default)]
145 pub prettier: Option<HashMap<String, serde_json::Value>>,
146 /// Whether to use language servers to provide code intelligence.
147 ///
148 /// Default: true
149 #[serde(default)]
150 pub enable_language_server: Option<bool>,
151 /// Controls whether copilot provides suggestion immediately (true)
152 /// or waits for a `copilot::Toggle` (false).
153 ///
154 /// Default: true
155 #[serde(default)]
156 pub show_copilot_suggestions: Option<bool>,
157 /// Whether to show tabs and spaces in the editor.
158 #[serde(default)]
159 pub show_whitespaces: Option<ShowWhitespaceSetting>,
160 /// Whether to start a new line with a comment when a previous line is a comment as well.
161 ///
162 /// Default: true
163 #[serde(default)]
164 pub extend_comment_on_newline: Option<bool>,
165 /// Inlay hint related settings.
166 #[serde(default)]
167 pub inlay_hints: Option<InlayHintSettings>,
168}
169
170#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
171pub struct CopilotSettingsContent {
172 #[serde(default)]
173 pub disabled_globs: Option<Vec<String>>,
174}
175
176#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
177#[serde(rename_all = "snake_case")]
178pub struct FeaturesContent {
179 pub copilot: Option<bool>,
180}
181
182#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
183#[serde(rename_all = "snake_case")]
184pub enum SoftWrap {
185 /// Do not soft wrap.
186 None,
187 /// Soft wrap lines that overflow the editor
188 EditorWidth,
189 /// Soft wrap lines at the preferred line length
190 PreferredLineLength,
191}
192
193#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
194#[serde(rename_all = "snake_case")]
195pub enum FormatOnSave {
196 On,
197 Off,
198 LanguageServer,
199 External {
200 command: Arc<str>,
201 arguments: Arc<[String]>,
202 },
203}
204
205#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
206#[serde(rename_all = "snake_case")]
207pub enum ShowWhitespaceSetting {
208 /// Draw tabs and spaces only for the selected text.
209 Selection,
210 /// Do not draw any tabs or spaces
211 None,
212 /// Draw all invisible symbols
213 All,
214}
215
216#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
217#[serde(rename_all = "snake_case")]
218pub enum Formatter {
219 /// Format files using Zed's Prettier integration (if applicable),
220 /// or falling back to formatting via language server.
221 #[default]
222 Auto,
223 /// Format code using the current language server.
224 LanguageServer,
225 /// Format code using Zed's Prettier integration.
226 Prettier,
227 /// Format code using an external command.
228 External {
229 command: Arc<str>,
230 arguments: Arc<[String]>,
231 },
232}
233
234#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
235pub struct InlayHintSettings {
236 /// Global switch to toggle hints on and off.
237 ///
238 /// Default: false
239 #[serde(default)]
240 pub enabled: bool,
241 #[serde(default = "default_true")]
242 pub show_type_hints: bool,
243 #[serde(default = "default_true")]
244 pub show_parameter_hints: bool,
245 #[serde(default = "default_true")]
246 pub show_other_hints: bool,
247}
248
249fn default_true() -> bool {
250 true
251}
252
253impl InlayHintSettings {
254 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
255 let mut kinds = HashSet::default();
256 if self.show_type_hints {
257 kinds.insert(Some(InlayHintKind::Type));
258 }
259 if self.show_parameter_hints {
260 kinds.insert(Some(InlayHintKind::Parameter));
261 }
262 if self.show_other_hints {
263 kinds.insert(None);
264 }
265 kinds
266 }
267}
268
269impl AllLanguageSettings {
270 pub fn language<'a>(&'a self, language_name: Option<&str>) -> &'a LanguageSettings {
271 if let Some(name) = language_name {
272 if let Some(overrides) = self.languages.get(name) {
273 return overrides;
274 }
275 }
276 &self.defaults
277 }
278
279 pub fn copilot_enabled_for_path(&self, path: &Path) -> bool {
280 !self
281 .copilot
282 .disabled_globs
283 .iter()
284 .any(|glob| glob.is_match(path))
285 }
286
287 pub fn copilot_enabled(&self, language: Option<&Arc<Language>>, path: Option<&Path>) -> bool {
288 if !self.copilot.feature_enabled {
289 return false;
290 }
291
292 if let Some(path) = path {
293 if !self.copilot_enabled_for_path(path) {
294 return false;
295 }
296 }
297
298 self.language(language.map(|l| l.name()).as_deref())
299 .show_copilot_suggestions
300 }
301}
302
303#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
304pub enum InlayHintKind {
305 Type,
306 Parameter,
307}
308
309impl InlayHintKind {
310 pub fn from_name(name: &str) -> Option<Self> {
311 match name {
312 "type" => Some(InlayHintKind::Type),
313 "parameter" => Some(InlayHintKind::Parameter),
314 _ => None,
315 }
316 }
317
318 pub fn name(&self) -> &'static str {
319 match self {
320 InlayHintKind::Type => "type",
321 InlayHintKind::Parameter => "parameter",
322 }
323 }
324}
325
326impl settings::Settings for AllLanguageSettings {
327 const KEY: Option<&'static str> = None;
328
329 type FileContent = AllLanguageSettingsContent;
330
331 fn load(
332 default_value: &Self::FileContent,
333 user_settings: &[&Self::FileContent],
334 _: &mut AppContext,
335 ) -> Result<Self> {
336 // A default is provided for all settings.
337 let mut defaults: LanguageSettings =
338 serde_json::from_value(serde_json::to_value(&default_value.defaults)?)?;
339
340 let mut languages = HashMap::default();
341 for (language_name, settings) in &default_value.languages {
342 let mut language_settings = defaults.clone();
343 merge_settings(&mut language_settings, &settings);
344 languages.insert(language_name.clone(), language_settings);
345 }
346
347 let mut copilot_enabled = default_value
348 .features
349 .as_ref()
350 .and_then(|f| f.copilot)
351 .ok_or_else(Self::missing_default)?;
352 let mut copilot_globs = default_value
353 .copilot
354 .as_ref()
355 .and_then(|c| c.disabled_globs.as_ref())
356 .ok_or_else(Self::missing_default)?;
357
358 for user_settings in user_settings {
359 if let Some(copilot) = user_settings.features.as_ref().and_then(|f| f.copilot) {
360 copilot_enabled = copilot;
361 }
362 if let Some(globs) = user_settings
363 .copilot
364 .as_ref()
365 .and_then(|f| f.disabled_globs.as_ref())
366 {
367 copilot_globs = globs;
368 }
369
370 // A user's global settings override the default global settings and
371 // all default language-specific settings.
372 merge_settings(&mut defaults, &user_settings.defaults);
373 for language_settings in languages.values_mut() {
374 merge_settings(language_settings, &user_settings.defaults);
375 }
376
377 // A user's language-specific settings override default language-specific settings.
378 for (language_name, user_language_settings) in &user_settings.languages {
379 merge_settings(
380 languages
381 .entry(language_name.clone())
382 .or_insert_with(|| defaults.clone()),
383 &user_language_settings,
384 );
385 }
386 }
387
388 Ok(Self {
389 copilot: CopilotSettings {
390 feature_enabled: copilot_enabled,
391 disabled_globs: copilot_globs
392 .iter()
393 .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher()))
394 .collect(),
395 },
396 defaults,
397 languages,
398 })
399 }
400
401 fn json_schema(
402 generator: &mut schemars::gen::SchemaGenerator,
403 params: &settings::SettingsJsonSchemaParams,
404 _: &AppContext,
405 ) -> schemars::schema::RootSchema {
406 let mut root_schema = generator.root_schema_for::<Self::FileContent>();
407
408 // Create a schema for a 'languages overrides' object, associating editor
409 // settings with specific languages.
410 assert!(root_schema
411 .definitions
412 .contains_key("LanguageSettingsContent"));
413
414 let languages_object_schema = SchemaObject {
415 instance_type: Some(InstanceType::Object.into()),
416 object: Some(Box::new(ObjectValidation {
417 properties: params
418 .language_names
419 .iter()
420 .map(|name| {
421 (
422 name.clone(),
423 Schema::new_ref("#/definitions/LanguageSettingsContent".into()),
424 )
425 })
426 .collect(),
427 ..Default::default()
428 })),
429 ..Default::default()
430 };
431
432 root_schema
433 .definitions
434 .extend([("Languages".into(), languages_object_schema.into())]);
435
436 root_schema
437 .schema
438 .object
439 .as_mut()
440 .unwrap()
441 .properties
442 .extend([
443 (
444 "languages".to_owned(),
445 Schema::new_ref("#/definitions/Languages".into()),
446 ),
447 // For backward compatibility
448 (
449 "language_overrides".to_owned(),
450 Schema::new_ref("#/definitions/Languages".into()),
451 ),
452 ]);
453
454 root_schema
455 }
456}
457
458fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) {
459 merge(&mut settings.tab_size, src.tab_size);
460 merge(&mut settings.hard_tabs, src.hard_tabs);
461 merge(&mut settings.soft_wrap, src.soft_wrap);
462 merge(&mut settings.show_wrap_guides, src.show_wrap_guides);
463 merge(&mut settings.wrap_guides, src.wrap_guides.clone());
464
465 merge(
466 &mut settings.preferred_line_length,
467 src.preferred_line_length,
468 );
469 merge(&mut settings.formatter, src.formatter.clone());
470 merge(&mut settings.prettier, src.prettier.clone());
471 merge(&mut settings.format_on_save, src.format_on_save.clone());
472 merge(
473 &mut settings.remove_trailing_whitespace_on_save,
474 src.remove_trailing_whitespace_on_save,
475 );
476 merge(
477 &mut settings.ensure_final_newline_on_save,
478 src.ensure_final_newline_on_save,
479 );
480 merge(
481 &mut settings.enable_language_server,
482 src.enable_language_server,
483 );
484 merge(
485 &mut settings.show_copilot_suggestions,
486 src.show_copilot_suggestions,
487 );
488 merge(&mut settings.show_whitespaces, src.show_whitespaces);
489 merge(
490 &mut settings.extend_comment_on_newline,
491 src.extend_comment_on_newline,
492 );
493 merge(&mut settings.inlay_hints, src.inlay_hints);
494 fn merge<T>(target: &mut T, value: Option<T>) {
495 if let Some(value) = value {
496 *target = value;
497 }
498 }
499}