richtext

package
v1.0.36 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2026 License: MIT Imports: 13 Imported by: 0

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

View Source
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"
)
View Source
const (
	// SemanticsVersion identifies the rich-text inline-formatting contract.
	// It must match the value negotiated in a replica manifest.
	SemanticsVersion uint64 = 1
)

Variables

View Source
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")
)
View Source
var ErrInvalidSemantic = errors.New("richtext: invalid semantic formatting")

Functions

This section is empty.

Types

type AttributeChange

type AttributeChange struct {
	Key    string
	Value  string
	Remove bool
}

AttributeChange describes one formatting assignment or retained removal. Remove must be true with an empty Value to make deletion unambiguous.

type Attributes

type Attributes map[string]string

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

type BlockFormat struct {
	Kind  string
	Level int
}

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

func UnmarshalDelta(data []byte) (Delta, error)

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

func (d Delta) MarshalBinary() ([]byte, error)

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

func (d Delta) MarshalJSON() ([]byte, error)

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 New

func New(replicaID string) (*Document, error)

New constructs a document with default bounds.

func NewFromClock

func NewFromClock(state clock.State) (*Document, error)

NewFromClock restores a document whose RGA HLC state was persisted with a rich-text snapshot before its replica ID is reused.

func NewFromClockWithOptions

func NewFromClockWithOptions(state clock.State, options Options) (*Document, error)

NewFromClockWithOptions restores a document with explicit bounds.

func NewFromSnapshot

func NewFromSnapshot(saved snapshot.Snapshot) (*Document, error)

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

func NewWithOptions(replicaID string, options Options) (*Document, error)

NewWithOptions constructs a document with explicit text and format bounds.

func (*Document) AnchorAt added in v1.0.25

func (d *Document) AnchorAt(offset int) (text.Anchor, error)

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

func (d *Document) ApplyDelta(delta Delta) error

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

func (d *Document) Blocks() []Block

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

func (d *Document) ClearBlocks(offset, count int) (Delta, error)

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

func (d *Document) ClearBlocksAnchored(start, end text.Anchor) (Delta, error)

ClearBlocksAnchored removes block markers from complete paragraphs selected by two existing text anchors.

func (*Document) ClockState

func (d *Document) ClockState() clock.State

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

func (d *Document) CompactEligibleTombstones(tags []crdt.Tag) (int, error)

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

func (d *Document) CompactTombstones(tags []crdt.Tag) (int, error)

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) Delete

func (d *Document) Delete(offset, count int) (Delta, error)

Delete removes count visible runes beginning at offset.

func (*Document) DeleteWithLimits

func (d *Document) DeleteWithLimits(offset, count int, limits frame.DecoderLimits) (Delta, error)

DeleteWithLimits preflights the outer rich-text delta before adding RGA tombstones.

func (*Document) EmbedAt added in v1.0.25

func (d *Document) EmbedAt(offset int) (Embed, bool)

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

func (d *Document) FormatAnchored(start, end text.Anchor, changes []AttributeChange) (Delta, error)

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

func (d *Document) FormatBlocksAnchored(start, end text.Anchor, format BlockFormat) (Delta, error)

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) Insert

func (d *Document) Insert(offset int, value string) (Delta, error)

Insert inserts unformatted UTF-8 text at a visible rune offset.

func (*Document) InsertEmbed added in v1.0.25

func (d *Document) InsertEmbed(offset int, embed Embed) (Delta, error)

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) Len

func (d *Document) Len() int

Len returns the number of visible Unicode scalar values.

func (*Document) MarshalBinary

func (d *Document) MarshalBinary() ([]byte, error)

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

func (d *Document) MarshalJSON() ([]byte, error)

MarshalJSON returns the diagnostic state summary, never rich text content.

func (*Document) Merge

func (d *Document) Merge(other *Document) error

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

func (d *Document) ResolveAnchor(anchor text.Anchor) (int, error)

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

func (d *Document) SetBold(offset, count int, enabled bool) (Delta, error)

SetBold applies or removes the semantic bold mark for an exact rune range.

func (*Document) SetItalic added in v1.0.25

func (d *Document) SetItalic(offset, count int, enabled bool) (Delta, error)

SetItalic applies or removes the semantic italic mark for an exact rune range.

func (*Document) SnapshotCurrentState

func (d *Document) SnapshotCurrentState() (snapshot.Snapshot, error)

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

func (d *Document) Spans() []Span

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) String

func (d *Document) String() string

String returns visible text without formatting metadata.

func (*Document) TombstoneTags added in v1.0.24

func (d *Document) TombstoneTags() []crdt.Tag

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

func (d *Document) UnmarshalBinary(data []byte) error

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

type Embed struct {
	Kind string
	Data string
}

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

type Options struct {
	Text                      text.Options
	MaxMarkEntries            int
	MaxAttributesPerOperation int
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL