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    /// Checks if the line is empty.
69    ///
70    /// # Examples
71    ///
72    /// ```
73    /// use text_editing::TextLine;
74    ///
75    /// let line = TextLine::new();
76    /// assert!(line.is_empty());
77    /// ```
78    pub fn is_empty(&self) -> bool {
79        self.text.is_empty()
80    }
81
82    /// Returns the length of the text line.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use text_editing::TextLine;
88    ///
89    /// let line = TextLine::from_string("Hello, world!".into());
90    /// assert_eq!(line.len(), 13);
91    /// ```
92    pub fn len(&self) -> usize {
93        if self.indices.is_empty() {
94            self.text.len()
95        } else {
96            self.indices.len()
97        }
98    }
99
100    /// Returns the text of the text line as a `str` reference.
101    ///
102    /// # Examples
103    ///
104    /// ```
105    /// use text_editing::TextLine;
106    ///
107    /// let line = TextLine::from_string("Hello, world!".into());
108    /// assert_eq!(line.as_str(), "Hello, world!");
109    /// ```
110    pub fn as_str(&self) -> &str {
111        &self.text
112    }
113
114    /// Converts the character index to the string index.
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use text_editing::TextLine;
120    ///
121    /// let line = TextLine::from_string("Hello, world!".into());
122    /// assert_eq!(line.string_index(7), 7);
123    /// ```
124    pub fn string_index(&self, index: usize) -> usize {
125        if !self.indices.is_empty() && index > 0 {
126            self.indices[index - 1] + index
127        } else {
128            index
129        }
130    }
131
132    /// Returns the char at the specified position.
133    ///
134    /// # Panics
135    ///
136    /// Panics if the position is out of bounds.
137    ///
138    /// # Examples
139    ///
140    /// ```
141    /// use text_editing::TextLine;
142    ///
143    /// let line = TextLine::from_string("Hello, world!".into());
144    /// assert_eq!(line.char_at(7), 'w');
145    /// ```
146    pub fn char_at(&self, at: usize) -> char {
147        self.char_at_checked(at).unwrap()
148    }
149
150    fn char_at_checked(&self, at: usize) -> Option<char> {
151        self.text[self.string_index(at)..].chars().next()
152    }
153
154    /// Inserts a new char into the text line.
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use text_editing::TextLine;
160    ///
161    /// let mut line = TextLine::from_string("Hello, orld!".into());
162    /// line.insert(7, 'w');
163    /// assert_eq!(line.as_str(), "Hello, world!");
164    /// ```
165    pub fn insert(&mut self, index: usize, c: char) {
166        self.text.insert(self.string_index(index), c);
167        self.indices.clear();
168        self.refresh_indices();
169    }
170
171    /// Removes a char from the text line and returns it.
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// use text_editing::TextLine;
177    ///
178    /// let mut line = TextLine::from_string("Hello, world!".into());
179    /// assert_eq!(line.remove(7), 'w');
180    /// assert_eq!(line.as_str(), "Hello, orld!");
181    /// ```
182    pub fn remove(&mut self, index: usize) -> char {
183        let result = self.text.remove(self.string_index(index));
184        self.indices.clear();
185        self.refresh_indices();
186        result
187    }
188
189    /// Removes the specified range from the text line.
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use text_editing::TextLine;
195    ///
196    /// let mut line = TextLine::from_string("Hello, world!".into());
197    /// line.remove_range(7..12);
198    /// assert_eq!(line.as_str(), "Hello, !");
199    /// ```
200    pub fn remove_range(&mut self, range: Range<usize>) {
201        self.text.replace_range(
202            self.string_index(range.start)..self.string_index(range.end),
203            "",
204        );
205        self.indices.clear();
206        self.refresh_indices();
207    }
208
209    /// Splits a text line into two.
210    ///
211    /// # Examples
212    ///
213    /// ```
214    /// use text_editing::TextLine;
215    ///
216    /// let mut line = TextLine::from_string("Hello, world!".into());
217    /// let second_half = line.split(7);
218    /// assert_eq!(line.as_str(), "Hello, ");
219    /// assert_eq!(second_half.as_str(), "world!");
220    /// ```
221    pub fn split(&mut self, index: usize) -> Self {
222        let mut result = Self {
223            text: self.text.split_off(self.string_index(index)),
224            indices: Vec::new(),
225        };
226        self.indices.clear();
227        self.refresh_indices();
228        result.refresh_indices();
229        result
230    }
231
232    /// Joins two text lines into one.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use text_editing::TextLine;
238    ///
239    /// let mut line1 = TextLine::from_string("Hello, ".into());
240    /// let line2 = TextLine::from_string("world!".into());
241    /// line1.join(line2);
242    /// assert_eq!(line1.as_str(), "Hello, world!");
243    /// ```
244    pub fn join(&mut self, other: Self) {
245        self.text.push_str(&other.text);
246        self.indices.clear();
247        self.refresh_indices();
248    }
249}
250
251impl FromStr for TextLine {
252    type Err = Infallible;
253
254    fn from_str(text: &str) -> Result<Self, Infallible> {
255        Ok(Self::from_string(text.to_string()))
256    }
257}
258
259impl Display for TextLine {
260    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
261        write!(f, "{}", self.as_str())
262    }
263}
264
265impl From<String> for TextLine {
266    fn from(text: String) -> Self {
267        Self::from_string(text)
268    }
269}
270
271impl From<TextLine> for String {
272    fn from(text_line: TextLine) -> Self {
273        text_line.text
274    }
275}
276
277mod cursor;
278mod editing;
279
280pub use cursor::Direction;