@@ -43,7 +43,7 @@ defmodule NextLS do
43
43
:runtime_task_supervisor ,
44
44
:dynamic_supervisor ,
45
45
:extensions ,
46
- :extension_registry ,
46
+ :registry ,
47
47
:symbol_table
48
48
] )
49
49
@@ -55,7 +55,8 @@ defmodule NextLS do
55
55
task_supervisor = Keyword . fetch! ( args , :task_supervisor )
56
56
runtime_task_supervisor = Keyword . fetch! ( args , :runtime_task_supervisor )
57
57
dynamic_supervisor = Keyword . fetch! ( args , :dynamic_supervisor )
58
- extension_registry = Keyword . fetch! ( args , :extension_registry )
58
+
59
+ registry = Keyword . fetch! ( args , :registry )
59
60
extensions = Keyword . get ( args , :extensions , [ NextLS.ElixirExtension ] )
60
61
cache = Keyword . fetch! ( args , :cache )
61
62
symbol_table = Keyword . fetch! ( args , :symbol_table )
@@ -72,9 +73,8 @@ defmodule NextLS do
72
73
task_supervisor: task_supervisor ,
73
74
runtime_task_supervisor: runtime_task_supervisor ,
74
75
dynamic_supervisor: dynamic_supervisor ,
75
- extension_registry: extension_registry ,
76
+ registry: registry ,
76
77
extensions: extensions ,
77
- runtime_tasks: nil ,
78
78
ready: false ,
79
79
client_capabilities: nil
80
80
) }
@@ -208,39 +208,44 @@ defmodule NextLS do
208
208
def handle_request ( % TextDocumentFormatting { params: % { text_document: % { uri: uri } } } , lsp ) do
209
209
document = lsp . assigns . documents [ uri ]
210
210
211
- { _ , % { runtime: runtime } } =
212
- Enum . find ( lsp . assigns . runtimes , fn { _name , % { uri: wuri } } -> String . starts_with? ( uri , wuri ) end )
213
-
214
- with { :ok , { formatter , _ } } <- Runtime . call ( runtime , { Mix.Tasks.Format , :formatter_for_file , [ ".formatter.exs" ] } ) ,
215
- { :ok , response } when is_binary ( response ) or is_list ( response ) <-
216
- Runtime . call ( runtime , { Kernel , :apply , [ formatter , [ Enum . join ( document , "\n " ) ] ] } ) do
217
- { :reply ,
218
- [
219
- % TextEdit {
220
- new_text: IO . iodata_to_binary ( response ) ,
221
- range: % Range {
222
- start: % Position { line: 0 , character: 0 } ,
223
- end: % Position {
224
- line: length ( document ) ,
225
- character: document |> List . last ( ) |> String . length ( ) |> Kernel . - ( 1 ) |> max ( 0 )
226
- }
227
- }
228
- }
229
- ] , lsp }
230
- else
231
- { :error , :not_ready } ->
232
- GenLSP . notify ( lsp , % GenLSP.Notifications.WindowShowMessage {
233
- params: % GenLSP.Structures.ShowMessageParams {
234
- type: GenLSP.Enumerations.MessageType . info ( ) ,
235
- message: "The NextLS runtime is still initializing!"
236
- }
237
- } )
238
-
239
- { :reply , nil , lsp }
211
+ [ resp ] =
212
+ dispatch ( lsp . assigns . registry , :runtimes , fn entries ->
213
+ for { runtime , % { uri: wuri } } <- entries , String . starts_with? ( uri , wuri ) do
214
+ with { :ok , { formatter , _ } } <-
215
+ Runtime . call ( runtime , { Mix.Tasks.Format , :formatter_for_file , [ ".formatter.exs" ] } ) ,
216
+ { :ok , response } when is_binary ( response ) or is_list ( response ) <-
217
+ Runtime . call ( runtime , { Kernel , :apply , [ formatter , [ Enum . join ( document , "\n " ) ] ] } ) do
218
+ { :reply ,
219
+ [
220
+ % TextEdit {
221
+ new_text: IO . iodata_to_binary ( response ) ,
222
+ range: % Range {
223
+ start: % Position { line: 0 , character: 0 } ,
224
+ end: % Position {
225
+ line: length ( document ) ,
226
+ character: document |> List . last ( ) |> String . length ( ) |> Kernel . - ( 1 ) |> max ( 0 )
227
+ }
228
+ }
229
+ }
230
+ ] , lsp }
231
+ else
232
+ { :error , :not_ready } ->
233
+ GenLSP . notify ( lsp , % GenLSP.Notifications.WindowShowMessage {
234
+ params: % GenLSP.Structures.ShowMessageParams {
235
+ type: GenLSP.Enumerations.MessageType . info ( ) ,
236
+ message: "The NextLS runtime is still initializing!"
237
+ }
238
+ } )
239
+
240
+ { :reply , nil , lsp }
241
+
242
+ _ ->
243
+ { :reply , nil , lsp }
244
+ end
245
+ end
246
+ end )
240
247
241
- _ ->
242
- { :reply , nil , lsp }
243
- end
248
+ resp
244
249
end
245
250
246
251
def handle_request ( % Shutdown { } , lsp ) do
@@ -267,87 +272,79 @@ defmodule NextLS do
267
272
{ :ok , _ } =
268
273
DynamicSupervisor . start_child (
269
274
lsp . assigns . dynamic_supervisor ,
270
- { extension , cache: lsp . assigns . cache , registry: lsp . assigns . extension_registry , publisher: self ( ) }
275
+ { extension , cache: lsp . assigns . cache , registry: lsp . assigns . registry , publisher: self ( ) }
271
276
)
272
277
end
273
278
274
- GenLSP . log ( lsp , "[NextLS] Booting runtime..." )
275
-
276
- runtimes =
277
- for % { uri: uri , name: name } <- lsp . assigns . workspace_folders do
278
- token = token ( )
279
- progress_start ( lsp , token , "Initializing NextLS runtime for folder #{ name } ..." )
279
+ GenLSP . log ( lsp , "[NextLS] Booting runtimes..." )
280
280
281
- { :ok , runtime } =
282
- DynamicSupervisor . start_child (
283
- lsp . assigns . dynamic_supervisor ,
284
- { NextLS.Runtime ,
285
- task_supervisor: lsp . assigns . runtime_task_supervisor ,
286
- extension_registry: lsp . assigns . extension_registry ,
287
- working_dir: URI . parse ( uri ) . path ,
288
- parent: self ( ) ,
289
- logger: lsp . assigns . logger }
290
- )
281
+ for % { uri: uri , name: name } <- lsp . assigns . workspace_folders do
282
+ token = token ( )
283
+ progress_start ( lsp , token , "Initializing NextLS runtime for folder #{ name } ..." )
284
+ parent = self ( )
291
285
292
- Process . monitor ( runtime )
293
-
294
- { name ,
295
- % { uri: uri , runtime: runtime , refresh_ref: { token , "NextLS runtime for folder #{ name } has initialized!" } } }
296
- end
297
-
298
- lsp = assign ( lsp , runtimes: Map . new ( runtimes ) )
299
-
300
- tasks =
301
- for { name , workspace } <- runtimes do
302
- Task.Supervisor . async_nolink ( lsp . assigns . task_supervisor , fn ->
303
- with false <- wait_until ( fn -> NextLS.Runtime . ready? ( workspace . runtime ) end ) do
304
- GenLSP . error ( lsp , "[NextLS] Failed to start runtime for folder #{ name } " )
305
- raise "Failed to boot runtime"
306
- end
286
+ { :ok , runtime } =
287
+ DynamicSupervisor . start_child (
288
+ lsp . assigns . dynamic_supervisor ,
289
+ { NextLS.Runtime ,
290
+ name: name ,
291
+ task_supervisor: lsp . assigns . runtime_task_supervisor ,
292
+ registry: lsp . assigns . registry ,
293
+ working_dir: URI . parse ( uri ) . path ,
294
+ uri: uri ,
295
+ parent: self ( ) ,
296
+ on_initialized: fn status ->
297
+ if status == :ready do
298
+ progress_end ( lsp , token , "NextLS runtime for folder #{ name } has initialized!" )
299
+ GenLSP . log ( lsp , "[NextLS] Runtime for folder #{ name } is ready..." )
300
+ send ( parent , { :runtime_ready , name , self ( ) } )
301
+ else
302
+ progress_end ( lsp , token )
303
+ GenLSP . error ( lsp , "[NextLS] Runtime for folder #{ name } failed to initialize" )
304
+ end
305
+ end ,
306
+ logger: lsp . assigns . logger }
307
+ )
307
308
308
- GenLSP . log ( lsp , "[NextLS] Runtime for folder #{ name } is ready..." )
309
+ ref = Process . monitor ( runtime )
309
310
310
- { name , :ready }
311
- end )
312
- end
311
+ Process . put ( ref , name )
313
312
314
- refresh_refs =
315
- tasks |> Enum . zip_with ( runtimes , fn task , { _name , runtime } -> { task . ref , runtime . refresh_ref } end ) |> Map . new ( )
313
+ { name , % { uri: uri , runtime: runtime } }
314
+ end
316
315
317
- { :noreply ,
318
- assign ( lsp ,
319
- refresh_refs: Map . merge ( lsp . assigns . refresh_refs , refresh_refs ) ,
320
- runtime_tasks: tasks
321
- ) }
316
+ { :noreply , lsp }
322
317
end
323
318
324
319
def handle_notification ( % TextDocumentDidSave { } , % { assigns: % { ready: false } } = lsp ) do
325
320
{ :noreply , lsp }
326
321
end
327
322
323
+ # TODO: add some test cases for saving files in multiple workspaces
328
324
def handle_notification (
329
325
% TextDocumentDidSave {
330
326
params: % GenLSP.Structures.DidSaveTextDocumentParams { text: text , text_document: % { uri: uri } }
331
327
} ,
332
328
% { assigns: % { ready: true } } = lsp
333
329
) do
334
- for task <- Task.Supervisor . children ( lsp . assigns . task_supervisor ) ,
335
- task not in for ( t <- lsp . assigns . runtime_tasks , do: t . pid ) do
330
+ for task <- Task.Supervisor . children ( lsp . assigns . task_supervisor ) do
336
331
Process . exit ( task , :kill )
337
332
end
338
333
339
- token = token ( )
334
+ refresh_refs =
335
+ dispatch ( lsp . assigns . registry , :runtimes , fn entries ->
336
+ for { pid , % { name: name , uri: wuri } } <- entries , String . starts_with? ( uri , wuri ) , into: % { } do
337
+ token = token ( )
338
+ progress_start ( lsp , token , "Compiling..." )
340
339
341
- progress_start ( lsp , token , "Compiling..." )
342
- runtimes = Enum . to_list ( lsp . assigns . runtimes )
340
+ task =
341
+ Task.Supervisor . async_nolink ( lsp . assigns . task_supervisor , fn ->
342
+ { name , Runtime . compile ( pid ) }
343
+ end )
343
344
344
- tasks =
345
- for { name , r } <- runtimes do
346
- Task.Supervisor . async_nolink ( lsp . assigns . task_supervisor , fn -> { name , Runtime . compile ( r . runtime ) } end )
347
- end
348
-
349
- refresh_refs =
350
- tasks |> Enum . zip_with ( runtimes , fn task , { _name , runtime } -> { task . ref , runtime . refresh_ref } end ) |> Map . new ( )
345
+ { task . ref , { token , "Compiled!" } }
346
+ end
347
+ end )
351
348
352
349
{ :noreply ,
353
350
lsp
@@ -363,8 +360,7 @@ defmodule NextLS do
363
360
% TextDocumentDidChange { params: % { text_document: % { uri: uri } , content_changes: [ % { text: text } ] } } ,
364
361
lsp
365
362
) do
366
- for task <- Task.Supervisor . children ( lsp . assigns . task_supervisor ) ,
367
- task not in for ( t <- lsp . assigns . runtime_tasks , do: t . pid ) do
363
+ for task <- Task.Supervisor . children ( lsp . assigns . task_supervisor ) do
368
364
Process . exit ( task , :kill )
369
365
end
370
366
@@ -420,30 +416,27 @@ defmodule NextLS do
420
416
{ :noreply , lsp }
421
417
end
422
418
423
- def handle_info ( { ref , resp } , % { assigns: % { refresh_refs: refs } } = lsp ) when is_map_key ( refs , ref ) do
424
- Process . demonitor ( ref , [ :flush ] )
425
- { { token , msg } , refs } = Map . pop ( refs , ref )
419
+ def handle_info ( { :runtime_ready , name , runtime_pid } , lsp ) do
420
+ token = token ( )
421
+ progress_start ( lsp , token , "Compiling..." )
426
422
427
- progress_end ( lsp , token , msg )
423
+ task =
424
+ Task.Supervisor . async_nolink ( lsp . assigns . task_supervisor , fn ->
425
+ { name , Runtime . compile ( runtime_pid ) }
426
+ end )
428
427
429
- lsp =
430
- case resp do
431
- { name , :ready } ->
432
- token = token ( )
433
- progress_start ( lsp , token , "Compiling..." )
428
+ refresh_refs = Map . put ( lsp . assigns . refresh_refs , task . ref , { token , "Compiled!" } )
434
429
435
- task =
436
- Task.Supervisor . async_nolink ( lsp . assigns . task_supervisor , fn ->
437
- { name , Runtime . compile ( lsp . assigns . runtimes [ name ] . runtime ) }
438
- end )
430
+ { :noreply , assign ( lsp , ready: true , refresh_refs: refresh_refs ) }
431
+ end
439
432
440
- assign ( lsp , ready: true , refresh_refs: Map . put ( refs , task . ref , { token , "Compiled!" } ) )
433
+ def handle_info ( { ref , _resp } , % { assigns: % { refresh_refs: refs } } = lsp ) when is_map_key ( refs , ref ) do
434
+ Process . demonitor ( ref , [ :flush ] )
435
+ { { token , msg } , refs } = Map . pop ( refs , ref )
441
436
442
- _ ->
443
- assign ( lsp , refresh_refs: refs )
444
- end
437
+ progress_end ( lsp , token , msg )
445
438
446
- { :noreply , lsp }
439
+ { :noreply , assign ( lsp , refresh_refs: refs ) }
447
440
end
448
441
449
442
def handle_info ( { :DOWN , ref , :process , _pid , _reason } , % { assigns: % { refresh_refs: refs } } = lsp )
@@ -455,35 +448,20 @@ defmodule NextLS do
455
448
{ :noreply , assign ( lsp , refresh_refs: refs ) }
456
449
end
457
450
458
- def handle_info ( { :DOWN , _ref , :process , runtime , _reason } , % { assigns: % { runtimes: runtimes } } = lsp ) do
459
- { name , _ } = Enum . find ( runtimes , fn { _name , % { runtime: r } } -> r == runtime end )
451
+ def handle_info ( { :DOWN , ref , :process , _runtime , _reason } , lsp ) do
452
+ name = Process . get ( ref )
453
+ Process . delete ( ref )
454
+
460
455
GenLSP . error ( lsp , "[NextLS] The runtime for #{ name } has crashed" )
461
456
462
- { :noreply , assign ( lsp , runtimes: Map . drop ( runtimes , name ) ) }
457
+ { :noreply , lsp }
463
458
end
464
459
465
460
def handle_info ( message , lsp ) do
466
- GenLSP . log ( lsp , "[NextLS] Unhanded message: #{ inspect ( message ) } " )
461
+ GenLSP . log ( lsp , "[NextLS] Unhandled message: #{ inspect ( message ) } " )
467
462
{ :noreply , lsp }
468
463
end
469
464
470
- defp wait_until ( cb ) do
471
- wait_until ( 120 , cb )
472
- end
473
-
474
- defp wait_until ( 0 , _cb ) do
475
- false
476
- end
477
-
478
- defp wait_until ( n , cb ) do
479
- if cb . ( ) do
480
- true
481
- else
482
- Process . sleep ( 1000 )
483
- wait_until ( n - 1 , cb )
484
- end
485
- end
486
-
487
465
defp progress_start ( lsp , token , msg ) do
488
466
GenLSP . notify ( lsp , % GenLSP.Notifications.DollarProgress {
489
467
params: % GenLSP.Structures.ProgressParams {
@@ -527,4 +505,22 @@ defmodule NextLS do
527
505
528
506
defp elixir_kind_to_lsp_kind ( kind ) when kind in [ :def , :defp , :defmacro , :defmacrop ] ,
529
507
do: GenLSP.Enumerations.SymbolKind . function ( )
508
+
509
+ # NOTE: this is only possible because the registry is not partitioned
510
+ # if it is partitioned, then the callback is called multiple times
511
+ # and this method of extracting the result doesn't really make sense
512
+ defp dispatch ( registry , key , callback ) do
513
+ ref = make_ref ( )
514
+ me = self ( )
515
+
516
+ Registry . dispatch ( registry , key , fn entries ->
517
+ result = callback . ( entries )
518
+
519
+ send ( me , { ref , result } )
520
+ end )
521
+
522
+ receive do
523
+ { ^ ref , result } -> result
524
+ end
525
+ end
530
526
end
0 commit comments