pub(crate) mod config;
mod fugue_span;
mod query_by_len;
pub(crate) mod richtext_state;
mod style_range_map;
mod tracker;
use crate::{change::Lamport, delta::StyleMeta, utils::string_slice::StringSlice, InternalString};
use fugue_span::*;
use loro_common::{Counter, IdLp, LoroValue, PeerID, ID};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
pub(crate) use fugue_span::{RichtextChunk, RichtextChunkValue};
pub(crate) use richtext_state::RichtextState;
pub(crate) use style_range_map::Styles;
pub(crate) use tracker::{CrdtRopeDelta, Tracker as RichtextTracker};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct RichtextSpan {
pub text: StringSlice,
pub attributes: StyleMeta,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct Style {
pub key: InternalString,
pub data: LoroValue,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct StyleOp {
pub(crate) lamport: Lamport,
pub(crate) peer: PeerID,
pub(crate) cnt: Counter,
pub(crate) key: InternalString,
pub(crate) value: LoroValue,
pub(crate) info: TextStyleInfoFlag,
}
#[derive(Debug, Hash, Eq, PartialEq, Clone, Serialize, Deserialize)]
pub(crate) enum StyleKey {
Key(InternalString),
}
impl StyleKey {
pub fn key(&self) -> &InternalString {
match self {
Self::Key(key) => key,
}
}
}
impl StyleOp {
pub fn to_style(&self) -> Style {
Style {
key: self.key.clone(),
data: self.value.clone(),
}
}
pub fn to_value(&self) -> LoroValue {
self.value.clone()
}
pub(crate) fn get_style_key(&self) -> StyleKey {
StyleKey::Key(self.key.clone())
}
#[cfg(test)]
pub fn new_for_test(n: isize, key: &str, value: LoroValue, info: TextStyleInfoFlag) -> Self {
Self {
lamport: n as Lamport,
peer: n as PeerID,
cnt: n as Counter,
key: key.to_string().into(),
value,
info,
}
}
#[inline(always)]
pub fn id(&self) -> ID {
ID::new(self.peer, self.cnt)
}
pub fn idlp(&self) -> IdLp {
IdLp::new(self.peer, self.lamport)
}
}
impl PartialOrd for StyleOp {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for StyleOp {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.lamport
.cmp(&other.lamport)
.then(self.peer.cmp(&other.peer))
}
}
#[derive(Default, Clone, Copy, Eq, PartialEq, Hash, serde::Serialize, serde::Deserialize)]
pub struct TextStyleInfoFlag {
data: u8,
}
impl Debug for TextStyleInfoFlag {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TextStyleInfo")
.field("data", &format!("{:#010b}", self.data))
.field("expand_before", &self.expand_before())
.field("expand_after", &self.expand_after())
.finish()
}
}
const EXPAND_BEFORE_MASK: u8 = 0b0000_0010;
const EXPAND_AFTER_MASK: u8 = 0b0000_0100;
const ALIVE_MASK: u8 = 0b1000_0000;
#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
pub enum ExpandType {
Before,
After,
Both,
None,
}
#[derive(
Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, serde::Serialize, serde::Deserialize,
)]
pub enum AnchorType {
Start,
End,
}
impl ExpandType {
#[inline(always)]
pub const fn expand_before(&self) -> bool {
matches!(self, ExpandType::Before | ExpandType::Both)
}
#[inline(always)]
pub const fn expand_after(&self) -> bool {
matches!(self, ExpandType::After | ExpandType::Both)
}
pub fn try_from_str(s: &str) -> Option<Self> {
match s {
"before" => Some(ExpandType::Before),
"after" => Some(ExpandType::After),
"both" => Some(ExpandType::Both),
"none" => Some(ExpandType::None),
_ => None,
}
}
pub const fn reverse(self) -> Self {
match self {
ExpandType::Before => ExpandType::Before,
ExpandType::After => ExpandType::After,
ExpandType::Both => ExpandType::None,
ExpandType::None => ExpandType::Both,
}
}
}
impl TextStyleInfoFlag {
#[inline(always)]
pub const fn expand_before(self) -> bool {
self.data & EXPAND_BEFORE_MASK != 0
}
#[inline(always)]
pub const fn expand_after(self) -> bool {
self.data & EXPAND_AFTER_MASK != 0
}
pub const fn expand_type(self) -> ExpandType {
match (self.expand_before(), self.expand_after()) {
(true, true) => ExpandType::Both,
(true, false) => ExpandType::Before,
(false, true) => ExpandType::After,
(false, false) => ExpandType::None,
}
}
#[inline]
pub fn prefer_insert_before(self, anchor_type: AnchorType) -> bool {
match anchor_type {
AnchorType::Start => {
!self.expand_before()
}
AnchorType::End => {
self.expand_after()
}
}
}
pub const fn new(expand_type: ExpandType) -> Self {
let mut data = ALIVE_MASK;
if expand_type.expand_before() {
data |= EXPAND_BEFORE_MASK;
}
if expand_type.expand_after() {
data |= EXPAND_AFTER_MASK;
}
TextStyleInfoFlag { data }
}
#[inline(always)]
pub const fn to_delete(self) -> Self {
TextStyleInfoFlag::new(self.expand_type().reverse())
}
pub const BOLD: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::After);
pub const LINK: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::None);
pub const COMMENT: TextStyleInfoFlag = TextStyleInfoFlag::new(ExpandType::None);
pub const fn to_byte(&self) -> u8 {
self.data
}
pub const fn from_byte(data: u8) -> Self {
Self { data }
}
}
#[cfg(test)]
mod test {
#[test]
fn test() {}
}