1//! Provides `language`-related settings.
2
3use crate::{
4 Buffer, BufferSnapshot, File, Language, LanguageName, LanguageServerName, ModelineSettings,
5};
6use collections::{FxHashMap, HashMap, HashSet};
7use ec4rs::{
8 Properties as EditorconfigProperties,
9 property::{FinalNewline, IndentSize, IndentStyle, MaxLineLen, TabWidth, TrimTrailingWs},
10};
11use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder};
12use gpui::{App, Modifiers, SharedString};
13use itertools::{Either, Itertools};
14use settings::IntoGpui;
15
16pub use settings::{
17 CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave,
18 Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode,
19 RewrapBehavior, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode,
20};
21use settings::{RegisterSetting, Settings, SettingsLocation, SettingsStore, merge_from::MergeFrom};
22use shellexpand;
23use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc};
24use text::ToOffset;
25
26/// Returns the settings for all languages from the provided file.
27pub fn all_language_settings<'a>(
28 file: Option<&'a Arc<dyn File>>,
29 cx: &'a App,
30) -> &'a AllLanguageSettings {
31 let location = file.map(|f| SettingsLocation {
32 worktree_id: f.worktree_id(cx),
33 path: f.path().as_ref(),
34 });
35 AllLanguageSettings::get(location, cx)
36}
37
38/// The settings for all languages.
39#[derive(Debug, Clone, RegisterSetting)]
40pub struct AllLanguageSettings {
41 /// The edit prediction settings.
42 pub edit_predictions: EditPredictionSettings,
43 pub defaults: LanguageSettings,
44 languages: HashMap<LanguageName, LanguageSettings>,
45 pub file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)>,
46}
47
48#[derive(Debug, Clone, PartialEq)]
49pub struct WhitespaceMap {
50 pub space: SharedString,
51 pub tab: SharedString,
52}
53
54/// The settings for a particular language.
55#[derive(Debug, Clone, PartialEq)]
56pub struct LanguageSettings {
57 /// How many columns a tab should occupy.
58 pub tab_size: NonZeroU32,
59 /// Whether to indent lines using tab characters, as opposed to multiple
60 /// spaces.
61 pub hard_tabs: bool,
62 /// How to soft-wrap long lines of text.
63 pub soft_wrap: settings::SoftWrap,
64 /// The column at which to soft-wrap lines, for buffers where soft-wrap
65 /// is enabled.
66 pub preferred_line_length: u32,
67 /// Whether to show wrap guides (vertical rulers) in the editor.
68 /// Setting this to true will show a guide at the 'preferred_line_length' value
69 /// if softwrap is set to 'preferred_line_length', and will show any
70 /// additional guides as specified by the 'wrap_guides' setting.
71 pub show_wrap_guides: bool,
72 /// Character counts at which to show wrap guides (vertical rulers) in the editor.
73 pub wrap_guides: Vec<usize>,
74 /// Indent guide related settings.
75 pub indent_guides: IndentGuideSettings,
76 /// Whether or not to perform a buffer format before saving.
77 pub format_on_save: FormatOnSave,
78 /// Whether or not to remove any trailing whitespace from lines of a buffer
79 /// before saving it.
80 pub remove_trailing_whitespace_on_save: bool,
81 /// Whether or not to ensure there's a single newline at the end of a buffer
82 /// when saving it.
83 pub ensure_final_newline_on_save: bool,
84 /// How to perform a buffer format.
85 pub formatter: settings::FormatterList,
86 /// Zed's Prettier integration settings.
87 pub prettier: PrettierSettings,
88 /// Whether to automatically close JSX tags.
89 pub jsx_tag_auto_close: bool,
90 /// Whether to use language servers to provide code intelligence.
91 pub enable_language_server: bool,
92 /// The list of language servers to use (or disable) for this language.
93 ///
94 /// This array should consist of language server IDs, as well as the following
95 /// special tokens:
96 /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
97 /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
98 pub language_servers: Vec<String>,
99 /// Controls where the `editor::Rewrap` action is allowed for this language.
100 ///
101 /// Note: This setting has no effect in Vim mode, as rewrap is already
102 /// allowed everywhere.
103 pub allow_rewrap: RewrapBehavior,
104 /// Controls whether edit predictions are shown immediately (true)
105 /// or manually by triggering `editor::ShowEditPrediction` (false).
106 pub show_edit_predictions: bool,
107 /// Controls whether edit predictions are shown in the given language
108 /// scopes.
109 pub edit_predictions_disabled_in: Vec<String>,
110 /// Whether to show tabs and spaces in the editor.
111 pub show_whitespaces: settings::ShowWhitespaceSetting,
112 /// Visible characters used to render whitespace when show_whitespaces is enabled.
113 pub whitespace_map: WhitespaceMap,
114 /// Whether to start a new line with a comment when a previous line is a comment as well.
115 pub extend_comment_on_newline: bool,
116 /// Whether to continue markdown lists when pressing enter.
117 pub extend_list_on_newline: bool,
118 /// Whether to indent list items when pressing tab after a list marker.
119 pub indent_list_on_tab: bool,
120 /// Inlay hint related settings.
121 pub inlay_hints: InlayHintSettings,
122 /// Whether to automatically close brackets.
123 pub use_autoclose: bool,
124 /// Whether to automatically surround text with brackets.
125 pub use_auto_surround: bool,
126 /// Whether to use additional LSP queries to format (and amend) the code after
127 /// every "trigger" symbol input, defined by LSP server capabilities.
128 pub use_on_type_format: bool,
129 /// Whether indentation should be adjusted based on the context whilst typing.
130 pub auto_indent: bool,
131 /// Whether indentation of pasted content should be adjusted based on the context.
132 pub auto_indent_on_paste: bool,
133 /// Controls how the editor handles the autoclosed characters.
134 pub always_treat_brackets_as_autoclosed: bool,
135 /// Which code actions to run on save
136 pub code_actions_on_format: HashMap<String, bool>,
137 /// Whether to perform linked edits
138 pub linked_edits: bool,
139 /// Task configuration for this language.
140 pub tasks: LanguageTaskSettings,
141 /// Whether to pop the completions menu while typing in an editor without
142 /// explicitly requesting it.
143 pub show_completions_on_input: bool,
144 /// Whether to display inline and alongside documentation for items in the
145 /// completions menu.
146 pub show_completion_documentation: bool,
147 /// Completion settings for this language.
148 pub completions: CompletionSettings,
149 /// Preferred debuggers for this language.
150 pub debuggers: Vec<String>,
151 /// Whether to enable word diff highlighting in the editor.
152 ///
153 /// When enabled, changed words within modified lines are highlighted
154 /// to show exactly what changed.
155 ///
156 /// Default: `true`
157 pub word_diff_enabled: bool,
158 /// Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor.
159 pub colorize_brackets: bool,
160}
161
162#[derive(Debug, Clone, PartialEq)]
163pub struct CompletionSettings {
164 /// Controls how words are completed.
165 /// For large documents, not all words may be fetched for completion.
166 ///
167 /// Default: `fallback`
168 pub words: WordsCompletionMode,
169 /// How many characters has to be in the completions query to automatically show the words-based completions.
170 /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
171 ///
172 /// Default: 3
173 pub words_min_length: usize,
174 /// Whether to fetch LSP completions or not.
175 ///
176 /// Default: true
177 pub lsp: bool,
178 /// When fetching LSP completions, determines how long to wait for a response of a particular server.
179 /// When set to 0, waits indefinitely.
180 ///
181 /// Default: 0
182 pub lsp_fetch_timeout_ms: u64,
183 /// Controls how LSP completions are inserted.
184 ///
185 /// Default: "replace_suffix"
186 pub lsp_insert_mode: LspInsertMode,
187}
188
189/// The settings for indent guides.
190#[derive(Debug, Clone, PartialEq)]
191pub struct IndentGuideSettings {
192 /// Whether to display indent guides in the editor.
193 ///
194 /// Default: true
195 pub enabled: bool,
196 /// The width of the indent guides in pixels, between 1 and 10.
197 ///
198 /// Default: 1
199 pub line_width: u32,
200 /// The width of the active indent guide in pixels, between 1 and 10.
201 ///
202 /// Default: 1
203 pub active_line_width: u32,
204 /// Determines how indent guides are colored.
205 ///
206 /// Default: Fixed
207 pub coloring: settings::IndentGuideColoring,
208 /// Determines how indent guide backgrounds are colored.
209 ///
210 /// Default: Disabled
211 pub background_coloring: settings::IndentGuideBackgroundColoring,
212}
213
214#[derive(Debug, Clone, PartialEq)]
215pub struct LanguageTaskSettings {
216 /// Extra task variables to set for a particular language.
217 pub variables: HashMap<String, String>,
218 pub enabled: bool,
219 /// Use LSP tasks over Zed language extension ones.
220 /// If no LSP tasks are returned due to error/timeout or regular execution,
221 /// Zed language extension tasks will be used instead.
222 ///
223 /// Other Zed tasks will still be shown:
224 /// * Zed task from either of the task config file
225 /// * Zed task from history (e.g. one-off task was spawned before)
226 pub prefer_lsp: bool,
227}
228
229/// Allows to enable/disable formatting with Prettier
230/// and configure default Prettier, used when no project-level Prettier installation is found.
231/// Prettier formatting is disabled by default.
232#[derive(Debug, Clone, PartialEq)]
233pub struct PrettierSettings {
234 /// Enables or disables formatting with Prettier for a given language.
235 pub allowed: bool,
236
237 /// Forces Prettier integration to use a specific parser name when formatting files with the language.
238 pub parser: Option<String>,
239
240 /// Forces Prettier integration to use specific plugins when formatting files with the language.
241 /// The default Prettier will be installed with these plugins.
242 pub plugins: HashSet<String>,
243
244 /// Default Prettier options, in the format as in package.json section for Prettier.
245 /// If project installs Prettier via its package.json, these options will be ignored.
246 pub options: HashMap<String, serde_json::Value>,
247}
248
249impl LanguageSettings {
250 /// A token representing the rest of the available language servers.
251 const REST_OF_LANGUAGE_SERVERS: &'static str = "...";
252
253 pub fn for_buffer<'a>(buffer: &'a Buffer, cx: &'a App) -> Cow<'a, LanguageSettings> {
254 Self::resolve(Some(buffer), None, cx)
255 }
256
257 pub fn for_buffer_at<'a, D: ToOffset>(
258 buffer: &'a Buffer,
259 position: D,
260 cx: &'a App,
261 ) -> Cow<'a, LanguageSettings> {
262 let language = buffer.language_at(position);
263 Self::resolve(Some(buffer), language.map(|l| l.name()).as_ref(), cx)
264 }
265
266 pub fn for_buffer_snapshot<'a>(
267 buffer: &'a BufferSnapshot,
268 offset: Option<usize>,
269 cx: &'a App,
270 ) -> Cow<'a, LanguageSettings> {
271 let location = buffer.file().map(|f| SettingsLocation {
272 worktree_id: f.worktree_id(cx),
273 path: f.path().as_ref(),
274 });
275
276 let language = if let Some(offset) = offset {
277 buffer.language_at(offset)
278 } else {
279 buffer.language()
280 };
281
282 let mut settings = AllLanguageSettings::get(location, cx).language(
283 location,
284 language.map(|l| l.name()).as_ref(),
285 cx,
286 );
287
288 if let Some(modeline) = buffer.modeline() {
289 merge_with_modeline(settings.to_mut(), modeline);
290 }
291
292 settings
293 }
294
295 pub fn resolve<'a>(
296 buffer: Option<&'a Buffer>,
297 override_language: Option<&LanguageName>,
298 cx: &'a App,
299 ) -> Cow<'a, LanguageSettings> {
300 let Some(buffer) = buffer else {
301 return AllLanguageSettings::get(None, cx).language(None, override_language, cx);
302 };
303 let location = buffer.file().map(|f| SettingsLocation {
304 worktree_id: f.worktree_id(cx),
305 path: f.path().as_ref(),
306 });
307 let all = AllLanguageSettings::get(location, cx);
308 let mut settings = if override_language.is_none() {
309 all.language(location, buffer.language().map(|l| l.name()).as_ref(), cx)
310 } else {
311 all.language(location, override_language, cx)
312 };
313
314 if let Some(modeline) = buffer.modeline() {
315 merge_with_modeline(settings.to_mut(), modeline);
316 }
317
318 settings
319 }
320
321 /// Returns the customized list of language servers from the list of
322 /// available language servers.
323 pub fn customized_language_servers(
324 &self,
325 available_language_servers: &[LanguageServerName],
326 ) -> Vec<LanguageServerName> {
327 Self::resolve_language_servers(&self.language_servers, available_language_servers)
328 }
329
330 pub(crate) fn resolve_language_servers(
331 configured_language_servers: &[String],
332 available_language_servers: &[LanguageServerName],
333 ) -> Vec<LanguageServerName> {
334 let (disabled_language_servers, enabled_language_servers): (
335 Vec<LanguageServerName>,
336 Vec<LanguageServerName>,
337 ) = configured_language_servers.iter().partition_map(
338 |language_server| match language_server.strip_prefix('!') {
339 Some(disabled) => Either::Left(LanguageServerName(disabled.to_string().into())),
340 None => Either::Right(LanguageServerName(language_server.clone().into())),
341 },
342 );
343
344 let rest = available_language_servers
345 .iter()
346 .filter(|&available_language_server| {
347 !disabled_language_servers.contains(available_language_server)
348 && !enabled_language_servers.contains(available_language_server)
349 })
350 .cloned()
351 .collect::<Vec<_>>();
352
353 enabled_language_servers
354 .into_iter()
355 .flat_map(|language_server| {
356 if language_server.0.as_ref() == Self::REST_OF_LANGUAGE_SERVERS {
357 rest.clone()
358 } else {
359 vec![language_server]
360 }
361 })
362 .collect::<Vec<_>>()
363 }
364}
365
366// The settings for inlay hints.
367#[derive(Copy, Clone, Debug, PartialEq, Eq)]
368pub struct InlayHintSettings {
369 /// Global switch to toggle hints on and off.
370 ///
371 /// Default: false
372 pub enabled: bool,
373 /// Global switch to toggle inline values on and off when debugging.
374 ///
375 /// Default: true
376 pub show_value_hints: bool,
377 /// Whether type hints should be shown.
378 ///
379 /// Default: true
380 pub show_type_hints: bool,
381 /// Whether parameter hints should be shown.
382 ///
383 /// Default: true
384 pub show_parameter_hints: bool,
385 /// Whether other hints should be shown.
386 ///
387 /// Default: true
388 pub show_other_hints: bool,
389 /// Whether to show a background for inlay hints.
390 ///
391 /// If set to `true`, the background will use the `hint.background` color
392 /// from the current theme.
393 ///
394 /// Default: false
395 pub show_background: bool,
396 /// Whether or not to debounce inlay hints updates after buffer edits.
397 ///
398 /// Set to 0 to disable debouncing.
399 ///
400 /// Default: 700
401 pub edit_debounce_ms: u64,
402 /// Whether or not to debounce inlay hints updates after buffer scrolls.
403 ///
404 /// Set to 0 to disable debouncing.
405 ///
406 /// Default: 50
407 pub scroll_debounce_ms: u64,
408 /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
409 /// If only a subset of the modifiers specified is pressed, hints are not toggled.
410 /// If no modifiers are specified, this is equivalent to `None`.
411 ///
412 /// Default: None
413 pub toggle_on_modifiers_press: Option<Modifiers>,
414}
415
416impl InlayHintSettings {
417 /// Returns the kinds of inlay hints that are enabled based on the settings.
418 pub fn enabled_inlay_hint_kinds(&self) -> HashSet<Option<InlayHintKind>> {
419 let mut kinds = HashSet::default();
420 if self.show_type_hints {
421 kinds.insert(Some(InlayHintKind::Type));
422 }
423 if self.show_parameter_hints {
424 kinds.insert(Some(InlayHintKind::Parameter));
425 }
426 if self.show_other_hints {
427 kinds.insert(None);
428 }
429 kinds
430 }
431}
432
433/// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot)
434/// or [Supermaven](https://supermaven.com).
435#[derive(Clone, Debug, Default)]
436pub struct EditPredictionSettings {
437 /// The provider that supplies edit predictions.
438 pub provider: settings::EditPredictionProvider,
439 /// Whether to use the experimental edit prediction context retrieval system.
440 pub use_context: bool,
441 /// A list of globs representing files that edit predictions should be disabled for.
442 /// This list adds to a pre-existing, sensible default set of globs.
443 /// Any additional ones you add are combined with them.
444 pub disabled_globs: Vec<DisabledGlob>,
445 /// Configures how edit predictions are displayed in the buffer.
446 pub mode: settings::EditPredictionsMode,
447 /// Settings specific to GitHub Copilot.
448 pub copilot: CopilotSettings,
449 /// Settings specific to Codestral.
450 pub codestral: CodestralSettings,
451 /// Whether edit predictions are enabled in the assistant panel.
452 /// This setting has no effect if globally disabled.
453 pub enabled_in_text_threads: bool,
454 pub examples_dir: Option<Arc<Path>>,
455 pub example_capture_rate: Option<u16>,
456}
457
458impl EditPredictionSettings {
459 /// Returns whether edit predictions are enabled for the given path.
460 pub fn enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
461 !self.disabled_globs.iter().any(|glob| {
462 if glob.is_absolute {
463 file.as_local()
464 .is_some_and(|local| glob.matcher.is_match(local.abs_path(cx)))
465 } else {
466 glob.matcher.is_match(file.path().as_std_path())
467 }
468 })
469 }
470}
471
472#[derive(Clone, Debug)]
473pub struct DisabledGlob {
474 matcher: GlobMatcher,
475 is_absolute: bool,
476}
477
478#[derive(Clone, Debug, Default)]
479pub struct CopilotSettings {
480 /// HTTP/HTTPS proxy to use for Copilot.
481 pub proxy: Option<String>,
482 /// Disable certificate verification for proxy (not recommended).
483 pub proxy_no_verify: Option<bool>,
484 /// Enterprise URI for Copilot.
485 pub enterprise_uri: Option<String>,
486 /// Whether the Copilot Next Edit Suggestions feature is enabled.
487 pub enabled_next_edit_suggestions: Option<bool>,
488}
489
490#[derive(Clone, Debug, Default)]
491pub struct CodestralSettings {
492 /// Model to use for completions.
493 pub model: Option<String>,
494 /// Maximum tokens to generate.
495 pub max_tokens: Option<u32>,
496 /// Custom API URL to use for Codestral.
497 pub api_url: Option<String>,
498}
499
500impl AllLanguageSettings {
501 /// Returns the [`LanguageSettings`] for the language with the specified name.
502 pub fn language<'a>(
503 &'a self,
504 location: Option<SettingsLocation<'a>>,
505 language_name: Option<&LanguageName>,
506 cx: &'a App,
507 ) -> Cow<'a, LanguageSettings> {
508 let settings = language_name
509 .and_then(|name| self.languages.get(name))
510 .unwrap_or(&self.defaults);
511
512 let editorconfig_properties = location.and_then(|location| {
513 cx.global::<SettingsStore>()
514 .editorconfig_store
515 .read(cx)
516 .properties(location.worktree_id, location.path)
517 });
518 if let Some(editorconfig_properties) = editorconfig_properties {
519 let mut settings = settings.clone();
520 merge_with_editorconfig(&mut settings, &editorconfig_properties);
521 Cow::Owned(settings)
522 } else {
523 Cow::Borrowed(settings)
524 }
525 }
526
527 /// Returns whether edit predictions are enabled for the given path.
528 pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
529 self.edit_predictions.enabled_for_file(file, cx)
530 }
531
532 /// Returns whether edit predictions are enabled for the given language and path.
533 pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
534 self.language(None, language.map(|l| l.name()).as_ref(), cx)
535 .show_edit_predictions
536 }
537
538 /// Returns the edit predictions preview mode for the given language and path.
539 pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
540 self.edit_predictions.mode
541 }
542}
543
544fn merge_with_modeline(settings: &mut LanguageSettings, modeline: &ModelineSettings) {
545 let show_whitespaces = modeline.show_trailing_whitespace.and_then(|v| {
546 if v {
547 Some(ShowWhitespaceSetting::Trailing)
548 } else {
549 None
550 }
551 });
552
553 settings
554 .tab_size
555 .merge_from_option(modeline.tab_size.as_ref());
556 settings
557 .hard_tabs
558 .merge_from_option(modeline.hard_tabs.as_ref());
559 settings
560 .preferred_line_length
561 .merge_from_option(modeline.preferred_line_length.map(u32::from).as_ref());
562 settings
563 .auto_indent
564 .merge_from_option(modeline.auto_indent.as_ref());
565 settings
566 .show_whitespaces
567 .merge_from_option(show_whitespaces.as_ref());
568 settings
569 .ensure_final_newline_on_save
570 .merge_from_option(modeline.ensure_final_newline.as_ref());
571}
572
573fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
574 let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
575 MaxLineLen::Value(u) => Some(u as u32),
576 MaxLineLen::Off => None,
577 });
578 let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
579 IndentSize::Value(u) => NonZeroU32::new(u as u32),
580 IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
581 TabWidth::Value(u) => NonZeroU32::new(u as u32),
582 }),
583 });
584 let hard_tabs = cfg
585 .get::<IndentStyle>()
586 .map(|v| v.eq(&IndentStyle::Tabs))
587 .ok();
588 let ensure_final_newline_on_save = cfg
589 .get::<FinalNewline>()
590 .map(|v| match v {
591 FinalNewline::Value(b) => b,
592 })
593 .ok();
594 let remove_trailing_whitespace_on_save = cfg
595 .get::<TrimTrailingWs>()
596 .map(|v| match v {
597 TrimTrailingWs::Value(b) => b,
598 })
599 .ok();
600
601 settings
602 .preferred_line_length
603 .merge_from_option(preferred_line_length.as_ref());
604 settings.tab_size.merge_from_option(tab_size.as_ref());
605 settings.hard_tabs.merge_from_option(hard_tabs.as_ref());
606 settings
607 .remove_trailing_whitespace_on_save
608 .merge_from_option(remove_trailing_whitespace_on_save.as_ref());
609 settings
610 .ensure_final_newline_on_save
611 .merge_from_option(ensure_final_newline_on_save.as_ref());
612}
613
614impl settings::Settings for AllLanguageSettings {
615 fn from_settings(content: &settings::SettingsContent) -> Self {
616 let all_languages = &content.project.all_languages;
617
618 fn load_from_content(settings: LanguageSettingsContent) -> LanguageSettings {
619 let inlay_hints = settings.inlay_hints.unwrap();
620 let completions = settings.completions.unwrap();
621 let prettier = settings.prettier.unwrap();
622 let indent_guides = settings.indent_guides.unwrap();
623 let tasks = settings.tasks.unwrap();
624 let whitespace_map = settings.whitespace_map.unwrap();
625
626 LanguageSettings {
627 tab_size: settings.tab_size.unwrap(),
628 hard_tabs: settings.hard_tabs.unwrap(),
629 soft_wrap: settings.soft_wrap.unwrap(),
630 preferred_line_length: settings.preferred_line_length.unwrap(),
631 show_wrap_guides: settings.show_wrap_guides.unwrap(),
632 wrap_guides: settings.wrap_guides.unwrap(),
633 indent_guides: IndentGuideSettings {
634 enabled: indent_guides.enabled.unwrap(),
635 line_width: indent_guides.line_width.unwrap(),
636 active_line_width: indent_guides.active_line_width.unwrap(),
637 coloring: indent_guides.coloring.unwrap(),
638 background_coloring: indent_guides.background_coloring.unwrap(),
639 },
640 format_on_save: settings.format_on_save.unwrap(),
641 remove_trailing_whitespace_on_save: settings
642 .remove_trailing_whitespace_on_save
643 .unwrap(),
644 ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(),
645 formatter: settings.formatter.unwrap(),
646 prettier: PrettierSettings {
647 allowed: prettier.allowed.unwrap(),
648 parser: prettier.parser.filter(|parser| !parser.is_empty()),
649 plugins: prettier.plugins.unwrap_or_default(),
650 options: prettier.options.unwrap_or_default(),
651 },
652 jsx_tag_auto_close: settings.jsx_tag_auto_close.unwrap().enabled.unwrap(),
653 enable_language_server: settings.enable_language_server.unwrap(),
654 language_servers: settings.language_servers.unwrap(),
655 allow_rewrap: settings.allow_rewrap.unwrap(),
656 show_edit_predictions: settings.show_edit_predictions.unwrap(),
657 edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(),
658 show_whitespaces: settings.show_whitespaces.unwrap(),
659 whitespace_map: WhitespaceMap {
660 space: SharedString::new(whitespace_map.space.unwrap().to_string()),
661 tab: SharedString::new(whitespace_map.tab.unwrap().to_string()),
662 },
663 extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(),
664 extend_list_on_newline: settings.extend_list_on_newline.unwrap(),
665 indent_list_on_tab: settings.indent_list_on_tab.unwrap(),
666 inlay_hints: InlayHintSettings {
667 enabled: inlay_hints.enabled.unwrap(),
668 show_value_hints: inlay_hints.show_value_hints.unwrap(),
669 show_type_hints: inlay_hints.show_type_hints.unwrap(),
670 show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(),
671 show_other_hints: inlay_hints.show_other_hints.unwrap(),
672 show_background: inlay_hints.show_background.unwrap(),
673 edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(),
674 scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(),
675 toggle_on_modifiers_press: inlay_hints
676 .toggle_on_modifiers_press
677 .map(|m| m.into_gpui()),
678 },
679 use_autoclose: settings.use_autoclose.unwrap(),
680 use_auto_surround: settings.use_auto_surround.unwrap(),
681 use_on_type_format: settings.use_on_type_format.unwrap(),
682 auto_indent: settings.auto_indent.unwrap(),
683 auto_indent_on_paste: settings.auto_indent_on_paste.unwrap(),
684 always_treat_brackets_as_autoclosed: settings
685 .always_treat_brackets_as_autoclosed
686 .unwrap(),
687 code_actions_on_format: settings.code_actions_on_format.unwrap(),
688 linked_edits: settings.linked_edits.unwrap(),
689 tasks: LanguageTaskSettings {
690 variables: tasks.variables.unwrap_or_default(),
691 enabled: tasks.enabled.unwrap(),
692 prefer_lsp: tasks.prefer_lsp.unwrap(),
693 },
694 show_completions_on_input: settings.show_completions_on_input.unwrap(),
695 show_completion_documentation: settings.show_completion_documentation.unwrap(),
696 colorize_brackets: settings.colorize_brackets.unwrap(),
697 completions: CompletionSettings {
698 words: completions.words.unwrap(),
699 words_min_length: completions.words_min_length.unwrap() as usize,
700 lsp: completions.lsp.unwrap(),
701 lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(),
702 lsp_insert_mode: completions.lsp_insert_mode.unwrap(),
703 },
704 debuggers: settings.debuggers.unwrap(),
705 word_diff_enabled: settings.word_diff_enabled.unwrap(),
706 }
707 }
708
709 let default_language_settings = load_from_content(all_languages.defaults.clone());
710
711 let mut languages = HashMap::default();
712 for (language_name, settings) in &all_languages.languages.0 {
713 let mut language_settings = all_languages.defaults.clone();
714 settings::merge_from::MergeFrom::merge_from(&mut language_settings, settings);
715 languages.insert(
716 LanguageName(language_name.clone().into()),
717 load_from_content(language_settings),
718 );
719 }
720
721 let edit_prediction_provider = all_languages
722 .features
723 .as_ref()
724 .and_then(|f| f.edit_prediction_provider);
725 let use_edit_prediction_context = all_languages
726 .features
727 .as_ref()
728 .and_then(|f| f.experimental_edit_prediction_context_retrieval)
729 .unwrap_or_default();
730
731 let edit_predictions = all_languages.edit_predictions.clone().unwrap();
732 let edit_predictions_mode = edit_predictions.mode.unwrap();
733
734 let disabled_globs: HashSet<&String> = edit_predictions
735 .disabled_globs
736 .as_ref()
737 .unwrap()
738 .iter()
739 .collect();
740
741 let copilot = edit_predictions.copilot.unwrap();
742 let copilot_settings = CopilotSettings {
743 proxy: copilot.proxy,
744 proxy_no_verify: copilot.proxy_no_verify,
745 enterprise_uri: copilot.enterprise_uri,
746 enabled_next_edit_suggestions: copilot.enabled_next_edit_suggestions,
747 };
748
749 let codestral = edit_predictions.codestral.unwrap();
750 let codestral_settings = CodestralSettings {
751 model: codestral.model,
752 max_tokens: codestral.max_tokens,
753 api_url: codestral.api_url,
754 };
755
756 let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
757
758 let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
759
760 for (language, patterns) in all_languages.file_types.iter().flatten() {
761 let mut builder = GlobSetBuilder::new();
762
763 for pattern in &patterns.0 {
764 builder.add(Glob::new(pattern).unwrap());
765 }
766
767 file_types.insert(
768 language.clone(),
769 (builder.build().unwrap(), patterns.0.clone()),
770 );
771 }
772
773 Self {
774 edit_predictions: EditPredictionSettings {
775 provider: if let Some(provider) = edit_prediction_provider {
776 provider
777 } else {
778 EditPredictionProvider::None
779 },
780 use_context: use_edit_prediction_context,
781 disabled_globs: disabled_globs
782 .iter()
783 .filter_map(|g| {
784 let expanded_g = shellexpand::tilde(g).into_owned();
785 Some(DisabledGlob {
786 matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
787 is_absolute: Path::new(&expanded_g).is_absolute(),
788 })
789 })
790 .collect(),
791 mode: edit_predictions_mode,
792 copilot: copilot_settings,
793 codestral: codestral_settings,
794 enabled_in_text_threads,
795 examples_dir: edit_predictions.examples_dir,
796 example_capture_rate: edit_predictions.example_capture_rate,
797 },
798 defaults: default_language_settings,
799 languages,
800 file_types,
801 }
802 }
803}
804
805#[derive(Default, Debug, Clone, PartialEq, Eq)]
806pub struct JsxTagAutoCloseSettings {
807 /// Enables or disables auto-closing of JSX tags.
808 pub enabled: bool,
809}
810
811#[cfg(test)]
812mod tests {
813 use super::*;
814 use gpui::TestAppContext;
815 use util::rel_path::rel_path;
816
817 #[gpui::test]
818 fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
819 use crate::TestFile;
820 use std::path::PathBuf;
821
822 let cx = cx.app.borrow_mut();
823
824 let build_settings = |globs: &[&str]| -> EditPredictionSettings {
825 EditPredictionSettings {
826 disabled_globs: globs
827 .iter()
828 .map(|glob_str| {
829 #[cfg(windows)]
830 let glob_str = {
831 let mut g = String::new();
832
833 if glob_str.starts_with('/') {
834 g.push_str("C:");
835 }
836
837 g.push_str(&glob_str.replace('/', "\\"));
838 g
839 };
840 #[cfg(windows)]
841 let glob_str = glob_str.as_str();
842 let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
843 DisabledGlob {
844 matcher: globset::Glob::new(&expanded_glob_str)
845 .unwrap()
846 .compile_matcher(),
847 is_absolute: Path::new(&expanded_glob_str).is_absolute(),
848 }
849 })
850 .collect(),
851 ..Default::default()
852 }
853 };
854
855 const WORKTREE_NAME: &str = "project";
856 let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
857 let path = segments.join("/");
858 let path = rel_path(&path);
859
860 Arc::new(TestFile {
861 path: path.into(),
862 root_name: WORKTREE_NAME.to_string(),
863 local_root: Some(PathBuf::from(if cfg!(windows) {
864 "C:\\absolute\\"
865 } else {
866 "/absolute/"
867 })),
868 })
869 };
870
871 let test_file = make_test_file(&["src", "test", "file.rs"]);
872
873 // Test relative globs
874 let settings = build_settings(&["*.rs"]);
875 assert!(!settings.enabled_for_file(&test_file, &cx));
876 let settings = build_settings(&["*.txt"]);
877 assert!(settings.enabled_for_file(&test_file, &cx));
878
879 // Test absolute globs
880 let settings = build_settings(&["/absolute/**/*.rs"]);
881 assert!(!settings.enabled_for_file(&test_file, &cx));
882 let settings = build_settings(&["/other/**/*.rs"]);
883 assert!(settings.enabled_for_file(&test_file, &cx));
884
885 // Test exact path match relative
886 let settings = build_settings(&["src/test/file.rs"]);
887 assert!(!settings.enabled_for_file(&test_file, &cx));
888 let settings = build_settings(&["src/test/otherfile.rs"]);
889 assert!(settings.enabled_for_file(&test_file, &cx));
890
891 // Test exact path match absolute
892 let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
893 assert!(!settings.enabled_for_file(&test_file, &cx));
894 let settings = build_settings(&["/other/test/otherfile.rs"]);
895 assert!(settings.enabled_for_file(&test_file, &cx));
896
897 // Test * glob
898 let settings = build_settings(&["*"]);
899 assert!(!settings.enabled_for_file(&test_file, &cx));
900 let settings = build_settings(&["*.txt"]);
901 assert!(settings.enabled_for_file(&test_file, &cx));
902
903 // Test **/* glob
904 let settings = build_settings(&["**/*"]);
905 assert!(!settings.enabled_for_file(&test_file, &cx));
906 let settings = build_settings(&["other/**/*"]);
907 assert!(settings.enabled_for_file(&test_file, &cx));
908
909 // Test directory/** glob
910 let settings = build_settings(&["src/**"]);
911 assert!(!settings.enabled_for_file(&test_file, &cx));
912
913 let test_file_root: Arc<dyn File> = Arc::new(TestFile {
914 path: rel_path("file.rs").into(),
915 root_name: WORKTREE_NAME.to_string(),
916 local_root: Some(PathBuf::from("/absolute/")),
917 });
918 assert!(settings.enabled_for_file(&test_file_root, &cx));
919
920 let settings = build_settings(&["other/**"]);
921 assert!(settings.enabled_for_file(&test_file, &cx));
922
923 // Test **/directory/* glob
924 let settings = build_settings(&["**/test/*"]);
925 assert!(!settings.enabled_for_file(&test_file, &cx));
926 let settings = build_settings(&["**/other/*"]);
927 assert!(settings.enabled_for_file(&test_file, &cx));
928
929 // Test multiple globs
930 let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
931 assert!(!settings.enabled_for_file(&test_file, &cx));
932 let settings = build_settings(&["*.txt", "*.md", "other/**"]);
933 assert!(settings.enabled_for_file(&test_file, &cx));
934
935 // Test dot files
936 let dot_file = make_test_file(&[".config", "settings.json"]);
937 let settings = build_settings(&[".*/**"]);
938 assert!(!settings.enabled_for_file(&dot_file, &cx));
939
940 let dot_env_file = make_test_file(&[".env"]);
941 let settings = build_settings(&[".env"]);
942 assert!(!settings.enabled_for_file(&dot_env_file, &cx));
943
944 // Test tilde expansion
945 let home = shellexpand::tilde("~").into_owned();
946 let home_file = Arc::new(TestFile {
947 path: rel_path("test.rs").into(),
948 root_name: "the-dir".to_string(),
949 local_root: Some(PathBuf::from(home)),
950 }) as Arc<dyn File>;
951 let settings = build_settings(&["~/the-dir/test.rs"]);
952 assert!(!settings.enabled_for_file(&home_file, &cx));
953 }
954
955 #[test]
956 fn test_resolve_language_servers() {
957 fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
958 names
959 .iter()
960 .copied()
961 .map(|name| LanguageServerName(name.to_string().into()))
962 .collect::<Vec<_>>()
963 }
964
965 let available_language_servers = language_server_names(&[
966 "typescript-language-server",
967 "biome",
968 "deno",
969 "eslint",
970 "tailwind",
971 ]);
972
973 // A value of just `["..."]` is the same as taking all of the available language servers.
974 assert_eq!(
975 LanguageSettings::resolve_language_servers(
976 &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
977 &available_language_servers,
978 ),
979 available_language_servers
980 );
981
982 // Referencing one of the available language servers will change its order.
983 assert_eq!(
984 LanguageSettings::resolve_language_servers(
985 &[
986 "biome".into(),
987 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
988 "deno".into()
989 ],
990 &available_language_servers
991 ),
992 language_server_names(&[
993 "biome",
994 "typescript-language-server",
995 "eslint",
996 "tailwind",
997 "deno",
998 ])
999 );
1000
1001 // Negating an available language server removes it from the list.
1002 assert_eq!(
1003 LanguageSettings::resolve_language_servers(
1004 &[
1005 "deno".into(),
1006 "!typescript-language-server".into(),
1007 "!biome".into(),
1008 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1009 ],
1010 &available_language_servers
1011 ),
1012 language_server_names(&["deno", "eslint", "tailwind"])
1013 );
1014
1015 // Adding a language server not in the list of available language servers adds it to the list.
1016 assert_eq!(
1017 LanguageSettings::resolve_language_servers(
1018 &[
1019 "my-cool-language-server".into(),
1020 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1021 ],
1022 &available_language_servers
1023 ),
1024 language_server_names(&[
1025 "my-cool-language-server",
1026 "typescript-language-server",
1027 "biome",
1028 "deno",
1029 "eslint",
1030 "tailwind",
1031 ])
1032 );
1033 }
1034}