makepad_code_editor/
str.rs

1use crate::char::CharExt;
2
3pub trait StrExt {
4    fn column_count(&self) -> usize;
5    fn indent_level(&self, indent_column_count: usize) -> usize;
6    fn next_indent_level(&self, indent_column_count: usize) -> usize;
7    fn prev_indent_level(&self, indent_column_count: usize) -> usize;
8    fn find_next_word_boundary(&self, index: usize, word_separators: &[char]) -> usize;
9    fn find_prev_word_boundary(&self, index: usize, word_separators: &[char]) -> usize;
10    fn indent(&self) -> Option<&str>;
11    fn longest_common_prefix(&self, other: &str) -> &str;
12    fn graphemes(&self) -> Graphemes<'_>;
13    fn grapheme_indices(&self) -> GraphemeIndices<'_>;
14    fn split_whitespace_boundaries(&self) -> SplitWhitespaceBoundaries<'_>;
15}
16
17impl StrExt for str {
18    fn column_count(&self) -> usize {
19        self.chars().map(|char| char.column_count()).sum()
20    }
21
22    fn indent_level(&self, indent_column_count: usize) -> usize {
23        self.indent().unwrap_or("").column_count() / indent_column_count
24    }
25
26    fn next_indent_level(&self, indent_column_count: usize) -> usize {
27        (self.indent().unwrap_or("").column_count() + indent_column_count) / indent_column_count
28    }
29
30    fn prev_indent_level(&self, indent_column_count: usize) -> usize {
31        self.indent().unwrap_or("").column_count().saturating_sub(1) / indent_column_count
32    }
33
34    fn find_next_word_boundary(&self, index: usize, word_separators: &[char]) -> usize {
35        if index == 0 {
36            return index;
37        }
38        let start = index;
39        self[index..]
40            .char_indices()
41            .find(|&(_, char)| word_separators.contains(&char))
42            .map(|(index, _)| start + index)
43            .unwrap_or_else(|| self.len())
44    }
45
46    fn find_prev_word_boundary(&self, index: usize, word_separators: &[char]) -> usize {
47        if index == self.len() {
48            return index;
49        }
50        self[..index]
51            .char_indices()
52            .rfind(|&(_, char)| word_separators.contains(&char))
53            .map(|(char_index, char)| char_index + char.len_utf8())
54            .unwrap_or(0)
55    }
56
57    fn indent(&self) -> Option<&str> {
58        self.char_indices()
59            .find(|(_, char)| !char.is_whitespace())
60            .map(|(index, _)| &self[..index])
61    }
62
63    fn longest_common_prefix(&self, other: &str) -> &str {
64        &self[..self
65            .char_indices()
66            .zip(other.chars())
67            .find(|((_, char_0), char_1)| char_0 == char_1)
68            .map(|((index, _), _)| index)
69            .unwrap_or_else(|| self.len().min(other.len()))]
70    }
71
72    fn graphemes(&self) -> Graphemes<'_> {
73        Graphemes { string: self }
74    }
75
76    fn grapheme_indices(&self) -> GraphemeIndices<'_> {
77        GraphemeIndices {
78            graphemes: self.graphemes(),
79            start: self.as_ptr() as usize,
80        }
81    }
82
83    fn split_whitespace_boundaries(&self) -> SplitWhitespaceBoundaries<'_> {
84        SplitWhitespaceBoundaries { string: self }
85    }
86}
87
88#[derive(Clone, Debug)]
89pub struct Graphemes<'a> {
90    string: &'a str,
91}
92
93impl<'a> Iterator for Graphemes<'a> {
94    type Item = &'a str;
95
96    fn next(&mut self) -> Option<Self::Item> {
97        if self.string.is_empty() {
98            return None;
99        }
100        let mut end = 1;
101        while !self.string.is_char_boundary(end) {
102            end += 1;
103        }
104        let (grapheme, string) = self.string.split_at(end);
105        self.string = string;
106        Some(grapheme)
107    }
108}
109
110impl<'a> DoubleEndedIterator for Graphemes<'a> {
111    fn next_back(&mut self) -> Option<Self::Item> {
112        if self.string.is_empty() {
113            return None;
114        }
115        let mut start = self.string.len() - 1;
116        while !self.string.is_char_boundary(start) {
117            start -= 1;
118        }
119        let (string, grapheme) = self.string.split_at(start);
120        self.string = string;
121        Some(grapheme)
122    }
123}
124
125#[derive(Clone, Debug)]
126pub struct GraphemeIndices<'a> {
127    graphemes: Graphemes<'a>,
128    start: usize,
129}
130
131impl<'a> Iterator for GraphemeIndices<'a> {
132    type Item = (usize, &'a str);
133
134    fn next(&mut self) -> Option<Self::Item> {
135        let grapheme = self.graphemes.next()?;
136        Some((grapheme.as_ptr() as usize - self.start, grapheme))
137    }
138}
139
140impl<'a> DoubleEndedIterator for GraphemeIndices<'a> {
141    fn next_back(&mut self) -> Option<Self::Item> {
142        let grapheme = self.graphemes.next_back()?;
143        Some((grapheme.as_ptr() as usize - self.start, grapheme))
144    }
145}
146
147#[derive(Clone, Debug)]
148pub struct SplitWhitespaceBoundaries<'a> {
149    string: &'a str,
150}
151
152impl<'a> Iterator for SplitWhitespaceBoundaries<'a> {
153    type Item = &'a str;
154
155    fn next(&mut self) -> Option<Self::Item> {
156        if self.string.is_empty() {
157            return None;
158        }
159        let mut prev_char_is_whitespace = None;
160        let index = self
161            .string
162            .char_indices()
163            .find_map(|(index, next_char)| {
164                let next_char_is_whitespace = next_char.is_whitespace();
165                let is_whitespace_boundary = prev_char_is_whitespace
166                    .map_or(false, |prev_char_is_whitespace| {
167                        prev_char_is_whitespace != next_char_is_whitespace
168                    });
169                prev_char_is_whitespace = Some(next_char_is_whitespace);
170                if is_whitespace_boundary {
171                    Some(index)
172                } else {
173                    None
174                }
175            })
176            .unwrap_or(self.string.len());
177        let (string_0, string_1) = self.string.split_at(index);
178        self.string = string_1;
179        Some(string_0)
180    }
181}