custom_highlights.rs

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