1use std::{
2 borrow::Cow,
3 fmt::Display,
4 ops::Range,
5};
6
7use ropey::{
8 Rope,
9 iter::Lines,
10};
11
12use crate::{
13 TextSelection,
14 editor_history::{
15 EditorHistory,
16 HistoryChange,
17 },
18 text_editor::{
19 Line,
20 TextEditor,
21 },
22};
23
24#[derive(Clone, Debug)]
26pub struct PreeditState {
27 pub start: usize,
29 pub len: usize,
31}
32
33pub struct RopeEditor {
35 pub(crate) rope: Rope,
36 pub(crate) selection: TextSelection,
37 pub(crate) indentation: u8,
38 pub(crate) history: EditorHistory,
39 pub(crate) preedit: Option<PreeditState>,
40}
41
42impl Display for RopeEditor {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 f.write_str(&self.rope.to_string())
45 }
46}
47
48impl RopeEditor {
49 pub fn new(
51 text: String,
52 selection: TextSelection,
53 indentation: u8,
54 history: EditorHistory,
55 ) -> Self {
56 Self {
57 rope: Rope::from_str(&text),
58 selection,
59 indentation,
60 history,
61 preedit: None,
62 }
63 }
64
65 pub fn rope(&self) -> &Rope {
66 &self.rope
67 }
68
69 pub fn set_preedit(&mut self, text: &str) {
75 let preedit_start = if let Some(preedit) = self.preedit.take() {
77 let start_char = self.rope.utf16_cu_to_char(preedit.start);
78 let end_char = self.rope.utf16_cu_to_char(preedit.start + preedit.len);
79 self.rope.remove(start_char..end_char);
80 preedit.start
81 } else {
82 self.cursor_pos()
83 };
84
85 let start_char = self.rope.utf16_cu_to_char(preedit_start);
87 let len_before = self.rope.len_utf16_cu();
88 self.rope.insert(start_char, text);
89 let len_after = self.rope.len_utf16_cu();
90 let preedit_len = len_after - len_before;
91
92 self.preedit = Some(PreeditState {
93 start: preedit_start,
94 len: preedit_len,
95 });
96 self.selection = TextSelection::Cursor(preedit_start + preedit_len);
97 }
98
99 pub fn clear_preedit(&mut self) {
101 if let Some(preedit) = self.preedit.take() {
102 let start_char = self.rope.utf16_cu_to_char(preedit.start);
103 let end_char = self.rope.utf16_cu_to_char(preedit.start + preedit.len);
104 self.rope.remove(start_char..end_char);
105 self.selection = TextSelection::Cursor(preedit.start);
106 }
107 }
108
109 pub fn has_preedit(&self) -> bool {
111 self.preedit.is_some()
112 }
113
114 pub fn committed_text(&self) -> String {
119 if let Some(preedit) = &self.preedit {
120 let start_char = self.rope.utf16_cu_to_char(preedit.start);
121 let end_char = self.rope.utf16_cu_to_char(preedit.start + preedit.len);
122 let before = self.rope.slice(..start_char);
123 let after = self.rope.slice(end_char..);
124 format!("{before}{after}")
125 } else {
126 self.rope.to_string()
127 }
128 }
129
130 pub fn preedit_text_segments(&self) -> (String, String, String) {
135 if let Some(preedit) = &self.preedit {
136 let start_char = self.rope.utf16_cu_to_char(preedit.start);
137 let end_char = self.rope.utf16_cu_to_char(preedit.start + preedit.len);
138 let before = self.rope.slice(..start_char).to_string();
139 let preedit_text = self.rope.slice(start_char..end_char).to_string();
140 let after = self.rope.slice(end_char..).to_string();
141 (before, preedit_text, after)
142 } else {
143 (self.rope.to_string(), String::new(), String::new())
144 }
145 }
146}
147
148impl TextEditor for RopeEditor {
149 type LinesIterator<'a> = LinesIterator<'a>;
150
151 fn lines(&self) -> Self::LinesIterator<'_> {
152 let lines = self.rope.lines();
153 LinesIterator { lines }
154 }
155
156 fn insert_char(&mut self, ch: char, idx: usize) -> usize {
157 let idx_utf8 = self.utf16_cu_to_char(idx);
158 let selection = self.selection.clone();
159
160 let len_before_insert = self.rope.len_utf16_cu();
161 self.rope.insert_char(idx_utf8, ch);
162 let len_after_insert = self.rope.len_utf16_cu();
163
164 let inserted_text_len = len_after_insert - len_before_insert;
165
166 self.history.push_change(HistoryChange::InsertChar {
167 idx,
168 ch,
169 len: inserted_text_len,
170 selection,
171 });
172
173 inserted_text_len
174 }
175
176 fn insert(&mut self, text: &str, idx: usize) -> usize {
177 let idx_utf8 = self.utf16_cu_to_char(idx);
178 let selection = self.selection.clone();
179
180 let len_before_insert = self.rope.len_utf16_cu();
181 self.rope.insert(idx_utf8, text);
182 let len_after_insert = self.rope.len_utf16_cu();
183
184 let inserted_text_len = len_after_insert - len_before_insert;
185
186 self.history.push_change(HistoryChange::InsertText {
187 idx,
188 text: text.to_owned(),
189 len: inserted_text_len,
190 selection,
191 });
192
193 inserted_text_len
194 }
195
196 fn remove(&mut self, range_utf16: Range<usize>) -> usize {
197 let range =
198 self.utf16_cu_to_char(range_utf16.start)..self.utf16_cu_to_char(range_utf16.end);
199 let text = self.rope.slice(range.clone()).to_string();
200 let selection = self.selection.clone();
201
202 let len_before_remove = self.rope.len_utf16_cu();
203 self.rope.remove(range);
204 let len_after_remove = self.rope.len_utf16_cu();
205
206 let removed_text_len = len_before_remove - len_after_remove;
207
208 self.history.push_change(HistoryChange::Remove {
209 idx: range_utf16.end - removed_text_len,
210 text,
211 len: removed_text_len,
212 selection,
213 });
214
215 removed_text_len
216 }
217
218 fn char_to_line(&self, char_idx: usize) -> usize {
219 self.rope.char_to_line(char_idx)
220 }
221
222 fn line_to_char(&self, line_idx: usize) -> usize {
223 self.rope.line_to_char(line_idx)
224 }
225
226 fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
227 self.rope.utf16_cu_to_char(utf16_cu_idx)
228 }
229
230 fn char_to_utf16_cu(&self, idx: usize) -> usize {
231 self.rope.char_to_utf16_cu(idx)
232 }
233
234 fn text(&self) -> Cow<'_, str> {
235 self.rope.slice(..).into()
236 }
237
238 fn line(&self, line_idx: usize) -> Option<Line<'_>> {
239 let line = self.rope.get_line(line_idx);
240
241 line.map(|line| Line {
242 text: line.into(),
243 utf16_len: line.len_utf16_cu(),
244 })
245 }
246
247 fn len_lines(&self) -> usize {
248 self.rope.len_lines()
249 }
250
251 fn len_chars(&self) -> usize {
252 self.rope.len_chars()
253 }
254
255 fn len_utf16_cu(&self) -> usize {
256 self.rope.len_utf16_cu()
257 }
258
259 fn selection(&self) -> &TextSelection {
260 &self.selection
261 }
262
263 fn selection_mut(&mut self) -> &mut TextSelection {
264 &mut self.selection
265 }
266
267 fn has_any_selection(&self) -> bool {
268 self.selection.is_range()
269 }
270
271 fn get_selection(&self) -> Option<(usize, usize)> {
272 match self.selection {
273 TextSelection::Cursor(_) => None,
274 TextSelection::Range { from, to } => Some((from, to)),
275 }
276 }
277
278 fn set(&mut self, text: &str) {
279 self.rope.remove(0..);
280 self.rope.insert(0, text);
281 if self.cursor_pos() > text.len() {
282 self.move_cursor_to(text.len());
283 }
284 }
285
286 fn clear_selection(&mut self) {
287 let end = self.selection().end();
288 self.selection_mut().set_as_cursor();
289 self.selection_mut().move_to(end);
290 }
291
292 fn set_selection(&mut self, (from, to): (usize, usize)) {
293 self.selection = TextSelection::Range { from, to };
294 }
295
296 fn get_selected_text(&self) -> Option<String> {
297 let (start, end) = self.get_selection_range()?;
298
299 let start = self.utf16_cu_to_char(start);
300 let end = self.utf16_cu_to_char(end);
301
302 Some(self.rope().get_slice(start..end)?.to_string())
303 }
304
305 fn get_selection_range(&self) -> Option<(usize, usize)> {
306 let (start, end) = match self.selection {
307 TextSelection::Cursor(_) => return None,
308 TextSelection::Range { from, to } => (from, to),
309 };
310
311 let (start, end) = if start < end {
313 (start, end)
314 } else {
315 (end, start)
316 };
317
318 Some((start, end))
319 }
320
321 fn undo(&mut self) -> Option<TextSelection> {
322 self.history.undo(&mut self.rope)
323 }
324
325 fn redo(&mut self) -> Option<TextSelection> {
326 self.history.redo(&mut self.rope)
327 }
328
329 fn editor_history(&self) -> &EditorHistory {
330 &self.history
331 }
332
333 fn editor_history_mut(&mut self) -> &mut EditorHistory {
334 &mut self.history
335 }
336
337 fn get_indentation(&self) -> u8 {
338 self.indentation
339 }
340}
341
342pub struct LinesIterator<'a> {
344 pub lines: Lines<'a>,
345}
346
347impl<'a> Iterator for LinesIterator<'a> {
348 type Item = Line<'a>;
349
350 fn next(&mut self) -> Option<Self::Item> {
351 let line = self.lines.next();
352
353 line.map(|line| Line {
354 text: line.into(),
355 utf16_len: line.len_utf16_cu(),
356 })
357 }
358}
359
360#[cfg(test)]
361mod test {
362 use std::time::Duration;
363
364 use keyboard_types::{
365 Key,
366 Modifiers,
367 NamedKey,
368 };
369
370 use super::RopeEditor;
371 use crate::{
372 EditorHistory,
373 TextSelection,
374 text_editor::TextEditor,
375 };
376
377 fn editor(text: &str) -> RopeEditor {
378 RopeEditor::new(
379 text.to_string(),
380 TextSelection::new_cursor(0),
381 4,
382 EditorHistory::new(Duration::ZERO),
383 )
384 }
385
386 fn press(ed: &mut RopeEditor, key: NamedKey) {
387 ed.process_key(
388 &Key::Named(key),
389 &Modifiers::empty(),
390 None,
391 None,
392 true,
393 true,
394 false,
395 false,
396 );
397 }
398
399 #[test]
400 fn cursor_moves_over_whole_graphemes() {
401 let mut ed = editor("a🙂👨👩👧e\u{301}\nb");
402
403 let mut positions = Vec::new();
404 while ed.cursor_right() {
405 positions.push(ed.cursor_pos());
406 }
407 assert_eq!(positions, [1, 3, 11, 13, 14, 15]);
408
409 positions.clear();
410 while ed.cursor_left() {
411 positions.push(ed.cursor_pos());
412 }
413 assert_eq!(positions, [14, 13, 11, 3, 1, 0]);
414 }
415
416 #[test]
417 fn backspace_and_delete_remove_whole_graphemes() {
418 let mut ed = editor("🙂a🙂");
419
420 press(&mut ed, NamedKey::Delete);
421 assert_eq!(ed.rope().to_string(), "a🙂");
422 assert_eq!(ed.cursor_pos(), 0);
423
424 ed.move_cursor_to(3);
425 press(&mut ed, NamedKey::Backspace);
426 assert_eq!(ed.rope().to_string(), "a");
427 assert_eq!(ed.cursor_pos(), 1);
428 }
429
430 #[test]
431 fn preedit_lifecycle() {
432 let mut ed = editor("Hello World");
433 ed.move_cursor_to(5);
435
436 assert!(!ed.has_preedit());
438 assert_eq!(ed.committed_text(), "Hello World");
439
440 ed.set_preedit("你好");
442 assert!(ed.has_preedit());
443 assert_eq!(ed.rope().to_string(), "Hello你好 World");
444 assert_eq!(ed.committed_text(), "Hello World");
445 assert_eq!(ed.cursor_pos(), 5 + "你好".encode_utf16().count());
446
447 ed.set_preedit("世界abc");
449 assert!(ed.has_preedit());
450 assert_eq!(ed.rope().to_string(), "Hello世界abc World");
451 assert_eq!(ed.committed_text(), "Hello World");
452 assert_eq!(ed.cursor_pos(), 5 + "世界abc".encode_utf16().count());
453
454 ed.clear_preedit();
456 assert!(!ed.has_preedit());
457 assert_eq!(ed.rope().to_string(), "Hello World");
458 assert_eq!(ed.committed_text(), "Hello World");
459 assert_eq!(ed.cursor_pos(), 5);
460 }
461
462 #[test]
463 fn preedit_skips_undo_history_and_clear_restores() {
464 let mut ed = editor("Hello");
465 ed.move_cursor_to(5);
466 assert!(!ed.history.can_undo());
467
468 ed.set_preedit("XY");
470 assert!(!ed.history.can_undo());
471 assert_eq!(ed.rope().to_string(), "HelloXY");
472
473 ed.set_preedit("Z");
475 assert!(!ed.history.can_undo());
476 assert_eq!(ed.rope().to_string(), "HelloZ");
477
478 ed.clear_preedit();
480 assert!(!ed.has_preedit());
481 assert!(!ed.history.can_undo());
482 assert_eq!(ed.rope().to_string(), "Hello");
483 assert_eq!(ed.cursor_pos(), 5);
484
485 ed.clear_preedit();
487 assert_eq!(ed.rope().to_string(), "Hello");
488 assert_eq!(ed.cursor_pos(), 5);
489 }
490}