mirror of
https://github.com/livebook-dev/livebook.git
synced 2024-09-20 10:05:57 +08:00
Add support for updating smart cell editor source and intellisense node (#2465)
This commit is contained in:
parent
6837a2f449
commit
e98cf466fa
|
@ -364,12 +364,6 @@ const JSView = {
|
|||
preselect_name: message.preselectName,
|
||||
options: message.options,
|
||||
});
|
||||
} else if (message.type === "setSmartCellEditorIntellisenseNode") {
|
||||
this.pushEvent("set_smart_cell_editor_intellisense_node", {
|
||||
js_view_ref: this.props.ref,
|
||||
node: message.node,
|
||||
cookie: message.cookie,
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -18,8 +18,7 @@ defmodule Livebook.Notebook.Cell.Smart do
|
|||
:kind,
|
||||
:attrs,
|
||||
:js_view,
|
||||
:editor,
|
||||
:editor_intellisense_node
|
||||
:editor
|
||||
]
|
||||
|
||||
alias Livebook.Utils
|
||||
|
@ -33,8 +32,7 @@ defmodule Livebook.Notebook.Cell.Smart do
|
|||
kind: String.t() | nil,
|
||||
attrs: attrs() | :__pruned__,
|
||||
js_view: Livebook.Runtime.js_view() | nil,
|
||||
editor: Livebook.Runtime.editor() | nil,
|
||||
editor_intellisense_node: {String.t(), String.t()} | nil
|
||||
editor: Livebook.Runtime.editor() | nil
|
||||
}
|
||||
|
||||
@type attrs :: map()
|
||||
|
@ -52,8 +50,7 @@ defmodule Livebook.Notebook.Cell.Smart do
|
|||
kind: nil,
|
||||
attrs: %{},
|
||||
js_view: nil,
|
||||
editor: nil,
|
||||
editor_intellisense_node: nil
|
||||
editor: nil
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -704,7 +704,12 @@ defprotocol Livebook.Runtime do
|
|||
@typedoc """
|
||||
Smart cell editor configuration.
|
||||
"""
|
||||
@type editor :: %{language: String.t() | nil, placement: :bottom | :top, source: String.t()}
|
||||
@type editor :: %{
|
||||
language: String.t() | nil,
|
||||
placement: :bottom | :top,
|
||||
source: String.t(),
|
||||
intellisense_node: {atom(), atom()} | nil
|
||||
}
|
||||
|
||||
@typedoc """
|
||||
An opaque file reference.
|
||||
|
@ -876,7 +881,7 @@ defprotocol Livebook.Runtime do
|
|||
pid(),
|
||||
intellisense_request(),
|
||||
parent_locators(),
|
||||
{String.t(), String.t()} | nil
|
||||
{atom(), atom()} | nil
|
||||
) :: reference()
|
||||
def handle_intellisense(runtime, send_to, request, parent_locators, node)
|
||||
|
||||
|
@ -945,6 +950,17 @@ defprotocol Livebook.Runtime do
|
|||
The attrs are persisted and may be used to restore the smart cell
|
||||
state later. Note that for persistence they get serialized and
|
||||
deserialized as JSON.
|
||||
|
||||
When the smart cell editor is enabled, the runtime owner sends the
|
||||
new editor source whenever it changes as:
|
||||
|
||||
* `{:editor_source, source :: String.t()}`
|
||||
|
||||
The cell can also update some of the editor configuration or source
|
||||
by sending:
|
||||
|
||||
* `{:runtime_smart_cell_editor_update, ref, %{optional(:source) => String.t(), optional(:intellisense_node) => {atom(), atom()} | nil}}`
|
||||
|
||||
"""
|
||||
@spec start_smart_cell(
|
||||
t(),
|
||||
|
|
|
@ -130,7 +130,7 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
|||
pid(),
|
||||
Runtime.intellisense_request(),
|
||||
Runtime.Runtime.parent_locators(),
|
||||
{String.t(), String.t()} | nil
|
||||
{atom(), atom()} | nil
|
||||
) :: reference()
|
||||
def handle_intellisense(pid, send_to, request, parent_locators, node) do
|
||||
ref = make_ref()
|
||||
|
@ -921,7 +921,6 @@ defmodule Livebook.Runtime.ErlDist.RuntimeServer do
|
|||
end
|
||||
|
||||
defp intellisense_node({node, cookie}) do
|
||||
{node, cookie} = {String.to_atom(node), String.to_atom(cookie)}
|
||||
Node.set_cookie(node, cookie)
|
||||
if Node.connect(node), do: node, else: node()
|
||||
end
|
||||
|
|
|
@ -1661,6 +1661,8 @@ defmodule Livebook.Session do
|
|||
end
|
||||
|
||||
def handle_info({:runtime_smart_cell_started, id, info}, state) do
|
||||
info = normalize_smart_cell_started_info(info)
|
||||
|
||||
info =
|
||||
if info.editor do
|
||||
normalize_newlines = &String.replace(&1, "\r\n", "\n")
|
||||
|
@ -1672,11 +1674,10 @@ defmodule Livebook.Session do
|
|||
|
||||
case Notebook.fetch_cell_and_section(state.data.notebook, id) do
|
||||
{:ok, cell, _section} ->
|
||||
chunks = info[:chunks]
|
||||
delta = Livebook.Text.Delta.diff(cell.source, info.source)
|
||||
|
||||
operation =
|
||||
{:smart_cell_started, @client_id, id, delta, chunks, info.js_view, info.editor}
|
||||
{:smart_cell_started, @client_id, id, delta, info.chunks, info.js_view, info.editor}
|
||||
|
||||
{:noreply, handle_operation(state, operation)}
|
||||
|
||||
|
@ -1714,6 +1715,42 @@ defmodule Livebook.Session do
|
|||
end
|
||||
end
|
||||
|
||||
def handle_info({:runtime_smart_cell_editor_update, id, options}, state) do
|
||||
case Notebook.fetch_cell_and_section(state.data.notebook, id) do
|
||||
{:ok, cell, _section} when cell.editor != nil ->
|
||||
state =
|
||||
case options do
|
||||
%{source: source} ->
|
||||
delta = Livebook.Text.Delta.diff(cell.editor.source, source)
|
||||
revision = state.data.cell_infos[cell.id].sources.secondary.revision
|
||||
|
||||
operation =
|
||||
{:apply_cell_delta, @client_id, cell.id, :secondary, delta, nil, revision}
|
||||
|
||||
handle_operation(state, operation)
|
||||
|
||||
%{} ->
|
||||
state
|
||||
end
|
||||
|
||||
state =
|
||||
case options do
|
||||
%{intellisense_node: intellisense_node} ->
|
||||
editor = %{cell.editor | intellisense_node: intellisense_node}
|
||||
operation = {:set_cell_attributes, @client_id, cell.id, %{editor: editor}}
|
||||
handle_operation(state, operation)
|
||||
|
||||
%{} ->
|
||||
state
|
||||
end
|
||||
|
||||
{:noreply, state}
|
||||
|
||||
_ ->
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:pong, {:smart_cell_evaluation, cell_id}, _info}, state) do
|
||||
state =
|
||||
with {:ok, cell, section} <- Notebook.fetch_cell_and_section(state.data.notebook, cell_id),
|
||||
|
@ -3070,4 +3107,17 @@ defmodule Livebook.Session do
|
|||
%{type: :unknown, output: other}
|
||||
|> normalize_runtime_output()
|
||||
end
|
||||
|
||||
# Normalizes :runtime_smart_cell_started info to match the latest
|
||||
# specification.
|
||||
defp normalize_smart_cell_started_info(info) when not is_map_key(info, :chunks) do
|
||||
normalize_smart_cell_started_info(put_in(info[:chunks], nil))
|
||||
end
|
||||
|
||||
defp normalize_smart_cell_started_info(info)
|
||||
when info.editor != nil and not is_map_key(info.editor, :intellisense_node) do
|
||||
normalize_smart_cell_started_info(put_in(info.editor[:intellisense_node], nil))
|
||||
end
|
||||
|
||||
defp normalize_smart_cell_started_info(info), do: info
|
||||
end
|
||||
|
|
|
@ -858,23 +858,6 @@ defmodule LivebookWeb.SessionLive do
|
|||
push_patch(socket, to: ~p"/sessions/#{socket.assigns.session.id}/settings/custom-view")}
|
||||
end
|
||||
|
||||
def handle_event(
|
||||
"set_smart_cell_editor_intellisense_node",
|
||||
%{"js_view_ref" => cell_id, "node" => node, "cookie" => cookie},
|
||||
socket
|
||||
) do
|
||||
node =
|
||||
if is_binary(node) and node =~ "@" and is_binary(cookie) and cookie != "" do
|
||||
{node, cookie}
|
||||
end
|
||||
|
||||
Session.set_cell_attributes(socket.assigns.session.pid, cell_id, %{
|
||||
editor_intellisense_node: node
|
||||
})
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:get_input_value, input_id}, _from, socket) do
|
||||
reply =
|
||||
|
@ -2105,7 +2088,7 @@ defmodule LivebookWeb.SessionLive do
|
|||
"#{notebook_name} - Livebook"
|
||||
end
|
||||
|
||||
defp intellisense_node(%Cell.Smart{editor_intellisense_node: node_cookie}), do: node_cookie
|
||||
defp intellisense_node(%Cell.Smart{editor: %{intellisense_node: node_cookie}}), do: node_cookie
|
||||
defp intellisense_node(_), do: nil
|
||||
|
||||
defp any_stale_cell?(data) do
|
||||
|
|
|
@ -1033,6 +1033,54 @@ defmodule Livebook.SessionTest do
|
|||
} = Session.get_data(session.pid)
|
||||
end
|
||||
|
||||
test "handles smart cell editor updates" do
|
||||
smart_cell = %{Notebook.Cell.new(:smart) | kind: "text", source: ""}
|
||||
notebook = %{Notebook.new() | sections: [%{Notebook.Section.new() | cells: [smart_cell]}]}
|
||||
session = start_session(notebook: notebook)
|
||||
|
||||
runtime = connected_noop_runtime()
|
||||
Session.set_runtime(session.pid, runtime)
|
||||
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_definitions,
|
||||
[%{kind: "text", name: "Text", requirement_presets: []}]}
|
||||
)
|
||||
|
||||
Session.subscribe(session.id)
|
||||
|
||||
editor = %{language: nil, placement: :bottom, source: "", intellisense_node: nil}
|
||||
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_started, smart_cell.id,
|
||||
%{source: "1", js_view: %{pid: self(), ref: "ref"}, editor: editor}}
|
||||
)
|
||||
|
||||
# Update editor source
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_editor_update, smart_cell.id, %{source: "new source"}}
|
||||
)
|
||||
|
||||
assert %{
|
||||
notebook: %{sections: [%{cells: [%{editor: %{source: "new source"}}]}]}
|
||||
} = Session.get_data(session.pid)
|
||||
|
||||
# Update intellisense node
|
||||
send(
|
||||
session.pid,
|
||||
{:runtime_smart_cell_editor_update, smart_cell.id,
|
||||
%{intellisense_node: {:test@test, :test}}}
|
||||
)
|
||||
|
||||
assert %{
|
||||
notebook: %{
|
||||
sections: [%{cells: [%{editor: %{intellisense_node: {:test@test, :test}}}]}]
|
||||
}
|
||||
} = Session.get_data(session.pid)
|
||||
end
|
||||
|
||||
test "pings the smart cell before evaluation to await all incoming messages" do
|
||||
smart_cell = %{Notebook.Cell.new(:smart) | kind: "text", source: ""}
|
||||
notebook = %{Notebook.new() | sections: [%{Notebook.Section.new() | cells: [smart_cell]}]}
|
||||
|
|
Loading…
Reference in a new issue