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}