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