Skip to main content

steel_core/command/brigadier/
string_range.rs

1//! UTF-16 command input ranges.
2
3use std::ops::Range;
4
5/// A half-open range measured in UTF-16 code units.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
7pub(crate) struct StringRange {
8    start: usize,
9    end: usize,
10}
11
12impl StringRange {
13    /// Creates an empty range at `position`.
14    pub(crate) const fn at(position: usize) -> Self {
15        Self {
16            start: position,
17            end: position,
18        }
19    }
20
21    /// Creates a range between two UTF-16 positions.
22    pub(crate) const fn between(start: usize, end: usize) -> Self {
23        assert!(start <= end, "command range start must not exceed its end");
24        Self { start, end }
25    }
26
27    /// Creates the smallest range containing both inputs.
28    pub(crate) const fn encompassing(first: Self, second: Self) -> Self {
29        Self {
30            start: if first.start < second.start {
31                first.start
32            } else {
33                second.start
34            },
35            end: if first.end > second.end {
36                first.end
37            } else {
38                second.end
39            },
40        }
41    }
42
43    /// Returns the inclusive start position.
44    pub(crate) const fn start(self) -> usize {
45        self.start
46    }
47
48    /// Returns the exclusive end position.
49    pub(crate) const fn end(self) -> usize {
50        self.end
51    }
52
53    /// Returns whether the range contains no UTF-16 code units.
54    pub(crate) const fn is_empty(self) -> bool {
55        self.start == self.end
56    }
57
58    /// Returns the range length in UTF-16 code units.
59    pub(crate) const fn len(self) -> usize {
60        self.end - self.start
61    }
62
63    pub(super) fn byte_range(self, input: &str) -> Option<Range<usize>> {
64        let start = Self::byte_index(input, self.start)?;
65        let end = Self::byte_index(input, self.end)?;
66        Some(start..end)
67    }
68
69    fn byte_index(input: &str, position: usize) -> Option<usize> {
70        let mut utf16_index = 0;
71        for (byte_index, character) in input.char_indices() {
72            if utf16_index == position {
73                return Some(byte_index);
74            }
75            utf16_index += character.len_utf16();
76            if utf16_index > position {
77                return None;
78            }
79        }
80        (utf16_index == position).then_some(input.len())
81    }
82}