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