mirror of
https://github.com/livebook-dev/livebook.git
synced 2025-09-08 14:04:31 +08:00
Fix smart cell indicator when source changes on start (#1851)
This commit is contained in:
parent
744406d1d8
commit
4d46b03cc8
6 changed files with 63 additions and 34 deletions
|
@ -24,6 +24,11 @@ defmodule Livebook.Delta do
|
|||
alias Livebook.Delta
|
||||
alias Livebook.Delta.{Operation, Transformation}
|
||||
|
||||
@typedoc """
|
||||
Delta carries a list of consecutive operations.
|
||||
|
||||
Note that we keep the operations in reversed order for efficiency.
|
||||
"""
|
||||
@type t :: %Delta{ops: list(Operation.t())}
|
||||
|
||||
@doc """
|
||||
|
@ -73,12 +78,7 @@ defmodule Livebook.Delta do
|
|||
"""
|
||||
@spec append(t(), Operation.t()) :: t()
|
||||
def append(delta, op) do
|
||||
Map.update!(delta, :ops, fn ops ->
|
||||
ops
|
||||
|> Enum.reverse()
|
||||
|> compact(op)
|
||||
|> Enum.reverse()
|
||||
end)
|
||||
Map.update!(delta, :ops, &compact(&1, op))
|
||||
end
|
||||
|
||||
defp compact(ops, {:insert, ""}), do: ops
|
||||
|
@ -110,18 +110,23 @@ defmodule Livebook.Delta do
|
|||
Removes trailing retain operations from the given delta.
|
||||
"""
|
||||
@spec trim(t()) :: t()
|
||||
def trim(%Delta{ops: []} = delta), do: delta
|
||||
def trim(%Delta{ops: [{:retain, _} | ops]} = delta), do: %{delta | ops: ops}
|
||||
def trim(delta), do: delta
|
||||
|
||||
def trim(delta) do
|
||||
case List.last(delta.ops) do
|
||||
{:retain, _} ->
|
||||
Map.update!(delta, :ops, fn ops ->
|
||||
ops |> Enum.reverse() |> tl() |> Enum.reverse()
|
||||
end)
|
||||
@doc """
|
||||
Checks if the delta has no changes.
|
||||
"""
|
||||
@spec empty?(t()) :: boolean()
|
||||
def empty?(delta) do
|
||||
trim(delta).ops == []
|
||||
end
|
||||
|
||||
_ ->
|
||||
delta
|
||||
end
|
||||
@doc """
|
||||
Returns data operations in the order in which they apply.
|
||||
"""
|
||||
@spec operations(t()) :: list(Operation.t())
|
||||
def operations(delta) do
|
||||
Enum.reverse(delta.ops)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -130,14 +135,16 @@ defmodule Livebook.Delta do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> delta = %Livebook.Delta{ops: [retain: 2, insert: "hey", delete: 3]}
|
||||
iex> delta = Delta.new([retain: 2, insert: "hey", delete: 3])
|
||||
iex> Livebook.Delta.to_compressed(delta)
|
||||
[2, "hey", -3]
|
||||
|
||||
"""
|
||||
@spec to_compressed(t()) :: list(Operation.compressed_t())
|
||||
def to_compressed(delta) do
|
||||
Enum.map(delta.ops, &Operation.to_compressed/1)
|
||||
delta.ops
|
||||
|> Enum.reverse()
|
||||
|> Enum.map(&Operation.to_compressed/1)
|
||||
end
|
||||
|
||||
@doc """
|
||||
|
@ -145,15 +152,19 @@ defmodule Livebook.Delta do
|
|||
|
||||
## Examples
|
||||
|
||||
iex> Livebook.Delta.from_compressed([2, "hey", -3])
|
||||
%Livebook.Delta{ops: [retain: 2, insert: "hey", delete: 3]}
|
||||
iex> delta = Livebook.Delta.from_compressed([2, "hey", -3])
|
||||
iex> Livebook.Delta.operations(delta)
|
||||
[retain: 2, insert: "hey", delete: 3]
|
||||
|
||||
"""
|
||||
@spec from_compressed(list(Operation.compressed_t())) :: t()
|
||||
def from_compressed(list) do
|
||||
list
|
||||
|> Enum.map(&Operation.from_compressed/1)
|
||||
|> new()
|
||||
ops =
|
||||
list
|
||||
|> Enum.map(&Operation.from_compressed/1)
|
||||
|> Enum.reverse()
|
||||
|
||||
%Delta{ops: ops}
|
||||
end
|
||||
|
||||
defdelegate transform(left, right, priority), to: Transformation
|
||||
|
|
|
@ -36,7 +36,7 @@ defmodule Livebook.Delta.Transformation do
|
|||
"""
|
||||
@spec transform(Delta.t(), Delta.t(), priority()) :: Delta.t()
|
||||
def transform(left, right, priority) do
|
||||
do_transform(left.ops, right.ops, priority, Delta.new())
|
||||
do_transform(Delta.operations(left), Delta.operations(right), priority, Delta.new())
|
||||
|> Delta.trim()
|
||||
end
|
||||
|
||||
|
|
|
@ -20,7 +20,8 @@ defmodule Livebook.JSInterop do
|
|||
def apply_delta_to_string(delta, string) do
|
||||
code_units = string_to_utf16_code_units(string)
|
||||
|
||||
delta.ops
|
||||
delta
|
||||
|> Delta.operations()
|
||||
|> apply_to_code_units(code_units)
|
||||
|> utf16_code_units_to_string()
|
||||
end
|
||||
|
|
|
@ -1858,6 +1858,18 @@ defmodule Livebook.Session do
|
|||
state
|
||||
end
|
||||
|
||||
defp after_operation(
|
||||
state,
|
||||
_prev_state,
|
||||
{:smart_cell_started, _client_id, cell_id, delta, _chunks, _js_view, _editor}
|
||||
) do
|
||||
unless Delta.empty?(delta) do
|
||||
hydrate_cell_source_digest(state, cell_id, :primary)
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
defp after_operation(
|
||||
state,
|
||||
_prev_state,
|
||||
|
|
|
@ -8,39 +8,39 @@ defmodule Livebook.DeltaTest do
|
|||
|
||||
describe "append/2" do
|
||||
test "ignores empty operations" do
|
||||
assert Delta.append(Delta.new(), {:insert, ""}) == %Delta{ops: []}
|
||||
assert Delta.append(Delta.new(), {:retain, 0}) == %Delta{ops: []}
|
||||
assert Delta.append(Delta.new(), {:delete, 0}) == %Delta{ops: []}
|
||||
assert Delta.new() |> Delta.append({:insert, ""}) |> Delta.operations() == []
|
||||
assert Delta.new() |> Delta.append({:retain, 0}) |> Delta.operations() == []
|
||||
assert Delta.new() |> Delta.append({:delete, 0}) |> Delta.operations() == []
|
||||
end
|
||||
|
||||
test "given empty delta just appends the operation" do
|
||||
delta = Delta.new()
|
||||
op = Operation.insert("cats")
|
||||
assert Delta.append(delta, op) == %Delta{ops: [insert: "cats"]}
|
||||
assert delta |> Delta.append(op) |> Delta.operations() == [insert: "cats"]
|
||||
end
|
||||
|
||||
test "merges consecutive inserts" do
|
||||
delta = Delta.new() |> Delta.insert("cats")
|
||||
op = Operation.insert(" rule")
|
||||
assert Delta.append(delta, op) == %Delta{ops: [insert: "cats rule"]}
|
||||
assert delta |> Delta.append(op) |> Delta.operations() == [insert: "cats rule"]
|
||||
end
|
||||
|
||||
test "merges consecutive retains" do
|
||||
delta = Delta.new() |> Delta.retain(2)
|
||||
op = Operation.retain(2)
|
||||
assert Delta.append(delta, op) == %Delta{ops: [retain: 4]}
|
||||
assert delta |> Delta.append(op) |> Delta.operations() == [retain: 4]
|
||||
end
|
||||
|
||||
test "merges consecutive delete" do
|
||||
delta = Delta.new() |> Delta.delete(2)
|
||||
op = Operation.delete(2)
|
||||
assert Delta.append(delta, op) == %Delta{ops: [delete: 4]}
|
||||
assert delta |> Delta.append(op) |> Delta.operations() == [delete: 4]
|
||||
end
|
||||
|
||||
test "given insert appended after delete, swaps the operations" do
|
||||
delta = Delta.new() |> Delta.delete(2)
|
||||
op = Operation.insert("cats")
|
||||
assert Delta.append(delta, op) == %Delta{ops: [insert: "cats", delete: 2]}
|
||||
assert delta |> Delta.append(op) |> Delta.operations() == [insert: "cats", delete: 2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -944,7 +944,7 @@ defmodule Livebook.SessionTest do
|
|||
end
|
||||
|
||||
test "pings the smart cell before evaluation to await all incoming messages" do
|
||||
smart_cell = %{Notebook.Cell.new(:smart) | kind: "text", source: "1"}
|
||||
smart_cell = %{Notebook.Cell.new(:smart) | kind: "text", source: ""}
|
||||
notebook = %{Notebook.new() | sections: [%{Notebook.Section.new() | cells: [smart_cell]}]}
|
||||
session = start_session(notebook: notebook)
|
||||
|
||||
|
@ -964,6 +964,11 @@ defmodule Livebook.SessionTest do
|
|||
%{source: "1", js_view: %{pid: self(), ref: "ref"}, editor: nil}}
|
||||
)
|
||||
|
||||
# Sends digest to clients when the source is different
|
||||
cell_id = smart_cell.id
|
||||
new_digest = :erlang.md5("1")
|
||||
assert_receive {:hydrate_cell_source_digest, ^cell_id, :primary, ^new_digest}
|
||||
|
||||
Session.queue_cell_evaluation(session.pid, smart_cell.id)
|
||||
|
||||
send(session.pid, {:runtime_evaluation_response, "setup", {:ok, ""}, @eval_meta})
|
||||
|
|
Loading…
Add table
Reference in a new issue