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 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 #[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 #[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 #[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 #[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 #[inline]
988 pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
989 self.text.line_at(row)
990 }
991
992 #[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 #[inline]
1005 pub fn line_graphemes(&self, row: upos_type) -> Result<Store::GraphemeIter<'_>, TextError> {
1006 self.text.line_graphemes(row)
1007 }
1008
1009 #[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 #[inline]
1033 pub fn len_lines(&self) -> upos_type {
1034 self.text.len_lines()
1035 }
1036}
1037
1038impl<Store: TextStore + Default> TextCore<Store> {
1039 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 pub fn text(&self) -> &Store {
1060 &self.text
1061 }
1062
1063 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 #[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 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 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 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 pub fn insert_char(&mut self, pos: TextPosition, c: char) -> Result<bool, TextError> {
1187 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 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 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"); (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 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 pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1292 self._remove_range(range, true)
1293 }
1294
1295 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 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 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 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 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 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 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 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 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}