text_delta v1.0.2 TextDelta.Operation

Operations represent a smallest possible change applicable to the document.

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.

Summary

Types

The result of comparison operation

Delete operation represents an intention to delete a sequence of characters from the document. 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 document. 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 document. 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

Types

comparison()
comparison() :: :eq | :gt | :lt

The result of comparison operation.

delete()
delete() :: %{delete: non_neg_integer}

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

element()
element() :: String.t | integer | map

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

insert()
insert ::
  %{insert: element} |
  %{insert: element, attributes: TextDelta.Attributes.t}

Insert operation represents an intention to add a text or an embedded element to a document. 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.

retain()
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 document. 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.

t()
t() :: insert | retain | delete

An operation. Either insert, retain or delete.

type()
type() :: :insert | :retain | :delete

Atom representing type of operation.

Functions

compact(op_a, op_b)
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}]
compare(op_a, op_b)
compare(t, t) :: comparison

Compares the length of two operations.

Example

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

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}
insert(el, attrs \\ %{})

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 document, 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"}}
length(op)
length(t) :: non_neg_integer

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
retain(len, attrs \\ %{})
retain(non_neg_integer, TextDelta.Attributes.t) :: retain

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}}
slice(op, idx)
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}}
trimmable?(op)
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
type(op)
type(t) :: type

Returns atom representing type of the given operation.

Example

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