rat_text/
text_core.rs

1use crate::cache::{Cache, LineWidthCache};
2use crate::clipboard::Clipboard;
3#[allow(deprecated)]
4use crate::glyph::{Glyph, GlyphIter};
5use crate::glyph2::{GlyphIter2, TextWrap2};
6use crate::grapheme::Grapheme;
7use crate::range_map::{RangeMap, expand_range_by, ranges_intersect, shrink_range_by};
8use crate::text_store::TextStore;
9use crate::undo_buffer::{StyleChange, TextPositionChange, UndoBuffer, UndoEntry, UndoOp};
10use crate::{Cursor, TextError, TextPosition, TextRange, upos_type};
11use dyn_clone::clone_box;
12use ratatui::layout::Size;
13use std::borrow::Cow;
14use std::cmp::min;
15use std::mem;
16use std::ops::Range;
17
18/// Core for text editing.
19#[derive(Debug)]
20pub struct TextCore<Store> {
21    /// Text store.
22    text: Store,
23
24    /// Cursor
25    cursor: TextPosition,
26    /// Anchor
27    anchor: TextPosition,
28
29    /// styles
30    styles: Option<Box<RangeMap>>,
31    /// undo-buffer
32    undo: Option<Box<dyn UndoBuffer>>,
33    /// clipboard
34    clip: Option<Box<dyn Clipboard>>,
35    /// cache
36    cache: Cache,
37
38    /// line-break
39    newline: String,
40    /// tab-width
41    tabs: u16,
42    /// expand tabs
43    expand_tabs: bool,
44    /// show ctrl chars in glyphs
45    glyph_ctrl: bool,
46    /// show text-wrap glyphs
47    wrap_ctrl: bool,
48    /// use line-breaks in glyphs
49    glyph_line_break: bool,
50}
51
52impl<Store: Clone> Clone for TextCore<Store> {
53    fn clone(&self) -> Self {
54        Self {
55            text: self.text.clone(),
56            cursor: self.cursor,
57            anchor: self.anchor,
58            styles: self.styles.clone(),
59            undo: self.undo.as_ref().map(|v| clone_box(v.as_ref())),
60            clip: self.clip.as_ref().map(|v| clone_box(v.as_ref())),
61            cache: Default::default(),
62            newline: self.newline.clone(),
63            tabs: self.tabs,
64            expand_tabs: self.expand_tabs,
65            glyph_ctrl: self.glyph_ctrl,
66            wrap_ctrl: self.wrap_ctrl,
67            glyph_line_break: self.glyph_line_break,
68        }
69    }
70}
71
72impl<Store: TextStore + Default> TextCore<Store> {
73    pub fn new(undo: Option<Box<dyn UndoBuffer>>, clip: Option<Box<dyn Clipboard>>) -> Self {
74        #[cfg(windows)]
75        const LINE_ENDING: &str = "\r\n";
76
77        #[cfg(not(windows))]
78        const LINE_ENDING: &str = "\n";
79
80        Self {
81            text: Store::default(),
82            cursor: Default::default(),
83            anchor: Default::default(),
84            styles: Default::default(),
85            undo,
86            clip,
87            cache: Default::default(),
88            newline: LINE_ENDING.to_string(),
89            tabs: 8,
90            expand_tabs: true,
91            glyph_ctrl: false,
92            wrap_ctrl: false,
93            glyph_line_break: true,
94        }
95    }
96
97    /// Sets the line ending to be used for insert.
98    /// There is no auto-detection or conversion done for set_value().
99    ///
100    /// Caution: If this doesn't match the line ending used in the value, you
101    /// will get a value with mixed line endings.
102    ///
103    /// Defaults to the system line-ending.
104    #[inline]
105    pub fn set_newline(&mut self, br: String) {
106        self.newline = br;
107    }
108
109    /// Line ending used for insert.
110    #[inline]
111    pub fn newline(&self) -> &str {
112        &self.newline
113    }
114
115    /// Set the tab-width.
116    /// Default is 8.
117    #[inline]
118    pub fn set_tab_width(&mut self, tabs: u16) {
119        self.tabs = tabs;
120    }
121
122    /// Tab-width
123    #[inline]
124    pub fn tab_width(&self) -> u16 {
125        self.tabs
126    }
127
128    /// Expand tabs to spaces. Only for new inputs.
129    #[inline]
130    pub fn set_expand_tabs(&mut self, expand: bool) {
131        self.expand_tabs = expand;
132    }
133
134    /// Expand tabs to spaces. Only for new inputs.
135    #[inline]
136    pub fn expand_tabs(&self) -> bool {
137        self.expand_tabs
138    }
139
140    /// Show control characters when iterating glyphs.
141    #[inline]
142    pub fn set_glyph_ctrl(&mut self, show_ctrl: bool) {
143        self.glyph_ctrl = show_ctrl;
144    }
145
146    /// Show glyphs for text-wrap.
147    pub fn glyph_ctrl(&self) -> bool {
148        self.glyph_ctrl
149    }
150
151    /// Show glyphs for text-wrap.
152    #[inline]
153    pub fn set_wrap_ctrl(&mut self, wrap_ctrl: bool) {
154        self.wrap_ctrl = wrap_ctrl;
155    }
156
157    /// Show control characters when iterating glyphs.
158    pub fn wrap_ctrl(&self) -> bool {
159        self.wrap_ctrl
160    }
161
162    /// Handle line-breaks when iterating glyphs.
163    /// If false everything is treated as one line.
164    #[inline]
165    pub fn set_glyph_line_break(&mut self, line_break: bool) {
166        self.glyph_line_break = line_break;
167    }
168
169    /// Handle line-breaks. If false everything is treated as one line.
170    pub fn glyph_line_break(&self) -> bool {
171        self.glyph_line_break
172    }
173}
174
175impl<Store: TextStore + Default> TextCore<Store> {
176    /// Clipboard
177    pub fn set_clipboard(&mut self, clip: Option<Box<dyn Clipboard + 'static>>) {
178        self.clip = clip;
179    }
180
181    /// Clipboard
182    pub fn clipboard(&self) -> Option<&dyn Clipboard> {
183        match &self.clip {
184            None => None,
185            Some(v) => Some(v.as_ref()),
186        }
187    }
188}
189
190impl<Store: TextStore + Default> TextCore<Store> {
191    /// Undo
192    #[inline]
193    pub fn set_undo_buffer(&mut self, undo: Option<Box<dyn UndoBuffer>>) {
194        self.undo = undo;
195    }
196
197    /// Set undo count
198    #[inline]
199    pub fn set_undo_count(&mut self, n: u32) {
200        if let Some(undo) = self.undo.as_mut() {
201            undo.set_undo_count(n);
202        };
203    }
204
205    /// Begin a sequence of changes that should be undone in one go.
206    #[inline]
207    pub fn begin_undo_seq(&mut self) {
208        if let Some(undo) = self.undo.as_mut() {
209            undo.begin_seq();
210        };
211    }
212
213    /// End a sequence of changes that should be undone in one go.
214    #[inline]
215    pub fn end_undo_seq(&mut self) {
216        if let Some(undo) = self.undo.as_mut() {
217            undo.end_seq();
218        };
219    }
220
221    /// Undo
222    #[inline]
223    pub fn undo_buffer(&self) -> Option<&dyn UndoBuffer> {
224        match &self.undo {
225            None => None,
226            Some(v) => Some(v.as_ref()),
227        }
228    }
229
230    /// Undo
231    #[inline]
232    pub fn undo_buffer_mut(&mut self) -> Option<&mut dyn UndoBuffer> {
233        match &mut self.undo {
234            None => None,
235            Some(v) => Some(v.as_mut()),
236        }
237    }
238
239    /// Undo last.
240    pub fn undo(&mut self) -> bool {
241        let Some(undo) = self.undo.as_mut() else {
242            return false;
243        };
244
245        undo.append(UndoOp::Undo);
246
247        self._undo()
248    }
249
250    /// Undo last.
251    fn _undo(&mut self) -> bool {
252        let Some(undo) = self.undo.as_mut() else {
253            return false;
254        };
255        let undo_op = undo.undo();
256        let changed = !undo_op.is_empty();
257        for op in undo_op {
258            match op {
259                UndoOp::InsertChar {
260                    bytes,
261                    cursor,
262                    anchor,
263                    ..
264                }
265                | UndoOp::InsertStr {
266                    bytes,
267                    cursor,
268                    anchor,
269                    ..
270                } => {
271                    self.text.remove_b(bytes.clone()).expect("valid_bytes");
272
273                    if let Some(sty) = &mut self.styles {
274                        sty.remap(|r, _| Some(shrink_range_by(bytes.clone(), r)));
275                    }
276                    self.anchor = anchor.before;
277                    self.cursor = cursor.before;
278                }
279                UndoOp::RemoveStr {
280                    bytes,
281                    cursor,
282                    anchor,
283                    txt,
284                    styles,
285                }
286                | UndoOp::RemoveChar {
287                    bytes,
288                    cursor,
289                    anchor,
290                    txt,
291                    styles,
292                } => {
293                    self.text.insert_b(bytes.start, txt).expect("valid_bytes");
294
295                    if let Some(sty) = &mut self.styles {
296                        for s in styles {
297                            sty.remove(s.after.clone(), s.style);
298                        }
299                        for s in styles {
300                            sty.add(s.before.clone(), s.style);
301                        }
302                        sty.remap(|r, _| {
303                            if ranges_intersect(bytes.clone(), r.clone()) {
304                                Some(r)
305                            } else {
306                                Some(expand_range_by(bytes.clone(), r))
307                            }
308                        });
309                    }
310                    self.anchor = anchor.before;
311                    self.cursor = cursor.before;
312                }
313                UndoOp::Cursor { cursor, anchor } => {
314                    self.anchor = anchor.before;
315                    self.cursor = cursor.before;
316                }
317                UndoOp::SetStyles { styles_before, .. } => {
318                    if let Some(sty) = &mut self.styles {
319                        sty.set(styles_before.iter().cloned());
320                    }
321                }
322                UndoOp::AddStyle { range, style } => {
323                    if let Some(sty) = &mut self.styles {
324                        sty.remove(range.clone(), *style);
325                    }
326                }
327                UndoOp::RemoveStyle { range, style } => {
328                    if let Some(sty) = &mut self.styles {
329                        sty.add(range.clone(), *style);
330                    }
331                }
332                UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
333                    unreachable!()
334                }
335            }
336        }
337        changed
338    }
339
340    /// Redo last.
341    pub fn redo(&mut self) -> bool {
342        let Some(undo) = self.undo.as_mut() else {
343            return false;
344        };
345
346        undo.append(UndoOp::Redo);
347
348        self._redo()
349    }
350
351    fn _redo(&mut self) -> bool {
352        let Some(undo) = self.undo.as_mut() else {
353            return false;
354        };
355        let redo_op = undo.redo();
356        let changed = !redo_op.is_empty();
357        for op in redo_op {
358            match op {
359                UndoOp::InsertChar {
360                    bytes,
361                    cursor,
362                    anchor,
363                    txt,
364                }
365                | UndoOp::InsertStr {
366                    bytes,
367                    cursor,
368                    anchor,
369                    txt,
370                } => {
371                    self.text.insert_b(bytes.start, txt).expect("valid_bytes");
372                    if let Some(sty) = &mut self.styles {
373                        sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
374                    }
375                    self.anchor = anchor.after;
376                    self.cursor = cursor.after;
377                }
378                UndoOp::RemoveChar {
379                    bytes,
380                    cursor,
381                    anchor,
382                    styles,
383                    ..
384                }
385                | UndoOp::RemoveStr {
386                    bytes,
387                    cursor,
388                    anchor,
389                    styles,
390                    ..
391                } => {
392                    self.text.remove_b(bytes.clone()).expect("valid_bytes");
393
394                    if let Some(sty) = &mut self.styles {
395                        sty.remap(|r, _| {
396                            if ranges_intersect(bytes.clone(), r.clone()) {
397                                Some(r)
398                            } else {
399                                Some(shrink_range_by(bytes.clone(), r))
400                            }
401                        });
402                        for s in styles {
403                            sty.remove(s.before.clone(), s.style);
404                        }
405                        for s in styles {
406                            sty.add(s.after.clone(), s.style);
407                        }
408                    }
409
410                    self.anchor = anchor.after;
411                    self.cursor = cursor.after;
412                }
413                UndoOp::Cursor { cursor, anchor } => {
414                    self.anchor = anchor.after;
415                    self.cursor = cursor.after;
416                }
417
418                UndoOp::SetStyles { styles_after, .. } => {
419                    if let Some(sty) = &mut self.styles {
420                        sty.set(styles_after.iter().cloned());
421                    }
422                }
423                UndoOp::AddStyle { range, style } => {
424                    if let Some(sty) = &mut self.styles {
425                        sty.add(range.clone(), *style);
426                    }
427                }
428                UndoOp::RemoveStyle { range, style } => {
429                    if let Some(sty) = &mut self.styles {
430                        sty.remove(range.clone(), *style);
431                    }
432                }
433                UndoOp::SetText { .. } | UndoOp::Undo | UndoOp::Redo => {
434                    unreachable!()
435                }
436            }
437        }
438        changed
439    }
440
441    /// Get last replay recording.
442    pub fn recent_replay_log(&mut self) -> Vec<UndoEntry> {
443        if let Some(undo) = &mut self.undo {
444            undo.recent_replay_log()
445        } else {
446            Vec::default()
447        }
448    }
449
450    /// Replay a recording of changes.
451    pub fn replay_log(&mut self, replay: &[UndoEntry]) {
452        for replay_entry in replay {
453            match &replay_entry.operation {
454                UndoOp::SetText { txt } => {
455                    self.text.set_string(txt);
456                    if let Some(sty) = &mut self.styles {
457                        sty.clear();
458                    }
459                    if let Some(undo) = self.undo.as_mut() {
460                        undo.clear();
461                    };
462                }
463                UndoOp::InsertChar { bytes, txt, .. } | UndoOp::InsertStr { bytes, txt, .. } => {
464                    self.text.insert_b(bytes.start, txt).expect("valid_range");
465                    if let Some(sty) = &mut self.styles {
466                        sty.remap(|r, _| Some(expand_range_by(bytes.clone(), r)));
467                    }
468                }
469                UndoOp::RemoveChar { bytes, styles, .. }
470                | UndoOp::RemoveStr { bytes, styles, .. } => {
471                    self.text.remove_b(bytes.clone()).expect("valid_range");
472                    if let Some(sty) = &mut self.styles {
473                        sty.remap(|r, _| {
474                            if ranges_intersect(bytes.clone(), r.clone()) {
475                                Some(r)
476                            } else {
477                                Some(shrink_range_by(bytes.clone(), r))
478                            }
479                        });
480                        for s in styles {
481                            sty.remove(s.before.clone(), s.style);
482                        }
483                        for s in styles {
484                            sty.add(s.after.clone(), s.style);
485                        }
486                    }
487                }
488                UndoOp::Cursor { .. } => {
489                    // don't do cursor
490                }
491
492                UndoOp::SetStyles { styles_after, .. } => {
493                    self.init_styles();
494                    if let Some(sty) = &mut self.styles {
495                        sty.set(styles_after.iter().cloned());
496                    }
497                }
498                UndoOp::AddStyle { range, style } => {
499                    self.init_styles();
500                    if let Some(sty) = &mut self.styles {
501                        sty.add(range.clone(), *style);
502                    }
503                }
504                UndoOp::RemoveStyle { range, style } => {
505                    self.init_styles();
506                    if let Some(sty) = &mut self.styles {
507                        sty.remove(range.clone(), *style);
508                    }
509                }
510                UndoOp::Undo => {
511                    self._undo();
512                }
513                UndoOp::Redo => {
514                    self._redo();
515                }
516            }
517
518            if let Some(undo) = self.undo.as_mut() {
519                undo.append_from_replay(replay_entry.clone());
520            };
521        }
522    }
523}
524
525impl<Store: TextStore + Default> TextCore<Store> {
526    fn init_styles(&mut self) {
527        if self.styles.is_none() {
528            self.styles = Some(Box::new(RangeMap::default()));
529        }
530    }
531
532    /// Set all styles.
533    ///
534    /// The ranges are TextRanges. The usize value is the index of the
535    /// actual style. Those are set with the widget.
536    #[inline]
537    pub fn set_range_styles(
538        &mut self,
539        new_styles: Vec<(TextRange, usize)>,
540    ) -> Result<(), TextError> {
541        let mut mapped = Vec::with_capacity(new_styles.len());
542        for (r, s) in new_styles {
543            let rr = self.bytes_at_range(r)?;
544            mapped.push((rr, s));
545        }
546        self.set_styles(mapped);
547        Ok(())
548    }
549
550    /// Set all styles.
551    ///
552    /// The ranges are byte-ranges. The usize value is the index of the
553    /// actual style. Those are set with the widget.
554    #[inline]
555    pub fn set_styles(&mut self, new_styles: Vec<(Range<usize>, usize)>) {
556        self.init_styles();
557
558        let Some(sty) = &mut self.styles else {
559            return;
560        };
561        if let Some(undo) = &mut self.undo {
562            if undo.undo_styles_enabled() || undo.has_replay_log() {
563                undo.append(UndoOp::SetStyles {
564                    styles_before: sty.values().collect::<Vec<_>>(),
565                    styles_after: new_styles.clone(),
566                });
567            }
568        }
569        sty.set(new_styles.into_iter());
570    }
571
572    /// Add a style for the given byte-range.
573    ///
574    /// The usize value is the index of the actual style.
575    /// Those are set at the widget.
576    #[inline]
577    pub fn add_style(&mut self, range: Range<usize>, style: usize) {
578        self.init_styles();
579
580        if let Some(sty) = &mut self.styles {
581            sty.add(range.clone(), style);
582        }
583        if let Some(undo) = &mut self.undo {
584            if undo.undo_styles_enabled() || undo.has_replay_log() {
585                undo.append(UndoOp::AddStyle { range, style });
586            }
587        }
588    }
589
590    /// Remove a style for the given byte-range.
591    ///
592    /// Range and style must match to be removed.
593    #[inline]
594    pub fn remove_style(&mut self, range: Range<usize>, style: usize) {
595        if let Some(sty) = &mut self.styles {
596            sty.remove(range.clone(), style);
597        }
598        if let Some(undo) = &mut self.undo {
599            if undo.undo_styles_enabled() || undo.has_replay_log() {
600                undo.append(UndoOp::RemoveStyle { range, style });
601            }
602        }
603    }
604
605    /// Find all values for the given position and writes them
606    /// to the output buffer. Clears the output buffer first.
607    ///
608    /// This creates a cache for the styles in the given range.
609    #[inline]
610    pub(crate) fn styles_at_page(&self, pos: usize, range: Range<usize>, buf: &mut Vec<usize>) {
611        if let Some(sty) = &self.styles {
612            sty.values_at_page(pos, range, buf);
613        }
614    }
615
616    /// Find all styles that touch the given range.
617    #[inline]
618    pub fn styles_in(&self, range: Range<usize>, buf: &mut Vec<(Range<usize>, usize)>) {
619        if let Some(sty) = &self.styles {
620            sty.values_in(range, buf);
621        }
622    }
623
624    /// Finds all styles for the given position.
625    #[inline]
626    pub fn styles_at(&self, byte_pos: usize, buf: &mut Vec<(Range<usize>, usize)>) {
627        if let Some(sty) = &self.styles {
628            sty.values_at(byte_pos, buf);
629        }
630    }
631
632    /// Check if the given style applies at the position and
633    /// return the complete range for the style.
634    #[inline]
635    pub fn style_match(&self, byte_pos: usize, style: usize) -> Option<Range<usize>> {
636        if let Some(sty) = &self.styles {
637            sty.value_match(byte_pos, style)
638        } else {
639            None
640        }
641    }
642
643    /// List of all styles.
644    #[inline]
645    pub fn styles(&self) -> Option<impl Iterator<Item = (Range<usize>, usize)> + '_> {
646        self.styles.as_ref().map(|v| v.values())
647    }
648}
649
650impl<Store: TextStore + Default> TextCore<Store> {
651    /// Set the cursor position.
652    /// The value is capped to the number of text lines and
653    /// the line-width for the given line.
654    ///
655    /// Returns true, if the cursor actually changed.
656    pub fn set_cursor(&mut self, mut cursor: TextPosition, extend_selection: bool) -> bool {
657        let old_cursor = self.cursor;
658        let old_anchor = self.anchor;
659
660        cursor.y = min(cursor.y, self.len_lines());
661        cursor.x = min(cursor.x, self.line_width(cursor.y).expect("valid-line"));
662
663        self.cursor = cursor;
664        if !extend_selection {
665            self.anchor = cursor;
666        }
667
668        if let Some(undo) = self.undo.as_mut() {
669            undo.append(UndoOp::Cursor {
670                cursor: TextPositionChange {
671                    before: old_cursor,
672                    after: self.cursor,
673                },
674                anchor: TextPositionChange {
675                    before: old_anchor,
676                    after: self.anchor,
677                },
678            });
679        }
680
681        old_cursor != self.cursor || old_anchor != self.anchor
682    }
683
684    /// Cursor position as grapheme-idx.
685    #[inline]
686    pub fn cursor(&self) -> TextPosition {
687        self.cursor
688    }
689
690    /// Selection anchor
691    #[inline]
692    pub fn anchor(&self) -> TextPosition {
693        self.anchor
694    }
695
696    /// Any text selection.
697    #[inline]
698    pub fn has_selection(&self) -> bool {
699        self.anchor != self.cursor
700    }
701
702    /// Select text.
703    /// Anchor and cursor are capped to a valid value.
704    #[inline]
705    pub fn set_selection(&mut self, anchor: TextPosition, cursor: TextPosition) -> bool {
706        let old_selection = self.selection();
707
708        self.set_cursor(anchor, false);
709        self.set_cursor(cursor, true);
710
711        old_selection != self.selection()
712    }
713
714    /// Select all text.
715    #[inline]
716    pub fn select_all(&mut self) -> bool {
717        let old_selection = self.selection();
718
719        self.set_cursor(TextPosition::new(0, self.len_lines()), false);
720        self.set_cursor(TextPosition::new(0, 0), true);
721
722        old_selection != self.selection()
723    }
724
725    /// Returns the selection as TextRange.
726    #[inline]
727    pub fn selection(&self) -> TextRange {
728        #[allow(clippy::comparison_chain)]
729        if self.cursor.y < self.anchor.y {
730            TextRange {
731                start: self.cursor,
732                end: self.anchor,
733            }
734        } else if self.cursor.y > self.anchor.y {
735            TextRange {
736                start: self.anchor,
737                end: self.cursor,
738            }
739        } else {
740            if self.cursor.x < self.anchor.x {
741                TextRange {
742                    start: self.cursor,
743                    end: self.anchor,
744                }
745            } else {
746                TextRange {
747                    start: self.anchor,
748                    end: self.cursor,
749                }
750            }
751        }
752    }
753}
754
755impl<Store: TextStore + Default> TextCore<Store> {
756    /// Minimum byte position that has been changed
757    /// since the last call of min_changed().
758    ///
759    /// Can be used to invalidate caches.
760    pub(crate) fn cache_validity(&self) -> Option<usize> {
761        self.text.cache_validity()
762    }
763}
764
765impl<Store: TextStore + Default> TextCore<Store> {
766    /// Empty.
767    #[inline]
768    pub fn is_empty(&self) -> bool {
769        self.len_lines() == 1 && self.line_width(0).expect("line") == 0
770    }
771
772    /// Grapheme position to byte position.
773    /// This is the (start,end) position of the single grapheme after pos.
774    #[inline]
775    pub fn byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
776        self.text.byte_range_at(pos)
777    }
778
779    /// Grapheme range to byte range.
780    #[inline]
781    pub fn bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
782        self.text.byte_range(range)
783    }
784
785    /// Byte position to grapheme position.
786    /// Returns the position that contains the given byte index.
787    #[inline]
788    pub fn byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
789        self.text.byte_to_pos(byte)
790    }
791
792    /// Byte range to grapheme range.
793    #[inline]
794    pub fn byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
795        self.text.bytes_to_range(bytes)
796    }
797
798    /// A range of the text as `Cow<str>`
799    #[inline]
800    pub fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
801        self.text.str_slice(range)
802    }
803
804    /// A range of the text as `Cow<str>`
805    #[inline]
806    pub fn str_slice_byte(&self, range: Range<usize>) -> Result<Cow<'_, str>, TextError> {
807        self.text.str_slice_byte(range)
808    }
809
810    /// Iterator for the glyphs of the lines in range.
811    /// Glyphs here a grapheme + display length.
812    #[inline]
813    #[deprecated(since = "1.1.0", note = "discontinued api")]
814    #[allow(deprecated)]
815    pub fn glyphs(
816        &self,
817        rows: Range<upos_type>,
818        screen_offset: u16,
819        screen_width: u16,
820    ) -> Result<impl Iterator<Item = Glyph<'_>>, TextError> {
821        let iter = self.graphemes(
822            TextRange::new((0, rows.start), (0, rows.end)),
823            TextPosition::new(0, rows.start),
824        )?;
825
826        let mut it = GlyphIter::new(TextPosition::new(0, rows.start), iter);
827        it.set_screen_offset(screen_offset);
828        it.set_screen_width(screen_width);
829        it.set_tabs(self.tabs);
830        it.set_show_ctrl(self.glyph_ctrl);
831        it.set_line_break(self.glyph_line_break);
832        Ok(it)
833    }
834
835    /// Limited access to the cache.
836    /// Gives only access to Debug.
837    #[inline]
838    pub fn cache(&self) -> &Cache {
839        &self.cache
840    }
841
842    /// Fill the cache for all the given rows completely.
843    #[allow(clippy::too_many_arguments)]
844    pub(crate) fn fill_cache(
845        &self,
846        rendered: Size,
847        sub_row_offset: upos_type,
848        rows: Range<upos_type>,
849        text_wrap: TextWrap2,
850        ctrl_char: bool,
851        left_margin: upos_type,
852        right_margin: upos_type,
853        word_margin: upos_type,
854    ) -> Result<(), TextError> {
855        _ = self.glyphs2(
856            rendered,
857            sub_row_offset,
858            rows,
859            text_wrap,
860            ctrl_char,
861            left_margin,
862            right_margin,
863            word_margin,
864        )?;
865        Ok(())
866    }
867
868    /// Iterator for the glyphs of the lines in range.
869    /// Glyphs here a grapheme + display length.
870    #[inline]
871    #[allow(clippy::too_many_arguments)]
872    pub(crate) fn glyphs2(
873        &self,
874        rendered: Size,
875        sub_row_offset: upos_type,
876        rows: Range<upos_type>,
877        text_wrap: TextWrap2,
878        ctrl_char: bool,
879        left_margin: upos_type,
880        right_margin: upos_type,
881        word_margin: upos_type,
882    ) -> Result<GlyphIter2<'_, Store::GraphemeIter<'_>>, TextError> {
883        self.cache.validate(
884            text_wrap,
885            left_margin,
886            rendered.width as upos_type,
887            rendered.height as upos_type,
888            ctrl_char,
889            self.cache_validity(),
890        );
891
892        let range = TextRange::new((sub_row_offset, rows.start), (0, rows.end));
893
894        let range_bytes;
895        let mut range_to_bytes = self.cache.range_to_bytes.borrow_mut();
896        if let Some(cache) = range_to_bytes.get(&range) {
897            range_bytes = cache.clone();
898        } else {
899            let cache = self.text.byte_range(range)?;
900            range_to_bytes.insert(range, cache.clone());
901            range_bytes = cache;
902        }
903
904        let iter = self.graphemes_byte(range_bytes.clone(), range_bytes.start)?;
905
906        let mut it = GlyphIter2::new(
907            range.start, //
908            range_bytes.start,
909            iter,
910            self.cache.clone(),
911        );
912        it.set_tabs(self.tabs as upos_type);
913        it.set_show_ctrl(self.glyph_ctrl);
914        it.set_wrap_ctrl(self.wrap_ctrl);
915        it.set_lf_breaks(self.glyph_line_break);
916        it.set_text_wrap(text_wrap);
917        it.set_left_margin(left_margin);
918        it.set_right_margin(right_margin);
919        it.set_word_margin(word_margin);
920        it.prepare()?;
921        Ok(it)
922    }
923
924    /// Get the grapheme at the given position.
925    #[inline]
926    pub fn grapheme_at(&self, pos: TextPosition) -> Result<Option<Grapheme<'_>>, TextError> {
927        let range_bytes = self.bytes_at_range(TextRange::new(pos, (pos.x + 1, pos.y)))?;
928        let pos_byte = self.byte_at(pos)?.start;
929
930        let mut it = self.text.graphemes_byte(range_bytes, pos_byte)?;
931
932        Ok(it.next())
933    }
934
935    /// Get a cursor over all the text with the current position set at pos.
936    #[inline]
937    pub fn text_graphemes(
938        &self,
939        pos: TextPosition,
940    ) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
941        let rows = self.len_lines() - 1;
942        let cols = self.line_width(rows).expect("valid_row");
943
944        let range_bytes = self.bytes_at_range(TextRange::new((0, 0), (cols, rows)))?;
945        let pos_byte = self.byte_at(pos)?.start;
946
947        self.text.graphemes_byte(range_bytes, pos_byte)
948    }
949
950    /// Get a cursor over the text-range the current position set at pos.
951    #[inline]
952    pub fn graphemes(
953        &self,
954        range: TextRange,
955        pos: TextPosition,
956    ) -> Result<Store::GraphemeIter<'_>, TextError> {
957        let range_bytes = self.bytes_at_range(range)?;
958        let pos_byte = self.byte_at(pos)?.start;
959
960        self.text.graphemes_byte(range_bytes, pos_byte)
961    }
962
963    /// Get a cursor over the text-range with the current position set at pos.
964    #[inline]
965    pub fn graphemes_byte(
966        &self,
967        range: Range<usize>,
968        pos: usize,
969    ) -> Result<Store::GraphemeIter<'_>, TextError> {
970        self.text.graphemes_byte(range, pos)
971    }
972
973    /// Line as str.
974    ///
975    /// * row must be <= len_lines
976    #[inline]
977    pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
978        self.text.line_at(row)
979    }
980
981    /// Iterate over text-lines, starting at row.
982    ///
983    /// * row must be <= len_lines
984    #[inline]
985    pub fn lines_at(
986        &self,
987        row: upos_type,
988    ) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
989        self.text.lines_at(row)
990    }
991
992    /// Get the text for a line as iterator over the graphemes.
993    #[inline]
994    pub fn line_graphemes(&self, row: upos_type) -> Result<Store::GraphemeIter<'_>, TextError> {
995        self.text.line_graphemes(row)
996    }
997
998    /// Line width as grapheme count. Excludes the terminating '\n'.
999    #[inline]
1000    pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
1001        self.cache.validate_byte_pos(self.cache_validity());
1002
1003        let mut line_width = self.cache.line_width.borrow_mut();
1004        if let Some(cache) = line_width.get(&row) {
1005            Ok(cache.width)
1006        } else {
1007            let width = self.text.line_width(row)?;
1008            let byte_pos = self.text.byte_range_at(TextPosition::new(width, row))?;
1009            line_width.insert(
1010                row,
1011                LineWidthCache {
1012                    width,
1013                    byte_pos: byte_pos.start,
1014                },
1015            );
1016            Ok(width)
1017        }
1018    }
1019
1020    /// Number of lines.
1021    #[inline]
1022    pub fn len_lines(&self) -> upos_type {
1023        self.text.len_lines()
1024    }
1025}
1026
1027impl<Store: TextStore + Default> TextCore<Store> {
1028    /// Clear the internal state.
1029    pub fn clear(&mut self) {
1030        self.text.set_string("");
1031        self.cursor = TextPosition::default();
1032        self.anchor = TextPosition::default();
1033        if let Some(sty) = &mut self.styles {
1034            sty.clear();
1035        }
1036        if let Some(undo) = &mut self.undo {
1037            undo.clear();
1038
1039            if undo.has_replay_log() {
1040                undo.append(UndoOp::SetText {
1041                    txt: self.text.string(),
1042                });
1043            }
1044        }
1045    }
1046
1047    /// Copy of the text-value.
1048    pub fn text(&self) -> &Store {
1049        &self.text
1050    }
1051
1052    /// Set the text as a TextStore
1053    /// Clears the styles.
1054    /// Caps cursor and anchor.
1055    pub fn set_text(&mut self, t: Store) -> bool {
1056        self.text = t;
1057        if let Some(sty) = &mut self.styles {
1058            sty.clear();
1059        }
1060        self.cache.clear();
1061
1062        self.cursor.y = 0;
1063        self.cursor.x = 0;
1064        self.anchor.y = 0;
1065        self.anchor.x = 0;
1066
1067        if let Some(undo) = &mut self.undo {
1068            undo.clear();
1069
1070            if undo.has_replay_log() {
1071                undo.append(UndoOp::SetText {
1072                    txt: self.text.string(),
1073                });
1074            }
1075        }
1076
1077        true
1078    }
1079
1080    /// Auto-quote the selected text.
1081    #[allow(clippy::needless_bool)]
1082    pub fn insert_quotes(&mut self, mut sel: TextRange, c: char) -> Result<bool, TextError> {
1083        self.begin_undo_seq();
1084
1085        // remove matching quotes/brackets
1086        if sel.end.x > 0 {
1087            let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
1088            let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
1089            let c0 = self.str_slice(first).expect("valid_slice");
1090            let c1 = self.str_slice(last).expect("valid_slice");
1091            let remove_quote = if c == '\'' || c == '`' || c == '"' {
1092                if c0 == "'" && c1 == "'" {
1093                    true
1094                } else if c0 == "\"" && c1 == "\"" {
1095                    true
1096                } else if c0 == "`" && c1 == "`" {
1097                    true
1098                } else {
1099                    false
1100                }
1101            } else {
1102                if c0 == "<" && c1 == ">" {
1103                    true
1104                } else if c0 == "(" && c1 == ")" {
1105                    true
1106                } else if c0 == "[" && c1 == "]" {
1107                    true
1108                } else if c0 == "{" && c1 == "}" {
1109                    true
1110                } else {
1111                    false
1112                }
1113            };
1114            if remove_quote {
1115                self.remove_char_range(last)?;
1116                self.remove_char_range(first)?;
1117                if sel.start.y == sel.end.y {
1118                    sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
1119                } else {
1120                    sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
1121                }
1122            }
1123        }
1124
1125        let cc = match c {
1126            '\'' => '\'',
1127            '`' => '`',
1128            '"' => '"',
1129            '<' => '>',
1130            '(' => ')',
1131            '[' => ']',
1132            '{' => '}',
1133            _ => unreachable!("invalid quotes"),
1134        };
1135        self.insert_char(sel.end, cc)?;
1136        self.insert_char(sel.start, c)?;
1137        if sel.start.y == sel.end.y {
1138            sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
1139        } else {
1140            sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
1141        }
1142        self.set_selection(sel.start, sel.end);
1143        self.end_undo_seq();
1144        Ok(true)
1145    }
1146
1147    /// Insert a tab, either expanded or literally.
1148    pub fn insert_tab(&mut self, mut pos: TextPosition) -> Result<bool, TextError> {
1149        if self.expand_tabs {
1150            let n = self.tabs as upos_type - (pos.x % self.tabs as upos_type);
1151            for _ in 0..n {
1152                self.insert_char(pos, ' ')?;
1153                pos.x += 1;
1154            }
1155        } else {
1156            self.insert_char(pos, '\t')?;
1157        }
1158        Ok(true)
1159    }
1160
1161    /// Insert a line break.
1162    pub fn insert_newline(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1163        if self.text.is_multi_line() {
1164            let newline = mem::take(&mut self.newline);
1165            let r = self.insert_str(pos, &newline);
1166            self.newline = newline;
1167            r?;
1168            Ok(true)
1169        } else {
1170            Ok(false)
1171        }
1172    }
1173
1174    /// Insert a character.
1175    pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
1176        // if the very last line doesn't end with a newline,
1177        // if the insert-position is at (0,len_lines).
1178        if self.text.should_insert_newline(pos) {
1179            let save_anchor = self.anchor;
1180            let save_cursor = self.cursor;
1181            self.insert_newline(pos)?;
1182            self.anchor = save_anchor;
1183            self.cursor = save_cursor;
1184        }
1185
1186        let (inserted_range, inserted_bytes) = self.text.insert_char(pos, c)?;
1187
1188        let old_cursor = self.cursor;
1189        let old_anchor = self.anchor;
1190
1191        if let Some(sty) = &mut self.styles {
1192            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1193        }
1194        self.cursor = inserted_range.expand_pos(self.cursor);
1195        self.anchor = inserted_range.expand_pos(self.anchor);
1196
1197        if let Some(undo) = self.undo.as_mut() {
1198            undo.append(UndoOp::InsertChar {
1199                bytes: inserted_bytes.clone(),
1200                cursor: TextPositionChange {
1201                    before: old_cursor,
1202                    after: self.cursor,
1203                },
1204                anchor: TextPositionChange {
1205                    before: old_anchor,
1206                    after: self.anchor,
1207                },
1208                txt: c.to_string(),
1209            });
1210        }
1211
1212        Ok(true)
1213    }
1214
1215    /// Insert a string at position.
1216    pub fn insert_str(&mut self, pos: TextPosition, t: &str) -> Result<bool, TextError> {
1217        let old_cursor = self.cursor;
1218        let old_anchor = self.anchor;
1219
1220        let (inserted_range, inserted_bytes) = self.text.insert_str(pos, t)?;
1221
1222        if let Some(sty) = &mut self.styles {
1223            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1224        }
1225        self.anchor = inserted_range.expand_pos(self.anchor);
1226        self.cursor = inserted_range.expand_pos(self.cursor);
1227
1228        if let Some(undo) = self.undo.as_mut() {
1229            undo.append(UndoOp::InsertStr {
1230                bytes: inserted_bytes.clone(),
1231                cursor: TextPositionChange {
1232                    before: old_cursor,
1233                    after: self.cursor,
1234                },
1235                anchor: TextPositionChange {
1236                    before: old_anchor,
1237                    after: self.anchor,
1238                },
1239                txt: t.to_string(),
1240            });
1241        }
1242
1243        Ok(true)
1244    }
1245
1246    /// Remove the previous character
1247    pub fn remove_prev_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1248        let (sx, sy) = if pos.y == 0 && pos.x == 0 {
1249            (0, 0)
1250        } else if pos.y > 0 && pos.x == 0 {
1251            let prev_line_width = self.line_width(pos.y - 1).expect("line_width"); // TODO
1252            (prev_line_width, pos.y - 1)
1253        } else {
1254            (pos.x - 1, pos.y)
1255        };
1256        let range = TextRange::new((sx, sy), (pos.x, pos.y));
1257
1258        self.remove_char_range(range)
1259    }
1260
1261    /// Remove the next characters.
1262    pub fn remove_next_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1263        let c_line_width = self.line_width(pos.y)?;
1264        let c_last_line = self.len_lines() - 1;
1265
1266        let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
1267            (pos.x, pos.y)
1268        } else if pos.y != c_last_line && pos.x == c_line_width {
1269            (0, pos.y + 1)
1270        } else {
1271            (pos.x + 1, pos.y)
1272        };
1273        let range = TextRange::new((pos.x, pos.y), (ex, ey));
1274
1275        self.remove_char_range(range)
1276    }
1277
1278    /// Remove a range.
1279    /// Put it into undo as 'char-removed'.
1280    pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1281        self._remove_range(range, true)
1282    }
1283
1284    /// Remove a range
1285    /// Put it into undo as 'str-removed'.
1286    pub fn remove_str_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1287        self._remove_range(range, false)
1288    }
1289
1290    fn _remove_range(&mut self, range: TextRange, char_range: bool) -> Result<bool, TextError> {
1291        let old_cursor = self.cursor;
1292        let old_anchor = self.anchor;
1293
1294        if range.is_empty() {
1295            return Ok(false);
1296        }
1297
1298        let (old_text, (_removed_range, removed_bytes)) = self.text.remove(range)?;
1299
1300        // remove deleted styles.
1301        let mut changed_style = Vec::new();
1302        if let Some(sty) = &mut self.styles {
1303            sty.remap(|r, s| {
1304                let new_range = shrink_range_by(removed_bytes.clone(), r.clone());
1305                if ranges_intersect(r.clone(), removed_bytes.clone()) {
1306                    changed_style.push(StyleChange {
1307                        before: r.clone(),
1308                        after: new_range.clone(),
1309                        style: s,
1310                    });
1311                    if new_range.is_empty() {
1312                        None
1313                    } else {
1314                        Some(new_range)
1315                    }
1316                } else {
1317                    Some(new_range)
1318                }
1319            });
1320        }
1321        self.anchor = range.shrink_pos(self.anchor);
1322        self.cursor = range.shrink_pos(self.cursor);
1323
1324        if let Some(undo) = &mut self.undo {
1325            if char_range {
1326                undo.append(UndoOp::RemoveChar {
1327                    bytes: removed_bytes.clone(),
1328                    cursor: TextPositionChange {
1329                        before: old_cursor,
1330                        after: self.cursor,
1331                    },
1332                    anchor: TextPositionChange {
1333                        before: old_anchor,
1334                        after: self.anchor,
1335                    },
1336                    txt: old_text,
1337                    styles: changed_style,
1338                });
1339            } else {
1340                undo.append(UndoOp::RemoveStr {
1341                    bytes: removed_bytes.clone(),
1342                    cursor: TextPositionChange {
1343                        before: old_cursor,
1344                        after: self.cursor,
1345                    },
1346                    anchor: TextPositionChange {
1347                        before: old_anchor,
1348                        after: self.anchor,
1349                    },
1350                    txt: old_text,
1351                    styles: changed_style,
1352                });
1353            }
1354        }
1355
1356        Ok(true)
1357    }
1358}
1359
1360impl<Store: TextStore + Default> TextCore<Store> {
1361    /// Find the start of the next word. If the position is at the start
1362    /// or inside a word, the same position is returned.
1363    pub fn next_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1364        let mut it = self.text_graphemes(pos)?;
1365        let mut last_pos = it.text_offset();
1366        loop {
1367            let Some(c) = it.next() else {
1368                break;
1369            };
1370            last_pos = c.text_bytes().start;
1371            if !c.is_whitespace() {
1372                break;
1373            }
1374        }
1375
1376        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1377    }
1378
1379    /// Find the end of the next word. Skips whitespace first, then goes on
1380    /// until it finds the next whitespace.
1381    pub fn next_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1382        let mut it = self.text_graphemes(pos)?;
1383        let mut last_pos = it.text_offset();
1384        let mut init = true;
1385        loop {
1386            let Some(c) = it.next() else {
1387                break;
1388            };
1389            last_pos = c.text_bytes().start;
1390            if init {
1391                if !c.is_whitespace() {
1392                    init = false;
1393                }
1394            } else {
1395                if c.is_whitespace() {
1396                    break;
1397                }
1398            }
1399            last_pos = c.text_bytes().end;
1400        }
1401
1402        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1403    }
1404
1405    /// Find the start of the prev word. Skips whitespace first, then goes on
1406    /// until it finds the next whitespace.
1407    ///
1408    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1409    /// both return start<=end!
1410    pub fn prev_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1411        let mut it = self.text_graphemes(pos)?;
1412        let mut last_pos = it.text_offset();
1413        let mut init = true;
1414        loop {
1415            let Some(c) = it.prev() else {
1416                break;
1417            };
1418            if init {
1419                if !c.is_whitespace() {
1420                    init = false;
1421                }
1422            } else {
1423                if c.is_whitespace() {
1424                    break;
1425                }
1426            }
1427            last_pos = c.text_bytes().start;
1428        }
1429
1430        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1431    }
1432
1433    /// Find the end of the previous word. Word is everything that is not whitespace.
1434    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1435    /// both return start<=end!
1436    pub fn prev_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1437        let mut it = self.text_graphemes(pos)?;
1438        let mut last_pos = it.text_offset();
1439        loop {
1440            let Some(c) = it.prev() else {
1441                break;
1442            };
1443            if !c.is_whitespace() {
1444                break;
1445            }
1446            last_pos = c.text_bytes().start;
1447        }
1448
1449        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1450    }
1451
1452    /// Is the position at a word boundary?
1453    pub fn is_word_boundary(&self, pos: TextPosition) -> Result<bool, TextError> {
1454        let mut it = self.text_graphemes(pos)?;
1455        if let Some(c0) = it.prev() {
1456            it.next();
1457            if let Some(c1) = it.next() {
1458                Ok(c0.is_whitespace() && !c1.is_whitespace()
1459                    || !c0.is_whitespace() && c1.is_whitespace())
1460            } else {
1461                Ok(false)
1462            }
1463        } else {
1464            Ok(false)
1465        }
1466    }
1467
1468    /// Find the start of the word at pos.
1469    /// Returns pos if the position is not inside a word.
1470    pub fn word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1471        let mut it = self.text_graphemes(pos)?;
1472        let mut last_pos = it.text_offset();
1473        loop {
1474            let Some(c) = it.prev() else {
1475                break;
1476            };
1477            if c.is_whitespace() {
1478                break;
1479            }
1480            last_pos = c.text_bytes().start;
1481        }
1482
1483        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1484    }
1485
1486    /// Find the end of the word at pos.
1487    /// Returns pos if the position is not inside a word.
1488    pub fn word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1489        let mut it = self.text_graphemes(pos)?;
1490        let mut last_pos = it.text_offset();
1491        loop {
1492            let Some(c) = it.next() else {
1493                break;
1494            };
1495            last_pos = c.text_bytes().start;
1496            if c.is_whitespace() {
1497                break;
1498            }
1499            last_pos = c.text_bytes().end;
1500        }
1501
1502        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1503    }
1504}