defmodule GenLSP do
defmacro __using__(_) do
quote do
@behaviour GenLSP
require Logger
def child_spec(opts) do
%{
id: __MODULE__,
start: {__MODULE__, :start_link, [opts]},
type: :worker,
restart: :permanent,
shutdown: 500
}
end
@impl true
def handle_info(_, state) do
Logger.warn("Unhandled message passed to handle_info/2")
{:noreply, state}
end
defoverridable handle_info: 2
end
end
require Logger
@callback init(init_arg :: term()) :: {:ok, state} when state: any()
@callback handle_request(request :: term(), state) ::
{:reply, id :: integer(), reply :: term(), state}
| {:noreply, state}
when state: any()
@callback handle_notification(notification :: term(), state) :: {:noreply, state}
when state: any()
@callback handle_info(message :: any(), state) :: {:noreply, state} when state: any()
def start_link(module, init_args, opts) do
:proc_lib.start_link(__MODULE__, :init, [
{module, init_args, opts[:name], self()}
])
end
@doc false
def init({module, init_args, name, parent}) do
case module.init(init_args) do
{:ok, user_state} ->
deb = :sys.debug_options([])
if name, do: Process.register(self(), name)
:proc_lib.init_ack(parent, {:ok, self()})
state = %{user_state: user_state, internal_state: %{mod: module}}
loop(state, parent, deb)
end
end
def request_server(lsp, request) do
from = self()
message = {:request, from, request}
send(lsp, message)
end
def notify_server(lsp, notification) do
from = self()
send(lsp, {:notification, from, notification})
end
def notify(notification) do
notification
|> GenLSP.Protocol.encode()
|> GenLSP.Buffer.outgoing()
end
defp loop(state, parent, deb) do
receive do
{:system, from, request} ->
:sys.handle_system_msg(request, from, parent, __MODULE__, deb, state)
{:request, _from, request} ->
attempt(
fn ->
req = GenLSP.Protocol.new(request)
case state.internal_state.mod.handle_request(req, state.user_state) do
{:reply, id, reply, new_user_state} ->
packet = %{
"jsonrpc" => "2.0",
"id" => id,
"result" => reply
}
GenLSP.Buffer.outgoing(packet)
loop(Map.put(state, :user_state, new_user_state), parent, deb)
{:noreply, new_user_state} ->
loop(Map.put(state, :user_state, new_user_state), parent, deb)
end
end,
"Last message received: handle_request #{inspect(request)}"
)
{:notification, _from, notification} ->
attempt(
fn ->
note = GenLSP.Protocol.new(notification)
case state.internal_state.mod.handle_notification(note, state.user_state) do
{:noreply, new_user_state} ->
loop(Map.put(state, :user_state, new_user_state), parent, deb)
end
end,
"Last message received: handle_notification #{inspect(notification)}"
)
message ->
attempt(
fn ->
case state.internal_state.mod.handle_info(message, state.user_state) do
{:noreply, new_user_state} ->
loop(Map.put(state, :user_state, new_user_state), parent, deb)
end
end,
"Last message received: handle_info #{inspect(message)}"
)
end
end
defp attempt(callback, message) do
callback.()
rescue
e ->
Logger.error("""
LSP Exited.
#{message}
#{Exception.format(:error, e, __STACKTRACE__)}
""")
reraise e, __STACKTRACE__
end
@doc false
def system_continue(parent, deb, state) do
loop(state, parent, deb)
end
@doc false
def system_terminate(reason, _parent, _deb, _chs) do
exit(reason)
end
@doc false
def system_get_state(state) do
{:ok, state}
end
@doc false
def system_replace_state(state_fun, state) do
new_state = state_fun.(state)
{:ok, new_state, new_state}
end
def log(level, message) when level in [:error, :warning, :info, :log] do
GenLSP.notify(%GenLSP.Protocol.Notifications.WindowLogMessage{
params: %GenLSP.Protocol.Structures.LogMessageParams{
type: GenLSP.Communication.LogLevel.level(level),
message: message
}
})
end
end