diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index 1c3253ce9..87389a16e 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -933,11 +933,11 @@ defmodule Livebook.Session do end end - def handle_info({:runtime_smart_cell_update, id, attrs, source}, state) do + def handle_info({:runtime_smart_cell_update, id, attrs, source, info}, state) do case Notebook.fetch_cell_and_section(state.data.notebook, id) do {:ok, cell, _section} -> delta = Livebook.JSInterop.diff(cell.source, source) - operation = {:update_smart_cell, self(), id, attrs, delta} + operation = {:update_smart_cell, self(), id, attrs, delta, info.reevaluate} {:noreply, handle_operation(state, operation)} :error -> diff --git a/lib/livebook/session/data.ex b/lib/livebook/session/data.ex index c79974c4c..95e4cf436 100644 --- a/lib/livebook/session/data.ex +++ b/lib/livebook/session/data.ex @@ -167,7 +167,8 @@ defmodule Livebook.Session.Data do | {:cancel_cell_evaluation, pid(), Cell.id()} | {:smart_cell_started, pid(), Cell.id(), Delta.t(), Runtime.js_view(), Cell.Smart.editor() | nil} - | {:update_smart_cell, pid(), Cell.id(), Cell.Smart.attrs(), Delta.t()} + | {:update_smart_cell, pid(), Cell.id(), Cell.Smart.attrs(), Delta.t(), + reevaluate :: boolean()} | {:erase_outputs, pid()} | {:set_notebook_name, pid(), String.t()} | {:set_section_name, pid(), Section.id(), String.t()} @@ -558,12 +559,13 @@ defmodule Livebook.Session.Data do end end - def apply_operation(data, {:update_smart_cell, client_pid, id, attrs, delta}) do - with {:ok, %Cell.Smart{} = cell, _section} <- + def apply_operation(data, {:update_smart_cell, client_pid, id, attrs, delta, reevaluate}) do + with {:ok, %Cell.Smart{} = cell, section} <- Notebook.fetch_cell_and_section(data.notebook, id) do data |> with_actions() |> update_smart_cell(cell, client_pid, attrs, delta) + |> maybe_queue_updated_smart_cell(cell, section, reevaluate) |> set_dirty() |> wrap_ok() else @@ -1251,6 +1253,21 @@ defmodule Livebook.Session.Data do |> add_action({:broadcast_delta, client_pid, updated_cell, :primary, delta}) end + defp maybe_queue_updated_smart_cell({data, _} = data_actions, cell, section, reevaluate) do + info = data.cell_infos[cell.id] + + evaluated? = info.eval.status == :ready and info.eval.validity in [:evaluated, :stale] + + if evaluated? and reevaluate do + data_actions + |> queue_prerequisite_cells_evaluation(cell) + |> queue_cell_evaluation(cell, section) + |> maybe_evaluate_queued() + else + data_actions + end + end + defp erase_outputs({data, _} = data_actions) do data_actions |> clear_all_evaluation() diff --git a/test/livebook/session/data_test.exs b/test/livebook/session/data_test.exs index 5ca779f47..16c4dae32 100644 --- a/test/livebook/session/data_test.exs +++ b/test/livebook/session/data_test.exs @@ -2748,7 +2748,7 @@ defmodule Livebook.Session.DataTest do attrs = %{"text" => "content!"} delta2 = Delta.new() |> Delta.retain(7) |> Delta.insert("!") - operation = {:update_smart_cell, client_pid, "c1", attrs, delta2} + operation = {:update_smart_cell, client_pid, "c1", attrs, delta2, false} assert {:ok, %{ @@ -2759,6 +2759,28 @@ defmodule Livebook.Session.DataTest do [{:broadcast_delta, ^client_pid, _cell, :primary, ^delta2}]} = Data.apply_operation(data, operation) end + + test "queues the cell when already evaluated and reevaluate is specified" do + client_pid = self() + + data = + data_after_operations!([ + {:insert_section, self(), 0, "s1"}, + {:set_runtime, self(), NoopRuntime.new()}, + {:set_smart_cell_definitions, self(), [%{kind: "text", name: "Text"}]}, + {:insert_cell, self(), "s1", 0, :smart, "c1", %{kind: "text"}}, + {:smart_cell_started, self(), "c1", Delta.new(), %{}, nil}, + {:queue_cells_evaluation, self(), ["c1"]}, + {:add_cell_evaluation_response, self(), "c1", @eval_resp, @eval_meta} + ]) + + operation = {:update_smart_cell, client_pid, "c1", %{}, Delta.new(), true} + + assert {:ok, + %{ + cell_infos: %{"c1" => %{eval: %{status: :evaluating}}} + }, _actions} = Data.apply_operation(data, operation) + end end describe "apply_operation/2 given :erase_outputs" do