Skip to content

Commit

Permalink
feat: workspace folders on startup (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
mhanberg authored Jul 21, 2023
1 parent e2194c5 commit 6b5ffaf
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 83 deletions.
136 changes: 82 additions & 54 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,26 @@ defmodule NextLS do
dynamic_supervisor: dynamic_supervisor,
extension_registry: extension_registry,
extensions: extensions,
runtime_task: nil,
ready: false
runtime_tasks: nil,
ready: false,
client_capabilities: nil
)}
end

@impl true
def handle_request(%Initialize{params: %InitializeParams{root_uri: root_uri}}, lsp) do
def handle_request(
%Initialize{
params: %InitializeParams{root_uri: root_uri, workspace_folders: workspace_folders, capabilities: caps}
},
lsp
) do
workspace_folders =
if caps.workspace.workspace_folders do
workspace_folders
else
%{name: Path.basename(root_uri), uri: root_uri}
end

{:reply,
%InitializeResult{
capabilities: %ServerCapabilities{
Expand All @@ -105,10 +118,16 @@ defmodule NextLS do
document_formatting_provider: true,
workspace_symbol_provider: true,
document_symbol_provider: true,
definition_provider: true
definition_provider: true,
workspace: %{
workspace_folders: %GenLSP.Structures.WorkspaceFoldersServerCapabilities{
supported: true,
change_notifications: true
}
}
},
server_info: %{name: "NextLS"}
}, assign(lsp, root_uri: root_uri)}
server_info: %{name: "Next LS"}
}, assign(lsp, root_uri: root_uri, workspace_folders: workspace_folders, client_capabilities: caps)}
end

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

def handle_request(%TextDocumentFormatting{params: %{text_document: %{uri: uri}}}, lsp) do
document = lsp.assigns.documents[uri]
runtime = lsp.assigns.runtime

{_, %{runtime: runtime}} =
lsp.assigns.runtimes |> Enum.find(fn {_name, %{uri: wuri}} -> String.starts_with?(uri, wuri) end)

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

working_dir = URI.parse(lsp.assigns.root_uri).path

for extension <- lsp.assigns.extensions do
{:ok, _} =
DynamicSupervisor.start_child(
Expand All @@ -267,44 +286,51 @@ defmodule NextLS do

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

token = token()

progress_start(lsp, token, "Initializing NextLS runtime...")

{:ok, runtime} =
DynamicSupervisor.start_child(
lsp.assigns.dynamic_supervisor,
{NextLS.Runtime,
task_supervisor: lsp.assigns.runtime_task_supervisor,
extension_registry: lsp.assigns.extension_registry,
working_dir: working_dir,
parent: self(),
logger: lsp.assigns.logger}
)
runtimes =
for %{uri: uri, name: name} <- lsp.assigns.workspace_folders do
token = token()
progress_start(lsp, token, "Initializing NextLS runtime for folder #{name}...")

{:ok, runtime} =
DynamicSupervisor.start_child(
lsp.assigns.dynamic_supervisor,
{NextLS.Runtime,
task_supervisor: lsp.assigns.runtime_task_supervisor,
extension_registry: lsp.assigns.extension_registry,
working_dir: URI.parse(uri).path,
parent: self(),
logger: lsp.assigns.logger}
)

Process.monitor(runtime)

{name,
%{uri: uri, runtime: runtime, refresh_ref: {token, "NextLS runtime for folder #{name} has initialized!"}}}
end

Process.monitor(runtime)
lsp = assign(lsp, runtimes: Map.new(runtimes))

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

task =
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
with false <-
wait_until(fn ->
NextLS.Runtime.ready?(runtime)
end) do
GenLSP.error(lsp, "[NextLS] Failed to start runtime")
raise "Failed to boot runtime"
end
GenLSP.log(lsp, "[NextLS] Runtime for folder #{name} is ready...")

GenLSP.log(lsp, "[NextLS] Runtime ready...")
{name, :ready}
end)
end

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

{:noreply,
assign(lsp,
refresh_refs: Map.put(lsp.assigns.refresh_refs, task.ref, {token, "NextLS runtime has initialized!"}),
runtime_task: task
refresh_refs: Map.merge(lsp.assigns.refresh_refs, refresh_refs),
runtime_tasks: tasks
)}
end

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

token = token()

progress_start(lsp, token, "Compiling...")
runtimes = Enum.to_list(lsp.assigns.runtimes)

tasks =
for {name, r} <- runtimes do
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn -> {name, Runtime.compile(r.runtime)} end)
end

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

{:noreply,
lsp
|> then(&put_in(&1.assigns.documents[uri], String.split(text, "\n")))
|> then(&put_in(&1.assigns.refresh_refs[task.ref], {token, "Compiled!"}))}
|> then(&put_in(&1.assigns.refresh_refs, refresh_refs))}
end

def handle_notification(%TextDocumentDidChange{}, %{assigns: %{ready: false}} = lsp) do
Expand All @@ -355,7 +385,7 @@ defmodule NextLS do
lsp
) do
for task <- Task.Supervisor.children(lsp.assigns.task_supervisor),
task != lsp.assigns.runtime_task.pid do
task not in for(t <- lsp.assigns.runtime_tasks, do: t.pid) do
Process.exit(task, :kill)
end

Expand Down Expand Up @@ -421,13 +451,13 @@ defmodule NextLS do

lsp =
case resp do
:ready ->
{name, :ready} ->
token = token()
progress_start(lsp, token, "Compiling...")

task =
Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn ->
Runtime.compile(lsp.assigns.runtime)
{name, Runtime.compile(lsp.assigns.runtimes[name].runtime)}
end)

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

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

{:noreply, assign(lsp, runtime: nil)}
{:noreply, assign(lsp, runtimes: Map.drop(runtimes, name))}
end

def handle_info(message, lsp) do
Expand Down
Loading

0 comments on commit 6b5ffaf

Please sign in to comment.