@@ -49,6 +49,8 @@ defmodule ElixirLS.LanguageServer.Server do
49
49
:root_uri ,
50
50
:project_dir ,
51
51
:settings ,
52
+ parse_file_refs: % { } ,
53
+ parser_diagnostics: % { } ,
52
54
build_diagnostics: [ ] ,
53
55
dialyzer_diagnostics: [ ] ,
54
56
needs_build?: false ,
@@ -270,6 +272,47 @@ defmodule ElixirLS.LanguageServer.Server do
270
272
{ :noreply , state }
271
273
end
272
274
275
+ @ impl GenServer
276
+ def handle_info (
277
+ { :parse_file , uri } ,
278
+ % __MODULE__ { source_files: source_files } = state
279
+ ) do
280
+ old_diagnostics =
281
+ state . build_diagnostics ++
282
+ state . dialyzer_diagnostics ++ List . flatten ( Map . values ( state . parser_diagnostics ) )
283
+
284
+ parser_diagnostics =
285
+ case source_files [ uri ] do
286
+ % SourceFile { } = source_file ->
287
+ file = SourceFile.Path . from_uri ( uri )
288
+ case parse_file ( source_file . text , file ) do
289
+ [ ] ->
290
+ Map . delete ( state . parser_diagnostics , uri )
291
+
292
+ diagnostics ->
293
+ Map . put ( state . parser_diagnostics , uri , diagnostics )
294
+ end
295
+
296
+ nil ->
297
+ Map . delete ( state . parser_diagnostics , uri )
298
+ end
299
+
300
+ state = % {
301
+ state
302
+ | parser_diagnostics: parser_diagnostics ,
303
+ parse_file_refs: Map . delete ( state . parse_file_refs , uri )
304
+ }
305
+
306
+ publish_diagnostics (
307
+ state . build_diagnostics ++
308
+ state . dialyzer_diagnostics ++ List . flatten ( Map . values ( state . parser_diagnostics ) ) ,
309
+ old_diagnostics ,
310
+ state . source_files
311
+ )
312
+
313
+ { :noreply , state }
314
+ end
315
+
273
316
## Helpers
274
317
275
318
defp handle_notification ( notification ( "initialized" ) , state = % __MODULE__ { } ) do
@@ -381,13 +424,9 @@ defmodule ElixirLS.LanguageServer.Server do
381
424
else
382
425
source_file = % SourceFile { text: text , version: version }
383
426
384
- Diagnostics . publish_file_diagnostics (
385
- uri ,
386
- state . build_diagnostics ++ state . dialyzer_diagnostics ,
387
- source_file
388
- )
389
-
390
- put_in ( state . source_files [ uri ] , source_file )
427
+ state = put_in ( state . source_files [ uri ] , source_file )
428
+ # parse handler will emit diagnostics for opened file
429
+ trigger_parse ( state , uri , 0 )
391
430
end
392
431
end
393
432
@@ -402,6 +441,9 @@ defmodule ElixirLS.LanguageServer.Server do
402
441
else
403
442
awaiting_contracts = reject_awaiting_contracts ( state . awaiting_contracts , uri )
404
443
444
+ # parse handler will clean up diagnostics and refs for closed file
445
+ state = trigger_parse ( state , uri , 0 )
446
+
405
447
% {
406
448
state
407
449
| source_files: Map . delete ( state . source_files , uri ) ,
@@ -421,10 +463,14 @@ defmodule ElixirLS.LanguageServer.Server do
421
463
422
464
state
423
465
else
424
- update_in ( state . source_files [ uri ] , fn source_file ->
425
- % SourceFile { source_file | version: version , dirty?: true }
426
- |> SourceFile . apply_content_changes ( content_changes )
427
- end )
466
+ state =
467
+ update_in ( state . source_files [ uri ] , fn source_file ->
468
+ % SourceFile { source_file | version: version , dirty?: true }
469
+ |> SourceFile . apply_content_changes ( content_changes )
470
+ end )
471
+
472
+ # trigger parse with debounce
473
+ trigger_parse ( state , uri , 300 )
428
474
end
429
475
end
430
476
@@ -1039,7 +1085,10 @@ defmodule ElixirLS.LanguageServer.Server do
1039
1085
end
1040
1086
1041
1087
defp handle_build_result ( status , diagnostics , state = % __MODULE__ { } ) do
1042
- old_diagnostics = state . build_diagnostics ++ state . dialyzer_diagnostics
1088
+ old_diagnostics =
1089
+ state . build_diagnostics ++
1090
+ state . dialyzer_diagnostics ++ List . flatten ( Map . values ( state . parser_diagnostics ) )
1091
+
1043
1092
state = put_in ( state . build_diagnostics , diagnostics )
1044
1093
1045
1094
state =
@@ -1055,7 +1104,8 @@ defmodule ElixirLS.LanguageServer.Server do
1055
1104
end
1056
1105
1057
1106
publish_diagnostics (
1058
- state . build_diagnostics ++ state . dialyzer_diagnostics ,
1107
+ state . build_diagnostics ++
1108
+ state . dialyzer_diagnostics ++ List . flatten ( Map . values ( state . parser_diagnostics ) ) ,
1059
1109
old_diagnostics ,
1060
1110
state . source_files
1061
1111
)
@@ -1432,4 +1482,82 @@ defmodule ElixirLS.LanguageServer.Server do
1432
1482
state
1433
1483
end
1434
1484
end
1485
+
1486
+ defp trigger_parse ( state , uri , debounce_timeout ) do
1487
+ if String . ends_with? ( uri , [ ".ex" , ".exs" , ".eex" ] ) do
1488
+ update_in ( state . parse_file_refs [ uri ] , fn old_ref ->
1489
+ if old_ref do
1490
+ Process . cancel_timer ( old_ref , info: false )
1491
+ end
1492
+
1493
+ Process . send_after ( self ( ) , { :parse_file , uri } , debounce_timeout )
1494
+ end )
1495
+ else
1496
+ state
1497
+ end
1498
+ end
1499
+
1500
+ defp parse_file ( text , file ) do
1501
+ { result , raw_diagnostics } =
1502
+ Build . with_diagnostics ( [ log: false ] , fn ->
1503
+ try do
1504
+ parser_options = [
1505
+ file: file ,
1506
+ columns: true
1507
+ ]
1508
+
1509
+ if String . ends_with? ( file , ".eex" ) do
1510
+ EEx . compile_string ( text ,
1511
+ file: file ,
1512
+ parser_options: parser_options
1513
+ )
1514
+ else
1515
+ Code . string_to_quoted! ( text , parser_options )
1516
+ end
1517
+
1518
+ :ok
1519
+ rescue
1520
+ e in [ EEx.SyntaxError , SyntaxError , TokenMissingError ] ->
1521
+ message = Exception . message ( e )
1522
+
1523
+ diagnostic = % Mix.Task.Compiler.Diagnostic {
1524
+ compiler_name: "ElixirLS" ,
1525
+ file: file ,
1526
+ position: { e . line , e . column } ,
1527
+ message: message ,
1528
+ severity: :error
1529
+ }
1530
+
1531
+ { :error , diagnostic }
1532
+
1533
+ e ->
1534
+ message = Exception . message ( e )
1535
+
1536
+ diagnostic = % Mix.Task.Compiler.Diagnostic {
1537
+ compiler_name: "ElixirLS" ,
1538
+ file: file ,
1539
+ position: { 1 , 1 } ,
1540
+ message: message ,
1541
+ severity: :error
1542
+ }
1543
+
1544
+ # e.g. https://github.com/elixir-lang/elixir/issues/12926
1545
+ Logger . warning (
1546
+ "Unexpected parser error, please report it to elixir project https://github.com/elixir-lang/elixir/issues\n " <>
1547
+ Exception . format ( :error , e , __STACKTRACE__ )
1548
+ )
1549
+
1550
+ { :error , diagnostic }
1551
+ end
1552
+ end )
1553
+
1554
+ warning_diagnostics =
1555
+ raw_diagnostics
1556
+ |> Enum . map ( & Diagnostics . code_diagnostic / 1 )
1557
+
1558
+ case result do
1559
+ :ok -> warning_diagnostics
1560
+ { :error , diagnostic } -> [ diagnostic | warning_diagnostics ]
1561
+ end
1562
+ end
1435
1563
end
0 commit comments