Skip to content

Commit 5d0ed74

Browse files
committed
build elixir_sense metadata in parser
1 parent 3730a44 commit 5d0ed74

File tree

8 files changed

+142
-65
lines changed

8 files changed

+142
-65
lines changed

apps/language_server/lib/language_server/parser.ex

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ defmodule ElixirLS.LanguageServer.Parser do
88
alias ElixirLS.LanguageServer.Build
99
alias ElixirLS.LanguageServer.Server
1010
alias ElixirLS.LanguageServer.SourceFile
11+
alias ElixirSense.Core.MetadataBuilder
1112
require Logger
1213

1314
@debounce_timeout 300
1415

15-
defstruct [
16-
:source_file,
17-
:path,
18-
:ast,
19-
:diagnostics
20-
]
16+
defmodule Context do
17+
defstruct [
18+
:source_file,
19+
:path,
20+
:ast,
21+
:diagnostics,
22+
:metadata,
23+
:parsed_version
24+
]
25+
end
2126

2227
def start_link(args) do
2328
GenServer.start_link(__MODULE__, args, name: __MODULE__)
@@ -31,6 +36,10 @@ defmodule ElixirLS.LanguageServer.Parser do
3136
GenServer.cast(__MODULE__, {:parse_with_debounce, uri, source_file})
3237
end
3338

39+
def parse_immediate(uri, source_file) do
40+
GenServer.call(__MODULE__, {:parse_immediate, uri, source_file})
41+
end
42+
3443
@impl true
3544
def init(_args) do
3645
# TODO get source files on start?
@@ -58,33 +67,78 @@ defmodule ElixirLS.LanguageServer.Parser do
5867
Process.send_after(self(), {:parse_file, uri}, @debounce_timeout)
5968
end)
6069

61-
update_in(state.files[uri], fn _old_file ->
62-
%__MODULE__{
63-
source_file: source_file,
64-
path: get_path(uri),
65-
ast: nil,
66-
diagnostics: []
67-
}
70+
update_in(state.files[uri], fn
71+
nil ->
72+
%Context{
73+
source_file: source_file,
74+
path: get_path(uri)
75+
}
76+
old_file ->
77+
%Context{old_file |
78+
source_file: source_file
79+
}
6880
end)
6981
else
82+
Logger.debug("Not parsing #{uri} with debounce")
7083
state
7184
end
7285

7386
{:noreply, state}
7487
end
7588

89+
@impl true
90+
def handle_call({:parse_immediate, uri, source_file}, _from, %{files: files, debounce_refs: debounce_refs} = state) do
91+
{reply, state} = if String.ends_with?(uri, [".ex", ".exs", ".eex"]) do
92+
{maybe_ref, updated_debounce_refs} = Map.pop(debounce_refs, uri)
93+
if maybe_ref do
94+
Process.cancel_timer(maybe_ref, info: false)
95+
end
96+
97+
current_version = source_file.version
98+
99+
case files[uri] do
100+
%Context{parsed_version: ^current_version} = file ->
101+
Logger.debug("#{uri} already parsed")
102+
# current version already parsed
103+
{file, state}
104+
_other ->
105+
Logger.debug("Parsing #{uri} immediately")
106+
# overwrite everything
107+
file = %Context{
108+
source_file: source_file,
109+
path: get_path(uri)
110+
}
111+
|> do_parse()
112+
113+
updated_files = Map.put(files, uri, file)
114+
115+
notify_diagnostics_updated(updated_files)
116+
117+
state = %{state | files: updated_files, debounce_refs: updated_debounce_refs}
118+
{file, state}
119+
end
120+
else
121+
Logger.debug("Not parsing #{uri} immediately")
122+
# not parsing - respond with empty struct
123+
reply = %Context{
124+
source_file: source_file,
125+
path: get_path(uri)
126+
}
127+
{reply, state}
128+
end
129+
130+
{:reply, reply, state}
131+
end
132+
76133
@impl GenServer
77134
def handle_info(
78135
{:parse_file, uri},
79136
%{files: files, debounce_refs: debounce_refs} = state
80137
) do
81-
Logger.debug("Parsing #{uri}")
82-
%__MODULE__{source_file: source_file, path: path} = file = Map.fetch!(files, uri)
83-
{ast, diagnostics} = parse_file(source_file.text, path)
84-
updated_file = %__MODULE__{file |
85-
ast: ast,
86-
diagnostics: diagnostics
87-
}
138+
Logger.debug("Parsing #{uri} after debounce")
139+
140+
updated_file = Map.fetch!(files, uri)
141+
|> do_parse()
88142

89143
updated_files = Map.put(files, uri, updated_file)
90144

@@ -95,10 +149,27 @@ defmodule ElixirLS.LanguageServer.Parser do
95149
{:noreply, state}
96150
end
97151

152+
defp do_parse(%Context{source_file: source_file, path: path} = file) do
153+
{ast, diagnostics} = parse_file(source_file.text, path)
154+
155+
metadata = if ast do
156+
acc = MetadataBuilder.build(ast)
157+
ElixirSense.Core.Metadata.fill(source_file.text, acc)
158+
end
159+
160+
%Context{file |
161+
ast: ast,
162+
diagnostics: diagnostics,
163+
metadata: metadata,
164+
parsed_version: source_file.version
165+
}
166+
end
167+
98168
defp get_path(uri) do
99169
case uri do
100170
"file:" <> _ -> SourceFile.Path.from_uri(uri)
101171
_ ->
172+
# TODO think if this is sane
102173
extension = uri
103174
|> String.split(".")
104175
|> List.last
@@ -108,7 +179,7 @@ defmodule ElixirLS.LanguageServer.Parser do
108179

109180
defp notify_diagnostics_updated(updated_files) do
110181
updated_files
111-
|> Enum.map(fn {_uri, %__MODULE__{diagnostics: diagnostics}} -> diagnostics end)
182+
|> Enum.map(fn {_uri, %Context{diagnostics: diagnostics}} -> diagnostics end)
112183
|> List.flatten
113184
|> Server.parser_finished()
114185
end

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

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
88
text before the cursor so we can filter out suggestions that are not relevant.
99
"""
1010
alias ElixirLS.LanguageServer.Protocol.TextEdit
11-
alias ElixirLS.LanguageServer.SourceFile
11+
alias ElixirLS.LanguageServer.{SourceFile, Parser}
1212
import ElixirLS.LanguageServer.Protocol, only: [range: 4]
1313
alias ElixirSense.Providers.Suggestion.Matcher
1414
alias ElixirSense.Core.Normalized.Code, as: NormalizedCode
@@ -93,7 +93,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
9393
[".", "@", "&", "%", "^", ":", "!", "-", "~"]
9494
end
9595

96-
def completion(text, line, character, options) do
96+
def completion(%Parser.Context{source_file: %SourceFile{text: text}, metadata: metadata}, line, character, options) do
9797
lines = SourceFile.lines(text)
9898
line_text = Enum.at(lines, line)
9999

@@ -106,8 +106,7 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
106106

107107
prefix = get_prefix(text_before_cursor)
108108

109-
# Can we use ElixirSense.Providers.Suggestion? ElixirSense.suggestions/3
110-
metadata = ElixirSense.Core.Parser.parse_string(text, true, true, {line, character})
109+
metadata = metadata || ElixirSense.Core.Parser.parse_string(text, true, true, {line, character})
111110

112111
env = ElixirSense.Core.Metadata.get_env(metadata, {line, character})
113112

@@ -173,8 +172,9 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
173172
SourceFile.elixir_position_to_lsp(text, position_to_insert_alias)
174173
)
175174

176-
items =
177-
build_suggestions(text, line, character, options)
175+
required_alias = Keyword.get(options, :auto_insert_required_alias, true)
176+
177+
items = ElixirSense.suggestions(text, line, character, required_alias: required_alias, metadata: metadata)
178178
|> maybe_reject_derived_functions(context, options)
179179
|> Enum.map(&from_completion_item(&1, context, options))
180180
|> maybe_add_do(context)
@@ -215,11 +215,6 @@ defmodule ElixirLS.LanguageServer.Providers.Completion do
215215
{:ok, %{"isIncomplete" => is_incomplete(items_json), "items" => items_json}}
216216
end
217217

218-
defp build_suggestions(text, line, character, options) do
219-
required_alias = Keyword.get(options, :auto_insert_required_alias, true)
220-
ElixirSense.suggestions(text, line, character, required_alias: required_alias)
221-
end
222-
223218
defp maybe_add_do(completion_items, context) do
224219
hint =
225220
case Regex.scan(~r/(?<=\s|^)[a-z]+$/u, context.text_before_cursor) do

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
defmodule ElixirLS.LanguageServer.Providers.Definition do
22
@moduledoc """
3-
Go-to-definition provider utilizing Elixir Sense
3+
textDocument/definition provider utilizing Elixir Sense
44
"""
55

6-
alias ElixirLS.LanguageServer.{Protocol, SourceFile}
6+
alias ElixirLS.LanguageServer.{Protocol, SourceFile, Parser}
77

8-
def definition(uri, text, line, character, project_dir) do
9-
{line, character} = SourceFile.lsp_position_to_elixir(text, {line, character})
8+
def definition(uri, %Parser.Context{source_file: source_file, metadata: metadata}, line, character, project_dir) do
9+
{line, character} = SourceFile.lsp_position_to_elixir(source_file.text, {line, character})
1010

1111
result =
12-
case ElixirSense.definition(text, line, character) do
12+
case ElixirSense.definition(source_file.text, line, character, if(metadata, do: [metadata: metadata], else: [])) do
1313
nil ->
1414
nil
1515

1616
%ElixirSense.Location{} = location ->
17-
Protocol.Location.new(location, uri, text, project_dir)
17+
Protocol.Location.new(location, uri, source_file.text, project_dir)
1818
end
1919

2020
{:ok, result}

apps/language_server/lib/language_server/providers/hover.ex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
11
defmodule ElixirLS.LanguageServer.Providers.Hover do
2-
alias ElixirLS.LanguageServer.{SourceFile, DocLinks}
2+
alias ElixirLS.LanguageServer.{SourceFile, DocLinks, Parser}
33
import ElixirLS.LanguageServer.Protocol
44
alias ElixirLS.LanguageServer.MarkdownUtils
55

66
@moduledoc """
7-
Hover provider utilizing Elixir Sense
7+
textDocument/hover provider utilizing Elixir Sense
88
"""
99

10-
def hover(text, line, character, _project_dir) do
11-
{line, character} = SourceFile.lsp_position_to_elixir(text, {line, character})
10+
def hover(%Parser.Context{source_file: source_file, metadata: metadata}, line, character) do
11+
{line, character} = SourceFile.lsp_position_to_elixir(source_file.text, {line, character})
1212

1313
response =
14-
case ElixirSense.docs(text, line, character) do
14+
case ElixirSense.docs(source_file.text, line, character, if(metadata, do: [metadata: metadata], else: [])) do
1515
nil ->
1616
nil
1717

1818
%{docs: docs, range: es_range} ->
19-
lines = SourceFile.lines(text)
19+
lines = SourceFile.lines(source_file.text)
2020

2121
try do
2222
%{

apps/language_server/lib/language_server/providers/implementation.ex

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
defmodule ElixirLS.LanguageServer.Providers.Implementation do
22
@moduledoc """
3-
Go-to-implementation provider utilizing Elixir Sense
3+
textDocument/implementation provider utilizing Elixir Sense
44
"""
55

6-
alias ElixirLS.LanguageServer.{Protocol, SourceFile}
6+
alias ElixirLS.LanguageServer.{Protocol, SourceFile, Parser}
77

8-
def implementation(uri, text, line, character, project_dir) do
9-
{line, character} = SourceFile.lsp_position_to_elixir(text, {line, character})
10-
locations = ElixirSense.implementations(text, line, character)
8+
def implementation(uri, %Parser.Context{source_file: source_file, metadata: metadata}, line, character, project_dir) do
9+
{line, character} = SourceFile.lsp_position_to_elixir(source_file.text, {line, character})
10+
locations = ElixirSense.implementations(source_file.text, line, character, if(metadata, do: [metadata: metadata], else: []))
1111

1212
results =
13-
for location <- locations, do: Protocol.Location.new(location, uri, text, project_dir)
13+
for location <- locations, do: Protocol.Location.new(location, uri, source_file.text, project_dir)
1414

1515
{:ok, results}
1616
end

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
defmodule ElixirLS.LanguageServer.Providers.References do
22
@moduledoc """
3-
This module provides References support by using `ElixirSense.references/3` to
3+
This module provides textDocument/references support by using `ElixirSense.references/3` to
44
find all references to any function or module identified at the provided
55
location.
66
@@ -10,20 +10,20 @@ defmodule ElixirLS.LanguageServer.Providers.References do
1010
https://microsoft.github.io//language-server-protocol/specifications/specification-3-14/#textDocument_references
1111
"""
1212

13-
alias ElixirLS.LanguageServer.{SourceFile, Build}
13+
alias ElixirLS.LanguageServer.{SourceFile, Build, Parser}
1414
import ElixirLS.LanguageServer.Protocol
1515
require Logger
1616

17-
def references(text, uri, line, character, _include_declaration, project_dir) do
18-
{line, character} = SourceFile.lsp_position_to_elixir(text, {line, character})
17+
def references(%Parser.Context{source_file: source_file, metadata: metadata}, uri, line, character, _include_declaration, project_dir) do
18+
{line, character} = SourceFile.lsp_position_to_elixir(source_file.text, {line, character})
1919

2020
Build.with_build_lock(fn ->
2121
trace = ElixirLS.LanguageServer.Tracer.get_trace()
2222

23-
ElixirSense.references(text, line, character, trace)
23+
ElixirSense.references(source_file.text, line, character, trace, if(metadata, do: [metadata: metadata], else: []))
2424
|> Enum.map(fn elixir_sense_reference ->
2525
elixir_sense_reference
26-
|> build_reference(uri, text, project_dir)
26+
|> build_reference(uri, source_file.text, project_dir)
2727
end)
2828
|> Enum.filter(&(not is_nil(&1)))
2929
# ElixirSense returns references from both compile tracer and current buffer

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
defmodule ElixirLS.LanguageServer.Providers.SignatureHelp do
2-
alias ElixirLS.LanguageServer.SourceFile
2+
@moduledoc """
3+
Provider handling textDocument/signatureHelp
4+
"""
5+
alias ElixirLS.LanguageServer.{SourceFile, Parser}
36

47
def trigger_characters(), do: ["(", ","]
58

6-
def signature(%SourceFile{} = source_file, line, character) do
9+
def signature(%Parser.Context{source_file: %SourceFile{} = source_file, metadata: metadata}, line, character) do
710
{line, character} = SourceFile.lsp_position_to_elixir(source_file.text, {line, character})
811

912
response =
10-
case ElixirSense.signature(source_file.text, line, character) do
13+
case ElixirSense.signature(source_file.text, line, character, if(metadata, do: [metadata: metadata], else: [])) do
1114
%{active_param: active_param, signatures: signatures} ->
1215
%{
1316
"activeSignature" => 0,

0 commit comments

Comments
 (0)