1use std::fmt;
4
5pub use neco_textview::RangeChange;
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum DecorError {
10 InvalidRange { start: usize, end: usize },
11 EmptyHighlight { offset: usize },
12}
13
14impl fmt::Display for DecorError {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 DecorError::InvalidRange { start, end } => {
18 write!(f, "invalid range: start {start} > end {end}")
19 }
20 DecorError::EmptyHighlight { offset } => {
21 write!(f, "empty highlight at offset {offset}")
22 }
23 }
24 }
25}
26
27impl std::error::Error for DecorError {}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum DecorationKind {
32 Highlight,
33 Marker,
34 Widget { block: bool },
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct Decoration {
40 start: usize,
41 end: usize,
42 kind: DecorationKind,
43 tag: u32,
44 priority: i16,
45}
46
47impl Decoration {
48 pub fn highlight(start: usize, end: usize, tag: u32) -> Result<Self, DecorError> {
49 if start > end {
50 return Err(DecorError::InvalidRange { start, end });
51 }
52 if start == end {
53 return Err(DecorError::EmptyHighlight { offset: start });
54 }
55 Ok(Self {
56 start,
57 end,
58 kind: DecorationKind::Highlight,
59 tag,
60 priority: 0,
61 })
62 }
63
64 pub fn marker(line_start: usize, tag: u32) -> Self {
65 Self {
66 start: line_start,
67 end: line_start,
68 kind: DecorationKind::Marker,
69 tag,
70 priority: 0,
71 }
72 }
73
74 pub fn widget(start: usize, end: usize, tag: u32, block: bool) -> Result<Self, DecorError> {
75 if start > end {
76 return Err(DecorError::InvalidRange { start, end });
77 }
78 Ok(Self {
79 start,
80 end,
81 kind: DecorationKind::Widget { block },
82 tag,
83 priority: 0,
84 })
85 }
86
87 pub fn with_priority(mut self, priority: i16) -> Self {
88 self.priority = priority;
89 self
90 }
91
92 pub fn start(&self) -> usize {
93 self.start
94 }
95
96 pub fn end(&self) -> usize {
97 self.end
98 }
99
100 pub fn kind(&self) -> DecorationKind {
101 self.kind
102 }
103
104 pub fn tag(&self) -> u32 {
105 self.tag
106 }
107
108 pub fn priority(&self) -> i16 {
109 self.priority
110 }
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
115pub struct DecorationId(u64);
116
117impl DecorationId {
118 pub fn into_raw(self) -> u64 {
120 self.0
121 }
122
123 pub fn from_raw(raw: u64) -> Self {
125 Self(raw)
126 }
127}
128
129#[derive(Debug, Clone)]
130struct DecorationEntry {
131 id: DecorationId,
132 decoration: Decoration,
133}
134
135#[derive(Debug, Clone)]
137pub struct DecorationSet {
138 entries: Vec<DecorationEntry>,
139 next_id: u64,
140}
141
142impl DecorationSet {
143 pub fn new() -> Self {
144 Self {
145 entries: Vec::new(),
146 next_id: 0,
147 }
148 }
149
150 pub fn add(&mut self, decoration: Decoration) -> DecorationId {
151 let id = DecorationId(self.next_id);
152 self.next_id += 1;
153 let pos = self
154 .entries
155 .partition_point(|e| e.decoration.start <= decoration.start);
156 self.entries.push(DecorationEntry { id, decoration });
157 let len = self.entries.len();
158 self.entries[pos..len].rotate_right(1);
159 id
160 }
161
162 pub fn remove(&mut self, id: DecorationId) -> bool {
163 if let Some(pos) = self.entries.iter().position(|e| e.id == id) {
164 self.entries.remove(pos);
165 true
166 } else {
167 false
168 }
169 }
170
171 pub fn query_range(&self, start: usize, end: usize) -> Vec<(DecorationId, &Decoration)> {
172 self.entries
173 .iter()
174 .filter(|e| {
175 let d = &e.decoration;
176 if d.start == d.end {
177 d.start >= start && d.start < end
178 } else {
179 d.start < end && d.end > start
180 }
181 })
182 .map(|e| (e.id, &e.decoration))
183 .collect()
184 }
185
186 pub fn query_tag(&self, tag: u32) -> Vec<(DecorationId, &Decoration)> {
187 self.entries
188 .iter()
189 .filter(|e| e.decoration.tag == tag)
190 .map(|e| (e.id, &e.decoration))
191 .collect()
192 }
193
194 pub fn iter(&self) -> impl Iterator<Item = (DecorationId, &Decoration)> {
195 self.entries.iter().map(|e| (e.id, &e.decoration))
196 }
197
198 pub fn len(&self) -> usize {
199 self.entries.len()
200 }
201
202 pub fn is_empty(&self) -> bool {
203 self.entries.is_empty()
204 }
205
206 pub fn clear(&mut self) {
207 self.entries.clear();
208 }
209
210 pub fn map_through_change(&mut self, change_start: usize, old_end: usize, new_end: usize) {
211 let delta = new_end as isize - old_end as isize;
212
213 self.entries.retain_mut(|entry| {
214 let d = &mut entry.decoration;
215
216 if d.end <= change_start {
218 return true;
219 }
220
221 if d.start >= old_end {
223 d.start = (d.start as isize + delta) as usize;
224 d.end = (d.end as isize + delta) as usize;
225 return true;
226 }
227
228 match d.kind {
230 DecorationKind::Highlight => {
231 if d.start >= change_start {
232 d.start = change_start;
233 }
234 if d.end > old_end {
235 d.end = (d.end as isize + delta) as usize;
236 } else {
237 d.end = new_end;
238 }
239 d.start < d.end
240 }
241 DecorationKind::Marker => {
242 if d.start >= change_start && d.start < old_end {
243 return false;
244 }
245 true
246 }
247 DecorationKind::Widget { .. } => {
248 if d.start >= change_start && d.end <= old_end {
250 return false;
251 }
252 if d.start < change_start {
254 } else {
256 d.start = change_start;
257 }
258 if d.end > old_end {
259 d.end = (d.end as isize + delta) as usize;
260 } else {
261 d.end = new_end;
262 }
263 true
264 }
265 }
266 });
267 }
268
269 pub fn map_through_changes(&mut self, changes: &[RangeChange]) {
270 for change in changes {
271 self.map_through_change(change.start(), change.old_end(), change.new_end());
272 }
273 }
274}
275
276impl Default for DecorationSet {
277 fn default() -> Self {
278 Self::new()
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn highlight_ok() {
288 let d = Decoration::highlight(0, 10, 1).unwrap();
289 assert_eq!(d.start(), 0);
290 assert_eq!(d.end(), 10);
291 assert_eq!(d.kind(), DecorationKind::Highlight);
292 assert_eq!(d.tag(), 1);
293 assert_eq!(d.priority(), 0);
294 }
295
296 #[test]
297 fn highlight_empty_range() {
298 let err = Decoration::highlight(5, 5, 1).unwrap_err();
299 assert_eq!(err, DecorError::EmptyHighlight { offset: 5 });
300 }
301
302 #[test]
303 fn highlight_invalid_range() {
304 let err = Decoration::highlight(10, 5, 1).unwrap_err();
305 assert_eq!(err, DecorError::InvalidRange { start: 10, end: 5 });
306 }
307
308 #[test]
309 fn marker_ok() {
310 let d = Decoration::marker(42, 2);
311 assert_eq!(d.start(), 42);
312 assert_eq!(d.end(), 42);
313 assert_eq!(d.kind(), DecorationKind::Marker);
314 assert_eq!(d.tag(), 2);
315 }
316
317 #[test]
318 fn widget_ok() {
319 let d = Decoration::widget(10, 20, 3, true).unwrap();
320 assert_eq!(d.kind(), DecorationKind::Widget { block: true });
321 }
322
323 #[test]
324 fn widget_empty_range_ok() {
325 let d = Decoration::widget(5, 5, 3, false).unwrap();
326 assert_eq!(d.start(), 5);
327 assert_eq!(d.end(), 5);
328 }
329
330 #[test]
331 fn widget_invalid_range() {
332 let err = Decoration::widget(20, 10, 3, false).unwrap_err();
333 assert_eq!(err, DecorError::InvalidRange { start: 20, end: 10 });
334 }
335
336 #[test]
337 fn with_priority() {
338 let d = Decoration::marker(0, 1).with_priority(5);
339 assert_eq!(d.priority(), 5);
340 }
341
342 #[test]
343 fn set_add_iter_sorted() {
344 let mut set = DecorationSet::new();
345 set.add(Decoration::marker(20, 1));
346 set.add(Decoration::marker(5, 2));
347 set.add(Decoration::marker(10, 3));
348
349 let starts: Vec<usize> = set.iter().map(|(_, d)| d.start()).collect();
350 assert_eq!(starts, vec![5, 10, 20]);
351 }
352
353 #[test]
354 fn set_add_remove_len() {
355 let mut set = DecorationSet::new();
356 let id1 = set.add(Decoration::marker(0, 1));
357 let id2 = set.add(Decoration::marker(10, 2));
358 assert_eq!(set.len(), 2);
359
360 assert!(set.remove(id1));
361 assert_eq!(set.len(), 1);
362 assert!(!set.remove(id1));
363
364 assert!(set.remove(id2));
365 assert!(set.is_empty());
366 }
367
368 #[test]
369 fn decoration_id_roundtrips_through_raw_value() {
370 let mut set = DecorationSet::new();
371 let id = set.add(Decoration::marker(0, 1));
372 let raw = id.into_raw();
373
374 assert!(set.remove(DecorationId::from_raw(raw)));
375 assert!(set.is_empty());
376 }
377
378 #[test]
379 fn set_query_range() {
380 let mut set = DecorationSet::new();
381 set.add(Decoration::highlight(0, 5, 1).unwrap());
382 set.add(Decoration::highlight(10, 20, 2).unwrap());
383 set.add(Decoration::highlight(30, 40, 3).unwrap());
384
385 let results = set.query_range(3, 15);
386 assert_eq!(results.len(), 2);
387 assert_eq!(results[0].1.tag(), 1);
388 assert_eq!(results[1].1.tag(), 2);
389 }
390
391 #[test]
392 fn set_query_tag() {
393 let mut set = DecorationSet::new();
394 set.add(Decoration::marker(0, 1));
395 set.add(Decoration::marker(10, 2));
396 set.add(Decoration::marker(20, 1));
397
398 let results = set.query_tag(1);
399 assert_eq!(results.len(), 2);
400 }
401
402 #[test]
403 fn set_query_range_includes_marker() {
404 let mut set = DecorationSet::new();
405 set.add(Decoration::marker(10, 1));
406 set.add(Decoration::marker(20, 2));
407
408 let results = set.query_range(10, 15);
409 assert_eq!(results.len(), 1);
410 assert_eq!(results[0].1.tag(), 1);
411
412 let results = set.query_range(5, 10);
413 assert_eq!(results.len(), 0);
414
415 let results = set.query_range(5, 11);
416 assert_eq!(results.len(), 1);
417 }
418
419 #[test]
420 fn map_before_change_unchanged() {
421 let mut set = DecorationSet::new();
422 set.add(Decoration::highlight(0, 5, 1).unwrap());
423 set.map_through_change(10, 15, 20);
424
425 let d = set.iter().next().unwrap().1;
426 assert_eq!(d.start(), 0);
427 assert_eq!(d.end(), 5);
428 }
429
430 #[test]
431 fn map_after_change_shifted() {
432 let mut set = DecorationSet::new();
433 set.add(Decoration::highlight(20, 30, 1).unwrap());
434 set.map_through_change(10, 15, 20);
436
437 let d = set.iter().next().unwrap().1;
438 assert_eq!(d.start(), 25);
439 assert_eq!(d.end(), 35);
440 }
441
442 #[test]
443 fn map_highlight_overlap_clamp() {
444 let mut set = DecorationSet::new();
445 set.add(Decoration::highlight(5, 15, 1).unwrap());
447 set.map_through_change(10, 20, 12);
448
449 let d = set.iter().next().unwrap().1;
450 assert_eq!(d.start(), 5);
451 assert_eq!(d.end(), 12);
453 }
454
455 #[test]
456 fn map_marker_deleted_in_change() {
457 let mut set = DecorationSet::new();
458 set.add(Decoration::marker(12, 1));
459 set.map_through_change(10, 20, 15);
461 assert!(set.is_empty());
462 }
463
464 #[test]
465 fn map_widget_fully_contained_deleted() {
466 let mut set = DecorationSet::new();
467 set.add(Decoration::widget(12, 18, 1, true).unwrap());
468 set.map_through_change(10, 20, 15);
470 assert!(set.is_empty());
471 }
472
473 #[test]
474 fn map_widget_partial_overlap_clamped() {
475 let mut set = DecorationSet::new();
476 set.add(Decoration::widget(5, 15, 1, false).unwrap());
478 set.map_through_change(10, 20, 12);
479
480 let d = set.iter().next().unwrap().1;
481 assert_eq!(d.start(), 5);
482 assert_eq!(d.end(), 12);
483 }
484
485 #[test]
486 fn map_through_changes_sequential() {
487 let mut set = DecorationSet::new();
488 set.add(Decoration::highlight(20, 30, 1).unwrap());
489
490 let changes = [
491 RangeChange::new(0, 5, 10), RangeChange::new(0, 3, 3), RangeChange::new(40, 40, 42), ];
495 set.map_through_changes(&changes);
496
497 let d = set.iter().next().unwrap().1;
498 assert_eq!(d.start(), 25);
499 assert_eq!(d.end(), 35);
500 }
501
502 #[test]
503 fn decor_error_display() {
504 let e = DecorError::InvalidRange { start: 10, end: 5 };
505 assert_eq!(e.to_string(), "invalid range: start 10 > end 5");
506
507 let e = DecorError::EmptyHighlight { offset: 7 };
508 assert_eq!(e.to_string(), "empty highlight at offset 7");
509 }
510
511 #[test]
512 fn set_clear() {
513 let mut set = DecorationSet::new();
514 set.add(Decoration::marker(0, 1));
515 set.add(Decoration::marker(10, 2));
516 assert_eq!(set.len(), 2);
517 set.clear();
518 assert!(set.is_empty());
519 }
520}