use collections::BTreeMap; use gpui::HighlightStyle; use language::Chunk; use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _}; use std::{ any::TypeId, cmp, iter::{self, Peekable}, ops::Range, sync::Arc, vec, }; use sum_tree::TreeMap; pub struct CustomHighlightsChunks<'a> { buffer_chunks: MultiBufferChunks<'a>, buffer_chunk: Option>, offset: usize, multibuffer_snapshot: &'a MultiBufferSnapshot, highlight_endpoints: Peekable>, active_highlights: BTreeMap, text_highlights: Option<&'a TreeMap>)>>>, } #[derive(Debug, Copy, Clone, Eq, PartialEq)] struct HighlightEndpoint { offset: usize, is_start: bool, tag: TypeId, style: HighlightStyle, } impl<'a> CustomHighlightsChunks<'a> { pub fn new( range: Range, language_aware: bool, text_highlights: Option<&'a TreeMap>)>>>, multibuffer_snapshot: &'a MultiBufferSnapshot, ) -> Self { Self { buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware), buffer_chunk: None, offset: range.start, text_highlights, highlight_endpoints: create_highlight_endpoints( &range, text_highlights, multibuffer_snapshot, ), active_highlights: Default::default(), multibuffer_snapshot, } } pub fn seek(&mut self, new_range: Range) { self.highlight_endpoints = create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot); self.offset = new_range.start; self.buffer_chunks.seek(new_range); self.buffer_chunk.take(); self.active_highlights.clear() } } fn create_highlight_endpoints( range: &Range, text_highlights: Option<&TreeMap>)>>>, buffer: &MultiBufferSnapshot, ) -> iter::Peekable> { let mut highlight_endpoints = Vec::new(); if let Some(text_highlights) = text_highlights { let start = buffer.anchor_after(range.start); let end = buffer.anchor_after(range.end); for (&tag, text_highlights) in text_highlights.iter() { let style = text_highlights.0; let ranges = &text_highlights.1; let start_ix = match ranges.binary_search_by(|probe| { let cmp = probe.end.cmp(&start, &buffer); if cmp.is_gt() { cmp::Ordering::Greater } else { cmp::Ordering::Less } }) { Ok(i) | Err(i) => i, }; for range in &ranges[start_ix..] { if range.start.cmp(&end, &buffer).is_ge() { break; } highlight_endpoints.push(HighlightEndpoint { offset: range.start.to_offset(&buffer), is_start: true, tag, style, }); highlight_endpoints.push(HighlightEndpoint { offset: range.end.to_offset(&buffer), is_start: false, tag, style, }); } } highlight_endpoints.sort(); } highlight_endpoints.into_iter().peekable() } impl<'a> Iterator for CustomHighlightsChunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { let mut next_highlight_endpoint = usize::MAX; while let Some(endpoint) = self.highlight_endpoints.peek().copied() { if endpoint.offset <= self.offset { if endpoint.is_start { self.active_highlights.insert(endpoint.tag, endpoint.style); } else { self.active_highlights.remove(&endpoint.tag); } self.highlight_endpoints.next(); } else { next_highlight_endpoint = endpoint.offset; break; } } let chunk = self .buffer_chunk .get_or_insert_with(|| self.buffer_chunks.next().unwrap()); if chunk.text.is_empty() { *chunk = self.buffer_chunks.next().unwrap(); } let (prefix, suffix) = chunk .text .split_at(chunk.text.len().min(next_highlight_endpoint - self.offset)); chunk.text = suffix; self.offset += prefix.len(); let mut prefix = Chunk { text: prefix, ..chunk.clone() }; if !self.active_highlights.is_empty() { let mut highlight_style = HighlightStyle::default(); for active_highlight in self.active_highlights.values() { highlight_style.highlight(*active_highlight); } prefix.highlight_style = Some(highlight_style); } Some(prefix) } } impl PartialOrd for HighlightEndpoint { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for HighlightEndpoint { fn cmp(&self, other: &Self) -> cmp::Ordering { self.offset .cmp(&other.offset) .then_with(|| other.is_start.cmp(&self.is_start)) } }