1use super::*;
4
5pub fn offset_from_utf16(text: &str, offset: usize) -> usize {
7 let mut utf8_offset = 0;
8 let mut utf16_count = 0;
9 for ch in text.chars() {
10 if utf16_count >= offset {
11 break;
12 }
13 utf16_count += ch.len_utf16();
14 utf8_offset += ch.len_utf8();
15 }
16 utf8_offset
17}
18
19pub fn offset_to_utf16(text: &str, offset: usize) -> usize {
21 let mut utf16_offset = 0;
22 let mut utf8_count = 0;
23 for ch in text.chars() {
24 if utf8_count >= offset {
25 break;
26 }
27 utf8_count += ch.len_utf8();
28 utf16_offset += ch.len_utf16();
29 }
30 utf16_offset
31}
32
33pub fn range_from_utf16(text: &str, range: Range<usize>) -> Range<usize> {
35 offset_from_utf16(text, range.start)..offset_from_utf16(text, range.end)
36}
37
38pub fn range_to_utf16(text: &str, range: Range<usize>) -> Range<usize> {
40 offset_to_utf16(text, range.start)..offset_to_utf16(text, range.end)
41}
42
43pub fn composition_selection(
45 text: &str,
46 start: usize,
47 selection: Option<Range<usize>>,
48) -> Range<usize> {
49 let range = selection
50 .map(|range| range_from_utf16(text, range))
51 .unwrap_or(text.len()..text.len());
52 start + range.start..start + range.end
53}
54
55pub fn previous_boundary(text: &str, offset: usize) -> usize {
59 text.grapheme_indices(true)
60 .rev()
61 .find_map(|(idx, _)| (idx < offset).then_some(idx))
62 .unwrap_or(0)
63}
64
65pub fn next_boundary(text: &str, offset: usize) -> usize {
67 text.grapheme_indices(true)
68 .find_map(|(idx, _)| (idx > offset).then_some(idx))
69 .unwrap_or(text.len())
70}
71
72pub fn joins_group(last: Option<(EditKind, usize)>, kind: EditKind, at: usize) -> bool {
80 last.is_some_and(|(last_kind, offset)| last_kind == kind && at == offset)
81}
82
83pub fn normalize(text: &str, shape: Shape) -> String {
93 let text = text.replace("\r\n", "\n").replace('\r', "\n");
94 if shape.is_multiline() {
95 text
96 } else {
97 text.replace('\n', " ")
98 }
99}
100
101pub fn line_start(text: &str, offset: usize) -> usize {
110 text[..offset].rfind('\n').map_or(0, |at| at + 1)
111}
112
113pub fn line_end(text: &str, offset: usize) -> usize {
115 text[offset..]
116 .find('\n')
117 .map_or(text.len(), |at| offset + at)
118}
119
120pub(super) fn is_word(segment: &str) -> bool {
123 segment.chars().any(char::is_alphanumeric)
124}
125
126pub fn previous_word_boundary(text: &str, offset: usize) -> usize {
134 text.split_word_bound_indices()
135 .filter(|(start, _)| *start < offset)
136 .rfind(|(_, segment)| is_word(segment))
137 .map(|(start, _)| start)
138 .unwrap_or(0)
139}
140
141pub fn next_word_boundary(text: &str, offset: usize) -> usize {
143 text.split_word_bound_indices()
144 .filter(|(start, segment)| start + segment.len() > offset)
145 .find(|(_, segment)| is_word(segment))
146 .map(|(start, segment)| start + segment.len())
147 .unwrap_or(text.len())
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq)]
153pub enum Granularity {
154 Char,
155 Word,
156 Line,
157}
158
159impl Granularity {
160 pub fn of_clicks(click_count: usize) -> Self {
161 match click_count {
162 0 | 1 => Self::Char,
163 2 => Self::Word,
164 _ => Self::Line,
165 }
166 }
167
168 pub fn around(self, text: &str, offset: usize) -> Range<usize> {
172 match self {
173 Self::Char => offset..offset,
174 Self::Word => {
175 let segments = || {
176 text.split_word_bound_indices()
177 .map(|(start, segment)| (start..start + segment.len(), is_word(segment)))
178 };
179 let after = segments().find(|(range, _)| range.contains(&offset));
180 let before = segments().find(|(range, _)| range.end == offset);
181 match (after, before) {
182 (Some((range, true)), _) | (_, Some((range, true))) => range,
183 (Some((range, _)), _) | (_, Some((range, _))) => range,
184 (None, None) => offset..offset,
185 }
186 }
187 Self::Line => line_start(text, offset)..line_end(text, offset),
188 }
189 }
190}
191
192pub fn drag_selection<T: Ord + std::marker::Copy>(pressed: Range<T>, span: Range<T>) -> (T, T) {
196 if span.start < pressed.start {
197 (pressed.end, span.start)
198 } else {
199 (pressed.start, span.end.max(pressed.end))
200 }
201}
202
203#[derive(Clone, Debug, Default)]
210pub struct Shown(std::rc::Rc<[(Range<usize>, Range<usize>)]>);
211
212impl Shown {
213 pub fn new(pairs: Vec<(Range<usize>, Range<usize>)>) -> Self {
215 Self(pairs.into())
216 }
217
218 pub fn at(&self, offset: usize) -> usize {
220 self.map(offset, false)
221 }
222
223 pub fn range(&self, range: &Range<usize>) -> Range<usize> {
225 self.map(range.start, false)..self.map(range.end, true)
226 }
227
228 fn map(&self, offset: usize, end: bool) -> usize {
229 let mut shift = 0isize;
230 for (source, shown) in self.0.iter() {
231 if offset <= source.start {
232 break;
233 }
234 if offset < source.end {
235 return if end { shown.end } else { shown.start };
236 }
237 shift = shown.end as isize - source.end as isize;
238 }
239 offset.saturating_add_signed(shift)
240 }
241
242 pub fn offset(&self, shown: usize) -> usize {
245 let mut shift = 0isize;
246 for (source, painted) in self.0.iter() {
247 if shown <= painted.start {
248 break;
249 }
250 if shown < painted.end {
251 return if shown - painted.start <= painted.end - shown {
252 source.start
253 } else {
254 source.end
255 };
256 }
257 shift = source.end as isize - painted.end as isize;
258 }
259 shown.saturating_add_signed(shift)
260 }
261}