-
Notifications
You must be signed in to change notification settings - Fork 423
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remote Intellisense #2217
Remote Intellisense #2217
Conversation
Uffizzi Preview |
@@ -19,11 +19,12 @@ defmodule Livebook.Intellisense.SignatureMatcher do | |||
{:ok, list(signature_info()), active_argument :: non_neg_integer()} | :error | |||
def get_matching_signatures(hint, intellisense_context) do | |||
%{env: env} = intellisense_context | |||
node = intellisense_context.node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to pass the node as an option here too. intellisense_context
comes from the node that does evaluation, and for the remote cell that's not the node we want to use.
test/livebook/intellisense_test.exs
Outdated
File.rm_rf!(@tmp_dir) | ||
File.mkdir_p!(@tmp_dir) | ||
File.write!("#{@tmp_dir}/Elixir.RemoteModule.beam", bytecode) | ||
|
||
true = :erpc.call(node, :code, :set_path, [:code.get_path() ++ [~c"#{@tmp_dir}"]]) | ||
{:ok, _} = :erpc.call(node, :application, :ensure_all_started, [:elixir]) | ||
{:module, RemoteModule} = :erpc.call(node, :code, :load_file, [:"Elixir.RemoteModule"]) | ||
:loaded = :erpc.call(node, :code, :module_status, [:"Elixir.RemoteModule"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To simplify we could use Livebook.Runtime.ElixirStandalone
. Start a runtime, evaluate code that defines the module, and then use runtime.node
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea but we can end-up accidentally polluting the node in tests and not find bugs. So I would try to keep the remote node as "clean" as possible, if that makes any sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the other tests run in the context of Livebook itself, so there is definitely much more polluted in that sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? To be clear, what I mean is that, if the remote node has Livebook's runtime, then we can accidentally do :erpc.call(node(), SomeLivebookModule)
which will not be available in actual remote nodes, only in tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean?
That when testing completion all the livebook modules could be listed, we just have the tests such that this is never the case.
then we can accidentally do :erpc.call(node(), SomeLivebookModule)
I see, we are clearly just fetching docs info so I'm not worried about that, but that's a fair point, we can keep the minimal version.
test/livebook/intellisense_test.exs
Outdated
} | ||
end | ||
end | ||
|
||
setup_all do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to the describe block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we can have a setup_all inside the describe block. Therefore my proposal is to keep these tests in a separate file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do a separate module in the same file just so it's still grouped together.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Separate module or separate file are both fine by me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved it back to a separate file
lib/livebook/intellisense/docs.ex
Outdated
members = MapSet.new(members) | ||
kinds = opts[:kinds] || [:function, :macro, :type] | ||
|
||
specs = | ||
with true <- :function in kinds or :macro in kinds, | ||
{:ok, specs} <- Code.Typespec.fetch_specs(module) do | ||
{:ok, specs} <- :erpc.call(node, :"Elixir.Code.Typespec", :fetch_specs, [module]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{:ok, specs} <- :erpc.call(node, :"Elixir.Code.Typespec", :fetch_specs, [module]) do | |
{:ok, specs} <- :erpc.call(node, Code.Typespec, :fetch_specs, [module]) do |
lib/livebook/intellisense/docs.ex
Outdated
case Code.fetch_docs(module) do | ||
@spec get_module_documentation(module(), node()) :: documentation() | ||
def get_module_documentation(module, node) do | ||
case :erpc.call(node, :"Elixir.Code", :fetch_docs, [module]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case :erpc.call(node, :"Elixir.Code", :fetch_docs, [module]) do | |
case :erpc.call(node, Code, :fetch_docs, [module]) do |
lib/livebook/intellisense/docs.ex
Outdated
Map.new(specs) | ||
else | ||
_ -> %{} | ||
end | ||
|
||
type_specs = | ||
with true <- :type in kinds, | ||
{:ok, types} <- Code.Typespec.fetch_types(module) do | ||
{:ok, types} <- :erpc.call(node, :"Elixir.Code.Typespec", :fetch_types, [module]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
{:ok, types} <- :erpc.call(node, :"Elixir.Code.Typespec", :fetch_types, [module]) do | |
{:ok, types} <- :erpc.call(node, Code.Typespec, :fetch_types, [module]) do |
lib/livebook/intellisense/docs.ex
Outdated
@@ -99,7 +100,7 @@ defmodule Livebook.Intellisense.Docs do | |||
_ -> %{} | |||
end | |||
|
|||
case Code.fetch_docs(module) do | |||
case :erpc.call(node, :"Elixir.Code", :fetch_docs, [module]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
case :erpc.call(node, :"Elixir.Code", :fetch_docs, [module]) do | |
case :erpc.call(node, Code, :fetch_docs, [module]) do |
funs = funs || exports(mod) | ||
node = ctx.intellisense_context.node | ||
|
||
if :erpc.call(node, :"Elixir.Code", :ensure_loaded?, [mod]) do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if :erpc.call(node, :"Elixir.Code", :ensure_loaded?, [mod]) do | |
if :erpc.call(node, Code, :ensure_loaded?, [mod]) do |
loaded = :erpc.call(node, :"Elixir.Code", :ensure_loaded?, [mod]) | ||
exported = :erpc.call(node, :"Elixir.Kernel", :function_exported?, [mod, :__info__, 1]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
loaded = :erpc.call(node, :"Elixir.Code", :ensure_loaded?, [mod]) | |
exported = :erpc.call(node, :"Elixir.Kernel", :function_exported?, [mod, :__info__, 1]) | |
loaded = :erpc.call(node, Code, :ensure_loaded?, [mod]) | |
exported = :erpc.call(node, :erlang, :function_exported, [mod, :__info__, 1]) |
test/livebook/intellisense_test.exs
Outdated
File.mkdir_p!(@tmp_dir) | ||
File.write!("#{@tmp_dir}/Elixir.RemoteModule.beam", bytecode) | ||
|
||
true = :erpc.call(node, :code, :set_path, [:code.get_path() ++ [~c"#{@tmp_dir}"]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true = :erpc.call(node, :code, :set_path, [:code.get_path() ++ [~c"#{@tmp_dir}"]]) | |
true = :erpc.call(node, :code, :set_path, [[~c"#{@tmp_dir}" | :code.get_path()]]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, let's prune this path a bit:
true = :erpc.call(node, :code, :set_path, [:code.get_path() ++ [~c"#{@tmp_dir}"]]) | |
build_path = Mix.Project.build_path() |> String.to_charlist() | |
paths = Enum.reject(:code.get_path, &List.starts_with?(&1, build_path)) | |
true = :erpc.call(node, :code, :set_path, [[~c"#{@tmp_dir}" | paths]]) |
This will make sure the node does not have any of our deps either!
Initial intellisense support for remote nodes
Remote modules with its docs and its exported functions with its docs