text_delta v1.4.0 TextDelta.Operation View Source

Operations represent a smallest possible change applicable to a text.

In case of text, there are exactly 3 possible operations we might want to perform:

insert and retain operations can also have optional TextDelta.Attributes.t/0 attached to them. This is how Delta manages rich text formatting without breaking the Operational Transformation paradigm.

Link to this section Summary

Types

The result of comparison operation

Delete operation represents an intention to delete a sequence of characters from the text. It is always a number and it is always positive

An insertable rich text element. Either a piece of text, a number or an embed

Insert operation represents an intention to add a text or an embedded element to a text state. Text additions are represented with binary strings and embedded elements are represented with either an integer or an object

Retain operation represents an intention to keep a sequence of characters unchanged in the text. It is always a number and it is always positive

t()

An operation. Either insert, retain or delete

Atom representing type of operation

Functions

Attempts to compact two given operations into one

Compares the length of two operations

Creates a new delete operation

Creates a new insert operation

Returns length of text affected by a given operation

Creates a new retain operation

Splits operations into two halves around the given index

Checks if given operation is trimmable

Returns atom representing type of the given operation

Link to this section Types

Link to this type comparison() View Source
comparison() :: :eq | :gt | :lt

The result of comparison operation.

Link to this type delete() View Source
delete() :: %{delete: non_neg_integer()}

Delete operation represents an intention to delete a sequence of characters from the text. It is always a number and it is always positive.

Link to this type element() View Source
element() :: String.t() | integer() | map()

An insertable rich text element. Either a piece of text, a number or an embed.

Link to this type insert() View Source
insert() ::
  %{insert: element()}
  | %{insert: element(), attributes: TextDelta.Attributes.t()}

Insert operation represents an intention to add a text or an embedded element to a text state. Text additions are represented with binary strings and embedded elements are represented with either an integer or an object.

Insert also allows us to attach attributes to the element being inserted.

Link to this type retain() View Source
retain() ::
  %{retain: non_neg_integer()}
  | %{retain: non_neg_integer(), attributes: TextDelta.Attributes.t()}

Retain operation represents an intention to keep a sequence of characters unchanged in the text. It is always a number and it is always positive.

In addition to indicating preservation of existing text, retain also allows us to change formatting of retained text or element via optional attributes.

An operation. Either insert, retain or delete.

Link to this type type() View Source
type() :: :insert | :retain | :delete

Atom representing type of operation.

Link to this section Functions

Link to this function compact(op_a, op_b) View Source
compact(t(), t()) :: [t()]

Attempts to compact two given operations into one.

If successful, will return a list with just a single, compacted operation. In any other case both operations will be returned back unchanged.

Compacting works by combining same operations with the same attributes together. Easiest way to think about this function is that it produces an exact opposite effect of TextDelta.Operation.slice/2.

Text insert is compacted by concatenating strings, retain or delete is compacted by adding the sequence numbers. Only operations with the same attribute set are compacted. This is mostly used to keep deltas short and canonical.

Examples

Text inserts are compacted into a single insert:

iex> TextDelta.Operation.compact(%{insert: "hel"}, %{insert: "lo"})
[%{insert: "hello"}]

Retains and deletes are compacted by adding their sequence numbers:

iex> TextDelta.Operation.compact(%{retain: 2}, %{retain: 3})
[%{retain: 5}]
Link to this function compare(op_a, op_b) View Source
compare(t(), t()) :: comparison()

Compares the length of two operations.

Example

iex> TextDelta.Operation.compare(%{insert: "hello!"}, %{delete: 3})
:gt

Creates a new delete operation.

Example

To delete 3 next characters from the text, we can create a following operation:

iex> TextDelta.Operation.delete(3)
%{delete: 3}

Creates a new insert operation.

Attributes are optional and are ignored if empty map or nil is provided.

Examples

To indicate that we need to insert a text “hello” into the text, we can use following insert:

iex> TextDelta.Operation.insert("hello")
%{insert: "hello"}

In addition, we can indicate that “hello” should be inserted with specific attributes:

iex> TextDelta.Operation.insert("hello", %{bold: true, color: "magenta"})
%{insert: "hello", attributes: %{bold: true, color: "magenta"}}

We can also insert non-text objects, such as an image:

iex> TextDelta.Operation.insert(%{img: "me.png"}, %{alt: "My photo"})
%{insert: %{img: "me.png"}, attributes: %{alt: "My photo"}}

Returns length of text affected by a given operation.

Length for insert operations is calculated by counting the length of text itself being inserted, length for retain or delete operations is a length of sequence itself. Attributes have no effect over the length.

Examples

For text inserts it is a length of text itself:

iex> TextDelta.Operation.length(%{insert: "hello!"})
6

For embed inserts, however, length is always 1:

iex> TextDelta.Operation.length(%{insert: 3})
1

For retain and deletes, the number itself is the length:

iex> TextDelta.Operation.length(%{retain: 4})
4

Creates a new retain operation.

Attributes are optional and are ignored if empty map or nil is provided.

Examples

To keep 5 next characters inside the text, we can use the following retain:

iex> TextDelta.Operation.retain(5)
%{retain: 5}

To make those exact 5 characters bold, while keeping them, we can use attributes:

iex> TextDelta.Operation.retain(5, %{bold: true})
%{retain: 5, attributes: %{bold: true}}
Link to this function slice(op, idx) View Source
slice(t(), non_neg_integer()) :: {t(), t()}

Splits operations into two halves around the given index.

Text insert is split via slicing the text itself, retain or delete is split by subtracting the sequence number. Attributes are preserved during splitting. This is mostly used for normalisation of deltas during iteration.

Examples

Text inserts are split via slicing the text itself:

iex> TextDelta.Operation.slice(%{insert: "hello"}, 3)
{%{insert: "hel"}, %{insert: "lo"}}

retain and delete are split by subtracting the sequence number:

iex> TextDelta.Operation.slice(%{retain: 5}, 2)
{%{retain: 2}, %{retain: 3}}
Link to this function trimmable?(op) View Source
trimmable?(t()) :: boolean()

Checks if given operation is trimmable.

Technically only retain operations are trimmable, but the creator of this library didn’t feel comfortable exposing that knowledge outside of this module.

Example

iex> TextDelta.Operation.trimmable?(%{retain: 3})
true

Returns atom representing type of the given operation.

Example

iex> TextDelta.Operation.type(%{retain: 5, attributes: %{bold: true}})
:retain