Skip to content

Commit 6ddcd32

Browse files
authored
Merge pull request #227 from akash-akya/fix-project-loading
Load all modules after first build
2 parents ead80a4 + ea23425 commit 6ddcd32

File tree

4 files changed

+137
-12
lines changed

4 files changed

+137
-12
lines changed

apps/language_server/lib/language_server/build.ex

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule ElixirLS.LanguageServer.Build do
22
alias ElixirLS.LanguageServer.{Server, JsonRpc, SourceFile, Diagnostics}
33

4-
def build(parent, root_path, fetch_deps?) do
4+
def build(parent, root_path, opts) do
55
if Path.absname(File.cwd!()) != Path.absname(root_path) do
66
IO.puts("Skipping build because cwd changed from #{root_path} to #{File.cwd!()}")
77
{nil, nil}
@@ -18,10 +18,16 @@ defmodule ElixirLS.LanguageServer.Build do
1818
case reload_project() do
1919
{:ok, mixfile_diagnostics} ->
2020
# FIXME: Private API
21-
if fetch_deps? and Mix.Dep.load_on_environment([]) != prev_deps,
22-
do: fetch_deps()
21+
if Keyword.get(opts, :fetch_deps?) and
22+
Mix.Dep.load_on_environment([]) != prev_deps,
23+
do: fetch_deps()
2324

2425
{status, diagnostics} = compile()
26+
27+
if status in [:ok, :noop] and Keyword.get(opts, :load_all_modules?) do
28+
load_all_modules()
29+
end
30+
2531
diagnostics = Diagnostics.normalize(diagnostics, root_path)
2632
Server.build_finished(parent, {status, mixfile_diagnostics ++ diagnostics})
2733

@@ -160,6 +166,29 @@ defmodule ElixirLS.LanguageServer.Build do
160166
end
161167
end
162168

169+
defp load_all_modules do
170+
apps =
171+
cond do
172+
Mix.Project.umbrella?() ->
173+
Mix.Project.apps_paths() |> Map.keys()
174+
175+
app = Keyword.get(Mix.Project.config(), :app) ->
176+
[app]
177+
178+
true ->
179+
[]
180+
end
181+
182+
Enum.each(apps, fn app ->
183+
true = Code.prepend_path(Path.join(Mix.Project.build_path(), "lib/#{app}/ebin"))
184+
185+
case Application.load(app) do
186+
:ok -> :ok
187+
{:error, {:already_loaded, _}} -> :ok
188+
end
189+
end)
190+
end
191+
163192
defp compile do
164193
case Mix.Task.run("compile", ["--return-errors", "--ignore-module-conflict"]) do
165194
{status, diagnostics} when status in [:ok, :error, :noop] and is_list(diagnostics) ->

apps/language_server/lib/language_server/server.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ defmodule ElixirLS.LanguageServer.Server do
4444
build_diagnostics: [],
4545
dialyzer_diagnostics: [],
4646
needs_build?: false,
47+
load_all_modules?: false,
4748
build_running?: false,
4849
analysis_ready?: false,
4950
received_shutdown?: false,
@@ -569,14 +570,20 @@ defmodule ElixirLS.LanguageServer.Server do
569570
defp trigger_build(state) do
570571
if build_enabled?(state) and not state.build_running? do
571572
fetch_deps? = Map.get(state.settings || %{}, "fetchDeps", true)
572-
{_pid, build_ref} = Build.build(self(), state.project_dir, fetch_deps?)
573+
574+
{_pid, build_ref} =
575+
Build.build(self(), state.project_dir,
576+
fetch_deps?: fetch_deps?,
577+
load_all_modules?: state.load_all_modules?
578+
)
573579

574580
%__MODULE__{
575581
state
576582
| build_ref: build_ref,
577583
needs_build?: false,
578584
build_running?: true,
579-
analysis_ready?: false
585+
analysis_ready?: false,
586+
load_all_modules?: false
580587
}
581588
else
582589
%__MODULE__{state | needs_build?: true, analysis_ready?: false}
@@ -774,7 +781,7 @@ defmodule ElixirLS.LanguageServer.Server do
774781

775782
is_nil(prev_project_dir) ->
776783
File.cd!(project_dir)
777-
put_in(state.project_dir, project_dir)
784+
Map.merge(state, %{project_dir: project_dir, load_all_modules?: true})
778785

779786
prev_project_dir != project_dir ->
780787
JsonRpc.show_message(
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule App2.Foo do
2+
def hello do
3+
:foo
4+
end
5+
end

apps/language_server/test/server_test.exs

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ defmodule ElixirLS.LanguageServer.ServerTest do
2020
SourceFile.path_to_uri(File.cwd!())
2121
end
2222

23-
setup do
24-
{:ok, server} = Server.start_link()
25-
{:ok, packet_capture} = PacketCapture.start_link(self())
26-
Process.group_leader(server, packet_capture)
27-
28-
{:ok, %{server: server}}
23+
setup context do
24+
unless context[:skip_server] do
25+
server = start_supervised!({Server, nil})
26+
packet_capture = start_supervised!({PacketCapture, self()})
27+
Process.group_leader(server, packet_capture)
28+
{:ok, %{server: server}}
29+
else
30+
:ok
31+
end
2932
end
3033

3134
test "textDocument/didChange when the client hasn't claimed ownership with textDocument/didOpen",
@@ -357,4 +360,85 @@ defmodule ElixirLS.LanguageServer.ServerTest do
357360
]) = resp
358361
end)
359362
end
363+
364+
@tag :skip_server
365+
test "loading of umbrella app dependencies" do
366+
in_fixture(__DIR__, "umbrella", fn ->
367+
# We test this by opening the umbrella project twice.
368+
# First to compile the applications and build the cache.
369+
# Second time to see if loads modules
370+
with_new_server(fn server ->
371+
initialize(server)
372+
wait_until_compiled(server)
373+
end)
374+
375+
# unload App2.Foo
376+
purge([App2.Foo])
377+
378+
# re-visiting the same project
379+
with_new_server(fn server ->
380+
initialize(server)
381+
wait_until_compiled(server)
382+
383+
file_path = "apps/app1/lib/bar.ex"
384+
uri = SourceFile.path_to_uri(file_path)
385+
386+
code = """
387+
defmodule Bar do
388+
def fnuc, do: App2.Fo
389+
# ^
390+
end
391+
"""
392+
393+
Server.receive_packet(server, did_open(uri, "elixir", 1, code))
394+
Server.receive_packet(server, completion_req(3, uri, 1, 23))
395+
396+
resp = assert_receive(%{"id" => 3}, 5000)
397+
398+
assert response(3, %{
399+
"isIncomplete" => false,
400+
"items" => [
401+
%{
402+
"detail" => "module",
403+
"documentation" => _,
404+
"kind" => 9,
405+
"label" => "Foo"
406+
}
407+
| _
408+
]
409+
}) = resp
410+
end)
411+
end)
412+
end
413+
414+
defp with_new_server(func) do
415+
server = start_supervised!({Server, nil})
416+
packet_capture = start_supervised!({PacketCapture, self()})
417+
Process.group_leader(server, packet_capture)
418+
419+
try do
420+
func.(server)
421+
after
422+
stop_supervised(Server)
423+
stop_supervised(PacketCapture)
424+
flush_mailbox()
425+
end
426+
end
427+
428+
defp flush_mailbox do
429+
receive do
430+
_ -> flush_mailbox()
431+
after
432+
0 -> :ok
433+
end
434+
end
435+
436+
defp wait_until_compiled(pid) do
437+
state = :sys.get_state(pid)
438+
439+
if state.build_running? do
440+
Process.sleep(500)
441+
wait_until_compiled(pid)
442+
end
443+
end
360444
end

0 commit comments

Comments
 (0)