1use crate::input::EditorMode;
2use std::{collections::BTreeMap, ops::Range};
3
4use gpui::{App, Context, HighlightStyle, Hsla, WeakEntity};
5use ropey::Rope;
6use sum_tree::Bias;
7
8use super::{InputBaseState, RopeExt as _};
9
10#[non_exhaustive]
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub enum RangeDecorationStyle {
14 Fill,
16 #[default]
18 Frame,
19}
20
21#[derive(Clone, Debug, PartialEq)]
23pub struct RangeDecoration {
24 range: Range<usize>,
25 style: RangeDecorationStyle,
26 color: Option<Hsla>,
27}
28
29impl RangeDecoration {
30 pub fn new(range: Range<usize>) -> Self {
32 Self {
33 range,
34 style: RangeDecorationStyle::default(),
35 color: None,
36 }
37 }
38
39 pub fn range(&self) -> &Range<usize> {
41 &self.range
42 }
43
44 pub fn style(&self) -> RangeDecorationStyle {
46 self.style
47 }
48
49 pub fn color(&self) -> Option<Hsla> {
51 self.color
52 }
53
54 pub fn with_style(mut self, style: RangeDecorationStyle) -> Self {
56 self.style = style;
57 self
58 }
59
60 pub fn with_color(mut self, color: Hsla) -> Self {
62 self.color = Some(color);
63 self
64 }
65}
66
67#[derive(Clone, Debug, PartialEq)]
72pub struct TextDecoration {
73 pub range: Range<usize>,
74 pub style: HighlightStyle,
75}
76
77impl TextDecoration {
78 pub fn new(range: Range<usize>, style: HighlightStyle) -> Self {
80 Self { range, style }
81 }
82}
83
84#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
85struct DecorationCollectionId(usize);
86
87#[derive(Clone, Debug)]
92pub struct TextDecorationCollection {
93 state: WeakEntity<InputBaseState<EditorMode>>,
94 id: DecorationCollectionId,
95}
96
97impl TextDecorationCollection {
98 pub fn set(&self, decorations: Vec<TextDecoration>, cx: &mut App) {
103 let _ = self.state.update(cx, |state, cx| {
104 let decorations = normalize(&state.text, decorations);
105 if state.extras.decorations.set(self.id, decorations) {
106 cx.notify();
107 }
108 });
109 }
110
111 pub fn append(&self, decorations: Vec<TextDecoration>, cx: &mut App) {
116 let _ = self.state.update(cx, |state, cx| {
117 let decorations = normalize(&state.text, decorations);
118 if state.extras.decorations.append(self.id, decorations) {
119 cx.notify();
120 }
121 });
122 }
123
124 pub fn clear(&self, cx: &mut App) {
129 self.set(Vec::new(), cx);
130 }
131
132 pub fn get_ranges(&self, cx: &App) -> Vec<Range<usize>> {
137 self.state
138 .read_with(cx, |state, _| {
139 state
140 .extras
141 .decorations
142 .get(self.id)
143 .unwrap_or_default()
144 .iter()
145 .map(|decoration| decoration.range.clone())
146 .collect()
147 })
148 .unwrap_or_default()
149 }
150}
151
152#[derive(Clone, Debug)]
158pub struct RangeDecorationCollection {
159 state: WeakEntity<InputBaseState<EditorMode>>,
160 id: DecorationCollectionId,
161}
162
163impl RangeDecorationCollection {
164 pub fn set(&self, decorations: Vec<RangeDecoration>, cx: &mut App) {
166 let _ = self.state.update(cx, |state, cx| {
167 let decorations = normalize(&state.text, decorations);
168 if state.extras.range_decorations.set(self.id, decorations) {
169 cx.notify();
170 }
171 });
172 }
173
174 pub fn append(&self, decorations: Vec<RangeDecoration>, cx: &mut App) {
176 let _ = self.state.update(cx, |state, cx| {
177 let decorations = normalize(&state.text, decorations);
178 if state.extras.range_decorations.append(self.id, decorations) {
179 cx.notify();
180 }
181 });
182 }
183
184 pub fn clear(&self, cx: &mut App) {
186 self.set(Vec::new(), cx);
187 }
188
189 pub fn dispose(&self, cx: &mut App) {
191 let _ = self.state.update(cx, |state, cx| {
192 if state
193 .extras
194 .range_decorations
195 .entries
196 .remove(&self.id)
197 .is_some()
198 {
199 cx.notify();
200 }
201 });
202 }
203
204 pub fn get_ranges(&self, cx: &App) -> Vec<Range<usize>> {
206 self.state
207 .read_with(cx, |state, _| {
208 state
209 .extras
210 .range_decorations
211 .get(self.id)
212 .unwrap_or_default()
213 .iter()
214 .map(|decoration| decoration.range.clone())
215 .collect()
216 })
217 .unwrap_or_default()
218 }
219}
220
221pub(crate) trait TrackedDecoration {
223 fn range(&self) -> &Range<usize>;
224 fn range_mut(&mut self) -> &mut Range<usize>;
225}
226
227impl TrackedDecoration for TextDecoration {
228 fn range(&self) -> &Range<usize> {
229 &self.range
230 }
231 fn range_mut(&mut self) -> &mut Range<usize> {
232 &mut self.range
233 }
234}
235
236impl TrackedDecoration for RangeDecoration {
237 fn range(&self) -> &Range<usize> {
238 &self.range
239 }
240 fn range_mut(&mut self) -> &mut Range<usize> {
241 &mut self.range
242 }
243}
244
245struct DecorationIndex {
249 indices: Vec<usize>,
250 max_ends: Vec<usize>,
251}
252
253impl DecorationIndex {
254 fn new<T: TrackedDecoration>(decorations: &[T]) -> Self {
255 let mut indices: Vec<_> = (0..decorations.len()).collect();
256 indices.sort_unstable_by_key(|&ix| (decorations[ix].range().start, ix));
257 let mut index = Self {
258 max_ends: vec![0; indices.len()],
259 indices,
260 };
261 index.build(decorations, 0..decorations.len());
262 index
263 }
264
265 fn build<T: TrackedDecoration>(&mut self, decorations: &[T], span: Range<usize>) -> usize {
266 if span.is_empty() {
267 return 0;
268 }
269 let mid = span.start + span.len() / 2;
270 let end = decorations[self.indices[mid]]
271 .range()
272 .end
273 .max(self.build(decorations, span.start..mid))
274 .max(self.build(decorations, mid + 1..span.end));
275 self.max_ends[mid] = end;
276 end
277 }
278
279 fn query<T: TrackedDecoration>(
281 &self,
282 decorations: &[T],
283 span: Range<usize>,
284 range: &Range<usize>,
285 matches: &mut Vec<usize>,
286 ) -> usize {
287 if span.is_empty() || range.is_empty() {
288 return 0;
289 }
290 let mid = span.start + span.len() / 2;
291 if self.max_ends[mid] <= range.start {
292 return 1;
293 }
294 let mut visited = 1 + self.query(decorations, span.start..mid, range, matches);
295 let ix = self.indices[mid];
296 let candidate = decorations[ix].range();
297 if candidate.start < range.end {
298 if candidate.end > range.start {
299 matches.push(ix);
300 }
301 visited += self.query(decorations, mid + 1..span.end, range, matches);
302 }
303 visited
304 }
305}
306
307struct DecorationEntries<T> {
308 decorations: Vec<T>,
309 index: DecorationIndex,
310}
311
312impl<T: TrackedDecoration> DecorationEntries<T> {
313 fn new(decorations: Vec<T>) -> Self {
314 let index = DecorationIndex::new(&decorations);
315 Self { decorations, index }
316 }
317
318 fn reindex(&mut self) {
319 self.index = DecorationIndex::new(&self.decorations);
320 }
321}
322
323pub(crate) struct DecorationCollections<T = TextDecoration> {
324 entries: BTreeMap<DecorationCollectionId, DecorationEntries<T>>,
325 next_id: usize,
326}
327
328impl<T> Default for DecorationCollections<T> {
329 fn default() -> Self {
330 Self {
331 entries: BTreeMap::new(),
332 next_id: 0,
333 }
334 }
335}
336
337impl<T: TrackedDecoration> DecorationCollections<T> {
338 fn create(&mut self, decorations: Vec<T>) -> DecorationCollectionId {
339 let id = DecorationCollectionId(self.next_id);
340 self.next_id += 1;
341 self.entries.insert(id, DecorationEntries::new(decorations));
342 id
343 }
344
345 fn set(&mut self, id: DecorationCollectionId, decorations: Vec<T>) -> bool {
346 let Some(current) = self.entries.get_mut(&id) else {
347 return false;
348 };
349 *current = DecorationEntries::new(decorations);
350 true
351 }
352
353 fn append(&mut self, id: DecorationCollectionId, decorations: Vec<T>) -> bool {
354 let Some(current) = self.entries.get_mut(&id) else {
355 return false;
356 };
357 current.decorations.extend(decorations);
358 current.reindex();
359 true
360 }
361
362 fn get(&self, id: DecorationCollectionId) -> Option<&[T]> {
363 self.entries
364 .get(&id)
365 .map(|entry| entry.decorations.as_slice())
366 }
367
368 pub(super) fn adjust_for_edit(&mut self, edited_range: &Range<usize>, inserted_len: usize) {
369 for entry in self.entries.values_mut() {
370 let len = entry.decorations.len();
371 if len == 0 || entry.index.max_ends[len / 2] <= edited_range.start {
372 continue;
373 }
374 let mut remap = Vec::with_capacity(len);
375 let mut retained = 0;
376 entry.decorations.retain_mut(|decoration| {
377 *decoration.range_mut() =
378 adjust_range_for_edit(decoration.range(), edited_range, inserted_len);
379 let keep = !decoration.range().is_empty();
380 remap.push(if keep { retained } else { usize::MAX });
381 retained += usize::from(keep);
382 keep
383 });
384 entry.index.indices.retain_mut(|ix| {
387 *ix = remap[*ix];
388 *ix != usize::MAX
389 });
390 entry.index.max_ends.resize(retained, 0);
391 entry.index.build(&entry.decorations, 0..retained);
392 }
393 }
394
395 pub(super) fn clear(&mut self) {
396 for entry in self.entries.values_mut() {
397 *entry = DecorationEntries::new(Vec::new());
398 }
399 }
400
401 pub(super) fn iter(&self) -> impl Iterator<Item = &[T]> {
402 self.entries
403 .values()
404 .map(|entry| entry.decorations.as_slice())
405 }
406
407 pub(super) fn intersecting(&self, ranges: &[Range<usize>]) -> Vec<&T> {
411 let mut result = Vec::new();
412 for entry in self.entries.values() {
413 let mut matches = Vec::new();
414 for range in ranges {
415 entry.index.query(
416 &entry.decorations,
417 0..entry.decorations.len(),
418 range,
419 &mut matches,
420 );
421 }
422 matches.sort_unstable();
423 matches.dedup();
424 result.extend(matches.into_iter().map(|ix| &entry.decorations[ix]));
425 }
426 result
427 }
428}
429
430fn adjust_range_for_edit(
431 range: &Range<usize>,
432 edited_range: &Range<usize>,
433 inserted_len: usize,
434) -> Range<usize> {
435 let removed_len = edited_range.end.saturating_sub(edited_range.start);
436 let shift = |offset: usize| {
437 if inserted_len >= removed_len {
438 offset.saturating_add(inserted_len - removed_len)
439 } else {
440 offset.saturating_sub(removed_len - inserted_len)
441 }
442 };
443
444 if edited_range.is_empty() {
445 let start = if range.start < edited_range.start {
446 range.start
447 } else {
448 shift(range.start)
449 };
450 let end = if range.end <= edited_range.start {
451 range.end
452 } else {
453 shift(range.end)
454 };
455 return start..end;
456 }
457
458 let inserted_end = edited_range.start + inserted_len;
459 let start = if range.start <= edited_range.start {
460 range.start
461 } else if range.start >= edited_range.end {
462 shift(range.start)
463 } else {
464 edited_range.start
465 };
466 let end = if range.end <= edited_range.start {
467 range.end
468 } else if range.end >= edited_range.end {
469 shift(range.end)
470 } else {
471 inserted_end
472 };
473 start..end
474}
475
476fn normalize<T: TrackedDecoration>(text: &Rope, decorations: Vec<T>) -> Vec<T> {
477 decorations
478 .into_iter()
479 .filter_map(|mut decoration| {
480 if decoration.range().is_empty() {
483 return None;
484 }
485 let range = text.clip_offset(decoration.range().start, Bias::Left)
486 ..text.clip_offset(decoration.range().end, Bias::Right);
487 if range.is_empty() {
488 return None;
489 }
490 *decoration.range_mut() = range;
491 Some(decoration)
492 })
493 .collect()
494}
495
496impl InputBaseState<EditorMode> {
497 pub fn create_range_decorations_collection(
511 &mut self,
512 decorations: Vec<RangeDecoration>,
513 cx: &mut Context<Self>,
514 ) -> RangeDecorationCollection {
515 let id = self
516 .extras
517 .range_decorations
518 .create(normalize(&self.text, decorations));
519 cx.notify();
520 RangeDecorationCollection {
521 state: cx.entity().downgrade(),
522 id,
523 }
524 }
525
526 pub fn create_decorations_collection(
543 &mut self,
544 decorations: Vec<TextDecoration>,
545 cx: &mut Context<Self>,
546 ) -> TextDecorationCollection {
547 let decorations = normalize(&self.text, decorations);
548 let id = self.extras.decorations.create(decorations);
549 cx.notify();
550 TextDecorationCollection {
551 state: cx.entity().downgrade(),
552 id,
553 }
554 }
555}
556
557#[cfg(test)]
558mod tests {
559 use super::*;
560
561 #[test]
562 fn geometric_collections_share_utf8_normalization_and_edit_affinity() {
563 let mut collections = DecorationCollections::<RangeDecoration>::default();
564 let text = Rope::from("héllo world");
565 let first = collections.create(normalize(
566 &text,
567 vec![
568 RangeDecoration::new(2..4),
569 RangeDecoration::new(2..1),
570 RangeDecoration::new(100..200),
571 ],
572 ));
573 let second = collections.create(normalize(&text, vec![RangeDecoration::new(7..12)]));
574 assert_eq!(collections.get(first).unwrap()[0].range(), &(1..4));
575 assert_eq!(collections.get(first).unwrap().len(), 1);
576 collections.adjust_for_edit(&(1..1), 2);
577 assert_eq!(collections.get(first).unwrap()[0].range(), &(3..6));
578 collections.adjust_for_edit(&(6..6), 1);
579 assert_eq!(collections.get(first).unwrap()[0].range(), &(3..6));
580 collections.adjust_for_edit(&(4..4), 2);
581 assert_eq!(collections.get(first).unwrap()[0].range(), &(3..8));
582 collections.adjust_for_edit(&(3..8), 0);
583 assert!(collections.get(first).unwrap().is_empty());
584 assert!(!collections.get(second).unwrap().is_empty());
585 collections.entries.remove(&first);
586 let third = collections.create(vec![]);
587 assert_ne!(third, first);
588 assert!(!collections.set(first, vec![RangeDecoration::new(0..1)]));
589 assert!(collections.get(second).is_some());
590 }
591
592 #[test]
593 fn visible_query_preserves_layers_and_skips_folded_spans() {
594 let mut collections = DecorationCollections::<RangeDecoration>::default();
595 let first = collections.create(vec![
596 RangeDecoration::new(90..100),
597 RangeDecoration::new(0..100),
598 RangeDecoration::new(40..50), RangeDecoration::new(0..5),
600 ]);
601 collections.create(vec![RangeDecoration::new(2..4)]);
602 let ranges = |collections: &DecorationCollections<RangeDecoration>| {
603 collections
604 .intersecting(&[0..5, 90..100])
605 .iter()
606 .map(|d| d.range().clone())
607 .collect::<Vec<_>>()
608 };
609 assert_eq!(ranges(&collections), vec![90..100, 0..100, 0..5, 2..4]);
610 collections.adjust_for_edit(&(0..0), 1);
611 assert_eq!(ranges(&collections), vec![91..101, 1..101, 1..6, 3..5]);
612 collections.set(first, vec![RangeDecoration::new(50..60)]);
613 assert_eq!(ranges(&collections), vec![3..5]);
614 }
615
616 #[test]
617 fn interval_index_culls_large_collections_even_with_a_spanning_range() {
618 let mut decorations: Vec<_> = (0..100_000)
619 .map(|ix| RangeDecoration::new(ix * 10..ix * 10 + 5))
620 .collect();
621 decorations.push(RangeDecoration::new(0..1_000_000));
622 let index = DecorationIndex::new(&decorations);
623 for query in [
624 0..1,
625 500_000..500_020,
626 999_990..1_000_001,
627 1_000_000..1_000_010,
628 ] {
629 let mut matches = Vec::new();
630 let visited = index.query(&decorations, 0..decorations.len(), &query, &mut matches);
631 matches.sort_unstable();
632 let expected: Vec<_> = decorations
633 .iter()
634 .enumerate()
635 .filter_map(|(ix, d)| {
636 (d.range.start < query.end && d.range.end > query.start).then_some(ix)
637 })
638 .collect();
639 assert_eq!(matches, expected);
640 assert!(visited < 100, "visited {visited} nodes for {query:?}");
641 }
642 }
643
644 #[test]
645 fn interval_index_matches_linear_reference_for_overlaps_and_mutations() {
646 let mut collections = DecorationCollections::<RangeDecoration>::default();
647 let id = collections.create(
648 (0..512)
649 .map(|ix| {
650 let start = (ix * 37) % 997;
651 RangeDecoration::new(start..start + ix % 61 + 1)
652 })
653 .collect(),
654 );
655 for edit in [0..0, 300..450, 900..1100] {
656 collections.adjust_for_edit(&edit, 3);
657 for start in (0..1100).step_by(13) {
658 let query = start..start + 17;
659 let expected: Vec<_> = collections
660 .get(id)
661 .unwrap()
662 .iter()
663 .filter(|d| d.range.start < query.end && d.range.end > query.start)
664 .map(|d| d.range.clone())
665 .collect();
666 let actual: Vec<_> = collections
667 .intersecting(&[query])
668 .iter()
669 .map(|d| d.range.clone())
670 .collect();
671 assert_eq!(actual, expected);
672 }
673 }
674 }
675
676 #[test]
677 fn collections_are_independent_and_ranges_are_clipped() {
678 let text = Rope::from("héllo");
679 let first_style = HighlightStyle {
680 font_weight: Some(gpui::FontWeight::BOLD),
681 ..Default::default()
682 };
683 let second_style = HighlightStyle {
684 background_color: Some(gpui::red()),
685 ..Default::default()
686 };
687 let mut collections = DecorationCollections::default();
688
689 let first = collections.create(normalize(
690 &text,
691 vec![TextDecoration::new(2..4, first_style)],
692 ));
693 let second = collections.create(normalize(
694 &text,
695 vec![TextDecoration::new(5..100, second_style)],
696 ));
697
698 assert_ne!(first, second);
699 assert_eq!(
700 collections.get(first),
701 Some(&[TextDecoration::new(1..4, first_style)][..])
702 );
703 assert_eq!(
704 collections.get(second),
705 Some(&[TextDecoration::new(5..6, second_style)][..])
706 );
707
708 assert!(collections.append(first, vec![TextDecoration::new(4..5, second_style)]));
709 assert_eq!(
710 collections.get(first),
711 Some(
712 &[
713 TextDecoration::new(1..4, first_style),
714 TextDecoration::new(4..5, second_style),
715 ][..]
716 )
717 );
718
719 assert!(collections.set(first, Vec::new()));
720 assert_eq!(collections.get(first), Some(&[][..]));
721 assert_eq!(
722 collections.get(second),
723 Some(&[TextDecoration::new(5..6, second_style)][..])
724 );
725 }
726
727 #[test]
728 fn decoration_ranges_follow_text_edits() {
729 let style = HighlightStyle::default();
730 let mut collections = DecorationCollections::default();
731 let collection = collections.create(vec![TextDecoration::new(2..6, style)]);
732
733 collections.adjust_for_edit(&(0..0), 2);
734 assert_eq!(
735 collections.get(collection),
736 Some(&[TextDecoration::new(4..8, style)][..])
737 );
738
739 collections.adjust_for_edit(&(6..6), 2);
740 assert_eq!(
741 collections.get(collection),
742 Some(&[TextDecoration::new(4..10, style)][..])
743 );
744
745 collections.adjust_for_edit(&(4..10), 3);
746 assert_eq!(
747 collections.get(collection),
748 Some(&[TextDecoration::new(4..7, style)][..])
749 );
750
751 assert_eq!(adjust_range_for_edit(&(2..6), &(2..2), 2), 4..8);
752 assert_eq!(adjust_range_for_edit(&(2..6), &(6..6), 2), 2..6);
753 assert_eq!(adjust_range_for_edit(&(2..6), &(2..6), 3), 2..5);
754 }
755}