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}
487
488#[derive(Clone, Debug, Default)]
489pub struct CodestralSettings {
490 /// Model to use for completions.
491 pub model: Option<String>,
492 /// Maximum tokens to generate.
493 pub max_tokens: Option<u32>,
494 /// Custom API URL to use for Codestral.
495 pub api_url: Option<String>,
496}
497
498impl AllLanguageSettings {
499 /// Returns the [`LanguageSettings`] for the language with the specified name.
500 pub fn language<'a>(
501 &'a self,
502 location: Option<SettingsLocation<'a>>,
503 language_name: Option<&LanguageName>,
504 cx: &'a App,
505 ) -> Cow<'a, LanguageSettings> {
506 let settings = language_name
507 .and_then(|name| self.languages.get(name))
508 .unwrap_or(&self.defaults);
509
510 let editorconfig_properties = location.and_then(|location| {
511 cx.global::<SettingsStore>()
512 .editorconfig_store
513 .read(cx)
514 .properties(location.worktree_id, location.path)
515 });
516 if let Some(editorconfig_properties) = editorconfig_properties {
517 let mut settings = settings.clone();
518 merge_with_editorconfig(&mut settings, &editorconfig_properties);
519 Cow::Owned(settings)
520 } else {
521 Cow::Borrowed(settings)
522 }
523 }
524
525 /// Returns whether edit predictions are enabled for the given path.
526 pub fn edit_predictions_enabled_for_file(&self, file: &Arc<dyn File>, cx: &App) -> bool {
527 self.edit_predictions.enabled_for_file(file, cx)
528 }
529
530 /// Returns whether edit predictions are enabled for the given language and path.
531 pub fn show_edit_predictions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
532 self.language(None, language.map(|l| l.name()).as_ref(), cx)
533 .show_edit_predictions
534 }
535
536 /// Returns the edit predictions preview mode for the given language and path.
537 pub fn edit_predictions_mode(&self) -> EditPredictionsMode {
538 self.edit_predictions.mode
539 }
540}
541
542fn merge_with_modeline(settings: &mut LanguageSettings, modeline: &ModelineSettings) {
543 let show_whitespaces = modeline.show_trailing_whitespace.and_then(|v| {
544 if v {
545 Some(ShowWhitespaceSetting::Trailing)
546 } else {
547 None
548 }
549 });
550
551 settings
552 .tab_size
553 .merge_from_option(modeline.tab_size.as_ref());
554 settings
555 .hard_tabs
556 .merge_from_option(modeline.hard_tabs.as_ref());
557 settings
558 .preferred_line_length
559 .merge_from_option(modeline.preferred_line_length.map(u32::from).as_ref());
560 settings
561 .auto_indent
562 .merge_from_option(modeline.auto_indent.as_ref());
563 settings
564 .show_whitespaces
565 .merge_from_option(show_whitespaces.as_ref());
566 settings
567 .ensure_final_newline_on_save
568 .merge_from_option(modeline.ensure_final_newline.as_ref());
569}
570
571fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) {
572 let preferred_line_length = cfg.get::<MaxLineLen>().ok().and_then(|v| match v {
573 MaxLineLen::Value(u) => Some(u as u32),
574 MaxLineLen::Off => None,
575 });
576 let tab_size = cfg.get::<IndentSize>().ok().and_then(|v| match v {
577 IndentSize::Value(u) => NonZeroU32::new(u as u32),
578 IndentSize::UseTabWidth => cfg.get::<TabWidth>().ok().and_then(|w| match w {
579 TabWidth::Value(u) => NonZeroU32::new(u as u32),
580 }),
581 });
582 let hard_tabs = cfg
583 .get::<IndentStyle>()
584 .map(|v| v.eq(&IndentStyle::Tabs))
585 .ok();
586 let ensure_final_newline_on_save = cfg
587 .get::<FinalNewline>()
588 .map(|v| match v {
589 FinalNewline::Value(b) => b,
590 })
591 .ok();
592 let remove_trailing_whitespace_on_save = cfg
593 .get::<TrimTrailingWs>()
594 .map(|v| match v {
595 TrimTrailingWs::Value(b) => b,
596 })
597 .ok();
598
599 settings
600 .preferred_line_length
601 .merge_from_option(preferred_line_length.as_ref());
602 settings.tab_size.merge_from_option(tab_size.as_ref());
603 settings.hard_tabs.merge_from_option(hard_tabs.as_ref());
604 settings
605 .remove_trailing_whitespace_on_save
606 .merge_from_option(remove_trailing_whitespace_on_save.as_ref());
607 settings
608 .ensure_final_newline_on_save
609 .merge_from_option(ensure_final_newline_on_save.as_ref());
610}
611
612impl settings::Settings for AllLanguageSettings {
613 fn from_settings(content: &settings::SettingsContent) -> Self {
614 let all_languages = &content.project.all_languages;
615
616 fn load_from_content(settings: LanguageSettingsContent) -> LanguageSettings {
617 let inlay_hints = settings.inlay_hints.unwrap();
618 let completions = settings.completions.unwrap();
619 let prettier = settings.prettier.unwrap();
620 let indent_guides = settings.indent_guides.unwrap();
621 let tasks = settings.tasks.unwrap();
622 let whitespace_map = settings.whitespace_map.unwrap();
623
624 LanguageSettings {
625 tab_size: settings.tab_size.unwrap(),
626 hard_tabs: settings.hard_tabs.unwrap(),
627 soft_wrap: settings.soft_wrap.unwrap(),
628 preferred_line_length: settings.preferred_line_length.unwrap(),
629 show_wrap_guides: settings.show_wrap_guides.unwrap(),
630 wrap_guides: settings.wrap_guides.unwrap(),
631 indent_guides: IndentGuideSettings {
632 enabled: indent_guides.enabled.unwrap(),
633 line_width: indent_guides.line_width.unwrap(),
634 active_line_width: indent_guides.active_line_width.unwrap(),
635 coloring: indent_guides.coloring.unwrap(),
636 background_coloring: indent_guides.background_coloring.unwrap(),
637 },
638 format_on_save: settings.format_on_save.unwrap(),
639 remove_trailing_whitespace_on_save: settings
640 .remove_trailing_whitespace_on_save
641 .unwrap(),
642 ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(),
643 formatter: settings.formatter.unwrap(),
644 prettier: PrettierSettings {
645 allowed: prettier.allowed.unwrap(),
646 parser: prettier.parser.filter(|parser| !parser.is_empty()),
647 plugins: prettier.plugins.unwrap_or_default(),
648 options: prettier.options.unwrap_or_default(),
649 },
650 jsx_tag_auto_close: settings.jsx_tag_auto_close.unwrap().enabled.unwrap(),
651 enable_language_server: settings.enable_language_server.unwrap(),
652 language_servers: settings.language_servers.unwrap(),
653 allow_rewrap: settings.allow_rewrap.unwrap(),
654 show_edit_predictions: settings.show_edit_predictions.unwrap(),
655 edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(),
656 show_whitespaces: settings.show_whitespaces.unwrap(),
657 whitespace_map: WhitespaceMap {
658 space: SharedString::new(whitespace_map.space.unwrap().to_string()),
659 tab: SharedString::new(whitespace_map.tab.unwrap().to_string()),
660 },
661 extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(),
662 extend_list_on_newline: settings.extend_list_on_newline.unwrap(),
663 indent_list_on_tab: settings.indent_list_on_tab.unwrap(),
664 inlay_hints: InlayHintSettings {
665 enabled: inlay_hints.enabled.unwrap(),
666 show_value_hints: inlay_hints.show_value_hints.unwrap(),
667 show_type_hints: inlay_hints.show_type_hints.unwrap(),
668 show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(),
669 show_other_hints: inlay_hints.show_other_hints.unwrap(),
670 show_background: inlay_hints.show_background.unwrap(),
671 edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(),
672 scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(),
673 toggle_on_modifiers_press: inlay_hints
674 .toggle_on_modifiers_press
675 .map(|m| m.into_gpui()),
676 },
677 use_autoclose: settings.use_autoclose.unwrap(),
678 use_auto_surround: settings.use_auto_surround.unwrap(),
679 use_on_type_format: settings.use_on_type_format.unwrap(),
680 auto_indent: settings.auto_indent.unwrap(),
681 auto_indent_on_paste: settings.auto_indent_on_paste.unwrap(),
682 always_treat_brackets_as_autoclosed: settings
683 .always_treat_brackets_as_autoclosed
684 .unwrap(),
685 code_actions_on_format: settings.code_actions_on_format.unwrap(),
686 linked_edits: settings.linked_edits.unwrap(),
687 tasks: LanguageTaskSettings {
688 variables: tasks.variables.unwrap_or_default(),
689 enabled: tasks.enabled.unwrap(),
690 prefer_lsp: tasks.prefer_lsp.unwrap(),
691 },
692 show_completions_on_input: settings.show_completions_on_input.unwrap(),
693 show_completion_documentation: settings.show_completion_documentation.unwrap(),
694 colorize_brackets: settings.colorize_brackets.unwrap(),
695 completions: CompletionSettings {
696 words: completions.words.unwrap(),
697 words_min_length: completions.words_min_length.unwrap() as usize,
698 lsp: completions.lsp.unwrap(),
699 lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(),
700 lsp_insert_mode: completions.lsp_insert_mode.unwrap(),
701 },
702 debuggers: settings.debuggers.unwrap(),
703 word_diff_enabled: settings.word_diff_enabled.unwrap(),
704 }
705 }
706
707 let default_language_settings = load_from_content(all_languages.defaults.clone());
708
709 let mut languages = HashMap::default();
710 for (language_name, settings) in &all_languages.languages.0 {
711 let mut language_settings = all_languages.defaults.clone();
712 settings::merge_from::MergeFrom::merge_from(&mut language_settings, settings);
713 languages.insert(
714 LanguageName(language_name.clone().into()),
715 load_from_content(language_settings),
716 );
717 }
718
719 let edit_prediction_provider = all_languages
720 .features
721 .as_ref()
722 .and_then(|f| f.edit_prediction_provider);
723 let use_edit_prediction_context = all_languages
724 .features
725 .as_ref()
726 .and_then(|f| f.experimental_edit_prediction_context_retrieval)
727 .unwrap_or_default();
728
729 let edit_predictions = all_languages.edit_predictions.clone().unwrap();
730 let edit_predictions_mode = edit_predictions.mode.unwrap();
731
732 let disabled_globs: HashSet<&String> = edit_predictions
733 .disabled_globs
734 .as_ref()
735 .unwrap()
736 .iter()
737 .collect();
738
739 let copilot = edit_predictions.copilot.unwrap();
740 let copilot_settings = CopilotSettings {
741 proxy: copilot.proxy,
742 proxy_no_verify: copilot.proxy_no_verify,
743 enterprise_uri: copilot.enterprise_uri,
744 };
745
746 let codestral = edit_predictions.codestral.unwrap();
747 let codestral_settings = CodestralSettings {
748 model: codestral.model,
749 max_tokens: codestral.max_tokens,
750 api_url: codestral.api_url,
751 };
752
753 let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap();
754
755 let mut file_types: FxHashMap<Arc<str>, (GlobSet, Vec<String>)> = FxHashMap::default();
756
757 for (language, patterns) in all_languages.file_types.iter().flatten() {
758 let mut builder = GlobSetBuilder::new();
759
760 for pattern in &patterns.0 {
761 builder.add(Glob::new(pattern).unwrap());
762 }
763
764 file_types.insert(
765 language.clone(),
766 (builder.build().unwrap(), patterns.0.clone()),
767 );
768 }
769
770 Self {
771 edit_predictions: EditPredictionSettings {
772 provider: if let Some(provider) = edit_prediction_provider {
773 provider
774 } else {
775 EditPredictionProvider::None
776 },
777 use_context: use_edit_prediction_context,
778 disabled_globs: disabled_globs
779 .iter()
780 .filter_map(|g| {
781 let expanded_g = shellexpand::tilde(g).into_owned();
782 Some(DisabledGlob {
783 matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(),
784 is_absolute: Path::new(&expanded_g).is_absolute(),
785 })
786 })
787 .collect(),
788 mode: edit_predictions_mode,
789 copilot: copilot_settings,
790 codestral: codestral_settings,
791 enabled_in_text_threads,
792 examples_dir: edit_predictions.examples_dir,
793 example_capture_rate: edit_predictions.example_capture_rate,
794 },
795 defaults: default_language_settings,
796 languages,
797 file_types,
798 }
799 }
800}
801
802#[derive(Default, Debug, Clone, PartialEq, Eq)]
803pub struct JsxTagAutoCloseSettings {
804 /// Enables or disables auto-closing of JSX tags.
805 pub enabled: bool,
806}
807
808#[cfg(test)]
809mod tests {
810 use super::*;
811 use gpui::TestAppContext;
812 use util::rel_path::rel_path;
813
814 #[gpui::test]
815 fn test_edit_predictions_enabled_for_file(cx: &mut TestAppContext) {
816 use crate::TestFile;
817 use std::path::PathBuf;
818
819 let cx = cx.app.borrow_mut();
820
821 let build_settings = |globs: &[&str]| -> EditPredictionSettings {
822 EditPredictionSettings {
823 disabled_globs: globs
824 .iter()
825 .map(|glob_str| {
826 #[cfg(windows)]
827 let glob_str = {
828 let mut g = String::new();
829
830 if glob_str.starts_with('/') {
831 g.push_str("C:");
832 }
833
834 g.push_str(&glob_str.replace('/', "\\"));
835 g
836 };
837 #[cfg(windows)]
838 let glob_str = glob_str.as_str();
839 let expanded_glob_str = shellexpand::tilde(glob_str).into_owned();
840 DisabledGlob {
841 matcher: globset::Glob::new(&expanded_glob_str)
842 .unwrap()
843 .compile_matcher(),
844 is_absolute: Path::new(&expanded_glob_str).is_absolute(),
845 }
846 })
847 .collect(),
848 ..Default::default()
849 }
850 };
851
852 const WORKTREE_NAME: &str = "project";
853 let make_test_file = |segments: &[&str]| -> Arc<dyn File> {
854 let path = segments.join("/");
855 let path = rel_path(&path);
856
857 Arc::new(TestFile {
858 path: path.into(),
859 root_name: WORKTREE_NAME.to_string(),
860 local_root: Some(PathBuf::from(if cfg!(windows) {
861 "C:\\absolute\\"
862 } else {
863 "/absolute/"
864 })),
865 })
866 };
867
868 let test_file = make_test_file(&["src", "test", "file.rs"]);
869
870 // Test relative globs
871 let settings = build_settings(&["*.rs"]);
872 assert!(!settings.enabled_for_file(&test_file, &cx));
873 let settings = build_settings(&["*.txt"]);
874 assert!(settings.enabled_for_file(&test_file, &cx));
875
876 // Test absolute globs
877 let settings = build_settings(&["/absolute/**/*.rs"]);
878 assert!(!settings.enabled_for_file(&test_file, &cx));
879 let settings = build_settings(&["/other/**/*.rs"]);
880 assert!(settings.enabled_for_file(&test_file, &cx));
881
882 // Test exact path match relative
883 let settings = build_settings(&["src/test/file.rs"]);
884 assert!(!settings.enabled_for_file(&test_file, &cx));
885 let settings = build_settings(&["src/test/otherfile.rs"]);
886 assert!(settings.enabled_for_file(&test_file, &cx));
887
888 // Test exact path match absolute
889 let settings = build_settings(&[&format!("/absolute/{}/src/test/file.rs", WORKTREE_NAME)]);
890 assert!(!settings.enabled_for_file(&test_file, &cx));
891 let settings = build_settings(&["/other/test/otherfile.rs"]);
892 assert!(settings.enabled_for_file(&test_file, &cx));
893
894 // Test * glob
895 let settings = build_settings(&["*"]);
896 assert!(!settings.enabled_for_file(&test_file, &cx));
897 let settings = build_settings(&["*.txt"]);
898 assert!(settings.enabled_for_file(&test_file, &cx));
899
900 // Test **/* glob
901 let settings = build_settings(&["**/*"]);
902 assert!(!settings.enabled_for_file(&test_file, &cx));
903 let settings = build_settings(&["other/**/*"]);
904 assert!(settings.enabled_for_file(&test_file, &cx));
905
906 // Test directory/** glob
907 let settings = build_settings(&["src/**"]);
908 assert!(!settings.enabled_for_file(&test_file, &cx));
909
910 let test_file_root: Arc<dyn File> = Arc::new(TestFile {
911 path: rel_path("file.rs").into(),
912 root_name: WORKTREE_NAME.to_string(),
913 local_root: Some(PathBuf::from("/absolute/")),
914 });
915 assert!(settings.enabled_for_file(&test_file_root, &cx));
916
917 let settings = build_settings(&["other/**"]);
918 assert!(settings.enabled_for_file(&test_file, &cx));
919
920 // Test **/directory/* glob
921 let settings = build_settings(&["**/test/*"]);
922 assert!(!settings.enabled_for_file(&test_file, &cx));
923 let settings = build_settings(&["**/other/*"]);
924 assert!(settings.enabled_for_file(&test_file, &cx));
925
926 // Test multiple globs
927 let settings = build_settings(&["*.rs", "*.txt", "src/**"]);
928 assert!(!settings.enabled_for_file(&test_file, &cx));
929 let settings = build_settings(&["*.txt", "*.md", "other/**"]);
930 assert!(settings.enabled_for_file(&test_file, &cx));
931
932 // Test dot files
933 let dot_file = make_test_file(&[".config", "settings.json"]);
934 let settings = build_settings(&[".*/**"]);
935 assert!(!settings.enabled_for_file(&dot_file, &cx));
936
937 let dot_env_file = make_test_file(&[".env"]);
938 let settings = build_settings(&[".env"]);
939 assert!(!settings.enabled_for_file(&dot_env_file, &cx));
940
941 // Test tilde expansion
942 let home = shellexpand::tilde("~").into_owned();
943 let home_file = Arc::new(TestFile {
944 path: rel_path("test.rs").into(),
945 root_name: "the-dir".to_string(),
946 local_root: Some(PathBuf::from(home)),
947 }) as Arc<dyn File>;
948 let settings = build_settings(&["~/the-dir/test.rs"]);
949 assert!(!settings.enabled_for_file(&home_file, &cx));
950 }
951
952 #[test]
953 fn test_resolve_language_servers() {
954 fn language_server_names(names: &[&str]) -> Vec<LanguageServerName> {
955 names
956 .iter()
957 .copied()
958 .map(|name| LanguageServerName(name.to_string().into()))
959 .collect::<Vec<_>>()
960 }
961
962 let available_language_servers = language_server_names(&[
963 "typescript-language-server",
964 "biome",
965 "deno",
966 "eslint",
967 "tailwind",
968 ]);
969
970 // A value of just `["..."]` is the same as taking all of the available language servers.
971 assert_eq!(
972 LanguageSettings::resolve_language_servers(
973 &[LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()],
974 &available_language_servers,
975 ),
976 available_language_servers
977 );
978
979 // Referencing one of the available language servers will change its order.
980 assert_eq!(
981 LanguageSettings::resolve_language_servers(
982 &[
983 "biome".into(),
984 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into(),
985 "deno".into()
986 ],
987 &available_language_servers
988 ),
989 language_server_names(&[
990 "biome",
991 "typescript-language-server",
992 "eslint",
993 "tailwind",
994 "deno",
995 ])
996 );
997
998 // Negating an available language server removes it from the list.
999 assert_eq!(
1000 LanguageSettings::resolve_language_servers(
1001 &[
1002 "deno".into(),
1003 "!typescript-language-server".into(),
1004 "!biome".into(),
1005 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1006 ],
1007 &available_language_servers
1008 ),
1009 language_server_names(&["deno", "eslint", "tailwind"])
1010 );
1011
1012 // Adding a language server not in the list of available language servers adds it to the list.
1013 assert_eq!(
1014 LanguageSettings::resolve_language_servers(
1015 &[
1016 "my-cool-language-server".into(),
1017 LanguageSettings::REST_OF_LANGUAGE_SERVERS.into()
1018 ],
1019 &available_language_servers
1020 ),
1021 language_server_names(&[
1022 "my-cool-language-server",
1023 "typescript-language-server",
1024 "biome",
1025 "deno",
1026 "eslint",
1027 "tailwind",
1028 ])
1029 );
1030 }
1031}