custom_highlights.rs

  1use collections::BTreeMap;
  2use gpui::HighlightStyle;
  3use language::Chunk;
  4use multi_buffer::{Anchor, MultiBufferChunks, MultiBufferSnapshot, ToOffset as _};
  5use std::{
  6    any::TypeId,
  7    cmp,
  8    iter::{self, Peekable},
  9    ops::Range,
 10    sync::Arc,
 11    vec,
 12};
 13use sum_tree::TreeMap;
 14
 15pub struct CustomHighlightsChunks<'a> {
 16    buffer_chunks: MultiBufferChunks<'a>,
 17    buffer_chunk: Option<Chunk<'a>>,
 18    offset: usize,
 19    multibuffer_snapshot: &'a MultiBufferSnapshot,
 20
 21    highlight_endpoints: Peekable<vec::IntoIter<HighlightEndpoint>>,
 22    active_highlights: BTreeMap<TypeId, HighlightStyle>,
 23    text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
 24}
 25
 26#[derive(Debug, Copy, Clone, Eq, PartialEq)]
 27struct HighlightEndpoint {
 28    offset: usize,
 29    is_start: bool,
 30    tag: TypeId,
 31    style: HighlightStyle,
 32}
 33
 34impl<'a> CustomHighlightsChunks<'a> {
 35    pub fn new(
 36        range: Range<usize>,
 37        language_aware: bool,
 38        text_highlights: Option<&'a TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
 39        multibuffer_snapshot: &'a MultiBufferSnapshot,
 40    ) -> Self {
 41        Self {
 42            buffer_chunks: multibuffer_snapshot.chunks(range.clone(), language_aware),
 43            buffer_chunk: None,
 44            offset: range.start,
 45
 46            text_highlights,
 47            highlight_endpoints: create_highlight_endpoints(
 48                &range,
 49                text_highlights,
 50                multibuffer_snapshot,
 51            ),
 52            active_highlights: Default::default(),
 53            multibuffer_snapshot,
 54        }
 55    }
 56
 57    pub fn seek(&mut self, new_range: Range<usize>) {
 58        self.highlight_endpoints =
 59            create_highlight_endpoints(&new_range, self.text_highlights, self.multibuffer_snapshot);
 60        self.offset = new_range.start;
 61        self.buffer_chunks.seek(new_range);
 62        self.buffer_chunk.take();
 63        self.active_highlights.clear()
 64    }
 65}
 66
 67fn create_highlight_endpoints(
 68    range: &Range<usize>,
 69    text_highlights: Option<&TreeMap<TypeId, Arc<(HighlightStyle, Vec<Range<Anchor>>)>>>,
 70    buffer: &MultiBufferSnapshot,
 71) -> iter::Peekable<vec::IntoIter<HighlightEndpoint>> {
 72    let mut highlight_endpoints = Vec::new();
 73    if let Some(text_highlights) = text_highlights {
 74        let start = buffer.anchor_after(range.start);
 75        let end = buffer.anchor_after(range.end);
 76        for (&tag, text_highlights) in text_highlights.iter() {
 77            let style = text_highlights.0;
 78            let ranges = &text_highlights.1;
 79
 80            let start_ix = match ranges.binary_search_by(|probe| {
 81                let cmp = probe.end.cmp(&start, &buffer);
 82                if cmp.is_gt() {
 83                    cmp::Ordering::Greater
 84                } else {
 85                    cmp::Ordering::Less
 86                }
 87            }) {
 88                Ok(i) | Err(i) => i,
 89            };
 90
 91            for range in &ranges[start_ix..] {
 92                if range.start.cmp(&end, &buffer).is_ge() {
 93                    break;
 94                }
 95
 96                highlight_endpoints.push(HighlightEndpoint {
 97                    offset: range.start.to_offset(&buffer),
 98                    is_start: true,
 99                    tag,
100                    style,
101                });
102                highlight_endpoints.push(HighlightEndpoint {
103                    offset: range.end.to_offset(&buffer),
104                    is_start: false,
105                    tag,
106                    style,
107                });
108            }
109        }
110        highlight_endpoints.sort();
111    }
112    highlight_endpoints.into_iter().peekable()
113}
114
115impl<'a> Iterator for CustomHighlightsChunks<'a> {
116    type Item = Chunk<'a>;
117
118    fn next(&mut self) -> Option<Self::Item> {
119        let mut next_highlight_endpoint = usize::MAX;
120        while let Some(endpoint) = self.highlight_endpoints.peek().copied() {
121            if endpoint.offset <= self.offset {
122                if endpoint.is_start {
123                    self.active_highlights.insert(endpoint.tag, endpoint.style);
124                } else {
125                    self.active_highlights.remove(&endpoint.tag);
126                }
127                self.highlight_endpoints.next();
128            } else {
129                next_highlight_endpoint = endpoint.offset;
130                break;
131            }
132        }
133
134        let chunk = self
135            .buffer_chunk
136            .get_or_insert_with(|| self.buffer_chunks.next().unwrap());
137        if chunk.text.is_empty() {
138            *chunk = self.buffer_chunks.next().unwrap();
139        }
140
141        let (prefix, suffix) = chunk
142            .text
143            .split_at(chunk.text.len().min(next_highlight_endpoint - self.offset));
144
145        chunk.text = suffix;
146        self.offset += prefix.len();
147        let mut prefix = Chunk {
148            text: prefix,
149            ..chunk.clone()
150        };
151        if !self.active_highlights.is_empty() {
152            let mut highlight_style = HighlightStyle::default();
153            for active_highlight in self.active_highlights.values() {
154                highlight_style.highlight(*active_highlight);
155            }
156            prefix.highlight_style = Some(highlight_style);
157        }
158        Some(prefix)
159    }
160}
161
162impl PartialOrd for HighlightEndpoint {
163    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
164        Some(self.cmp(other))
165    }
166}
167
168impl Ord for HighlightEndpoint {
169    fn cmp(&self, other: &Self) -> cmp::Ordering {
170        self.offset
171            .cmp(&other.offset)
172            .then_with(|| other.is_start.cmp(&self.is_start))
173    }
174}