Skip to content

Commit

Permalink
Load all modules after first build
Browse files Browse the repository at this point in the history
  • Loading branch information
akash-akya committed Apr 28, 2020
1 parent ea4b938 commit cadd374
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 6 deletions.
35 changes: 32 additions & 3 deletions apps/language_server/lib/language_server/build.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule ElixirLS.LanguageServer.Build do
alias ElixirLS.LanguageServer.{Server, JsonRpc, SourceFile}

def build(parent, root_path, fetch_deps?) do
def build(parent, root_path, opts) do
if Path.absname(File.cwd!()) != Path.absname(root_path) do
IO.puts("Skipping build because cwd changed from #{root_path} to #{File.cwd!()}")
{nil, nil}
Expand All @@ -19,10 +19,16 @@ defmodule ElixirLS.LanguageServer.Build do
case reload_project() do
{:ok, mixfile_diagnostics} ->
# FIXME: Private API
if fetch_deps? and Mix.Dep.load_on_environment([]) != prev_deps,
do: fetch_deps()
if Keyword.get(opts, :fetch_deps?) and
Mix.Dep.load_on_environment([]) != prev_deps,
do: fetch_deps()

{status, diagnostics} = compile()

if status in [:ok, :noop] and Keyword.get(opts, :load_all_modules?) do
load_all_modules()
end

Server.build_finished(parent, {status, mixfile_diagnostics ++ diagnostics})

{:error, mixfile_diagnostics} ->
Expand Down Expand Up @@ -160,6 +166,29 @@ defmodule ElixirLS.LanguageServer.Build do
end
end

defp load_all_modules do
apps =
cond do
Mix.Project.umbrella?() ->
Mix.Project.apps_paths() |> Map.keys()

app = Keyword.get(Mix.Project.config(), :app) ->
[app]

true ->
[]
end

Enum.each(apps, fn app ->
true = Code.prepend_path(Path.join(Mix.Project.build_path(), "lib/#{app}/ebin"))

case Application.load(app) do
:ok -> :ok
{:error, {:already_loaded, _}} -> :ok
end
end)
end

defp compile do
case Mix.Task.run("compile", ["--return-errors", "--ignore-module-conflict"]) do
{status, diagnostics} when status in [:ok, :error, :noop] and is_list(diagnostics) ->
Expand Down
12 changes: 12 additions & 0 deletions apps/language_server/lib/language_server/protocol.ex
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ defmodule ElixirLS.LanguageServer.Protocol do
end
end

defmacro shutdown_req(id) do
quote do
request(unquote(id), "shutdown", %{})
end
end

defmacro exit_req(id) do
quote do
request(unquote(id), "exit", %{})
end
end

# Other utilities

defmacro range(start_line, start_character, end_line, end_character) do
Expand Down
13 changes: 10 additions & 3 deletions apps/language_server/lib/language_server/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ defmodule ElixirLS.LanguageServer.Server do
build_diagnostics: [],
dialyzer_diagnostics: [],
needs_build?: false,
load_all_modules?: false,
build_running?: false,
analysis_ready?: false,
received_shutdown?: false,
Expand Down Expand Up @@ -567,14 +568,20 @@ defmodule ElixirLS.LanguageServer.Server do
defp trigger_build(state) do
if build_enabled?(state) and not state.build_running? do
fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true)
{_pid, build_ref} = Build.build(self(), state.project_dir, fetch_deps?)

{_pid, build_ref} =
Build.build(self(), state.project_dir,
fetch_deps?: fetch_deps?,
load_all_modules?: state.load_all_modules?
)

%__MODULE__{
state
| build_ref: build_ref,
needs_build?: false,
build_running?: true,
analysis_ready?: false
analysis_ready?: false,
load_all_modules?: false
}
else
%__MODULE__{state | needs_build?: true, analysis_ready?: false}
Expand Down Expand Up @@ -771,7 +778,7 @@ defmodule ElixirLS.LanguageServer.Server do

is_nil(prev_project_dir) ->
File.cd!(project_dir)
put_in(state.project_dir, project_dir)
Map.merge(state, %{project_dir: project_dir, load_all_modules?: true})

prev_project_dir != project_dir ->
JsonRpc.show_message(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule App2.Foo do
def hello do
:foo
end
end
52 changes: 52 additions & 0 deletions apps/language_server/test/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -336,4 +336,56 @@ defmodule ElixirLS.LanguageServer.ServerTest do
]) = resp
end)
end

test "loading of umbrella app dependencies", %{server: server} do
in_fixture(__DIR__, "umbrella", fn ->
# We test this by opening the umbrella project twice.
# First to compile the applications and build the cache.
# Second time to see if loads modules
initialize(server)

# Upon first vist, server complies and loads all umbrella applications and modules
Process.sleep(1_500)

Server.receive_packet(server, shutdown_req(2))
assert response(2, nil) = assert_receive(%{"id" => 2}, 5000)
Server.receive_packet(server, exit_req(3))

Process.sleep(500)
# unload App2.Foo
purge([App2.Foo])

# Upon re-visiting, server does not attempt to compile umbrella applications
initialize(server)
Process.sleep(1_500)

file_path = "apps/app1/lib/bar.ex"
uri = SourceFile.path_to_uri(file_path)

code = """
defmodule Bar do
def fnuc, do: App2.Fo
#____________________^
end
"""

Server.receive_packet(server, did_open(uri, "elixir", 1, code))
Server.receive_packet(server, completion_req(2, uri, 1, 23))

resp = assert_receive(%{"id" => 2}, 5000)

assert response(2, %{
"isIncomplete" => false,
"items" => [
%{
"detail" => "module",
"documentation" => _,
"kind" => 9,
"label" => "Foo"
}
| _
]
}) = resp
end)
end
end

0 comments on commit cadd374

Please sign in to comment.