Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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}

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)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the server process is handling a message, then simply calling :sys.get_state will make the calling process block until the server process is ready. If the server is waiting for an async message from other process you can call :sys.get_state in a loop with short sleeps until the state returned indicates that async task is completed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok. I'll will try to update test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, please check

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good


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