diff --git a/assets/js/hooks/js_view/iframe.js b/assets/js/hooks/js_view/iframe.js index 28302f589..fb5206f6f 100644 --- a/assets/js/hooks/js_view/iframe.js +++ b/assets/js/hooks/js_view/iframe.js @@ -35,7 +35,7 @@ export function initializeIframeSource(iframe, iframePort, iframeUrl) { iframe.sandbox = "allow-scripts allow-same-origin allow-downloads allow-modals allow-popups"; iframe.allow = - "accelerometer; ambient-light-sensor; camera; display-capture; encrypted-media; geolocation; gyroscope; microphone; midi; usb; xr-spatial-tracking"; + "accelerometer; ambient-light-sensor; camera; display-capture; encrypted-media; geolocation; gyroscope; microphone; midi; usb; xr-spatial-tracking; clipboard-read; clipboard-write"; iframe.src = url; }); } diff --git a/lib/livebook/runtime/evaluator.ex b/lib/livebook/runtime/evaluator.ex index bee363dd8..a308e876b 100644 --- a/lib/livebook/runtime/evaluator.ex +++ b/lib/livebook/runtime/evaluator.ex @@ -308,8 +308,6 @@ defmodule Livebook.Runtime.Evaluator do end defp handle_cast({:evaluate_code, code, ref, base_ref, opts}, state) do - Evaluator.IOProxy.configure(state.io_proxy, ref) - Evaluator.ObjectTracker.remove_reference(state.object_tracker, {self(), ref}) context = get_context(state, base_ref) @@ -317,6 +315,8 @@ defmodule Livebook.Runtime.Evaluator do context = put_in(context.env.file, file) start_time = System.monotonic_time() + Evaluator.IOProxy.configure(state.io_proxy, ref, file) + {result_context, result, code_error} = case eval(code, context.binding, context.env) do {:ok, value, binding, env} -> diff --git a/lib/livebook/runtime/evaluator/io_proxy.ex b/lib/livebook/runtime/evaluator/io_proxy.ex index 720e3361a..f82b7e4de 100644 --- a/lib/livebook/runtime/evaluator/io_proxy.ex +++ b/lib/livebook/runtime/evaluator/io_proxy.ex @@ -23,7 +23,9 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do @doc """ Starts an IO device process. - Make sure to use `configure/3` to correctly proxy the requests. + For all supported requests a message is sent to the configured + `:send_to` process, so this device serves as a proxy. Make sure + to also call configure/3` before every evaluation. """ @spec start_link(pid(), pid(), pid(), pid()) :: GenServer.on_start() def start_link(evaluator, send_to, runtime_broadcast_to, object_tracker) do @@ -31,16 +33,13 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do end @doc """ - Sets IO proxy destination and the reference to be attached to all - messages. + Configures IO proxy for a new evaluation. - For all supported requests a message is sent to the configured - `:send_to` process, so this device serves as a proxy. The given - evaluation reference is also included in all messages. + The given reference is attached to all the proxied messages. """ - @spec configure(pid(), Evaluator.ref()) :: :ok - def configure(pid, ref) do - GenServer.cast(pid, {:configure, ref}) + @spec configure(pid(), Evaluator.ref(), String.t()) :: :ok + def configure(pid, ref, file) do + GenServer.cast(pid, {:configure, ref, file}) end @doc """ @@ -75,6 +74,7 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do %{ encoding: :unicode, ref: nil, + file: nil, buffer: [], input_cache: %{}, token_count: 0, @@ -86,8 +86,8 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do end @impl true - def handle_cast({:configure, ref}, state) do - {:noreply, %{state | ref: ref, token_count: 0}} + def handle_cast({:configure, ref, file}, state) do + {:noreply, %{state | ref: ref, file: file, token_count: 0}} end def handle_cast(:clear_input_cache, state) do @@ -230,6 +230,10 @@ defmodule Livebook.Runtime.Evaluator.IOProxy do {{:ok, state.runtime_broadcast_to}, state} end + defp io_request(:livebook_get_evaluation_file, state) do + {state.file, state} + end + defp io_request(_, state) do {{:error, :request}, state} end diff --git a/lib/livebook/session.ex b/lib/livebook/session.ex index bb49b0a5f..1b38ef907 100644 --- a/lib/livebook/session.ex +++ b/lib/livebook/session.ex @@ -1485,7 +1485,7 @@ defmodule Livebook.Session do file -> file.path end - file = path <> "#cell" + file = path <> "#cell:#{cell.id}" smart_cell_ref = case cell do diff --git a/lib/livebook_web/live/output.ex b/lib/livebook_web/live/output.ex index 6949cc32e..b3c81be3c 100644 --- a/lib/livebook_web/live/output.ex +++ b/lib/livebook_web/live/output.ex @@ -34,6 +34,8 @@ defmodule LivebookWeb.Output do defp border?({:stdout, _text}), do: true defp border?({:text, _text}), do: true defp border?({:error, _message}), do: true + # TODO fix spacing and make it an option + defp border?({:grid, _, _}), do: true defp border?(_output), do: false defp wrapper?({:frame, _outputs, _info}), do: true diff --git a/test/livebook/runtime/evaluator/io_proxy_test.exs b/test/livebook/runtime/evaluator/io_proxy_test.exs index 1352cc44f..5983334a6 100644 --- a/test/livebook/runtime/evaluator/io_proxy_test.exs +++ b/test/livebook/runtime/evaluator/io_proxy_test.exs @@ -11,7 +11,7 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do start_supervised({Evaluator, [send_to: self(), object_tracker: object_tracker]}) io = Process.info(evaluator.pid)[:group_leader] - IOProxy.configure(io, :ref) + IOProxy.configure(io, :ref, "cell") %{io: io} end @@ -67,7 +67,7 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do test "clear_input_cache/1 clears all cached input information", %{io: io} do pid = spawn_link(fn -> - IOProxy.configure(io, :ref) + IOProxy.configure(io, :ref, "cell") assert livebook_get_input_value(io, "input1") == {:ok, :value1} IOProxy.clear_input_cache(io) assert livebook_get_input_value(io, "input1") == {:ok, :value2} @@ -106,25 +106,25 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do describe "token requests" do test "returns different tokens for subsequent calls", %{io: io} do - IOProxy.configure(io, :ref1) + IOProxy.configure(io, :ref1, "cell1") token1 = livebook_generate_token(io) token2 = livebook_generate_token(io) assert token1 != token2 end test "returns different tokens for different refs", %{io: io} do - IOProxy.configure(io, :ref1) + IOProxy.configure(io, :ref1, "cell1") token1 = livebook_generate_token(io) - IOProxy.configure(io, :ref2) + IOProxy.configure(io, :ref2, "cell2") token2 = livebook_generate_token(io) assert token1 != token2 end test "returns same tokens for the same ref", %{io: io} do - IOProxy.configure(io, :ref) + IOProxy.configure(io, :ref, "cell") token1 = livebook_generate_token(io) token2 = livebook_generate_token(io) - IOProxy.configure(io, :ref) + IOProxy.configure(io, :ref, "cell") token3 = livebook_generate_token(io) token4 = livebook_generate_token(io) assert token1 == token3 @@ -132,6 +132,13 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do end end + describe "evaluation file requests" do + test "returns the configured file", %{io: io} do + IOProxy.configure(io, :ref1, "cell1") + assert livebook_get_evaluation_file(io) == "cell1" + end + end + # Helpers defp reply_to_input_request(_ref, _input_id, _reply, 0), do: :ok @@ -156,6 +163,10 @@ defmodule Livebook.Runtime.Evaluator.IOProxyTest do io_request(io, :livebook_generate_token) end + defp livebook_get_evaluation_file(io) do + io_request(io, :livebook_get_evaluation_file) + end + defp io_request(io, request) do ref = make_ref() send(io, {:io_request, self(), ref, request})