loro_internal/container/
richtext.rs

1//! # Index
2//!
3//! There are several types of indexes:
4//!
5//! - Unicode index: the index of a unicode code point in the text.
6//! - Entity index: unicode index + style anchor index. Each unicode code point or style anchor is an entity.
7//! - Utf16 index
8//!
9//! In [crate::op::Op], we always use entity index to persist richtext ops.
10//!
11//! The users of this type can only operate on unicode index or utf16 index, but calculated entity index will be provided.
12
13pub(crate) mod config;
14mod fugue_span;
15pub(crate) mod richtext_state;
16pub(crate) mod str_slice;
17mod style_range_map;
18mod tracker;
19
20use crate::{change::Lamport, delta::StyleMeta, utils::string_slice::StringSlice, InternalString};
21use fugue_span::*;
22use loro_common::{Counter, IdFull, IdLp, LoroValue, PeerID, ID};
23use serde::{Deserialize, Serialize};
24use std::fmt::Debug;
25
26pub(crate) use fugue_span::{RichtextChunk, RichtextChunkValue};
27pub(crate) use richtext_state::RichtextState;
28pub(crate) use style_range_map::Styles;
29pub(crate) use tracker::{CrdtRopeDelta, Tracker as RichtextTracker};
30
31/// This is the data structure that represents a span of rich text.
32/// It's used to communicate with the frontend.
33#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
34pub struct RichtextSpan {
35    pub text: StringSlice,
36    pub attributes: StyleMeta,
37}
38
39/// This is used to communicate with the frontend.
40#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
41pub struct Style {
42    pub key: InternalString,
43    pub data: LoroValue,
44}
45
46// TODO: change visibility back to crate after #116 is done
47#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
48pub struct StyleOp {
49    pub(crate) lamport: Lamport,
50    pub(crate) peer: PeerID,
51    pub(crate) cnt: Counter,
52    pub(crate) key: InternalString,
53    pub(crate) value: LoroValue,
54    pub(crate) info: TextStyleInfoFlag,
55}
56
57#[derive(Debug, Hash, Eq, PartialEq, Clone, Serialize, Deserialize)]
58pub(crate) enum StyleKey {
59    Key(InternalString),
60}
61
62impl StyleKey {
63    pub fn key(&self) -> &InternalString {
64        match self {
65            Self::Key(key) => key,
66        }
67    }
68}
69
70impl StyleOp {
71    pub fn to_style(&self) -> Style {
72        Style {
73            key: self.key.clone(),
74            data: self.value.clone(),
75        }
76    }
77
78    pub fn to_value(&self) -> LoroValue {
79        self.value.clone()
80    }
81
82    pub(crate) fn get_style_key(&self) -> StyleKey {
83        StyleKey::Key(self.key.clone())
84    }
85
86    #[cfg(test)]
87    pub fn new_for_test(n: isize, key: &str, value: LoroValue, info: TextStyleInfoFlag) -> Self {
88        Self {
89            lamport: n as Lamport,
90            peer: n as PeerID,
91            cnt: n as Counter,
92            key: key.to_string().into(),
93            value,
94            info,
95        }
96    }
97
98    #[inline(always)]
99    pub fn id(&self) -> ID {
100        ID::new(self.peer, self.cnt)
101    }
102
103    pub fn idlp(&self) -> IdLp {
104        IdLp::new(self.peer, self.lamport)
105    }
106
107    pub fn id_full(&self) -> IdFull {
108        IdFull::new(self.peer, self.cnt, self.lamport)
109    }
110}
111
112impl PartialOrd for StyleOp {
113    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
114        Some(self.cmp(other))
115    }
116}
117
118impl Ord for StyleOp {
119    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
120        self.lamport
121            .cmp(&other.lamport)
122            .then(self.peer.cmp(&other.peer))
123    }
124}
125
126/// TODO: We can remove this type already
127///
128/// A compact representation of a rich text style config.
129///
130/// Note: we assume style with the same key has the same `Mergeable` and `isContainer` value.
131///
132/// - 0              (1st bit)
133/// - Expand Before  (2nd bit): when inserting new text before this style, whether the new text should inherit this style.
134/// - Expand After   (3rd bit): when inserting new text after  this style, whether the new text should inherit this style.
135/// - 0              (4th bit):
136/// - 0              (5th bit):
137/// - 0              (6th bit)
138/// - 0              (7th bit)
139/// - 0              (8th bit):
140#[derive(Default, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
141pub struct TextStyleInfoFlag {
142    data: u8,
143}
144
145impl Debug for TextStyleInfoFlag {
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        f.debug_struct("TextStyleInfo")
148            // write data in binary format
149            .field("data", &format!("{:#010b}", self.data))
150            .field("expand_before", &self.expand_before())
151            .field("expand_after", &self.expand_after())
152            .finish()
153    }
154}
155
156const EXPAND_BEFORE_MASK: u8 = 0b0000_0010;
157const EXPAND_AFTER_MASK: u8 = 0b0000_0100;
158const ALIVE_MASK: u8 = 0b1000_0000;
159
160/// Whether to expand the style when inserting new text around it.
161///
162/// - Before: when inserting new text before this style, the new text should inherit this style.
163/// - After: when inserting new text after this style, the new text should inherit this style.
164/// - Both: when inserting new text before or after this style, the new text should inherit this style.
165/// - None: when inserting new text before or after this style, the new text should **not** inherit this style.
166#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
167pub enum ExpandType {
168    Before,
169    After,
170    Both,
171    None,
172}
173
174#[derive(
175    Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, serde::Serialize, serde::Deserialize,
176)]
177pub enum AnchorType {
178    Start,
179    End,
180}
181
182impl ExpandType {
183    #[inline(always)]
184    pub const fn expand_before(&self) -> bool {
185        matches!(self, ExpandType::Before | ExpandType::Both)
186    }
187
188    #[inline(always)]
189    pub const fn expand_after(&self) -> bool {
190        matches!(self, ExpandType::After | ExpandType::Both)
191    }
192
193    /// 'before'|'after'|'both'|'none'
194    pub fn try_from_str(s: &str) -> Option<Self> {
195        match s {
196            "before" => Some(ExpandType::Before),
197            "after" => Some(ExpandType::After),
198            "both" => Some(ExpandType::Both),
199            "none" => Some(ExpandType::None),
200            _ => None,
201        }
202    }
203
204    /// Toggle expand type between for deletion and for creation
205    ///
206    /// For a style that expand after, when we delete the style, we need to have another style that expands after to nullify it,
207    /// so that the expand behavior is not changed.
208    ///
209    /// Before  -> Before
210    /// After   -> After
211    /// Both    -> None
212    /// None    -> Both
213    ///
214    /// Because the creation of text styles and the deletion of the text styles have reversed expand type.
215    /// This method is useful to convert between the two
216    pub const fn reverse(self) -> Self {
217        match self {
218            ExpandType::Before => ExpandType::Before,
219            ExpandType::After => ExpandType::After,
220            ExpandType::Both => ExpandType::None,
221            ExpandType::None => ExpandType::Both,
222        }
223    }
224}
225
226impl TextStyleInfoFlag {
227    /// When inserting new text around this style, prefer inserting after it.
228    #[inline(always)]
229    pub const fn expand_before(self) -> bool {
230        self.data & EXPAND_BEFORE_MASK != 0
231    }
232
233    /// When inserting new text around this style, prefer inserting before it.
234    #[inline(always)]
235    pub const fn expand_after(self) -> bool {
236        self.data & EXPAND_AFTER_MASK != 0
237    }
238
239    pub const fn expand_type(self) -> ExpandType {
240        match (self.expand_before(), self.expand_after()) {
241            (true, true) => ExpandType::Both,
242            (true, false) => ExpandType::Before,
243            (false, true) => ExpandType::After,
244            (false, false) => ExpandType::None,
245        }
246    }
247
248    /// This method tells that when we can insert text before/after this style anchor, whether we insert the new text before the anchor.
249    #[inline]
250    pub fn prefer_insert_before(self, anchor_type: AnchorType) -> bool {
251        match anchor_type {
252            AnchorType::Start => {
253                // If we need to expand the style, the new text should be inserted **after** the start anchor
254                !self.expand_before()
255            }
256            AnchorType::End => {
257                // If we need to expand the style, the new text should be inserted **before** the end anchor
258                self.expand_after()
259            }
260        }
261    }
262
263    pub const fn new(expand_type: ExpandType) -> Self {
264        let mut data = ALIVE_MASK;
265        if expand_type.expand_before() {
266            data |= EXPAND_BEFORE_MASK;
267        }
268        if expand_type.expand_after() {
269            data |= EXPAND_AFTER_MASK;
270        }
271
272        TextStyleInfoFlag { data }
273    }
274
275    #[inline(always)]
276    pub const fn to_delete(self) -> Self {
277        TextStyleInfoFlag::new(self.expand_type().reverse())
278    }
279
280    pub const BOLD: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::After);
281    pub const LINK: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::None);
282    pub const COMMENT: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::None);
283
284    pub const fn to_byte(&self) -> u8 {
285        self.data
286    }
287
288    pub const fn from_byte(data: u8) -> Self {
289        Self { data }
290    }
291}
292
293#[cfg(test)]
294mod test {
295
296    #[test]
297    fn test() {}
298}