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