1pub(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#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
34pub struct RichtextSpan {
35 pub text: StringSlice,
36 pub attributes: StyleMeta,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
41pub struct Style {
42 pub key: InternalString,
43 pub data: LoroValue,
44}
45
46#[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#[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 .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#[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 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 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 #[inline(always)]
229 pub const fn expand_before(self) -> bool {
230 self.data & EXPAND_BEFORE_MASK != 0
231 }
232
233 #[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 #[inline]
250 pub fn prefer_insert_before(self, anchor_type: AnchorType) -> bool {
251 match anchor_type {
252 AnchorType::Start => {
253 !self.expand_before()
255 }
256 AnchorType::End => {
257 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}