vizia_core/text/
editable_text.rs

1#![allow(dead_code)]
2
3use std::{borrow::Cow, ops::Range};
4
5use unicode_segmentation::{GraphemeCursor, UnicodeSegmentation};
6
7pub trait EditableText: Sized {
8    /// Replace range with new text.
9    /// Can panic if supplied an invalid range.
10    fn edit(&mut self, range: Range<usize>, new: impl Into<Self>);
11
12    /// Get slice of text at range.
13    fn slice(&self, range: Range<usize>) -> Option<Cow<str>>;
14
15    /// Get length of text (in bytes).
16    fn len(&self) -> usize;
17
18    /// Get the previous word offset from the given offset, if it exists.
19    fn prev_word_offset(&self, offset: usize) -> Option<usize>;
20
21    /// Get the next word offset from the given offset, if it exists.
22    fn next_word_offset(&self, offset: usize) -> Option<usize>;
23
24    /// Get the next grapheme offset from the given offset, if it exists.
25    fn prev_grapheme_offset(&self, offset: usize) -> Option<usize>;
26
27    /// Get the next grapheme offset from the given offset, if it exists.
28    fn next_grapheme_offset(&self, offset: usize) -> Option<usize>;
29
30    fn current_grapheme_offset(&self, offset: usize) -> usize;
31
32    /// Get the previous codepoint offset from the given offset, if it exists.
33    fn prev_codepoint_offset(&self, offset: usize) -> Option<usize>;
34
35    /// Get the next codepoint offset from the given offset, if it exists.
36    fn next_codepoint_offset(&self, offset: usize) -> Option<usize>;
37
38    fn prev_codepoint(&self, offset: usize) -> Option<char>;
39
40    /// Get the preceding line break offset from the given offset
41    fn preceding_line_break(&self, offset: usize) -> usize;
42
43    /// Get the next line break offset from the given offset
44    fn next_line_break(&self, offset: usize) -> usize;
45
46    /// Returns `true` if this text has 0 length.
47    fn is_empty(&self) -> bool;
48
49    /// Construct an instance of this type from a `&str`.
50    fn from_str(s: &str) -> Self;
51}
52
53impl EditableText for String {
54    fn edit(&mut self, range: Range<usize>, new: impl Into<Self>) {
55        self.replace_range(range, &new.into());
56    }
57
58    fn slice(&self, range: Range<usize>) -> Option<Cow<str>> {
59        self.get(range).map(Cow::from)
60    }
61
62    fn len(&self) -> usize {
63        self.len()
64    }
65
66    fn prev_grapheme_offset(&self, from: usize) -> Option<usize> {
67        let mut c = GraphemeCursor::new(from, self.len(), true);
68        c.prev_boundary(self, 0).unwrap()
69    }
70
71    fn next_grapheme_offset(&self, from: usize) -> Option<usize> {
72        let mut c = GraphemeCursor::new(from, self.len(), true);
73        c.next_boundary(self, 0).unwrap()
74    }
75
76    fn current_grapheme_offset(&self, from: usize) -> usize {
77        if from == self.len() {
78            self.graphemes(true).count()
79        } else {
80            let mut current = self.graphemes(true).count();
81
82            let mut iter = self.grapheme_indices(true).peekable();
83            let mut count = 0;
84            while let Some((i, _)) = iter.next() {
85                let ni = if let Some(next) = iter.peek() { next.0 } else { self.len() };
86
87                if from >= i && from < ni {
88                    current = count;
89                    break;
90                }
91
92                count += 1;
93            }
94
95            current
96        }
97    }
98
99    fn prev_codepoint_offset(&self, current_pos: usize) -> Option<usize> {
100        if current_pos == 0 {
101            None
102        } else {
103            let mut len = 1;
104            while !self.is_char_boundary(current_pos - len) {
105                len += 1;
106            }
107
108            Some(current_pos - len)
109        }
110    }
111
112    fn next_codepoint_offset(&self, current_pos: usize) -> Option<usize> {
113        if current_pos == self.len() {
114            None
115        } else {
116            let b = self.as_bytes()[current_pos];
117            Some(current_pos + len_utf8_from_first_byte(b))
118        }
119    }
120
121    fn prev_word_offset(&self, from: usize) -> Option<usize> {
122        let mut offset = from;
123        let mut passed_alphanumeric = false;
124        for prev_grapheme in self.get(0..from)?.graphemes(true).rev() {
125            let is_alphanumeric = prev_grapheme.chars().next()?.is_alphanumeric();
126            if is_alphanumeric {
127                passed_alphanumeric = true;
128            } else if passed_alphanumeric {
129                return Some(offset);
130            }
131            offset -= prev_grapheme.len();
132        }
133        None
134    }
135
136    fn next_word_offset(&self, from: usize) -> Option<usize> {
137        let mut offset = from;
138        let mut passed_alphanumeric = false;
139        for next_grapheme in self.get(from..)?.graphemes(true) {
140            let is_alphanumeric = next_grapheme.chars().next()?.is_alphanumeric();
141            if is_alphanumeric {
142                passed_alphanumeric = true;
143            } else if passed_alphanumeric {
144                return Some(offset);
145            }
146            offset += next_grapheme.len();
147        }
148        Some(self.len())
149    }
150
151    fn is_empty(&self) -> bool {
152        self.is_empty()
153    }
154
155    fn from_str(s: &str) -> Self {
156        s.to_string()
157    }
158
159    fn preceding_line_break(&self, from: usize) -> usize {
160        let mut offset = from;
161
162        for byte in self.get(0..from).unwrap_or("").bytes().rev() {
163            if byte == 0x0a {
164                return offset;
165            }
166            offset -= 1;
167        }
168
169        0
170    }
171
172    fn next_line_break(&self, from: usize) -> usize {
173        let mut offset = from;
174
175        for char in self.get(from..).unwrap_or("").bytes() {
176            if char == 0x0a {
177                return offset;
178            }
179            offset += 1;
180        }
181
182        self.len()
183    }
184
185    fn prev_codepoint(&self, offset: usize) -> Option<char> {
186        if let Some(prev) = self.prev_codepoint_offset(offset) {
187            self[prev..].chars().next()
188        } else {
189            None
190        }
191    }
192}
193
194pub fn len_utf8_from_first_byte(b: u8) -> usize {
195    match b {
196        b if b < 0x80 => 1,
197        b if b < 0xe0 => 2,
198        b if b < 0xf0 => 3,
199        _ => 4,
200    }
201}