1use crate::{
45 error::Error,
46 id::{OpId, ReplicaId},
47 list::{List, ListOp},
48 version::VersionVector,
49};
50use std::collections::HashMap;
51
52#[cfg(feature = "serde")]
53use serde::{Deserialize, Serialize};
54
55#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
61#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
62pub enum AnchorSide {
63 Before,
65 After,
67}
68
69#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
76#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
77pub enum Anchor {
78 Start,
80 End,
82 Char(OpId, AnchorSide),
84}
85
86#[derive(Clone, Debug, PartialEq, Eq)]
93#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
94pub enum SpanValue {
95 On,
97 Off,
99 Set(String),
101 Unset,
103}
104
105#[derive(Clone, Debug, PartialEq, Eq)]
107#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
108pub struct Span {
109 pub id: OpId,
111 pub start: Anchor,
113 pub end: Anchor,
115 pub name: String,
117 pub value: SpanValue,
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
123#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
124pub enum TextOp {
125 Char(ListOp<char>),
127 Mark(Span),
129}
130
131impl TextOp {
132 #[must_use]
134 pub fn id(&self) -> OpId {
135 match self {
136 TextOp::Char(op) => op.id(),
137 TextOp::Mark(s) => s.id,
138 }
139 }
140}
141
142#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Default)]
148#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
149pub enum ExpandRule {
150 #[default]
152 None,
153 Right,
155 Left,
157 Both,
159}
160
161#[derive(Clone, Debug, PartialEq, Eq)]
167#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
168pub enum MarkValue {
169 Boolean,
171 Value(String),
173}
174
175#[derive(Clone, Debug, Default, PartialEq, Eq)]
177pub struct MarkSet {
178 inner: std::collections::BTreeMap<String, MarkValue>,
179}
180
181impl MarkSet {
182 #[must_use]
184 pub fn new() -> Self {
185 Self::default()
186 }
187
188 pub fn contains(&self, name: &str) -> bool {
190 self.inner.contains_key(name)
191 }
192
193 pub fn value_of(&self, name: &str) -> Option<&str> {
195 match self.inner.get(name) {
196 Some(MarkValue::Value(s)) => Some(s.as_str()),
197 _ => None,
198 }
199 }
200
201 pub fn iter(&self) -> impl Iterator<Item = &str> + '_ {
203 self.inner.keys().map(String::as_str)
204 }
205
206 pub fn iter_with_values(&self) -> impl Iterator<Item = (&str, &MarkValue)> + '_ {
208 self.inner.iter().map(|(k, v)| (k.as_str(), v))
209 }
210
211 pub fn iter_booleans(&self) -> impl Iterator<Item = &str> + '_ {
213 self.inner.iter().filter_map(|(k, v)| match v {
214 MarkValue::Boolean => Some(k.as_str()),
215 MarkValue::Value(_) => None,
216 })
217 }
218
219 pub fn iter_values(&self) -> impl Iterator<Item = (&str, &str)> + '_ {
221 self.inner.iter().filter_map(|(k, v)| match v {
222 MarkValue::Boolean => None,
223 MarkValue::Value(s) => Some((k.as_str(), s.as_str())),
224 })
225 }
226
227 #[must_use]
229 pub fn len(&self) -> usize {
230 self.inner.len()
231 }
232
233 #[must_use]
235 pub fn is_empty(&self) -> bool {
236 self.inner.is_empty()
237 }
238}
239
240#[derive(Clone, Debug)]
250#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
251pub struct Text {
252 chars: List<char>,
253 spans: Vec<Span>,
257 replica: ReplicaId,
259 log: Vec<TextOp>,
261 version: VersionVector,
263}
264
265impl Text {
266 #[must_use]
268 pub fn new(replica: ReplicaId) -> Self {
269 Self {
270 chars: List::<char>::new(replica),
271 spans: Vec::new(),
272 replica,
273 log: Vec::new(),
274 version: VersionVector::new(),
275 }
276 }
277
278 #[must_use]
281 pub fn new_random() -> Self {
282 Self::new(crate::id::new_replica_id())
283 }
284
285 #[must_use]
287 pub fn replica_id(&self) -> ReplicaId {
288 self.replica
289 }
290
291 #[must_use]
297 pub fn len(&self) -> usize {
298 self.chars.len()
299 }
300
301 #[must_use]
303 pub fn is_empty(&self) -> bool {
304 self.chars.is_empty()
305 }
306
307 #[must_use]
314 pub fn grapheme_count(&self) -> usize {
315 use unicode_segmentation::UnicodeSegmentation;
316 let s: String = self.chars.iter().collect();
317 s.graphemes(true).count()
318 }
319
320 #[must_use]
325 pub fn grapheme_to_char_pos(&self, grapheme_pos: usize) -> usize {
326 use unicode_segmentation::UnicodeSegmentation;
327 let s: String = self.chars.iter().collect();
328 let mut char_acc = 0usize;
329 for (gi, g) in s.graphemes(true).enumerate() {
330 if gi == grapheme_pos {
331 return char_acc;
332 }
333 char_acc += g.chars().count();
334 }
335 self.len()
336 }
337
338 #[must_use]
344 pub fn char_to_grapheme_pos(&self, char_pos: usize) -> usize {
345 use unicode_segmentation::UnicodeSegmentation;
346 let s: String = self.chars.iter().collect();
347 let mut char_acc = 0usize;
348 for (gi, g) in s.graphemes(true).enumerate() {
349 if char_acc >= char_pos {
350 return gi;
351 }
352 char_acc += g.chars().count();
353 }
354 s.graphemes(true).count()
355 }
356
357 pub fn insert_grapheme_str(&mut self, g_pos: usize, s: &str) -> Vec<TextOp> {
364 let char_pos = self.grapheme_to_char_pos(g_pos);
365 self.insert_str(char_pos, s)
366 }
367
368 pub fn delete_grapheme(&mut self, g_pos: usize) -> Vec<TextOp> {
371 use unicode_segmentation::UnicodeSegmentation;
372 let s: String = self.chars.iter().collect();
373 let mut char_pos = 0usize;
374 let mut g_chars = 0usize;
375 for (gi, g) in s.graphemes(true).enumerate() {
376 if gi == g_pos {
377 g_chars = g.chars().count();
378 break;
379 }
380 char_pos += g.chars().count();
381 if gi == g_pos {
382 g_chars = g.chars().count();
383 break;
384 }
385 }
386 if g_chars == 0 {
387 return Vec::new();
388 }
389 let mut ops = Vec::with_capacity(g_chars);
390 for _ in 0..g_chars {
391 ops.push(self.delete(char_pos));
392 }
393 ops
394 }
395
396 #[must_use]
402 pub fn as_string(&self) -> String {
403 self.chars.iter().collect()
404 }
405
406 pub fn insert(&mut self, pos: usize, ch: char) -> TextOp {
408 let op = self.chars.insert(pos, ch);
409 let text_op = TextOp::Char(op);
410 self.version.observe(text_op.id());
411 self.log.push(text_op.clone());
412 text_op
413 }
414
415 pub fn insert_str(&mut self, pos: usize, s: &str) -> Vec<TextOp> {
417 let mut ops = Vec::new();
418 for (i, ch) in s.chars().enumerate() {
419 ops.push(self.insert(pos + i, ch));
420 }
421 ops
422 }
423
424 pub fn delete(&mut self, pos: usize) -> TextOp {
426 let op = self.chars.delete(pos);
427 let text_op = TextOp::Char(op);
428 self.version.observe(text_op.id());
429 self.log.push(text_op.clone());
430 text_op
431 }
432
433 pub fn delete_range(&mut self, range: std::ops::Range<usize>) -> Vec<TextOp> {
435 let mut ops = Vec::new();
436 for _ in range.clone() {
438 ops.push(self.delete(range.start));
439 }
440 ops
441 }
442
443 pub fn set_mark(&mut self, range: std::ops::Range<usize>, name: &str, on: bool) -> TextOp {
458 let value = if on { SpanValue::On } else { SpanValue::Off };
459 self.set_mark_with_rule(range, name, value, ExpandRule::None)
460 }
461
462 pub fn set_value_mark(
469 &mut self,
470 range: std::ops::Range<usize>,
471 name: &str,
472 value: Option<&str>,
473 ) -> TextOp {
474 let v = match value {
475 Some(s) => SpanValue::Set(s.to_string()),
476 None => SpanValue::Unset,
477 };
478 self.set_mark_with_rule(range, name, v, ExpandRule::None)
479 }
480
481 pub fn set_mark_with_rule(
488 &mut self,
489 range: std::ops::Range<usize>,
490 name: &str,
491 value: SpanValue,
492 rule: ExpandRule,
493 ) -> TextOp {
494 assert!(range.start <= range.end, "set_mark: empty/inverted range");
495 assert!(range.end <= self.len(), "set_mark: range past end of text");
496 let (start, end) = self.anchors_for_range(range, rule);
497 self.set_mark_with_anchors(start, end, name, value)
498 }
499
500 pub fn set_mark_with_anchors(
503 &mut self,
504 start: Anchor,
505 end: Anchor,
506 name: &str,
507 value: SpanValue,
508 ) -> TextOp {
509 let id = self.chars.next_op_id();
512 let span = Span {
513 id,
514 start,
515 end,
516 name: name.to_string(),
517 value,
518 };
519 self.insert_span_sorted(span.clone());
520 let text_op = TextOp::Mark(span);
521 self.version.observe(id);
522 self.log.push(text_op.clone());
523 text_op
524 }
525
526 fn anchors_for_range(
529 &self,
530 range: std::ops::Range<usize>,
531 rule: ExpandRule,
532 ) -> (Anchor, Anchor) {
533 let len = self.len();
534 let expand_left = matches!(rule, ExpandRule::Left | ExpandRule::Both);
535 let expand_right = matches!(rule, ExpandRule::Right | ExpandRule::Both);
536
537 let start = if range.start == 0 {
538 if expand_left {
539 Anchor::Start
542 } else if len == 0 {
543 Anchor::Start
544 } else {
545 Anchor::Char(self.chars.id_at(0).unwrap(), AnchorSide::Before)
548 }
549 } else if expand_left {
550 Anchor::Char(
552 self.chars.id_at(range.start - 1).unwrap(),
553 AnchorSide::After,
554 )
555 } else {
556 Anchor::Char(self.chars.id_at(range.start).unwrap(), AnchorSide::Before)
558 };
559
560 let end = if range.end == 0 {
561 if expand_left {
563 Anchor::Start
564 } else {
565 start
566 }
567 } else if range.end == len {
568 if expand_right {
569 Anchor::End
571 } else if len == 0 {
572 Anchor::Start
573 } else {
574 Anchor::Char(self.chars.id_at(range.end - 1).unwrap(), AnchorSide::After)
575 }
576 } else if expand_right {
577 Anchor::Char(self.chars.id_at(range.end).unwrap(), AnchorSide::Before)
579 } else {
580 Anchor::Char(self.chars.id_at(range.end - 1).unwrap(), AnchorSide::After)
582 };
583
584 (start, end)
585 }
586
587 pub fn apply_inverse(&mut self, op: &TextOp) -> Option<TextOp> {
610 match op {
611 TextOp::Char(char_op) => {
612 let inv = self.chars.apply_inverse(char_op)?;
613 let text_op = TextOp::Char(inv);
614 self.version.observe(text_op.id());
615 self.log.push(text_op.clone());
616 Some(text_op)
617 }
618 TextOp::Mark(span) => {
619 let inverse_value = match &span.value {
620 SpanValue::On => SpanValue::Off,
621 SpanValue::Set(_) => SpanValue::Unset,
622 SpanValue::Off | SpanValue::Unset => return None,
626 };
627 let id = self.chars.next_op_id();
628 let inv_span = Span {
629 id,
630 start: span.start,
631 end: span.end,
632 name: span.name.clone(),
633 value: inverse_value,
634 };
635 self.insert_span_sorted(inv_span.clone());
636 let text_op = TextOp::Mark(inv_span);
637 self.version.observe(id);
638 self.log.push(text_op.clone());
639 Some(text_op)
640 }
641 }
642 }
643
644 pub fn apply(&mut self, op: TextOp) -> Result<(), Error> {
646 let op_id = op.id();
647 if self.version.contains(op_id) {
648 return Ok(());
649 }
650 match &op {
651 TextOp::Char(char_op) => {
652 self.chars.apply(char_op.clone())?;
653 }
654 TextOp::Mark(span) => {
655 self.chars.observe_external(span.id);
658 self.insert_span_sorted(span.clone());
659 }
660 }
661 self.version.observe(op_id);
662 self.log.push(op);
663 Ok(())
664 }
665
666 pub fn merge(&mut self, other: &Self) {
668 let mut to_apply: Vec<&TextOp> = other
669 .log
670 .iter()
671 .filter(|op| !self.version.contains(op.id()))
672 .collect();
673 to_apply.sort_by_key(|op| op.id());
674 for op in to_apply {
675 self.apply(op.clone())
676 .expect("text apply cannot fail in merge");
677 }
678 }
679
680 #[must_use]
682 pub fn ops(&self) -> &[TextOp] {
683 &self.log
684 }
685
686 pub fn ops_since<'a>(
688 &'a self,
689 since: &'a VersionVector,
690 ) -> impl Iterator<Item = &'a TextOp> + 'a {
691 self.log.iter().filter(move |op| !since.contains(op.id()))
692 }
693
694 #[must_use]
696 pub fn spans(&self) -> &[Span] {
697 &self.spans
698 }
699
700 #[must_use]
702 pub fn version(&self) -> &VersionVector {
703 &self.version
704 }
705
706 pub fn iter_with_marks(&self) -> Box<dyn Iterator<Item = (char, MarkSet)> + '_> {
716 let positions = self.chars.phantom_positions();
717 let visible_ids = self.chars.op_ids();
718 let len = visible_ids.len();
719 let resolved: Vec<(usize, usize, &Span)> = self
723 .spans
724 .iter()
725 .filter_map(|s| {
726 let sp = self.resolve_anchor(&s.start, &positions, len, false)?;
727 let ep = self.resolve_anchor(&s.end, &positions, len, true)?;
728 if sp >= ep {
729 None
730 } else {
731 Some((sp, ep, s))
732 }
733 })
734 .collect();
735 Box::new((0..len).map(move |idx| {
736 let _ = visible_ids[idx]; let ch = self.chars.get(idx).copied().unwrap_or('\0');
738 let mut state: HashMap<&str, &SpanValue> = HashMap::new();
741 for &(sp, ep, span) in &resolved {
742 if sp <= idx && idx < ep {
743 state.insert(span.name.as_str(), &span.value);
744 }
745 }
746 let mut marks = MarkSet::new();
747 for (name, value) in state {
748 match value {
749 SpanValue::On => {
750 marks.inner.insert(name.to_string(), MarkValue::Boolean);
751 }
752 SpanValue::Set(s) => {
753 marks
754 .inner
755 .insert(name.to_string(), MarkValue::Value(s.clone()));
756 }
757 SpanValue::Off | SpanValue::Unset => {}
759 }
760 }
761 (ch, marks)
762 }))
763 }
764
765 #[must_use]
767 pub fn render(&self) -> Vec<(char, MarkSet)> {
768 self.iter_with_marks().collect()
769 }
770
771 fn insert_span_sorted(&mut self, span: Span) {
776 let pos = self
777 .spans
778 .binary_search_by_key(&span.id, |s| s.id)
779 .unwrap_or_else(|e| e);
780 self.spans.insert(pos, span);
781 }
782
783 fn resolve_anchor(
795 &self,
796 anchor: &Anchor,
797 positions: &HashMap<OpId, usize>,
798 len: usize,
799 _as_end: bool,
800 ) -> Option<usize> {
801 match anchor {
802 Anchor::Start => Some(0),
803 Anchor::End => Some(len),
804 Anchor::Char(id, side) => {
805 let &pos = positions.get(id)?;
806 let visible = self.chars.is_visible(*id).unwrap_or(false);
807 Some(match (side, visible) {
808 (AnchorSide::After, true) => pos + 1,
810 _ => pos,
814 })
815 }
816 }
817 }
818}
819
820#[derive(Clone, Debug, PartialEq, Eq)]
830#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
831#[cfg_attr(feature = "serde", serde(untagged))]
832pub enum AttrValue {
833 Bool(bool),
835 String(String),
837}
838
839#[derive(Clone, Debug, PartialEq, Eq)]
844#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
845pub struct DeltaOp {
846 pub insert: String,
848 #[cfg_attr(
850 feature = "serde",
851 serde(default, skip_serializing_if = "std::collections::BTreeMap::is_empty")
852 )]
853 pub attributes: std::collections::BTreeMap<String, AttrValue>,
854}
855
856impl Text {
857 #[must_use]
868 pub fn to_delta(&self) -> Vec<DeltaOp> {
869 let mut deltas: Vec<DeltaOp> = Vec::new();
870 let mut current_text = String::new();
871 let mut current_attrs: std::collections::BTreeMap<String, AttrValue> =
872 std::collections::BTreeMap::new();
873
874 for (ch, marks) in self.iter_with_marks() {
875 let mut new_attrs: std::collections::BTreeMap<String, AttrValue> =
876 std::collections::BTreeMap::new();
877 for (name, val) in marks.iter_with_values() {
878 let attr = match val {
879 MarkValue::Boolean => AttrValue::Bool(true),
880 MarkValue::Value(s) => AttrValue::String(s.clone()),
881 };
882 new_attrs.insert(name.to_string(), attr);
883 }
884
885 if new_attrs != current_attrs && !current_text.is_empty() {
886 deltas.push(DeltaOp {
887 insert: std::mem::take(&mut current_text),
888 attributes: std::mem::take(&mut current_attrs),
889 });
890 }
891 current_text.push(ch);
892 current_attrs = new_attrs;
893 }
894 if !current_text.is_empty() {
895 deltas.push(DeltaOp {
896 insert: current_text,
897 attributes: current_attrs,
898 });
899 }
900 deltas
901 }
902
903 pub fn from_delta(replica: ReplicaId, deltas: &[DeltaOp]) -> Self {
908 let mut text = Self::new(replica);
909 for op in deltas {
910 let start = text.len();
911 for ch in op.insert.chars() {
912 text.insert(text.len(), ch);
913 }
914 let end = text.len();
915 if end == start {
916 continue;
917 }
918 for (name, attr) in &op.attributes {
919 let value = match attr {
920 AttrValue::Bool(true) => SpanValue::On,
921 AttrValue::Bool(false) => SpanValue::Off,
922 AttrValue::String(s) => SpanValue::Set(s.clone()),
923 };
924 text.set_mark_with_rule(start..end, name, value, ExpandRule::None);
925 }
926 }
927 text
928 }
929}
930
931impl Default for Text {
932 fn default() -> Self {
933 Self::new(0)
934 }
935}
936
937impl std::fmt::Display for Text {
939 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
940 for c in self.chars.iter() {
941 f.write_str(c.encode_utf8(&mut [0u8; 4]))?;
942 }
943 Ok(())
944 }
945}
946
947#[cfg(test)]
949impl Text {
950 fn chars_for_test(&self) -> &List<char> {
951 &self.chars
952 }
953}
954
955#[cfg(test)]
960mod tests {
961 use super::*;
962
963 fn marks_at(text: &Text, pos: usize) -> Vec<String> {
964 text.iter_with_marks()
965 .nth(pos)
966 .map(|(_, m)| m.iter().map(String::from).collect())
967 .unwrap_or_default()
968 }
969
970 #[test]
971 fn empty_doc() {
972 let t = Text::new(1);
973 assert!(t.is_empty());
974 assert_eq!(t.to_string(), "");
975 assert_eq!(t.iter_with_marks().count(), 0);
976 }
977
978 #[test]
979 fn insert_and_render() {
980 let mut t = Text::new(1);
981 t.insert_str(0, "Hello");
982 assert_eq!(t.len(), 5);
983 assert_eq!(t.to_string(), "Hello");
984 for (_, marks) in t.iter_with_marks() {
985 assert!(marks.is_empty());
986 }
987 }
988
989 #[test]
990 fn bold_a_range() {
991 let mut t = Text::new(1);
992 t.insert_str(0, "Hello, world!");
993 t.set_mark(0..5, "bold", true);
994 assert_eq!(marks_at(&t, 0), vec!["bold"]);
995 assert_eq!(marks_at(&t, 4), vec!["bold"]);
996 assert!(marks_at(&t, 5).is_empty());
998 }
999
1000 #[test]
1001 fn unbold_a_range_via_off_op() {
1002 let mut t = Text::new(1);
1003 t.insert_str(0, "Hello, world!");
1004 t.set_mark(0..5, "bold", true);
1005 t.set_mark(0..5, "bold", false);
1007 assert!(marks_at(&t, 0).is_empty());
1008 assert!(marks_at(&t, 4).is_empty());
1009 }
1010
1011 #[test]
1012 fn typing_in_middle_of_bold_range_inherits() {
1013 let mut t = Text::new(1);
1014 t.insert_str(0, "Hello");
1015 t.set_mark(0..5, "bold", true);
1016 t.insert(2, 'X');
1018 assert_eq!(t.to_string(), "HeXllo");
1021 assert_eq!(marks_at(&t, 2), vec!["bold"], "X should inherit bold");
1022 assert_eq!(marks_at(&t, 5), vec!["bold"]);
1026 }
1027
1028 #[test]
1029 fn typing_after_span_does_not_inherit() {
1030 let mut t = Text::new(1);
1031 t.insert_str(0, "Hello");
1032 t.set_mark(0..5, "bold", true);
1033 t.insert(5, '!');
1035 assert_eq!(t.to_string(), "Hello!");
1036 assert!(marks_at(&t, 5).is_empty());
1039 }
1040
1041 #[test]
1042 fn concurrent_mark_higher_op_wins() {
1043 let mut alice = Text::new(1);
1044 let mut bob = Text::new(2);
1045 alice.insert_str(0, "Hello");
1046 bob.merge(&alice);
1047
1048 alice.set_mark(0..5, "bold", true);
1050 bob.set_mark(0..5, "bold", false);
1051
1052 let mut a2 = alice.clone();
1053 a2.merge(&bob);
1054 let mut b2 = bob.clone();
1055 b2.merge(&alice);
1056
1057 let render_a: Vec<bool> = a2
1059 .iter_with_marks()
1060 .map(|(_, m)| m.contains("bold"))
1061 .collect();
1062 let render_b: Vec<bool> = b2
1063 .iter_with_marks()
1064 .map(|(_, m)| m.contains("bold"))
1065 .collect();
1066 assert_eq!(render_a, render_b);
1067 assert_eq!(render_a, vec![false; 5]);
1069 }
1070
1071 #[test]
1072 fn idempotent_apply() {
1073 let mut a = Text::new(1);
1074 a.insert_str(0, "Hi");
1075 a.set_mark(0..2, "italic", true);
1076
1077 let mut b = Text::new(2);
1078 for op in a.ops().to_vec() {
1079 b.apply(op.clone()).unwrap();
1080 b.apply(op).unwrap(); }
1082 assert_eq!(b.to_string(), "Hi");
1083 assert_eq!(marks_at(&b, 0), vec!["italic"]);
1084 assert_eq!(marks_at(&b, 1), vec!["italic"]);
1085 }
1086
1087 #[test]
1088 fn deleting_marked_text() {
1089 let mut t = Text::new(1);
1090 t.insert_str(0, "Hello");
1091 t.set_mark(0..5, "bold", true);
1092 t.delete(0); assert_eq!(t.to_string(), "ello");
1094 for i in 0..t.len() {
1097 assert_eq!(marks_at(&t, i), vec!["bold"]);
1098 }
1099 }
1100
1101 #[test]
1102 fn multiple_marks_layer() {
1103 let mut t = Text::new(1);
1104 t.insert_str(0, "Hello");
1105 t.set_mark(0..3, "bold", true);
1106 t.set_mark(2..5, "italic", true);
1107 assert_eq!(marks_at(&t, 0), vec!["bold"]);
1109 let m2 = marks_at(&t, 2);
1110 assert!(m2.contains(&"bold".to_string()));
1111 assert!(m2.contains(&"italic".to_string()));
1112 assert_eq!(marks_at(&t, 4), vec!["italic"]);
1113 }
1114
1115 #[test]
1120 fn valued_mark_set_and_read() {
1121 let mut t = Text::new(1);
1122 t.insert_str(0, "Click here for the link");
1123 t.set_value_mark(6..10, "href", Some("https://example.com"));
1124 let row = t.iter_with_marks().nth(6).unwrap();
1125 assert_eq!(row.0, 'h');
1126 assert_eq!(row.1.value_of("href"), Some("https://example.com"));
1127 let outside = t.iter_with_marks().next().unwrap();
1129 assert_eq!(outside.1.value_of("href"), None);
1130 }
1131
1132 #[test]
1133 fn valued_mark_unset() {
1134 let mut t = Text::new(1);
1135 t.insert_str(0, "Hello");
1136 t.set_value_mark(0..5, "color", Some("red"));
1137 t.set_value_mark(0..5, "color", None); for (_, m) in t.iter_with_marks() {
1139 assert_eq!(m.value_of("color"), None);
1140 assert!(!m.contains("color"));
1141 }
1142 }
1143
1144 #[test]
1145 fn valued_mark_concurrent_resolution() {
1146 let mut a = Text::new(1);
1147 let mut b = Text::new(2);
1148 a.insert_str(0, "Link");
1149 b.merge(&a);
1150
1151 a.set_value_mark(0..4, "href", Some("https://alice.example/"));
1152 b.set_value_mark(0..4, "href", Some("https://bob.example/"));
1153
1154 let mut a2 = a.clone();
1155 a2.merge(&b);
1156 let mut b2 = b.clone();
1157 b2.merge(&a);
1158
1159 let render_a: Vec<Option<String>> = a2
1160 .iter_with_marks()
1161 .map(|(_, m)| m.value_of("href").map(String::from))
1162 .collect();
1163 let render_b: Vec<Option<String>> = b2
1164 .iter_with_marks()
1165 .map(|(_, m)| m.value_of("href").map(String::from))
1166 .collect();
1167 assert_eq!(render_a, render_b);
1168 assert_eq!(render_a[0].as_deref(), Some("https://bob.example/"));
1170 }
1171
1172 #[test]
1173 fn boolean_and_valued_marks_coexist() {
1174 let mut t = Text::new(1);
1175 t.insert_str(0, "Hello");
1176 t.set_mark(0..5, "bold", true);
1177 t.set_value_mark(0..5, "color", Some("blue"));
1178 let row = t.iter_with_marks().next().unwrap();
1179 assert!(row.1.contains("bold"));
1180 assert!(row.1.contains("color"));
1181 assert_eq!(row.1.value_of("color"), Some("blue"));
1182 assert_eq!(row.1.value_of("bold"), None);
1184 let booleans: Vec<&str> = row.1.iter_booleans().collect();
1186 let values: Vec<(&str, &str)> = row.1.iter_values().collect();
1187 assert_eq!(booleans, vec!["bold"]);
1188 assert_eq!(values, vec![("color", "blue")]);
1189 }
1190
1191 fn marks_at_with_set(text: &Text, pos: usize) -> Vec<String> {
1196 text.iter_with_marks()
1197 .nth(pos)
1198 .map(|(_, m)| m.iter().map(String::from).collect())
1199 .unwrap_or_default()
1200 }
1201
1202 #[test]
1203 fn expand_right_extends_to_new_typing() {
1204 let mut t = Text::new(1);
1205 t.insert_str(0, "Hi");
1206 t.set_mark_with_rule(0..2, "bold", SpanValue::On, ExpandRule::Right);
1208 t.insert(2, '!');
1210 t.insert(3, '?');
1211 assert_eq!(t.as_string(), "Hi!?");
1212 for i in 0..t.len() {
1213 assert_eq!(marks_at_with_set(&t, i), vec!["bold"], "pos {i}");
1214 }
1215 }
1216
1217 #[test]
1218 fn expand_right_does_not_extend_left() {
1219 let mut t = Text::new(1);
1220 t.insert_str(0, "Hi");
1221 t.set_mark_with_rule(0..2, "bold", SpanValue::On, ExpandRule::Right);
1222 t.insert(0, 'X');
1224 assert_eq!(t.as_string(), "XHi");
1225 assert!(marks_at_with_set(&t, 0).is_empty());
1226 assert_eq!(marks_at_with_set(&t, 1), vec!["bold"]);
1227 }
1228
1229 #[test]
1230 fn expand_left_extends_to_new_typing_at_front() {
1231 let mut t = Text::new(1);
1232 t.insert_str(0, "Hi");
1233 t.set_mark_with_rule(0..2, "bold", SpanValue::On, ExpandRule::Left);
1234 t.insert(0, 'X');
1236 assert_eq!(t.as_string(), "XHi");
1237 assert_eq!(marks_at_with_set(&t, 0), vec!["bold"]);
1238 }
1239
1240 #[test]
1241 fn expand_both_extends_both_ways() {
1242 let mut t = Text::new(1);
1243 t.insert_str(0, "ab");
1244 t.set_mark_with_rule(0..2, "bold", SpanValue::On, ExpandRule::Both);
1245 t.insert(0, 'X'); t.insert(t.len(), 'Y'); assert_eq!(t.as_string(), "XabY");
1248 for i in 0..t.len() {
1249 assert_eq!(marks_at_with_set(&t, i), vec!["bold"], "pos {i}");
1250 }
1251 }
1252
1253 #[test]
1254 fn anchor_expand_right() {
1255 let mut t = Text::new(1);
1256 t.insert_str(0, "Hi");
1257 let start_id = t.chars_for_test().id_at(0).unwrap();
1262 let end_id = t.chars_for_test().id_at(1).unwrap();
1263 let _ = (start_id, end_id);
1274 t.set_mark(0..2, "bold", true);
1275 t.insert(2, '!');
1276 assert!(marks_at(&t, 2).is_empty());
1278 }
1279
1280 #[test]
1285 fn grapheme_count_for_emoji_family() {
1286 let mut t = Text::new(1);
1287 t.insert_str(0, "Hi 👨👩👧!");
1289 assert_eq!(t.len(), 9); assert_eq!(t.grapheme_count(), 5); }
1292
1293 #[test]
1294 fn grapheme_pos_conversion_round_trip() {
1295 let mut t = Text::new(1);
1296 t.insert_str(0, "a👨👩👧b");
1297 assert_eq!(t.grapheme_count(), 3);
1299 assert_eq!(t.grapheme_to_char_pos(0), 0);
1300 assert_eq!(t.grapheme_to_char_pos(1), 1);
1301 assert_eq!(t.grapheme_to_char_pos(2), 6);
1302 assert_eq!(t.char_to_grapheme_pos(0), 0);
1303 assert_eq!(t.char_to_grapheme_pos(1), 1);
1304 assert_eq!(t.char_to_grapheme_pos(6), 2);
1305 }
1306
1307 #[test]
1308 fn insert_grapheme_str_at_grapheme_position() {
1309 let mut t = Text::new(1);
1310 t.insert_str(0, "a👨👩👧b");
1311 t.insert_grapheme_str(2, "Z");
1313 assert_eq!(t.as_string(), "a👨👩👧Zb");
1314 assert_eq!(t.grapheme_count(), 4);
1315 }
1316
1317 #[test]
1318 fn delete_grapheme_removes_whole_emoji() {
1319 let mut t = Text::new(1);
1320 t.insert_str(0, "a👨👩👧b");
1321 t.delete_grapheme(1);
1323 assert_eq!(t.as_string(), "ab");
1324 assert_eq!(t.grapheme_count(), 2);
1325 }
1326
1327 #[test]
1332 fn delta_round_trip_plain_text() {
1333 let mut t = Text::new(1);
1334 t.insert_str(0, "Hello, world!");
1335 let delta = t.to_delta();
1336 assert_eq!(delta.len(), 1);
1337 assert_eq!(delta[0].insert, "Hello, world!");
1338 assert!(delta[0].attributes.is_empty());
1339
1340 let restored = Text::from_delta(2, &delta);
1341 assert_eq!(restored.as_string(), "Hello, world!");
1342 }
1343
1344 #[test]
1345 fn delta_round_trip_with_marks() {
1346 let mut t = Text::new(1);
1347 t.insert_str(0, "Hello world");
1348 t.set_mark(0..5, "bold", true);
1349 t.set_mark(6..11, "italic", true);
1350
1351 let delta = t.to_delta();
1352 assert_eq!(delta.len(), 3);
1354 assert_eq!(delta[0].insert, "Hello");
1355 assert_eq!(delta[0].attributes.len(), 1);
1356 assert!(matches!(
1357 delta[0].attributes.get("bold"),
1358 Some(AttrValue::Bool(true))
1359 ));
1360 assert_eq!(delta[1].insert, " ");
1361 assert!(delta[1].attributes.is_empty());
1362 assert_eq!(delta[2].insert, "world");
1363 assert!(matches!(
1364 delta[2].attributes.get("italic"),
1365 Some(AttrValue::Bool(true))
1366 ));
1367
1368 let restored = Text::from_delta(2, &delta);
1370 let delta2 = restored.to_delta();
1371 assert_eq!(delta, delta2);
1372 assert_eq!(restored.as_string(), "Hello world");
1373 }
1374
1375 #[test]
1376 fn delta_with_valued_marks() {
1377 let mut t = Text::new(1);
1378 t.insert_str(0, "Click here");
1379 t.set_value_mark(6..10, "href", Some("https://example.com"));
1380
1381 let delta = t.to_delta();
1382 let last = delta.last().unwrap();
1383 assert_eq!(last.insert, "here");
1384 assert_eq!(
1385 last.attributes.get("href"),
1386 Some(&AttrValue::String("https://example.com".to_string()))
1387 );
1388
1389 let restored = Text::from_delta(2, &delta);
1390 let restored_delta = restored.to_delta();
1391 assert_eq!(delta, restored_delta);
1392 }
1393
1394 #[test]
1395 fn delta_overlapping_marks() {
1396 let mut t = Text::new(1);
1397 t.insert_str(0, "Hello");
1398 t.set_mark(0..5, "bold", true);
1399 t.set_mark(2..5, "italic", true);
1400 let delta = t.to_delta();
1404 assert_eq!(delta.len(), 2);
1405 assert_eq!(delta[0].insert, "He");
1406 assert_eq!(delta[0].attributes.len(), 1);
1407 assert_eq!(delta[1].insert, "llo");
1408 assert_eq!(delta[1].attributes.len(), 2);
1409 }
1410}