Skip to repository content1603 lines · 54.6 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T04:34:03.052Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
language.rs
1use std::{borrow::Cow, num::NonZeroU32};
2
3use collections::{HashMap, HashSet};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6use settings_macros::{MergeFrom, with_fallible_options};
7use std::sync::Arc;
8
9use crate::{DocumentFoldingRanges, DocumentSymbols, ExtendingSet, SemanticTokens, merge_from};
10
11/// The state of the modifier keys at some point in time
12#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
13pub struct ModifiersContent {
14 /// The control key
15 #[serde(default)]
16 pub control: bool,
17 /// The alt key
18 /// Sometimes also known as the 'meta' key
19 #[serde(default)]
20 pub alt: bool,
21 /// The shift key
22 #[serde(default)]
23 pub shift: bool,
24 /// The command key, on macos
25 /// the windows key, on windows
26 /// the super key, on linux
27 #[serde(default)]
28 pub platform: bool,
29 /// The function key
30 #[serde(default)]
31 pub function: bool,
32}
33
34#[with_fallible_options]
35#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
36pub struct AllLanguageSettingsContent {
37 /// The edit prediction settings.
38 pub edit_predictions: Option<EditPredictionSettingsContent>,
39 /// The default language settings.
40 #[serde(flatten)]
41 pub defaults: LanguageSettingsContent,
42 /// The settings for individual languages.
43 #[serde(default)]
44 pub languages: LanguageToSettingsMap,
45 /// Settings for associating file extensions and filenames
46 /// with languages.
47 pub file_types: Option<FileTypeMap>,
48}
49
50impl merge_from::MergeFrom for AllLanguageSettingsContent {
51 fn merge_from(&mut self, other: &Self) {
52 self.file_types.merge_from(&other.file_types);
53 self.edit_predictions.merge_from(&other.edit_predictions);
54
55 // A user's global settings override the default global settings and
56 // all default language-specific settings.
57 self.defaults.merge_from(&other.defaults);
58 for language_settings in self.languages.0.values_mut() {
59 let language_servers = language_settings.language_servers.take();
60 language_settings.merge_from(&other.defaults);
61 language_settings.language_servers = language_servers;
62 }
63
64 // A user's language-specific settings override default language-specific settings.
65 for (language_name, user_language_settings) in &other.languages.0 {
66 if let Some(existing) = self.languages.0.get_mut(language_name) {
67 existing.merge_from(&user_language_settings);
68 } else {
69 let mut new_settings = self.defaults.clone();
70 new_settings.language_servers = None;
71 new_settings.merge_from(&user_language_settings);
72
73 self.languages.0.insert(language_name.clone(), new_settings);
74 }
75 }
76 }
77}
78
79/// The provider that supplies edit predictions.
80#[derive(
81 Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom,
82)]
83#[serde(rename_all = "snake_case")]
84pub enum EditPredictionProvider {
85 None,
86 #[default]
87 Copilot,
88 Zed,
89 Codestral,
90 Ollama,
91 OpenAiCompatibleApi,
92 Mercury,
93}
94
95impl EditPredictionProvider {
96 pub fn is_zed(&self) -> bool {
97 match self {
98 EditPredictionProvider::Zed => true,
99 EditPredictionProvider::None
100 | EditPredictionProvider::Copilot
101 | EditPredictionProvider::Codestral
102 | EditPredictionProvider::Ollama
103 | EditPredictionProvider::OpenAiCompatibleApi
104 | EditPredictionProvider::Mercury => false,
105 }
106 }
107
108 pub fn display_name(&self) -> Option<&'static str> {
109 match self {
110 EditPredictionProvider::Zed => Some("Zed AI"),
111 EditPredictionProvider::Copilot => Some("GitHub Copilot"),
112 EditPredictionProvider::Codestral => Some("Codestral"),
113 EditPredictionProvider::Mercury => Some("Mercury"),
114 EditPredictionProvider::None => None,
115 EditPredictionProvider::Ollama => Some("Ollama"),
116 EditPredictionProvider::OpenAiCompatibleApi => Some("OpenAI-Compatible API"),
117 }
118 }
119}
120
121/// The contents of the edit prediction settings.
122#[with_fallible_options]
123#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
124pub struct EditPredictionSettingsContent {
125 /// Determines which edit prediction provider to use.
126 pub provider: Option<EditPredictionProvider>,
127 /// A list of globs representing files that edit predictions should be disabled for.
128 /// This list adds to a pre-existing, sensible default set of globs.
129 /// Any additional ones you add are combined with them.
130 pub disabled_globs: Option<Vec<String>>,
131 /// The mode used to display edit predictions in the buffer.
132 /// Provider support required.
133 pub mode: Option<EditPredictionsMode>,
134 /// Settings specific to GitHub Copilot.
135 pub copilot: Option<CopilotSettingsContent>,
136 /// Settings specific to Codestral.
137 pub codestral: Option<CodestralSettingsContent>,
138 /// Settings specific to Ollama.
139 pub ollama: Option<OllamaEditPredictionSettingsContent>,
140 /// Settings specific to using custom OpenAI-compatible servers for edit prediction.
141 pub open_ai_compatible_api: Option<CustomEditPredictionProviderSettingsContent>,
142 /// Controls whether Omega may collect training data when using Zed's Edit Predictions.
143 /// Data is only ever captured for files in projects that are detected as open source.
144 ///
145 /// - `"default"`: use the preference previously set via the status-bar toggle,
146 /// or false if no preference has been stored.
147 /// - `"yes"`: allow data collection for files in open-source projects.
148 /// - `"no"`: never allow data collection.
149 pub allow_data_collection: Option<EditPredictionDataCollectionChoice>,
150}
151
152#[with_fallible_options]
153#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
154pub struct CustomEditPredictionProviderSettingsContent {
155 /// Api URL to use for completions.
156 ///
157 /// Default: ""
158 pub api_url: Option<String>,
159 /// The prompt format to use for completions. Set to `""` to have the format be derived from the model name.
160 ///
161 /// Default: ""
162 pub prompt_format: Option<EditPredictionPromptFormatContent>,
163 /// The name of the model.
164 ///
165 /// Default: ""
166 pub model: Option<String>,
167 /// Maximum tokens to generate.
168 ///
169 /// Default: 256
170 pub max_output_tokens: Option<u32>,
171}
172
173#[derive(
174 Copy,
175 Clone,
176 Debug,
177 Default,
178 PartialEq,
179 Eq,
180 Serialize,
181 Deserialize,
182 JsonSchema,
183 MergeFrom,
184 strum::VariantArray,
185 strum::VariantNames,
186)]
187#[serde(rename_all = "snake_case")]
188pub enum EditPredictionPromptFormatContent {
189 #[default]
190 Infer,
191 Zeta,
192 Zeta2,
193 Zeta2_1,
194 CodeLlama,
195 StarCoder,
196 DeepseekCoder,
197 Qwen,
198 CodeGemma,
199 Codestral,
200 Glm,
201}
202
203#[with_fallible_options]
204#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
205pub struct CopilotSettingsContent {
206 /// HTTP/HTTPS proxy to use for Copilot.
207 ///
208 /// Default: none
209 pub proxy: Option<String>,
210 /// Disable certificate verification for the proxy (not recommended).
211 ///
212 /// Default: false
213 pub proxy_no_verify: Option<bool>,
214 /// Enterprise URI for Copilot.
215 ///
216 /// Default: none
217 pub enterprise_uri: Option<String>,
218 /// Whether the Copilot Next Edit Suggestions feature is enabled.
219 ///
220 /// Default: true
221 pub enable_next_edit_suggestions: Option<bool>,
222}
223
224#[with_fallible_options]
225#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
226pub struct CodestralSettingsContent {
227 /// Model to use for completions.
228 ///
229 /// Default: "codestral-latest"
230 pub model: Option<String>,
231 /// Maximum tokens to generate.
232 ///
233 /// Default: 150
234 pub max_tokens: Option<u32>,
235 /// Api URL to use for completions.
236 ///
237 /// Default: "https://codestral.mistral.ai"
238 pub api_url: Option<String>,
239}
240
241/// Ollama model name for edit predictions.
242#[with_fallible_options]
243#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
244#[serde(transparent)]
245pub struct OllamaModelName(pub String);
246
247impl AsRef<str> for OllamaModelName {
248 fn as_ref(&self) -> &str {
249 &self.0
250 }
251}
252
253impl From<String> for OllamaModelName {
254 fn from(value: String) -> Self {
255 Self(value)
256 }
257}
258
259impl From<OllamaModelName> for String {
260 fn from(value: OllamaModelName) -> Self {
261 value.0
262 }
263}
264
265#[with_fallible_options]
266#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
267pub struct OllamaEditPredictionSettingsContent {
268 /// Model to use for completions.
269 ///
270 /// Default: none
271 pub model: Option<OllamaModelName>,
272 /// Maximum tokens to generate for FIM models.
273 ///
274 /// Default: 256
275 pub max_output_tokens: Option<u32>,
276 /// Api URL to use for completions.
277 ///
278 /// Default: "http://localhost:11434"
279 pub api_url: Option<String>,
280
281 /// The prompt format to use for completions. Set to `""` to have the format be derived from the model name.
282 ///
283 /// Default: ""
284 pub prompt_format: Option<EditPredictionPromptFormatContent>,
285}
286
287/// Controls whether Omega collects training data when using Zed's Edit Predictions.
288#[derive(
289 Copy,
290 Clone,
291 Debug,
292 Default,
293 Eq,
294 PartialEq,
295 Serialize,
296 Deserialize,
297 JsonSchema,
298 MergeFrom,
299 strum::VariantArray,
300 strum::VariantNames,
301)]
302#[serde(rename_all = "snake_case")]
303pub enum EditPredictionDataCollectionChoice {
304 /// Use the preference previously set via the status-bar toggle, or false
305 /// if no preference has been stored.
306 #[default]
307 Default,
308 /// Allow Omega to collect training data from open-source projects.
309 Yes,
310 /// Never allow training data collection.
311 No,
312}
313
314/// The mode in which edit predictions should be displayed.
315#[derive(
316 Copy,
317 Clone,
318 Debug,
319 Default,
320 Eq,
321 PartialEq,
322 Serialize,
323 Deserialize,
324 JsonSchema,
325 MergeFrom,
326 strum::VariantArray,
327 strum::VariantNames,
328)]
329#[serde(rename_all = "snake_case")]
330pub enum EditPredictionsMode {
331 /// If provider supports it, display inline when holding modifier key (e.g., alt).
332 /// Otherwise, eager preview is used.
333 #[serde(alias = "auto")]
334 Subtle,
335 /// Display inline when there are no language server completions available.
336 #[default]
337 #[serde(alias = "eager_preview")]
338 Eager,
339}
340
341/// Controls the soft-wrapping behavior in the editor.
342#[derive(
343 Copy,
344 Clone,
345 Debug,
346 Serialize,
347 Deserialize,
348 PartialEq,
349 Eq,
350 JsonSchema,
351 MergeFrom,
352 strum::VariantArray,
353 strum::VariantNames,
354)]
355#[serde(rename_all = "snake_case")]
356pub enum AutoIndentMode {
357 /// Adjusts indentation based on syntax context when typing.
358 /// Uses tree-sitter to analyze code structure and indent accordingly.
359 SyntaxAware,
360 /// Preserve the indentation of the current line when creating new lines,
361 /// but don't adjust based on syntax context.
362 PreserveIndent,
363 /// No automatic indentation. New lines start at column 0.
364 None,
365}
366
367/// Controls the soft-wrapping behavior in the editor.
368#[derive(
369 Copy,
370 Clone,
371 Debug,
372 Serialize,
373 Deserialize,
374 PartialEq,
375 Eq,
376 JsonSchema,
377 MergeFrom,
378 strum::VariantArray,
379 strum::VariantNames,
380)]
381#[serde(rename_all = "snake_case")]
382pub enum SoftWrap {
383 /// Prefer a single line generally, unless an overly long line is encountered.
384 None,
385 /// Deprecated: use None instead. Left to avoid breaking existing users' configs.
386 /// Prefer a single line generally, unless an overly long line is encountered.
387 PreferLine,
388 /// Soft wrap lines that exceed the editor width.
389 EditorWidth,
390 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
391 #[serde(alias = "preferred_line_length")]
392 Bounded,
393}
394
395pub const REST_OF_LANGUAGE_SERVERS: &str = "...";
396
397#[derive(Clone, Debug, PartialEq, Eq, JsonSchema)]
398#[schemars(with = "String")]
399pub struct ConfiguredLanguageServer {
400 pub name: Arc<str>,
401 pub disabled: bool,
402}
403
404impl ConfiguredLanguageServer {
405 const DISABLED_CHAR: char = '!';
406
407 pub fn new(name: impl Into<Arc<str>>) -> Self {
408 Self {
409 name: name.into(),
410 disabled: false,
411 }
412 }
413
414 pub fn new_disabled(name: impl Into<Arc<str>>) -> Self {
415 Self {
416 name: name.into(),
417 disabled: true,
418 }
419 }
420}
421
422impl From<&str> for ConfiguredLanguageServer {
423 fn from(value: &str) -> Self {
424 match value.strip_prefix(Self::DISABLED_CHAR) {
425 Some(name) => Self::new_disabled(name),
426 None => Self::new(value),
427 }
428 }
429}
430
431impl Serialize for ConfiguredLanguageServer {
432 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
433 where
434 S: serde::Serializer,
435 {
436 if self.disabled {
437 serializer.collect_str(&format_args!("{}{}", Self::DISABLED_CHAR, self.name))
438 } else {
439 serializer.serialize_str(&self.name)
440 }
441 }
442}
443
444impl<'de> Deserialize<'de> for ConfiguredLanguageServer {
445 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
446 where
447 D: serde::Deserializer<'de>,
448 {
449 let value = Cow::<'de, str>::deserialize(deserializer)?;
450 Ok(Self::from(value.as_ref()))
451 }
452}
453
454/// The settings for a particular language.
455#[with_fallible_options]
456#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
457pub struct LanguageSettingsContent {
458 /// How many columns a tab should occupy.
459 ///
460 /// Default: 4
461 #[schemars(range(min = 1, max = 128))]
462 pub tab_size: Option<NonZeroU32>,
463 /// Whether to indent lines using tab characters, as opposed to multiple
464 /// spaces.
465 ///
466 /// Default: false
467 pub hard_tabs: Option<bool>,
468 /// How to soft-wrap long lines of text.
469 ///
470 /// Default: none
471 pub soft_wrap: Option<SoftWrap>,
472 /// The column at which to soft-wrap lines, for buffers where soft-wrap
473 /// is enabled.
474 ///
475 /// Default: 80
476 pub preferred_line_length: Option<u32>,
477 /// Whether to show wrap guides in the editor. Setting this to true will
478 /// show a guide at the 'preferred_line_length' value if softwrap is set to
479 /// 'preferred_line_length', and will show any additional guides as specified
480 /// by the 'wrap_guides' setting.
481 ///
482 /// Default: true
483 pub show_wrap_guides: Option<bool>,
484 /// Character counts at which to show wrap guides in the editor.
485 ///
486 /// Default: []
487 pub wrap_guides: Option<Vec<usize>>,
488 /// Indent guide related settings.
489 pub indent_guides: Option<IndentGuideSettingsContent>,
490 /// Whether or not to perform a buffer format before saving.
491 ///
492 /// Default: on
493 pub format_on_save: Option<FormatOnSave>,
494 /// Whether or not to remove any trailing whitespace from lines of a buffer
495 /// before saving it.
496 ///
497 /// Default: true
498 pub remove_trailing_whitespace_on_save: Option<bool>,
499 /// Whether or not to ensure there's a single newline at the end of a buffer
500 /// when saving it.
501 ///
502 /// Default: true
503 pub ensure_final_newline_on_save: Option<bool>,
504 /// How line endings should be handled for new files and during format and
505 /// save operations.
506 ///
507 /// - `detect`: Detect existing line endings and otherwise use the platform
508 /// default (`lf` on Unix, `crlf` on Windows).
509 /// - `prefer_lf`: Prefer LF for new files and files with no existing line
510 /// ending.
511 /// - `prefer_crlf`: Prefer CRLF for new files and files with no existing
512 /// line ending.
513 /// - `enforce_lf`: Enforce LF during format and save.
514 /// - `enforce_crlf`: Enforce CRLF during format and save.
515 ///
516 /// The EditorConfig `end_of_line` property overrides this setting and
517 /// behaves like `enforce_lf` or `enforce_crlf`.
518 ///
519 /// Default: detect
520 pub line_ending: Option<LineEndingSetting>,
521 /// How to perform a buffer format.
522 ///
523 /// Default: auto
524 pub formatter: Option<FormatterList>,
525 /// Omega's Prettier integration settings.
526 /// Allows to enable/disable formatting with Prettier
527 /// and configure default Prettier, used when no project-level Prettier installation is found.
528 ///
529 /// Default: off
530 pub prettier: Option<PrettierSettingsContent>,
531 /// Whether to automatically close JSX tags.
532 pub jsx_tag_auto_close: Option<JsxTagAutoCloseSettingsContent>,
533 /// Whether to use language servers to provide code intelligence.
534 ///
535 /// Default: true
536 pub enable_language_server: Option<bool>,
537 /// The list of language servers to use (or disable) for this language.
538 ///
539 /// This array should consist of language server IDs, as well as the following
540 /// special tokens:
541 /// - `"!<language_server_id>"` - A language server ID prefixed with a `!` will be disabled.
542 /// - `"..."` - A placeholder to refer to the **rest** of the registered language servers for this language.
543 ///
544 /// Default: ["..."]
545 pub language_servers: Option<Vec<ConfiguredLanguageServer>>,
546 /// Controls how semantic tokens from language servers are used for syntax highlighting.
547 ///
548 /// Options:
549 /// - "off": Do not request semantic tokens from language servers.
550 /// - "combined": Use LSP semantic tokens together with tree-sitter highlighting.
551 /// - "full": Use LSP semantic tokens exclusively, replacing tree-sitter highlighting.
552 ///
553 /// Default: "off"
554 pub semantic_tokens: Option<SemanticTokens>,
555 /// Controls whether folding ranges from language servers are used instead of
556 /// tree-sitter and indent-based folding.
557 ///
558 /// Options:
559 /// - "off": Use tree-sitter and indent-based folding (default).
560 /// - "on": Use LSP folding wherever possible, falling back to tree-sitter and indent-based folding when no results were returned by the server.
561 ///
562 /// Default: "off"
563 pub document_folding_ranges: Option<DocumentFoldingRanges>,
564 /// Controls the source of document symbols used for outlines and breadcrumbs.
565 ///
566 /// Options:
567 /// - "off": Use tree-sitter queries to compute document symbols (default).
568 /// - "on": Use the language server's `textDocument/documentSymbol` LSP response. When enabled, tree-sitter is not used for document symbols.
569 ///
570 /// Default: "off"
571 pub document_symbols: Option<DocumentSymbols>,
572 /// Controls where the `editor::Rewrap` action is allowed for this language.
573 ///
574 /// Note: This setting has no effect in Vim mode, as rewrap is already
575 /// allowed everywhere.
576 ///
577 /// Default: "in_comments"
578 pub allow_rewrap: Option<RewrapBehavior>,
579 /// Controls whether edit predictions are shown immediately (true)
580 /// or manually by triggering `editor::ShowEditPrediction` (false).
581 ///
582 /// Default: true
583 pub show_edit_predictions: Option<bool>,
584 /// Controls whether edit predictions are shown in the given language
585 /// scopes.
586 ///
587 /// Example: ["string", "comment"]
588 ///
589 /// Default: []
590 pub edit_predictions_disabled_in: Option<Vec<String>>,
591 /// Whether to show tabs and spaces in the editor.
592 pub show_whitespaces: Option<ShowWhitespaceSetting>,
593 /// Visible characters used to render whitespace when show_whitespaces is enabled.
594 ///
595 /// Default: "•" for spaces, "→" for tabs.
596 pub whitespace_map: Option<WhitespaceMapContent>,
597 /// Whether to start a new line with a comment when a previous line is a comment as well.
598 ///
599 /// Default: true
600 pub extend_comment_on_newline: Option<bool>,
601 /// Whether to continue markdown lists when pressing enter.
602 ///
603 /// Default: true
604 pub extend_list_on_newline: Option<bool>,
605 /// Whether to indent list items when pressing tab after a list marker.
606 ///
607 /// Default: true
608 pub indent_list_on_tab: Option<bool>,
609 /// Inlay hint related settings.
610 pub inlay_hints: Option<InlayHintSettingsContent>,
611 /// Whether to automatically type closing characters for you. For example,
612 /// when you type '(', Omega will automatically add a closing ')' at the correct position.
613 ///
614 /// Default: true
615 pub use_autoclose: Option<bool>,
616 /// Whether to automatically surround text with characters for you. For example,
617 /// when you select text and type '(', Omega will automatically surround text with ().
618 ///
619 /// Default: true
620 pub use_auto_surround: Option<bool>,
621 /// Controls how the editor handles the autoclosed characters.
622 /// When set to `false`(default), skipping over and auto-removing of the closing characters
623 /// happen only for auto-inserted characters.
624 /// Otherwise(when `true`), the closing characters are always skipped over and auto-removed
625 /// no matter how they were inserted.
626 ///
627 /// Default: false
628 pub always_treat_brackets_as_autoclosed: Option<bool>,
629 /// Whether to use additional LSP queries to format (and amend) the code after
630 /// every "trigger" symbol input, defined by LSP server capabilities.
631 ///
632 /// Default: true
633 pub use_on_type_format: Option<bool>,
634 /// Which code actions to run on save before the formatter.
635 /// These are not run if formatting is off.
636 ///
637 /// Default: {} (or {"source.organizeImports": true} for Go).
638 pub code_actions_on_format: Option<HashMap<String, bool>>,
639 /// Whether to perform linked edits of associated ranges, if the language server supports it.
640 /// For example, when editing opening <html> tag, the contents of the closing </html> tag will be edited as well.
641 ///
642 /// Default: true
643 pub linked_edits: Option<bool>,
644 /// Controls automatic indentation behavior when typing.
645 ///
646 /// - "syntax_aware": Adjusts indentation based on syntax context (default)
647 /// - "preserve_indent": Preserves current line's indentation on new lines
648 /// - "none": No automatic indentation
649 ///
650 /// Default: syntax_aware
651 pub auto_indent: Option<AutoIndentMode>,
652 /// Whether indentation of pasted content should be adjusted based on the context.
653 ///
654 /// Default: true
655 pub auto_indent_on_paste: Option<bool>,
656 /// Task configuration for this language.
657 ///
658 /// Default: {}
659 pub tasks: Option<LanguageTaskSettingsContent>,
660 /// Whether to pop the completions menu while typing in an editor without
661 /// explicitly requesting it.
662 ///
663 /// Default: true
664 pub show_completions_on_input: Option<bool>,
665 /// Whether to display inline and alongside documentation for items in the
666 /// completions menu.
667 ///
668 /// Default: true
669 pub show_completion_documentation: Option<bool>,
670 /// Controls how completions are processed for this language.
671 pub completions: Option<CompletionSettingsContent>,
672 /// Preferred debuggers for this language.
673 ///
674 /// Default: []
675 pub debuggers: Option<Vec<String>>,
676 /// Whether to enable word diff highlighting in the editor.
677 ///
678 /// When enabled, changed words within modified lines are highlighted
679 /// to show exactly what changed.
680 ///
681 /// Default: true
682 pub word_diff_enabled: Option<bool>,
683 /// Whether to use tree-sitter bracket queries to detect and colorize the brackets in the editor.
684 ///
685 /// Default: false
686 pub colorize_brackets: Option<bool>,
687}
688
689/// Controls how whitespace should be displayedin the editor.
690#[derive(
691 Copy,
692 Clone,
693 Debug,
694 Serialize,
695 Deserialize,
696 PartialEq,
697 Eq,
698 JsonSchema,
699 MergeFrom,
700 strum::VariantArray,
701 strum::VariantNames,
702)]
703#[serde(rename_all = "snake_case")]
704pub enum ShowWhitespaceSetting {
705 /// Draw whitespace only for the selected text.
706 Selection,
707 /// Do not draw any tabs or spaces.
708 None,
709 /// Draw all invisible symbols.
710 All,
711 /// Draw whitespaces at boundaries only.
712 ///
713 /// For a whitespace to be on a boundary, any of the following conditions need to be met:
714 /// - It is a tab
715 /// - It is adjacent to an edge (start or end)
716 /// - It is adjacent to a whitespace (left or right)
717 Boundary,
718 /// Draw whitespaces only after non-whitespace characters.
719 Trailing,
720}
721
722#[with_fallible_options]
723#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)]
724pub struct WhitespaceMapContent {
725 pub space: Option<char>,
726 pub tab: Option<char>,
727}
728
729/// The behavior of `editor::Rewrap`.
730#[derive(
731 Debug,
732 PartialEq,
733 Clone,
734 Copy,
735 Default,
736 Serialize,
737 Deserialize,
738 JsonSchema,
739 MergeFrom,
740 strum::VariantArray,
741 strum::VariantNames,
742)]
743#[serde(rename_all = "snake_case")]
744pub enum RewrapBehavior {
745 /// Only rewrap within comments.
746 #[default]
747 InComments,
748 /// Only rewrap within the current selection(s).
749 InSelections,
750 /// Allow rewrapping anywhere.
751 Anywhere,
752}
753
754#[with_fallible_options]
755#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
756pub struct JsxTagAutoCloseSettingsContent {
757 /// Enables or disables auto-closing of JSX tags.
758 pub enabled: Option<bool>,
759}
760
761/// The settings for inlay hints.
762#[with_fallible_options]
763#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)]
764pub struct InlayHintSettingsContent {
765 /// Global switch to toggle hints on and off.
766 ///
767 /// Default: false
768 pub enabled: Option<bool>,
769 /// Global switch to toggle inline values on and off when debugging.
770 ///
771 /// Default: true
772 pub show_value_hints: Option<bool>,
773 /// Whether type hints should be shown.
774 ///
775 /// Default: true
776 pub show_type_hints: Option<bool>,
777 /// Whether parameter hints should be shown.
778 ///
779 /// Default: true
780 pub show_parameter_hints: Option<bool>,
781 /// Whether other hints should be shown.
782 ///
783 /// Default: true
784 pub show_other_hints: Option<bool>,
785 /// Whether to show a background for inlay hints.
786 ///
787 /// If set to `true`, the background will use the `hint.background` color
788 /// from the current theme.
789 ///
790 /// Default: false
791 pub show_background: Option<bool>,
792 /// Whether or not to debounce inlay hints updates after buffer edits.
793 ///
794 /// Set to 0 to disable debouncing.
795 ///
796 /// Default: 700
797 pub edit_debounce_ms: Option<u64>,
798 /// Whether or not to debounce inlay hints updates after buffer scrolls.
799 ///
800 /// Set to 0 to disable debouncing.
801 ///
802 /// Default: 50
803 pub scroll_debounce_ms: Option<u64>,
804 /// Toggles inlay hints (hides or shows) when the user presses the modifiers specified.
805 /// If only a subset of the modifiers specified is pressed, hints are not toggled.
806 /// If no modifiers are specified, this is equivalent to `null`.
807 ///
808 /// Default: null
809 pub toggle_on_modifiers_press: Option<ModifiersContent>,
810}
811
812/// The kind of an inlay hint.
813#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
814pub enum InlayHintKind {
815 /// An inlay hint for a type.
816 Type,
817 /// An inlay hint for a parameter.
818 Parameter,
819}
820
821impl InlayHintKind {
822 /// Returns the [`InlayHintKind`]fromthe given name.
823 ///
824 /// Returns `None` if `name` does not match any of the expected
825 /// string representations.
826 pub fn from_name(name: &str) -> Option<Self> {
827 match name {
828 "type" => Some(InlayHintKind::Type),
829 "parameter" => Some(InlayHintKind::Parameter),
830 _ => None,
831 }
832 }
833
834 /// Returns the name of this [`InlayHintKind`].
835 pub fn name(&self) -> &'static str {
836 match self {
837 InlayHintKind::Type => "type",
838 InlayHintKind::Parameter => "parameter",
839 }
840 }
841}
842
843/// Controls how completions are processed for this language.
844#[with_fallible_options]
845#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)]
846#[serde(rename_all = "snake_case")]
847pub struct CompletionSettingsContent {
848 /// Controls how words are completed.
849 /// For large documents, not all words may be fetched for completion.
850 ///
851 /// Default: `fallback`
852 pub words: Option<WordsCompletionMode>,
853 /// How many characters has to be in the completions query to automatically show the words-based completions.
854 /// Before that value, it's still possible to trigger the words-based completion manually with the corresponding editor command.
855 ///
856 /// Default: 3
857 pub words_min_length: Option<u32>,
858 /// Whether to fetch LSP completions or not.
859 ///
860 /// Default: true
861 pub lsp: Option<bool>,
862 /// When fetching LSP completions, determines how long to wait for a response of a particular server.
863 /// When set to 0, waits indefinitely.
864 ///
865 /// Default: 0
866 pub lsp_fetch_timeout_ms: Option<u64>,
867 /// Controls how LSP completions are inserted.
868 ///
869 /// Default: "replace_suffix"
870 pub lsp_insert_mode: Option<LspInsertMode>,
871}
872
873#[derive(
874 Copy,
875 Clone,
876 Debug,
877 Serialize,
878 Deserialize,
879 PartialEq,
880 Eq,
881 JsonSchema,
882 MergeFrom,
883 strum::VariantArray,
884 strum::VariantNames,
885)]
886#[serde(rename_all = "snake_case")]
887pub enum LspInsertMode {
888 /// Replaces text before the cursor, using the `insert` range described in the LSP specification.
889 Insert,
890 /// Replaces text before and after the cursor, using the `replace` range described in the LSP specification.
891 Replace,
892 /// Behaves like `"replace"` if the text that would be replaced is a subsequence of the completion text,
893 /// and like `"insert"` otherwise.
894 ReplaceSubsequence,
895 /// Behaves like `"replace"` if the text after the cursor is a suffix of the completion, and like
896 /// `"insert"` otherwise.
897 ReplaceSuffix,
898}
899
900/// Controls how document's words are completed.
901#[derive(
902 Copy,
903 Clone,
904 Debug,
905 Serialize,
906 Deserialize,
907 PartialEq,
908 Eq,
909 JsonSchema,
910 MergeFrom,
911 strum::VariantArray,
912 strum::VariantNames,
913)]
914#[serde(rename_all = "snake_case")]
915pub enum WordsCompletionMode {
916 /// Always fetch document's words for completions along with LSP completions.
917 Enabled,
918 /// Only if LSP response errors or times out,
919 /// use document's words to show completions.
920 Fallback,
921 /// Never fetch or complete document's words for completions.
922 /// (Word-based completions can still be queried via a separate action)
923 Disabled,
924}
925
926/// Allows to enable/disable formatting with Prettier
927/// and configure default Prettier, used when no project-level Prettier installation is found.
928/// Prettier formatting is disabled by default.
929#[with_fallible_options]
930#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
931pub struct PrettierSettingsContent {
932 /// Enables or disables formatting with Prettier for a given language.
933 pub allowed: Option<bool>,
934
935 /// Forces Prettier integration to use a specific parser name when formatting files with the language.
936 pub parser: Option<String>,
937
938 /// Forces Prettier integration to use specific plugins when formatting files with the language.
939 /// The default Prettier will be installed with these plugins.
940 pub plugins: Option<HashSet<String>>,
941
942 /// Default Prettier options, in the format as in package.json section for Prettier.
943 /// If project installs Prettier via its package.json, these options will be ignored.
944 #[serde(flatten)]
945 pub options: Option<HashMap<String, serde_json::Value>>,
946}
947
948/// TODO: this should just be a bool
949/// Controls the behavior of formatting files when they are saved.
950#[derive(
951 Debug,
952 Clone,
953 Copy,
954 PartialEq,
955 Eq,
956 Serialize,
957 Deserialize,
958 JsonSchema,
959 MergeFrom,
960 strum::VariantArray,
961 strum::VariantNames,
962)]
963#[serde(rename_all = "snake_case")]
964pub enum FormatOnSave {
965 /// Files should be formatted on save.
966 On,
967 /// Files should not be formatted on save.
968 Off,
969 /// Only lines with unstaged changes are formatted on save.
970 /// Requires source control and LSP range formatting support.
971 /// If no git diff is available or if the LSP doesn't support
972 /// range formatting, formatting is skipped.
973 Modifications,
974 /// Only lines with unstaged changes are formatted on save.
975 /// If no git diff is available (e.g., when source control is
976 /// unavailable) or if the LSP doesn't support range formatting,
977 /// falls back to formatting the whole file.
978 ModificationsIfAvailable,
979}
980
981/// Controls how line endings are normalized when a buffer is saved.
982#[derive(
983 Debug,
984 Clone,
985 Copy,
986 PartialEq,
987 Eq,
988 Serialize,
989 Deserialize,
990 JsonSchema,
991 MergeFrom,
992 strum::VariantArray,
993 strum::VariantNames,
994)]
995#[serde(rename_all = "snake_case")]
996pub enum LineEndingSetting {
997 /// Preserve the existing line endings of the file. New files use the
998 /// platform default line ending.
999 #[strum(serialize = "Detect")]
1000 Detect,
1001 /// Use LF for new files and files with no existing line-ending
1002 /// convention, while preserving existing LF or CRLF files.
1003 #[strum(serialize = "Prefer LF")]
1004 PreferLf,
1005 /// Use CRLF for new files and files with no existing line-ending
1006 /// convention, while preserving existing LF or CRLF files.
1007 #[strum(serialize = "Prefer CRLF")]
1008 PreferCrlf,
1009 /// Normalize line endings to LF (`\n`) during format and save.
1010 #[strum(serialize = "Enforce LF")]
1011 EnforceLf,
1012 /// Normalize line endings to CRLF (`\r\n`) during format and save.
1013 #[strum(serialize = "Enforce CRLF")]
1014 EnforceCrlf,
1015}
1016
1017/// Controls which formatters should be used when formatting code.
1018#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1019#[serde(untagged)]
1020pub enum FormatterList {
1021 Single(Formatter),
1022 Vec(Vec<Formatter>),
1023}
1024
1025impl Default for FormatterList {
1026 fn default() -> Self {
1027 Self::Single(Formatter::default())
1028 }
1029}
1030
1031impl AsRef<[Formatter]> for FormatterList {
1032 fn as_ref(&self) -> &[Formatter] {
1033 match &self {
1034 Self::Single(single) => std::slice::from_ref(single),
1035 Self::Vec(v) => v,
1036 }
1037 }
1038}
1039
1040/// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration.
1041#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1042#[serde(rename_all = "snake_case")]
1043pub enum Formatter {
1044 /// Format files using Omega's Prettier integration (if applicable),
1045 /// or falling back to formatting via language server.
1046 #[default]
1047 Auto,
1048 /// Do not format code.
1049 None,
1050 /// Format code using Omega's Prettier integration.
1051 Prettier,
1052 /// Format code using an external command.
1053 External {
1054 /// The external program to run.
1055 command: String,
1056 /// The arguments to pass to the program.
1057 arguments: Option<Vec<String>>,
1058 },
1059 /// Files should be formatted using a code action executed by language servers.
1060 CodeAction(String),
1061 /// Format code using a language server.
1062 #[serde(untagged)]
1063 LanguageServer(LanguageServerFormatterSpecifier),
1064}
1065
1066#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1067#[serde(
1068 rename_all = "snake_case",
1069 // allow specifying language servers as "language_server" or {"language_server": {"name": ...}}
1070 from = "LanguageServerVariantContent",
1071 into = "LanguageServerVariantContent"
1072)]
1073pub enum LanguageServerFormatterSpecifier {
1074 Specific {
1075 name: String,
1076 },
1077 #[default]
1078 Current,
1079}
1080
1081impl From<LanguageServerVariantContent> for LanguageServerFormatterSpecifier {
1082 fn from(value: LanguageServerVariantContent) -> Self {
1083 match value {
1084 LanguageServerVariantContent::Specific {
1085 language_server: LanguageServerSpecifierContent { name: Some(name) },
1086 } => Self::Specific { name },
1087 _ => Self::Current,
1088 }
1089 }
1090}
1091
1092impl From<LanguageServerFormatterSpecifier> for LanguageServerVariantContent {
1093 fn from(value: LanguageServerFormatterSpecifier) -> Self {
1094 match value {
1095 LanguageServerFormatterSpecifier::Specific { name } => Self::Specific {
1096 language_server: LanguageServerSpecifierContent { name: Some(name) },
1097 },
1098 LanguageServerFormatterSpecifier::Current => {
1099 Self::Current(CurrentLanguageServerContent::LanguageServer)
1100 }
1101 }
1102 }
1103}
1104
1105#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1106#[serde(rename_all = "snake_case", untagged)]
1107enum LanguageServerVariantContent {
1108 /// Format code using a specific language server.
1109 Specific {
1110 language_server: LanguageServerSpecifierContent,
1111 },
1112 /// Format code using the current language server.
1113 Current(CurrentLanguageServerContent),
1114}
1115
1116#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1117#[serde(rename_all = "snake_case")]
1118enum CurrentLanguageServerContent {
1119 #[default]
1120 LanguageServer,
1121}
1122
1123#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)]
1124#[serde(rename_all = "snake_case")]
1125struct LanguageServerSpecifierContent {
1126 /// The name of the language server to format with
1127 name: Option<String>,
1128}
1129
1130/// The settings for indent guides.
1131#[with_fallible_options]
1132#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)]
1133pub struct IndentGuideSettingsContent {
1134 /// Whether to display indent guides in the editor.
1135 ///
1136 /// Default: true
1137 pub enabled: Option<bool>,
1138 /// The width of the indent guides in pixels, between 1 and 10.
1139 ///
1140 /// Default: 1
1141 pub line_width: Option<u32>,
1142 /// The width of the active indent guide in pixels, between 1 and 10.
1143 ///
1144 /// Default: 1
1145 pub active_line_width: Option<u32>,
1146 /// Determines how indent guides are colored.
1147 ///
1148 /// Default: Fixed
1149 pub coloring: Option<IndentGuideColoring>,
1150 /// Determines how indent guide backgrounds are colored.
1151 ///
1152 /// Default: Disabled
1153 pub background_coloring: Option<IndentGuideBackgroundColoring>,
1154}
1155
1156/// The task settings for a particular language.
1157#[with_fallible_options]
1158#[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize, JsonSchema, MergeFrom)]
1159pub struct LanguageTaskSettingsContent {
1160 /// Extra task variables to set for a particular language.
1161 pub variables: Option<HashMap<String, String>>,
1162 pub enabled: Option<bool>,
1163 /// Use LSP tasks over Omega language extension ones.
1164 /// If no LSP tasks are returned due to error/timeout or regular execution,
1165 /// Omega language extension tasks will be used instead.
1166 ///
1167 /// Other Omega tasks will still be shown:
1168 /// * Omega task from either of the task config file
1169 /// * Omega task from history (e.g. one-off task was spawned before)
1170 pub prefer_lsp: Option<bool>,
1171}
1172
1173/// Map from language name to settings.
1174#[with_fallible_options]
1175#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
1176pub struct LanguageToSettingsMap(pub HashMap<String, LanguageSettingsContent>);
1177
1178/// Map from language name to file patterns.
1179#[with_fallible_options]
1180#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)]
1181pub struct FileTypeMap(pub HashMap<Arc<str>, ExtendingSet<String>>);
1182
1183impl<'a> IntoIterator for &'a FileTypeMap {
1184 type Item = (&'a Arc<str>, &'a ExtendingSet<String>);
1185 type IntoIter = std::collections::hash_map::Iter<'a, Arc<str>, ExtendingSet<String>>;
1186
1187 fn into_iter(self) -> Self::IntoIter {
1188 self.0.iter()
1189 }
1190}
1191
1192/// Determines how indent guides are colored.
1193#[derive(
1194 Default,
1195 Debug,
1196 Copy,
1197 Clone,
1198 PartialEq,
1199 Eq,
1200 Serialize,
1201 Deserialize,
1202 JsonSchema,
1203 MergeFrom,
1204 strum::VariantArray,
1205 strum::VariantNames,
1206)]
1207#[serde(rename_all = "snake_case")]
1208pub enum IndentGuideColoring {
1209 /// Do not render any lines for indent guides.
1210 Disabled,
1211 /// Use the same color for all indentation levels.
1212 #[default]
1213 Fixed,
1214 /// Use a different color for each indentation level.
1215 IndentAware,
1216}
1217
1218/// Determines how indent guide backgrounds are colored.
1219#[derive(
1220 Default,
1221 Debug,
1222 Copy,
1223 Clone,
1224 PartialEq,
1225 Eq,
1226 Serialize,
1227 Deserialize,
1228 JsonSchema,
1229 MergeFrom,
1230 strum::VariantArray,
1231 strum::VariantNames,
1232)]
1233#[serde(rename_all = "snake_case")]
1234pub enum IndentGuideBackgroundColoring {
1235 /// Do not render any background for indent guides.
1236 #[default]
1237 Disabled,
1238 /// Use a different color for each indentation level.
1239 IndentAware,
1240}
1241
1242#[cfg(test)]
1243mod test {
1244
1245 use crate::{ParseStatus, fallible_options, merge_from::MergeFrom};
1246
1247 use super::*;
1248
1249 #[test]
1250 fn test_formatter_deserialization() {
1251 let raw_auto = "{\"formatter\": \"auto\"}";
1252 let settings: LanguageSettingsContent = serde_json::from_str(raw_auto).unwrap();
1253 assert_eq!(
1254 settings.formatter,
1255 Some(FormatterList::Single(Formatter::Auto))
1256 );
1257 let raw_none = "{\"formatter\": \"none\"}";
1258 let settings: LanguageSettingsContent = serde_json::from_str(raw_none).unwrap();
1259 assert_eq!(
1260 settings.formatter,
1261 Some(FormatterList::Single(Formatter::None))
1262 );
1263 let raw = "{\"formatter\": \"language_server\"}";
1264 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1265 assert_eq!(
1266 settings.formatter,
1267 Some(FormatterList::Single(Formatter::LanguageServer(
1268 LanguageServerFormatterSpecifier::Current
1269 )))
1270 );
1271
1272 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}]}";
1273 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1274 assert_eq!(
1275 settings.formatter,
1276 Some(FormatterList::Vec(vec![Formatter::LanguageServer(
1277 LanguageServerFormatterSpecifier::Current
1278 )]))
1279 );
1280 let raw = "{\"formatter\": [{\"language_server\": {\"name\": null}}, \"language_server\", \"prettier\"]}";
1281 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1282 assert_eq!(
1283 settings.formatter,
1284 Some(FormatterList::Vec(vec![
1285 Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
1286 Formatter::LanguageServer(LanguageServerFormatterSpecifier::Current),
1287 Formatter::Prettier
1288 ]))
1289 );
1290
1291 let raw = "{\"formatter\": [{\"language_server\": {\"name\": \"ruff\"}}, \"prettier\"]}";
1292 let settings: LanguageSettingsContent = serde_json::from_str(raw).unwrap();
1293 assert_eq!(
1294 settings.formatter,
1295 Some(FormatterList::Vec(vec![
1296 Formatter::LanguageServer(LanguageServerFormatterSpecifier::Specific {
1297 name: "ruff".to_string()
1298 }),
1299 Formatter::Prettier
1300 ]))
1301 );
1302
1303 assert_eq!(
1304 serde_json::to_string(&LanguageServerFormatterSpecifier::Current).unwrap(),
1305 "\"language_server\"",
1306 );
1307 }
1308
1309 #[test]
1310 fn test_formatter_deserialization_invalid() {
1311 let raw_auto = "{\"formatter\": {}}";
1312 let (_, result) = fallible_options::parse_json::<LanguageSettingsContent>(raw_auto);
1313 assert!(matches!(result, ParseStatus::Failed { .. }));
1314 }
1315
1316 #[test]
1317 fn test_configured_language_server_serialization() {
1318 let enabled = ConfiguredLanguageServer::new("rust-analyzer");
1319 let disabled = ConfiguredLanguageServer::new_disabled("rust-analyzer");
1320
1321 assert_eq!(
1322 serde_json::to_string(&enabled).expect("enabled server should serialize"),
1323 "\"rust-analyzer\""
1324 );
1325 assert_eq!(
1326 serde_json::to_string(&disabled).expect("disabled server should serialize"),
1327 "\"!rust-analyzer\""
1328 );
1329 assert_eq!(
1330 serde_json::from_str::<ConfiguredLanguageServer>("\"rust-analyzer\"")
1331 .expect("enabled server should deserialize"),
1332 enabled
1333 );
1334 assert_eq!(
1335 serde_json::from_str::<ConfiguredLanguageServer>("\"!rust-analyzer\"")
1336 .expect("disabled server should deserialize"),
1337 disabled
1338 );
1339 }
1340
1341 #[test]
1342 fn test_language_servers_merge_keeps_per_language_lists_pure() {
1343 let default_typescript_servers = vec![
1344 ConfiguredLanguageServer::new_disabled("typescript-language-server"),
1345 ConfiguredLanguageServer::new("vtsls"),
1346 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1347 ];
1348 let mut base = AllLanguageSettingsContent {
1349 defaults: LanguageSettingsContent {
1350 language_servers: Some(vec![REST_OF_LANGUAGE_SERVERS.into()]),
1351 ..LanguageSettingsContent::default()
1352 },
1353 languages: LanguageToSettingsMap(
1354 [(
1355 "TypeScript".into(),
1356 LanguageSettingsContent {
1357 language_servers: Some(default_typescript_servers.clone()),
1358 ..LanguageSettingsContent::default()
1359 },
1360 )]
1361 .into_iter()
1362 .collect(),
1363 ),
1364 ..AllLanguageSettingsContent::default()
1365 };
1366
1367 let user = AllLanguageSettingsContent {
1368 defaults: LanguageSettingsContent {
1369 language_servers: Some(vec!["!vtsls".into(), REST_OF_LANGUAGE_SERVERS.into()]),
1370 ..LanguageSettingsContent::default()
1371 },
1372 ..AllLanguageSettingsContent::default()
1373 };
1374 base.merge_from(&user);
1375 assert_eq!(
1376 base.languages.0["TypeScript"].language_servers.as_ref(),
1377 Some(&default_typescript_servers),
1378 "a global list must not rewrite per-language lists"
1379 );
1380 assert_eq!(
1381 base.defaults.language_servers.as_ref(),
1382 Some(&vec![
1383 ConfiguredLanguageServer::new_disabled("vtsls"),
1384 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1385 ])
1386 );
1387
1388 let project = AllLanguageSettingsContent {
1389 languages: LanguageToSettingsMap(
1390 [(
1391 "TypeScript".into(),
1392 LanguageSettingsContent {
1393 language_servers: Some(vec![
1394 "vtsls".into(),
1395 REST_OF_LANGUAGE_SERVERS.into(),
1396 ]),
1397 ..LanguageSettingsContent::default()
1398 },
1399 )]
1400 .into_iter()
1401 .collect(),
1402 ),
1403 ..AllLanguageSettingsContent::default()
1404 };
1405 base.merge_from(&project);
1406 assert_eq!(
1407 base.languages.0["TypeScript"].language_servers.as_ref(),
1408 Some(&vec![
1409 ConfiguredLanguageServer::new("vtsls"),
1410 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1411 ]),
1412 "a per-language list must replace the older one wholesale"
1413 );
1414 }
1415
1416 #[test]
1417 fn test_language_servers_merge_no_per_language_config_stays_unset() {
1418 let mut base = AllLanguageSettingsContent {
1419 defaults: LanguageSettingsContent {
1420 language_servers: Some(vec![REST_OF_LANGUAGE_SERVERS.into()]),
1421 ..LanguageSettingsContent::default()
1422 },
1423 languages: LanguageToSettingsMap(
1424 [(
1425 "Rust".into(),
1426 LanguageSettingsContent {
1427 tab_size: Some(std::num::NonZeroU32::new(4).unwrap()),
1428 ..LanguageSettingsContent::default()
1429 },
1430 )]
1431 .into_iter()
1432 .collect(),
1433 ),
1434 ..AllLanguageSettingsContent::default()
1435 };
1436
1437 let user = AllLanguageSettingsContent {
1438 defaults: LanguageSettingsContent {
1439 language_servers: Some(vec!["!eslint".into(), REST_OF_LANGUAGE_SERVERS.into()]),
1440 ..LanguageSettingsContent::default()
1441 },
1442 ..AllLanguageSettingsContent::default()
1443 };
1444
1445 base.merge_from(&user);
1446
1447 assert_eq!(
1448 base.languages.0["Rust"].language_servers, None,
1449 "languages without their own list must not get a stale copy of the global one"
1450 );
1451 assert_eq!(
1452 base.defaults.language_servers.as_ref(),
1453 Some(&vec![
1454 ConfiguredLanguageServer::new_disabled("eslint"),
1455 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1456 ])
1457 );
1458 }
1459
1460 #[test]
1461 fn test_language_servers_merge_user_per_language_overrides() {
1462 let mut base = AllLanguageSettingsContent {
1463 defaults: LanguageSettingsContent {
1464 language_servers: Some(vec![REST_OF_LANGUAGE_SERVERS.into()]),
1465 ..LanguageSettingsContent::default()
1466 },
1467 languages: LanguageToSettingsMap(
1468 [(
1469 "TypeScript".into(),
1470 LanguageSettingsContent {
1471 language_servers: Some(vec![
1472 "!typescript-language-server".into(),
1473 "vtsls".into(),
1474 REST_OF_LANGUAGE_SERVERS.into(),
1475 ]),
1476 ..LanguageSettingsContent::default()
1477 },
1478 )]
1479 .into_iter()
1480 .collect(),
1481 ),
1482 ..AllLanguageSettingsContent::default()
1483 };
1484
1485 let user = AllLanguageSettingsContent {
1486 defaults: LanguageSettingsContent {
1487 language_servers: Some(vec!["!eslint".into(), REST_OF_LANGUAGE_SERVERS.into()]),
1488 ..LanguageSettingsContent::default()
1489 },
1490 languages: LanguageToSettingsMap(
1491 [(
1492 "TypeScript".into(),
1493 LanguageSettingsContent {
1494 language_servers: Some(vec![
1495 "deno".into(),
1496 "!vtsls".into(),
1497 REST_OF_LANGUAGE_SERVERS.into(),
1498 ]),
1499 ..LanguageSettingsContent::default()
1500 },
1501 )]
1502 .into_iter()
1503 .collect(),
1504 ),
1505 ..AllLanguageSettingsContent::default()
1506 };
1507
1508 base.merge_from(&user);
1509
1510 let ts_servers = base.languages.0["TypeScript"]
1511 .language_servers
1512 .as_ref()
1513 .unwrap();
1514 assert_eq!(
1515 ts_servers,
1516 &vec![
1517 ConfiguredLanguageServer::new("deno"),
1518 ConfiguredLanguageServer::new_disabled("vtsls"),
1519 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1520 ]
1521 );
1522 }
1523
1524 #[test]
1525 fn test_user_per_language_config_overrides_default_disables() {
1526 let mut base = AllLanguageSettingsContent {
1527 defaults: LanguageSettingsContent {
1528 language_servers: Some(vec![REST_OF_LANGUAGE_SERVERS.into()]),
1529 ..LanguageSettingsContent::default()
1530 },
1531 languages: LanguageToSettingsMap(
1532 [(
1533 "TypeScript".into(),
1534 LanguageSettingsContent {
1535 language_servers: Some(vec![
1536 "!typescript-language-server".into(),
1537 "vtsls".into(),
1538 REST_OF_LANGUAGE_SERVERS.into(),
1539 ]),
1540 ..LanguageSettingsContent::default()
1541 },
1542 )]
1543 .into_iter()
1544 .collect(),
1545 ),
1546 ..AllLanguageSettingsContent::default()
1547 };
1548
1549 let user = AllLanguageSettingsContent {
1550 languages: LanguageToSettingsMap(
1551 [(
1552 "TypeScript".into(),
1553 LanguageSettingsContent {
1554 language_servers: Some(vec![
1555 "typescript-language-server".into(),
1556 REST_OF_LANGUAGE_SERVERS.into(),
1557 ]),
1558 ..LanguageSettingsContent::default()
1559 },
1560 )]
1561 .into_iter()
1562 .collect(),
1563 ),
1564 ..AllLanguageSettingsContent::default()
1565 };
1566
1567 base.merge_from(&user);
1568
1569 let ts_servers = base.languages.0["TypeScript"]
1570 .language_servers
1571 .as_ref()
1572 .unwrap();
1573 assert_eq!(
1574 ts_servers,
1575 &vec![
1576 ConfiguredLanguageServer::new("typescript-language-server"),
1577 ConfiguredLanguageServer::new(REST_OF_LANGUAGE_SERVERS),
1578 ]
1579 );
1580 }
1581
1582 #[test]
1583 fn test_prettier_options() {
1584 let raw_prettier = r#"{"allowed": false, "tabWidth": 4, "semi": false}"#;
1585 let result = serde_json::from_str::<PrettierSettingsContent>(raw_prettier)
1586 .expect("Failed to parse prettier options");
1587 assert!(
1588 result
1589 .options
1590 .as_ref()
1591 .expect("options were flattened")
1592 .contains_key("semi")
1593 );
1594 assert!(
1595 result
1596 .options
1597 .as_ref()
1598 .expect("options were flattened")
1599 .contains_key("tabWidth")
1600 );
1601 }
1602}
1603