Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for rendering erlang see tags #287

Merged
merged 2 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
# 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.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.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.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.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.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.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.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.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 (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.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 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 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 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 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.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.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.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 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)

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)
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 @@

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
Loading