diff --git a/lib/next_ls.ex b/lib/next_ls.ex index 74484a37..ae0ac94f 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -402,32 +402,16 @@ defmodule NextLS do end) value = - with {:ok, {:docs_v1, _, _lang, content_type, %{"en" => mod_doc}, _, fdocs}} <- result do + with {:ok, result} <- result, + %NextLS.Docs{} = doc <- NextLS.Docs.new(result, reference.module) do case reference.type do "alias" -> - """ - ## #{reference.module} - - #{NextLS.HoverHelpers.to_markdown(content_type, mod_doc)} - """ + NextLS.Docs.module(doc) "function" -> - doc = - Enum.find(fdocs, fn {{type, name, _a}, _, _, _doc, _} -> - type in [:function, :macro] and to_string(name) == reference.identifier - end) - - case doc do - {_, _, _, %{"en" => fdoc}, _} -> - """ - ## #{Macro.to_string(mod)}.#{reference.identifier}/#{reference.arity} - - #{NextLS.HoverHelpers.to_markdown(content_type, fdoc)} - """ - - _ -> - nil - end + NextLS.Docs.function(doc, fn name, a, documentation, _other -> + to_string(name) == reference.identifier and documentation != :hidden and a >= reference.arity + end) _ -> nil @@ -590,62 +574,33 @@ defmodule NextLS do completion_item else %{"uri" => uri, "data" => data} -> - docs = - case data |> Base.decode64!() |> :erlang.binary_to_term() do - {mod, function, arity} -> - result = - dispatch(lsp.assigns.registry, :runtimes, fn entries -> - [result] = - for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do - Runtime.call(runtime, {Code, :fetch_docs, [mod]}) - end - - result - end) - - with {:ok, {:docs_v1, _, _lang, content_type, %{"en" => _mod_doc}, _, fdocs}} <- result do - doc = - Enum.find(fdocs, fn {{type, name, a}, _some_number, _signature, doc, _other} -> - type in [:function, :macro] and to_string(name) == function and doc != :hidden and a >= arity - end) - - case doc do - {_, _, [signature], %{"en" => fdoc}, _} -> - """ - ## #{Macro.to_string(mod)}.#{function}/#{arity} - - `#{signature}` + data = data |> Base.decode64!() |> :erlang.binary_to_term() - #{NextLS.HoverHelpers.to_markdown(content_type, fdoc)} - """ - - _ -> - nil - end - else - _ -> nil - end + module = + case data do + {mod, _function, _arity} -> mod + mod -> mod + end - mod -> - result = - dispatch(lsp.assigns.registry, :runtimes, fn entries -> - [result] = - for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do - Runtime.call(runtime, {Code, :fetch_docs, [mod]}) - end + result = + dispatch_to_workspace(lsp.assigns.registry, uri, fn runtime -> + Runtime.call(runtime, {Code, :fetch_docs, [module]}) + end) - result + docs = + with {:ok, doc} <- result, + %NextLS.Docs{} = doc <- NextLS.Docs.new(doc, module) do + case data do + {_mod, function, arity} -> + NextLS.Docs.function(doc, fn name, a, documentation, _other -> + to_string(name) == function and documentation != :hidden and a >= arity end) - with {:ok, {:docs_v1, _, _lang, content_type, %{"en" => doc}, _, _fdocs}} <- result do - """ - ## #{Macro.to_string(mod)} - - #{NextLS.HoverHelpers.to_markdown(content_type, doc)} - """ - else - _ -> nil - end + mod when is_atom(mod) -> + NextLS.Docs.module(doc) + end + else + _ -> nil end %{completion_item | documentation: docs} @@ -1438,6 +1393,27 @@ defmodule NextLS do end end + defp dispatch_to_workspace(registry, uri, callback) do + ref = make_ref() + me = self() + + Registry.dispatch(registry, :runtimes, fn entries -> + [result] = + for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do + callback.(runtime) + end + + send(me, {ref, result}) + end) + + receive do + {^ref, result} -> result + after + 1000 -> + :timeout + end + end + defp symbol_info(file, line, col, database) do definition_query = ~Q""" SELECT module, type, name diff --git a/lib/next_ls/autocomplete.ex b/lib/next_ls/autocomplete.ex index edad5367..df7df9f0 100644 --- a/lib/next_ls/autocomplete.ex +++ b/lib/next_ls/autocomplete.ex @@ -316,7 +316,7 @@ defmodule NextLS.Autocomplete do %{ kind: :module, name: mod, - data: mod + data: String.to_atom(mod) # docs: """ ### #{Macro.to_string(mod)} diff --git a/lib/next_ls/docs.ex b/lib/next_ls/docs.ex new file mode 100644 index 00000000..8359d840 --- /dev/null +++ b/lib/next_ls/docs.ex @@ -0,0 +1,154 @@ +defmodule NextLS.Docs do + @moduledoc false + + defstruct module: nil, mdoc: nil, functions: [], content_type: nil + + def new({:docs_v1, _, _lang, content_type, mdoc, _, fdocs}, module) do + mdoc = + case mdoc do + %{"en" => mdoc} -> mdoc + _ -> nil + end + + %__MODULE__{ + content_type: content_type, + module: module, + mdoc: mdoc, + functions: fdocs + } + end + + def new(_, _) do + nil + end + + def module(%__MODULE__{} = doc) do + """ + ## #{Macro.to_string(doc.module)} + + #{to_markdown(doc.content_type, doc.mdoc)} + """ + end + + def function(%__MODULE__{} = doc, callback) do + result = + Enum.find(doc.functions, fn {{type, name, arity}, _some_number, _signature, doc, other} -> + type in [:function, :macro] and callback.(name, arity, doc, other) + end) + + case result do + {{_, name, arity}, _some_number, signature, %{"en" => fdoc}, _other} -> + """ + ## #{Macro.to_string(doc.module)}.#{name}/#{arity} + + `#{signature}` + + #{to_markdown(doc.content_type, fdoc)} + """ + + _ -> + nil + end + end + + @spec to_markdown(String.t(), String.t() | list()) :: String.t() + def to_markdown(type, docs) + def to_markdown("text/markdown", docs), do: docs + + def to_markdown("application/erlang+html" = type, [{:p, _, children} | rest]) do + String.trim(to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest)) + end + + def to_markdown("application/erlang+html" = type, [{:div, attrs, children} | rest]) do + prefix = + if attrs[:class] in ~w do + "> " + else + "" + end + + prefix <> to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:a, attrs, children} | rest]) do + space = if List.last(children) == " ", do: " ", else: "" + + "[#{String.trim(to_markdown(type, children))}](#{attrs[:href]})" <> space <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [doc | rest]) when is_binary(doc) do + doc <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:h1, _, children} | rest]) do + "# #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:h2, _, children} | rest]) do + "## #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:h3, _, children} | rest]) do + "### #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:h4, _, children} | rest]) do + "#### #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:h5, _, children} | rest]) do + "##### #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:pre, _, [{:code, _, children}]} | rest]) do + "```erlang\n#{to_markdown(type, children)}\n```\n\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:ul, [class: "types"], lis} | rest]) do + "### Types\n\n#{to_markdown(type, lis)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:ul, _, lis} | rest]) do + "#{to_markdown(type, lis)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:li, [name: text], _} | rest]) do + "* #{text}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:li, _, children} | rest]) do + "* #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:code, _, bins} | rest]) do + "`#{IO.iodata_to_binary(bins)}`" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:em, _, bins} | rest]) do + "_#{IO.iodata_to_binary(bins)}_" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:dl, _, lis} | rest]) do + "#{to_markdown(type, lis)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:dt, _, children} | rest]) do + "* #{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:dd, _, children} | rest]) do + "#{to_markdown(type, children)}\n" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html" = type, [{:i, _, children} | rest]) do + "_#{IO.iodata_to_binary(children)}_" <> to_markdown(type, rest) + end + + def to_markdown("application/erlang+html", []) do + "" + end + + def to_markdown("application/erlang+html", nil) do + "" + end +end diff --git a/lib/next_ls/helpers/hover_helpers.ex b/lib/next_ls/helpers/hover_helpers.ex deleted file mode 100644 index efe4d4e8..00000000 --- a/lib/next_ls/helpers/hover_helpers.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule NextLS.HoverHelpers do - @moduledoc false - - @spec to_markdown(String.t(), String.t() | list()) :: String.t() - def to_markdown(type, docs) - def to_markdown("text/markdown", docs), do: docs - - def to_markdown("application/erlang+html" = type, [{:p, _, children} | rest]) do - String.trim(to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest)) - end - - def to_markdown("application/erlang+html" = type, [{:div, attrs, children} | rest]) do - prefix = - if attrs[:class] in ~w do - "> " - else - "" - end - - prefix <> to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:a, attrs, children} | rest]) do - space = if List.last(children) == " ", do: " ", else: "" - - "[#{String.trim(to_markdown(type, children))}](#{attrs[:href]})" <> space <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [doc | rest]) when is_binary(doc) do - doc <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:pre, _, [{:code, _, children}]} | rest]) do - "```erlang\n#{to_markdown(type, children)}\n```\n\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:ul, _, lis} | rest]) do - "#{to_markdown(type, lis)}\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:li, _, children} | rest]) do - "* #{to_markdown(type, children)}\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:code, _, bins} | rest]) do - "`#{IO.iodata_to_binary(bins)}`" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:em, _, bins} | rest]) do - "_#{IO.iodata_to_binary(bins)}_" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:dl, _, lis} | rest]) do - "#{to_markdown(type, lis)}\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:dt, _, children} | rest]) do - "* #{to_markdown(type, children)}\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html" = type, [{:dd, _, children} | rest]) do - "#{to_markdown(type, children)}\n" <> to_markdown(type, rest) - end - - def to_markdown("application/erlang+html", []) do - "" - end -end diff --git a/test/next_ls/helpers/hover_helpers_test.exs b/test/next_ls/docs_test.exs similarity index 95% rename from test/next_ls/helpers/hover_helpers_test.exs rename to test/next_ls/docs_test.exs index 012502f6..75ac8782 100644 --- a/test/next_ls/helpers/hover_helpers_test.exs +++ b/test/next_ls/docs_test.exs @@ -1,7 +1,7 @@ -defmodule NextLS.HoverHelpersTest do +defmodule NextLS.DocsTest do use ExUnit.Case, async: true - alias NextLS.HoverHelpers + alias NextLS.Docs describe "converts erlang html format to markdown" do test "some divs and p and code" do @@ -35,7 +35,7 @@ defmodule NextLS.HoverHelpersTest do ]} ] - actual = HoverHelpers.to_markdown("application/erlang+html", html) + actual = Docs.to_markdown("application/erlang+html", html) assert actual == String.trim(""" @@ -60,7 +60,7 @@ defmodule NextLS.HoverHelpersTest do ]} ] - actual = HoverHelpers.to_markdown("application/erlang+html", html) + actual = Docs.to_markdown("application/erlang+html", html) assert actual == String.trim(""" @@ -103,7 +103,7 @@ defmodule NextLS.HoverHelpersTest do {:p, [], ["Allowed in guard tests."]} ] - actual = HoverHelpers.to_markdown("application/erlang+html", html) + actual = Docs.to_markdown("application/erlang+html", html) assert actual == String.trim(""" @@ -191,7 +191,7 @@ defmodule NextLS.HoverHelpersTest do ]} ] - actual = HoverHelpers.to_markdown("application/erlang+html", html) + actual = Docs.to_markdown("application/erlang+html", html) assert String.trim(actual) == String.trim(""" @@ -231,7 +231,7 @@ defmodule NextLS.HoverHelpersTest do {:p, [], ["Returns ", {:code, [], ["error"]}, " if no value is associated with ", {:code, [], ["Flag"]}, "."]} ] - actual = HoverHelpers.to_markdown("application/erlang+html", html) + actual = Docs.to_markdown("application/erlang+html", html) assert String.trim(actual) == String.trim("""