use std::borrow::Cow; use std::iter::{self, Peekable}; use std::mem::{replace, take}; use std::slice; use hashbrown::HashMap; use crate::query_iter::{MatchedNode, QueryIter, QueryIterEvent}; use crate::{Injection, LayerId, Range, Syntax}; /// Indicates which highlight should be applied to a region of source code. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub struct Highlight(pub u32); impl Highlight{ pub(crate) const NONE = Highlight(u32::MAX); } #[derive(Debug)] struct LocalDef<'a> { name: Cow<'a, str>, value_range: Range, highlight: Option, } #[derive(Debug)] struct LocalScope<'a> { inherits: bool, range: Range, local_defs: Vec>, } #[derive(Debug)] struct HighlightedNode { end: u32, highlight: Highlight, } #[derive(Debug, Default)] struct LayerData<'a> { parent_highlights: usize, dormant_highlights: Vec, scope_stack: Vec>, } struct HighlighterConfig<'a> { new_precedance: bool, highlight_indices: &'a [Highlight], } pub struct Highligther<'a> { query: QueryIter<'a, LayerData<'a>>, next_query_event: Option>>, active_highlights: Vec, next_highlight_end: u32, next_highlight_start: u32, config: HighlighterConfig<'a>, } pub struct HighlightList<'a>(slice::Iter<'a, HighlightedNode>); impl<'a> Iterator for HighlightList<'a> { type Item = Highlight; fn next(&mut self) -> Option { self.0.next().map(|node| node.highlight) } } pub enum HighlighEvent<'a> { RefreshHiglights(HighlightList<'a>), PushHighlights(HighlightList<'a>), } impl<'a> Highligther<'a> { pub fn active_highlights(&self) -> HighlightList<'_> { HighlightList(self.active_highlights.iter()) } pub fn next_event_offset(&self) -> u32 { self.next_highlight_start.min(self.next_highlight_end) } pub fn advance(&mut self) -> HighlighEvent<'_> { let mut refresh = false; let prev_stack_size = self.active_highlights.len(); let pos = self.next_event_offset(); if self.next_highlight_end == pos { self.process_injection_ends(); self.process_higlight_end(); refresh = true; } let mut first_highlight = true; while self.next_highlight_start == pos { let Some(query_event) = self.adance_query_iter() else { break; }; match query_event { QueryIterEvent::EnterInjection(_) => self.enter_injection(), QueryIterEvent::Match(node) => self.start_highlight(node, &mut first_highlight), QueryIterEvent::ExitInjection { injection, state } => { // state is returned if the layer is finifhed, if it isn't we have // a combined injection and need to deactive its highlights if state.is_none() { self.deactive_layer(injection.layer); refresh = true; } } } } self.next_highlight_end = self .active_highlights .last() .map_or(u32::MAX, |node| node.end); if refresh { HighlighEvent::RefreshHiglights(HighlightList(self.active_highlights.iter())) } else { HighlighEvent::PushHighlights(HighlightList( self.active_highlights[prev_stack_size..].iter(), )) } } fn adance_query_iter(&mut self) -> Option>> { let event = replace(&mut self.next_query_event, self.query.next()); self.next_highlight_start = self .next_query_event .as_ref() .map_or(u32::MAX, |event| event.start()); event } fn process_higlight_end(&mut self) { let i = self .active_highlights .iter() .rposition(|highlight| highlight.end != self.next_highlight_end) .unwrap(); self.active_highlights.truncate(i); } /// processes injections that end at the same position as highlights first. fn process_injection_ends(&mut self) { while self.next_highlight_end == self.next_highlight_start { match self.next_query_event.as_ref() { Some(QueryIterEvent::ExitInjection { injection, state }) => { if state.is_none() { self.deactive_layer(injection.layer); } } Some(QueryIterEvent::Match(matched_node)) if matched_node.byte_range.is_empty() => { } _ => break, } } } fn enter_injection(&mut self) { self.query.current_layer_state().parent_highlights = self.active_highlights.len(); } fn deactive_layer(&mut self, layer: LayerId) { let LayerData { parent_highlights, ref mut dormant_highlights, .. } = *self.query.layer_state(layer); let i = self.active_highlights[parent_highlights..] .iter() .rposition(|highlight| highlight.end != self.next_highlight_end) .unwrap(); self.active_highlights.truncate(parent_highlights + i); dormant_highlights.extend(self.active_highlights.drain(parent_highlights..)) } fn start_highlight(&mut self, node: MatchedNode, first_highlight: &mut bool) { if node.byte_range.is_empty() { return; } // if there are multiple matches for the exact same node // only use one of the (the last with new/nvim precedance) if !*first_highlight && self.active_highlights.last().map_or(false, |prev_node| { prev_node.end == node.byte_range.end as u32 }) { if self.config.new_precedance { self.active_highlights.pop(); } else { return; } } let highlight = self.config.highlight_indices[node.capture.idx()]; if highlight.0 == u32::MAX { return; } self.active_highlights.push(HighlightedNode { end: node.byte_range.end as u32, highlight, }); *first_highlight = false; } }