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}