Skip to main content

gpui_base/input/editor/
decorations.rs

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/// Geometric presentation for an editor range decoration.
11#[non_exhaustive]
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
13pub enum RangeDecorationStyle {
14    /// Fill the continuous visual range.
15    Fill,
16    /// Draw a continuous one-pixel frame around the visual range.
17    #[default]
18    Frame,
19}
20
21/// A geometric decoration over a UTF-8 byte range.
22#[derive(Clone, Debug, PartialEq)]
23pub struct RangeDecoration {
24    range: Range<usize>,
25    style: RangeDecorationStyle,
26    color: Option<Hsla>,
27}
28
29impl RangeDecoration {
30    /// Create a frame using the editor foreground color.
31    pub fn new(range: Range<usize>) -> Self {
32        Self {
33            range,
34            style: RangeDecorationStyle::default(),
35            color: None,
36        }
37    }
38
39    /// The half-open UTF-8 byte range supplied to this decoration.
40    pub fn range(&self) -> &Range<usize> {
41        &self.range
42    }
43
44    /// The geometric paint style.
45    pub fn style(&self) -> RangeDecorationStyle {
46        self.style
47    }
48
49    /// An application-owned color override, or `None` for the editor fallback.
50    pub fn color(&self) -> Option<Hsla> {
51        self.color
52    }
53
54    /// Choose a fill or frame without changing text layout.
55    pub fn with_style(mut self, style: RangeDecorationStyle) -> Self {
56        self.style = style;
57        self
58    }
59
60    /// Override the editor foreground fallback with an application-owned color.
61    pub fn with_color(mut self, color: Hsla) -> Self {
62        self.color = Some(color);
63        self
64    }
65}
66
67/// A presentation style applied to a UTF-8 byte range in an input.
68///
69/// This is the GPUI [`HighlightStyle`] counterpart of Monaco's
70/// [`IModelDeltaDecoration`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IModelDeltaDecoration.html).
71#[derive(Clone, Debug, PartialEq)]
72pub struct TextDecoration {
73    pub range: Range<usize>,
74    pub style: HighlightStyle,
75}
76
77impl TextDecoration {
78    /// Create a text decoration from a UTF-8 byte range and a GPUI style.
79    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/// An independently managed collection of [`TextDecoration`]s.
88///
89/// This is the GPUI Component counterpart of Monaco's
90/// [`IEditorDecorationsCollection`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IEditorDecorationsCollection.html).
91#[derive(Clone, Debug)]
92pub struct TextDecorationCollection {
93    state: WeakEntity<InputBaseState<EditorMode>>,
94    id: DecorationCollectionId,
95}
96
97impl TextDecorationCollection {
98    /// Replace all decorations in this collection.
99    ///
100    /// This corresponds to Monaco's
101    /// [`IEditorDecorationsCollection.set`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IEditorDecorationsCollection.html#set).
102    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    /// Add decorations to this collection.
112    ///
113    /// This corresponds to Monaco's
114    /// [`IEditorDecorationsCollection.append`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IEditorDecorationsCollection.html#append).
115    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    /// Remove all decorations from this collection.
125    ///
126    /// This corresponds to Monaco's
127    /// [`IEditorDecorationsCollection.clear`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IEditorDecorationsCollection.html#clear).
128    pub fn clear(&self, cx: &mut App) {
129        self.set(Vec::new(), cx);
130    }
131
132    /// Return the UTF-8 byte ranges in this collection.
133    ///
134    /// This corresponds to Monaco's
135    /// [`IEditorDecorationsCollection.getRanges`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.IEditorDecorationsCollection.html#getRanges).
136    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/// An independently managed collection of geometric range decorations.
153///
154/// Clones address the same collection. Dropping a handle does not clear it; use
155/// [`Self::clear`] to empty it or [`Self::dispose`] to release it permanently.
156/// Operations on a disposed collection or a dropped editor are harmless no-ops.
157#[derive(Clone, Debug)]
158pub struct RangeDecorationCollection {
159    state: WeakEntity<InputBaseState<EditorMode>>,
160    id: DecorationCollectionId,
161}
162
163impl RangeDecorationCollection {
164    /// Replace only this owner's decorations, clipping ranges to UTF-8 boundaries.
165    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    /// Append decorations, preserving their paint order.
175    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    /// Empty this collection without invalidating its handles.
185    pub fn clear(&self, cx: &mut App) {
186        self.set(Vec::new(), cx);
187    }
188
189    /// Release this collection, invalidating all of its cloned handles.
190    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    /// Read tracked UTF-8 byte ranges in insertion order.
205    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
221/// Both text styles and geometric decorations share normalization and edit affinity.
222pub(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
245/// A balanced interval index over stable insertion-order entries. Each midpoint
246/// stores the maximum end of its subtree, so one document-spanning decoration
247/// does not force a scan of every preceding decoration on each frame.
248struct 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    // Returns the number of visited nodes, allowing deterministic complexity tests.
280    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            // Anchor transforms are monotone. Preserve start ordering and remap
385            // removed entries rather than sorting on every keystroke: O(n).
386            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    /// Query visible buffer spans (not the intervening folded-away text).
408    /// Rebuilds happen on mutations, never in layout/paint. Preserve owner/item
409    /// order after deduplicating ranges crossing multiple visible lines.
410    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            // Reject reversed ranges before clipping, which could otherwise turn a
481            // reversed pair within a multibyte character into a nonempty range.
482            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    /// Create an independently owned collection of geometric range decorations.
498    ///
499    /// Ranges use UTF-8 byte offsets and the same tracking as text decorations:
500    /// insertion at either edge does not expand the range, insertion inside does,
501    /// replacement clips overlapping anchors, and deletion removes empty ranges.
502    /// Undo, redo, whole-document replacement and formatting apply these same edit
503    /// transforms; decorations themselves are not undo history, so deleted ranges
504    /// are not resurrected by undo. Folding changes projection, not stored ranges.
505    ///
506    /// Fills paint behind frames; within each style, later collections/items paint
507    /// over earlier ones. Neither affects text layout, hit testing or focus. The
508    /// default color is the editor foreground (12% opacity for fills).
509    /// Collections live until explicitly disposed or the editor is dropped.
510    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    /// Create an independently managed collection of text decorations.
527    ///
528    /// This follows Monaco's
529    /// [`createDecorationsCollection`](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor_editor_api.editor.ICodeEditor.html#createDecorationsCollection)
530    /// ownership model. Ranges use UTF-8 byte offsets into [`Self::value`].
531    ///
532    /// Decoration ranges follow text edits and do not need to be set again
533    /// after each change. Insertions at a range boundary do not expand the
534    /// range, matching Monaco's
535    /// [`NeverGrowsWhenTypingAtEdges`](https://microsoft.github.io/monaco-editor/typedoc/enums/editor_editor_api.editor.TrackedRangeStickiness.html#NeverGrowsWhenTypingAtEdges)
536    /// behavior. Decorations are not rendered while the input is masked.
537    /// Collections live until their [`InputBaseState`] is dropped.
538    ///
539    /// Collections are layered in insertion order; the first collection wins
540    /// when overlapping decorations set the same [`HighlightStyle`] property.
541    /// Callers should avoid conflicting overlaps within one collection.
542    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), // hidden in a fold
599            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}