Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:52:00.178Z 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_config.rs

526 lines · 21.1 KB · rust
1use crate::LanguageName;
2use collections::{HashMap, HashSet, IndexSet};
3use gpui_shared_string::SharedString;
4use regex::Regex;
5use schemars::{JsonSchema, SchemaGenerator, json_schema};
6use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
7use std::{num::NonZeroU32, path::Path, sync::Arc};
8
9/// Controls the soft-wrapping behavior in the editor.
10#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
11#[serde(rename_all = "snake_case")]
12pub enum SoftWrap {
13    /// Prefer a single line generally, unless an overly long line is encountered.
14    None,
15    /// Deprecated: use None instead. Left to avoid breaking existing users' configs.
16    /// Prefer a single line generally, unless an overly long line is encountered.
17    PreferLine,
18    /// Soft wrap lines that exceed the editor width.
19    EditorWidth,
20    /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
21    #[serde(alias = "preferred_line_length")]
22    Bounded,
23}
24
25/// Top-level configuration for a language, typically loaded from a `config.toml`
26/// shipped alongside the grammar.
27#[derive(Clone, Debug, Deserialize, JsonSchema)]
28pub struct LanguageConfig {
29    /// Human-readable name of the language.
30    pub name: LanguageName,
31    /// The name of this language for a Markdown code fence block
32    pub code_fence_block_name: Option<Arc<str>>,
33    /// Alternative language names that Jupyter kernels may report for this language.
34    /// Used when a kernel's `language` field differs from Omega's language name.
35    /// For example, the Nu extension would set this to `["nushell"]`.
36    #[serde(default)]
37    pub kernel_language_names: Vec<Arc<str>>,
38    // The name of the grammar in a WASM bundle (experimental).
39    pub grammar: Option<Arc<str>>,
40    /// The criteria for matching this language to a given file.
41    #[serde(flatten)]
42    pub matcher: Arc<LanguageMatcher>,
43    /// List of bracket types in a language.
44    #[serde(default)]
45    pub brackets: BracketPairConfig,
46    /// If set to true, auto indentation uses last non empty line to determine
47    /// the indentation level for a new line.
48    #[serde(default = "default_true")]
49    pub auto_indent_using_last_non_empty_line: bool,
50    // Whether indentation of pasted content should be adjusted based on the context.
51    #[serde(default)]
52    pub auto_indent_on_paste: Option<bool>,
53    /// A regex that is used to determine whether the indentation level should be
54    /// increased in the following line.
55    #[serde(default, deserialize_with = "deserialize_regex")]
56    #[schemars(schema_with = "regex_json_schema")]
57    pub increase_indent_pattern: Option<Regex>,
58    /// A regex that is used to determine whether the indentation level should be
59    /// decreased in the following line.
60    #[serde(default, deserialize_with = "deserialize_regex")]
61    #[schemars(schema_with = "regex_json_schema")]
62    pub decrease_indent_pattern: Option<Regex>,
63    /// A list of rules for decreasing indentation. Each rule pairs a regex with a set of valid
64    /// "block-starting" tokens. When a line matches a pattern, its indentation is aligned with
65    /// the most recent line that began with a corresponding token. This enables context-aware
66    /// outdenting, like aligning an `else` with its `if`.
67    #[serde(default)]
68    pub decrease_indent_patterns: Vec<DecreaseIndentConfig>,
69    /// A list of characters that trigger the automatic insertion of a closing
70    /// bracket when they immediately precede the point where an opening
71    /// bracket is inserted.
72    #[serde(default)]
73    pub autoclose_before: String,
74    /// A placeholder used internally by Semantic Index.
75    #[serde(default)]
76    pub collapsed_placeholder: String,
77    /// A line comment string that is inserted in e.g. `toggle comments` action.
78    /// A language can have multiple flavours of line comments. All of the provided line comments are
79    /// used for comment continuations on the next line, but only the first one is used for Editor::ToggleComments.
80    #[serde(default)]
81    pub line_comments: Vec<Arc<str>>,
82    /// Delimiters and configuration for recognizing and formatting block comments.
83    #[serde(default)]
84    pub block_comment: Option<BlockCommentConfig>,
85    /// Delimiters and configuration for recognizing and formatting documentation comments.
86    #[serde(default, alias = "documentation")]
87    pub documentation_comment: Option<BlockCommentConfig>,
88    /// List markers that are inserted unchanged on newline (e.g., `- `, `* `, `+ `).
89    #[serde(default)]
90    pub unordered_list: Vec<Arc<str>>,
91    /// Configuration for ordered lists with auto-incrementing numbers on newline (e.g., `1. ` becomes `2. `).
92    #[serde(default)]
93    pub ordered_list: Vec<OrderedListConfig>,
94    /// Configuration for task lists where multiple markers map to a single continuation prefix (e.g., `- [x] ` continues as `- [ ] `).
95    #[serde(default)]
96    pub task_list: Option<TaskListConfig>,
97    /// A list of additional regex patterns that should be treated as prefixes
98    /// for creating boundaries during rewrapping, ensuring content from one
99    /// prefixed section doesn't merge with another (e.g., markdown list items).
100    /// By default, Omega treats as paragraph and comment prefixes as boundaries.
101    #[serde(default, deserialize_with = "deserialize_regex_vec")]
102    #[schemars(schema_with = "regex_vec_json_schema")]
103    pub rewrap_prefixes: Vec<Regex>,
104    /// A list of language servers that are allowed to run on subranges of a given language.
105    #[serde(default)]
106    pub scope_opt_in_language_servers: Vec<SharedString>,
107    #[serde(default)]
108    pub overrides: HashMap<String, LanguageConfigOverride>,
109    /// A list of characters that Omega should treat as word characters for the
110    /// purpose of features that operate on word boundaries, like 'move to next word end'
111    /// or a whole-word search in buffer search.
112    #[serde(default)]
113    pub word_characters: HashSet<char>,
114    /// Whether to indent lines using tab characters, as opposed to multiple
115    /// spaces.
116    #[serde(default)]
117    pub hard_tabs: Option<bool>,
118    /// How many columns a tab should occupy.
119    #[serde(default)]
120    #[schemars(range(min = 1, max = 128))]
121    pub tab_size: Option<NonZeroU32>,
122    /// How to soft-wrap long lines of text.
123    #[serde(default)]
124    pub soft_wrap: Option<SoftWrap>,
125    /// When set, selections can be wrapped using prefix/suffix pairs on both sides.
126    #[serde(default)]
127    pub wrap_characters: Option<WrapCharactersConfig>,
128    /// The name of a Prettier parser that will be used for this language when no file path is available.
129    /// If there's a parser name in the language settings, that will be used instead.
130    #[serde(default)]
131    pub prettier_parser_name: Option<String>,
132    /// If true, this language is only for syntax highlighting via an injection into other
133    /// languages, but should not appear to the user as a distinct language.
134    #[serde(default)]
135    pub hidden: bool,
136    /// If configured, this language contains JSX style tags, and should support auto-closing of those tags.
137    #[serde(default)]
138    pub jsx_tag_auto_close: Option<JsxTagAutoCloseConfig>,
139    /// A list of characters that Omega should treat as word characters for completion queries.
140    #[serde(default)]
141    pub completion_query_characters: HashSet<char>,
142    /// A list of characters that Omega should treat as word characters for linked edit operations.
143    #[serde(default)]
144    pub linked_edit_characters: HashSet<char>,
145    /// A list of preferred debuggers for this language.
146    #[serde(default)]
147    pub debuggers: IndexSet<SharedString>,
148}
149
150impl LanguageConfig {
151    pub const FILE_NAME: &str = "config.toml";
152
153    pub fn load(config_path: impl AsRef<Path>) -> anyhow::Result<Self> {
154        let config = std::fs::read_to_string(config_path.as_ref())?;
155        toml::from_str(&config).map_err(Into::into)
156    }
157}
158
159impl Default for LanguageConfig {
160    fn default() -> Self {
161        Self {
162            name: LanguageName::new_static(""),
163            code_fence_block_name: None,
164            kernel_language_names: Default::default(),
165            grammar: None,
166            matcher: Arc::default(),
167            brackets: Default::default(),
168            auto_indent_using_last_non_empty_line: default_true(),
169            auto_indent_on_paste: None,
170            increase_indent_pattern: Default::default(),
171            decrease_indent_pattern: Default::default(),
172            decrease_indent_patterns: Default::default(),
173            autoclose_before: Default::default(),
174            line_comments: Default::default(),
175            block_comment: Default::default(),
176            documentation_comment: Default::default(),
177            unordered_list: Default::default(),
178            ordered_list: Default::default(),
179            task_list: Default::default(),
180            rewrap_prefixes: Default::default(),
181            scope_opt_in_language_servers: Default::default(),
182            overrides: Default::default(),
183            word_characters: Default::default(),
184            collapsed_placeholder: Default::default(),
185            hard_tabs: None,
186            tab_size: None,
187            soft_wrap: None,
188            wrap_characters: None,
189            prettier_parser_name: None,
190            hidden: false,
191            jsx_tag_auto_close: None,
192            completion_query_characters: Default::default(),
193            linked_edit_characters: Default::default(),
194            debuggers: Default::default(),
195        }
196    }
197}
198
199#[derive(Clone, Debug, Deserialize, Default, JsonSchema)]
200pub struct DecreaseIndentConfig {
201    #[serde(default, deserialize_with = "deserialize_regex")]
202    #[schemars(schema_with = "regex_json_schema")]
203    pub pattern: Option<Regex>,
204    #[serde(default)]
205    pub valid_after: Vec<String>,
206}
207
208/// Configuration for continuing ordered lists with auto-incrementing numbers.
209#[derive(Clone, Debug, Deserialize, JsonSchema)]
210pub struct OrderedListConfig {
211    /// A regex pattern with a capture group for the number portion (e.g., `(\\d+)\\. `).
212    pub pattern: String,
213    /// A format string where `{1}` is replaced with the incremented number (e.g., `{1}. `).
214    pub format: String,
215}
216
217/// Configuration for continuing task lists on newline.
218#[derive(Clone, Debug, Deserialize, JsonSchema)]
219pub struct TaskListConfig {
220    /// The list markers to match (e.g., `- [ ] `, `- [x] `).
221    pub prefixes: Vec<Arc<str>>,
222    /// The marker to insert when continuing the list on a new line (e.g., `- [ ] `).
223    pub continuation: Arc<str>,
224}
225
226#[derive(Debug, Serialize, Deserialize, Default, JsonSchema)]
227pub struct LanguageMatcher {
228    /// Given a list of `LanguageConfig`'s, the language of a file can be determined based on the path extension matching any of the `path_suffixes`.
229    #[serde(default)]
230    pub path_suffixes: Vec<String>,
231    /// A regex pattern that determines whether the language should be assigned to a file or not.
232    #[serde(
233        default,
234        serialize_with = "serialize_regex",
235        deserialize_with = "deserialize_regex"
236    )]
237    #[schemars(schema_with = "regex_json_schema")]
238    pub first_line_pattern: Option<Regex>,
239    /// Alternative names for this language used in vim/emacs modelines.
240    /// These are matched case-insensitively against the `mode` (emacs) or
241    /// `filetype`/`ft` (vim) specified in the modeline.
242    #[serde(default)]
243    pub modeline_aliases: Vec<String>,
244}
245
246impl Ord for LanguageMatcher {
247    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
248        self.path_suffixes
249            .cmp(&other.path_suffixes)
250            .then_with(|| {
251                self.first_line_pattern
252                    .as_ref()
253                    .map(Regex::as_str)
254                    .cmp(&other.first_line_pattern.as_ref().map(Regex::as_str))
255            })
256            .then_with(|| self.modeline_aliases.cmp(&other.modeline_aliases))
257    }
258}
259
260impl PartialOrd for LanguageMatcher {
261    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
262        Some(self.cmp(other))
263    }
264}
265
266impl Eq for LanguageMatcher {}
267
268impl PartialEq for LanguageMatcher {
269    fn eq(&self, other: &Self) -> bool {
270        self.path_suffixes == other.path_suffixes
271            && self.first_line_pattern.as_ref().map(Regex::as_str)
272                == other.first_line_pattern.as_ref().map(Regex::as_str)
273            && self.modeline_aliases == other.modeline_aliases
274    }
275}
276
277/// The configuration for JSX tag auto-closing.
278#[derive(Clone, Deserialize, JsonSchema, Debug)]
279pub struct JsxTagAutoCloseConfig {
280    /// The name of the node for a opening tag
281    pub open_tag_node_name: String,
282    /// The name of the node for an closing tag
283    pub close_tag_node_name: String,
284    /// The name of the node for a complete element with children for open and close tags
285    pub jsx_element_node_name: String,
286    /// The name of the node found within both opening and closing
287    /// tags that describes the tag name
288    pub tag_name_node_name: String,
289    /// Alternate Node names for tag names.
290    /// Specifically needed as TSX represents the name in `<Foo.Bar>`
291    /// as `member_expression` rather than `identifier` as usual
292    #[serde(default)]
293    pub tag_name_node_name_alternates: Vec<String>,
294    /// Some grammars are smart enough to detect a closing tag
295    /// that is not valid i.e. doesn't match it's corresponding
296    /// opening tag or does not have a corresponding opening tag
297    /// This should be set to the name of the node for invalid
298    /// closing tags if the grammar contains such a node, otherwise
299    /// detecting already closed tags will not work properly
300    #[serde(default)]
301    pub erroneous_close_tag_node_name: Option<String>,
302    /// See above for erroneous_close_tag_node_name for details
303    /// This should be set if the node used for the tag name
304    /// within erroneous closing tags is different from the
305    /// normal tag name node name
306    #[serde(default)]
307    pub erroneous_close_tag_name_node_name: Option<String>,
308}
309
310/// The configuration for block comments for this language.
311#[derive(Clone, Debug, JsonSchema, PartialEq)]
312pub struct BlockCommentConfig {
313    /// A start tag of block comment.
314    pub start: Arc<str>,
315    /// A end tag of block comment.
316    pub end: Arc<str>,
317    /// A character to add as a prefix when a new line is added to a block comment.
318    pub prefix: Arc<str>,
319    /// A indent to add for prefix and end line upon new line.
320    #[schemars(range(min = 1, max = 128))]
321    pub tab_size: u32,
322}
323
324impl<'de> Deserialize<'de> for BlockCommentConfig {
325    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
326    where
327        D: Deserializer<'de>,
328    {
329        #[derive(Deserialize)]
330        #[serde(untagged)]
331        enum BlockCommentConfigHelper {
332            New {
333                start: Arc<str>,
334                end: Arc<str>,
335                prefix: Arc<str>,
336                tab_size: u32,
337            },
338            Old([Arc<str>; 2]),
339        }
340
341        match BlockCommentConfigHelper::deserialize(deserializer)? {
342            BlockCommentConfigHelper::New {
343                start,
344                end,
345                prefix,
346                tab_size,
347            } => Ok(BlockCommentConfig {
348                start,
349                end,
350                prefix,
351                tab_size,
352            }),
353            BlockCommentConfigHelper::Old([start, end]) => Ok(BlockCommentConfig {
354                start,
355                end,
356                prefix: "".into(),
357                tab_size: 0,
358            }),
359        }
360    }
361}
362
363#[derive(Clone, Deserialize, Default, Debug, JsonSchema)]
364pub struct LanguageConfigOverride {
365    #[serde(default)]
366    pub line_comments: Override<Vec<Arc<str>>>,
367    #[serde(default)]
368    pub block_comment: Override<BlockCommentConfig>,
369    #[serde(skip)]
370    pub disabled_bracket_ixs: Vec<u16>,
371    #[serde(default)]
372    pub word_characters: Override<HashSet<char>>,
373    #[serde(default)]
374    pub completion_query_characters: Override<HashSet<char>>,
375    #[serde(default)]
376    pub linked_edit_characters: Override<HashSet<char>>,
377    #[serde(default)]
378    pub opt_into_language_servers: Vec<SharedString>,
379    #[serde(default)]
380    pub prefer_label_for_snippet: Option<bool>,
381}
382
383#[derive(Clone, Deserialize, Debug, Serialize, JsonSchema)]
384#[serde(untagged)]
385pub enum Override<T> {
386    Remove { remove: bool },
387    Set(T),
388}
389
390impl<T> Default for Override<T> {
391    fn default() -> Self {
392        Override::Remove { remove: false }
393    }
394}
395
396impl<T> Override<T> {
397    pub fn as_option<'a>(this: Option<&'a Self>, original: Option<&'a T>) -> Option<&'a T> {
398        match this {
399            Some(Self::Set(value)) => Some(value),
400            Some(Self::Remove { remove: true }) => None,
401            Some(Self::Remove { remove: false }) | None => original,
402        }
403    }
404}
405
406/// Configuration of handling bracket pairs for a given language.
407///
408/// This struct includes settings for defining which pairs of characters are considered brackets and
409/// also specifies any language-specific scopes where these pairs should be ignored for bracket matching purposes.
410#[derive(Clone, Debug, Default, JsonSchema)]
411#[schemars(with = "Vec::<BracketPairContent>")]
412pub struct BracketPairConfig {
413    /// A list of character pairs that should be treated as brackets in the context of a given language.
414    pub pairs: Vec<BracketPair>,
415    /// A list of tree-sitter scopes for which a given bracket should not be active.
416    /// N-th entry in `[Self::disabled_scopes_by_bracket_ix]` contains a list of disabled scopes for an n-th entry in `[Self::pairs]`
417    pub disabled_scopes_by_bracket_ix: Vec<Vec<String>>,
418}
419
420impl BracketPairConfig {
421    pub fn is_closing_brace(&self, c: char) -> bool {
422        self.pairs.iter().any(|pair| pair.end.starts_with(c))
423    }
424}
425
426#[derive(Deserialize, JsonSchema)]
427pub struct BracketPairContent {
428    #[serde(flatten)]
429    pub bracket_pair: BracketPair,
430    #[serde(default)]
431    pub not_in: Vec<String>,
432}
433
434impl<'de> Deserialize<'de> for BracketPairConfig {
435    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
436    where
437        D: Deserializer<'de>,
438    {
439        let result = Vec::<BracketPairContent>::deserialize(deserializer)?;
440        let (brackets, disabled_scopes_by_bracket_ix) = result
441            .into_iter()
442            .map(|entry| (entry.bracket_pair, entry.not_in))
443            .unzip();
444
445        Ok(BracketPairConfig {
446            pairs: brackets,
447            disabled_scopes_by_bracket_ix,
448        })
449    }
450}
451
452/// Describes a single bracket pair and how an editor should react to e.g. inserting
453/// an opening bracket or to a newline character insertion in between `start` and `end` characters.
454#[derive(Clone, Debug, Default, Deserialize, PartialEq, JsonSchema)]
455pub struct BracketPair {
456    /// Starting substring for a bracket.
457    pub start: String,
458    /// Ending substring for a bracket.
459    pub end: String,
460    /// True if `end` should be automatically inserted right after `start` characters.
461    pub close: bool,
462    /// True if selected text should be surrounded by `start` and `end` characters.
463    #[serde(default = "default_true")]
464    pub surround: bool,
465    /// True if an extra newline should be inserted while the cursor is in the middle
466    /// of that bracket pair.
467    pub newline: bool,
468}
469
470#[derive(Clone, Debug, Deserialize, JsonSchema)]
471pub struct WrapCharactersConfig {
472    /// Opening token split into a prefix and suffix. The first caret goes
473    /// after the prefix (i.e., between prefix and suffix).
474    pub start_prefix: String,
475    pub start_suffix: String,
476    /// Closing token split into a prefix and suffix. The second caret goes
477    /// after the prefix (i.e., between prefix and suffix).
478    pub end_prefix: String,
479    pub end_suffix: String,
480}
481
482pub fn default_true() -> bool {
483    true
484}
485
486pub fn deserialize_regex<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Regex>, D::Error> {
487    let source = Option::<String>::deserialize(d)?;
488    if let Some(source) = source {
489        Ok(Some(regex::Regex::new(&source).map_err(de::Error::custom)?))
490    } else {
491        Ok(None)
492    }
493}
494
495pub fn regex_json_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
496    json_schema!({
497        "type": "string"
498    })
499}
500
501pub fn serialize_regex<S>(regex: &Option<Regex>, serializer: S) -> Result<S::Ok, S::Error>
502where
503    S: Serializer,
504{
505    match regex {
506        Some(regex) => serializer.serialize_str(regex.as_str()),
507        None => serializer.serialize_none(),
508    }
509}
510
511pub fn deserialize_regex_vec<'de, D: Deserializer<'de>>(d: D) -> Result<Vec<Regex>, D::Error> {
512    let sources = Vec::<String>::deserialize(d)?;
513    sources
514        .into_iter()
515        .map(|source| regex::Regex::new(&source))
516        .collect::<Result<_, _>>()
517        .map_err(de::Error::custom)
518}
519
520pub fn regex_vec_json_schema(_: &mut SchemaGenerator) -> schemars::Schema {
521    json_schema!({
522        "type": "array",
523        "items": { "type": "string" }
524    })
525}
526
Served at tenant.openagents/omega Member data and write actions are omitted.