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}