Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:35:09.982Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

language.rs

1603 lines · 54.6 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.