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