Skip to content

Commit c5a68df

Browse files
mhanbergsineed
andauthored
feat: hover (#220)
Co-authored-by: Denis Tataurov <[email protected]>
1 parent 627bb94 commit c5a68df

12 files changed

+789
-53
lines changed

bin/nextls

Lines changed: 0 additions & 18 deletions
This file was deleted.

lib/next_ls.ex

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ defmodule NextLS do
1919
alias GenLSP.Requests.TextDocumentDefinition
2020
alias GenLSP.Requests.TextDocumentDocumentSymbol
2121
alias GenLSP.Requests.TextDocumentFormatting
22+
alias GenLSP.Requests.TextDocumentHover
2223
alias GenLSP.Requests.TextDocumentReferences
2324
alias GenLSP.Requests.WorkspaceSymbol
2425
alias GenLSP.Structures.DidChangeWatchedFilesParams
@@ -110,6 +111,7 @@ defmodule NextLS do
110111
change: TextDocumentSyncKind.full()
111112
},
112113
document_formatting_provider: true,
114+
hover_provider: true,
113115
workspace_symbol_provider: true,
114116
document_symbol_provider: true,
115117
references_provider: true,
@@ -248,6 +250,102 @@ defmodule NextLS do
248250
{:reply, locations, lsp}
249251
end
250252

253+
def handle_request(%TextDocumentHover{params: %{position: position, text_document: %{uri: uri}}}, lsp) do
254+
file = URI.parse(uri).path
255+
line = position.line + 1
256+
col = position.character + 1
257+
258+
select = ~w<identifier type module arity start_line start_column end_line end_column>a
259+
260+
reference_query = ~Q"""
261+
SELECT :select
262+
FROM "references" refs
263+
WHERE refs.file = ?
264+
AND ? BETWEEN refs.start_line AND refs.end_line
265+
AND ? BETWEEN refs.start_column AND refs.end_column
266+
ORDER BY refs.id ASC
267+
LIMIT 1
268+
"""
269+
270+
locations =
271+
dispatch(lsp.assigns.registry, :databases, fn databases ->
272+
Enum.flat_map(databases, fn {database, _} ->
273+
DB.query(database, reference_query, args: [file, line, col], select: select)
274+
end)
275+
end)
276+
277+
resp =
278+
case locations do
279+
[reference] ->
280+
mod =
281+
if reference.module == String.downcase(reference.module) do
282+
String.to_existing_atom(reference.module)
283+
else
284+
Module.concat([reference.module])
285+
end
286+
287+
result =
288+
dispatch(lsp.assigns.registry, :runtimes, fn entries ->
289+
[result] =
290+
for {runtime, %{uri: wuri}} <- entries, String.starts_with?(uri, wuri) do
291+
Runtime.call(runtime, {Code, :fetch_docs, [mod]})
292+
end
293+
294+
result
295+
end)
296+
297+
value =
298+
with {:ok, {:docs_v1, _, _lang, content_type, %{"en" => mod_doc}, _, fdocs}} <- result do
299+
case reference.type do
300+
"alias" ->
301+
"""
302+
## #{reference.module}
303+
304+
#{NextLS.HoverHelpers.to_markdown(content_type, mod_doc)}
305+
"""
306+
307+
"function" ->
308+
{_, _, _, doc, _} =
309+
Enum.find(fdocs, fn {{type, name, _a}, _, _, _doc, _} ->
310+
type in [:function, :macro] and to_string(name) == reference.identifier
311+
end)
312+
313+
case doc do
314+
%{"en" => fdoc} ->
315+
"""
316+
## #{Macro.to_string(mod)}.#{reference.identifier}/#{reference.arity}
317+
318+
#{NextLS.HoverHelpers.to_markdown(content_type, fdoc)}
319+
"""
320+
321+
_ ->
322+
nil
323+
end
324+
end
325+
else
326+
_ -> nil
327+
end
328+
329+
with value when is_binary(value) <- value do
330+
%GenLSP.Structures.Hover{
331+
contents: %GenLSP.Structures.MarkupContent{
332+
kind: GenLSP.Enumerations.MarkupKind.markdown(),
333+
value: String.trim(value)
334+
},
335+
range: %Range{
336+
start: %Position{line: reference.start_line - 1, character: reference.start_column - 1},
337+
end: %Position{line: reference.end_line - 1, character: reference.end_column - 1}
338+
}
339+
}
340+
end
341+
342+
_ ->
343+
nil
344+
end
345+
346+
{:reply, resp, lsp}
347+
end
348+
251349
def handle_request(%WorkspaceSymbol{params: %{query: query}}, lsp) do
252350
case_sensitive? = String.downcase(query) != query
253351

lib/next_ls/db.ex

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule NextLS.DB do
1111
end
1212

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

1616
@spec insert_symbol(pid(), map()) :: :ok
1717
def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload})
@@ -43,10 +43,26 @@ defmodule NextLS.DB do
4343
}}
4444
end
4545

46-
def handle_call({:query, query, args}, _from, %{conn: conn} = s) do
46+
def handle_call({:query, query, args_or_opts}, _from, %{conn: conn} = s) do
4747
{:message_queue_len, count} = Process.info(self(), :message_queue_len)
4848
NextLS.DB.Activity.update(s.activity, count)
49-
rows = __query__({conn, s.logger}, query, args)
49+
opts = if Keyword.keyword?(args_or_opts), do: args_or_opts, else: [args: args_or_opts]
50+
51+
query =
52+
if opts[:select] do
53+
String.replace(query, ":select", Enum.map_join(opts[:select], ", ", &to_string/1))
54+
else
55+
query
56+
end
57+
58+
rows =
59+
for row <- __query__({conn, s.logger}, query, opts[:args] || []) do
60+
if opts[:select] do
61+
opts[:select] |> Enum.zip(row) |> Map.new()
62+
else
63+
row
64+
end
65+
end
5066

5167
{:reply, rows, s}
5268
end
@@ -134,23 +150,25 @@ defmodule NextLS.DB do
134150
source: source
135151
} = reference
136152

137-
line = meta[:line] || 1
138-
col = meta[:column] || 0
153+
if (meta[:line] && meta[:column]) || (reference[:range][:start] && reference[:range][:stop]) do
154+
line = meta[:line] || 1
155+
col = meta[:column] || 0
139156

140-
{start_line, start_column} = reference[:range][:start] || {line, col}
157+
{start_line, start_column} = reference[:range][:start] || {line, col}
141158

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

146-
__query__(
147-
{conn, s.logger},
148-
~Q"""
149-
INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column, source)
150-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
151-
""",
152-
[identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column, source]
153-
)
163+
__query__(
164+
{conn, s.logger},
165+
~Q"""
166+
INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column, source)
167+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
168+
""",
169+
[identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column, source]
170+
)
171+
end
154172

155173
{:noreply, s}
156174
end
File renamed without changes.

lib/next_ls/helpers/hover_helpers.ex

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
defmodule NextLS.HoverHelpers do
2+
@moduledoc false
3+
4+
@spec to_markdown(String.t(), String.t() | list()) :: String.t()
5+
def to_markdown(type, docs)
6+
def to_markdown("text/markdown", docs), do: docs
7+
8+
def to_markdown("application/erlang+html" = type, [{:p, _, children} | rest]) do
9+
String.trim(to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest))
10+
end
11+
12+
def to_markdown("application/erlang+html" = type, [{:div, attrs, children} | rest]) do
13+
prefix =
14+
if attrs[:class] in ~w<warning note do dont quote> do
15+
"> "
16+
else
17+
""
18+
end
19+
20+
prefix <> to_markdown(type, children) <> "\n\n" <> to_markdown(type, rest)
21+
end
22+
23+
def to_markdown("application/erlang+html" = type, [{:a, attrs, children} | rest]) do
24+
space = if List.last(children) == " ", do: " ", else: ""
25+
26+
"[#{String.trim(to_markdown(type, children))}](#{attrs[:href]})" <> space <> to_markdown(type, rest)
27+
end
28+
29+
def to_markdown("application/erlang+html" = type, [doc | rest]) when is_binary(doc) do
30+
doc <> to_markdown(type, rest)
31+
end
32+
33+
def to_markdown("application/erlang+html" = type, [{:pre, _, [{:code, _, children}]} | rest]) do
34+
"```erlang\n#{to_markdown(type, children)}\n```\n\n" <> to_markdown(type, rest)
35+
end
36+
37+
def to_markdown("application/erlang+html" = type, [{:ul, _, lis} | rest]) do
38+
"#{to_markdown(type, lis)}\n" <> to_markdown(type, rest)
39+
end
40+
41+
def to_markdown("application/erlang+html" = type, [{:li, _, children} | rest]) do
42+
"* #{to_markdown(type, children)}\n" <> to_markdown(type, rest)
43+
end
44+
45+
def to_markdown("application/erlang+html" = type, [{:code, _, bins} | rest]) do
46+
"`#{IO.iodata_to_binary(bins)}`" <> to_markdown(type, rest)
47+
end
48+
49+
def to_markdown("application/erlang+html" = type, [{:em, _, bins} | rest]) do
50+
"_#{IO.iodata_to_binary(bins)}_" <> to_markdown(type, rest)
51+
end
52+
53+
def to_markdown("application/erlang+html", []) do
54+
""
55+
end
56+
end

lib/next_ls/runtime/sidecar.ex

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ defmodule NextLS.Runtime.Sidecar do
1616
{:ok, %{db: db}}
1717
end
1818

19+
def handle_info({:tracer, :dbg, term}, state) do
20+
dbg(term)
21+
22+
{:noreply, state}
23+
end
24+
1925
def handle_info({:tracer, payload}, state) do
2026
attributes = Attributes.get_module_attributes(payload.file, payload.module)
2127
payload = Map.put_new(payload, :symbols, attributes)

priv/monkey/_next_ls_private_compiler.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,9 @@ defmodule NextLSPrivate.Tracer do
8282
:ok
8383
end
8484

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

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

@@ -176,6 +177,7 @@ defmodule NextLSPrivate.Tracer do
176177

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

180182
defs = Module.definitions_in(env.module)
181183

test/next_ls/dependency_test.exs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ defmodule NextLS.DependencyTest do
160160
4,
161161
[
162162
%{
163-
"range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 11, "line" => 7}},
163+
"range" => %{"start" => %{"character" => 8, "line" => 7}, "end" => %{"character" => 10, "line" => 7}},
164164
"uri" => uri
165165
}
166166
]
@@ -190,11 +190,11 @@ defmodule NextLS.DependencyTest do
190190
4,
191191
[
192192
%{
193-
"range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 7, "line" => 3}},
193+
"range" => %{"start" => %{"character" => 4, "line" => 3}, "end" => %{"character" => 6, "line" => 3}},
194194
"uri" => uri
195195
},
196196
%{
197-
"range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 7, "line" => 7}},
197+
"range" => %{"start" => %{"character" => 4, "line" => 7}, "end" => %{"character" => 6, "line" => 7}},
198198
"uri" => uri
199199
}
200200
]
@@ -229,19 +229,11 @@ defmodule NextLS.DependencyTest do
229229
4,
230230
[
231231
%{
232-
"range" => %{"end" => %{"character" => 15, "line" => 1}, "start" => %{"character" => 6, "line" => 1}},
232+
"range" => %{"end" => %{"character" => 14, "line" => 1}, "start" => %{"character" => 6, "line" => 1}},
233233
"uri" => uri
234234
},
235235
%{
236-
"range" => %{"end" => %{"character" => 8, "line" => 1}, "start" => %{"character" => 0, "line" => 1}},
237-
"uri" => uri
238-
},
239-
%{
240-
"range" => %{"end" => %{"character" => 8, "line" => 1}, "start" => %{"character" => 0, "line" => 1}},
241-
"uri" => uri
242-
},
243-
%{
244-
"range" => %{"end" => %{"character" => 13, "line" => 8}, "start" => %{"character" => 4, "line" => 8}},
236+
"range" => %{"end" => %{"character" => 12, "line" => 8}, "start" => %{"character" => 4, "line" => 8}},
245237
"uri" => uri
246238
}
247239
]

0 commit comments

Comments
 (0)