Skip to content

Commit 503f6c2

Browse files
author
Étienne Lévesque
committed
Provide code lenses for running tests (WIP)
1 parent eb8c927 commit 503f6c2

File tree

2 files changed

+106
-23
lines changed

2 files changed

+106
-23
lines changed

apps/language_server/lib/language_server/providers/code_lens.ex

Lines changed: 93 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
1111
"""
1212

1313
alias ElixirLS.LanguageServer.{Server, SourceFile}
14+
alias ElixirSense.Core.Parser
15+
alias ElixirSense.Core.State
1416
alias Erl2ex.Convert.{Context, ErlForms}
1517
alias Erl2ex.Pipeline.{Parse, ModuleData, ExSpec}
1618
import ElixirLS.LanguageServer.Protocol
@@ -110,30 +112,104 @@ defmodule ElixirLS.LanguageServer.Providers.CodeLens do
110112
end
111113
end
112114

113-
def code_lens(server_instance_id, uri, text) do
115+
def spec_code_lens(server_instance_id, uri, text) do
114116
resp =
115117
for {_, line, {mod, fun, arity}, contract, is_macro} <- Server.suggest_contracts(uri),
116118
SourceFile.function_def_on_line?(text, line, fun),
117119
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-
]
120+
build_code_lens(
121+
line,
122+
"@spec #{spec}",
123+
"spec:#{server_instance_id}",
124+
%{
125+
"uri" => uri,
126+
"mod" => to_string(mod),
127+
"fun" => to_string(fun),
128+
"arity" => arity,
129+
"spec" => spec,
130+
"line" => line
133131
}
134-
}
132+
)
135133
end
136134

137135
{:ok, resp}
138136
end
137+
138+
def test_code_lens(uri, src) do
139+
file_path = SourceFile.path_from_uri(uri)
140+
141+
if imports?(src, ExUnit.Case) do
142+
test_calls = calls_to(src, :test)
143+
describe_calls = calls_to(src, :describe)
144+
145+
calls_lenses =
146+
for {line, _col} <- test_calls ++ describe_calls do
147+
test_filter = "#{file_path}:#{line}"
148+
149+
build_code_lens(line, "Run test", "elixir.test.run", test_filter)
150+
end
151+
152+
file_lens = build_code_lens(1, "Run test", "elixir.test.run", file_path)
153+
154+
{:ok, [file_lens | calls_lenses]}
155+
end
156+
end
157+
158+
@spec imports?(String.t(), [atom()] | atom()) :: boolean()
159+
defp imports?(buffer, modules) do
160+
buffer_file_metadata =
161+
buffer
162+
|> Parser.parse_string(true, true, 1)
163+
164+
imports_set =
165+
buffer_file_metadata.lines_to_env
166+
|> get_imports()
167+
|> MapSet.new()
168+
169+
modules
170+
|> List.wrap()
171+
|> MapSet.new()
172+
|> MapSet.subset?(imports_set)
173+
end
174+
175+
defp get_imports(lines_to_env) do
176+
%State.Env{imports: imports} =
177+
lines_to_env
178+
|> Enum.max_by(fn {k, _v} -> k end)
179+
|> elem(1)
180+
181+
imports
182+
end
183+
184+
@spec calls_to(String.t(), atom() | {atom(), integer()}) :: [{pos_integer(), pos_integer()}]
185+
defp calls_to(buffer, function) do
186+
buffer_file_metadata =
187+
buffer
188+
|> Parser.parse_string(true, true, 1)
189+
190+
buffer_file_metadata.calls
191+
|> Enum.map(fn {_k, v} -> v end)
192+
|> List.flatten()
193+
|> Enum.filter(&is_call_to(&1, function))
194+
|> Enum.map(fn call -> call.position end)
195+
end
196+
197+
defp is_call_to(%State.CallInfo{} = call_info, {function, arity}) do
198+
call_info.func == function and call_info.arity == arity
199+
end
200+
201+
defp is_call_to(%State.CallInfo{} = call_info, function) when is_atom(function) do
202+
call_info.func == function
203+
end
204+
205+
def build_code_lens(line, title, command, argument) do
206+
%{
207+
"range" => range(line - 1, 0, line - 1, 0),
208+
"command" => %{
209+
"title" => title,
210+
"command" => command,
211+
"arguments" => [argument]
212+
}
213+
}
214+
end
139215
end

apps/language_server/lib/language_server/server.ex

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -530,13 +530,20 @@ defmodule ElixirLS.LanguageServer.Server do
530530
end
531531

532532
defp handle_request(code_lens_req(_id, uri), state) do
533-
if dialyzer_enabled?(state) and state.settings["suggestSpecs"] != false do
534-
{:async,
535-
fn -> CodeLens.code_lens(state.server_instance_id, uri, state.source_files[uri].text) end,
536-
state}
537-
else
538-
{:ok, nil, state}
533+
fun = fn ->
534+
{:ok, spec_code_lens} =
535+
if dialyzer_enabled?(state) and state.settings["suggestSpecs"] != false do
536+
CodeLens.spec_code_lens(state.server_instance_id, uri, state.source_files[uri].text)
537+
else
538+
[]
539+
end
540+
541+
{:ok, test_code_lens} = CodeLens.test_code_lens(uri, state.source_files[uri].text)
542+
543+
{:ok, spec_code_lens ++ test_code_lens}
539544
end
545+
546+
{:async, fun, state}
540547
end
541548

542549
defp handle_request(execute_command_req(_id, command, args), state) do

0 commit comments

Comments
 (0)