makepad_code_editor/
str.rs1use 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}