Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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