Skip to content

Commit

Permalink
Merge pull request elixir-editors#227 from akash-akya/fix-project-loa…
Browse files Browse the repository at this point in the history
…ding

Load all modules after first build
  • Loading branch information
axelson authored May 16, 2020
2 parents ead80a4 + ea23425 commit 6ddcd32
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 12 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, Diagnostics}

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 @@ -18,10 +18,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

diagnostics = Diagnostics.normalize(diagnostics, root_path)
Server.build_finished(parent, {status, mixfile_diagnostics ++ 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
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 @@ -569,14 +570,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 @@ -774,7 +781,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
96 changes: 90 additions & 6 deletions apps/language_server/test/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@ defmodule ElixirLS.LanguageServer.ServerTest do
SourceFile.path_to_uri(File.cwd!())
end

setup do
{:ok, server} = Server.start_link()
{:ok, packet_capture} = PacketCapture.start_link(self())
Process.group_leader(server, packet_capture)

{:ok, %{server: server}}
setup context do
unless context[:skip_server] do
server = start_supervised!({Server, nil})
packet_capture = start_supervised!({PacketCapture, self()})
Process.group_leader(server, packet_capture)
{:ok, %{server: server}}
else
:ok
end
end

test "textDocument/didChange when the client hasn't claimed ownership with textDocument/didOpen",
Expand Down Expand Up @@ -357,4 +360,85 @@ defmodule ElixirLS.LanguageServer.ServerTest do
]) = resp
end)
end

@tag :skip_server
test "loading of umbrella app dependencies" 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
with_new_server(fn server ->
initialize(server)
wait_until_compiled(server)
end)

# unload App2.Foo
purge([App2.Foo])

# re-visiting the same project
with_new_server(fn server ->
initialize(server)
wait_until_compiled(server)

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(3, uri, 1, 23))

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

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

defp with_new_server(func) do
server = start_supervised!({Server, nil})
packet_capture = start_supervised!({PacketCapture, self()})
Process.group_leader(server, packet_capture)

try do
func.(server)
after
stop_supervised(Server)
stop_supervised(PacketCapture)
flush_mailbox()
end
end

defp flush_mailbox do
receive do
_ -> flush_mailbox()
after
0 -> :ok
end
end

defp wait_until_compiled(pid) do
state = :sys.get_state(pid)

if state.build_running? do
Process.sleep(500)
wait_until_compiled(pid)
end
end
end

0 comments on commit 6ddcd32

Please sign in to comment.