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