1use std::ops::Range;
2
3use densa_core::Rect;
4
5use crate::history::{Edit, History, Kind};
6use crate::selection::backspace_start;
7use crate::style::{is_char_range, ParagraphError, ParagraphStyle};
8use crate::{Affinity, Motion, Paragraph, TextSelection, TextSystem};
9
10#[derive(Debug)]
33pub struct TextEditor {
34 text: String,
35 style: ParagraphStyle,
36 paragraph: Paragraph,
37 selection: TextSelection,
38 composition: Option<Composition>,
39 shows_caret: bool,
40 history: History,
41}
42
43#[derive(Debug, Clone)]
45struct Composition {
46 range: Range<usize>,
48 replaced: String,
50 before: (usize, usize),
52}
53
54const LINE_BREAKS: [char; 7] = [
57 '\n', '\r', '\u{0B}', '\u{0C}', '\u{85}', '\u{2028}', '\u{2029}',
58];
59
60impl TextEditor {
61 pub fn new(
66 system: &mut TextSystem,
67 text: impl Into<String>,
68 style: ParagraphStyle,
69 ) -> Result<Self, ParagraphError> {
70 let text = text.into();
71 let paragraph = system.paragraph(&text, &style, &[])?;
72 let selection = TextSelection::caret_at(¶graph, text.len());
73 Ok(Self {
74 text,
75 style,
76 paragraph,
77 selection,
78 composition: None,
79 shows_caret: true,
80 history: History::default(),
81 })
82 }
83
84 pub fn text(&self) -> &str {
86 &self.text
87 }
88
89 pub fn style(&self) -> &ParagraphStyle {
91 &self.style
92 }
93
94 pub fn paragraph(&self) -> &Paragraph {
96 &self.paragraph
97 }
98
99 pub fn selection(&self) -> TextSelection {
101 self.selection
102 }
103
104 pub fn selected_text(&self) -> &str {
106 &self.text[self.selection.range()]
107 }
108
109 pub fn composition(&self) -> Option<Range<usize>> {
111 self.composition.as_ref().map(|c| c.range.clone())
112 }
113
114 pub fn shows_caret(&self) -> bool {
116 self.shows_caret
117 }
118
119 pub fn set_selection(&mut self, selection: TextSelection) {
122 self.keep_composition();
123 self.history.seal();
124 self.selection = selection.refreshed(&self.paragraph);
125 }
126
127 pub fn move_caret(&mut self, motion: Motion, extend: bool) {
130 self.keep_composition();
131 self.history.seal();
132 self.selection = self.selection.moved(&self.paragraph, motion, extend);
133 }
134
135 pub fn insert(&mut self, system: &mut TextSystem, text: &str) -> Result<(), ParagraphError> {
142 self.insert_as(system, text, Kind::Typing)
143 }
144
145 pub fn paste(&mut self, system: &mut TextSystem, text: &str) -> Result<(), ParagraphError> {
151 self.insert_as(system, text, Kind::Other)
152 }
153
154 fn insert_as(
155 &mut self,
156 system: &mut TextSystem,
157 text: &str,
158 kind: Kind,
159 ) -> Result<(), ParagraphError> {
160 let (range, removed, before) = match &self.composition {
161 Some(c) => (c.range.clone(), c.replaced.clone(), c.before),
162 None => (
163 self.selection.range(),
164 self.selected_text().to_owned(),
165 self.ends(),
166 ),
167 };
168 let at = range.start;
169 let caret = range.start + text.len();
170 let affinity = if text.ends_with(LINE_BREAKS) {
173 Affinity::Downstream
174 } else {
175 Affinity::Upstream
176 };
177 self.replace(system, range, text, |p| {
178 let at = TextSelection::caret_with(p, caret, affinity);
179 if at.focus() < caret {
182 at.moved(p, Motion::Next, false)
183 } else {
184 at
185 }
186 })?;
187 self.composition = None;
188 self.shows_caret = true;
189 self.record(at, removed, text, before, kind);
190 Ok(())
191 }
192
193 pub fn delete(
203 &mut self,
204 system: &mut TextSystem,
205 motion: Motion,
206 ) -> Result<(), ParagraphError> {
207 if self.composition.is_some() {
208 return self.set_composition(system, "", None);
209 }
210 let range = self.deletion_range(motion);
211 if range.is_empty() {
212 return Ok(());
213 }
214 let kind = match motion {
215 _ if !self.selection.is_caret() => Kind::Other,
216 Motion::Previous | Motion::PreviousWord => Kind::Backspace,
217 Motion::Next | Motion::NextWord => Kind::ForwardDelete,
218 _ => Kind::Other,
219 };
220 let (before, removed) = (self.ends(), self.text[range.clone()].to_owned());
221 let caret = range.start;
222 self.replace(system, range, "", |p| TextSelection::caret_at(p, caret))?;
223 self.record(caret, removed, "", before, kind);
224 Ok(())
225 }
226
227 pub fn undo(&mut self, system: &mut TextSystem) -> Result<bool, ParagraphError> {
233 self.cancel_composition(system)?;
234 let Some(edit) = self.history.take_undo() else {
235 return Ok(false);
236 };
237 let range = edit.at..edit.at + edit.inserted.len();
238 let (anchor, focus) = edit.before;
239 let undone = self.replace(system, range, &edit.removed, |p| {
240 TextSelection::from_range(p, anchor, focus)
241 });
242 match undone {
243 Ok(()) => self.history.push_redo(edit),
244 Err(_) => self.history.push_undo(edit),
245 }
246 undone.map(|()| true)
247 }
248
249 pub fn redo(&mut self, system: &mut TextSystem) -> Result<bool, ParagraphError> {
255 self.cancel_composition(system)?;
256 let Some(edit) = self.history.take_redo() else {
257 return Ok(false);
258 };
259 let range = edit.at..edit.at + edit.removed.len();
260 let (anchor, focus) = edit.after;
261 let redone = self.replace(system, range, &edit.inserted, |p| {
262 TextSelection::from_range(p, anchor, focus)
263 });
264 match redone {
265 Ok(()) => self.history.push_undo(edit),
266 Err(_) => self.history.push_redo(edit),
267 }
268 redone.map(|()| true)
269 }
270
271 pub fn can_undo(&self) -> bool {
273 self.history.can_undo()
274 }
275
276 pub fn can_redo(&self) -> bool {
278 self.history.can_redo()
279 }
280
281 pub fn set_text(&mut self, system: &mut TextSystem, text: &str) -> Result<(), ParagraphError> {
287 let all = 0..self.text.len();
288 let caret = text.len();
289 self.replace(system, all, text, |p| TextSelection::caret_at(p, caret))?;
290 self.composition = None;
291 self.shows_caret = true;
292 self.history.clear();
293 Ok(())
294 }
295
296 pub fn set_style(
302 &mut self,
303 system: &mut TextSystem,
304 style: ParagraphStyle,
305 ) -> Result<(), ParagraphError> {
306 let paragraph = system.paragraph(&self.text, &style, &[])?;
307 self.selection = self.selection.refreshed(¶graph);
308 self.paragraph = paragraph;
309 self.style = style;
310 Ok(())
311 }
312
313 pub fn set_width(&mut self, max_width: Option<f32>) -> Result<(), ParagraphError> {
320 self.paragraph.reflow(max_width)?;
321 self.style.max_width = max_width;
322 self.selection = self.selection.refreshed(&self.paragraph);
323 Ok(())
324 }
325
326 pub fn set_composition(
336 &mut self,
337 system: &mut TextSystem,
338 text: &str,
339 cursor: Option<Range<usize>>,
340 ) -> Result<(), ParagraphError> {
341 if let Some(cursor) = cursor.as_ref().filter(|c| !is_char_range(text, c)) {
342 return Err(ParagraphError::InvalidRange(cursor.clone()));
343 }
344 if text.is_empty() {
345 return self.cancel_composition(system);
346 }
347 let (range, replaced, before) = match &self.composition {
348 Some(c) => (c.range.clone(), c.replaced.clone(), c.before),
349 None => (
350 self.selection.range(),
351 self.selected_text().to_owned(),
352 self.ends(),
353 ),
354 };
355 let start = range.start;
356 let selected = cursor.clone().unwrap_or(0..0);
357 self.replace(system, range, text, |p| {
358 TextSelection::from_range(p, start + selected.start, start + selected.end)
359 })?;
360 self.composition = Some(Composition {
361 range: start..start + text.len(),
362 replaced,
363 before,
364 });
365 self.shows_caret = cursor.is_some();
366 Ok(())
367 }
368
369 pub fn for_each_composition_rect(&self, f: impl FnMut(Rect)) {
372 if let Some(range) = self.composition() {
373 TextSelection::from_range(&self.paragraph, range.start, range.end)
374 .for_each_rect(&self.paragraph, f);
375 }
376 }
377
378 pub fn ime_area(&self) -> Rect {
381 let caret = self.selection.caret_rect(&self.paragraph, 0.0);
382 let mut area: Option<Rect> = None;
383 self.for_each_composition_rect(|rect| {
384 area = Some(area.map_or(rect, |a| union(a, rect)));
385 });
386 area.unwrap_or(caret)
387 }
388
389 fn deletion_range(&self, motion: Motion) -> Range<usize> {
391 if !self.selection.is_caret() {
392 return self.selection.range();
393 }
394 let caret = self.selection.focus();
395 if motion == Motion::Previous {
396 return backspace_start(&self.paragraph, &self.text, caret)..caret;
397 }
398 self.selection.moved(&self.paragraph, motion, true).range()
399 }
400
401 fn cancel_composition(&mut self, system: &mut TextSystem) -> Result<(), ParagraphError> {
403 if let Some(Composition {
404 range, replaced, ..
405 }) = self.composition.clone()
406 {
407 let (start, end) = (range.start, range.start + replaced.len());
408 self.replace(system, range, &replaced, |p| {
409 TextSelection::from_range(p, start, end)
410 })?;
411 }
412 self.composition = None;
413 self.shows_caret = true;
414 Ok(())
415 }
416
417 fn keep_composition(&mut self) {
419 if let Some(c) = self.composition.take() {
420 let kept = self.text[c.range.clone()].to_owned();
421 self.record(c.range.start, c.replaced, &kept, c.before, Kind::Other);
422 }
423 self.shows_caret = true;
424 }
425
426 fn ends(&self) -> (usize, usize) {
428 (self.selection.anchor(), self.selection.focus())
429 }
430
431 fn record(
433 &mut self,
434 at: usize,
435 removed: String,
436 inserted: &str,
437 before: (usize, usize),
438 kind: Kind,
439 ) {
440 if removed.is_empty() && inserted.is_empty() {
441 return;
442 }
443 self.history.record(Edit {
444 at,
445 removed,
446 inserted: inserted.to_owned(),
447 before,
448 after: self.ends(),
449 kind,
450 });
451 }
452
453 fn replace(
456 &mut self,
457 system: &mut TextSystem,
458 range: Range<usize>,
459 with: &str,
460 select: impl FnOnce(&Paragraph) -> TextSelection,
461 ) -> Result<(), ParagraphError> {
462 let mut text = String::with_capacity(self.text.len() - range.len() + with.len());
463 text.push_str(&self.text[..range.start]);
464 text.push_str(with);
465 text.push_str(&self.text[range.end..]);
466 let paragraph = system.paragraph(&text, &self.style, &[])?;
467 self.selection = select(¶graph);
468 self.paragraph = paragraph;
469 self.text = text;
470 Ok(())
471 }
472}
473
474fn union(a: Rect, b: Rect) -> Rect {
476 Rect::from_edges(
477 a.left().min(b.left()),
478 a.top().min(b.top()),
479 a.right().max(b.right()),
480 a.bottom().max(b.bottom()),
481 )
482}