Skip to content

Commit 434f6bc

Browse files
authored
Implementations provider (#415)
* move to provider * no need to feature check as formatter and references are available since elixir 1.7 and we require 1.8 * elixir_sense suggestions for erlang modules now properly include : no need to patch * elixir_sense definitions now return nil when not found extract location related code to a new module * add implementations provider * update elixir_sense * do not format test/tmp fixtures * move fixtures to common dir * do not build filesystem URIs by string concat as it will break on Windows * add tests * fix invalid uris * Revert "do not format test/tmp fixtures" This reverts commit 5012101. * Revert "fix invalid uris" This reverts commit 38eeb67. * run formatter * increase timeout * bump elixir_sense * don't catch everyting * bump elixir_sense fix tests on elixir < 1.11
1 parent 99a6447 commit 434f6bc

21 files changed

+229
-107
lines changed

apps/language_server/lib/language_server/protocol.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@ defmodule ElixirLS.LanguageServer.Protocol do
101101
end
102102
end
103103

104+
defmacro implementation_req(id, uri, line, character) do
105+
quote do
106+
request(unquote(id), "textDocument/implementation", %{
107+
"textDocument" => %{"uri" => unquote(uri)},
108+
"position" => %{"line" => unquote(line), "character" => unquote(character)}
109+
})
110+
end
111+
end
112+
104113
defmacro completion_req(id, uri, line, character) do
105114
quote do
106115
request(unquote(id), "textDocument/completion", %{

apps/language_server/lib/language_server/protocol/location.ex

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,23 @@ defmodule ElixirLS.LanguageServer.Protocol.Location do
66
"""
77
@derive JasonVendored.Encoder
88
defstruct [:uri, :range]
9+
10+
alias ElixirLS.LanguageServer.SourceFile
11+
alias ElixirLS.LanguageServer.Protocol
12+
13+
def new(%ElixirSense.Location{file: file, line: line, column: column}, uri) do
14+
uri =
15+
case file do
16+
nil -> uri
17+
_ -> SourceFile.path_to_uri(file)
18+
end
19+
20+
%Protocol.Location{
21+
uri: uri,
22+
range: %{
23+
"start" => %{"line" => line - 1, "character" => column - 1},
24+
"end" => %{"line" => line - 1, "character" => column - 1}
25+
}
26+
}
27+
end
928
end

apps/language_server/lib/language_server/providers/completion.ex

Lines changed: 17 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -236,37 +236,26 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
236236

237237
defp from_completion_item(
238238
%{type: :module, name: name, summary: summary, subtype: subtype, metadata: metadata},
239-
%{
240-
def_before: nil,
241-
prefix: prefix
242-
},
239+
%{def_before: nil},
243240
_options
244241
) do
245-
capitalized? = String.first(name) == String.upcase(String.first(name))
246-
247-
if String.ends_with?(prefix, ":") and capitalized? do
248-
nil
249-
else
250-
label = if capitalized?, do: name, else: ":" <> name
251-
252-
detail =
253-
if subtype do
254-
Atom.to_string(subtype)
255-
else
256-
"module"
257-
end
242+
detail =
243+
if subtype do
244+
Atom.to_string(subtype)
245+
else
246+
"module"
247+
end
258248

259-
%__MODULE__{
260-
label: label,
261-
kind: :module,
262-
detail: detail,
263-
documentation: summary,
264-
insert_text: name,
265-
filter_text: name,
266-
priority: 14,
267-
tags: metadata_to_tags(metadata)
268-
}
269-
end
249+
%__MODULE__{
250+
label: name,
251+
kind: :module,
252+
detail: detail,
253+
documentation: summary,
254+
insert_text: name,
255+
filter_text: name,
256+
priority: 14,
257+
tags: metadata_to_tags(metadata)
258+
}
270259
end
271260

272261
defp from_completion_item(

apps/language_server/lib/language_server/providers/definition.ex

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,18 @@ defmodule ElixirLS.LanguageServer.Providers.Definition do
33
Go-to-definition provider utilizing Elixir Sense
44
"""
55

6-
alias ElixirLS.LanguageServer.SourceFile
76
alias ElixirLS.LanguageServer.Protocol
87

98
def definition(uri, text, line, character) do
10-
case ElixirSense.definition(text, line + 1, character + 1) do
11-
%ElixirSense.Location{found: false} ->
12-
{:ok, []}
9+
result =
10+
case ElixirSense.definition(text, line + 1, character + 1) do
11+
nil ->
12+
nil
1313

14-
%ElixirSense.Location{file: file, line: line, column: column} ->
15-
line = line || 0
16-
column = column || 0
14+
%ElixirSense.Location{} = location ->
15+
Protocol.Location.new(location, uri)
16+
end
1717

18-
uri =
19-
case file do
20-
nil -> uri
21-
_ -> SourceFile.path_to_uri(file)
22-
end
23-
24-
{:ok,
25-
%Protocol.Location{
26-
uri: uri,
27-
range: %{
28-
"start" => %{"line" => line - 1, "character" => column - 1},
29-
"end" => %{"line" => line - 1, "character" => column - 1}
30-
}
31-
}}
32-
end
18+
{:ok, result}
3319
end
3420
end

apps/language_server/lib/language_server/providers/formatting.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,6 @@ defmodule ElixirLS.LanguageServer.Providers.Formatting do
22
import ElixirLS.LanguageServer.Protocol, only: [range: 4]
33
alias ElixirLS.LanguageServer.SourceFile
44

5-
def supported? do
6-
function_exported?(Code, :format_string!, 2)
7-
end
8-
95
def format(%SourceFile{} = source_file, uri, project_dir) do
106
if can_format?(uri, project_dir) do
117
case SourceFile.formatter_opts(uri) do
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule ElixirLS.LanguageServer.Providers.Implementation do
2+
@moduledoc """
3+
Go-to-implementation provider utilizing Elixir Sense
4+
"""
5+
6+
alias ElixirLS.LanguageServer.Protocol
7+
8+
def implementation(uri, text, line, character) do
9+
locations = ElixirSense.implementations(text, line + 1, character + 1)
10+
results = for location <- locations, do: Protocol.Location.new(location, uri)
11+
12+
{:ok, results}
13+
end
14+
end

apps/language_server/lib/language_server/providers/references.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ defmodule ElixirLS.LanguageServer.Providers.References do
2424
end)
2525
end
2626

27-
def supported? do
28-
Mix.Tasks.Xref.__info__(:functions) |> Enum.member?({:calls, 0})
29-
end
30-
3127
defp build_reference(ref, current_file_uri) do
3228
%{
3329
range: %{

apps/language_server/lib/language_server/providers/signature_help.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
defmodule ElixirLS.LanguageServer.Providers.SignatureHelp do
22
alias ElixirLS.LanguageServer.SourceFile
33

4+
def trigger_characters(), do: ["("]
5+
46
def signature(%SourceFile{} = source_file, line, character) do
57
response =
68
case ElixirSense.signature(source_file.text, line + 1, character + 1) do

apps/language_server/lib/language_server/server.ex

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ defmodule ElixirLS.LanguageServer.Server do
2222
Completion,
2323
Hover,
2424
Definition,
25+
Implementation,
2526
References,
2627
Formatting,
2728
SignatureHelp,
@@ -475,10 +476,6 @@ defmodule ElixirLS.LanguageServer.Server do
475476
e in InvalidParamError ->
476477
JsonRpc.respond_with_error(id, :invalid_params, e.message)
477478
state
478-
479-
other ->
480-
JsonRpc.respond_with_error(id, :internal_error, other.message)
481-
state
482479
end
483480

484481
defp handle_request_packet(id, _packet, state) do
@@ -546,6 +543,14 @@ defmodule ElixirLS.LanguageServer.Server do
546543
{:async, fun, state}
547544
end
548545

546+
defp handle_request(implementation_req(_id, uri, line, character), state) do
547+
fun = fn ->
548+
Implementation.implementation(uri, state.source_files[uri].text, line, character)
549+
end
550+
551+
{:async, fun, state}
552+
end
553+
549554
defp handle_request(references_req(_id, uri, line, character, include_declaration), state) do
550555
source_file = get_source_file(state, uri)
551556

@@ -731,9 +736,6 @@ defmodule ElixirLS.LanguageServer.Server do
731736
rescue
732737
e in InvalidParamError ->
733738
{:error, :invalid_params, e.message}
734-
735-
other ->
736-
{:error, :internal_error, other.message}
737739
end
738740

739741
GenServer.call(parent, {:request_finished, id, result}, :infinity)
@@ -751,9 +753,10 @@ defmodule ElixirLS.LanguageServer.Server do
751753
"hoverProvider" => true,
752754
"completionProvider" => %{"triggerCharacters" => Completion.trigger_characters()},
753755
"definitionProvider" => true,
754-
"referencesProvider" => References.supported?(),
755-
"documentFormattingProvider" => Formatting.supported?(),
756-
"signatureHelpProvider" => %{"triggerCharacters" => ["("]},
756+
"implementationProvider" => true,
757+
"referencesProvider" => true,
758+
"documentFormattingProvider" => true,
759+
"signatureHelpProvider" => %{"triggerCharacters" => SignatureHelp.trigger_characters()},
757760
"documentSymbolProvider" => true,
758761
"workspaceSymbolProvider" => true,
759762
"documentOnTypeFormattingProvider" => %{"firstTriggerCharacter" => "\n"},

apps/language_server/test/providers/definition_test.exs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ defmodule ElixirLS.LanguageServer.Providers.DefinitionTest do
44
alias ElixirLS.LanguageServer.Providers.Definition
55
alias ElixirLS.LanguageServer.Protocol.Location
66
alias ElixirLS.LanguageServer.SourceFile
7+
alias ElixirLS.LanguageServer.Test.FixtureHelpers
78
require ElixirLS.Test.TextLoc
89

910
test "find definition" do
10-
file_path = Path.join(__DIR__, "../support/references_a.ex") |> Path.expand()
11+
file_path = FixtureHelpers.get_path("references_a.ex")
1112
text = File.read!(file_path)
1213
uri = SourceFile.path_to_uri(file_path)
1314

14-
b_file_path = Path.join(__DIR__, "../support/references_b.ex") |> Path.expand()
15+
b_file_path = FixtureHelpers.get_path("references_b.ex")
1516
b_uri = SourceFile.path_to_uri(b_file_path)
1617

1718
{line, char} = {2, 30}

0 commit comments

Comments
 (0)