Skip to content

Bring in work from head fork #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Jul 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
0ab223e
Fixes incorrect dialyzer warning. Resolves #85
znorris Jul 3, 2018
1ef05b9
Merge pull request #86 from znorris/fix-dialyzer-warning
JakeBecker Jul 3, 2018
e0d4307
Disable custom logger backend on Elixir 1.7.0-dev due to incompatibility
JakeBecker Jul 6, 2018
f7797d7
Merge pull request #88 from JakeBecker/elixir-1.7-compat
JakeBecker Jul 6, 2018
f3bf2c2
Update version numbers
JakeBecker Jul 6, 2018
b278880
Reduce required Elixir version to 1.6.5 since it contains the functio…
JakeBecker Jul 6, 2018
622c8c0
Don't kill VM on errors because this disappears the Output pane in VS…
JakeBecker Jul 6, 2018
5077994
Improve Dialyzer compatibility with Elixir 1.7 and Erlang OTP 21
JakeBecker Jul 6, 2018
67e5632
Declare Elixir 1.6.0 as the minimum supported version
JakeBecker Jul 6, 2018
d7d08ec
Remove check for variable name in test because it depends on Elixir v…
JakeBecker Jul 6, 2018
d4a0ca4
Update Elixir Sense
JakeBecker Jul 6, 2018
40618e2
Use the new API for go-to-definition
JakeBecker Jul 6, 2018
6c2a0c4
Fix markdown error
JakeBecker Jul 6, 2018
ed233e4
Path file path instead of contents to :dialyzer_utils.get_core_from_b…
JakeBecker Jul 13, 2018
2de8588
Exclude from analysis beam files where we can't get the debug info
JakeBecker Jul 13, 2018
12d4f58
Merge pull request #95 from JakeBecker/dialyzer-crash
JakeBecker Jul 13, 2018
7e0c08e
Remove unneccessary check for existing PLT file when determining Dial…
JakeBecker Jul 13, 2018
5c3bf81
Bump all version numbers up to 0.2.21 to be in sync with VS Code plugin
JakeBecker Jul 13, 2018
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ The Elixir Language Server provides a server that runs in the background, provid
## Supported versions

Elixir:
- 1.4 minimum
- \>= 1.6 recommended. Required for reporting of build warnings and errors, and for code formatting support.
- 1.6.0 minimum
- \>= 1.6.6 recommended

Erlang:
- OTP 18 minimum
- OTP 20 recommended. >= OTP 19 is required for debugger support, and OTP 20 is recommended for automatic incremental Dialyzer integration.
- \>= OTP 20 recommended

You may want to install Elixir and Erlang from source, using the [kiex](https://github.com/taylor/kiex) and [kerl](https://github.com/kerl/kerl) tools. This will let you go-to-definition for core Elixir and Erlang modules.

Expand Down
8 changes: 3 additions & 5 deletions apps/debugger/lib/debugger.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,10 @@ defmodule ElixirLS.Debugger do
end

def stop(_state) do
# If IO is being intercepted (meaning we're running in production), allow time to flush errors
# then kill the VM
# If we crash, try to print a message. We don't kill the VM because that makes it harder to
# figure out what happened
if ElixirLS.Utils.WireProtocol.io_intercepted?() do
IO.puts("Stopping ElixirLS debugger due to errors.")
:timer.sleep(100)
:init.stop(1)
IO.puts(:standard_error, "ElixirLS debugger has crashed")
end

:ok
Expand Down
4 changes: 2 additions & 2 deletions apps/debugger/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ defmodule ElixirLS.Debugger.Mixfile do
def project do
[
app: :debugger,
version: "0.2.18",
version: "0.2.21",
build_path: "../../_build",
config_path: "config/config.exs",
deps_path: "../../deps",
lockfile: "../../mix.lock",
elixir: ">= 1.7.0-dev",
elixir: ">= 1.6.5",
build_embedded: false,
start_permanent: true,
build_per_environment: false,
Expand Down
2 changes: 1 addition & 1 deletion apps/debugger/test/debugger_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ defmodule ElixirLS.Debugger.ServerTest do
assert_receive response(_, 8, "variables", %{
"variables" => [
%{
"name" => "_x@1",
"name" => _,
"type" => "integer",
"value" => "2",
"variablesReference" => 0
Expand Down
4 changes: 2 additions & 2 deletions apps/elixir_ls_utils/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ defmodule ElixirLS.Utils.Mixfile do
def project do
[
app: :elixir_ls_utils,
version: "0.2.18",
version: "0.2.21",
build_path: "../../_build",
config_path: "../../config/config.exs",
deps_path: "../../deps",
elixirc_paths: ["lib", "test/support"],
lockfile: "../../mix.lock",
elixir: ">= 1.7.0-dev",
elixir: ">= 1.6.5",
build_embedded: false,
start_permanent: false,
build_per_environment: false,
Expand Down
11 changes: 6 additions & 5 deletions apps/language_server/lib/language_server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ defmodule ElixirLS.LanguageServer do
end

def stop(_state) do
# If IO is being intercepted (meaning we're running in production), allow time to flush errors
# then kill the VM
# VS Code, unfortunately, disappears the "Output" pane if the server terminates so we can't see
# an error message, so we can't kill the VM... We attempt to show an error message instead.
if ElixirLS.Utils.WireProtocol.io_intercepted?() do
IO.puts("Stopping ElixirLS due to errors.")
:timer.sleep(100)
:init.stop(1)
ElixirLS.LanguageServer.JsonRpc.show_message(
:error,
"ElixirLS has crashed. See Output panel."
)
end

:ok
Expand Down
8 changes: 6 additions & 2 deletions apps/language_server/lib/language_server/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@ defmodule ElixirLS.LanguageServer.CLI do
WireProtocol.intercept_output(&JsonRpc.print/1, &JsonRpc.print_err/1)
Launch.start_mix()

configure_logger()
Application.ensure_all_started(:language_server, :permanent)
# TODO: Figure out a safe way to use the custom logger backend in Elixir 1.7
unless Version.match?(System.version(), ">= 1.7.0-dev") do
configure_logger()
end

Application.ensure_all_started(:language_server, :temporary)
IO.puts("Started ElixirLS")

Mix.shell(ElixirLS.LanguageServer.MixShell)
Expand Down
51 changes: 26 additions & 25 deletions apps/language_server/lib/language_server/dialyzer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,22 @@ defmodule ElixirLS.LanguageServer.Dialyzer do

# Client API

def supported? do
{otp_version, _} = Integer.parse(to_string(:erlang.system_info(:otp_release)))
otp_version >= 20
def check_support do
otp_release = String.to_integer(System.otp_release())

cond do
otp_release < 20 ->
{:error,
"Dialyzer integration requires Erlang OTP 20 or higher (Currently OTP #{otp_release})"}

not dialyzable?(System) ->
{:error,
"Dialyzer is disabled because core Elixir modules are missing debug info. " <>
"You may need to recompile Elixir with Erlang >= OTP 20"}

true ->
:ok
end
end

def start_link({parent, root_path}) do
Expand Down Expand Up @@ -198,19 +211,21 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
changed_contents =
Task.async_stream(changed, fn file ->
content = File.read!(file)
{file, content, module_md5(content)}
{file, content, module_md5(file)}
end)
|> Enum.into([])

file_changes =
Enum.reduce(changed_contents, file_changes, fn {:ok, {file, content, hash}}, file_changes ->
if hash == md5[file] do
if is_nil(hash) or hash == md5[file] do
Map.delete(file_changes, file)
else
Map.put(file_changes, file, {content, hash})
end
end)

removed_files = Enum.uniq((removed_files ++ removed) -- changed)
undialyzable = for {:ok, {file, _, nil}} <- changed_contents, do: file
removed_files = Enum.uniq(removed_files ++ removed ++ undialyzable -- changed)
{removed_files, file_changes}
end

Expand Down Expand Up @@ -344,31 +359,17 @@ defmodule ElixirLS.LanguageServer.Dialyzer do
File.exists?(path) and String.starts_with?(Path.absname(path), File.cwd!())
end

defp module_md5(content) do
case get_core_from_beam(content) do
nil ->
nil

core ->
defp module_md5(file) do
case :dialyzer_utils.get_core_from_beam(to_charlist(file)) do
{:ok, core} ->
core_bin = :erlang.term_to_binary(core)
:crypto.hash(:md5, core_bin)
end
end

defp get_core_from_beam(content) do
with {:ok, {_, kw}} when is_list(kw) <- :beam_lib.chunks(content, [:abstract_code]),
code when code != :no_abstract_code <- Keyword.get(kw, :abstract_code) do
code
else
_ -> nil
{:error, _} ->
nil
end
end

defp dialyzable?(module) do
file = :code.which(module)
is_list(file) and get_core_from_beam(file) != nil
end

defp to_diagnostics(warnings_map, warn_opts) do
tags_enabled = Analyzer.matching_tags(warn_opts)

Expand Down
10 changes: 5 additions & 5 deletions apps/language_server/lib/language_server/dialyzer/manifest.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do

def read(root_path) do
manifest_path = manifest_path(root_path)
timestamp = Mix.Utils.last_modified(manifest_path)
timestamp = normalize_timestamp(Mix.Utils.last_modified(manifest_path))

{
@manifest_vsn,
Expand Down Expand Up @@ -102,6 +102,10 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
_ -> build_elixir_plt()
end

def elixir_plt_path() do
Path.join([Mix.Utils.mix_home(), "elixir-ls-#{otp_vsn()}_elixir-#{System.version()}"])
end

defp build_elixir_plt() do
JsonRpc.show_message(:info, "Building core Elixir PLT. This will take a few minutes.")

Expand All @@ -126,10 +130,6 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Manifest do
:dialyzer_plt.from_file(to_charlist(elixir_plt_path()))
end

defp elixir_plt_path() do
Path.join([Mix.Utils.mix_home(), "elixir-ls-#{otp_vsn()}_elixir-#{System.version()}"])
end

defp otp_vsn() do
major = :erlang.system_info(:otp_release) |> List.to_string()
vsn_file = Path.join([:code.root_dir(), "releases", major, "OTP_VERSION"])
Expand Down
16 changes: 15 additions & 1 deletion apps/language_server/lib/language_server/dialyzer/utils.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
defmodule ElixirLS.LanguageServer.Dialyzer.Utils do
@epoch_gregorian_seconds 62_167_219_200

def dialyzable?(module) do
file = :code.which(module)
is_list(file) and match?({:ok, _}, :dialyzer_utils.get_core_from_beam(file))
end

def pathname_to_module(path) do
String.to_atom(Path.basename(path, ".beam"))
end
Expand All @@ -11,7 +18,7 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Utils do

def expand_references([module | rest], exclude, result) do
result =
if module in result or module in exclude do
if module in result or module in exclude or not dialyzable?(module) do
result
else
result = MapSet.put(result, module)
Expand All @@ -21,6 +28,13 @@ defmodule ElixirLS.LanguageServer.Dialyzer.Utils do
expand_references(rest, exclude, result)
end

# Mix.Utils.last_modified/1 returns a :calendar.universal_time() in Elixir < 1.7,
# otherwise posix time, so we normalize to a :calendar.universal_time()
def normalize_timestamp(timestamp) when is_integer(timestamp),
do: :calendar.gregorian_seconds_to_datetime(timestamp + @epoch_gregorian_seconds)

def normalize_timestamp(timestamp), do: timestamp

defp module_references(mod) do
try do
forms = :forms.read(mod)
Expand Down
18 changes: 12 additions & 6 deletions apps/language_server/lib/language_server/providers/definition.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@ defmodule ElixirLS.LanguageServer.Providers.Definition do
"""

alias ElixirLS.LanguageServer.SourceFile
alias ElixirSense.Providers.Definition.Location

def definition(text, line, character) do
def definition(uri, text, line, character) do
case ElixirSense.definition(text, line + 1, character + 1) do
{"non_existing", nil} ->
%Location{found: false} ->
{:ok, []}

{file, line} ->
%Location{file: file, line: line, column: column} ->
line = line || 0
uri = SourceFile.path_to_uri(file)
column = column || 0
uri =
case file do
nil -> uri
_ -> SourceFile.path_to_uri(file)
end

{:ok,
%{
"uri" => uri,
"range" => %{
"start" => %{"line" => line - 1, "character" => 0},
"end" => %{"line" => line - 1, "character" => 0}
"start" => %{"line" => line - 1, "character" => column - 1},
"end" => %{"line" => line - 1, "character" => column - 1}
}
}}
end
Expand Down
45 changes: 20 additions & 25 deletions apps/language_server/lib/language_server/server.ex
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ defmodule ElixirLS.LanguageServer.Server do

defp handle_request(definition_req(_id, uri, line, character), state) do
fun = fn ->
Definition.definition(state.source_files[uri].text, line, character)
Definition.definition(uri, state.source_files[uri].text, line, character)
end

{:async, fun, state}
Expand Down Expand Up @@ -523,7 +523,7 @@ defmodule ElixirLS.LanguageServer.Server do
end

defp dialyzer_enabled?(state) do
Dialyzer.supported?() and build_enabled?(state) and state.dialyzer_sup != nil
Dialyzer.check_support() == :ok and build_enabled?(state) and state.dialyzer_sup != nil
end

defp publish_diagnostics(new_diagnostics, old_diagnostics, source_files) do
Expand All @@ -536,39 +536,34 @@ defmodule ElixirLS.LanguageServer.Server do
end

defp show_version_warnings do
unless Version.match?(System.version(), ">= 1.6.0-dev") do
unless Version.match?(System.version(), ">= 1.6.0") do
JsonRpc.show_message(
:info,
"Upgrade to Elixir >= 1.6 for build warnings and errors and for code formatting. " <>
"(Currently v#{System.version()})"
:warning,
"Elixir versions below 1.6 are not supported. (Currently v#{System.version()})"
)
end

{otp_version, _} = Integer.parse(to_string(:erlang.system_info(:otp_release)))

warning =
cond do
otp_version < 19 ->
"Upgrade Erlang to version OTP 20 for debugging support and automatic, " <>
"incremental Dialyzer integration."

otp_version < 20 ->
"Upgrade Erlang to version OTP 20 for automatic, incremental Dialyzer integration."
otp_release = String.to_integer(System.otp_release())

otp_version > 20 ->
"ElixirLS Dialyzer integration has not been tested with Erlang versions other than " <>
"OTP 20. To disable, set \"elixirLS.enableDialyzer\" to false."
if otp_release < 19 do
JsonRpc.show_message(
:info,
"Upgrade Erlang to version OTP 20 for debugging support (Currently OTP #{otp_release})"
)
end

true ->
nil
end
case Dialyzer.check_support() do
:ok -> :ok
{:error, msg} -> JsonRpc.show_message(:info, msg)
end

if warning != nil,
do: JsonRpc.show_message(:info, warning <> " (Currently OTP #{otp_version})")
:ok
end

defp set_settings(state, settings) do
enable_dialyzer = Dialyzer.supported?() && Map.get(settings, "dialyzerEnabled", true)
enable_dialyzer =
Dialyzer.check_support() == :ok && Map.get(settings, "dialyzerEnabled", true)

mix_env = Map.get(settings, "mixEnv", "test")
project_dir = Map.get(settings, "projectDir")

Expand Down
4 changes: 2 additions & 2 deletions apps/language_server/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ defmodule ElixirLS.LanguageServer.Mixfile do
def project do
[
app: :language_server,
version: "0.2.18",
elixir: ">= 1.7.0-dev",
version: "0.2.21",
elixir: ">= 1.6.5",
build_path: "../../_build",
config_path: "config/config.exs",
deps_path: "../../deps",
Expand Down
6 changes: 4 additions & 2 deletions apps/language_server/test/server_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,13 @@ defmodule ElixirLS.LanguageServer.ServerTest do

assert_receive response(1, %{
"range" => %{
"end" => %{"character" => 0, "line" => 0},
"start" => %{"character" => 0, "line" => 0}
"end" => %{"character" => column, "line" => 0},
"start" => %{"character" => column, "line" => 0}
},
"uri" => ^uri
})

assert column > 0
end

test "requests cancellation", %{server: server} do
Expand Down
Loading