Packages

Elixir counter-part for the Quill.js Delta library. It provides a baseline for Operational Transformation of rich text.

Current section

Files

Jump to
text_delta lib text_delta application.ex
Raw

lib/text_delta/application.ex

defmodule TextDelta.Application do
@moduledoc """
The application of a delta onto a text state.
Text state is always represented as a set of `t:TextDelta.Operation.insert/0`
operations. This means that any application should always result in a set of
`insert` operations or produce an error tuple.
In simpler terms this means that it is not possible to apply delta, which
combined length of `retain` and `delete` operations is longer than the length
of original text. This situation will always result in `:length_mismatch`
error.
"""
@typedoc """
Reason for an application error.
"""
@type error_reason :: :length_mismatch
@typedoc """
Result of an application.
An ok/error tuple. Represents either a successful application in form of
`{:ok, new_state}` or an error in form of `{:error, reason}`.
"""
@type result :: {:ok, TextDelta.state}
| {:error, error_reason}
@doc """
Applies given delta to a particular text state, resulting in a new state.
Text state is a set of `t:TextDelta.Operation.insert/0` operations. If
applying delta results in anything but a set of `insert` operations, `:error`
tuple is returned instead.
## Examples
successful application:
iex> doc = TextDelta.insert(TextDelta.new(), "hi")
%TextDelta{ops: [%{insert: "hi"}]}
iex> TextDelta.apply(doc, TextDelta.insert(TextDelta.new(), "oh, "))
{:ok, %TextDelta{ops: [%{insert: "oh, hi"}]}}
error handling:
iex> doc = TextDelta.insert(TextDelta.new(), "hi")
%TextDelta{ops: [%{insert: "hi"}]}
iex> TextDelta.apply(doc, TextDelta.delete(TextDelta.new(), 5))
{:error, :length_mismatch}
"""
@spec apply(TextDelta.state, TextDelta.t) :: result
def apply(state, delta) do
case delta_within_text_length?(delta, state) do
true ->
{:ok, TextDelta.compose(state, delta)}
false ->
{:error, :length_mismatch}
end
end
@doc """
Applies given delta to a particular text state, resulting in a new state.
Equivalent to `&TextDelta.Application.apply/2`, but instead of returning
ok/error tuples returns a new state or raises a `RuntimeError`.
"""
@spec apply!(TextDelta.state, TextDelta.t) :: TextDelta.state | no_return
def apply!(state, delta) do
case __MODULE__.apply(state, delta) do
{:ok, new_state} ->
new_state
{:error, reason} ->
raise "Can not apply delta to state: #{Atom.to_string(reason)}"
end
end
defp delta_within_text_length?(delta, state) do
TextDelta.length(state) >= TextDelta.length(delta, [:retain, :delete])
end
end