Skip to main content

sequoia_buffer/
styled.rs

1use std::ops::Range;
2
3use crate::attr::{SpanAttr, StyledSpan, normalize_spans};
4use crate::piece::TextBuffer;
5
6#[derive(Clone, Debug, Default)]
7pub struct StyledBuffer {
8    text: TextBuffer,
9    spans: Vec<StyledSpan>,
10}
11
12impl StyledBuffer {
13    pub fn new() -> Self {
14        Self {
15            text: TextBuffer::new(),
16            spans: Vec::new(),
17        }
18    }
19
20    pub fn from(text: impl Into<String>) -> Self {
21        Self {
22            text: TextBuffer::from(text),
23            spans: Vec::new(),
24        }
25    }
26
27    pub fn from_parts(text: TextBuffer, mut spans: Vec<StyledSpan>) -> Self {
28        let len = text.len_bytes();
29        for s in &mut spans {
30            s.start = text.snap(s.start.min(len));
31            s.end = text.snap(s.end.min(len).max(s.start));
32        }
33        spans.retain(|s| !s.is_empty() && !s.attr.is_plain());
34        normalize_spans(&mut spans);
35        Self { text, spans }
36    }
37
38    pub fn text(&self) -> &TextBuffer {
39        &self.text
40    }
41
42    pub fn text_mut(&mut self) -> &mut TextBuffer {
43        &mut self.text
44    }
45
46    pub fn spans(&self) -> &[StyledSpan] {
47        &self.spans
48    }
49
50    pub fn len_bytes(&self) -> usize {
51        self.text.len_bytes()
52    }
53
54    pub fn len_chars(&self) -> usize {
55        self.text.len_chars()
56    }
57
58    pub fn is_empty(&self) -> bool {
59        self.text.is_empty()
60    }
61
62    pub fn to_string(&self) -> String {
63        self.text.to_string()
64    }
65
66    pub fn snap(&self, offset: usize) -> usize {
67        self.text.snap(offset)
68    }
69
70    pub fn lines(&self) -> &crate::LineIndex {
71        self.text.lines()
72    }
73
74    pub fn substring(&self, range: Range<usize>) -> String {
75        self.text.substring(range)
76    }
77
78    pub fn insert(&mut self, pos: usize, text: &str, attr: Option<SpanAttr>) {
79        if text.is_empty() {
80            return;
81        }
82        let len = self.text.len_bytes();
83        let pos = self.text.snap(pos.min(len));
84        let ins_len = text.len();
85
86        match attr {
87            None => self.insert_inherit(pos, ins_len),
88            Some(a) if a.is_plain() => self.shift_for_insert(pos, ins_len, None),
89            Some(a) => self.shift_for_insert(pos, ins_len, Some(a)),
90        }
91        self.text.insert(pos, text);
92        normalize_spans(&mut self.spans);
93        debug_assert!(self.check_spans_sorted());
94    }
95
96    fn shift_for_insert(&mut self, pos: usize, ins_len: usize, middle: Option<SpanAttr>) {
97        let host = self.spans.iter().position(|s| s.start < pos && pos < s.end);
98        if let Some(idx) = host {
99            let (hs, he, hattr) = {
100                let h = &self.spans[idx];
101                (h.start, h.end, h.attr.clone())
102            };
103
104            let mut out = Vec::with_capacity(self.spans.len() + 1);
105            for s in self.spans.iter().take(idx) {
106                out.push(s.clone());
107            }
108            out.push(StyledSpan::new(hs, pos, hattr.clone()));
109            if let Some(m) = middle {
110                if m == hattr {
111                    out.pop();
112                    out.push(StyledSpan::new(hs, he + ins_len, hattr));
113                } else {
114                    out.push(StyledSpan::new(pos, pos + ins_len, m));
115                    out.push(StyledSpan::new(pos + ins_len, he + ins_len, hattr));
116                }
117            } else {
118                out.push(StyledSpan::new(pos + ins_len, he + ins_len, hattr));
119            }
120            for s in self.spans.iter().skip(idx + 1) {
121                out.push(StyledSpan::new(
122                    s.start + ins_len,
123                    s.end + ins_len,
124                    s.attr.clone(),
125                ));
126            }
127            self.spans = out;
128            return;
129        }
130
131        let mut out = Vec::with_capacity(self.spans.len() + 1);
132        for s in &self.spans {
133            if s.start >= pos {
134                out.push(StyledSpan::new(
135                    s.start + ins_len,
136                    s.end + ins_len,
137                    s.attr.clone(),
138                ));
139            } else {
140                debug_assert!(s.end <= pos);
141                out.push(s.clone());
142            }
143        }
144        if let Some(m) = middle {
145            out.push(StyledSpan::new(pos, pos + ins_len, m));
146            out.sort_by_key(|s| (s.start, s.end));
147        } else {
148        }
149        self.spans = out;
150    }
151
152    fn insert_inherit(&mut self, pos: usize, ins_len: usize) {
153        if let Some(idx) = self.spans.iter().position(|s| s.start < pos && pos < s.end) {
154            let (hs, he, hattr) = {
155                let h = &self.spans[idx];
156                (h.start, h.end, h.attr.clone())
157            };
158            let mut out = Vec::with_capacity(self.spans.len());
159            for s in self.spans.iter().take(idx) {
160                out.push(s.clone());
161            }
162            out.push(StyledSpan::new(hs, he + ins_len, hattr));
163            for s in self.spans.iter().skip(idx + 1) {
164                out.push(StyledSpan::new(
165                    s.start + ins_len,
166                    s.end + ins_len,
167                    s.attr.clone(),
168                ));
169            }
170            self.spans = out;
171            return;
172        }
173
174        if let Some(idx) = self.spans.iter().position(|s| s.end == pos) {
175            let mut out = self.spans.clone();
176            out[idx].end += ins_len;
177
178            for s in &mut out {
179                if s.start >= pos {
180                    s.start += ins_len;
181                    s.end += ins_len;
182                }
183            }
184            self.spans = out;
185            return;
186        }
187        if let Some(idx) = self.spans.iter().position(|s| s.start == pos) {
188            let mut out = self.spans.clone();
189
190            for (i, s) in out.iter_mut().enumerate() {
191                if i == idx {
192                    s.end += ins_len;
193                } else if s.start >= pos {
194                    s.start += ins_len;
195                    s.end += ins_len;
196                }
197            }
198            self.spans = out;
199            return;
200        }
201        self.shift_for_insert(pos, ins_len, None);
202    }
203
204    pub fn delete(&mut self, range: Range<usize>) -> (String, Vec<StyledSpan>) {
205        let len = self.text.len_bytes();
206        let mut s = range.start.min(len);
207        let mut e = range.end.min(len).max(s);
208        s = self.text.snap(s);
209        e = self.text.snap(e).max(s);
210        if s == e {
211            return (String::new(), Vec::new());
212        }
213        let d = e - s;
214        let mut removed_spans = Vec::new();
215        let mut kept = Vec::with_capacity(self.spans.len());
216        for sp in &self.spans {
217            if sp.end <= s {
218                kept.push(sp.clone());
219            } else if sp.start >= e {
220                kept.push(StyledSpan::new(sp.start - d, sp.end - d, sp.attr.clone()));
221            } else if sp.start < s && sp.end > e {
222                kept.push(StyledSpan::new(sp.start, sp.end - d, sp.attr.clone()));
223                if let Some(c) = sp.clipped(s..e) {
224                    removed_spans.push(c);
225                }
226            } else if sp.start < s {
227                debug_assert!(sp.end > s && sp.end <= e);
228                kept.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
229                if let Some(c) = sp.clipped(s..e) {
230                    removed_spans.push(c);
231                }
232            } else if sp.end > e {
233                debug_assert!(sp.start >= s && sp.start < e);
234                kept.push(StyledSpan::new(s, sp.end - d, sp.attr.clone()));
235                if let Some(c) = sp.clipped(s..e) {
236                    removed_spans.push(c);
237                }
238            } else {
239                removed_spans.push(sp.clone());
240            }
241        }
242        let removed_text = self.text.delete(s..e);
243        self.spans = kept;
244        normalize_spans(&mut self.spans);
245        debug_assert!(self.check_spans_sorted());
246        (removed_text, removed_spans)
247    }
248
249    pub fn replace(
250        &mut self,
251        range: Range<usize>,
252        text: &str,
253        attr: Option<SpanAttr>,
254    ) -> (String, Vec<StyledSpan>) {
255        let len = self.text.len_bytes();
256        let s = self.text.snap(range.start.min(len));
257        let e = self.text.snap(range.end.min(len).max(s));
258        if text.is_empty() {
259            return self.delete(s..e);
260        }
261        let (removed_text, removed_spans) = self.delete(s..e);
262        self.insert(s, text, attr);
263        (removed_text, removed_spans)
264    }
265
266    pub fn set_attr(&mut self, range: Range<usize>, attr: SpanAttr) {
267        if attr.is_plain() {
268            self.clear_attr(range);
269            return;
270        }
271        let len = self.text.len_bytes();
272        let mut s = range.start.min(len);
273        let mut e = range.end.min(len).max(s);
274        s = self.text.snap(s);
275        e = self.text.snap(e).max(s);
276        if s == e {
277            return;
278        }
279        let mut out = Vec::with_capacity(self.spans.len() + 1);
280        for sp in &self.spans {
281            if sp.end <= s || sp.start >= e {
282                out.push(sp.clone());
283            } else {
284                if sp.start < s {
285                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
286                }
287                if sp.end > e {
288                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
289                }
290            }
291        }
292        out.push(StyledSpan::new(s, e, attr));
293        normalize_spans(&mut out);
294        self.spans = out;
295        debug_assert!(self.check_spans_sorted());
296    }
297
298    pub fn clear_attr(&mut self, range: Range<usize>) {
299        let len = self.text.len_bytes();
300        let mut s = range.start.min(len);
301        let mut e = range.end.min(len).max(s);
302        s = self.text.snap(s);
303        e = self.text.snap(e).max(s);
304        if s == e {
305            return;
306        }
307        let mut out = Vec::with_capacity(self.spans.len());
308        for sp in &self.spans {
309            if sp.end <= s || sp.start >= e {
310                out.push(sp.clone());
311            } else {
312                if sp.start < s {
313                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
314                }
315                if sp.end > e {
316                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
317                }
318            }
319        }
320        self.spans = out;
321        debug_assert!(self.check_spans_sorted());
322    }
323
324    pub fn spans_for_line(&self, line_range: Range<usize>) -> Vec<StyledSpan> {
325        if line_range.start >= line_range.end {
326            return Vec::new();
327        }
328        self.spans
329            .iter()
330            .filter(|s| s.start < line_range.end && line_range.start < s.end)
331            .filter_map(|s| s.clipped(line_range.clone()))
332            .collect()
333    }
334
335    pub fn apply_attr_range(&mut self, range: Range<usize>, spans: &[StyledSpan]) {
336        let len = self.text.len_bytes();
337        let mut s = range.start.min(len);
338        let mut e = range.end.min(len).max(s);
339        s = self.text.snap(s);
340        e = self.text.snap(e).max(s);
341        let mut out = Vec::with_capacity(self.spans.len() + spans.len());
342        for sp in &self.spans {
343            if sp.end <= s || sp.start >= e {
344                out.push(sp.clone());
345            } else {
346                if sp.start < s {
347                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
348                }
349                if sp.end > e {
350                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
351                }
352            }
353        }
354        for sp in spans {
355            if let Some(c) = sp.clipped(s..e)
356                && !c.attr.is_plain()
357            {
358                out.push(c);
359            }
360        }
361        normalize_spans(&mut out);
362        self.spans = out;
363        debug_assert!(self.check_spans_sorted());
364    }
365
366    pub fn replace_spans(&mut self, mut spans: Vec<StyledSpan>) {
367        let len = self.text.len_bytes();
368        for s in &mut spans {
369            s.start = self.text.snap(s.start.min(len));
370            s.end = self.text.snap(s.end.min(len).max(s.start));
371        }
372        spans.retain(|s| !s.is_empty() && !s.attr.is_plain());
373        normalize_spans(&mut spans);
374        self.spans = spans;
375    }
376
377    fn check_spans_sorted(&self) -> bool {
378        let len = self.text.len_bytes();
379        let mut prev_end = None;
380        for s in &self.spans {
381            if s.start >= s.end {
382                return false;
383            }
384            if s.end > len {
385                return false;
386            }
387
388            if self.text.snap(s.start) != s.start || self.text.snap(s.end) != s.end {
389                return false;
390            }
391            if let Some(p) = prev_end {
392                if s.start < p {
393                    return false;
394                }
395                if s.start == p && self.spans_first_eq_prev(s) {
396                    return false;
397                }
398            }
399            prev_end = Some(s.end);
400        }
401        true
402    }
403
404    fn spans_first_eq_prev(&self, _s: &StyledSpan) -> bool {
405        let mut prev: Option<&StyledSpan> = None;
406        for cur in &self.spans {
407            if let Some(p) = prev
408                && p.end == cur.start
409                && p.attr == cur.attr
410            {
411                return true;
412            }
413            prev = Some(cur);
414            if cur.start == _s.start && cur.end == _s.end {
415                break;
416            }
417        }
418        false
419    }
420
421    #[cfg(test)]
422    fn check_invariants(&self) {
423        assert!(self.check_spans_sorted(), "spans: {:?}", self.spans);
424    }
425}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    fn bold_span(s: usize, e: usize) -> StyledSpan {
432        StyledSpan::new(s, e, SpanAttr::bold())
433    }
434
435    #[test]
436    fn insert_delete_set_attr_roundtrip() {
437        let mut b = StyledBuffer::from("hello world");
438        b.set_attr(0..5, SpanAttr::bold());
439        assert_eq!(b.spans(), &[bold_span(0, 5)]);
440        let (t, sp) = b.delete(0..5);
441        assert_eq!(t, "hello");
442        assert_eq!(sp, vec![bold_span(0, 5)]);
443        assert!(b.spans().is_empty());
444        assert_eq!(b.to_string(), " world");
445
446        b.insert(0, "hello", Some(SpanAttr::plain()));
447        assert_eq!(b.to_string(), "hello world");
448        b.apply_attr_range(0..5, &[bold_span(0, 5)]);
449        assert_eq!(b.spans(), &[bold_span(0, 5)]);
450        b.check_invariants();
451    }
452
453    #[test]
454    fn neighbours_merge_after_set() {
455        let mut b = StyledBuffer::from("abcdef");
456        b.set_attr(0..2, SpanAttr::bold());
457        b.set_attr(2..4, SpanAttr::bold());
458        assert_eq!(b.spans(), &[bold_span(0, 4)]);
459        b.check_invariants();
460    }
461
462    #[test]
463    fn typing_inside_span_inherits() {
464        let mut b = StyledBuffer::from("hello");
465        b.set_attr(0..5, SpanAttr::bold());
466        b.insert(2, "X", None);
467        assert_eq!(b.to_string(), "heXllo");
468        assert_eq!(b.spans(), &[bold_span(0, 6)]);
469
470        let mut c = StyledBuffer::from("hello");
471        c.set_attr(0..5, SpanAttr::bold());
472        c.insert(2, "X", Some(SpanAttr::plain()));
473        assert_eq!(c.spans(), &[bold_span(0, 2), bold_span(3, 6)]);
474
475        let mut d = StyledBuffer::from("hello");
476        d.set_attr(0..5, SpanAttr::bold());
477        d.insert(2, "X", Some(SpanAttr::link("u")));
478        assert_eq!(d.spans().len(), 3);
479        assert_eq!(d.to_string(), "heXllo");
480        b.check_invariants();
481        c.check_invariants();
482        d.check_invariants();
483    }
484
485    #[test]
486    fn boundary_inserts_left_biased() {
487        let mut b = StyledBuffer::from("ab");
488        b.set_attr(0..1, SpanAttr::bold());
489        b.set_attr(1..2, SpanAttr::link("u"));
490
491        b.insert(1, "X", None);
492        assert_eq!(b.to_string(), "aXb");
493        assert_eq!(b.spans()[0], bold_span(0, 2));
494        b.check_invariants();
495    }
496
497    #[test]
498    fn grapheme_text_does_not_break_spans() {
499        let text = "a๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง๐Ÿ‡ต๐Ÿ‡ฑรฉ\r\nz";
500        let mut b = StyledBuffer::from(text);
501        let len = b.len_bytes();
502        b.set_attr(0..len, SpanAttr::bold());
503        assert_eq!(b.spans(), &[bold_span(0, len)]);
504
505        let mid = 2usize;
506        let snapped = b.snap(mid);
507        assert_eq!(b.snap(snapped), snapped);
508        b.insert(snapped, "Q", None);
509        assert!(b.snap(b.len_bytes()) == b.len_bytes());
510        let (t, _) = b.delete(0..1);
511        assert_eq!(t, "a");
512        b.check_invariants();
513
514        let flag = "๐Ÿ‡ต๐Ÿ‡ฑ";
515        let mut f = StyledBuffer::from(flag);
516        f.set_attr(0..flag.len(), SpanAttr::bold());
517        f.insert(4, "ยท", Some(SpanAttr::plain()));
518        assert_eq!(f.to_string(), "๐Ÿ‡ตยท๐Ÿ‡ฑ");
519        f.check_invariants();
520    }
521
522    #[test]
523    fn spans_for_line_clips() {
524        let mut b = StyledBuffer::from("ab\ncde\n\nf");
525        b.set_attr(0..9, SpanAttr::bold());
526
527        let v = b.spans_for_line(3..6);
528        assert_eq!(v, vec![bold_span(3, 6)]);
529        assert!(b.spans_for_line(6..6).is_empty());
530
531        let v = b.spans_for_line(5..8);
532        assert_eq!(v, vec![bold_span(5, 8)]);
533    }
534
535    #[test]
536    fn clear_attr_splits() {
537        let mut b = StyledBuffer::from("abcdef");
538        b.set_attr(0..6, SpanAttr::bold());
539        b.clear_attr(2..4);
540        assert_eq!(b.spans(), &[bold_span(0, 2), bold_span(4, 6)]);
541        b.check_invariants();
542    }
543
544    #[test]
545    fn delete_covering_joins() {
546        let mut b = StyledBuffer::from("hello world");
547        b.set_attr(0..11, SpanAttr::bold());
548        let (t, sp) = b.delete(5..6);
549        assert_eq!(t, " ");
550        assert_eq!(sp, vec![bold_span(5, 6)]);
551        assert_eq!(b.spans(), &[bold_span(0, 10)]);
552        b.check_invariants();
553    }
554}