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#[derive(Debug)]
20pub struct TextCore<Store> {
21 text: Store,
23
24 cursor: TextPosition,
26 anchor: TextPosition,
28
29 styles: Option<Box<RangeMap>>,
31 undo: Option<Box<dyn UndoBuffer>>,
33 clip: Option<Box<dyn Clipboard>>,
35 cache: Cache,
37
38 newline: String,
40 tabs: u16,
42 expand_tabs: bool,
44 glyph_ctrl: bool,
46 wrap_ctrl: bool,
48 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 #[inline]
105 pub fn set_newline(&mut self, br: String) {
106 self.newline = br;
107 }
108
109 #[inline]
111 pub fn newline(&self) -> &str {
112 &self.newline
113 }
114
115 #[inline]
118 pub fn set_tab_width(&mut self, tabs: u16) {
119 self.tabs = tabs;
120 }
121
122 #[inline]
124 pub fn tab_width(&self) -> u16 {
125 self.tabs
126 }
127
128 #[inline]
130 pub fn set_expand_tabs(&mut self, expand: bool) {
131 self.expand_tabs = expand;
132 }
133
134 #[inline]
136 pub fn expand_tabs(&self) -> bool {
137 self.expand_tabs
138 }
139
140 #[inline]
142 pub fn set_glyph_ctrl(&mut self, show_ctrl: bool) {
143 self.glyph_ctrl = show_ctrl;
144 }
145
146 pub fn glyph_ctrl(&self) -> bool {
148 self.glyph_ctrl
149 }
150
151 #[inline]
153 pub fn set_wrap_ctrl(&mut self, wrap_ctrl: bool) {
154 self.wrap_ctrl = wrap_ctrl;
155 }
156
157 pub fn wrap_ctrl(&self) -> bool {
159 self.wrap_ctrl
160 }
161
162 #[inline]
165 pub fn set_glyph_line_break(&mut self, line_break: bool) {
166 self.glyph_line_break = line_break;
167 }
168
169 pub fn glyph_line_break(&self) -> bool {
171 self.glyph_line_break
172 }
173}
174
175impl<Store: TextStore + Default> TextCore<Store> {
176 pub fn set_clipboard(&mut self, clip: Option<Box<dyn Clipboard + 'static>>) {
178 self.clip = clip;
179 }
180
181 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 #[inline]
193 pub fn set_undo_buffer(&mut self, undo: Option<Box<dyn UndoBuffer>>) {
194 self.undo = undo;
195 }
196
197 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 }
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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 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 #[inline]
686 pub fn cursor(&self) -> TextPosition {
687 self.cursor
688 }
689
690 #[inline]
692 pub fn anchor(&self) -> TextPosition {
693 self.anchor
694 }
695
696 #[inline]
698 pub fn has_selection(&self) -> bool {
699 self.anchor != self.cursor
700 }
701
702 #[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 #[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 #[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 pub(crate) fn cache_validity(&self) -> Option<usize> {
761 self.text.cache_validity()
762 }
763}
764
765impl<Store: TextStore + Default> TextCore<Store> {
766 #[inline]
768 pub fn is_empty(&self) -> bool {
769 self.len_lines() == 1 && self.line_width(0).expect("line") == 0
770 }
771
772 #[inline]
775 pub fn byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
776 self.text.byte_range_at(pos)
777 }
778
779 #[inline]
781 pub fn bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
782 self.text.byte_range(range)
783 }
784
785 #[inline]
788 pub fn byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
789 self.text.byte_to_pos(byte)
790 }
791
792 #[inline]
794 pub fn byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
795 self.text.bytes_to_range(bytes)
796 }
797
798 #[inline]
800 pub fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
801 self.text.str_slice(range)
802 }
803
804 #[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 #[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 #[inline]
838 pub fn cache(&self) -> &Cache {
839 &self.cache
840 }
841
842 #[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 #[inline]
871 #[allow(clippy::too_many_arguments)]
872 pub(crate) fn glyphs2(
873 &self,
874 rendered: Size,
875 sub_row_offset: upos_type,
876 rows: Range<upos_type>,
877 text_wrap: TextWrap2,
878 ctrl_char: bool,
879 left_margin: upos_type,
880 right_margin: upos_type,
881 word_margin: upos_type,
882 ) -> Result<GlyphIter2<'_, Store::GraphemeIter<'_>>, TextError> {
883 self.cache.validate(
884 text_wrap,
885 left_margin,
886 rendered.width as upos_type,
887 rendered.height as upos_type,
888 ctrl_char,
889 self.cache_validity(),
890 );
891
892 let range = TextRange::new((sub_row_offset, rows.start), (0, rows.end));
893
894 let range_bytes;
895 let mut range_to_bytes = self.cache.range_to_bytes.borrow_mut();
896 if let Some(cache) = range_to_bytes.get(&range) {
897 range_bytes = cache.clone();
898 } else {
899 let cache = self.text.byte_range(range)?;
900 range_to_bytes.insert(range, cache.clone());
901 range_bytes = cache;
902 }
903
904 let iter = self.graphemes_byte(range_bytes.clone(), range_bytes.start)?;
905
906 let mut it = GlyphIter2::new(
907 range.start, range_bytes.start,
909 iter,
910 self.cache.clone(),
911 );
912 it.set_tabs(self.tabs as upos_type);
913 it.set_show_ctrl(self.glyph_ctrl);
914 it.set_wrap_ctrl(self.wrap_ctrl);
915 it.set_lf_breaks(self.glyph_line_break);
916 it.set_text_wrap(text_wrap);
917 it.set_left_margin(left_margin);
918 it.set_right_margin(right_margin);
919 it.set_word_margin(word_margin);
920 it.prepare()?;
921 Ok(it)
922 }
923
924 #[inline]
926 pub fn grapheme_at(&self, pos: TextPosition) -> Result<Option<Grapheme<'_>>, TextError> {
927 let range_bytes = self.bytes_at_range(TextRange::new(pos, (pos.x + 1, pos.y)))?;
928 let pos_byte = self.byte_at(pos)?.start;
929
930 let mut it = self.text.graphemes_byte(range_bytes, pos_byte)?;
931
932 Ok(it.next())
933 }
934
935 #[inline]
937 pub fn text_graphemes(
938 &self,
939 pos: TextPosition,
940 ) -> Result<impl Cursor<Item = Grapheme<'_>>, TextError> {
941 let rows = self.len_lines() - 1;
942 let cols = self.line_width(rows).expect("valid_row");
943
944 let range_bytes = self.bytes_at_range(TextRange::new((0, 0), (cols, rows)))?;
945 let pos_byte = self.byte_at(pos)?.start;
946
947 self.text.graphemes_byte(range_bytes, pos_byte)
948 }
949
950 #[inline]
952 pub fn graphemes(
953 &self,
954 range: TextRange,
955 pos: TextPosition,
956 ) -> Result<Store::GraphemeIter<'_>, TextError> {
957 let range_bytes = self.bytes_at_range(range)?;
958 let pos_byte = self.byte_at(pos)?.start;
959
960 self.text.graphemes_byte(range_bytes, pos_byte)
961 }
962
963 #[inline]
965 pub fn graphemes_byte(
966 &self,
967 range: Range<usize>,
968 pos: usize,
969 ) -> Result<Store::GraphemeIter<'_>, TextError> {
970 self.text.graphemes_byte(range, pos)
971 }
972
973 #[inline]
977 pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
978 self.text.line_at(row)
979 }
980
981 #[inline]
985 pub fn lines_at(
986 &self,
987 row: upos_type,
988 ) -> Result<impl Iterator<Item = Cow<'_, str>>, TextError> {
989 self.text.lines_at(row)
990 }
991
992 #[inline]
994 pub fn line_graphemes(&self, row: upos_type) -> Result<Store::GraphemeIter<'_>, TextError> {
995 self.text.line_graphemes(row)
996 }
997
998 #[inline]
1000 pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
1001 self.cache.validate_byte_pos(self.cache_validity());
1002
1003 let mut line_width = self.cache.line_width.borrow_mut();
1004 if let Some(cache) = line_width.get(&row) {
1005 Ok(cache.width)
1006 } else {
1007 let width = self.text.line_width(row)?;
1008 let byte_pos = self.text.byte_range_at(TextPosition::new(width, row))?;
1009 line_width.insert(
1010 row,
1011 LineWidthCache {
1012 width,
1013 byte_pos: byte_pos.start,
1014 },
1015 );
1016 Ok(width)
1017 }
1018 }
1019
1020 #[inline]
1022 pub fn len_lines(&self) -> upos_type {
1023 self.text.len_lines()
1024 }
1025}
1026
1027impl<Store: TextStore + Default> TextCore<Store> {
1028 pub fn clear(&mut self) {
1030 self.text.set_string("");
1031 self.cursor = TextPosition::default();
1032 self.anchor = TextPosition::default();
1033 if let Some(sty) = &mut self.styles {
1034 sty.clear();
1035 }
1036 if let Some(undo) = &mut self.undo {
1037 undo.clear();
1038
1039 if undo.has_replay_log() {
1040 undo.append(UndoOp::SetText {
1041 txt: self.text.string(),
1042 });
1043 }
1044 }
1045 }
1046
1047 pub fn text(&self) -> &Store {
1049 &self.text
1050 }
1051
1052 pub fn set_text(&mut self, t: Store) -> bool {
1056 self.text = t;
1057 if let Some(sty) = &mut self.styles {
1058 sty.clear();
1059 }
1060 self.cache.clear();
1061
1062 self.cursor.y = 0;
1063 self.cursor.x = 0;
1064 self.anchor.y = 0;
1065 self.anchor.x = 0;
1066
1067 if let Some(undo) = &mut self.undo {
1068 undo.clear();
1069
1070 if undo.has_replay_log() {
1071 undo.append(UndoOp::SetText {
1072 txt: self.text.string(),
1073 });
1074 }
1075 }
1076
1077 true
1078 }
1079
1080 #[allow(clippy::needless_bool)]
1082 pub fn insert_quotes(&mut self, mut sel: TextRange, c: char) -> Result<bool, TextError> {
1083 self.begin_undo_seq();
1084
1085 if sel.end.x > 0 {
1087 let first = TextRange::new(sel.start, (sel.start.x + 1, sel.start.y));
1088 let last = TextRange::new((sel.end.x - 1, sel.end.y), sel.end);
1089 let c0 = self.str_slice(first).expect("valid_slice");
1090 let c1 = self.str_slice(last).expect("valid_slice");
1091 let remove_quote = if c == '\'' || c == '`' || c == '"' {
1092 if c0 == "'" && c1 == "'" {
1093 true
1094 } else if c0 == "\"" && c1 == "\"" {
1095 true
1096 } else if c0 == "`" && c1 == "`" {
1097 true
1098 } else {
1099 false
1100 }
1101 } else {
1102 if c0 == "<" && c1 == ">" {
1103 true
1104 } else if c0 == "(" && c1 == ")" {
1105 true
1106 } else if c0 == "[" && c1 == "]" {
1107 true
1108 } else if c0 == "{" && c1 == "}" {
1109 true
1110 } else {
1111 false
1112 }
1113 };
1114 if remove_quote {
1115 self.remove_char_range(last)?;
1116 self.remove_char_range(first)?;
1117 if sel.start.y == sel.end.y {
1118 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 2, sel.end.y));
1119 } else {
1120 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x - 1, sel.end.y));
1121 }
1122 }
1123 }
1124
1125 let cc = match c {
1126 '\'' => '\'',
1127 '`' => '`',
1128 '"' => '"',
1129 '<' => '>',
1130 '(' => ')',
1131 '[' => ']',
1132 '{' => '}',
1133 _ => unreachable!("invalid quotes"),
1134 };
1135 self.insert_char(sel.end, cc)?;
1136 self.insert_char(sel.start, c)?;
1137 if sel.start.y == sel.end.y {
1138 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 2, sel.end.y));
1139 } else {
1140 sel = TextRange::new(sel.start, TextPosition::new(sel.end.x + 1, sel.end.y));
1141 }
1142 self.set_selection(sel.start, sel.end);
1143 self.end_undo_seq();
1144 Ok(true)
1145 }
1146
1147 pub fn insert_tab(&mut self, mut pos: TextPosition) -> Result<bool, TextError> {
1149 if self.expand_tabs {
1150 let n = self.tabs as upos_type - (pos.x % self.tabs as upos_type);
1151 for _ in 0..n {
1152 self.insert_char(pos, ' ')?;
1153 pos.x += 1;
1154 }
1155 } else {
1156 self.insert_char(pos, '\t')?;
1157 }
1158 Ok(true)
1159 }
1160
1161 pub fn insert_newline(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1163 if self.text.is_multi_line() {
1164 let newline = mem::take(&mut self.newline);
1165 let r = self.insert_str(pos, &newline);
1166 self.newline = newline;
1167 r?;
1168 Ok(true)
1169 } else {
1170 Ok(false)
1171 }
1172 }
1173
1174 pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
1176 if self.text.should_insert_newline(pos) {
1179 let save_anchor = self.anchor;
1180 let save_cursor = self.cursor;
1181 self.insert_newline(pos)?;
1182 self.anchor = save_anchor;
1183 self.cursor = save_cursor;
1184 }
1185
1186 let (inserted_range, inserted_bytes) = self.text.insert_char(pos, c)?;
1187
1188 let old_cursor = self.cursor;
1189 let old_anchor = self.anchor;
1190
1191 if let Some(sty) = &mut self.styles {
1192 sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1193 }
1194 self.cursor = inserted_range.expand_pos(self.cursor);
1195 self.anchor = inserted_range.expand_pos(self.anchor);
1196
1197 if let Some(undo) = self.undo.as_mut() {
1198 undo.append(UndoOp::InsertChar {
1199 bytes: inserted_bytes.clone(),
1200 cursor: TextPositionChange {
1201 before: old_cursor,
1202 after: self.cursor,
1203 },
1204 anchor: TextPositionChange {
1205 before: old_anchor,
1206 after: self.anchor,
1207 },
1208 txt: c.to_string(),
1209 });
1210 }
1211
1212 Ok(true)
1213 }
1214
1215 pub fn insert_str(&mut self, pos: TextPosition, t: &str) -> Result<bool, TextError> {
1217 let old_cursor = self.cursor;
1218 let old_anchor = self.anchor;
1219
1220 let (inserted_range, inserted_bytes) = self.text.insert_str(pos, t)?;
1221
1222 if let Some(sty) = &mut self.styles {
1223 sty.remap(|r, _| Some(expand_range_by(inserted_bytes.clone(), r)));
1224 }
1225 self.anchor = inserted_range.expand_pos(self.anchor);
1226 self.cursor = inserted_range.expand_pos(self.cursor);
1227
1228 if let Some(undo) = self.undo.as_mut() {
1229 undo.append(UndoOp::InsertStr {
1230 bytes: inserted_bytes.clone(),
1231 cursor: TextPositionChange {
1232 before: old_cursor,
1233 after: self.cursor,
1234 },
1235 anchor: TextPositionChange {
1236 before: old_anchor,
1237 after: self.anchor,
1238 },
1239 txt: t.to_string(),
1240 });
1241 }
1242
1243 Ok(true)
1244 }
1245
1246 pub fn remove_prev_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1248 let (sx, sy) = if pos.y == 0 && pos.x == 0 {
1249 (0, 0)
1250 } else if pos.y > 0 && pos.x == 0 {
1251 let prev_line_width = self.line_width(pos.y - 1).expect("line_width"); (prev_line_width, pos.y - 1)
1253 } else {
1254 (pos.x - 1, pos.y)
1255 };
1256 let range = TextRange::new((sx, sy), (pos.x, pos.y));
1257
1258 self.remove_char_range(range)
1259 }
1260
1261 pub fn remove_next_char(&mut self, pos: TextPosition) -> Result<bool, TextError> {
1263 let c_line_width = self.line_width(pos.y)?;
1264 let c_last_line = self.len_lines() - 1;
1265
1266 let (ex, ey) = if pos.y == c_last_line && pos.x == c_line_width {
1267 (pos.x, pos.y)
1268 } else if pos.y != c_last_line && pos.x == c_line_width {
1269 (0, pos.y + 1)
1270 } else {
1271 (pos.x + 1, pos.y)
1272 };
1273 let range = TextRange::new((pos.x, pos.y), (ex, ey));
1274
1275 self.remove_char_range(range)
1276 }
1277
1278 pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1281 self._remove_range(range, true)
1282 }
1283
1284 pub fn remove_str_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1287 self._remove_range(range, false)
1288 }
1289
1290 fn _remove_range(&mut self, range: TextRange, char_range: bool) -> Result<bool, TextError> {
1291 let old_cursor = self.cursor;
1292 let old_anchor = self.anchor;
1293
1294 if range.is_empty() {
1295 return Ok(false);
1296 }
1297
1298 let (old_text, (_removed_range, removed_bytes)) = self.text.remove(range)?;
1299
1300 let mut changed_style = Vec::new();
1302 if let Some(sty) = &mut self.styles {
1303 sty.remap(|r, s| {
1304 let new_range = shrink_range_by(removed_bytes.clone(), r.clone());
1305 if ranges_intersect(r.clone(), removed_bytes.clone()) {
1306 changed_style.push(StyleChange {
1307 before: r.clone(),
1308 after: new_range.clone(),
1309 style: s,
1310 });
1311 if new_range.is_empty() {
1312 None
1313 } else {
1314 Some(new_range)
1315 }
1316 } else {
1317 Some(new_range)
1318 }
1319 });
1320 }
1321 self.anchor = range.shrink_pos(self.anchor);
1322 self.cursor = range.shrink_pos(self.cursor);
1323
1324 if let Some(undo) = &mut self.undo {
1325 if char_range {
1326 undo.append(UndoOp::RemoveChar {
1327 bytes: removed_bytes.clone(),
1328 cursor: TextPositionChange {
1329 before: old_cursor,
1330 after: self.cursor,
1331 },
1332 anchor: TextPositionChange {
1333 before: old_anchor,
1334 after: self.anchor,
1335 },
1336 txt: old_text,
1337 styles: changed_style,
1338 });
1339 } else {
1340 undo.append(UndoOp::RemoveStr {
1341 bytes: removed_bytes.clone(),
1342 cursor: TextPositionChange {
1343 before: old_cursor,
1344 after: self.cursor,
1345 },
1346 anchor: TextPositionChange {
1347 before: old_anchor,
1348 after: self.anchor,
1349 },
1350 txt: old_text,
1351 styles: changed_style,
1352 });
1353 }
1354 }
1355
1356 Ok(true)
1357 }
1358}
1359
1360impl<Store: TextStore + Default> TextCore<Store> {
1361 pub fn next_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1364 let mut it = self.text_graphemes(pos)?;
1365 let mut last_pos = it.text_offset();
1366 loop {
1367 let Some(c) = it.next() else {
1368 break;
1369 };
1370 last_pos = c.text_bytes().start;
1371 if !c.is_whitespace() {
1372 break;
1373 }
1374 }
1375
1376 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1377 }
1378
1379 pub fn next_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1382 let mut it = self.text_graphemes(pos)?;
1383 let mut last_pos = it.text_offset();
1384 let mut init = true;
1385 loop {
1386 let Some(c) = it.next() else {
1387 break;
1388 };
1389 last_pos = c.text_bytes().start;
1390 if init {
1391 if !c.is_whitespace() {
1392 init = false;
1393 }
1394 } else {
1395 if c.is_whitespace() {
1396 break;
1397 }
1398 }
1399 last_pos = c.text_bytes().end;
1400 }
1401
1402 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1403 }
1404
1405 pub fn prev_word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1411 let mut it = self.text_graphemes(pos)?;
1412 let mut last_pos = it.text_offset();
1413 let mut init = true;
1414 loop {
1415 let Some(c) = it.prev() else {
1416 break;
1417 };
1418 if init {
1419 if !c.is_whitespace() {
1420 init = false;
1421 }
1422 } else {
1423 if c.is_whitespace() {
1424 break;
1425 }
1426 }
1427 last_pos = c.text_bytes().start;
1428 }
1429
1430 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1431 }
1432
1433 pub fn prev_word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1437 let mut it = self.text_graphemes(pos)?;
1438 let mut last_pos = it.text_offset();
1439 loop {
1440 let Some(c) = it.prev() else {
1441 break;
1442 };
1443 if !c.is_whitespace() {
1444 break;
1445 }
1446 last_pos = c.text_bytes().start;
1447 }
1448
1449 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1450 }
1451
1452 pub fn is_word_boundary(&self, pos: TextPosition) -> Result<bool, TextError> {
1454 let mut it = self.text_graphemes(pos)?;
1455 if let Some(c0) = it.prev() {
1456 it.next();
1457 if let Some(c1) = it.next() {
1458 Ok(c0.is_whitespace() && !c1.is_whitespace()
1459 || !c0.is_whitespace() && c1.is_whitespace())
1460 } else {
1461 Ok(false)
1462 }
1463 } else {
1464 Ok(false)
1465 }
1466 }
1467
1468 pub fn word_start(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1471 let mut it = self.text_graphemes(pos)?;
1472 let mut last_pos = it.text_offset();
1473 loop {
1474 let Some(c) = it.prev() else {
1475 break;
1476 };
1477 if c.is_whitespace() {
1478 break;
1479 }
1480 last_pos = c.text_bytes().start;
1481 }
1482
1483 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1484 }
1485
1486 pub fn word_end(&self, pos: TextPosition) -> Result<TextPosition, TextError> {
1489 let mut it = self.text_graphemes(pos)?;
1490 let mut last_pos = it.text_offset();
1491 loop {
1492 let Some(c) = it.next() else {
1493 break;
1494 };
1495 last_pos = c.text_bytes().start;
1496 if c.is_whitespace() {
1497 break;
1498 }
1499 last_pos = c.text_bytes().end;
1500 }
1501
1502 Ok(self.byte_pos(last_pos).expect("valid_pos"))
1503 }
1504}