Skip to content

Commit

Permalink
Add support for rendering erlang see tags (#287)
Browse files Browse the repository at this point in the history
* add support for rendering erlang see tags

fix rendering of links in erlang docs

* format
  • Loading branch information
lukaszsamson committed Jan 24, 2024
1 parent c38b0e2 commit 2ded9a1
Show file tree
Hide file tree
Showing 9 changed files with 526 additions and 62 deletions.
15 changes: 15 additions & 0 deletions lib/elixir_sense/core/applications.ex
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,19 @@ defmodule ElixirSense.Core.Applications do
# for performance.
:ets.match(:ac_tab, {{:loaded, :"$1"}, :_})
end

def get_application(module) do
Enum.find_value(:application.loaded_applications(), fn {app, _, _} ->
modules =

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.14.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.15.x | Erlang/OTP 26.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.12.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.14.x | Erlang/OTP 26.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 22.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.15.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.14.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.14.x | Erlang/OTP 23.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.15.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test (Elixir 1.13.x | Erlang/OTP 23.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 22.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / static analysis (Elixir 1.15.x | Erlang/OTP 26.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 23.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.15.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.15.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.13.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.14.x | Erlang/OTP 26.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.14.x | Erlang/OTP 23.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.14.x | Erlang/OTP 24.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.15.x | Erlang/OTP 26.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)

Check warning on line 57 in lib/elixir_sense/core/applications.ex

View workflow job for this annotation

GitHub Actions / mix test windows (Elixir 1.14.x | Erlang/OTP 25.x)

variable "modules" is unused (if the variable is not meant to be used, prefix it with an underscore)
case :application.get_key(app, :modules) do
{:ok, modules} ->
if module in modules do
app
end

:undefined ->
nil
end
end)
end
end
245 changes: 210 additions & 35 deletions lib/elixir_sense/core/erlang_html.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,88 +35,99 @@ defmodule ElixirSense.Core.ErlangHtml do
Document AST is defined in http://erlang.org/doc/apps/erl_docgen/doc_storage.html
"""

@spec to_markdown(chunk_element()) :: String.t()
def to_markdown(ast), do: to_markdown(ast, [], :normal)
@spec to_markdown(chunk_element(), module(), atom()) :: String.t()
def to_markdown(ast, module, app), do: to_markdown(ast, [], :normal, module, app)

def to_markdown(binary, _parents, sanitize_mode) when is_binary(binary) do
def to_markdown(binary, _parents, sanitize_mode, _module, _app) when is_binary(binary) do
sanitize(binary, sanitize_mode)
end

def to_markdown(list, parents, sanitize_mode) when is_list(list) do
Enum.map_join(list, "", &to_markdown(&1, parents, sanitize_mode))
def to_markdown(list, parents, sanitize_mode, module, app) when is_list(list) do
Enum.map_join(list, "", &to_markdown(&1, parents, sanitize_mode, module, app))
end

def to_markdown({:br, _attrs, _inner}, parents, _sanitize_mode) do
def to_markdown({:br, _attrs, _inner}, parents, _sanitize_mode, _module, _app) do
" \n" <> build_prefix(parents)
end

def to_markdown({:p, _attrs, inner}, parents, sanitize_mode) do
def to_markdown({:p, _attrs, inner}, parents, sanitize_mode, module, app) do
prefix = build_prefix(parents)
to_markdown(inner, parents, sanitize_mode) <> "\n" <> prefix <> "\n" <> prefix
to_markdown(inner, parents, sanitize_mode, module, app) <> "\n" <> prefix <> "\n" <> prefix
end

def to_markdown({tag, _attrs, inner}, parents, sanitize_mode) when tag in [:em, :i] do
"*" <> to_markdown(inner, parents, sanitize_mode) <> "*"
def to_markdown({tag, _attrs, inner}, parents, sanitize_mode, module, app)
when tag in [:em, :i] do
"*" <> to_markdown(inner, parents, sanitize_mode, module, app) <> "*"
end

def to_markdown({tag, _attrs, inner}, parents, sanitize_mode) when tag in [:strong, :b] do
"**" <> to_markdown(inner, parents, sanitize_mode) <> "**"
def to_markdown({tag, _attrs, inner}, parents, sanitize_mode, module, app)
when tag in [:strong, :b] do
"**" <> to_markdown(inner, parents, sanitize_mode, module, app) <> "**"
end

def to_markdown({:ul, _attrs, inner}, parents, sanitize_mode) do
def to_markdown({:ul, _attrs, inner}, parents, sanitize_mode, module, app) do
prefix = build_prefix(parents)

items =
inner
|> Enum.map_join("", fn {:li, _attrs, li_inner} ->
"- #{to_markdown(li_inner, [:li, :ul | parents], sanitize_mode)}\n" <> prefix
"- #{to_markdown(li_inner, [:li, :ul | parents], sanitize_mode, module, app)}\n" <> prefix
end)

items <> "\n" <> prefix
end

def to_markdown({:ol, _attrs, inner}, parents, sanitize_mode) do
def to_markdown({:ol, _attrs, inner}, parents, sanitize_mode, module, app) do
prefix = build_prefix(parents)

items =
inner
|> Enum.with_index(1)
|> Enum.map_join("", fn {{:li, _attrs, li_inner}, index} ->
"#{index}. #{to_markdown(li_inner, [:li, :ol | parents], sanitize_mode)}\n" <> prefix
"#{index}. #{to_markdown(li_inner, [:li, :ol | parents], sanitize_mode, module, app)}\n" <>
prefix
end)

items <> "\n" <> prefix
end

def to_markdown({:dl, _attrs, inner}, parents, sanitize_mode) do
def to_markdown({:dl, _attrs, inner}, parents, sanitize_mode, module, app) do
prefix = build_prefix(parents)
to_markdown(inner, parents, sanitize_mode) <> "\n" <> prefix
to_markdown(inner, parents, sanitize_mode, module, app) <> "\n" <> prefix
end

def to_markdown({:dt, _attrs, inner}, parents, sanitize_mode) do
"**" <> to_markdown(inner, parents, sanitize_mode) <> ":** "
def to_markdown({:dt, _attrs, inner}, parents, sanitize_mode, module, app) do
"**" <> to_markdown(inner, parents, sanitize_mode, module, app) <> ":** "
end

def to_markdown({:dd, _attrs, inner}, parents, sanitize_mode) do
def to_markdown({:dd, _attrs, inner}, parents, sanitize_mode, module, app) do
prefix = build_prefix(parents)
to_markdown(inner, parents, sanitize_mode) <> " \n" <> prefix
to_markdown(inner, parents, sanitize_mode, module, app) <> " \n" <> prefix
end

def to_markdown({:pre, _attrs1, [{:code, _attrs2, inner}]}, parents, _sanitize_mode) do
def to_markdown(
{:pre, _attrs1, [{:code, _attrs2, inner}]},
parents,
_sanitize_mode,
module,
app
) do
prefix = build_prefix(parents)
# TODO should we fence it as erlang?
"```\n" <> prefix <> to_markdown(inner, parents, :none) <> "\n" <> prefix <> "```\n" <> prefix
"```\n" <>
prefix <>
to_markdown(inner, parents, :none, module, app) <> "\n" <> prefix <> "```\n" <> prefix
end

for i <- 1..6,
tag = :"h#{i}",
prefix = for(_ <- 1..i, into: "", do: "#") do
def to_markdown({unquote(tag), _attrs, inner}, parents, sanitize_mode) do
unquote(prefix) <> " " <> to_markdown(inner, parents, sanitize_mode) <> "\n\n"
def to_markdown({unquote(tag), _attrs, inner}, parents, sanitize_mode, module, app) do
unquote(prefix) <> " " <> to_markdown(inner, parents, sanitize_mode, module, app) <> "\n\n"
end
end

def to_markdown({:div, attrs, inner}, parents, sanitize_mode) do
def to_markdown({:div, attrs, inner}, parents, sanitize_mode, module, app) do
class = attrs[:class]
prefix = build_prefix(parents)
maybe_class = if class != nil, do: String.upcase(class) <> ": \n" <> prefix, else: ""
Expand All @@ -131,20 +142,184 @@ defmodule ElixirSense.Core.ErlangHtml do
"\n" <>
prefix <>
maybe_class <>
to_markdown(inner, parents, sanitize_mode) <>
to_markdown(inner, parents, sanitize_mode, module, app) <>
"\n" <> prefix <> "\n" <> prefix <> "---" <> "\n" <> prefix <> "\n" <> prefix
end

def to_markdown({:code, _attrs, inner}, parents, _sanitize_mode) do
"`" <> to_markdown(inner, parents, :backtick) <> "`"
def to_markdown({:code, _attrs, inner}, parents, _sanitize_mode, module, app) do
"`" <> to_markdown(inner, parents, :backtick, module, app) <> "`"
end

def to_markdown({:a, _attrs, []}, _parents, _sanitize_mode) do
""
end
def to_markdown({:a, attrs, inner}, parents, sanitize_mode, module_fallback, app_fallback) do
href = Keyword.get(attrs, :href, "")

rel = Keyword.get(attrs, :rel)

href =
if rel do
[base, hash] =
case String.split(href, "#", parts: 2) do
[base, hash] -> [base, hash |> String.replace(" ", "%20")]
[base] -> [base, ""]
end

[app, rest] =
case String.split(base, ":", parts: 2) do
[app, rest] -> [app, rest]
[rest] -> ["", rest]
end

# based on
# https://www.erlang.org/doc/apps/erl_docgen/inline_tags#%3Csee*%3E---see-tags
case Keyword.get(attrs, :rel) do
"https://erlang.org/doc/link/seemfa" ->
# we need to transform
# stdlib:gen_server#Module:handle_call/3
# gen_server#Module:handle_call/3
# #Module:handle_call/3
# into
# https://www.erlang.org/doc/man/gen_server#Module:handle_call-3

module =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/#{module}#" <> String.replace(hash, "/", "-")

"https://erlang.org/doc/link/seeerl" ->
# stdlib:string -> https://www.erlang.org/doc/man/string
# stdlib:string#oldapi -> https://www.erlang.org/doc/man/string#oldapi

module =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/" <>
module <> if(hash != "", do: "#" <> hash, else: "")

"https://erlang.org/doc/link/seetype" ->
# stdlib:gen_server#server_ref
# https://www.erlang.org/doc/man/gen_server#type-server_ref

module =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/#{module}#type-#{hash}"

"https://erlang.org/doc/link/seeapp" ->
# stdlib:STDLIB_app -> https://www.erlang.org/doc/man/stdlib_app
# stdlib:index -> https://www.erlang.org/doc/apps/stdlib/

if app == "" do
app = rest |> String.downcase() |> String.replace_suffix("_app", "")

app =
if app != "" do
app
else
to_string(app_fallback)
end

"https://www.erlang.org/doc/man/#{app}_app"
else
if rest == "index" do
"https://www.erlang.org/doc/apps/#{app}/"
else
"https://www.erlang.org/doc/man/#{app}_app"
end
end <> if(hash != "", do: "#" <> hash, else: "")

"https://erlang.org/doc/link/seecom" ->
# erts:epmd -> https://www.erlang.org/doc/man/epmd
# erts:erl#remsh
# erl
# erl#async_thread_pool_size
# #environment_variables

module =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/" <>
module <> if(hash != "", do: "#" <> hash, else: "")

"https://erlang.org/doc/link/seecref" ->
# erts:erl_nif -> https://www.erlang.org/doc/man/erl_nif
# erl_driver#driver_alloc_binary
# #call

module =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/" <>
module <> if(hash != "", do: "#" <> hash, else: "")

"https://erlang.org/doc/link/seefile" ->
# kernel:config -> https://www.erlang.org/doc/man/config
# figures/perf-beamasm.svg
# diameter_dict#MESSAGE_RECORDS
# #FILE_FORMAT
# diameter_dict

file =
if rest != "" do
rest
else
to_string(module_fallback)
end

"https://www.erlang.org/doc/man/" <> file <> if(hash != "", do: "#" <> hash, else: "")

"https://erlang.org/doc/link/seeguide" ->
# kernel:index -> https://www.erlang.org/doc/apps/kernel/users_guide
# kernel:logger_chapter -> https://www.erlang.org/doc/apps/kernel/logger_chapter

# #listen
# erl_dist_protocol#dflags
# stdlib:unicode_usage#unicode_file_names
# system/design_principles:gen_server_concepts -> https://www.erlang.org/doc/design_principles/gen_server_concepts

if String.starts_with?(app, "system/") do
page =
app
|> String.replace_prefix("system/", "")

"https://www.erlang.org/doc/#{page}/#{if(rest == "index", do: "users_guide", else: rest)}"
else
app =
if app == "" do
to_string(app_fallback)
else
app
end

"https://www.erlang.org/doc/apps/#{app}/#{if(rest == "index", do: "users_guide", else: rest)}"
end <> if(hash != "", do: "#" <> hash, else: "")
end
else
href
end

def to_markdown({:a, _attrs, inner}, parents, sanitize_mode) do
"[" <> to_markdown(inner, parents, sanitize_mode) <> "]"
"[" <>
to_markdown(inner, parents, sanitize_mode, module_fallback, app_fallback) <>
"](" <> href <> ")"
end

defp build_prefix(list), do: build_prefix(list, "")
Expand Down
4 changes: 3 additions & 1 deletion lib/elixir_sense/core/metadata.ex
Original file line number Diff line number Diff line change
Expand Up @@ -374,13 +374,15 @@ defmodule ElixirSense.Core.Metadata do

meta = %{implementing: behaviour}
spec = Introspection.get_spec_as_string(nil, f, a, kind, meta)
app = ElixirSense.Core.Applications.get_application(behaviour)

Check warning on line 377 in lib/elixir_sense/core/metadata.ex

View workflow job for this annotation

GitHub Actions / static analysis (Elixir 1.15.x | Erlang/OTP 26.x)

Nested modules could be aliased at the top of the invoking module.

case docs[{f, a}] do
nil ->
{spec, "", meta}

{_signatures, docs, callback_meta, mime_type} ->
{spec, docs |> NormalizedCode.extract_docs(mime_type), callback_meta |> Map.merge(meta)}
{spec, docs |> NormalizedCode.extract_docs(mime_type, behaviour, app),
callback_meta |> Map.merge(meta)}
end
end

Expand Down
Loading

0 comments on commit 2ded9a1

Please sign in to comment.