Documentation
¶
Overview ¶
Package richtext implements bounded, inline formatted collaborative text.
It composes a private run-v2 text RGA with per-position LWW attribute registers. The package deliberately accepts opaque string attributes rather than HTML, CSS, or executable values; rendering and attribute schemas remain application-owned.
Index ¶
- Constants
- Variables
- type AttributeChange
- type Attributes
- type Block
- type BlockFormat
- type Delta
- type Document
- func New(replicaID string) (*Document, error)
- func NewFromClock(state clock.State) (*Document, error)
- func NewFromClockWithOptions(state clock.State, options Options) (*Document, error)
- func NewFromSnapshot(saved snapshot.Snapshot) (*Document, error)
- func NewFromSnapshotWithOptions(saved snapshot.Snapshot, options Options, limits frame.DecoderLimits) (*Document, error)
- func NewWithOptions(replicaID string, options Options) (*Document, error)
- func (d *Document) AnchorAt(offset int) (text.Anchor, error)
- func (d *Document) AnchorRangeAt(start, end int) (text.AnchorRange, error)
- func (d *Document) ApplyDelta(delta Delta) error
- func (d *Document) ApplyDeltaWithLimits(delta Delta, limits frame.DecoderLimits) error
- func (d *Document) ApplyEditorDelta(operations []EditorOperation) (Delta, error)
- func (d *Document) ApplyEditorDeltaWithLimits(operations []EditorOperation, limits frame.DecoderLimits) (Delta, error)
- func (d *Document) AttributesAt(offset int) (Attributes, bool)
- func (d *Document) BlockFormatAt(offset int) (BlockFormat, bool)
- func (d *Document) Blocks() []Block
- func (d *Document) ClearBlocks(offset, count int) (Delta, error)
- func (d *Document) ClearBlocksAnchored(start, end text.Anchor) (Delta, error)
- func (d *Document) ClockState() clock.State
- func (d *Document) CompactEligibleTombstones(tags []crdt.Tag) (int, error)
- func (d *Document) CompactTombstones(tags []crdt.Tag) (int, error)
- func (d *Document) Delete(offset, count int) (Delta, error)
- func (d *Document) DeleteWithLimits(offset, count int, limits frame.DecoderLimits) (Delta, error)
- func (d *Document) EmbedAt(offset int) (Embed, bool)
- func (d *Document) Format(offset, count int, changes []AttributeChange) (Delta, error)
- func (d *Document) FormatAnchored(start, end text.Anchor, changes []AttributeChange) (Delta, error)
- func (d *Document) FormatAnchoredWithLimits(start, end text.Anchor, changes []AttributeChange, limits frame.DecoderLimits) (Delta, error)
- func (d *Document) FormatBlocks(offset, count int, format BlockFormat) (Delta, error)
- func (d *Document) FormatBlocksAnchored(start, end text.Anchor, format BlockFormat) (Delta, error)
- func (d *Document) FormatWithLimits(offset, count int, changes []AttributeChange, limits frame.DecoderLimits) (Delta, error)
- func (d *Document) Insert(offset int, value string) (Delta, error)
- func (d *Document) InsertEmbed(offset int, embed Embed) (Delta, error)
- func (d *Document) InsertWithAttributes(offset int, value string, attributes Attributes) (Delta, error)
- func (d *Document) InsertWithAttributesWithLimits(offset int, value string, attributes Attributes, limits frame.DecoderLimits) (Delta, error)
- func (d *Document) InsertWithBlockFormat(offset int, value string, attributes Attributes, format BlockFormat) (Delta, error)
- func (d *Document) Len() int
- func (d *Document) MarshalBinary() ([]byte, error)
- func (d *Document) MarshalBinaryWithLimits(limits frame.DecoderLimits) ([]byte, error)
- func (d *Document) MarshalJSON() ([]byte, error)
- func (d *Document) Merge(other *Document) error
- func (d *Document) ResolveAnchor(anchor text.Anchor) (int, error)
- func (d *Document) ResolveAnchorRange(anchors text.AnchorRange) (start, end int, err error)
- func (d *Document) SetBold(offset, count int, enabled bool) (Delta, error)
- func (d *Document) SetItalic(offset, count int, enabled bool) (Delta, error)
- func (d *Document) SnapshotCurrentState() (snapshot.Snapshot, error)
- func (d *Document) SnapshotCurrentStateWithLimits(limits frame.DecoderLimits) (snapshot.Snapshot, error)
- func (d *Document) Spans() []Span
- func (d *Document) State() crdt.StateSnapshot
- func (d *Document) String() string
- func (d *Document) TombstoneTags() []crdt.Tag
- func (d *Document) UnmarshalBinary(data []byte) error
- func (d *Document) UnmarshalBinaryWithLimits(data []byte, limits frame.DecoderLimits) error
- type EditorOperation
- type Embed
- type Options
- type Span
Constants ¶
const ( // SemanticSchemaID identifies the optional, application-negotiated schema // implemented by this adapter. It does not alter rich-text v1 framing. SemanticSchemaID = "github.com/DarkInno/crdt/richtext/semantic/v1" // ObjectReplacementCharacter occupies exactly one RGA position for an // embedded application object. Renderers must not treat its JSON as markup. ObjectReplacementCharacter = "\uFFFC" AttributeBold = "rt.bold" AttributeItalic = "rt.italic" AttributeEmbedKind = "rt.embed.kind" AttributeEmbedData = "rt.embed.data" AttributeBlock = "rt.block" )
const ( // SemanticsVersion identifies the rich-text inline-formatting contract. // It must match the value negotiated in a replica manifest. SemanticsVersion uint64 = 1 )
Variables ¶
var ( ErrNilDocument = errors.New("richtext: nil document") ErrInvalidAttribute = errors.New("richtext: invalid attribute") ErrInvalidDelta = errors.New("richtext: invalid delta") ErrTagConflict = errors.New("richtext: conflicting attribute for one tag") ErrResourceLimit = errors.New("richtext: resource limit exceeded") // ErrUnsafeCompaction means a rich-text tombstone cannot be retired without // changing retained text structure. Attribute-only tombstones may compact // when they are part of the exact-acknowledged batch, but text positions // still follow the RGA leaf-before-parent rule. ErrUnsafeCompaction = errors.New("richtext: unsafe tombstone compaction") )
var ErrInvalidSemantic = errors.New("richtext: invalid semantic formatting")
Functions ¶
This section is empty.
Types ¶
type AttributeChange ¶
AttributeChange describes one formatting assignment or retained removal. Remove must be true with an empty Value to make deletion unambiguous.
type Attributes ¶
Attributes is the presentation-safe view of one span's live attributes. Values are opaque UTF-8 strings. Their meaning, validation, and rendering policy are owned by the application.
type Block ¶ added in v1.0.25
type Block struct {
Text string
Format BlockFormat
Formatted bool
}
Block is one newline-delimited presentation block. Formatted is true only when every current position in the block carries the same valid rt.block value. Concurrent conflicting block edits therefore remain observable instead of being silently presented as one arbitrary block type.
type BlockFormat ¶ added in v1.0.25
BlockFormat is a paragraph-level presentation marker. A block format is applied to every existing rune in touched paragraphs so its CRDT lifetime follows the existing exact-position/LWW model. Editors must explicitly pass block attributes when inserting new text; no hidden inheritance is applied.
type Delta ¶
type Delta struct {
// contains filtered or unexported fields
}
Delta is an opaque, canonical rich-text delta. It may contain a nested run-v2 RGA delta, formatting operations, or both.
func UnmarshalDelta ¶
UnmarshalDelta decodes one bounded canonical rich-text delta frame.
func UnmarshalDeltaWithLimits ¶
func UnmarshalDeltaWithLimits(data []byte, limits frame.DecoderLimits) (Delta, error)
UnmarshalDeltaWithLimits decodes one bounded canonical rich-text delta frame. It rejects wrong nested types, non-canonical ordering, and any trailing payload before returning a usable delta.
func (Delta) MarshalBinary ¶
MarshalBinary returns one canonical rich-text delta frame.
func (Delta) MarshalBinaryWithLimits ¶
func (d Delta) MarshalBinaryWithLimits(limits frame.DecoderLimits) ([]byte, error)
MarshalBinaryWithLimits returns one canonical rich-text delta while applying caller-selected bounds before allocating an outer frame payload.
func (Delta) MarshalJSON ¶
MarshalJSON returns a diagnostic summary for one delta and never includes text content, attributes, tags, or frame bytes.
type Document ¶
type Document struct {
// contains filtered or unexported fields
}
Document is a concurrent-safe, inline rich-text CRDT. Its RGA is private so compound text-and-format deltas cannot be bypassed by a caller mutating the text substrate independently.
func NewFromClock ¶
NewFromClock restores a document whose RGA HLC state was persisted with a rich-text snapshot before its replica ID is reused.
func NewFromClockWithOptions ¶
NewFromClockWithOptions restores a document with explicit bounds.
func NewFromSnapshot ¶
NewFromSnapshot restores a complete rich-text snapshot with default bounds.
func NewFromSnapshotWithOptions ¶
func NewFromSnapshotWithOptions(saved snapshot.Snapshot, options Options, limits frame.DecoderLimits) (*Document, error)
NewFromSnapshotWithOptions validates and restores a rich-text snapshot under explicit document and decoder limits.
func NewWithOptions ¶
NewWithOptions constructs a document with explicit text and format bounds.
func (*Document) AnchorAt ¶ added in v1.0.25
AnchorAt returns the existing text.Anchor representation for a visible rune boundary. text.Anchor has a versioned host-metadata encoding for durable cursors, selections, and comments, but is never embedded in a rich-text frame. Keeping this API typed as text.Anchor deliberately avoids creating a second relative-position identity format for rich text.
func (*Document) AnchorRangeAt ¶ added in v1.0.32
func (d *Document) AnchorRangeAt(start, end int) (text.AnchorRange, error)
AnchorRangeAt captures two relative boundaries from one rich-text revision. It is appropriate for an editor selection or a comment range. The returned text.AnchorRange preserves the supplied order, so a backwards selection can retain its anchor/head direction. Its versioned binary encoding is host metadata and must not be stored in rich-text state/delta frames.
func (*Document) ApplyDelta ¶
ApplyDelta joins one canonical rich-text delta. The entire frame is decoded and resource-checked before text or formatting metadata is changed.
func (*Document) ApplyDeltaWithLimits ¶ added in v1.0.28
func (d *Document) ApplyDeltaWithLimits(delta Delta, limits frame.DecoderLimits) error
ApplyDeltaWithLimits joins one canonical rich-text delta under explicit decoder limits. The entire frame has already been decoded by callers that receive bytes; keeping this method separate lets bounded browser runtimes enforce their negotiated limits through both decode and mutation.
func (*Document) ApplyEditorDelta ¶ added in v1.0.28
func (d *Document) ApplyEditorDelta(operations []EditorOperation) (Delta, error)
ApplyEditorDelta applies one complete editor transaction as one canonical rich-text frame. It preserves inserted inline attributes and retained-range formatting while preflighting the final text/mark resource usage before the document changes. An invalid or over-limit transaction never leaves a delete-only or partially formatted local document.
func (*Document) ApplyEditorDeltaWithLimits ¶ added in v1.0.28
func (d *Document) ApplyEditorDeltaWithLimits(operations []EditorOperation, limits frame.DecoderLimits) (Delta, error)
ApplyEditorDeltaWithLimits is ApplyEditorDelta with explicit framing limits. It is intended for browser and WebView adapters, which must keep local editor work within the same negotiated boundary as received frames.
func (*Document) AttributesAt ¶
func (d *Document) AttributesAt(offset int) (Attributes, bool)
AttributesAt returns a copy of the live attributes at a visible rune offset.
func (*Document) BlockFormatAt ¶ added in v1.0.25
func (d *Document) BlockFormatAt(offset int) (BlockFormat, bool)
BlockFormatAt reports a valid semantic block marker at a visible offset.
func (*Document) Blocks ¶ added in v1.0.25
Blocks returns a presentation projection of newline-delimited blocks. A trailing newline terminates the preceding block and does not manufacture an unanchored empty block after it.
func (*Document) ClearBlocks ¶ added in v1.0.25
ClearBlocks records LWW removals for block markers on complete touched paragraphs. It does not remove text or infer a replacement block format.
func (*Document) ClearBlocksAnchored ¶ added in v1.0.25
ClearBlocksAnchored removes block markers from complete paragraphs selected by two existing text anchors.
func (*Document) ClockState ¶
ClockState returns the shared RGA clock state that must be saved atomically with MarshalBinary or SnapshotCurrentState before a replica ID is reused.
func (*Document) CompactEligibleTombstones ¶ added in v1.0.24
CompactEligibleTombstones makes best-effort progress through an exact-acknowledged tombstone batch. Deleted text descendants are removed before their deleted ancestors; a non-leaf text tombstone cannot prevent independent attribute-removal tombstones or structurally safe descendants from compacting. It remains fail-closed while the nested RGA has pending dependencies. tombstonegc.SimpleCollector may call it only for its documented local-only lifecycle.
func (*Document) CompactTombstones ¶ added in v1.0.24
CompactTombstones removes an exact, structurally-safe text tombstone batch and any selected attribute-removal tombstones. For replicated state, callers must establish exact acknowledgement before calling it. tombstonegc. SimpleCollector may call it only for its documented local-only lifecycle. It is all-or-nothing for text structure: an unresolved, unknown, or non-leaf text tombstone returns ErrUnsafeCompaction without changing text or formatting metadata. Formatting attached to a text position is removed only when that position was retained before and is no longer retained after successful RGA compaction.
func (*Document) DeleteWithLimits ¶
DeleteWithLimits preflights the outer rich-text delta before adding RGA tombstones.
func (*Document) EmbedAt ¶ added in v1.0.25
EmbedAt returns the semantic embed at offset. It rejects malformed generic attributes rather than exposing them as a trusted object.
func (*Document) Format ¶
func (d *Document) Format(offset, count int, changes []AttributeChange) (Delta, error)
Format applies changes to the exact visible positions selected at the time of the call. An empty selection returns an empty canonical delta.
func (*Document) FormatAnchored ¶ added in v1.0.25
FormatAnchored resolves relative boundaries using default decoder limits.
func (*Document) FormatAnchoredWithLimits ¶ added in v1.0.25
func (d *Document) FormatAnchoredWithLimits(start, end text.Anchor, changes []AttributeChange, limits frame.DecoderLimits) (Delta, error)
FormatAnchoredWithLimits resolves both relative boundaries and formats the resulting exact positions while holding one document lock. This prevents a concurrent insertion from changing the selected range between resolution and mutation. The end boundary is exclusive.
func (*Document) FormatBlocks ¶ added in v1.0.25
func (d *Document) FormatBlocks(offset, count int, format BlockFormat) (Delta, error)
FormatBlocks expands the selection to complete touched paragraphs, then records a validated block marker on their current positions. A collapsed selection formats its current paragraph. Paragraphs are separated by '\n'; the end boundary is exclusive unless it falls inside a paragraph. This keeps the feature within v1's exact-position semantics.
func (*Document) FormatBlocksAnchored ¶ added in v1.0.25
FormatBlocksAnchored formats complete paragraphs selected by two existing text anchors. Both anchors are resolved under the document lock, so a concurrent insertion cannot change the intended selection between resolve and mutation.
func (*Document) FormatWithLimits ¶
func (d *Document) FormatWithLimits(offset, count int, changes []AttributeChange, limits frame.DecoderLimits) (Delta, error)
FormatWithLimits preflights a formatting delta before retained LWW registers change. Remove records a tombstone and therefore wins over delayed values.
func (*Document) InsertEmbed ¶ added in v1.0.25
InsertEmbed inserts a single object-replacement character with bounded, validated semantic metadata. Applications still authorize Kind and validate individual JSON fields according to their authenticated manifest schema.
func (*Document) InsertWithAttributes ¶
func (d *Document) InsertWithAttributes(offset int, value string, attributes Attributes) (Delta, error)
InsertWithAttributes inserts text and explicitly applies attributes to only the newly inserted positions. It never infers inherited formatting.
func (*Document) InsertWithAttributesWithLimits ¶
func (d *Document) InsertWithAttributesWithLimits(offset int, value string, attributes Attributes, limits frame.DecoderLimits) (Delta, error)
InsertWithAttributesWithLimits preflights the complete outer delta before mutating document content. A failed preflight may advance the persisted HLC while reserving safe-to-skip tags, but it does not add text or formatting.
func (*Document) InsertWithBlockFormat ¶ added in v1.0.25
func (d *Document) InsertWithBlockFormat(offset int, value string, attributes Attributes, format BlockFormat) (Delta, error)
InsertWithBlockFormat explicitly assigns a block marker to newly inserted text. It is the opt-in counterpart to the deliberately absent implicit block inheritance rule.
func (*Document) MarshalBinary ¶
MarshalBinary returns one canonical rich-text state frame.
func (*Document) MarshalBinaryWithLimits ¶
func (d *Document) MarshalBinaryWithLimits(limits frame.DecoderLimits) ([]byte, error)
MarshalBinaryWithLimits returns a complete rich-text state. It refuses any incomplete RGA state through the nested run-v2 state encoder.
func (*Document) MarshalJSON ¶
MarshalJSON returns the diagnostic state summary, never rich text content.
func (*Document) Merge ¶
Merge joins another rich-text document without exposing either document's mutable RGA. A concurrent update on other is captured as a single state.
func (*Document) ResolveAnchor ¶ added in v1.0.25
ResolveAnchor returns the current visible rune boundary for an existing text.Anchor. A compacted anchor fails closed with text.ErrAnchorGone.
func (*Document) ResolveAnchorRange ¶ added in v1.0.32
func (d *Document) ResolveAnchorRange(anchors text.AnchorRange) (start, end int, err error)
ResolveAnchorRange maps both retained relative boundaries to the current visible rune offsets from one document projection. A compacted position fails closed with text.ErrAnchorGone instead of silently moving a selection or comment.
func (*Document) SetBold ¶ added in v1.0.25
SetBold applies or removes the semantic bold mark for an exact rune range.
func (*Document) SetItalic ¶ added in v1.0.25
SetItalic applies or removes the semantic italic mark for an exact rune range.
func (*Document) SnapshotCurrentState ¶
SnapshotCurrentState returns an HLC-backed rich-text snapshot. Persist the state and its clock atomically before the replica ID is reused.
func (*Document) SnapshotCurrentStateWithLimits ¶
func (d *Document) SnapshotCurrentStateWithLimits(limits frame.DecoderLimits) (snapshot.Snapshot, error)
SnapshotCurrentStateWithLimits returns a validated HLC-backed snapshot with caller-selected frame limits.
func (*Document) Spans ¶
Spans materializes visible text into maximal adjacent runs that share equal live attributes. Returned maps are safe for the caller to modify.
func (*Document) State ¶
func (d *Document) State() crdt.StateSnapshot
State returns a diagnostic summary without text, attributes, positions, or HLC state. It is not a replication or persistence format.
func (*Document) TombstoneTags ¶ added in v1.0.24
TombstoneTags returns every retained text or attribute-removal tombstone in canonical order. It is an exact-acknowledgement input, not proof that a tag is safe to collect. Before calling either compactor, an application must authenticate exact acknowledgements for one membership epoch, persist the post-compaction snapshot, and retire all old-epoch frames.
func (*Document) UnmarshalBinary ¶
UnmarshalBinary installs one complete rich-text state after full decode and canonical validation. A failed state leaves document content unchanged.
func (*Document) UnmarshalBinaryWithLimits ¶
func (d *Document) UnmarshalBinaryWithLimits(data []byte, limits frame.DecoderLimits) error
UnmarshalBinaryWithLimits installs a complete rich-text state with caller selected decoder limits.
type EditorOperation ¶ added in v1.0.28
type EditorOperation struct {
Retain int
Delete int
Insert string
Changes []AttributeChange
}
EditorOperation is one Quill-style, offset-based operation in an editor transaction. Exactly one of Retain, Delete, or Insert must be present.
Changes on a retained range update only the named attributes. A removed attribute is represented by AttributeChange{Key: key, Remove: true}. Inserted text may only receive live attributes; a removal has no meaningful value on a newly created position and is rejected. Offsets are Unicode scalar positions, not UTF-16 code units.
This is deliberately a small common denominator for rich editors. It does not accept HTML, editor nodes, arbitrary embeds, or a renderer schema. An application must bind the exact allowed attribute schema in its manifest and validate it before constructing an EditorOperation.
type Embed ¶ added in v1.0.25
Embed is one application-owned, non-executable embedded object. Data is a bounded JSON object so schema validators can inspect fields before rendering it; this package never interprets it as HTML, CSS, a URL, or executable code.
type Options ¶
Options bounds the text and formatting metadata retained for one document. Every value must be positive.
func DefaultOptions ¶
func DefaultOptions() Options
DefaultOptions returns conservative per-document limits. Applications that accept untrusted peers should lower them to their authenticated group budget.
type Span ¶
type Span struct {
Text string
Attributes Attributes
}
Span is a maximal contiguous visible text run with equal live attributes. A nil Attributes map means that the run has no active attributes.