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#[derive(Debug)]
14pub struct TextCore<Store> {
15 text: Store,
17
18 cursor: TextPosition,
20 anchor: TextPosition,
22
23 styles: Option<Box<RangeMap>>,
25 undo: Option<Box<dyn UndoBuffer>>,
27 clip: Option<Box<dyn Clipboard>>,
29
30 newline: String,
32 tabs: u16,
34 expand_tabs: bool,
36 glyph_ctrl: bool,
38 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 #[inline]
91 pub fn set_newline(&mut self, br: String) {
92 self.newline = br;
93 }
94
95 #[inline]
97 pub fn newline(&self) -> &str {
98 &self.newline
99 }
100
101 #[inline]
104 pub fn set_tab_width(&mut self, tabs: u16) {
105 self.tabs = tabs;
106 }
107
108 #[inline]
110 pub fn tab_width(&self) -> u16 {
111 self.tabs
112 }
113
114 #[inline]
116 pub fn set_expand_tabs(&mut self, expand: bool) {
117 self.expand_tabs = expand;
118 }
119
120 #[inline]
122 pub fn expand_tabs(&self) -> bool {
123 self.expand_tabs
124 }
125
126 #[inline]
128 pub fn set_glyph_ctrl(&mut self, show_ctrl: bool) {
129 self.glyph_ctrl = show_ctrl;
130 }
131
132 pub fn glyph_ctrl(&self) -> bool {
134 self.glyph_ctrl
135 }
136
137 #[inline]
140 pub fn set_glyph_line_break(&mut self, line_break: bool) {
141 self.glyph_line_break = line_break;
142 }
143
144 pub fn glyph_line_break(&self) -> bool {
146 self.glyph_line_break
147 }
148}
149
150impl<Store: TextStore + Default> TextCore<Store> {
151 pub fn set_clipboard(&mut self, clip: Option<Box<dyn Clipboard + 'static>>) {
153 self.clip = clip;
154 }
155
156 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 #[inline]
168 pub fn set_undo_buffer(&mut self, undo: Option<Box<dyn UndoBuffer>>) {
169 self.undo = undo;
170 }
171
172 #[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 #[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 #[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 #[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 #[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 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 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 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 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 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 }
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 #[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 #[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 #[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 #[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 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 #[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 #[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 #[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 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 #[inline]
641 pub fn cursor(&self) -> TextPosition {
642 self.cursor
643 }
644
645 #[inline]
647 pub fn anchor(&self) -> TextPosition {
648 self.anchor
649 }
650
651 #[inline]
653 pub fn has_selection(&self) -> bool {
654 self.anchor != self.cursor
655 }
656
657 #[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 #[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 #[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 #[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 #[inline]
721 pub fn byte_at(&self, pos: TextPosition) -> Result<Range<usize>, TextError> {
722 self.text.byte_range_at(pos)
723 }
724
725 #[inline]
727 pub fn bytes_at_range(&self, range: TextRange) -> Result<Range<usize>, TextError> {
728 self.text.byte_range(range)
729 }
730
731 #[inline]
734 pub fn byte_pos(&self, byte: usize) -> Result<TextPosition, TextError> {
735 self.text.byte_to_pos(byte)
736 }
737
738 #[inline]
740 pub fn byte_range(&self, bytes: Range<usize>) -> Result<TextRange, TextError> {
741 self.text.bytes_to_range(bytes)
742 }
743
744 #[inline]
746 pub fn str_slice(&self, range: TextRange) -> Result<Cow<'_, str>, TextError> {
747 self.text.str_slice(range)
748 }
749
750 #[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 #[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 #[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 #[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 #[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 #[inline]
814 pub fn line_at(&self, row: upos_type) -> Result<Cow<'_, str>, TextError> {
815 self.text.line_at(row)
816 }
817
818 #[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 #[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 #[inline]
840 pub fn line_width(&self, row: upos_type) -> Result<upos_type, TextError> {
841 self.text.line_width(row)
842 }
843
844 #[inline]
846 pub fn len_lines(&self) -> upos_type {
847 self.text.len_lines()
848 }
849}
850
851impl<Store: TextStore + Default> TextCore<Store> {
852 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 pub fn text(&self) -> &Store {
873 &self.text
874 }
875
876 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 #[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 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 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 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 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 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 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 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 pub fn remove_char_range(&mut self, range: TextRange) -> Result<bool, TextError> {
1094 self._remove_range(range, true)
1095 }
1096
1097 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 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 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 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 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 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 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 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 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}