text_editing/lib.rs
1#![deny(missing_docs)]
2/*!
3Utilities to simplify text editing when implementing text editors.
4**/
5
6use std::{
7 convert::Infallible,
8 fmt::{Display, Formatter},
9 ops::Range,
10 str::FromStr,
11};
12
13/// The text line represents editable text lines.
14#[derive(Clone, Default)]
15pub struct TextLine {
16 text: String,
17 indices: Vec<usize>,
18}
19
20impl TextLine {
21 /// Creates a new empty text line.
22 ///
23 /// # Examples
24 ///
25 /// ```
26 /// use text_editing::TextLine;
27 ///
28 /// let line = TextLine::new();
29 /// assert!(line.is_empty());
30 /// ```
31 pub fn new() -> Self {
32 Self::default()
33 }
34
35 fn enable_indices(&mut self) {
36 let mut index = 0;
37 for c in self.text.chars() {
38 index += c.len_utf8() - 1;
39 self.indices.push(index);
40 }
41 }
42
43 fn refresh_indices(&mut self) {
44 if !self.text.is_ascii() {
45 self.enable_indices();
46 }
47 }
48
49 /// Creates a text line from a `String`.
50 ///
51 /// # Examples
52 ///
53 /// ```
54 /// use text_editing::TextLine;
55 ///
56 /// let line = TextLine::from_string("Hello, world!".into());
57 /// assert_eq!(line.as_str(), "Hello, world!");
58 /// ```
59 pub fn from_string(text: String) -> Self {
60 let mut result = Self {
61 text,
62 indices: Vec::new(),
63 };
64 result.refresh_indices();
65 result
66 }
67
68 /// Clears the text line, removing all content.
69 ///
70 /// # Examples
71 ///
72 /// ```
73 /// use text_editing::TextLine;
74 ///
75 /// let mut line = TextLine::from_string("Hello, world!".into());
76 /// line.clear();
77 /// assert!(line.is_empty());
78 /// ```
79 pub fn clear(&mut self) {
80 self.text.clear();
81 self.indices.clear();
82 }
83
84 /// Checks if the line is empty.
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// use text_editing::TextLine;
90 ///
91 /// let line = TextLine::new();
92 /// assert!(line.is_empty());
93 /// ```
94 pub fn is_empty(&self) -> bool {
95 self.text.is_empty()
96 }
97
98 /// Returns the length of the text line.
99 ///
100 /// # Examples
101 ///
102 /// ```
103 /// use text_editing::TextLine;
104 ///
105 /// let line = TextLine::from_string("Hello, world!".into());
106 /// assert_eq!(line.len(), 13);
107 /// ```
108 pub fn len(&self) -> usize {
109 if self.indices.is_empty() {
110 self.text.len()
111 } else {
112 self.indices.len()
113 }
114 }
115
116 /// Returns the text of the text line as a `str` reference.
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// use text_editing::TextLine;
122 ///
123 /// let line = TextLine::from_string("Hello, world!".into());
124 /// assert_eq!(line.as_str(), "Hello, world!");
125 /// ```
126 pub fn as_str(&self) -> &str {
127 &self.text
128 }
129
130 /// Converts the character index to the string index.
131 ///
132 /// # Examples
133 ///
134 /// ```
135 /// use text_editing::TextLine;
136 ///
137 /// let line = TextLine::from_string("Hello, world!".into());
138 /// assert_eq!(line.string_index(7), 7);
139 /// ```
140 pub fn string_index(&self, index: usize) -> usize {
141 if !self.indices.is_empty() && index > 0 {
142 self.indices[index - 1] + index
143 } else {
144 index
145 }
146 }
147
148 /// Returns the char at the specified position.
149 ///
150 /// # Panics
151 ///
152 /// Panics if the position is out of bounds.
153 ///
154 /// # Examples
155 ///
156 /// ```
157 /// use text_editing::TextLine;
158 ///
159 /// let line = TextLine::from_string("Hello, world!".into());
160 /// assert_eq!(line.char_at(7), 'w');
161 /// ```
162 pub fn char_at(&self, at: usize) -> char {
163 self.char_at_checked(at).unwrap()
164 }
165
166 fn char_at_checked(&self, at: usize) -> Option<char> {
167 self.text[self.string_index(at)..].chars().next()
168 }
169
170 /// Inserts a new char into the text line.
171 ///
172 /// # Examples
173 ///
174 /// ```
175 /// use text_editing::TextLine;
176 ///
177 /// let mut line = TextLine::from_string("Hello, orld!".into());
178 /// line.insert(7, 'w');
179 /// assert_eq!(line.as_str(), "Hello, world!");
180 /// ```
181 pub fn insert(&mut self, index: usize, c: char) {
182 self.text.insert(self.string_index(index), c);
183 self.indices.clear();
184 self.refresh_indices();
185 }
186
187 /// Removes a char from the text line and returns it.
188 ///
189 /// # Examples
190 ///
191 /// ```
192 /// use text_editing::TextLine;
193 ///
194 /// let mut line = TextLine::from_string("Hello, world!".into());
195 /// assert_eq!(line.remove(7), 'w');
196 /// assert_eq!(line.as_str(), "Hello, orld!");
197 /// ```
198 pub fn remove(&mut self, index: usize) -> char {
199 let result = self.text.remove(self.string_index(index));
200 self.indices.clear();
201 self.refresh_indices();
202 result
203 }
204
205 /// Removes the specified range from the text line.
206 ///
207 /// # Examples
208 ///
209 /// ```
210 /// use text_editing::TextLine;
211 ///
212 /// let mut line = TextLine::from_string("Hello, world!".into());
213 /// line.remove_range(7..12);
214 /// assert_eq!(line.as_str(), "Hello, !");
215 /// ```
216 pub fn remove_range(&mut self, range: Range<usize>) {
217 self.text.replace_range(
218 self.string_index(range.start)..self.string_index(range.end),
219 "",
220 );
221 self.indices.clear();
222 self.refresh_indices();
223 }
224
225 /// Splits a text line into two.
226 ///
227 /// # Examples
228 ///
229 /// ```
230 /// use text_editing::TextLine;
231 ///
232 /// let mut line = TextLine::from_string("Hello, world!".into());
233 /// let second_half = line.split(7);
234 /// assert_eq!(line.as_str(), "Hello, ");
235 /// assert_eq!(second_half.as_str(), "world!");
236 /// ```
237 pub fn split(&mut self, index: usize) -> Self {
238 let mut result = Self {
239 text: self.text.split_off(self.string_index(index)),
240 indices: Vec::new(),
241 };
242 self.indices.clear();
243 self.refresh_indices();
244 result.refresh_indices();
245 result
246 }
247
248 /// Joins two text lines into one.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use text_editing::TextLine;
254 ///
255 /// let mut line1 = TextLine::from_string("Hello, ".into());
256 /// let line2 = TextLine::from_string("world!".into());
257 /// line1.join(line2);
258 /// assert_eq!(line1.as_str(), "Hello, world!");
259 /// ```
260 pub fn join(&mut self, other: Self) {
261 self.text.push_str(&other.text);
262 self.indices.clear();
263 self.refresh_indices();
264 }
265}
266
267impl FromStr for TextLine {
268 type Err = Infallible;
269
270 fn from_str(text: &str) -> Result<Self, Infallible> {
271 Ok(Self::from_string(text.into()))
272 }
273}
274
275impl Display for TextLine {
276 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
277 write!(f, "{}", self.as_str())
278 }
279}
280
281impl From<String> for TextLine {
282 fn from(text: String) -> Self {
283 Self::from_string(text)
284 }
285}
286
287impl From<TextLine> for String {
288 fn from(text_line: TextLine) -> Self {
289 text_line.text
290 }
291}
292
293mod cursor;
294mod editing;
295mod selection;
296
297pub use cursor::Direction;