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        let pos = TextPosition::new(sub_row_offset, rows.start);
894
895        let range_bytes;
896        let pos_byte;
897
898        let mut range_to_bytes = self.cache.range_to_bytes.borrow_mut();
899        if let Some(cache) = range_to_bytes.get(&range) {
900            range_bytes = cache.clone();
901        } else {
902            let cache = self.text.byte_range(range)?;
903            range_to_bytes.insert(range, cache.clone());
904            range_bytes = cache;
905        }
906
907        let mut pos_to_bytes = self.cache.pos_to_bytes.borrow_mut();
908        if let Some(cache) = pos_to_bytes.get(&pos) {
909            pos_byte = cache.start;
910        } else {
911            let cache = self.text.byte_range_at(pos)?;
912            pos_to_bytes.insert(pos, cache.clone());
913            pos_byte = cache.start;
914        }
915
916        let iter = self.graphemes_byte(range_bytes, pos_byte)?;
917
918        let mut it = GlyphIter2::new(
919            TextPosition::new(sub_row_offset, rows.start),
920            iter,
921            self.cache.clone(),
922        );
923        it.set_tabs(self.tabs as upos_type);
924        it.set_show_ctrl(self.glyph_ctrl);
925        it.set_wrap_ctrl(self.wrap_ctrl);
926        it.set_lf_breaks(self.glyph_line_break);
927        it.set_text_wrap(text_wrap);
928        it.set_left_margin(left_margin);
929        it.set_right_margin(right_margin);
930        it.set_word_margin(word_margin);
931        it.prepare()?;
932        Ok(it)
933    }
934
935    /// Get the grapheme at the given position.
936    #[inline]
937    pub fn grapheme_at(&self, pos: TextPosition) -> Result<Option<Grapheme<'_>>, TextError> {
938        let range_bytes = self.bytes_at_range(TextRange::new(pos, (pos.x + 1, pos.y)))?;
939        let pos_byte = self.byte_at(pos)?.start;
940
941        let mut it = self.text.graphemes_byte(range_bytes, pos_byte)?;
942
943        Ok(it.next())
944    }
945
946    /// Get a cursor over all the text with the current position set at pos.
947    #[inline]
948    pub fn text_graphemes(
949        &self,
950        pos: TextPosition,
951    ) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
952        let rows = self.len_lines() - 1;
953        let cols = self.line_width(rows).expect("valid_row");
954
955        let range_bytes = self.bytes_at_range(TextRange::new((0, 0), (cols, rows)))?;
956        let pos_byte = self.byte_at(pos)?.start;
957
958        self.text.graphemes_byte(range_bytes, pos_byte)
959    }
960
961    /// Get a cursor over the text-range the current position set at pos.
962    #[inline]
963    pub fn graphemes(
964        &self,
965        range: TextRange,
966        pos: TextPosition,
967    ) -> Result<Store::GraphemeIter<'_>, TextError> {
968        let range_bytes = self.bytes_at_range(range)?;
969        let pos_byte = self.byte_at(pos)?.start;
970
971        self.text.graphemes_byte(range_bytes, pos_byte)
972    }
973
974    /// Get a cursor over the text-range with the current position set at pos.
975    #[inline]
976    pub fn graphemes_byte(
977        &self,
978        range: Range<usize>,
979        pos: usize,
980    ) -> Result<Store::GraphemeIter<'_>, TextError> {
981        self.text.graphemes_byte(range, pos)
982    }
983
984    /// Line as str.
985    ///
986    /// * row must be <= len_lines
987    #[inline]
988    pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
989        self.text.line_at(row)
990    }
991
992    /// Iterate over text-lines, starting at row.
993    ///
994    /// * row must be <= len_lines
995    #[inline]
996    pub fn lines_at(
997        &self,
998        row: upos_type,
999    ) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
1000        self.text.lines_at(row)
1001    }
1002
1003    /// Get the text for a line as iterator over the graphemes.
1004    #[inline]
1005    pub fn line_graphemes(&self, row: upos_type) -> Result<Store::GraphemeIter<'_>, TextError> {
1006        self.text.line_graphemes(row)
1007    }
1008
1009    /// Line width as grapheme count. Excludes the terminating '\n'.
1010    #[inline]
1011    pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
1012        self.cache.validate_byte_pos(self.cache_validity());
1013
1014        let mut line_width = self.cache.line_width.borrow_mut();
1015        if let Some(cache) = line_width.get(&row) {
1016            Ok(cache.width)
1017        } else {
1018            let width = self.text.line_width(row)?;
1019            let byte_pos = self.text.byte_range_at(TextPosition::new(width, row))?;
1020            line_width.insert(
1021                row,
1022                LineWidthCache {
1023                    width,
1024                    byte_pos: byte_pos.start,
1025                },
1026            );
1027            Ok(width)
1028        }
1029    }
1030
1031    /// Number of lines.
1032    #[inline]
1033    pub fn len_lines(&self) -> upos_type {
1034        self.text.len_lines()
1035    }
1036}
1037
1038impl<Store: TextStore + Default> TextCore<Store> {
1039    /// Clear the internal state.
1040    pub fn clear(&mut self) {
1041        self.text.set_string("");
1042        self.cursor = TextPosition::default();
1043        self.anchor = TextPosition::default();
1044        if let Some(sty) = &mut self.styles {
1045            sty.clear();
1046        }
1047        if let Some(undo) = &mut self.undo {
1048            undo.clear();
1049
1050            if undo.has_replay_log() {
1051                undo.append(UndoOp::SetText {
1052                    txt: self.text.string(),
1053                });
1054            }
1055        }
1056    }
1057
1058    /// Copy of the text-value.
1059    pub fn text(&self) -> &Store {
1060        &self.text
1061    }
1062
1063    /// Set the text as a TextStore
1064    /// Clears the styles.
1065    /// Caps cursor and anchor.
1066    pub fn set_text(&mut self, t: Store) -> bool {
1067        self.text = t;
1068        if let Some(sty) = &mut self.styles {
1069            sty.clear();
1070        }
1071        self.cache.clear();
1072
1073        self.cursor.y = 0;
1074        self.cursor.x = 0;
1075        self.anchor.y = 0;
1076        self.anchor.x = 0;
1077
1078        if let Some(undo) = &mut self.undo {
1079            undo.clear();
1080
1081            if undo.has_replay_log() {
1082                undo.append(UndoOp::SetText {
1083                    txt: self.text.string(),
1084                });
1085            }
1086        }
1087
1088        true
1089    }
1090
1091    /// Auto-quote the selected text.
1092    #[allow(clippy::needless_bool)]
1093    pub fn insert_quotes(&mut self, mut sel: TextRange, c: char) -> Result<bool, TextError> {
1094        self.begin_undo_seq();
1095
1096        // remove matching quotes/brackets
1097        if sel.end.x > 0 {
1098            let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
1099            let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
1100            let c0 = self.str_slice(first).expect("valid_slice");
1101            let c1 = self.str_slice(last).expect("valid_slice");
1102            let remove_quote = if c == '\'' || c == '`' || c == '"' {
1103                if c0 == "'" && c1 == "'" {
1104                    true
1105                } else if c0 == "\"" && c1 == "\"" {
1106                    true
1107                } else if c0 == "`" && c1 == "`" {
1108                    true
1109                } else {
1110                    false
1111                }
1112            } else {
1113                if c0 == "<" && c1 == ">" {
1114                    true
1115                } else if c0 == "(" && c1 == ")" {
1116                    true
1117                } else if c0 == "[" && c1 == "]" {
1118                    true
1119                } else if c0 == "{" && c1 == "}" {
1120                    true
1121                } else {
1122                    false
1123                }
1124            };
1125            if remove_quote {
1126                self.remove_char_range(last)?;
1127                self.remove_char_range(first)?;
1128                if sel.start.y == sel.end.y {
1129                    sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
1130                } else {
1131                    sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
1132                }
1133            }
1134        }
1135
1136        let cc = match c {
1137            '\'' => '\'',
1138            '`' => '`',
1139            '"' => '"',
1140            '<' => '>',
1141            '(' => ')',
1142            '[' => ']',
1143            '{' => '}',
1144            _ => unreachable!("invalid quotes"),
1145        };
1146        self.insert_char(sel.end, cc)?;
1147        self.insert_char(sel.start, c)?;
1148        if sel.start.y == sel.end.y {
1149            sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
1150        } else {
1151            sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
1152        }
1153        self.set_selection(sel.start, sel.end);
1154        self.end_undo_seq();
1155        Ok(true)
1156    }
1157
1158    /// Insert a tab, either expanded or literally.
1159    pub fn insert_tab(&mut self, mut pos: TextPosition) -> Result<bool, TextError> {
1160        if self.expand_tabs {
1161            let n = self.tabs as upos_type - (pos.x % self.tabs as upos_type);
1162            for _ in 0..n {
1163                self.insert_char(pos, ' ')?;
1164                pos.x += 1;
1165            }
1166        } else {
1167            self.insert_char(pos, '\t')?;
1168        }
1169        Ok(true)
1170    }
1171
1172    /// Insert a line break.
1173    pub fn insert_newline(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1174        if self.text.is_multi_line() {
1175            let newline = mem::take(&mut self.newline);
1176            let r = self.insert_str(pos, &newline);
1177            self.newline = newline;
1178            r?;
1179            Ok(true)
1180        } else {
1181            Ok(false)
1182        }
1183    }
1184
1185    /// Insert a character.
1186    pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
1187        // if the very last line doesn't end with a newline,
1188        // if the insert-position is at (0,len_lines).
1189        if self.text.should_insert_newline(pos) {
1190            let save_anchor = self.anchor;
1191            let save_cursor = self.cursor;
1192            self.insert_newline(pos)?;
1193            self.anchor = save_anchor;
1194            self.cursor = save_cursor;
1195        }
1196
1197        let (inserted_range, inserted_bytes) = self.text.insert_char(pos, c)?;
1198
1199        let old_cursor = self.cursor;
1200        let old_anchor = self.anchor;
1201
1202        if let Some(sty) = &mut self.styles {
1203            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1204        }
1205        self.cursor = inserted_range.expand_pos(self.cursor);
1206        self.anchor = inserted_range.expand_pos(self.anchor);
1207
1208        if let Some(undo) = self.undo.as_mut() {
1209            undo.append(UndoOp::InsertChar {
1210                bytes: inserted_bytes.clone(),
1211                cursor: TextPositionChange {
1212                    before: old_cursor,
1213                    after: self.cursor,
1214                },
1215                anchor: TextPositionChange {
1216                    before: old_anchor,
1217                    after: self.anchor,
1218                },
1219                txt: c.to_string(),
1220            });
1221        }
1222
1223        Ok(true)
1224    }
1225
1226    /// Insert a string at position.
1227    pub fn insert_str(&mut self, pos: TextPosition, t: &str) -> Result<bool, TextError> {
1228        let old_cursor = self.cursor;
1229        let old_anchor = self.anchor;
1230
1231        let (inserted_range, inserted_bytes) = self.text.insert_str(pos, t)?;
1232
1233        if let Some(sty) = &mut self.styles {
1234            sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1235        }
1236        self.anchor = inserted_range.expand_pos(self.anchor);
1237        self.cursor = inserted_range.expand_pos(self.cursor);
1238
1239        if let Some(undo) = self.undo.as_mut() {
1240            undo.append(UndoOp::InsertStr {
1241                bytes: inserted_bytes.clone(),
1242                cursor: TextPositionChange {
1243                    before: old_cursor,
1244                    after: self.cursor,
1245                },
1246                anchor: TextPositionChange {
1247                    before: old_anchor,
1248                    after: self.anchor,
1249                },
1250                txt: t.to_string(),
1251            });
1252        }
1253
1254        Ok(true)
1255    }
1256
1257    /// Remove the previous character
1258    pub fn remove_prev_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1259        let (sx, sy) = if pos.y == 0 && pos.x == 0 {
1260            (0, 0)
1261        } else if pos.y > 0 && pos.x == 0 {
1262            let prev_line_width = self.line_width(pos.y - 1).expect("line_width"); // TODO
1263            (prev_line_width, pos.y - 1)
1264        } else {
1265            (pos.x - 1, pos.y)
1266        };
1267        let range = TextRange::new((sx, sy), (pos.x, pos.y));
1268
1269        self.remove_char_range(range)
1270    }
1271
1272    /// Remove the next characters.
1273    pub fn remove_next_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1274        let c_line_width = self.line_width(pos.y)?;
1275        let c_last_line = self.len_lines() - 1;
1276
1277        let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
1278            (pos.x, pos.y)
1279        } else if pos.y != c_last_line && pos.x == c_line_width {
1280            (0, pos.y + 1)
1281        } else {
1282            (pos.x + 1, pos.y)
1283        };
1284        let range = TextRange::new((pos.x, pos.y), (ex, ey));
1285
1286        self.remove_char_range(range)
1287    }
1288
1289    /// Remove a range.
1290    /// Put it into undo as 'char-removed'.
1291    pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1292        self._remove_range(range, true)
1293    }
1294
1295    /// Remove a range
1296    /// Put it into undo as 'str-removed'.
1297    pub fn remove_str_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1298        self._remove_range(range, false)
1299    }
1300
1301    fn _remove_range(&mut self, range: TextRange, char_range: bool) -> Result<bool, TextError> {
1302        let old_cursor = self.cursor;
1303        let old_anchor = self.anchor;
1304
1305        if range.is_empty() {
1306            return Ok(false);
1307        }
1308
1309        let (old_text, (_removed_range, removed_bytes)) = self.text.remove(range)?;
1310
1311        // remove deleted styles.
1312        let mut changed_style = Vec::new();
1313        if let Some(sty) = &mut self.styles {
1314            sty.remap(|r, s| {
1315                let new_range = shrink_range_by(removed_bytes.clone(), r.clone());
1316                if ranges_intersect(r.clone(), removed_bytes.clone()) {
1317                    changed_style.push(StyleChange {
1318                        before: r.clone(),
1319                        after: new_range.clone(),
1320                        style: s,
1321                    });
1322                    if new_range.is_empty() {
1323                        None
1324                    } else {
1325                        Some(new_range)
1326                    }
1327                } else {
1328                    Some(new_range)
1329                }
1330            });
1331        }
1332        self.anchor = range.shrink_pos(self.anchor);
1333        self.cursor = range.shrink_pos(self.cursor);
1334
1335        if let Some(undo) = &mut self.undo {
1336            if char_range {
1337                undo.append(UndoOp::RemoveChar {
1338                    bytes: removed_bytes.clone(),
1339                    cursor: TextPositionChange {
1340                        before: old_cursor,
1341                        after: self.cursor,
1342                    },
1343                    anchor: TextPositionChange {
1344                        before: old_anchor,
1345                        after: self.anchor,
1346                    },
1347                    txt: old_text,
1348                    styles: changed_style,
1349                });
1350            } else {
1351                undo.append(UndoOp::RemoveStr {
1352                    bytes: removed_bytes.clone(),
1353                    cursor: TextPositionChange {
1354                        before: old_cursor,
1355                        after: self.cursor,
1356                    },
1357                    anchor: TextPositionChange {
1358                        before: old_anchor,
1359                        after: self.anchor,
1360                    },
1361                    txt: old_text,
1362                    styles: changed_style,
1363                });
1364            }
1365        }
1366
1367        Ok(true)
1368    }
1369}
1370
1371impl<Store: TextStore + Default> TextCore<Store> {
1372    /// Find the start of the next word. If the position is at the start
1373    /// or inside a word, the same position is returned.
1374    pub fn next_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1375        let mut it = self.text_graphemes(pos)?;
1376        let mut last_pos = it.text_offset();
1377        loop {
1378            let Some(c) = it.next() else {
1379                break;
1380            };
1381            last_pos = c.text_bytes().start;
1382            if !c.is_whitespace() {
1383                break;
1384            }
1385        }
1386
1387        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1388    }
1389
1390    /// Find the end of the next word. Skips whitespace first, then goes on
1391    /// until it finds the next whitespace.
1392    pub fn next_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1393        let mut it = self.text_graphemes(pos)?;
1394        let mut last_pos = it.text_offset();
1395        let mut init = true;
1396        loop {
1397            let Some(c) = it.next() else {
1398                break;
1399            };
1400            last_pos = c.text_bytes().start;
1401            if init {
1402                if !c.is_whitespace() {
1403                    init = false;
1404                }
1405            } else {
1406                if c.is_whitespace() {
1407                    break;
1408                }
1409            }
1410            last_pos = c.text_bytes().end;
1411        }
1412
1413        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1414    }
1415
1416    /// Find the start of the prev word. Skips whitespace first, then goes on
1417    /// until it finds the next whitespace.
1418    ///
1419    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1420    /// both return start<=end!
1421    pub fn prev_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1422        let mut it = self.text_graphemes(pos)?;
1423        let mut last_pos = it.text_offset();
1424        let mut init = true;
1425        loop {
1426            let Some(c) = it.prev() else {
1427                break;
1428            };
1429            if init {
1430                if !c.is_whitespace() {
1431                    init = false;
1432                }
1433            } else {
1434                if c.is_whitespace() {
1435                    break;
1436                }
1437            }
1438            last_pos = c.text_bytes().start;
1439        }
1440
1441        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1442    }
1443
1444    /// Find the end of the previous word. Word is everything that is not whitespace.
1445    /// Attention: start/end are mirrored here compared to next_word_start/next_word_end,
1446    /// both return start<=end!
1447    pub fn prev_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1448        let mut it = self.text_graphemes(pos)?;
1449        let mut last_pos = it.text_offset();
1450        loop {
1451            let Some(c) = it.prev() else {
1452                break;
1453            };
1454            if !c.is_whitespace() {
1455                break;
1456            }
1457            last_pos = c.text_bytes().start;
1458        }
1459
1460        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1461    }
1462
1463    /// Is the position at a word boundary?
1464    pub fn is_word_boundary(&self, pos: TextPosition) -> Result<bool, TextError> {
1465        let mut it = self.text_graphemes(pos)?;
1466        if let Some(c0) = it.prev() {
1467            it.next();
1468            if let Some(c1) = it.next() {
1469                Ok(c0.is_whitespace() && !c1.is_whitespace()
1470                    || !c0.is_whitespace() && c1.is_whitespace())
1471            } else {
1472                Ok(false)
1473            }
1474        } else {
1475            Ok(false)
1476        }
1477    }
1478
1479    /// Find the start of the word at pos.
1480    /// Returns pos if the position is not inside a word.
1481    pub fn word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1482        let mut it = self.text_graphemes(pos)?;
1483        let mut last_pos = it.text_offset();
1484        loop {
1485            let Some(c) = it.prev() else {
1486                break;
1487            };
1488            if c.is_whitespace() {
1489                break;
1490            }
1491            last_pos = c.text_bytes().start;
1492        }
1493
1494        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1495    }
1496
1497    /// Find the end of the word at pos.
1498    /// Returns pos if the position is not inside a word.
1499    pub fn word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1500        let mut it = self.text_graphemes(pos)?;
1501        let mut last_pos = it.text_offset();
1502        loop {
1503            let Some(c) = it.next() else {
1504                break;
1505            };
1506            last_pos = c.text_bytes().start;
1507            if c.is_whitespace() {
1508                break;
1509            }
1510            last_pos = c.text_bytes().end;
1511        }
1512
1513        Ok(self.byte_pos(last_pos).expect("valid_pos"))
1514    }
1515}