Skip to content

Commit c100485

Browse files
authored
Build cleanup (#698)
* check if loading apps is still needed * Revert "check if loading apps is still needed" This reverts commit d03cfe5. * rename function to match what it actually do * extract diagnostic related code * extract common mix.exs code * fix tests
1 parent ec1b31c commit c100485

File tree

8 files changed

+190
-177
lines changed

8 files changed

+190
-177
lines changed

apps/elixir_ls_debugger/lib/debugger/server.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ defmodule ElixirLS.Debugger.Server do
2525
}
2626

2727
alias ElixirLS.Debugger.Stacktrace.Frame
28+
alias ElixirLS.Utils.MixfileHelpers
2829
use GenServer
2930
use Protocol
3031

@@ -955,11 +956,11 @@ defmodule ElixirLS.Debugger.Server do
955956
File.cd!(project_dir)
956957

957958
# Mixfile may already be loaded depending on cwd when launching debugger task
958-
mixfile = Path.absname(System.get_env("MIX_EXS") || "mix.exs")
959+
mixfile = Path.absname(MixfileHelpers.mix_exs())
959960

960961
# FIXME: Private API
961962
unless match?(%{file: ^mixfile}, Mix.ProjectStack.peek()) do
962-
Code.compile_file(System.get_env("MIX_EXS") || "mix.exs")
963+
Code.compile_file(MixfileHelpers.mix_exs())
963964
end
964965

965966
task = task || Mix.Project.config()[:default_task]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
defmodule ElixirLS.Utils.MixfileHelpers do
2+
def mix_exs do
3+
System.get_env("MIX_EXS") || "mix.exs"
4+
end
5+
end

apps/elixir_ls_utils/test/support/mix_test.case.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
defmodule ElixirLS.Utils.MixTest.Case do
22
# This module is based heavily on MixTest.Case in Elixir's tests
33
use ExUnit.CaseTemplate
4+
alias ElixirLS.Utils.MixfileHelpers
45

56
using do
67
quote do
@@ -103,7 +104,7 @@ defmodule ElixirLS.Utils.MixTest.Case do
103104
clear_mix_cache()
104105

105106
# Attempt to purge mixfiles for dependencies to avoid module redefinition warnings
106-
mix_exs = System.get_env("MIX_EXS") || "mix.exs"
107+
mix_exs = MixfileHelpers.mix_exs()
107108

108109
for {mod, :in_memory} <- :code.all_loaded(),
109110
source = mod.module_info[:compile][:source],
Lines changed: 13 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
defmodule ElixirLS.LanguageServer.Build do
2-
alias ElixirLS.LanguageServer.{Server, JsonRpc, SourceFile, Diagnostics}
2+
alias ElixirLS.LanguageServer.{Server, JsonRpc, Diagnostics}
3+
alias ElixirLS.Utils.MixfileHelpers
34

45
def build(parent, root_path, opts) when is_binary(root_path) do
56
if Path.absname(File.cwd!()) != Path.absname(root_path) do
@@ -28,8 +29,8 @@ defmodule ElixirLS.LanguageServer.Build do
2829
purge_consolidated_protocols()
2930
{status, diagnostics} = compile()
3031

31-
if status in [:ok, :noop] and Keyword.get(opts, :load_all_modules?) do
32-
load_all_modules()
32+
if status in [:ok, :noop] and Keyword.get(opts, :load_all_mix_applications?) do
33+
load_all_mix_applications()
3334
end
3435

3536
diagnostics = Diagnostics.normalize(diagnostics, root_path)
@@ -49,79 +50,12 @@ defmodule ElixirLS.LanguageServer.Build do
4950
end
5051
end
5152

52-
def publish_file_diagnostics(uri, all_diagnostics, source_file) do
53-
diagnostics =
54-
all_diagnostics
55-
|> Enum.filter(&(SourceFile.path_to_uri(&1.file) == uri))
56-
|> Enum.sort_by(fn %{position: position} -> position end)
57-
58-
diagnostics_json =
59-
for diagnostic <- diagnostics do
60-
severity =
61-
case diagnostic.severity do
62-
:error -> 1
63-
:warning -> 2
64-
:information -> 3
65-
:hint -> 4
66-
end
67-
68-
message =
69-
case diagnostic.message do
70-
m when is_binary(m) -> m
71-
m when is_list(m) -> m |> Enum.join("\n")
72-
end
73-
74-
%{
75-
"message" => message,
76-
"severity" => severity,
77-
"range" => range(diagnostic.position, source_file),
78-
"source" => diagnostic.compiler_name
79-
}
80-
end
81-
82-
JsonRpc.notify("textDocument/publishDiagnostics", %{
83-
"uri" => uri,
84-
"diagnostics" => diagnostics_json
85-
})
86-
end
87-
88-
def mixfile_diagnostic({file, line, message}, severity) do
89-
%Mix.Task.Compiler.Diagnostic{
90-
compiler_name: "ElixirLS",
91-
file: file,
92-
position: line,
93-
message: message,
94-
severity: severity
95-
}
96-
end
97-
98-
def exception_to_diagnostic(error) do
99-
msg =
100-
case error do
101-
{:shutdown, 1} ->
102-
"Build failed for unknown reason. See output log."
103-
104-
_ ->
105-
Exception.format_exit(error)
106-
end
107-
108-
%Mix.Task.Compiler.Diagnostic{
109-
compiler_name: "ElixirLS",
110-
file: Path.absname(System.get_env("MIX_EXS") || "mix.exs"),
111-
# 0 means unknown
112-
position: 0,
113-
message: msg,
114-
severity: :error,
115-
details: error
116-
}
117-
end
118-
11953
def with_build_lock(func) do
12054
:global.trans({__MODULE__, self()}, func)
12155
end
12256

12357
defp reload_project do
124-
mixfile = Path.absname(System.get_env("MIX_EXS") || "mix.exs")
58+
mixfile = Path.absname(MixfileHelpers.mix_exs)
12559

12660
if File.exists?(mixfile) do
12761
# FIXME: Private API
@@ -145,13 +79,13 @@ defmodule ElixirLS.LanguageServer.Build do
14579
{status, diagnostics} =
14680
case Kernel.ParallelCompiler.compile([mixfile]) do
14781
{:ok, _, warnings} ->
148-
{:ok, Enum.map(warnings, &mixfile_diagnostic(&1, :warning))}
82+
{:ok, Enum.map(warnings, &Diagnostics.mixfile_diagnostic(&1, :warning))}
14983

15084
{:error, errors, warnings} ->
15185
{
15286
:error,
153-
Enum.map(warnings, &mixfile_diagnostic(&1, :warning)) ++
154-
Enum.map(errors, &mixfile_diagnostic(&1, :error))
87+
Enum.map(warnings, &Diagnostics.mixfile_diagnostic(&1, :warning)) ++
88+
Enum.map(errors, &Diagnostics.mixfile_diagnostic(&1, :error))
15589
}
15690
end
15791

@@ -174,7 +108,11 @@ defmodule ElixirLS.LanguageServer.Build do
174108
end
175109
end
176110

177-
def load_all_modules do
111+
# TODO It looks like that function is no longer needed on elixir >= 1.11
112+
# it was added in https://github.com/elixir-lsp/elixir-ls/pull/227
113+
# removing it doesn't break tests and I'm not able to reproduce
114+
# https://github.com/elixir-lsp/elixir-ls/issues/209 on recent elixir (1.13)
115+
def load_all_mix_applications do
178116
apps =
179117
cond do
180118
Mix.Project.umbrella?() ->
@@ -278,92 +216,4 @@ defmodule ElixirLS.LanguageServer.Build do
278216

279217
:ok
280218
end
281-
282-
# for details see
283-
# https://hexdocs.pm/mix/1.13.4/Mix.Task.Compiler.Diagnostic.html#t:position/0
284-
# https://microsoft.github.io/language-server-protocol/specifications/specification-3-16/#diagnostic
285-
286-
# position is a 1 based line number
287-
# we return a range of trimmed text in that line
288-
defp range(position, source_file)
289-
when is_integer(position) and position >= 1 and not is_nil(source_file) do
290-
# line is 1 based
291-
line = position - 1
292-
text = Enum.at(SourceFile.lines(source_file), line) || ""
293-
294-
start_idx = String.length(text) - String.length(String.trim_leading(text)) + 1
295-
length = max(String.length(String.trim(text)), 1)
296-
297-
%{
298-
"start" => %{
299-
"line" => line,
300-
"character" => SourceFile.elixir_character_to_lsp(text, start_idx)
301-
},
302-
"end" => %{
303-
"line" => line,
304-
"character" => SourceFile.elixir_character_to_lsp(text, start_idx + length)
305-
}
306-
}
307-
end
308-
309-
# position is a 1 based line number and 0 based character cursor (UTF8)
310-
# we return a 0 length range exactly at that location
311-
defp range({line_start, char_start}, source_file)
312-
when line_start >= 1 and not is_nil(source_file) do
313-
lines = SourceFile.lines(source_file)
314-
# line is 1 based
315-
start_line = Enum.at(lines, line_start - 1)
316-
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based bere
317-
character = SourceFile.elixir_character_to_lsp(start_line, char_start + 1)
318-
319-
%{
320-
"start" => %{
321-
"line" => line_start - 1,
322-
"character" => character
323-
},
324-
"end" => %{
325-
"line" => line_start - 1,
326-
"character" => character
327-
}
328-
}
329-
end
330-
331-
# position is a range defined by 1 based line numbers and 0 based character cursors (UTF8)
332-
# we return exactly that range
333-
defp range({line_start, char_start, line_end, char_end}, source_file)
334-
when line_start >= 1 and line_end >= 1 and not is_nil(source_file) do
335-
lines = SourceFile.lines(source_file)
336-
# line is 1 based
337-
start_line = Enum.at(lines, line_start - 1)
338-
end_line = Enum.at(lines, line_end - 1)
339-
340-
# SourceFile.elixir_character_to_lsp assumes char to be 1 based but it's 0 based bere
341-
start_char = SourceFile.elixir_character_to_lsp(start_line, char_start + 1)
342-
end_char = SourceFile.elixir_character_to_lsp(end_line, char_end + 1)
343-
344-
%{
345-
"start" => %{
346-
"line" => line_start - 1,
347-
"character" => start_char
348-
},
349-
"end" => %{
350-
"line" => line_end - 1,
351-
"character" => end_char
352-
}
353-
}
354-
end
355-
356-
# position is 0 which means unknown
357-
# we return the full file range
358-
defp range(0, source_file) when not is_nil(source_file) do
359-
SourceFile.full_range(source_file)
360-
end
361-
362-
# source file is unknown
363-
# we discard any position information as it is meaningless
364-
# unfortunately LSP does not allow `null` range so we need to return something
365-
defp range(_, nil) do
366-
# we don't care about utf16 positions here as we send 0
367-
%{"start" => %{"line" => 0, "character" => 0}, "end" => %{"line" => 0, "character" => 0}}
368-
end
369219
end

0 commit comments

Comments
 (0)