Skip to main content

hjkl_buffer/
span.rs

1/// One styled byte range on a buffer row.
2///
3/// The buffer holds these per row (`Vec<Vec<Span>>`) so the render
4/// path doesn't have to re-tokenise each frame. `style` is opaque to
5/// the buffer — sqeel-vim layers tree-sitter and LSP diagnostic
6/// styling on top, then hands the merged spans back via
7/// [`Buffer::set_spans`]. The render layer turns it into a real
8/// `ratatui::style::Style` at draw time.
9///
10/// Byte ranges are half-open: `[start_byte, end_byte)`. They line up
11/// with the row's `String` so callers can slice without re-deriving
12/// indices.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Span {
15    pub start_byte: usize,
16    pub end_byte: usize,
17    /// Opaque style id resolved by the host's render layer.
18    pub style: u32,
19}
20
21impl Span {
22    pub const fn new(start_byte: usize, end_byte: usize, style: u32) -> Self {
23        Self {
24            start_byte,
25            end_byte,
26            style,
27        }
28    }
29
30    /// Width of the span in bytes; useful for render-cache fingerprints.
31    pub const fn len(self) -> usize {
32        self.end_byte.saturating_sub(self.start_byte)
33    }
34
35    pub const fn is_empty(self) -> bool {
36        self.end_byte <= self.start_byte
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::Span;
43
44    #[test]
45    fn len_and_is_empty() {
46        assert_eq!(Span::new(0, 5, 0).len(), 5);
47        assert!(Span::new(3, 3, 0).is_empty());
48        assert!(Span::new(7, 5, 0).is_empty());
49    }
50}