grammar.rs

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