rat_text/
text_core.rs

1use crate::cache::{Cache, LineWidthCache};
2use crate::clipboard::Clipboard;
3use crate::glyph2::{GlyphIter2, TextWrap2};
4use crate::grapheme::Grapheme;
5use crate::range_map::{RangeMap, expand_range_by, ranges_intersect, shrink_range_by};
6use crate::text_store::TextStore;
7use crate::undo_buffer::{StyleChange, TextPositionChange, UndoBuffer, UndoEntry, UndoOp};
8use crate::{TextError, TextPosition, TextRange, upos_type};
9use dyn_clone::clone_box;
10use ratatui::layout::Size;
11use std::borrow::Cow;
12use std::cell::Cell;
13use std::cmp::min;
14use std::ops::Range;
15use std::rc::Rc;
16
17pub mod core_op;
18
19#[derive(Debug, Default, PartialEq, Eq, Clone, Copy, Hash)]
20pub(crate) struct TextCursor {
21    pub(crate) anchor: TextPosition,
22    pub(crate) cursor: TextPosition,
23}
24
25impl TextCursor {
26    pub fn new(anchor: TextPosition, cursor: TextPosition) -> Self {
27        Self { anchor, cursor }
28    }
29}
30
31/// Core for text editing.
32#[derive(Debug)]
33pub struct TextCore<Store> {
34    /// Text store.
35    text: Store,
36
37    cursor: Rc<Cell<TextCursor>>,
38
39    /// styles
40    styles: Option<Box<RangeMap>>,
41    /// undo-buffer
42    undo: Option<Box<dyn UndoBuffer>>,
43    /// clipboard
44    clip: Option<Box<dyn Clipboard>>,
45    /// cache
46    cache: Cache,
47
48    /// show ctrl chars in glyphs
49    glyph_ctrl: bool,
50    /// show text-wrap glyphs
51    wrap_ctrl: bool,
52}
53
54impl<Store: Clone> Clone for TextCore<Store> {
55    fn clone(&self) -> Self {
56        Self {
57            text: self.text.clone(),
58            cursor: self.cursor.clone(),
59            styles: self.styles.clone(),
60            undo: self.undo.as_ref().map(|v| clone_box(v.as_ref())),
61            clip: self.clip.as_ref().map(|v| clone_box(v.as_ref())),
62            cache: Default::default(),
63            glyph_ctrl: self.glyph_ctrl,
64            wrap_ctrl: self.wrap_ctrl,
65        }
66    }
67}
68
69impl<Store: TextStore + Default> TextCore<Store> {
70    pub fn new(undo: Option<Box<dyn UndoBuffer>>, clip: Option<Box<dyn Clipboard>>) -> Self {
71        Self {
72            text: Store::default(),
73            cursor: Default::default(),
74            styles: Default::default(),
75            undo,
76            clip,
77            cache: Default::default(),
78            glyph_ctrl: false,
79            wrap_ctrl: false,
80        }
81    }
82
83    /// Show control characters when iterating glyphs.
84    #[inline]
85    pub fn set_glyph_ctrl(&mut self, show_ctrl: bool) {
86        self.glyph_ctrl = show_ctrl;
87    }
88
89    /// Show glyphs for text-wrap.
90    pub fn glyph_ctrl(&self) -> bool {
91        self.glyph_ctrl
92    }
93
94    /// Show glyphs for text-wrap.
95    #[inline]
96    pub fn set_wrap_ctrl(&mut self, wrap_ctrl: bool) {
97        self.wrap_ctrl = wrap_ctrl;
98    }
99
100    /// Show control characters when iterating glyphs.
101    pub fn wrap_ctrl(&self) -> bool {
102        self.wrap_ctrl
103    }
104}
105
106impl<Store: TextStore + Default> TextCore<Store> {
107    /// Clipboard
108    pub fn set_clipboard(&mut self, clip: Option<Box<dyn Clipboard + 'static>>) {
109        self.clip = clip;
110    }
111
112    /// Clipboard
113    pub fn clipboard(&self) -> Option<&dyn Clipboard> {
114        match &self.clip {
115            None => None,
116            Some(v) => Some(v.as_ref()),
117        }
118    }
119}
120
121impl<Store: TextStore + Default> TextCore<Store> {
122    /// Undo
123    #[inline]
124    pub fn set_undo_buffer(&mut self, undo: Option<Box<dyn UndoBuffer>>) {
125        self.undo = undo;
126    }
127
128    /// Set undo count
129    #[inline]
130    pub fn set_undo_count(&mut self, n: u32) {
131        if let Some(undo) = self.undo.as_mut() {
132            undo.set_undo_count(n);
133        };
134    }
135
136    /// Begin a sequence of changes that should be undone in one go.
137    #[inline]
138    pub fn begin_undo_seq(&mut self) {
139        if let Some(undo) = self.undo.as_mut() {
140            undo.begin_seq();
141        };
142    }
143
144    /// End a sequence of changes that should be undone in one go.
145    #[inline]
146    pub fn end_undo_seq(&mut self) {
147        if let Some(undo) = self.undo.as_mut() {
148            undo.end_seq();
149        };
150    }
151
152    /// Undo
153    #[inline]
154    pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
155        match &self.undo {
156            None => None,
157            Some(v) => Some(v.as_ref()),
158        }
159    }
160
161    /// Undo
162    #[inline]
163    pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
164        match &mut self.undo {
165            None => None,
166            Some(v) => Some(v.as_mut()),
167        }
168    }
169
170    /// Undo last.
171    pub fn undo(&mut self) -> bool {
172        let Some(undo) = self.undo.as_mut() else {
173            return false;
174        };
175
176        undo.append(UndoOp::Undo);
177
178        self._undo()
179    }
180
181    /// Undo last.
182    fn _undo(&mut self) -> bool {
183        let Some(undo) = self.undo.as_mut() else {
184            return false;
185        };
186        let undo_op = undo.undo();
187        let changed = !undo_op.is_empty();
188        for op in undo_op {
189            match op {
190                UndoOp::InsertChar {
191                    bytes,
192                    cursor,
193                    anchor,
194                    ..
195                }
196                | UndoOp::InsertStr {
197                    bytes,
198                    cursor,
199                    anchor,
200                    ..
201                } => {
202                    self.text.remove_b(bytes.clone()).expect("valid_bytes");
203
204                    if let Some(sty) = &mut self.styles {
205                        sty.remap(|r, _| Some(shrink_range_by(bytes.clone(), r)));
206                    }
207                    self.cursor
208                        .set(TextCursor::new(anchor.before, cursor.before));
209                }
210                UndoOp::RemoveStr {
211                    bytes,
212                    cursor,
213                    anchor,
214                    txt,
215                    styles,
216                }
217                | UndoOp::RemoveChar {
218                    bytes,
219                    cursor,
220                    anchor,
221                    txt,
222                    styles,
223                } => {
224                    self.text.insert_b(bytes.start, txt).expect("valid_bytes");
225
226                    if let Some(sty) = &mut self.styles {
227                        for s in styles {
228                            sty.remove(s.after.clone(), s.style);
229                        }
230                        for s in styles {
231                            sty.add(s.before.clone(), s.style);
232                        }
233                        sty.remap(|r, _| {
234                            if ranges_intersect(bytes.clone(), r.clone()) {
235                                Some(r)
236                            } else {
237                                Some(expand_range_by(bytes.clone(), r))
238                            }
239                        });
240                    }
241                    self.cursor
242                        .set(TextCursor::new(anchor.before, cursor.before));
243                }
244                UndoOp::Cursor { cursor, anchor } => {
245                    self.cursor
246                        .set(TextCursor::new(anchor.before, cursor.before));
247                }
248                UndoOp::SetStyles { styles_before, .. } => {
249                    if let Some(sty) = &mut self.styles {
250                        sty.set(styles_before.iter().cloned());
251                    }
252                }
253                UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
254                    unreachable!()
255                }
256            }
257        }
258        changed
259    }
260
261    /// Redo last.
262    pub fn redo(&mut self) -> bool {
263        let Some(undo) = self.undo.as_mut() else {
264            return false;
265        };
266
267        undo.append(UndoOp::Redo);
268
269        self._redo()
270    }
271
272    fn _redo(&mut self) -> bool {
273        let Some(undo) = self.undo.as_mut() else {
274            return false;
275        };
276        let redo_op = undo.redo();
277        let changed = !redo_op.is_empty();
278        for op in redo_op {
279            match op {
280                UndoOp::InsertChar {
281                    bytes,
282                    cursor,
283                    anchor,
284                    txt,
285                }
286                | UndoOp::InsertStr {
287                    bytes,
288                    cursor,
289                    anchor,
290                    txt,
291                } => {
292                    self.text.insert_b(bytes.start, txt).expect("valid_bytes");
293                    if let Some(sty) = &mut self.styles {
294                        sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
295                    }
296                    self.cursor.set(TextCursor::new(anchor.after, cursor.after));
297                }
298                UndoOp::RemoveChar {
299                    bytes,
300                    cursor,
301                    anchor,
302                    styles,
303                    ..
304                }
305                | UndoOp::RemoveStr {
306                    bytes,
307                    cursor,
308                    anchor,
309                    styles,
310                    ..
311                } => {
312                    self.text.remove_b(bytes.clone()).expect("valid_bytes");
313
314                    if let Some(sty) = &mut self.styles {
315                        sty.remap(|r, _| {
316                            if ranges_intersect(bytes.clone(), r.clone()) {
317                                Some(r)
318                            } else {
319                                Some(shrink_range_by(bytes.clone(), r))
320                            }
321                        });
322                        for s in styles {
323                            sty.remove(s.before.clone(), s.style);
324                        }
325                        for s in styles {
326                            sty.add(s.after.clone(), s.style);
327                        }
328                    }
329                    self.cursor.set(TextCursor::new(anchor.after, cursor.after));
330                }
331                UndoOp::Cursor { cursor, anchor } => {
332                    self.cursor.set(TextCursor::new(anchor.after, cursor.after));
333                }
334
335                UndoOp::SetStyles { styles_after, .. } => {
336                    if let Some(sty) = &mut self.styles {
337                        sty.set(styles_after.iter().cloned());
338                    }
339                }
340                UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
341                    unreachable!()
342                }
343            }
344        }
345        changed
346    }
347
348    /// Get last replay recording.
349    pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
350        if let Some(undo) = &mut self.undo {
351            undo.recent_replay_log()
352        } else {
353            Vec::default()
354        }
355    }
356
357    /// Replay a recording of changes.
358    pub fn replay_log(&mut self, replay: &[UndoEntry]) {
359        for replay_entry in replay {
360            match &replay_entry.operation {
361                UndoOp::SetText { txt } => {
362                    self.text.set_string(txt);
363                    if let Some(sty) = &mut self.styles {
364                        sty.clear();
365                    }
366                    if let Some(undo) = self.undo.as_mut() {
367                        undo.clear();
368                    };
369                }
370                UndoOp::InsertChar { bytes, txt, .. } | UndoOp::InsertStr { bytes, txt, .. } => {
371                    self.text.insert_b(bytes.start, txt).expect("valid_range");
372                    if let Some(sty) = &mut self.styles {
373                        sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
374                    }
375                }
376                UndoOp::RemoveChar { bytes, styles, .. }
377                | UndoOp::RemoveStr { bytes, styles, .. } => {
378                    self.text.remove_b(bytes.clone()).expect("valid_range");
379                    if let Some(sty) = &mut self.styles {
380                        sty.remap(|r, _| {
381                            if ranges_intersect(bytes.clone(), r.clone()) {
382                                Some(r)
383                            } else {
384                                Some(shrink_range_by(bytes.clone(), r))
385                            }
386                        });
387                        for s in styles {
388                            sty.remove(s.before.clone(), s.style);
389                        }
390                        for s in styles {
391                            sty.add(s.after.clone(), s.style);
392                        }
393                    }
394                }
395                UndoOp::Cursor { .. } => {
396                    // don't do cursor
397                }
398
399                UndoOp::SetStyles { styles_after, .. } => {
400                    self.init_styles();
401                    if let Some(sty) = &mut self.styles {
402                        sty.set(styles_after.iter().cloned());
403                    }
404                }
405                UndoOp::Undo => {
406                    self._undo();
407                }
408                UndoOp::Redo => {
409                    self._redo();
410                }
411            }
412
413            if let Some(undo) = self.undo.as_mut() {
414                undo.append_from_replay(replay_entry.clone());
415            };
416        }
417    }
418}
419
420impl<Store: TextStore + Default> TextCore<Store> {
421    fn init_styles(&mut self) {
422        if self.styles.is_none() {
423            self.styles = Some(Box::new(RangeMap::default()));
424        }
425    }
426
427    /// Set all styles.
428    ///
429    /// The ranges are byte-ranges. The usize value is the index of the
430    /// actual style. Those are set with the widget.
431    #[inline]
432    pub fn set_styles(&mut self, new_styles: Vec<(Range<usize>, usize)>) {
433        self.init_styles();
434
435        let Some(sty) = &mut self.styles else {
436            return;
437        };
438        if let Some(undo) = &mut self.undo {
439            if undo.undo_styles_enabled() || undo.has_replay_log() {
440                undo.append(UndoOp::SetStyles {
441                    styles_before: sty.values().collect::<Vec<_>>(),
442                    styles_after: new_styles.clone(),
443                });
444            }
445        }
446        sty.set(new_styles.into_iter());
447    }
448
449    /// Add a style for the given byte-range.
450    ///
451    /// The usize value is the index of the actual style.
452    /// Those are set at the widget.
453    #[inline]
454    pub fn add_style(&mut self, range: Range<usize>, style: usize) {
455        self.init_styles();
456
457        if let Some(sty) = &mut self.styles {
458            sty.add(range.clone(), style);
459        }
460    }
461
462    /// Remove a style for the given byte-range.
463    ///
464    /// Range and style must match to be removed.
465    #[inline]
466    pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
467        if let Some(sty) = &mut self.styles {
468            sty.remove(range.clone(), style);
469        }
470    }
471
472    /// Remove all ranges for the given style.
473    #[inline]
474    pub fn remove_style_fully(&mut self, style: usize) {
475        let Some(sty) = self.styles.as_mut() else {
476            return;
477        };
478        let styles = sty
479            .values()
480            .filter(|(_, s)| *s == style)
481            .collect::<Vec<_>>();
482        for (range, style) in &styles {
483            sty.remove(range.clone(), *style);
484        }
485    }
486
487    /// Find all values for the given position and writes them
488    /// to the output buffer. Clears the output buffer first.
489    ///
490    /// This creates a cache for the styles in the given range.
491    #[inline]
492    pub(crate) fn styles_at_page(&self, pos: usize, range: Range<usize>, buf: &mut Vec<usize>) {
493        if let Some(sty) = &self.styles {
494            sty.values_at_page(pos, range, buf);
495        }
496    }
497
498    /// Find all styles that touch the given range.
499    #[inline]
500    pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
501        if let Some(sty) = &self.styles {
502            sty.values_in(range, buf);
503        }
504    }
505
506    /// Find all styles that touch the given range.
507    #[inline]
508    pub fn styles_in_match(
509        &self,
510        range: Range<usize>,
511        style: usize,
512        buf: &mut Vec<(Range<usize>, usize)>,
513    ) {
514        if let Some(sty) = &self.styles {
515            sty.values_in_match(range, style, buf);
516        }
517    }
518
519    /// Finds all styles for the given position.
520    #[inline]
521    pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
522        if let Some(sty) = &self.styles {
523            sty.values_at(byte_pos, buf);
524        }
525    }
526
527    /// Finds all styles for the given position.
528    #[inline]
529    pub fn styles_at_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
530        if let Some(sty) = &self.styles {
531            sty.value_match(byte_pos, style)
532        } else {
533            None
534        }
535    }
536
537    /// List of all styles.
538    #[inline]
539    pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
540        self.styles.as_ref().map(|v| v.values())
541    }
542}
543
544impl<Store: TextStore + Default> TextCore<Store> {
545    /// Shared cursor.
546    pub(crate) fn shared_cursor(&self) -> Rc<Cell<TextCursor>> {
547        self.cursor.clone()
548    }
549
550    /// Set the cursor position.
551    /// The value is capped to the number of text lines and
552    /// the line-width for the given line.
553    ///
554    /// Returns true, if the cursor actually changed.
555    pub fn set_cursor(&mut self, cursor: TextPosition, extend_selection: bool) -> bool {
556        let old_cursor = self.cursor.get();
557
558        let cursor_pos = TextPosition::new(
559            min(cursor.x, self.line_width(cursor.y).expect("valid-line")),
560            min(cursor.y, self.len_lines()),
561        );
562        let new_cursor = TextCursor {
563            cursor: cursor_pos,
564            anchor: if !extend_selection {
565                cursor_pos
566            } else {
567                old_cursor.anchor
568            },
569        };
570        self.cursor.set(new_cursor);
571
572        if let Some(undo) = self.undo.as_mut() {
573            undo.append(UndoOp::Cursor {
574                cursor: TextPositionChange {
575                    before: old_cursor.cursor,
576                    after: new_cursor.cursor,
577                },
578                anchor: TextPositionChange {
579                    before: old_cursor.anchor,
580                    after: new_cursor.anchor,
581                },
582            });
583        }
584
585        old_cursor != self.cursor.get()
586    }
587
588    /// Cursor position as grapheme-idx.
589    #[inline]
590    pub fn cursor(&self) -> TextPosition {
591        self.cursor.get().cursor
592    }
593
594    /// Selection anchor
595    #[inline]
596    pub fn anchor(&self) -> TextPosition {
597        self.cursor.get().anchor
598    }
599
600    /// Any text selection.
601    #[inline]
602    pub fn has_selection(&self) -> bool {
603        let cursor = self.cursor.get();
604        cursor.anchor != cursor.cursor
605    }
606
607    /// Select text.
608    /// Anchor and cursor are capped to a valid value.
609    #[inline]
610    pub fn set_selection(&mut self, anchor: TextPosition, cursor: TextPosition) -> bool {
611        let old_selection = self.selection();
612
613        self.set_cursor(anchor, false);
614        self.set_cursor(cursor, true);
615
616        old_selection != self.selection()
617    }
618
619    /// Select all text.
620    #[inline]
621    pub fn select_all(&mut self) -> bool {
622        let old_selection = self.selection();
623
624        self.set_cursor(TextPosition::new(0, self.len_lines()), false);
625        self.set_cursor(TextPosition::new(0, 0), true);
626
627        old_selection != self.selection()
628    }
629
630    /// Returns the selection as TextRange.
631    #[inline]
632    pub fn selection(&self) -> TextRange {
633        let cursor = self.cursor.get();
634        #[allow(clippy::comparison_chain)]
635        if cursor.cursor.y < cursor.anchor.y {
636            TextRange {
637                start: cursor.cursor,
638                end: cursor.anchor,
639            }
640        } else if cursor.cursor.y > cursor.anchor.y {
641            TextRange {
642                start: cursor.anchor,
643                end: cursor.cursor,
644            }
645        } else {
646            if cursor.cursor.x < cursor.anchor.x {
647                TextRange {
648                    start: cursor.cursor,
649                    end: cursor.anchor,
650                }
651            } else {
652                TextRange {
653                    start: cursor.anchor,
654                    end: cursor.cursor,
655                }
656            }
657        }
658    }
659}
660
661impl<Store: TextStore + Default> TextCore<Store> {
662    /// Minimum byte position that has been changed
663    /// since the last call of min_changed().
664    ///
665    /// Can be used to invalidate caches.
666    pub(crate) fn cache_validity(&self) -> Option<usize> {
667        self.text.cache_validity()
668    }
669}
670
671impl<Store: TextStore + Default> TextCore<Store> {
672    /// Empty.
673    #[inline]
674    pub fn is_empty(&self) -> bool {
675        self.len_bytes() == 0
676    }
677
678    /// Grapheme position to byte position.
679    /// This is the (start,end) position of the single grapheme after pos.
680    #[inline]
681    pub fn byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
682        self.text.byte_range_at(pos)
683    }
684
685    /// Grapheme range to byte range.
686    #[inline]
687    pub fn bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
688        self.text.byte_range(range)
689    }
690
691    /// Byte position to grapheme position.
692    /// Returns the position that contains the given byte index.
693    #[inline]
694    pub fn byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
695        self.text.byte_to_pos(byte)
696    }
697
698    /// Byte range to grapheme range.
699    #[inline]
700    pub fn byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
701        self.text.bytes_to_range(bytes)
702    }
703
704    /// A range of the text as `Cow<str>`
705    #[inline]
706    pub fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
707        self.text.str_slice(range)
708    }
709
710    /// A range of the text as `Cow<str>`
711    #[inline]
712    pub fn str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
713        self.text.str_slice_byte(range)
714    }
715
716    /// Limited access to the cache.
717    /// Gives only access to Debug.
718    #[inline]
719    pub fn cache(&self) -> &Cache {
720        &self.cache
721    }
722
723    /// Fill the cache for all the given rows completely.
724    #[allow(clippy::too_many_arguments)]
725    pub(crate) fn fill_cache(
726        &self,
727        rendered: Size,
728        sub_row_offset: upos_type,
729        rows: Range<upos_type>,
730        tab_width: u32,
731        text_wrap: TextWrap2,
732        ctrl_char: bool,
733        left_margin: upos_type,
734        right_margin: upos_type,
735        word_margin: upos_type,
736    ) -> Result<(), TextError> {
737        _ = self.glyphs2(
738            rendered,
739            sub_row_offset,
740            rows,
741            tab_width,
742            text_wrap,
743            ctrl_char,
744            left_margin,
745            right_margin,
746            word_margin,
747        )?;
748        Ok(())
749    }
750
751    /// Iterator for the glyphs of the lines in range.
752    /// Glyphs here a grapheme + display length.
753    #[inline]
754    #[allow(clippy::too_many_arguments)]
755    pub(crate) fn glyphs2(
756        &self,
757        rendered: Size,
758        sub_row_offset: upos_type,
759        rows: Range<upos_type>,
760        tab_width: u32,
761        text_wrap: TextWrap2,
762        ctrl_char: bool,
763        left_margin: upos_type,
764        right_margin: upos_type,
765        word_margin: upos_type,
766    ) -> Result<GlyphIter2<'_, Store::GraphemeIter<'_>>, TextError> {
767        self.cache.validate(
768            text_wrap,
769            left_margin,
770            rendered.width as upos_type,
771            rendered.height as upos_type,
772            ctrl_char,
773            self.cache_validity(),
774        );
775
776        let range = TextRange::new((sub_row_offset, rows.start), (0, rows.end));
777
778        let range_bytes;
779        let mut range_to_bytes = self.cache.range_to_bytes.borrow_mut();
780        if let Some(cache) = range_to_bytes.get(&range) {
781            range_bytes = cache.clone();
782        } else {
783            let cache = self.text.byte_range(range)?;
784            range_to_bytes.insert(range, cache.clone());
785            range_bytes = cache;
786        }
787
788        let iter = self.graphemes_byte(range_bytes.clone(), range_bytes.start)?;
789
790        let mut it = GlyphIter2::new(
791            range.start, //
792            range_bytes.start,
793            iter,
794            self.cache.clone(),
795        );
796        it.set_tabs(tab_width);
797        it.set_show_ctrl(self.glyph_ctrl);
798        it.set_wrap_ctrl(self.wrap_ctrl);
799        it.set_lf_breaks(self.text().is_multi_line());
800        it.set_text_wrap(text_wrap);
801        it.set_left_margin(left_margin);
802        it.set_right_margin(right_margin);
803        it.set_word_margin(word_margin);
804        it.prepare()?;
805        Ok(it)
806    }
807
808    /// Get the grapheme at the given position.
809    #[inline]
810    pub fn grapheme_at(&self, pos: TextPosition) -> Result<Option<Grapheme<'_>>, TextError> {
811        let range_bytes = self.bytes_at_range(TextRange::new(pos, (pos.x + 1, pos.y)))?;
812        let pos_byte = self.byte_at(pos)?.start;
813
814        let mut it = self.text.graphemes_byte(range_bytes, pos_byte)?;
815
816        Ok(it.next())
817    }
818
819    /// Get a cursor over all the text with the current position set at pos.
820    #[inline]
821    pub fn text_graphemes(&self, pos: TextPosition) -> Result<Store::GraphemeIter<'_>, TextError> {
822        let rows = self.len_lines() - 1;
823        let cols = self.line_width(rows).expect("valid_row");
824
825        let range_bytes = self.bytes_at_range(TextRange::new((0, 0), (cols, rows)))?;
826        let pos_byte = self.byte_at(pos)?.start;
827
828        self.text.graphemes_byte(range_bytes, pos_byte)
829    }
830
831    /// Get a cursor over the text-range the current position set at pos.
832    #[inline]
833    pub fn graphemes(
834        &self,
835        range: TextRange,
836        pos: TextPosition,
837    ) -> Result<Store::GraphemeIter<'_>, TextError> {
838        let range_bytes = self.bytes_at_range(range)?;
839        let pos_byte = self.byte_at(pos)?.start;
840
841        self.text.graphemes_byte(range_bytes, pos_byte)
842    }
843
844    /// Get a cursor over the text-range with the current position set at pos.
845    #[inline]
846    pub fn graphemes_byte(
847        &self,
848        range: Range<usize>,
849        pos: usize,
850    ) -> Result<Store::GraphemeIter<'_>, TextError> {
851        self.text.graphemes_byte(range, pos)
852    }
853
854    /// Line as str.
855    ///
856    /// * row must be <= len_lines
857    #[inline]
858    pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
859        self.text.line_at(row)
860    }
861
862    /// Iterate over text-lines, starting at row.
863    ///
864    /// * row must be <= len_lines
865    #[inline]
866    pub fn lines_at(
867        &self,
868        row: upos_type,
869    ) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
870        self.text.lines_at(row)
871    }
872
873    /// Get the text for a line as iterator over the graphemes.
874    #[inline]
875    pub fn line_graphemes(&self, row: upos_type) -> Result<Store::GraphemeIter<'_>, TextError> {
876        self.text.line_graphemes(row)
877    }
878
879    /// Line width as grapheme count. Excludes the terminating '\n'.
880    #[inline]
881    pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
882        self.cache.validate_byte_pos(self.cache_validity());
883
884        let mut line_width = self.cache.line_width.borrow_mut();
885        if let Some(cache) = line_width.get(&row) {
886            Ok(cache.width)
887        } else {
888            let width = self.text.line_width(row)?;
889            let byte_pos = self.text.byte_range_at(TextPosition::new(width, row))?;
890            line_width.insert(
891                row,
892                LineWidthCache {
893                    width,
894                    byte_pos: byte_pos.start,
895                },
896            );
897            Ok(width)
898        }
899    }
900
901    /// Number of lines.
902    #[inline]
903    pub fn len_lines(&self) -> upos_type {
904        self.text.len_lines()
905    }
906
907    /// Length in bytes.
908    #[inline]
909    pub fn len_bytes(&self) -> usize {
910        self.text.len_bytes()
911    }
912}
913
914impl<Store: TextStore + Default> TextCore<Store> {
915    /// Clear the internal state.
916    pub fn clear(&mut self) {
917        self.text.set_string(Default::default());
918        self.cursor.set(Default::default());
919        if let Some(sty) = &mut self.styles {
920            sty.clear();
921        }
922        if let Some(undo) = &mut self.undo {
923            undo.clear();
924
925            if undo.has_replay_log() {
926                undo.append(UndoOp::SetText {
927                    txt: self.text.string(),
928                });
929            }
930        }
931    }
932
933    /// Returns the TextStore.
934    pub fn text(&self) -> &Store {
935        &self.text
936    }
937
938    /// Set the text as a TextStore.
939    /// Clears the styles, cursor and anchor.
940    pub fn set_text(&mut self, t: Store) -> bool {
941        self.text = t;
942        if let Some(sty) = &mut self.styles {
943            sty.clear();
944        }
945        self.cache.clear();
946        self.cursor.set(Default::default());
947
948        if let Some(undo) = &mut self.undo {
949            undo.clear();
950
951            if undo.has_replay_log() {
952                undo.append(UndoOp::SetText {
953                    txt: self.text.string(),
954                });
955            }
956        }
957
958        true
959    }
960
961    /// Insert a character.
962    ///
963    /// Has no special handling for '\n' and '\t' and just adds them
964    /// as they are. '\n' *is* treated as line-break, but it might not be
965    /// the correct byte-sequence for your platform.
966    pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
967        let (inserted_range, inserted_bytes) = self.text.insert_char(pos, c)?;
968
969        let old_cursor = self.cursor.get();
970
971        if let Some(sty) = &mut self.styles {
972            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
973        }
974
975        let new_cursor = TextCursor {
976            anchor: inserted_range.expand_pos(old_cursor.cursor),
977            cursor: inserted_range.expand_pos(old_cursor.anchor),
978        };
979        self.cursor.set(new_cursor);
980
981        if let Some(undo) = self.undo.as_mut() {
982            undo.append(UndoOp::InsertChar {
983                bytes: inserted_bytes.clone(),
984                cursor: TextPositionChange {
985                    before: old_cursor.cursor,
986                    after: new_cursor.cursor,
987                },
988                anchor: TextPositionChange {
989                    before: old_cursor.anchor,
990                    after: new_cursor.anchor,
991                },
992                txt: c.to_string(),
993            });
994        }
995
996        Ok(true)
997    }
998
999    /// Insert a string at position.
1000    pub fn insert_str(&mut self, pos: TextPosition, t: &str) -> Result<bool, TextError> {
1001        let old_cursor = self.cursor.get();
1002
1003        let (inserted_range, inserted_bytes) = self.text.insert_str(pos, t)?;
1004
1005        if let Some(sty) = &mut self.styles {
1006            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1007        }
1008        let new_cursor = TextCursor {
1009            anchor: inserted_range.expand_pos(old_cursor.anchor),
1010            cursor: inserted_range.expand_pos(old_cursor.cursor),
1011        };
1012        self.cursor.set(new_cursor);
1013
1014        if let Some(undo) = self.undo.as_mut() {
1015            undo.append(UndoOp::InsertStr {
1016                bytes: inserted_bytes.clone(),
1017                cursor: TextPositionChange {
1018                    before: old_cursor.cursor,
1019                    after: new_cursor.cursor,
1020                },
1021                anchor: TextPositionChange {
1022                    before: old_cursor.anchor,
1023                    after: new_cursor.anchor,
1024                },
1025                txt: t.to_string(),
1026            });
1027        }
1028
1029        Ok(true)
1030    }
1031
1032    /// Remove a range.
1033    ///
1034    /// Put it into undo as 'char-removed'. This can merge with other 'char-removed'
1035    /// undoes if they are next to each other.
1036    pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1037        self._remove_range(range, true)
1038    }
1039
1040    /// Remove a range
1041    ///
1042    /// Put it into undo as 'str-removed'. This will not be merged with other undoes.
1043    pub fn remove_str_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1044        self._remove_range(range, false)
1045    }
1046
1047    fn _remove_range(&mut self, range: TextRange, char_range: bool) -> Result<bool, TextError> {
1048        let old_cursor = self.cursor.get();
1049
1050        if range.is_empty() {
1051            return Ok(false);
1052        }
1053
1054        let (old_text, (_removed_range, removed_bytes)) = self.text.remove(range)?;
1055
1056        // remove deleted styles.
1057        let mut changed_style = Vec::new();
1058        if let Some(sty) = &mut self.styles {
1059            sty.remap(|r, s| {
1060                let new_range = shrink_range_by(removed_bytes.clone(), r.clone());
1061                if ranges_intersect(r.clone(), removed_bytes.clone()) {
1062                    changed_style.push(StyleChange {
1063                        before: r.clone(),
1064                        after: new_range.clone(),
1065                        style: s,
1066                    });
1067                    if new_range.is_empty() {
1068                        None
1069                    } else {
1070                        Some(new_range)
1071                    }
1072                } else {
1073                    Some(new_range)
1074                }
1075            });
1076        }
1077        let new_cursor = TextCursor {
1078            anchor: range.shrink_pos(old_cursor.anchor),
1079            cursor: range.shrink_pos(old_cursor.cursor),
1080        };
1081        self.cursor.set(new_cursor);
1082
1083        if let Some(undo) = &mut self.undo {
1084            if char_range {
1085                undo.append(UndoOp::RemoveChar {
1086                    bytes: removed_bytes.clone(),
1087                    cursor: TextPositionChange {
1088                        before: old_cursor.cursor,
1089                        after: new_cursor.cursor,
1090                    },
1091                    anchor: TextPositionChange {
1092                        before: old_cursor.anchor,
1093                        after: new_cursor.anchor,
1094                    },
1095                    txt: old_text,
1096                    styles: changed_style,
1097                });
1098            } else {
1099                undo.append(UndoOp::RemoveStr {
1100                    bytes: removed_bytes.clone(),
1101                    cursor: TextPositionChange {
1102                        before: old_cursor.cursor,
1103                        after: new_cursor.cursor,
1104                    },
1105                    anchor: TextPositionChange {
1106                        before: old_cursor.anchor,
1107                        after: new_cursor.anchor,
1108                    },
1109                    txt: old_text,
1110                    styles: changed_style,
1111                });
1112            }
1113        }
1114
1115        Ok(true)
1116    }
1117}