diff --git a/apps/protocol/lib/lexical/protocol/json_rpc.ex b/apps/protocol/lib/lexical/protocol/json_rpc.ex index 03aefa6a6..970bf220d 100644 --- a/apps/protocol/lib/lexical/protocol/json_rpc.ex +++ b/apps/protocol/lib/lexical/protocol/json_rpc.ex @@ -2,6 +2,8 @@ defmodule Lexical.Protocol.JsonRpc do alias Lexical.Protocol.Notifications alias Lexical.Protocol.Requests + require Logger + @crlf "\r\n" def decode(message_string) do @@ -30,8 +32,10 @@ defmodule Lexical.Protocol.JsonRpc do {:ok, json_rpc} end + # These messages appear to be empty Responses (per LSP spec) sent to + # aknowledge Requests sent from the language server to the client. defp do_decode(%{"id" => _id, "result" => nil}) do - :error + {:error, :empty_response} end defp do_decode(%{"method" => method, "id" => _id} = request) do diff --git a/apps/server/lib/lexical/server/transport/std_io.ex b/apps/server/lib/lexical/server/transport/std_io.ex index 31479b4ee..97bf0a76d 100644 --- a/apps/server/lib/lexical/server/transport/std_io.ex +++ b/apps/server/lib/lexical/server/transport/std_io.ex @@ -1,6 +1,8 @@ defmodule Lexical.Server.Transport.StdIO do alias Lexical.Protocol.JsonRpc + require Logger + @behaviour Lexical.Server.Transport def start_link(device, callback) do @@ -59,16 +61,22 @@ defmodule Lexical.Server.Transport.StdIO do "\n" -> headers = parse_headers(buffer) - with {:ok, content_length} <- - header_value(headers, "content-length", &String.to_integer/1), + with {:ok, content_length} <- content_length(headers), {:ok, data} <- read_body(device, content_length), {:ok, message} <- JsonRpc.decode(data) do callback.(message) + else + {:error, :empty_response} -> + :noop + + {:error, reason} -> + Logger.critical("read protocol message: #{inspect(reason)}") end loop([], device, callback) :eof -> + Logger.critical("stdio received :eof, server will stop.") maybe_stop() line -> @@ -76,27 +84,44 @@ defmodule Lexical.Server.Transport.StdIO do end end - defp parse_headers(headers) do - Enum.map(headers, &parse_header/1) + defp content_length(headers) do + with {:ok, len_str} <- find_header(headers, "content-length") do + parse_length(len_str) + end + end + + defp find_header(headers, name) do + case List.keyfind(headers, name, 0) do + {_, len_str} -> {:ok, len_str} + nil -> {:error, {:header_not_found, name}} + end end - defp header_value(headers, header_name, converter) do - case List.keyfind(headers, header_name, 0) do - nil -> :error - {_, value} -> {:ok, converter.(value)} + defp parse_length(len_str) when is_binary(len_str) do + case Integer.parse(len_str) do + {int, ""} -> {:ok, int} + :error -> {:error, {:cant_parse_length, len_str}} end end defp read_body(device, byte_count) do case IO.binread(device, byte_count) do - data when is_binary(data) or is_list(data) -> + data when is_binary(data) -> {:ok, data} - other -> - other + :eof -> + Logger.critical("stdio received :eof, server will stop.") + maybe_stop() + + {:error, reason} -> + {:error, reason} end end + defp parse_headers(headers) do + Enum.map(headers, &parse_header/1) + end + defp parse_header(line) do [name, value] = String.split(line, ":")