Packages
livebook
0.4.1
0.19.8
0.19.7
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.6
0.18.5
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.3
0.17.2
0.17.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.14.0-rc.1
0.14.0-rc.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.4
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.3
0.9.2
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.1
0.4.0
0.3.2
0.3.1
0.3.0
0.2.3
0.2.2
0.2.1
0.2.0
0.1.2
0.1.1
0.1.0
Automate code & data workflows with interactive notebooks
Current section
Files
Jump to
Current section
Files
lib/livebook/delta/operation.ex
defmodule Livebook.Delta.Operation do
@moduledoc false
# An operation represents an atomic change applicable to a text.
#
# For plain-text (our use case) an operation can be either of:
#
# * `{:insert, string}` - insert the given text at the current position
# * `{:retain, length}` - preserve the given number of characters (effectively moving the cursor)
# * `{:delete, number}` - delete the given number of characters starting from the current position
import Kernel, except: [length: 1]
@type t :: insert | retain | delete
@type insert :: {:insert, String.t()}
@type retain :: {:retain, non_neg_integer()}
@type delete :: {:delete, non_neg_integer()}
@type compressed_t :: String.t() | non_neg_integer() | neg_integer()
@spec insert(String.t()) :: t()
def insert(string), do: {:insert, string}
@spec retain(non_neg_integer()) :: t()
def retain(length), do: {:retain, length}
@spec delete(non_neg_integer()) :: t()
def delete(length), do: {:delete, length}
@doc """
Returns length of text affected by a given operation.
"""
@spec length(t()) :: non_neg_integer()
def length({:insert, string}), do: String.length(string)
def length({:retain, length}), do: length
def length({:delete, length}), do: length
@doc """
Splits the given operation into two at the specified offset.
"""
@spec split_at(t(), non_neg_integer()) :: {t(), t()}
def split_at(op, position)
def split_at({:insert, string}, position) do
{part_one, part_two} = String.split_at(string, position)
{insert(part_one), insert(part_two)}
end
def split_at({:retain, length}, position) do
{retain(position), retain(length - position)}
end
def split_at({:delete, length}, position) do
{delete(position), delete(length - position)}
end
@doc """
Converts the given operation to a basic type uniquely identifying it.
"""
@spec to_compressed(t()) :: compressed_t()
def to_compressed({:insert, string}), do: string
def to_compressed({:retain, length}), do: length
def to_compressed({:delete, length}), do: -length
@doc """
Converts the given basic type to the corresponding operation.
"""
@spec from_compressed(compressed_t()) :: t()
def from_compressed(string) when is_binary(string), do: {:insert, string}
def from_compressed(length) when is_integer(length) and length >= 0, do: {:retain, length}
def from_compressed(length) when is_integer(length) and length < 0, do: {:delete, -length}
@doc """
Modifies the given operation lists, so that their heads
have the same operation length.
## Examples
iex> left = [{:insert, "cat"}]
iex> right = [{:retain, 2}, {:delete, 2}]
iex> Livebook.Delta.Operation.align_heads(left, right)
{
[{:insert, "ca"}, {:insert, "t"}],
[{:retain, 2}, {:delete, 2}]
}
"""
@spec align_heads(list(t()), list(t())) :: {list(t()), list(t())}
def align_heads([head_a | tail_a], [head_b | tail_b]) do
len_a = length(head_a)
len_b = length(head_b)
cond do
len_a > len_b ->
{left_a, right_a} = split_at(head_a, len_b)
{[left_a, right_a | tail_a], [head_b | tail_b]}
len_a < len_b ->
{left_b, right_b} = split_at(head_b, len_a)
{[head_a | tail_a], [left_b, right_b | tail_b]}
true ->
{[head_a | tail_a], [head_b | tail_b]}
end
end
end