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