|
1 | 1 | defmodule ElixirLS.LanguageServer.Providers.CodeLens do
|
2 | 2 | @moduledoc """
|
3 |
| - Collects the success typings inferred by Dialyzer, translates the syntax to Elixir, and shows them |
4 |
| - inline in the editor as @spec suggestions. |
| 3 | + Provides different code lenses to the client. |
5 | 4 |
|
6 |
| - The server, unfortunately, has no way to force the client to refresh the @spec code lenses when new |
7 |
| - success typings, so we let this request block until we know we have up-to-date results from |
8 |
| - Dialyzer. We rely on the client being able to await this result while still making other requests |
9 |
| - in parallel. If the client is unable to perform requests in parallel, the client or user should |
10 |
| - disable this feature. |
| 5 | + Supports the following code lenses: |
| 6 | + * Suggestions for Dialyzer @spec definitions |
| 7 | + * Shortcuts for executing tests |
11 | 8 | """
|
12 | 9 |
|
13 |
| - alias ElixirLS.LanguageServer.{Server, SourceFile} |
14 |
| - alias Erl2ex.Convert.{Context, ErlForms} |
15 |
| - alias Erl2ex.Pipeline.{Parse, ModuleData, ExSpec} |
| 10 | + alias ElixirLS.LanguageServer.Providers.CodeLens |
16 | 11 | import ElixirLS.LanguageServer.Protocol
|
17 | 12 |
|
18 |
| - defmodule ContractTranslator do |
19 |
| - def translate_contract(fun, contract, is_macro) do |
20 |
| - # FIXME: Private module |
21 |
| - {[%ExSpec{specs: [spec]} | _], _} = |
22 |
| - "-spec foo#{contract}." |
23 |
| - # FIXME: Private module |
24 |
| - |> Parse.string() |
25 |
| - |> hd() |
26 |
| - |> elem(0) |
27 |
| - # FIXME: Private module |
28 |
| - |> ErlForms.conv_form(%Context{ |
29 |
| - in_type_expr: true, |
30 |
| - # FIXME: Private module |
31 |
| - module_data: %ModuleData{} |
32 |
| - }) |
| 13 | + def spec_code_lens(server_instance_id, uri, text), |
| 14 | + do: CodeLens.TypeSpec.code_lens(server_instance_id, uri, text) |
33 | 15 |
|
34 |
| - spec |
35 |
| - |> Macro.postwalk(&tweak_specs/1) |
36 |
| - |> drop_macro_env(is_macro) |
37 |
| - |> Macro.to_string() |
38 |
| - |> String.replace("()", "") |
39 |
| - |> Code.format_string!(line_length: :infinity) |
40 |
| - |> IO.iodata_to_binary() |
41 |
| - |> String.replace_prefix("foo", to_string(fun)) |
42 |
| - end |
| 16 | + def test_code_lens(uri, text), do: CodeLens.Test.code_lens(uri, text) |
43 | 17 |
|
44 |
| - defp tweak_specs({:list, _meta, args}) do |
45 |
| - case args do |
46 |
| - [{:{}, _, [{:atom, _, []}, {wild, _, _}]}] when wild in [:_, :any] -> quote do: keyword() |
47 |
| - list -> list |
48 |
| - end |
49 |
| - end |
50 |
| - |
51 |
| - defp tweak_specs({:nonempty_list, _meta, args}) do |
52 |
| - case args do |
53 |
| - [{:any, _, []}] -> quote do: [...] |
54 |
| - _ -> args ++ quote do: [...] |
55 |
| - end |
56 |
| - end |
57 |
| - |
58 |
| - defp tweak_specs({:%{}, _meta, fields}) do |
59 |
| - fields = |
60 |
| - Enum.map(fields, fn |
61 |
| - {:map_field_exact, _, [key, value]} -> {key, value} |
62 |
| - {key, value} -> quote do: {optional(unquote(key)), unquote(value)} |
63 |
| - field -> field |
64 |
| - end) |
65 |
| - |> Enum.reject(&match?({{:optional, _, [{:any, _, []}]}, {:any, _, []}}, &1)) |
66 |
| - |
67 |
| - fields |
68 |
| - |> Enum.find_value(fn |
69 |
| - {:__struct__, struct_type} when is_atom(struct_type) -> struct_type |
70 |
| - _ -> nil |
71 |
| - end) |
72 |
| - |> case do |
73 |
| - nil -> {:%{}, [], fields} |
74 |
| - struct_type -> {{:., [], [struct_type, :t]}, [], []} |
75 |
| - end |
76 |
| - end |
77 |
| - |
78 |
| - # Undo conversion of _ to any() when inside binary spec |
79 |
| - defp tweak_specs({:<<>>, _, children}) do |
80 |
| - children = |
81 |
| - Macro.postwalk(children, fn |
82 |
| - {:any, _, []} -> quote do: _ |
83 |
| - other -> other |
84 |
| - end) |
85 |
| - |
86 |
| - {:<<>>, [], children} |
87 |
| - end |
88 |
| - |
89 |
| - defp tweak_specs({:_, _, _}) do |
90 |
| - quote do: any() |
91 |
| - end |
92 |
| - |
93 |
| - defp tweak_specs({:when, [], [spec, substitutions]}) do |
94 |
| - substitutions = Enum.reject(substitutions, &match?({:_, {:any, _, []}}, &1)) |
95 |
| - |
96 |
| - case substitutions do |
97 |
| - [] -> spec |
98 |
| - _ -> {:when, [], [spec, substitutions]} |
99 |
| - end |
100 |
| - end |
101 |
| - |
102 |
| - defp tweak_specs(node) do |
103 |
| - node |
104 |
| - end |
105 |
| - |
106 |
| - defp drop_macro_env(ast, false), do: ast |
107 |
| - |
108 |
| - defp drop_macro_env({:"::", [], [{:foo, [], [_env | rest]}, res]}, true) do |
109 |
| - {:"::", [], [{:foo, [], rest}, res]} |
110 |
| - end |
111 |
| - end |
112 |
| - |
113 |
| - def code_lens(server_instance_id, uri, text) do |
114 |
| - resp = |
115 |
| - for {_, line, {mod, fun, arity}, contract, is_macro} <- Server.suggest_contracts(uri), |
116 |
| - SourceFile.function_def_on_line?(text, line, fun), |
117 |
| - spec = ContractTranslator.translate_contract(fun, contract, is_macro) do |
118 |
| - %{ |
119 |
| - "range" => range(line - 1, 0, line - 1, 0), |
120 |
| - "command" => %{ |
121 |
| - "title" => "@spec #{spec}", |
122 |
| - "command" => "spec:#{server_instance_id}", |
123 |
| - "arguments" => [ |
124 |
| - %{ |
125 |
| - "uri" => uri, |
126 |
| - "mod" => to_string(mod), |
127 |
| - "fun" => to_string(fun), |
128 |
| - "arity" => arity, |
129 |
| - "spec" => spec, |
130 |
| - "line" => line |
131 |
| - } |
132 |
| - ] |
133 |
| - } |
134 |
| - } |
135 |
| - end |
136 |
| - |
137 |
| - {:ok, resp} |
| 18 | + def build_code_lens(line, title, command, argument) do |
| 19 | + %{ |
| 20 | + "range" => range(line - 1, 0, line - 1, 0), |
| 21 | + "command" => %{ |
| 22 | + "title" => title, |
| 23 | + "command" => command, |
| 24 | + "arguments" => [argument] |
| 25 | + } |
| 26 | + } |
138 | 27 | end
|
139 | 28 | end
|
0 commit comments