Skip to main content

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