bekoedit_markdown/
range.rs1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
11pub struct ByteRange {
12 pub start: usize,
13 pub end: usize,
14}
15
16impl ByteRange {
17 pub fn new(start: usize, end: usize) -> Self {
18 Self { start, end }
19 }
20
21 pub fn len(&self) -> usize {
22 self.end.saturating_sub(self.start)
23 }
24
25 pub fn is_empty(&self) -> bool {
26 self.end <= self.start
27 }
28
29 pub fn contains(&self, other: &ByteRange) -> bool {
30 self.start <= other.start && other.end <= self.end
31 }
32
33 pub fn validate(&self, text: &str) -> Result<(), RangeError> {
40 if self.start > self.end {
41 return Err(RangeError::Inverted {
42 start: self.start,
43 end: self.end,
44 });
45 }
46 if self.end > text.len() {
47 return Err(RangeError::OutOfBounds {
48 end: self.end,
49 len: text.len(),
50 });
51 }
52 if !text.is_char_boundary(self.start) {
53 return Err(RangeError::NotCharBoundary { offset: self.start });
54 }
55 if !text.is_char_boundary(self.end) {
56 return Err(RangeError::NotCharBoundary { offset: self.end });
57 }
58 Ok(())
59 }
60
61 pub fn slice<'a>(&self, text: &'a str) -> Result<&'a str, RangeError> {
63 self.validate(text)?;
64 Ok(&text[self.start..self.end])
65 }
66}
67
68pub fn utf16_to_utf8_offset(text: &str, utf16_offset: usize) -> Option<usize> {
76 let mut byte_pos = 0usize;
77 let mut utf16_pos = 0usize;
78 for ch in text.chars() {
79 if utf16_pos == utf16_offset {
80 return Some(byte_pos);
81 }
82 if utf16_pos > utf16_offset {
85 return None;
86 }
87 utf16_pos += ch.len_utf16();
88 byte_pos += ch.len_utf8();
89 }
90 (utf16_pos == utf16_offset).then_some(byte_pos)
91}
92
93impl From<std::ops::Range<usize>> for ByteRange {
94 fn from(r: std::ops::Range<usize>) -> Self {
95 Self::new(r.start, r.end)
96 }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error, Serialize, Deserialize)]
101pub enum RangeError {
102 #[error("range start {start} is greater than end {end}")]
103 Inverted { start: usize, end: usize },
104 #[error("range end {end} exceeds text length {len}")]
105 OutOfBounds { end: usize, len: usize },
106 #[error("offset {offset} is not a UTF-8 character boundary")]
107 NotCharBoundary { offset: usize },
108}