rat_text/
text_core.rs

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