Skip to main content

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;