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