1
1
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
3
4
4
5
def build ( parent , root_path , opts ) when is_binary ( root_path ) do
5
6
if Path . absname ( File . cwd! ( ) ) != Path . absname ( root_path ) do
@@ -28,8 +29,8 @@ defmodule ElixirLS.LanguageServer.Build do
28
29
purge_consolidated_protocols ( )
29
30
{ status , diagnostics } = compile ( )
30
31
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 ( )
33
34
end
34
35
35
36
diagnostics = Diagnostics . normalize ( diagnostics , root_path )
@@ -49,79 +50,12 @@ defmodule ElixirLS.LanguageServer.Build do
49
50
end
50
51
end
51
52
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
-
119
53
def with_build_lock ( func ) do
120
54
:global . trans ( { __MODULE__ , self ( ) } , func )
121
55
end
122
56
123
57
defp reload_project do
124
- mixfile = Path . absname ( System . get_env ( "MIX_EXS" ) || "mix.exs" )
58
+ mixfile = Path . absname ( MixfileHelpers . mix_exs )
125
59
126
60
if File . exists? ( mixfile ) do
127
61
# FIXME: Private API
@@ -145,13 +79,13 @@ defmodule ElixirLS.LanguageServer.Build do
145
79
{ status , diagnostics } =
146
80
case Kernel.ParallelCompiler . compile ( [ mixfile ] ) do
147
81
{ :ok , _ , warnings } ->
148
- { :ok , Enum . map ( warnings , & mixfile_diagnostic ( & 1 , :warning ) ) }
82
+ { :ok , Enum . map ( warnings , & Diagnostics . mixfile_diagnostic ( & 1 , :warning ) ) }
149
83
150
84
{ :error , errors , warnings } ->
151
85
{
152
86
: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 ) )
155
89
}
156
90
end
157
91
@@ -174,7 +108,11 @@ defmodule ElixirLS.LanguageServer.Build do
174
108
end
175
109
end
176
110
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
178
116
apps =
179
117
cond do
180
118
Mix.Project . umbrella? ( ) ->
@@ -278,92 +216,4 @@ defmodule ElixirLS.LanguageServer.Build do
278
216
279
217
:ok
280
218
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
369
219
end
0 commit comments