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