Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T02:59:22.692Z 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

grammar.rs

771 lines · 25.5 KB · rust
1use crate::{
2    HighlightId, HighlightMap, LanguageConfig, LanguageConfigOverride, LanguageName,
3    LanguageQueries, language_config::BracketPairConfig,
4};
5use anyhow::{Context as _, Result};
6use collections::HashMap;
7use gpui_shared_string::SharedString;
8use parking_lot::Mutex;
9use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
10use tree_sitter::Query;
11
12pub static NEXT_GRAMMAR_ID: AtomicUsize = AtomicUsize::new(0);
13
14#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
15pub struct GrammarId(pub usize);
16
17impl GrammarId {
18    pub fn new() -> Self {
19        Self(NEXT_GRAMMAR_ID.fetch_add(1, SeqCst))
20    }
21}
22
23impl Default for GrammarId {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29pub struct Grammar {
30    id: GrammarId,
31    pub ts_language: tree_sitter::Language,
32    pub error_query: Option<Query>,
33    pub highlights_config: Option<HighlightsConfig>,
34    pub brackets_config: Option<BracketsConfig>,
35    pub redactions_config: Option<RedactionConfig>,
36    pub runnable_config: Option<RunnableConfig>,
37    pub indents_config: Option<IndentConfig>,
38    pub outline_config: Option<OutlineConfig>,
39    pub text_object_config: Option<TextObjectConfig>,
40    pub injection_config: Option<InjectionConfig>,
41    pub override_config: Option<OverrideConfig>,
42    pub debug_variables_config: Option<DebugVariablesConfig>,
43    pub highlight_map: Mutex<HighlightMap>,
44}
45
46pub struct HighlightsConfig {
47    pub query: Query,
48    pub identifier_capture_indices: Vec<u32>,
49}
50
51pub struct IndentConfig {
52    pub query: Query,
53    pub indent_capture_ix: u32,
54    pub start_capture_ix: Option<u32>,
55    pub end_capture_ix: Option<u32>,
56    pub outdent_capture_ix: Option<u32>,
57    pub suffixed_start_captures: HashMap<u32, SharedString>,
58}
59
60pub struct OutlineConfig {
61    pub query: Query,
62    pub item_capture_ix: u32,
63    pub name_capture_ix: u32,
64    pub context_capture_ix: Option<u32>,
65    pub extra_context_capture_ix: Option<u32>,
66    pub open_capture_ix: Option<u32>,
67    pub close_capture_ix: Option<u32>,
68    pub annotation_capture_ix: Option<u32>,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq)]
72pub enum DebuggerTextObject {
73    Variable,
74    Scope,
75}
76
77impl DebuggerTextObject {
78    pub fn from_capture_name(name: &str) -> Option<DebuggerTextObject> {
79        match name {
80            "debug-variable" => Some(DebuggerTextObject::Variable),
81            "debug-scope" => Some(DebuggerTextObject::Scope),
82            _ => None,
83        }
84    }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq)]
88pub enum TextObject {
89    InsideFunction,
90    AroundFunction,
91    InsideClass,
92    AroundClass,
93    InsideComment,
94    AroundComment,
95}
96
97impl TextObject {
98    pub fn from_capture_name(name: &str) -> Option<TextObject> {
99        match name {
100            "function.inside" => Some(TextObject::InsideFunction),
101            "function.around" => Some(TextObject::AroundFunction),
102            "class.inside" => Some(TextObject::InsideClass),
103            "class.around" => Some(TextObject::AroundClass),
104            "comment.inside" => Some(TextObject::InsideComment),
105            "comment.around" => Some(TextObject::AroundComment),
106            _ => None,
107        }
108    }
109
110    pub fn around(&self) -> Option<Self> {
111        match self {
112            TextObject::InsideFunction => Some(TextObject::AroundFunction),
113            TextObject::InsideClass => Some(TextObject::AroundClass),
114            TextObject::InsideComment => Some(TextObject::AroundComment),
115            _ => None,
116        }
117    }
118}
119
120pub struct TextObjectConfig {
121    pub query: Query,
122    pub text_objects_by_capture_ix: Vec<(u32, TextObject)>,
123}
124
125pub struct InjectionConfig {
126    pub query: Query,
127    pub content_capture_ix: u32,
128    pub language_capture_ix: Option<u32>,
129    pub patterns: Vec<InjectionPatternConfig>,
130}
131
132pub struct RedactionConfig {
133    pub query: Query,
134    pub redaction_capture_ix: u32,
135}
136
137#[derive(Clone, Debug, PartialEq)]
138pub enum RunnableCapture {
139    /// `@run` — marks the node that identifies the runnable (e.g. the test
140    /// function name, the subtest string literal).
141    Run,
142    /// `@run_item` — marks one candidate runnable inside a larger grammar
143    /// match. Used by the per-match dispatch pipeline so a single tree-sitter
144    /// match can produce multiple runnables (e.g. Go table-test rows).
145    RunItem,
146    /// Any other named capture, exposed by name to language resolvers and used
147    /// to populate `RunnableRange::extra_captures`.
148    Named(SharedString),
149}
150
151pub struct RunnableConfig {
152    pub query: Query,
153    /// A mapping from capture index to capture kind
154    pub extra_captures: Vec<RunnableCapture>,
155    /// `true` if the query uses `@run_item`, the marker for matches that
156    /// emit multiple runnables.
157    pub supports_grouped_runnables: bool,
158}
159
160pub struct OverrideConfig {
161    pub query: Query,
162    pub values: HashMap<u32, OverrideEntry>,
163}
164
165#[derive(Debug)]
166pub struct OverrideEntry {
167    pub name: String,
168    pub range_is_inclusive: bool,
169    pub value: LanguageConfigOverride,
170}
171
172#[derive(Default, Clone)]
173pub struct InjectionPatternConfig {
174    pub language: Option<Box<str>>,
175    pub combined: bool,
176}
177
178#[derive(Debug)]
179pub struct BracketsConfig {
180    pub query: Query,
181    pub open_capture_ix: u32,
182    pub close_capture_ix: u32,
183    pub patterns: Vec<BracketsPatternConfig>,
184}
185
186#[derive(Clone, Debug, Default)]
187pub struct BracketsPatternConfig {
188    pub newline_only: bool,
189    pub rainbow_exclude: bool,
190}
191
192pub struct DebugVariablesConfig {
193    pub query: Query,
194    pub objects_by_capture_ix: Vec<(u32, DebuggerTextObject)>,
195}
196
197enum Capture<'a> {
198    Required(&'static str, &'a mut u32),
199    Optional(&'static str, &'a mut Option<u32>),
200}
201
202fn populate_capture_indices(
203    query: &Query,
204    language_name: &LanguageName,
205    query_type: &str,
206    expected_prefixes: &[&str],
207    captures: &mut [Capture<'_>],
208) -> bool {
209    let mut found_required_indices = Vec::new();
210    'outer: for (ix, name) in query.capture_names().iter().enumerate() {
211        for (required_ix, capture) in captures.iter_mut().enumerate() {
212            match capture {
213                Capture::Required(capture_name, index) if capture_name == name => {
214                    **index = ix as u32;
215                    found_required_indices.push(required_ix);
216                    continue 'outer;
217                }
218                Capture::Optional(capture_name, index) if capture_name == name => {
219                    **index = Some(ix as u32);
220                    continue 'outer;
221                }
222                _ => {}
223            }
224        }
225        if !name.starts_with("_")
226            && !expected_prefixes
227                .iter()
228                .any(|&prefix| name.starts_with(prefix))
229        {
230            log::warn!(
231                "unrecognized capture name '{}' in {} {} TreeSitter query \
232                (suppress this warning by prefixing with '_')",
233                name,
234                language_name,
235                query_type
236            );
237        }
238    }
239    let mut missing_required_captures = Vec::new();
240    for (capture_ix, capture) in captures.iter().enumerate() {
241        if let Capture::Required(capture_name, _) = capture
242            && !found_required_indices.contains(&capture_ix)
243        {
244            missing_required_captures.push(*capture_name);
245        }
246    }
247    let success = missing_required_captures.is_empty();
248    if !success {
249        log::error!(
250            "missing required capture(s) in {} {} TreeSitter query: {}",
251            language_name,
252            query_type,
253            missing_required_captures.join(", ")
254        );
255    }
256    success
257}
258
259impl Grammar {
260    pub fn new(ts_language: tree_sitter::Language) -> Self {
261        Self {
262            id: GrammarId::new(),
263            highlights_config: None,
264            brackets_config: None,
265            outline_config: None,
266            text_object_config: None,
267            indents_config: None,
268            injection_config: None,
269            override_config: None,
270            redactions_config: None,
271            runnable_config: None,
272            error_query: Query::new(&ts_language, "(ERROR) @error").ok(),
273            debug_variables_config: None,
274            ts_language,
275            highlight_map: Default::default(),
276        }
277    }
278
279    pub fn id(&self) -> GrammarId {
280        self.id
281    }
282
283    pub fn highlight_map(&self) -> HighlightMap {
284        self.highlight_map.lock().clone()
285    }
286
287    pub fn highlight_id_for_name(&self, name: &str) -> Option<HighlightId> {
288        self.highlights_config
289            .as_ref()?
290            .query
291            .capture_index_for_name(name)
292            .and_then(|capture_id| self.highlight_map.lock().get(capture_id))
293    }
294
295    pub fn debug_variables_config(&self) -> Option<&DebugVariablesConfig> {
296        self.debug_variables_config.as_ref()
297    }
298
299    /// Load all queries from `LanguageQueries` into this grammar, mutating the
300    /// associated `LanguageConfig` (the override query clears
301    /// `brackets.disabled_scopes_by_bracket_ix`).
302    pub fn with_queries(
303        mut self,
304        queries: LanguageQueries,
305        config: &mut LanguageConfig,
306    ) -> Result<Self> {
307        let name = &config.name;
308        if let Some(query) = queries.highlights {
309            self = self
310                .with_highlights_query(query.as_ref())
311                .context("Error loading highlights query")?;
312        }
313        if let Some(query) = queries.brackets {
314            self = self
315                .with_brackets_query(query.as_ref(), name)
316                .context("Error loading brackets query")?;
317        }
318        if let Some(query) = queries.indents {
319            self = self
320                .with_indents_query(query.as_ref(), name)
321                .context("Error loading indents query")?;
322        }
323        if let Some(query) = queries.outline {
324            self = self
325                .with_outline_query(query.as_ref(), name)
326                .context("Error loading outline query")?;
327        }
328        if let Some(query) = queries.injections {
329            self = self
330                .with_injection_query(query.as_ref(), name)
331                .context("Error loading injection query")?;
332        }
333        if let Some(query) = queries.overrides {
334            self = self
335                .with_override_query(
336                    query.as_ref(),
337                    name,
338                    &config.overrides,
339                    &mut config.brackets,
340                    &config.scope_opt_in_language_servers,
341                )
342                .context("Error loading override query")?;
343        }
344        if let Some(query) = queries.redactions {
345            self = self
346                .with_redaction_query(query.as_ref(), name)
347                .context("Error loading redaction query")?;
348        }
349        if let Some(query) = queries.runnables {
350            self = self
351                .with_runnable_query(query.as_ref())
352                .context("Error loading runnables query")?;
353        }
354        if let Some(query) = queries.text_objects {
355            self = self
356                .with_text_object_query(query.as_ref(), name)
357                .context("Error loading textobject query")?;
358        }
359        if let Some(query) = queries.debugger {
360            self = self
361                .with_debug_variables_query(query.as_ref(), name)
362                .context("Error loading debug variables query")?;
363        }
364        Ok(self)
365    }
366
367    pub fn with_highlights_query(mut self, source: &str) -> Result<Self> {
368        let query = Query::new(&self.ts_language, source)?;
369
370        let mut identifier_capture_indices = Vec::new();
371        for name in [
372            "variable",
373            "constant",
374            "constructor",
375            "function",
376            "function.method",
377            "function.method.call",
378            "function.special",
379            "property",
380            "type",
381            "type.interface",
382        ] {
383            identifier_capture_indices.extend(query.capture_index_for_name(name));
384        }
385
386        self.highlights_config = Some(HighlightsConfig {
387            query,
388            identifier_capture_indices,
389        });
390
391        Ok(self)
392    }
393
394    pub fn with_runnable_query(mut self, source: &str) -> Result<Self> {
395        let query = Query::new(&self.ts_language, source)?;
396        let extra_captures: Vec<_> = query
397            .capture_names()
398            .iter()
399            .map(|&name| match name {
400                "run" => RunnableCapture::Run,
401                "run_item" => RunnableCapture::RunItem,
402                name => RunnableCapture::Named(name.to_string().into()),
403            })
404            .collect();
405        let supports_grouped_runnables = extra_captures
406            .iter()
407            .any(|capture| matches!(capture, RunnableCapture::RunItem));
408
409        self.runnable_config = Some(RunnableConfig {
410            extra_captures,
411            query,
412            supports_grouped_runnables,
413        });
414
415        Ok(self)
416    }
417
418    pub fn with_outline_query(
419        mut self,
420        source: &str,
421        language_name: &LanguageName,
422    ) -> Result<Self> {
423        let query = Query::new(&self.ts_language, source)?;
424        let mut item_capture_ix = 0;
425        let mut name_capture_ix = 0;
426        let mut context_capture_ix = None;
427        let mut extra_context_capture_ix = None;
428        let mut open_capture_ix = None;
429        let mut close_capture_ix = None;
430        let mut annotation_capture_ix = None;
431        if populate_capture_indices(
432            &query,
433            language_name,
434            "outline",
435            &[],
436            &mut [
437                Capture::Required("item", &mut item_capture_ix),
438                Capture::Required("name", &mut name_capture_ix),
439                Capture::Optional("context", &mut context_capture_ix),
440                Capture::Optional("context.extra", &mut extra_context_capture_ix),
441                Capture::Optional("open", &mut open_capture_ix),
442                Capture::Optional("close", &mut close_capture_ix),
443                Capture::Optional("annotation", &mut annotation_capture_ix),
444            ],
445        ) {
446            self.outline_config = Some(OutlineConfig {
447                query,
448                item_capture_ix,
449                name_capture_ix,
450                context_capture_ix,
451                extra_context_capture_ix,
452                open_capture_ix,
453                close_capture_ix,
454                annotation_capture_ix,
455            });
456        }
457        Ok(self)
458    }
459
460    pub fn with_text_object_query(
461        mut self,
462        source: &str,
463        language_name: &LanguageName,
464    ) -> Result<Self> {
465        let query = Query::new(&self.ts_language, source)?;
466
467        let mut text_objects_by_capture_ix = Vec::new();
468        for (ix, name) in query.capture_names().iter().enumerate() {
469            if let Some(text_object) = TextObject::from_capture_name(name) {
470                text_objects_by_capture_ix.push((ix as u32, text_object));
471            } else {
472                log::warn!(
473                    "unrecognized capture name '{}' in {} textobjects TreeSitter query",
474                    name,
475                    language_name,
476                );
477            }
478        }
479
480        self.text_object_config = Some(TextObjectConfig {
481            query,
482            text_objects_by_capture_ix,
483        });
484        Ok(self)
485    }
486
487    pub fn with_debug_variables_query(
488        mut self,
489        source: &str,
490        language_name: &LanguageName,
491    ) -> Result<Self> {
492        let query = Query::new(&self.ts_language, source)?;
493
494        let mut objects_by_capture_ix = Vec::new();
495        for (ix, name) in query.capture_names().iter().enumerate() {
496            if let Some(text_object) = DebuggerTextObject::from_capture_name(name) {
497                objects_by_capture_ix.push((ix as u32, text_object));
498            } else {
499                log::warn!(
500                    "unrecognized capture name '{}' in {} debugger TreeSitter query",
501                    name,
502                    language_name,
503                );
504            }
505        }
506
507        self.debug_variables_config = Some(DebugVariablesConfig {
508            query,
509            objects_by_capture_ix,
510        });
511        Ok(self)
512    }
513
514    pub fn with_brackets_query(
515        mut self,
516        source: &str,
517        language_name: &LanguageName,
518    ) -> Result<Self> {
519        let query = Query::new(&self.ts_language, source)?;
520        let mut open_capture_ix = 0;
521        let mut close_capture_ix = 0;
522        if populate_capture_indices(
523            &query,
524            language_name,
525            "brackets",
526            &[],
527            &mut [
528                Capture::Required("open", &mut open_capture_ix),
529                Capture::Required("close", &mut close_capture_ix),
530            ],
531        ) {
532            let patterns = (0..query.pattern_count())
533                .map(|ix| {
534                    let mut config = BracketsPatternConfig::default();
535                    for setting in query.property_settings(ix) {
536                        let setting_key = setting.key.as_ref();
537                        if setting_key == "newline.only" {
538                            config.newline_only = true
539                        }
540                        if setting_key == "rainbow.exclude" {
541                            config.rainbow_exclude = true
542                        }
543                    }
544                    config
545                })
546                .collect();
547            self.brackets_config = Some(BracketsConfig {
548                query,
549                open_capture_ix,
550                close_capture_ix,
551                patterns,
552            });
553        }
554        Ok(self)
555    }
556
557    pub fn with_indents_query(
558        mut self,
559        source: &str,
560        language_name: &LanguageName,
561    ) -> Result<Self> {
562        let query = Query::new(&self.ts_language, source)?;
563        let mut indent_capture_ix = 0;
564        let mut start_capture_ix = None;
565        let mut end_capture_ix = None;
566        let mut outdent_capture_ix = None;
567        if populate_capture_indices(
568            &query,
569            language_name,
570            "indents",
571            &["start."],
572            &mut [
573                Capture::Required("indent", &mut indent_capture_ix),
574                Capture::Optional("start", &mut start_capture_ix),
575                Capture::Optional("end", &mut end_capture_ix),
576                Capture::Optional("outdent", &mut outdent_capture_ix),
577            ],
578        ) {
579            let mut suffixed_start_captures = HashMap::default();
580            for (ix, name) in query.capture_names().iter().enumerate() {
581                if let Some(suffix) = name.strip_prefix("start.") {
582                    suffixed_start_captures.insert(ix as u32, suffix.to_owned().into());
583                }
584            }
585
586            self.indents_config = Some(IndentConfig {
587                query,
588                indent_capture_ix,
589                start_capture_ix,
590                end_capture_ix,
591                outdent_capture_ix,
592                suffixed_start_captures,
593            });
594        }
595        Ok(self)
596    }
597
598    pub fn with_injection_query(
599        mut self,
600        source: &str,
601        language_name: &LanguageName,
602    ) -> Result<Self> {
603        let query = Query::new(&self.ts_language, source)?;
604        let mut language_capture_ix = None;
605        let mut injection_language_capture_ix = None;
606        let mut content_capture_ix = None;
607        let mut injection_content_capture_ix = None;
608        if populate_capture_indices(
609            &query,
610            language_name,
611            "injections",
612            &[],
613            &mut [
614                Capture::Optional("language", &mut language_capture_ix),
615                Capture::Optional("injection.language", &mut injection_language_capture_ix),
616                Capture::Optional("content", &mut content_capture_ix),
617                Capture::Optional("injection.content", &mut injection_content_capture_ix),
618            ],
619        ) {
620            language_capture_ix = match (language_capture_ix, injection_language_capture_ix) {
621                (None, Some(ix)) => Some(ix),
622                (Some(_), Some(_)) => {
623                    anyhow::bail!("both language and injection.language captures are present");
624                }
625                _ => language_capture_ix,
626            };
627            content_capture_ix = match (content_capture_ix, injection_content_capture_ix) {
628                (None, Some(ix)) => Some(ix),
629                (Some(_), Some(_)) => {
630                    anyhow::bail!("both content and injection.content captures are present")
631                }
632                _ => content_capture_ix,
633            };
634            let patterns = (0..query.pattern_count())
635                .map(|ix| {
636                    let mut config = InjectionPatternConfig::default();
637                    for setting in query.property_settings(ix) {
638                        match setting.key.as_ref() {
639                            "language" | "injection.language" => {
640                                config.language.clone_from(&setting.value);
641                            }
642                            "combined" | "injection.combined" => {
643                                config.combined = true;
644                            }
645                            _ => {}
646                        }
647                    }
648                    config
649                })
650                .collect();
651            if let Some(content_capture_ix) = content_capture_ix {
652                self.injection_config = Some(InjectionConfig {
653                    query,
654                    language_capture_ix,
655                    content_capture_ix,
656                    patterns,
657                });
658            } else {
659                log::error!(
660                    "missing required capture in injections {} TreeSitter query: \
661                    content or injection.content",
662                    language_name,
663                );
664            }
665        }
666        Ok(self)
667    }
668
669    pub fn with_override_query(
670        mut self,
671        source: &str,
672        language_name: &LanguageName,
673        overrides: &HashMap<String, LanguageConfigOverride>,
674        brackets: &mut BracketPairConfig,
675        scope_opt_in_language_servers: &[SharedString],
676    ) -> Result<Self> {
677        let query = Query::new(&self.ts_language, source)?;
678
679        let mut override_configs_by_id = HashMap::default();
680        for (ix, mut name) in query.capture_names().iter().copied().enumerate() {
681            let mut range_is_inclusive = false;
682            if name.starts_with('_') {
683                continue;
684            }
685            if let Some(prefix) = name.strip_suffix(".inclusive") {
686                name = prefix;
687                range_is_inclusive = true;
688            }
689
690            let value = overrides.get(name).cloned().unwrap_or_default();
691            for server_name in &value.opt_into_language_servers {
692                if !scope_opt_in_language_servers.contains(server_name) {
693                    gpui_util::debug_panic!(
694                        "Server {server_name:?} has been opted-in by scope {name:?} but has not been marked as an opt-in server"
695                    );
696                }
697            }
698
699            override_configs_by_id.insert(
700                ix as u32,
701                OverrideEntry {
702                    name: name.to_string(),
703                    range_is_inclusive,
704                    value,
705                },
706            );
707        }
708
709        let referenced_override_names = overrides
710            .keys()
711            .chain(brackets.disabled_scopes_by_bracket_ix.iter().flatten());
712
713        for referenced_name in referenced_override_names {
714            if !override_configs_by_id
715                .values()
716                .any(|entry| entry.name == *referenced_name)
717            {
718                anyhow::bail!(
719                    "language {:?} has overrides in config not in query: {referenced_name:?}",
720                    language_name
721                );
722            }
723        }
724
725        for entry in override_configs_by_id.values_mut() {
726            entry.value.disabled_bracket_ixs = brackets
727                .disabled_scopes_by_bracket_ix
728                .iter()
729                .enumerate()
730                .filter_map(|(ix, disabled_scope_names)| {
731                    if disabled_scope_names.contains(&entry.name) {
732                        Some(ix as u16)
733                    } else {
734                        None
735                    }
736                })
737                .collect();
738        }
739
740        brackets.disabled_scopes_by_bracket_ix.clear();
741
742        self.override_config = Some(OverrideConfig {
743            query,
744            values: override_configs_by_id,
745        });
746        Ok(self)
747    }
748
749    pub fn with_redaction_query(
750        mut self,
751        source: &str,
752        language_name: &LanguageName,
753    ) -> Result<Self> {
754        let query = Query::new(&self.ts_language, source)?;
755        let mut redaction_capture_ix = 0;
756        if populate_capture_indices(
757            &query,
758            language_name,
759            "redactions",
760            &[],
761            &mut [Capture::Required("redact", &mut redaction_capture_ix)],
762        ) {
763            self.redactions_config = Some(RedactionConfig {
764                query,
765                redaction_capture_ix,
766            });
767        }
768        Ok(self)
769    }
770}
771
Served at tenant.openagents/omega Member data and write actions are omitted.