Skip to main content

styled_str/types/
spans.rs

1//! Span types.
2
3use core::num::NonZeroUsize;
4
5use anstyle::Style;
6use compile_fmt::compile_panic;
7
8/// Continuous span of styled text.
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub(crate) struct StyledSpan {
11    /// Style applied to the text.
12    pub(crate) style: Style,
13    /// Starting position of the span in text.
14    pub(crate) start: usize,
15    /// Length of text in bytes.
16    pub(crate) len: NonZeroUsize,
17}
18
19impl StyledSpan {
20    pub(crate) const DUMMY: Self = Self {
21        style: Style::new(),
22        start: 0,
23        len: NonZeroUsize::new(1).unwrap(),
24    };
25
26    pub(crate) const fn end(&self) -> usize {
27        self.start + self.len.get()
28    }
29
30    pub(crate) const fn extend_len(&mut self, add: usize) {
31        self.len = self.len.checked_add(add).expect("length overflow");
32    }
33
34    pub(crate) const fn shrink_len(&mut self, sub: usize) {
35        let new_len = self.len.get().checked_sub(sub).expect("length underflow");
36        self.len = NonZeroUsize::new(new_len).expect("length underflow");
37    }
38
39    pub(crate) fn can_contain(&self, needle: &Self) -> bool {
40        self.style == needle.style && self.len >= needle.len
41    }
42}
43
44/// Text with a uniform [`Style`] attached to it. Returned by the [`StyledStr::spans()`](crate::StyledStr::spans()) iterator.
45#[derive(Debug, Clone, Copy, PartialEq)]
46pub struct SpanStr<'a> {
47    /// Unstyled text.
48    pub text: &'a str,
49    /// Style applied to the text.
50    pub style: Style,
51}
52
53impl<'a> SpanStr<'a> {
54    /// Creates a string spanned with the specified style.
55    ///
56    /// # Panics
57    ///
58    /// Panics if `text` contains `\x1b` escapes.
59    pub const fn new(text: &'a str, style: Style) -> Self {
60        let text_bytes = text.as_bytes();
61        let mut pos = 0;
62        while pos < text_bytes.len() {
63            if text_bytes[pos] == 0x1b {
64                compile_panic!(
65                    "text contains \\x1b escape, first at position ",
66                    pos => compile_fmt::fmt::<usize>()
67                );
68            }
69            pos += 1;
70        }
71        Self { text, style }
72    }
73
74    /// Creates a string with the default style.
75    ///
76    /// # Panics
77    ///
78    /// Panics if `text` contains `\x1b` escapes.
79    pub const fn plain(text: &'a str) -> Self {
80        Self::new(text, Style::new())
81    }
82}