Skip to content

Commit

Permalink
feat: hover (#220)
Browse files Browse the repository at this point in the history
Co-authored-by: Denis Tataurov <[email protected]>
  • Loading branch information
mhanberg and sineed committed Sep 17, 2023
1 parent 627bb94 commit c5a68df
Show file tree
Hide file tree
Showing 12 changed files with 789 additions and 53 deletions.
18 changes: 0 additions & 18 deletions bin/nextls

This file was deleted.

98 changes: 98 additions & 0 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ defmodule NextLS do
alias GenLSP.Requests.TextDocumentDefinition
alias GenLSP.Requests.TextDocumentDocumentSymbol
alias GenLSP.Requests.TextDocumentFormatting
alias GenLSP.Requests.TextDocumentHover
alias GenLSP.Requests.TextDocumentReferences
alias GenLSP.Requests.WorkspaceSymbol
alias GenLSP.Structures.DidChangeWatchedFilesParams
Expand Down Expand Up @@ -110,6 +111,7 @@ defmodule NextLS do
change: TextDocumentSyncKind.full()
},
document_formatting_provider: true,
hover_provider: true,
workspace_symbol_provider: true,
document_symbol_provider: true,
references_provider: true,
Expand Down Expand Up @@ -248,6 +250,102 @@ defmodule NextLS do
{:reply, locations, lsp}
end

def handle_request(%TextDocumentHover{params: %{position: position, text_document: %{uri: uri}}}, lsp) do
file = URI.parse(uri).path
line = position.line + 1
col = position.character + 1

select = ~w<identifier type module arity start_line start_column end_line end_column>a

reference_query = ~Q"""
SELECT :select
FROM "references" refs
WHERE refs.file = ?
AND ? BETWEEN refs.start_line AND refs.end_line
AND ? BETWEEN refs.start_column AND refs.end_column
ORDER BY refs.id ASC
LIMIT 1
"""

locations =
dispatch(lsp.assigns.registry, :databases, fn databases ->
Enum.flat_map(databases, fn {database, _} ->
DB.query(database, reference_query, args: [file, line, col], select: select)
end)
end)

resp =
case locations do
[reference] ->
mod =
if reference.module == String.downcase(reference.module) do
String.to_existing_atom(reference.module)
else
Module.concat([reference.module])
end

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)

value =
with {:ok, {:docs_v1, _, _lang, content_type, %{"en" => mod_doc}, _, fdocs}} <- result do
case reference.type do
"alias" ->
"""
## #{reference.module}
#{NextLS.HoverHelpers.to_markdown(content_type, mod_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
end
else
_ -> nil
end

with value when is_binary(value) <- value do
%GenLSP.Structures.Hover{
contents: %GenLSP.Structures.MarkupContent{
kind: GenLSP.Enumerations.MarkupKind.markdown(),
value: String.trim(value)
},
range: %Range{
start: %Position{line: reference.start_line - 1, character: reference.start_column - 1},
end: %Position{line: reference.end_line - 1, character: reference.end_column - 1}
}
}
end

_ ->
nil
end

{:reply, resp, lsp}
end

def handle_request(%WorkspaceSymbol{params: %{query: query}}, lsp) do
case_sensitive? = String.downcase(query) != query

Expand Down
52 changes: 35 additions & 17 deletions lib/next_ls/db.ex
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ defmodule NextLS.DB do
end

@spec query(pid(), query(), list()) :: list()
def query(server, query, args \\ []), do: GenServer.call(server, {:query, query, args}, :infinity)
def query(server, query, opts \\ []), do: GenServer.call(server, {:query, query, opts}, :infinity)

@spec insert_symbol(pid(), map()) :: :ok
def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload})
Expand Down Expand Up @@ -43,10 +43,26 @@ defmodule NextLS.DB do
}}
end

def handle_call({:query, query, args}, _from, %{conn: conn} = s) do
def handle_call({:query, query, args_or_opts}, _from, %{conn: conn} = s) do
{:message_queue_len, count} = Process.info(self(), :message_queue_len)
NextLS.DB.Activity.update(s.activity, count)
rows = __query__({conn, s.logger}, query, args)
opts = if Keyword.keyword?(args_or_opts), do: args_or_opts, else: [args: args_or_opts]

query =
if opts[:select] do
String.replace(query, ":select", Enum.map_join(opts[:select], ", ", &to_string/1))
else
query
end

rows =
for row <- __query__({conn, s.logger}, query, opts[:args] || []) do
if opts[:select] do
opts[:select] |> Enum.zip(row) |> Map.new()
else
row
end
end

{:reply, rows, s}
end
Expand Down Expand Up @@ -134,23 +150,25 @@ defmodule NextLS.DB do
source: source
} = reference

line = meta[:line] || 1
col = meta[:column] || 0
if (meta[:line] && meta[:column]) || (reference[:range][:start] && reference[:range][:stop]) do
line = meta[:line] || 1
col = meta[:column] || 0

{start_line, start_column} = reference[:range][:start] || {line, col}
{start_line, start_column} = reference[:range][:start] || {line, col}

{end_line, end_column} =
reference[:range][:stop] ||
{line, col + String.length(identifier |> to_string() |> String.replace("Elixir.", ""))}
{end_line, end_column} =
reference[:range][:stop] ||
{line, col + String.length(identifier |> to_string() |> String.replace("Elixir.", "")) - 1}

__query__(
{conn, s.logger},
~Q"""
INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
[identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column, source]
)
__query__(
{conn, s.logger},
~Q"""
INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column, source)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
[identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column, source]
)
end

{:noreply, s}
end
Expand Down
File renamed without changes.
56 changes: 56 additions & 0 deletions lib/next_ls/helpers/hover_helpers.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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<warning note do dont quote> 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", []) do
""
end
end
6 changes: 6 additions & 0 deletions lib/next_ls/runtime/sidecar.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ defmodule NextLS.Runtime.Sidecar do
{:ok, %{db: db}}
end

def handle_info({:tracer, :dbg, term}, state) do
dbg(term)

{:noreply, state}
end

def handle_info({:tracer, payload}, state) do
attributes = Attributes.get_module_attributes(payload.file, payload.module)
payload = Map.put_new(payload, :symbols, attributes)
Expand Down
4 changes: 3 additions & 1 deletion priv/monkey/_next_ls_private_compiler.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,9 @@ defmodule NextLSPrivate.Tracer do
:ok
end

def trace({:alias_reference, meta, module}, env) do
def trace({:alias_reference, meta, module} = term, env) do
parent = parent_pid()
# Process.send(parent, {:tracer, :dbg, {term, env.file}}, [])

alias_map = Map.new(env.aliases, fn {alias, mod} -> {mod, alias} end)

Expand Down Expand Up @@ -176,6 +177,7 @@ defmodule NextLSPrivate.Tracer do

def trace({:on_module, bytecode, _}, env) do
parent = parent_pid()
# Process.send(parent, {:tracer, :dbg, {:on_module, env}}, [])

defs = Module.definitions_in(env.module)

Expand Down
18 changes: 5 additions & 13 deletions test/next_ls/dependency_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ defmodule NextLS.DependencyTest do
4,
[
%{
"range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 11, "line" => 7}},
"range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 10, "line" => 7}},
"uri" => uri
}
]
Expand Down Expand Up @@ -190,11 +190,11 @@ defmodule NextLS.DependencyTest do
4,
[
%{
"range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 7, "line" => 3}},
"range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 6, "line" => 3}},
"uri" => uri
},
%{
"range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 7, "line" => 7}},
"range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 6, "line" => 7}},
"uri" => uri
}
]
Expand Down Expand Up @@ -229,19 +229,11 @@ defmodule NextLS.DependencyTest do
4,
[
%{
"range" => %{"end" => %{"character" => 15, "line" => 1}, "start" => %{"character" => 6, "line" => 1}},
"range" => %{"end" => %{"character" => 14, "line" => 1}, "start" => %{"character" => 6, "line" => 1}},
"uri" => uri
},
%{
"range" => %{"end" => %{"character" => 8, "line" => 1}, "start" => %{"character" => 0, "line" => 1}},
"uri" => uri
},
%{
"range" => %{"end" => %{"character" => 8, "line" => 1}, "start" => %{"character" => 0, "line" => 1}},
"uri" => uri
},
%{
"range" => %{"end" => %{"character" => 13, "line" => 8}, "start" => %{"character" => 4, "line" => 8}},
"range" => %{"end" => %{"character" => 12, "line" => 8}, "start" => %{"character" => 4, "line" => 8}},
"uri" => uri
}
]
Expand Down
File renamed without changes.
Loading

0 comments on commit c5a68df

Please sign in to comment.