Skip to main content

comfit_core/
query.rs

1//! Compile query metadata and filter matches with custom predicates.
2
3use crate::{
4    Error,
5    backend::{Backend, Match},
6};
7
8mod predicate;
9
10pub use predicate::Predicate;
11
12/// A compiled query and its metadata in the shared format.
13pub struct Query<Q> {
14    /// Runtime-specific compiled query.
15    pub raw: Q,
16    /// Capture names indexed by [`crate::backend::Capture::index`].
17    pub capture_names: Vec<String>,
18    /// Pattern metadata indexed by [`Match::pattern`].
19    pub patterns: Vec<Pattern>,
20    /// Metadata required for each capture across all query patterns.
21    pub metadata: Vec<Metadata>,
22}
23
24/// Node metadata needed by a query capture.
25#[derive(Clone, Copy, Default)]
26pub struct Metadata {
27    /// Maximum number of ancestor names to collect. Zero disables collection.
28    pub ancestors: usize,
29    /// Whether to collect ancestor depth without relying on ancestor names.
30    pub depth: bool,
31    /// Whether the immediate parent ID is needed for direct rainbow scopes.
32    pub parent: bool,
33    /// Whether to collect immediate children to exclude from injected content.
34    pub children: bool,
35}
36
37/// Properties and custom predicates for one query pattern.
38pub struct Pattern {
39    /// Property settings in runtime order.
40    pub properties: Vec<Property>,
41    /// Custom predicates applied after runtime matching.
42    pub predicates: Vec<Predicate>,
43    /// Required local-binding state, or `None` for either state.
44    pub local: Option<bool>,
45    /// Compiled highlight priority, defaulting to 100.
46    pub priority: i32,
47    offsets: bool,
48}
49
50impl Pattern {
51    /// Compile custom predicates and retain normalized pattern metadata.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`Error::Query`] for unsupported or malformed custom predicates.
56    pub fn new(
57        properties: Vec<Property>,
58        predicates: Vec<(String, Vec<Argument>)>,
59        local: Option<bool>,
60    ) -> Result<Self, Error> {
61        let predicates = predicates
62            .into_iter()
63            .map(|(operator, arguments)| Predicate::compile(&operator, arguments))
64            .collect::<Result<Vec<_>, _>>()?;
65        let priority = properties
66            .iter()
67            .find(|property| property.key == "priority")
68            .and_then(|property| property.value.as_deref())
69            .and_then(|value| value.parse().ok())
70            .unwrap_or(100);
71        let offsets = predicates
72            .iter()
73            .any(|predicate| matches!(predicate, Predicate::Offset { .. }));
74
75        Ok(Self {
76            properties,
77            predicates,
78            local,
79            priority,
80            offsets,
81        })
82    }
83
84    /// Return the first property matching the key.
85    pub fn property(&self, key: &str) -> Option<&Property> {
86        self.properties.iter().find(|property| property.key == key)
87    }
88
89    /// Return the first matching property's value, if present.
90    pub fn value(&self, key: &str) -> Option<&str> {
91        self.property(key)
92            .and_then(|property| property.value.as_deref())
93    }
94}
95
96/// A query property in the shared format.
97pub struct Property {
98    /// Property name, such as `injection.language`.
99    pub key: String,
100    /// Optional literal property value.
101    pub value: Option<String>,
102    /// Optional capture index associated with the property.
103    pub capture: Option<u32>,
104}
105
106/// A custom-predicate argument from a compiled query.
107#[derive(Debug)]
108pub enum Argument {
109    /// A capture index in the query.
110    Capture(u32),
111    /// A literal string argument.
112    String(String),
113}
114
115impl<Q> Query<Q> {
116    /// Store a compiled query and determine the node metadata each capture needs.
117    pub fn new(raw: Q, capture_names: Vec<String>, patterns: Vec<Pattern>) -> Self {
118        let mut metadata = vec![Metadata::default(); capture_names.len()];
119
120        for (name, metadata) in capture_names.iter().zip(&mut metadata) {
121            metadata.depth = matches!(name.as_str(), "local.scope" | "rainbow.scope");
122            metadata.parent = name == "rainbow.bracket";
123            metadata.children = name == "injection.content"
124                && patterns
125                    .iter()
126                    .any(|pattern| pattern.property("injection.include-children").is_none());
127        }
128
129        for pattern in &patterns {
130            for predicate in &pattern.predicates {
131                if let Predicate::Ancestor {
132                    capture, parent, ..
133                } = predicate
134                {
135                    let required = if *parent { 1 } else { usize::MAX };
136                    metadata[*capture as usize].ancestors =
137                        metadata[*capture as usize].ancestors.max(required);
138                }
139            }
140        }
141
142        Self {
143            raw,
144            capture_names,
145            patterns,
146            metadata,
147        }
148    }
149
150    /// Run a query and filter its matches. Empty queries skip runtime execution.
151    ///
152    /// # Errors
153    ///
154    /// Returns runtime, predicate, or invalid-range errors.
155    pub fn matches<B: Backend<Query = Q>>(
156        &self,
157        backend: &mut B,
158        tree: &B::Tree,
159        source: &str,
160    ) -> Result<Vec<Match>, Error> {
161        if self.patterns.is_empty() {
162            return Ok(Vec::new());
163        }
164        self.filtered(backend.matches(self, tree, source)?, source)
165    }
166
167    /// Validate captures, filter custom predicates, and apply capture offsets.
168    ///
169    /// Reuses the input vector and preserves the order of accepted matches.
170    /// Input indices must refer to this query.
171    ///
172    /// # Errors
173    ///
174    /// Returns [`Error::InvalidRange`] for invalid source boundaries or offsets.
175    ///
176    /// # Panics
177    ///
178    /// Panics if a match references a pattern outside [`Self::patterns`].
179    pub fn filtered(&self, mut matches: Vec<Match>, source: &str) -> Result<Vec<Match>, Error> {
180        let mut accepted = 0;
181
182        for index in 0..matches.len() {
183            let matched = &mut matches[index];
184            for capture in &matched.captures {
185                if source.get(capture.node.range.clone()).is_none() {
186                    return Err(Error::InvalidRange(capture.node.range.clone()));
187                }
188            }
189
190            let pattern = &self.patterns[matched.pattern];
191
192            if !pattern
193                .predicates
194                .iter()
195                .all(|predicate| predicate.accepts(matched, source))
196            {
197                continue;
198            }
199
200            if pattern.offsets {
201                for predicate in &pattern.predicates {
202                    predicate.apply(matched, source)?;
203                }
204
205                for capture in &matched.captures {
206                    if source.get(capture.node.range.clone()).is_none() {
207                        return Err(Error::InvalidRange(capture.node.range.clone()));
208                    }
209                }
210            }
211
212            matches.swap(accepted, index);
213            accepted += 1;
214        }
215
216        matches.truncate(accepted);
217        Ok(matches)
218    }
219}