Skip to content
This repository was archived by the owner on Oct 8, 2025. It is now read-only.

Commit 6b5ffaf

Browse files
authored
feat: workspace folders on startup (#117)
1 parent e2194c5 commit 6b5ffaf

File tree

2 files changed

+117
-83
lines changed

2 files changed

+117
-83
lines changed

lib/next_ls.ex

Lines changed: 82 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,26 @@ defmodule NextLS do
8787
dynamic_supervisor: dynamic_supervisor,
8888
extension_registry: extension_registry,
8989
extensions: extensions,
90-
runtime_task: nil,
91-
ready: false
90+
runtime_tasks: nil,
91+
ready: false,
92+
client_capabilities: nil
9293
)}
9394
end
9495

9596
@impl true
96-
def handle_request(%Initialize{params: %InitializeParams{root_uri: root_uri}}, lsp) do
97+
def handle_request(
98+
%Initialize{
99+
params: %InitializeParams{root_uri: root_uri, workspace_folders: workspace_folders, capabilities: caps}
100+
},
101+
lsp
102+
) do
103+
workspace_folders =
104+
if caps.workspace.workspace_folders do
105+
workspace_folders
106+
else
107+
%{name: Path.basename(root_uri), uri: root_uri}
108+
end
109+
97110
{:reply,
98111
%InitializeResult{
99112
capabilities: %ServerCapabilities{
@@ -105,10 +118,16 @@ defmodule NextLS do
105118
document_formatting_provider: true,
106119
workspace_symbol_provider: true,
107120
document_symbol_provider: true,
108-
definition_provider: true
121+
definition_provider: true,
122+
workspace: %{
123+
workspace_folders: %GenLSP.Structures.WorkspaceFoldersServerCapabilities{
124+
supported: true,
125+
change_notifications: true
126+
}
127+
}
109128
},
110-
server_info: %{name: "NextLS"}
111-
}, assign(lsp, root_uri: root_uri)}
129+
server_info: %{name: "Next LS"}
130+
}, assign(lsp, root_uri: root_uri, workspace_folders: workspace_folders, client_capabilities: caps)}
112131
end
113132

114133
def handle_request(%TextDocumentDefinition{params: %{text_document: %{uri: uri}, position: position}}, lsp) do
@@ -201,7 +220,9 @@ defmodule NextLS do
201220

202221
def handle_request(%TextDocumentFormatting{params: %{text_document: %{uri: uri}}}, lsp) do
203222
document = lsp.assigns.documents[uri]
204-
runtime = lsp.assigns.runtime
223+
224+
{_, %{runtime: runtime}} =
225+
lsp.assigns.runtimes |> Enum.find(fn {_name, %{uri: wuri}} -> String.starts_with?(uri, wuri) end)
205226

206227
with {:ok, {formatter, _}} <- Runtime.call(runtime, {Mix.Tasks.Format, :formatter_for_file, [".formatter.exs"]}),
207228
{:ok, response} when is_binary(response) or is_list(response) <-
@@ -255,8 +276,6 @@ defmodule NextLS do
255276
def handle_notification(%Initialized{}, lsp) do
256277
GenLSP.log(lsp, "[NextLS] NextLS v#{version()} has initialized!")
257278

258-
working_dir = URI.parse(lsp.assigns.root_uri).path
259-
260279
for extension <- lsp.assigns.extensions do
261280
{:ok, _} =
262281
DynamicSupervisor.start_child(
@@ -267,44 +286,51 @@ defmodule NextLS do
267286

268287
GenLSP.log(lsp, "[NextLS] Booting runtime...")
269288

270-
token = token()
271-
272-
progress_start(lsp, token, "Initializing NextLS runtime...")
273-
274-
{:ok, runtime} =
275-
DynamicSupervisor.start_child(
276-
lsp.assigns.dynamic_supervisor,
277-
{NextLS.Runtime,
278-
task_supervisor: lsp.assigns.runtime_task_supervisor,
279-
extension_registry: lsp.assigns.extension_registry,
280-
working_dir: working_dir,
281-
parent: self(),
282-
logger: lsp.assigns.logger}
283-
)
289+
runtimes =
290+
for %{uri: uri, name: name} <- lsp.assigns.workspace_folders do
291+
token = token()
292+
progress_start(lsp, token, "Initializing NextLS runtime for folder #{name}...")
293+
294+
{:ok, runtime} =
295+
DynamicSupervisor.start_child(
296+
lsp.assigns.dynamic_supervisor,
297+
{NextLS.Runtime,
298+
task_supervisor: lsp.assigns.runtime_task_supervisor,
299+
extension_registry: lsp.assigns.extension_registry,
300+
working_dir: URI.parse(uri).path,
301+
parent: self(),
302+
logger: lsp.assigns.logger}
303+
)
304+
305+
Process.monitor(runtime)
306+
307+
{name,
308+
%{uri: uri, runtime: runtime, refresh_ref: {token, "NextLS runtime for folder #{name} has initialized!"}}}
309+
end
284310

285-
Process.monitor(runtime)
311+
lsp = assign(lsp, runtimes: Map.new(runtimes))
286312

287-
lsp = assign(lsp, runtime: runtime)
313+
tasks =
314+
for {name, workspace} <- runtimes do
315+
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
316+
with false <- wait_until(fn -> NextLS.Runtime.ready?(workspace.runtime) end) do
317+
GenLSP.error(lsp, "[NextLS] Failed to start runtime for folder #{name}")
318+
raise "Failed to boot runtime"
319+
end
288320

289-
task =
290-
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
291-
with false <-
292-
wait_until(fn ->
293-
NextLS.Runtime.ready?(runtime)
294-
end) do
295-
GenLSP.error(lsp, "[NextLS] Failed to start runtime")
296-
raise "Failed to boot runtime"
297-
end
321+
GenLSP.log(lsp, "[NextLS] Runtime for folder #{name} is ready...")
298322

299-
GenLSP.log(lsp, "[NextLS] Runtime ready...")
323+
{name, :ready}
324+
end)
325+
end
300326

301-
:ready
302-
end)
327+
refresh_refs =
328+
Enum.zip_with(tasks, runtimes, fn task, {_name, runtime} -> {task.ref, runtime.refresh_ref} end) |> Map.new()
303329

304330
{:noreply,
305331
assign(lsp,
306-
refresh_refs: Map.put(lsp.assigns.refresh_refs, task.ref, {token, "NextLS runtime has initialized!"}),
307-
runtime_task: task
332+
refresh_refs: Map.merge(lsp.assigns.refresh_refs, refresh_refs),
333+
runtime_tasks: tasks
308334
)}
309335
end
310336

@@ -322,23 +348,27 @@ defmodule NextLS do
322348
%{assigns: %{ready: true}} = lsp
323349
) do
324350
for task <- Task.Supervisor.children(lsp.assigns.task_supervisor),
325-
task != lsp.assigns.runtime_task.pid do
351+
task not in for(t <- lsp.assigns.runtime_tasks, do: t.pid) do
326352
Process.exit(task, :kill)
327353
end
328354

329355
token = token()
330356

331357
progress_start(lsp, token, "Compiling...")
358+
runtimes = Enum.to_list(lsp.assigns.runtimes)
359+
360+
tasks =
361+
for {name, r} <- runtimes do
362+
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn -> {name, Runtime.compile(r.runtime)} end)
363+
end
332364

333-
task =
334-
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
335-
Runtime.compile(lsp.assigns.runtime)
336-
end)
365+
refresh_refs =
366+
Enum.zip_with(tasks, runtimes, fn task, {_name, runtime} -> {task.ref, runtime.refresh_ref} end) |> Map.new()
337367

338368
{:noreply,
339369
lsp
340370
|> then(&put_in(&1.assigns.documents[uri], String.split(text, "\n")))
341-
|> then(&put_in(&1.assigns.refresh_refs[task.ref], {token, "Compiled!"}))}
371+
|> then(&put_in(&1.assigns.refresh_refs, refresh_refs))}
342372
end
343373

344374
def handle_notification(%TextDocumentDidChange{}, %{assigns: %{ready: false}} = lsp) do
@@ -355,7 +385,7 @@ defmodule NextLS do
355385
lsp
356386
) do
357387
for task <- Task.Supervisor.children(lsp.assigns.task_supervisor),
358-
task != lsp.assigns.runtime_task.pid do
388+
task not in for(t <- lsp.assigns.runtime_tasks, do: t.pid) do
359389
Process.exit(task, :kill)
360390
end
361391

@@ -421,13 +451,13 @@ defmodule NextLS do
421451

422452
lsp =
423453
case resp do
424-
:ready ->
454+
{name, :ready} ->
425455
token = token()
426456
progress_start(lsp, token, "Compiling...")
427457

428458
task =
429459
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
430-
Runtime.compile(lsp.assigns.runtime)
460+
{name, Runtime.compile(lsp.assigns.runtimes[name].runtime)}
431461
end)
432462

433463
assign(lsp, ready: true, refresh_refs: Map.put(refs, task.ref, {token, "Compiled!"}))
@@ -448,13 +478,11 @@ defmodule NextLS do
448478
{:noreply, assign(lsp, refresh_refs: refs)}
449479
end
450480

451-
def handle_info(
452-
{:DOWN, _ref, :process, runtime, _reason},
453-
%{assigns: %{runtime: runtime}} = lsp
454-
) do
455-
GenLSP.error(lsp, "[NextLS] The runtime has crashed")
481+
def handle_info({:DOWN, _ref, :process, runtime, _reason}, %{assigns: %{runtimes: runtimes}} = lsp) do
482+
{name, _} = Enum.find(runtimes, fn {_name, %{runtime: r}} -> r == runtime end)
483+
GenLSP.error(lsp, "[NextLS] The runtime for #{name} has crashed")
456484

457-
{:noreply, assign(lsp, runtime: nil)}
485+
{:noreply, assign(lsp, runtimes: Map.drop(runtimes, name))}
458486
end
459487

460488
def handle_info(message, lsp) do

0 commit comments

Comments
 (0)