1use std::borrow::Cow;
2use std::cmp;
3use std::fmt;
4use std::mem::replace;
5use std::num::NonZeroU32;
6use std::ops::RangeBounds;
7use std::slice;
8use std::sync::Arc;
9
10use crate::config::{LanguageConfig, LanguageLoader};
11use crate::locals::ScopeCursor;
12use crate::query_iter::{MatchedNode, QueryIter, QueryIterEvent, QueryLoader};
13use crate::{Injection, Language, Layer, Syntax};
14use arc_swap::ArcSwap;
15use hashbrown::{HashMap, HashSet};
16use ropey::RopeSlice;
17use tree_sitter::{
18 query::{self, InvalidPredicateError, Query, UserPredicate},
19 Capture, Grammar,
20};
21use tree_sitter::{Pattern, QueryMatch};
22
23#[derive(Debug)]
27pub struct HighlightQuery {
28 pub query: Query,
29 highlight_indices: ArcSwap<Vec<Option<Highlight>>>,
30 #[allow(dead_code)]
31 non_local_patterns: HashSet<Pattern>,
33 local_reference_capture: Option<Capture>,
34 first_locals_pattern: Pattern,
37}
38
39impl HighlightQuery {
40 pub(crate) fn new(
41 grammar: Grammar,
42 highlight_query_text: &str,
43 local_query_text: &str,
44 ) -> Result<Self, query::ParseError> {
45 let mut query_source =
47 String::with_capacity(highlight_query_text.len() + local_query_text.len());
48 query_source.push_str(highlight_query_text);
49 query_source.push_str(local_query_text);
50
51 let mut non_local_patterns = HashSet::new();
52 let mut query = Query::new(grammar, &query_source, |pattern, predicate| {
53 match predicate {
54 UserPredicate::SetProperty {
58 key: "local.scope-inherits",
59 ..
60 } => (),
61 UserPredicate::IsPropertySet {
64 negate: true,
65 key: "local",
66 val: None,
67 } => {
68 non_local_patterns.insert(pattern);
69 }
70 _ => return Err(InvalidPredicateError::unknown(predicate)),
71 }
72 Ok(())
73 })?;
74
75 let first_locals_pattern = query
79 .patterns()
80 .find(|&p| query.start_byte_for_pattern(p) >= highlight_query_text.len())
81 .unwrap_or(Pattern::SENTINEL);
82
83 query.disable_capture("local.scope");
86 let local_definition_captures: Vec<_> = query
87 .captures()
88 .filter(|&(_, name)| name.starts_with("local.definition."))
89 .map(|(_, name)| Box::<str>::from(name))
90 .collect();
91 for name in local_definition_captures {
92 query.disable_capture(&name);
93 }
94
95 Ok(Self {
96 highlight_indices: ArcSwap::from_pointee(vec![None; query.num_captures() as usize]),
97 non_local_patterns,
98 local_reference_capture: query.get_capture("local.reference"),
99 first_locals_pattern,
100 query,
101 })
102 }
103
104 pub(crate) fn configure(&self, f: &mut impl FnMut(&str) -> Option<Highlight>) {
121 let highlight_indices = self
122 .query
123 .captures()
124 .map(|(_, capture_name)| f(capture_name))
125 .collect();
126 self.highlight_indices.store(Arc::new(highlight_indices));
127 }
128}
129
130#[derive(Copy, Clone, PartialEq, Eq, Hash)]
135pub struct Highlight(NonZeroU32);
136
137impl Highlight {
138 pub const MAX: u32 = u32::MAX - 1;
139
140 pub const fn new(inner: u32) -> Self {
141 assert!(inner != u32::MAX);
142 Self(unsafe { NonZeroU32::new_unchecked(inner ^ u32::MAX) })
144 }
145
146 pub const fn get(&self) -> u32 {
147 self.0.get() ^ u32::MAX
148 }
149
150 pub const fn idx(&self) -> usize {
151 self.get() as usize
152 }
153}
154
155impl fmt::Debug for Highlight {
156 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157 f.debug_tuple("Highlight").field(&self.get()).finish()
158 }
159}
160
161#[derive(Debug)]
162struct HighlightedNode {
163 end: u32,
164 highlight: Highlight,
165}
166
167#[derive(Debug, Default)]
168pub struct LayerData {
169 parent_highlights: usize,
170 dormant_highlights: Vec<HighlightedNode>,
171}
172
173pub struct Highlighter<'a, 'tree, Loader: LanguageLoader> {
174 query: QueryIter<'a, 'tree, HighlightQueryLoader<&'a Loader>, ()>,
175 next_query_event: Option<QueryIterEvent<'tree, ()>>,
176 active_highlights: Vec<HighlightedNode>,
191 next_highlight_end: u32,
192 next_highlight_start: u32,
193 active_config: Option<&'a LanguageConfig>,
194 current_layer: Layer,
200 layer_states: HashMap<Layer, LayerData>,
201}
202
203pub struct HighlightList<'a>(slice::Iter<'a, HighlightedNode>);
204
205impl Iterator for HighlightList<'_> {
206 type Item = Highlight;
207
208 fn next(&mut self) -> Option<Highlight> {
209 self.0.next().map(|node| node.highlight)
210 }
211
212 fn size_hint(&self) -> (usize, Option<usize>) {
213 self.0.size_hint()
214 }
215}
216
217impl DoubleEndedIterator for HighlightList<'_> {
218 fn next_back(&mut self) -> Option<Self::Item> {
219 self.0.next_back().map(|node| node.highlight)
220 }
221}
222
223impl ExactSizeIterator for HighlightList<'_> {
224 fn len(&self) -> usize {
225 self.0.len()
226 }
227}
228
229#[derive(Debug, Clone, Copy, PartialEq, Eq)]
230pub enum HighlightEvent {
231 Refresh,
233 Push,
235}
236
237impl<'a, 'tree: 'a, Loader: LanguageLoader> Highlighter<'a, 'tree, Loader> {
238 pub fn new(
239 syntax: &'tree Syntax,
240 src: RopeSlice<'a>,
241 loader: &'a Loader,
242 range: impl RangeBounds<u32>,
243 ) -> Self {
244 let mut query = QueryIter::new(syntax, src, HighlightQueryLoader(loader), range);
245 let active_language = query.current_language();
246 let mut res = Highlighter {
247 active_config: query.loader().0.get_config(active_language),
248 next_query_event: None,
249 current_layer: query.current_layer(),
250 layer_states: Default::default(),
251 active_highlights: Vec::new(),
252 next_highlight_end: u32::MAX,
253 next_highlight_start: 0,
254 query,
255 };
256 res.advance_query_iter();
257 res
258 }
259
260 pub fn active_highlights(&self) -> HighlightList<'_> {
261 HighlightList(self.active_highlights.iter())
262 }
263
264 pub fn next_event_offset(&self) -> u32 {
265 self.next_highlight_start.min(self.next_highlight_end)
266 }
267
268 pub fn advance(&mut self) -> (HighlightEvent, HighlightList<'_>) {
269 let mut refresh = false;
270
271 let pos = self.next_event_offset();
272 if self.next_highlight_end == pos {
273 self.process_highlight_end(pos);
274 refresh = true;
275 }
276
277 let prev_stack_size = self.active_highlights.len();
283
284 let mut first_highlight = true;
285 let mut local_ref_candidate: Option<(u32, Highlight)> = None;
286 while self.next_highlight_start == pos {
287 let Some(query_event) = self.advance_query_iter() else {
288 break;
289 };
290 match query_event {
291 QueryIterEvent::EnterInjection(injection) => self.enter_injection(injection.layer),
292 QueryIterEvent::Match(node) => self.start_highlight(
293 node,
294 &mut first_highlight,
295 prev_stack_size,
296 &mut local_ref_candidate,
297 ),
298 QueryIterEvent::ExitInjection { injection, state } => {
299 let layer_is_finished = state.is_some()
306 && self
307 .current_layer_highlights()
308 .iter()
309 .all(|h| h.end <= injection.range.end);
310 if layer_is_finished {
311 self.layer_states.remove(&injection.layer);
312 } else {
313 self.deactivate_layer(injection);
314 refresh = true;
315 }
316 let active_language = self.query.syntax().layer(self.current_layer).language;
317 self.active_config = self.query.loader().0.get_config(active_language);
318 }
319 }
320 }
321 if let Some((end, highlight)) = local_ref_candidate {
323 let node = HighlightedNode { end, highlight };
324 let search_start = prev_stack_size.min(self.active_highlights.len());
325 let insert_position = self.active_highlights[search_start..]
326 .iter()
327 .rposition(|h| h.end <= end)
328 .map(|rel_idx| rel_idx + search_start);
329 match insert_position {
330 Some(idx) => match self.active_highlights[idx].end.cmp(&end) {
331 cmp::Ordering::Equal => self.active_highlights[idx] = node,
332 cmp::Ordering::Less => self.active_highlights.insert(idx, node),
333 cmp::Ordering::Greater => unreachable!(),
334 },
335 None => self.active_highlights.extend(Some(node)),
336 }
337 }
338
339 self.next_highlight_end = self
340 .active_highlights
341 .last()
342 .map_or(u32::MAX, |node| node.end);
343
344 if refresh {
345 (
346 HighlightEvent::Refresh,
347 HighlightList(self.active_highlights.iter()),
348 )
349 } else {
350 (
351 HighlightEvent::Push,
352 HighlightList(self.active_highlights[prev_stack_size..].iter()),
353 )
354 }
355 }
356
357 fn advance_query_iter(&mut self) -> Option<QueryIterEvent<'tree, ()>> {
358 self.current_layer = self.query.current_layer();
363 let event = replace(&mut self.next_query_event, self.query.next());
364 self.next_highlight_start = self
365 .next_query_event
366 .as_ref()
367 .map_or(u32::MAX, |event| event.start_byte());
368 event
369 }
370
371 fn process_highlight_end(&mut self, pos: u32) {
372 let i = self
373 .active_highlights
374 .iter()
375 .rposition(|highlight| highlight.end != pos)
376 .map_or(0, |i| i + 1);
377 self.active_highlights.truncate(i);
378 }
379
380 fn current_layer_highlights(&self) -> &[HighlightedNode] {
381 let parent_start = self
382 .layer_states
383 .get(&self.current_layer)
384 .map(|layer| layer.parent_highlights)
385 .unwrap_or_default()
386 .min(self.active_highlights.len());
387 &self.active_highlights[parent_start..]
388 }
389
390 fn enter_injection(&mut self, layer: Layer) {
391 debug_assert_eq!(layer, self.current_layer);
392 let active_language = self.query.syntax().layer(layer).language;
393 self.active_config = self.query.loader().0.get_config(active_language);
394
395 let state = self.layer_states.entry(layer).or_default();
396 state.parent_highlights = self.active_highlights.len();
397 self.active_highlights.append(&mut state.dormant_highlights);
398 }
399
400 fn deactivate_layer(&mut self, injection: Injection) {
401 let LayerData {
402 mut parent_highlights,
403 ref mut dormant_highlights,
404 ..
405 } = self.layer_states.get_mut(&injection.layer).unwrap();
406 parent_highlights = parent_highlights.min(self.active_highlights.len());
407 dormant_highlights.extend(self.active_highlights.drain(parent_highlights..));
408 self.process_highlight_end(injection.range.end);
409 }
410
411 fn start_highlight(
412 &mut self,
413 node: MatchedNode,
414 first_highlight: &mut bool,
415 prev_stack_size: usize,
416 local_ref_candidate: &mut Option<(u32, Highlight)>,
417 ) {
418 let range = node.node.byte_range();
419 debug_assert!(
421 !range.is_empty(),
422 "QueryIter should not emit matches with empty ranges"
423 );
424
425 let config = self
426 .active_config
427 .expect("must have an active config to emit matches");
428
429 let is_local_reference =
430 Some(node.capture) == config.highlight_query.local_reference_capture;
431
432 if node.pattern >= config.highlight_query.first_locals_pattern && !is_local_reference {
436 if local_ref_candidate.is_some_and(|(end, _)| end == range.end) {
437 *local_ref_candidate = None;
438 }
439 return;
440 }
441
442 let highlight = if is_local_reference {
443 let text: Cow<str> = self
446 .query
447 .source()
448 .byte_slice(range.start as usize..range.end as usize)
449 .into();
450 let Some(definition) = self
451 .query
452 .syntax()
453 .layer(self.current_layer)
454 .locals
455 .lookup_reference(node.scope, &text)
456 .filter(|def| range.start >= def.range.end)
457 else {
458 return;
459 };
460 let highlight = config
461 .injection_query
462 .local_definition_captures
463 .load()
464 .get(&definition.capture)
465 .copied();
466 if let Some(h) = highlight {
469 *local_ref_candidate = Some((range.end, h));
470 }
471 return;
472 } else {
473 config.highlight_query.highlight_indices.load()[node.capture.idx()]
474 };
475
476 let highlight = highlight.map(|highlight| HighlightedNode {
477 end: range.end,
478 highlight,
479 });
480
481 if !*first_highlight {
484 let search_start = prev_stack_size.min(self.active_highlights.len());
489 let insert_position = self.active_highlights[search_start..]
490 .iter()
491 .rposition(|h| h.end <= range.end)
492 .map(|rel_idx| rel_idx + search_start);
493 if let Some(idx) = insert_position {
494 match self.active_highlights[idx].end.cmp(&range.end) {
495 cmp::Ordering::Equal => {
497 if let Some(highlight) = highlight {
498 self.active_highlights[idx] = highlight;
499 } else {
500 self.active_highlights.remove(idx);
501 }
502 }
503 cmp::Ordering::Less => {
507 if let Some(highlight) = highlight {
508 self.active_highlights.insert(idx, highlight)
509 }
510 }
511 cmp::Ordering::Greater => unreachable!(),
513 }
514 } else {
515 self.active_highlights.extend(highlight);
516 }
517 } else if let Some(highlight) = highlight {
518 self.active_highlights.push(highlight);
519 *first_highlight = false;
520 }
521
522 debug_assert!(
526 self.current_layer_highlights().is_sorted_by_key(|h| cmp::Reverse(h.end)),
532 "unsorted highlights on layer {:?}: {:?}\nall active highlights must be sorted by `end` descending",
533 self.current_layer,
534 self.active_highlights,
535 );
536 }
537}
538
539pub(crate) struct HighlightQueryLoader<T>(T);
540
541impl<'a, T: LanguageLoader> QueryLoader<'a> for HighlightQueryLoader<&'a T> {
542 fn get_query(&mut self, lang: Language) -> Option<&'a Query> {
543 self.0
544 .get_config(lang)
545 .map(|config| &config.highlight_query.query)
546 }
547
548 fn are_predicates_satisfied(
549 &self,
550 lang: Language,
551 mat: &QueryMatch<'_, '_>,
552 source: RopeSlice<'_>,
553 locals_cursor: &ScopeCursor<'_>,
554 ) -> bool {
555 let highlight_query = &self
556 .0
557 .get_config(lang)
558 .expect("must have a config to emit matches")
559 .highlight_query;
560
561 if highlight_query.local_reference_capture.is_some()
570 && highlight_query.non_local_patterns.contains(&mat.pattern())
571 {
572 let has_local_reference = mat.matched_nodes().any(|n| {
573 let range = n.node.byte_range();
574 let text: Cow<str> = source
575 .byte_slice(range.start as usize..range.end as usize)
576 .into();
577 locals_cursor
578 .locals
579 .lookup_reference(locals_cursor.current_scope(), &text)
580 .is_some_and(|def| range.start >= def.range.start)
581 });
582 if has_local_reference {
583 return false;
584 }
585 }
586
587 true
588 }
589}