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        }
148        self.spans = out;
149    }
150
151    fn insert_inherit(&mut self, pos: usize, ins_len: usize) {
152        if let Some(idx) = self.spans.iter().position(|s| s.start < pos && pos < s.end) {
153            let (hs, he, hattr) = {
154                let h = &self.spans[idx];
155                (h.start, h.end, h.attr.clone())
156            };
157            let mut out = Vec::with_capacity(self.spans.len());
158            for s in self.spans.iter().take(idx) {
159                out.push(s.clone());
160            }
161            out.push(StyledSpan::new(hs, he + ins_len, hattr));
162            for s in self.spans.iter().skip(idx + 1) {
163                out.push(StyledSpan::new(
164                    s.start + ins_len,
165                    s.end + ins_len,
166                    s.attr.clone(),
167                ));
168            }
169            self.spans = out;
170            return;
171        }
172
173        if let Some(idx) = self.spans.iter().position(|s| s.end == pos) {
174            let mut out = self.spans.clone();
175            out[idx].end += ins_len;
176
177            for s in &mut out {
178                if s.start >= pos {
179                    s.start += ins_len;
180                    s.end += ins_len;
181                }
182            }
183            self.spans = out;
184            return;
185        }
186        if let Some(idx) = self.spans.iter().position(|s| s.start == pos) {
187            let mut out = self.spans.clone();
188
189            for (i, s) in out.iter_mut().enumerate() {
190                if i == idx {
191                    s.end += ins_len;
192                } else if s.start >= pos {
193                    s.start += ins_len;
194                    s.end += ins_len;
195                }
196            }
197            self.spans = out;
198            return;
199        }
200        self.shift_for_insert(pos, ins_len, None);
201    }
202
203    pub fn delete(&mut self, range: Range<usize>) -> (String, Vec<StyledSpan>) {
204        let len = self.text.len_bytes();
205        let mut s = range.start.min(len);
206        let mut e = range.end.min(len).max(s);
207        s = self.text.snap(s);
208        e = self.text.snap(e).max(s);
209        if s == e {
210            return (String::new(), Vec::new());
211        }
212        let d = e - s;
213        let mut removed_spans = Vec::new();
214        let mut kept = Vec::with_capacity(self.spans.len());
215        for sp in &self.spans {
216            if sp.end <= s {
217                kept.push(sp.clone());
218            } else if sp.start >= e {
219                kept.push(StyledSpan::new(sp.start - d, sp.end - d, sp.attr.clone()));
220            } else if sp.start < s && sp.end > e {
221                kept.push(StyledSpan::new(sp.start, sp.end - d, sp.attr.clone()));
222                if let Some(c) = sp.clipped(s..e) {
223                    removed_spans.push(c);
224                }
225            } else if sp.start < s {
226                debug_assert!(sp.end > s && sp.end <= e);
227                kept.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
228                if let Some(c) = sp.clipped(s..e) {
229                    removed_spans.push(c);
230                }
231            } else if sp.end > e {
232                debug_assert!(sp.start >= s && sp.start < e);
233                kept.push(StyledSpan::new(s, sp.end - d, sp.attr.clone()));
234                if let Some(c) = sp.clipped(s..e) {
235                    removed_spans.push(c);
236                }
237            } else {
238                removed_spans.push(sp.clone());
239            }
240        }
241        let removed_text = self.text.delete(s..e);
242        self.spans = kept;
243        normalize_spans(&mut self.spans);
244        debug_assert!(self.check_spans_sorted());
245        (removed_text, removed_spans)
246    }
247
248    pub fn replace(
249        &mut self,
250        range: Range<usize>,
251        text: &str,
252        attr: Option<SpanAttr>,
253    ) -> (String, Vec<StyledSpan>) {
254        let len = self.text.len_bytes();
255        let s = self.text.snap(range.start.min(len));
256        let e = self.text.snap(range.end.min(len).max(s));
257        if text.is_empty() {
258            return self.delete(s..e);
259        }
260        let (removed_text, removed_spans) = self.delete(s..e);
261        self.insert(s, text, attr);
262        (removed_text, removed_spans)
263    }
264
265    pub fn set_attr(&mut self, range: Range<usize>, attr: SpanAttr) {
266        if attr.is_plain() {
267            self.clear_attr(range);
268            return;
269        }
270        let len = self.text.len_bytes();
271        let mut s = range.start.min(len);
272        let mut e = range.end.min(len).max(s);
273        s = self.text.snap(s);
274        e = self.text.snap(e).max(s);
275        if s == e {
276            return;
277        }
278        let mut out = Vec::with_capacity(self.spans.len() + 1);
279        for sp in &self.spans {
280            if sp.end <= s || sp.start >= e {
281                out.push(sp.clone());
282            } else {
283                if sp.start < s {
284                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
285                }
286                if sp.end > e {
287                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
288                }
289            }
290        }
291        out.push(StyledSpan::new(s, e, attr));
292        normalize_spans(&mut out);
293        self.spans = out;
294        debug_assert!(self.check_spans_sorted());
295    }
296
297    pub fn clear_attr(&mut self, range: Range<usize>) {
298        let len = self.text.len_bytes();
299        let mut s = range.start.min(len);
300        let mut e = range.end.min(len).max(s);
301        s = self.text.snap(s);
302        e = self.text.snap(e).max(s);
303        if s == e {
304            return;
305        }
306        let mut out = Vec::with_capacity(self.spans.len());
307        for sp in &self.spans {
308            if sp.end <= s || sp.start >= e {
309                out.push(sp.clone());
310            } else {
311                if sp.start < s {
312                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
313                }
314                if sp.end > e {
315                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
316                }
317            }
318        }
319        self.spans = out;
320        debug_assert!(self.check_spans_sorted());
321    }
322
323    pub fn spans_for_line(&self, line_range: Range<usize>) -> Vec<StyledSpan> {
324        if line_range.start >= line_range.end {
325            return Vec::new();
326        }
327        self.spans
328            .iter()
329            .filter(|s| s.start < line_range.end && line_range.start < s.end)
330            .filter_map(|s| s.clipped(line_range.clone()))
331            .collect()
332    }
333
334    pub fn apply_attr_range(&mut self, range: Range<usize>, spans: &[StyledSpan]) {
335        let len = self.text.len_bytes();
336        let mut s = range.start.min(len);
337        let mut e = range.end.min(len).max(s);
338        s = self.text.snap(s);
339        e = self.text.snap(e).max(s);
340        let mut out = Vec::with_capacity(self.spans.len() + spans.len());
341        for sp in &self.spans {
342            if sp.end <= s || sp.start >= e {
343                out.push(sp.clone());
344            } else {
345                if sp.start < s {
346                    out.push(StyledSpan::new(sp.start, s, sp.attr.clone()));
347                }
348                if sp.end > e {
349                    out.push(StyledSpan::new(e, sp.end, sp.attr.clone()));
350                }
351            }
352        }
353        for sp in spans {
354            if let Some(c) = sp.clipped(s..e)
355                && !c.attr.is_plain()
356            {
357                out.push(c);
358            }
359        }
360        normalize_spans(&mut out);
361        self.spans = out;
362        debug_assert!(self.check_spans_sorted());
363    }
364
365    pub fn replace_spans(&mut self, mut spans: Vec<StyledSpan>) {
366        let len = self.text.len_bytes();
367        for s in &mut spans {
368            s.start = self.text.snap(s.start.min(len));
369            s.end = self.text.snap(s.end.min(len).max(s.start));
370        }
371        spans.retain(|s| !s.is_empty() && !s.attr.is_plain());
372        normalize_spans(&mut spans);
373        self.spans = spans;
374    }
375
376    fn check_spans_sorted(&self) -> bool {
377        let len = self.text.len_bytes();
378        let mut prev_end = None;
379        for s in &self.spans {
380            if s.start >= s.end {
381                return false;
382            }
383            if s.end > len {
384                return false;
385            }
386
387            if self.text.snap(s.start) != s.start || self.text.snap(s.end) != s.end {
388                return false;
389            }
390            if let Some(p) = prev_end {
391                if s.start < p {
392                    return false;
393                }
394                if s.start == p && self.spans_first_eq_prev(s) {
395                    return false;
396                }
397            }
398            prev_end = Some(s.end);
399        }
400        true
401    }
402
403    fn spans_first_eq_prev(&self, _s: &StyledSpan) -> bool {
404        let mut prev: Option<&StyledSpan> = None;
405        for cur in &self.spans {
406            if let Some(p) = prev
407                && p.end == cur.start
408                && p.attr == cur.attr
409            {
410                return true;
411            }
412            prev = Some(cur);
413            if cur.start == _s.start && cur.end == _s.end {
414                break;
415            }
416        }
417        false
418    }
419
420    #[cfg(test)]
421    fn check_invariants(&self) {
422        assert!(self.check_spans_sorted(), "spans: {:?}", self.spans);
423    }
424}
425
426#[cfg(test)]
427mod tests {
428    use super::*;
429
430    fn bold_span(s: usize, e: usize) -> StyledSpan {
431        StyledSpan::new(s, e, SpanAttr::bold())
432    }
433
434    #[test]
435    fn insert_delete_set_attr_roundtrip() {
436        let mut b = StyledBuffer::from("hello world");
437        b.set_attr(0..5, SpanAttr::bold());
438        assert_eq!(b.spans(), &[bold_span(0, 5)]);
439        let (t, sp) = b.delete(0..5);
440        assert_eq!(t, "hello");
441        assert_eq!(sp, vec![bold_span(0, 5)]);
442        assert!(b.spans().is_empty());
443        assert_eq!(b.to_string(), " world");
444
445        b.insert(0, "hello", Some(SpanAttr::plain()));
446        assert_eq!(b.to_string(), "hello world");
447        b.apply_attr_range(0..5, &[bold_span(0, 5)]);
448        assert_eq!(b.spans(), &[bold_span(0, 5)]);
449        b.check_invariants();
450    }
451
452    #[test]
453    fn neighbours_merge_after_set() {
454        let mut b = StyledBuffer::from("abcdef");
455        b.set_attr(0..2, SpanAttr::bold());
456        b.set_attr(2..4, SpanAttr::bold());
457        assert_eq!(b.spans(), &[bold_span(0, 4)]);
458        b.check_invariants();
459    }
460
461    #[test]
462    fn typing_inside_span_inherits() {
463        let mut b = StyledBuffer::from("hello");
464        b.set_attr(0..5, SpanAttr::bold());
465        b.insert(2, "X", None);
466        assert_eq!(b.to_string(), "heXllo");
467        assert_eq!(b.spans(), &[bold_span(0, 6)]);
468
469        let mut c = StyledBuffer::from("hello");
470        c.set_attr(0..5, SpanAttr::bold());
471        c.insert(2, "X", Some(SpanAttr::plain()));
472        assert_eq!(c.spans(), &[bold_span(0, 2), bold_span(3, 6)]);
473
474        let mut d = StyledBuffer::from("hello");
475        d.set_attr(0..5, SpanAttr::bold());
476        d.insert(2, "X", Some(SpanAttr::link("u")));
477        assert_eq!(d.spans().len(), 3);
478        assert_eq!(d.to_string(), "heXllo");
479        b.check_invariants();
480        c.check_invariants();
481        d.check_invariants();
482    }
483
484    #[test]
485    fn boundary_inserts_left_biased() {
486        let mut b = StyledBuffer::from("ab");
487        b.set_attr(0..1, SpanAttr::bold());
488        b.set_attr(1..2, SpanAttr::link("u"));
489
490        b.insert(1, "X", None);
491        assert_eq!(b.to_string(), "aXb");
492        assert_eq!(b.spans()[0], bold_span(0, 2));
493        b.check_invariants();
494    }
495
496    #[test]
497    fn grapheme_text_does_not_break_spans() {
498        let text = "a๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ง๐Ÿ‡ต๐Ÿ‡ฑรฉ\r\nz";
499        let mut b = StyledBuffer::from(text);
500        let len = b.len_bytes();
501        b.set_attr(0..len, SpanAttr::bold());
502        assert_eq!(b.spans(), &[bold_span(0, len)]);
503
504        let mid = 2usize;
505        let snapped = b.snap(mid);
506        assert_eq!(b.snap(snapped), snapped);
507        b.insert(snapped, "Q", None);
508        assert!(b.snap(b.len_bytes()) == b.len_bytes());
509        let (t, _) = b.delete(0..1);
510        assert_eq!(t, "a");
511        b.check_invariants();
512
513        let flag = "๐Ÿ‡ต๐Ÿ‡ฑ";
514        let mut f = StyledBuffer::from(flag);
515        f.set_attr(0..flag.len(), SpanAttr::bold());
516        f.insert(4, "ยท", Some(SpanAttr::plain()));
517        assert_eq!(f.to_string(), "๐Ÿ‡ตยท๐Ÿ‡ฑ");
518        f.check_invariants();
519    }
520
521    #[test]
522    fn spans_for_line_clips() {
523        let mut b = StyledBuffer::from("ab\ncde\n\nf");
524        b.set_attr(0..9, SpanAttr::bold());
525
526        let v = b.spans_for_line(3..6);
527        assert_eq!(v, vec![bold_span(3, 6)]);
528        assert!(b.spans_for_line(6..6).is_empty());
529
530        let v = b.spans_for_line(5..8);
531        assert_eq!(v, vec![bold_span(5, 8)]);
532    }
533
534    #[test]
535    fn clear_attr_splits() {
536        let mut b = StyledBuffer::from("abcdef");
537        b.set_attr(0..6, SpanAttr::bold());
538        b.clear_attr(2..4);
539        assert_eq!(b.spans(), &[bold_span(0, 2), bold_span(4, 6)]);
540        b.check_invariants();
541    }
542
543    #[test]
544    fn delete_covering_joins() {
545        let mut b = StyledBuffer::from("hello world");
546        b.set_attr(0..11, SpanAttr::bold());
547        let (t, sp) = b.delete(5..6);
548        assert_eq!(t, " ");
549        assert_eq!(sp, vec![bold_span(5, 6)]);
550        assert_eq!(b.spans(), &[bold_span(0, 10)]);
551        b.check_invariants();
552    }
553}