Skip to content

Commit f462587

Browse files
authored
Merge pull request #793 from scohen/scohen/generated-lsp-structures
Scohen/generated lsp structures
2 parents 29b91a6 + d0d8e52 commit f462587

File tree

18 files changed

+17797
-6
lines changed

18 files changed

+17797
-6
lines changed

apps/language_server/lib/language_server/experimental/protocol/proto/field.ex

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,31 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Field do
8888
end
8989
end
9090

91+
def extract({:tuple, tuple_types}, field_name, field_value) when is_list(field_value) do
92+
result =
93+
field_value
94+
|> Enum.zip(tuple_types)
95+
|> Enum.reduce_while([], fn {value, type}, acc ->
96+
case extract(type, field_name, value) do
97+
{:ok, value} -> {:cont, [value | acc]}
98+
error -> {:halt, error}
99+
end
100+
end)
101+
102+
case result do
103+
value when is_list(value) ->
104+
value_as_tuple =
105+
value
106+
|> Enum.reverse()
107+
|> List.to_tuple()
108+
109+
{:ok, value_as_tuple}
110+
111+
error ->
112+
error
113+
end
114+
end
115+
91116
def extract({:params, param_defs}, _field_name, field_value)
92117
when is_map(field_value) do
93118
result =
@@ -127,12 +152,18 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Field do
127152
end
128153

129154
def encode({:one_of, types}, field_value) do
130-
Enum.reduce_while(types, nil, fn type, _ ->
131-
case encode(type, field_value) do
132-
{:ok, _} = success -> {:halt, success}
133-
error -> {:cont, error}
134-
end
135-
end)
155+
encoded =
156+
Enum.reduce_while(types, nil, fn type, _ ->
157+
case encode(type, field_value) do
158+
{:ok, _} = success -> {:halt, success}
159+
error -> {:cont, error}
160+
end
161+
end)
162+
163+
case encoded do
164+
encoded_list when is_list(encoded_list) -> {:ok, encoded_list}
165+
error -> error
166+
end
136167
end
137168

138169
def encode({:list, list_type}, field_value) when is_list(field_value) do
@@ -168,6 +199,10 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Field do
168199
{:ok, float_value}
169200
end
170201

202+
def encode(:float, field_value) when is_float(field_value) do
203+
field_value
204+
end
205+
171206
def encode(:string, field_value) when is_binary(field_value) do
172207
{:ok, field_value}
173208
end
@@ -191,6 +226,24 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Field do
191226
end
192227
end
193228

229+
def encode({:tuple, types}, field_value) when is_tuple(field_value) do
230+
encoded =
231+
field_value
232+
|> Tuple.to_list()
233+
|> Enum.zip(types)
234+
|> Enum.reduce_while([], fn {value, type}, acc ->
235+
case encode(type, value) do
236+
{:ok, encoded} -> {:cont, [encoded | acc]}
237+
error -> {:halt, error}
238+
end
239+
end)
240+
241+
case encoded do
242+
encoded_list when is_list(encoded_list) -> {:ok, Enum.reverse(encoded_list)}
243+
error -> error
244+
end
245+
end
246+
194247
def encode({:params, param_defs}, field_value) when is_map(field_value) do
195248
param_fields =
196249
Enum.reduce_while(param_defs, [], fn {param_name, param_type}, acc ->

apps/language_server/lib/language_server/experimental/protocol/proto/type_functions.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.TypeFunctions do
3131
{:list, type}
3232
end
3333

34+
def tuple_of(types) when is_list(types) do
35+
{:tuple, types}
36+
end
37+
3438
def map_of(type, opts \\ []) do
3539
field_name = Keyword.get(opts, :as)
3640
{:map, type, field_name}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
defmodule Mix.Tasks.Lsp.DataModel do
2+
alias Mix.Tasks.Lsp.DataModel.Enumeration
3+
alias Mix.Tasks.Lsp.DataModel.Structure
4+
alias Mix.Tasks.Lsp.DataModel.TypeAlias
5+
6+
defstruct names_to_types: %{},
7+
notifications: %{},
8+
requests: %{},
9+
structures: %{},
10+
type_aliases: %{},
11+
enumerations: %{}
12+
13+
def new do
14+
with {:ok, root_meta} <- load_meta_model() do
15+
names_to_types =
16+
root_meta
17+
|> Map.take(~w(structures enumerations typeAliases))
18+
|> Enum.flat_map(fn {type, list_of_things} ->
19+
Enum.map(list_of_things, fn %{"name" => name} -> {name, type_name(type)} end)
20+
end)
21+
|> Map.new()
22+
23+
type_aliases = load_from_meta(root_meta, "typeAliases", &TypeAlias.new/1)
24+
enumerations = load_from_meta(root_meta, "enumerations", &Enumeration.new/1)
25+
structures = load_from_meta(root_meta, "structures", &Structure.new/1)
26+
27+
data_model = %__MODULE__{
28+
names_to_types: names_to_types,
29+
enumerations: enumerations,
30+
type_aliases: type_aliases,
31+
structures: structures
32+
}
33+
34+
{:ok, data_model}
35+
end
36+
end
37+
38+
def all_types(%__MODULE__{} = data_model) do
39+
aliases = Map.values(data_model.type_aliases)
40+
structures = Map.values(data_model.structures)
41+
enumerations = Map.values(data_model.enumerations)
42+
43+
aliases ++ enumerations ++ structures
44+
end
45+
46+
def fetch(%__MODULE__{} = data_model, name) do
47+
field =
48+
case kind(data_model, name) do
49+
{:ok, :structure} -> :structures
50+
{:ok, :type_alias} -> :type_aliases
51+
{:ok, :enumeration} -> :enumerations
52+
:error -> :error
53+
end
54+
55+
data_model
56+
|> Map.get(field, %{})
57+
|> Map.fetch(name)
58+
|> case do
59+
{:ok, %element_module{} = element} ->
60+
{:ok, element_module.resolve(element, data_model)}
61+
62+
:error ->
63+
:error
64+
end
65+
end
66+
67+
def fetch!(%__MODULE__{} = data_model, name) do
68+
case fetch(data_model, name) do
69+
{:ok, thing} -> thing
70+
:error -> raise "Could not find type #{name}"
71+
end
72+
end
73+
74+
def references(%__MODULE__{} = data_model, %{name: name}) do
75+
references(data_model, name)
76+
end
77+
78+
def references(%__MODULE__{} = data_model, roots) do
79+
collect_references(data_model, List.wrap(roots), MapSet.new())
80+
end
81+
82+
defp collect_references(%__MODULE__{}, [], %MapSet{} = references) do
83+
MapSet.to_list(references)
84+
end
85+
86+
defp collect_references(%__MODULE__{} = data_model, [first | rest], %MapSet{} = references) do
87+
with false <- MapSet.member?(references, first),
88+
{:ok, %referred_type{} = referred} <- fetch(data_model, first) do
89+
new_refs = referred_type.references(referred)
90+
collect_references(data_model, rest ++ new_refs, MapSet.put(references, first))
91+
else
92+
_ ->
93+
collect_references(data_model, rest, references)
94+
end
95+
end
96+
97+
defp load_from_meta(root_meta, name, new_fn) do
98+
root_meta
99+
|> Map.get(name)
100+
|> Map.new(fn definition ->
101+
loaded = new_fn.(definition)
102+
{loaded.name, loaded}
103+
end)
104+
end
105+
106+
defp kind(%__MODULE__{} = data_model, name) do
107+
Map.fetch(data_model.names_to_types, name)
108+
end
109+
110+
defp type_name("structures"), do: :structure
111+
defp type_name("enumerations"), do: :enumeration
112+
defp type_name("typeAliases"), do: :type_alias
113+
114+
@meta_model_file_name "metamodel.3.17.json"
115+
defp load_meta_model do
116+
file_name =
117+
__ENV__.file
118+
|> Path.dirname()
119+
|> Path.join([@meta_model_file_name])
120+
121+
with {:ok, file_contents} <- File.read(file_name) do
122+
JasonVendored.decode(file_contents)
123+
end
124+
end
125+
end
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
defmodule Mix.Tasks.Lsp.DataModel.Enumeration do
2+
defmodule Value do
3+
defstruct [:name, :value, :documentation]
4+
5+
def new(%{"name" => name, "value" => value} = value_meta) do
6+
docs = value_meta["documentation"]
7+
%__MODULE__{name: name, value: value, documentation: docs}
8+
end
9+
end
10+
11+
alias Mix.Tasks.Lsp.Mappings
12+
alias Mix.Tasks.Lsp.DataModel
13+
alias Mix.Tasks.Lsp.DataModel.Type
14+
defstruct [:name, :values, :type]
15+
16+
def new(%{"name" => name, "type" => type, "values" => values}) do
17+
%__MODULE__{
18+
name: name,
19+
type: Type.new(name, type),
20+
values: Enum.map(values, &Value.new/1)
21+
}
22+
end
23+
24+
def to_protocol(%__MODULE__{} = enumeration, _, _) do
25+
module_name = Module.concat([enumeration.name])
26+
quote(do: unquote(module_name))
27+
end
28+
29+
def resolve(%__MODULE__{} = enumeration, %DataModel{} = data_model) do
30+
%__MODULE__{enumeration | type: Type.resolve(enumeration.type, data_model)}
31+
end
32+
33+
def build_definition(
34+
%__MODULE__{} = enumeration,
35+
%Mappings{} = mappings,
36+
%DataModel{}
37+
) do
38+
proto_module = Mappings.proto_module(mappings)
39+
40+
with {:ok, destination_module} <-
41+
Mappings.fetch_destination_module(mappings, enumeration.name) do
42+
values =
43+
Enum.map(enumeration.values, fn value ->
44+
name = value.name |> Macro.underscore() |> String.to_atom()
45+
quote(do: {unquote(name), unquote(value.value)})
46+
end)
47+
48+
ast =
49+
quote do
50+
defmodule unquote(destination_module) do
51+
alias unquote(proto_module)
52+
use Proto
53+
54+
defenum unquote(values)
55+
end
56+
end
57+
58+
{:ok, ast}
59+
end
60+
end
61+
62+
def references(%__MODULE__{}) do
63+
[]
64+
end
65+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
defmodule Mix.Tasks.Lsp.DataModel.Notification do
2+
defstruct [:method, :direction, :params, :documentation]
3+
end
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
defmodule Mix.Tasks.Lsp.DataModel.Property do
2+
alias Mix.Tasks.Lsp.DataModel
3+
alias Mix.Tasks.Lsp.Mappings
4+
alias Mix.Tasks.Lsp.DataModel.Type
5+
6+
defstruct [:name, :type, :required?, :references, :documentation]
7+
8+
def new(%{"name" => name, "type" => type} = property_meta) do
9+
required? = !Map.get(property_meta, "optional", false)
10+
11+
keys = Keyword.merge([name: name, required?: required?], type: Type.new(name, type))
12+
struct(__MODULE__, keys)
13+
end
14+
15+
def resolve(%__MODULE__{} = property, %DataModel{} = data_model) do
16+
%__MODULE__{property | type: Type.resolve(property.type, data_model)}
17+
end
18+
19+
def to_protocol(%__MODULE__{} = property, %DataModel{} = data_model, %Mappings{} = mappings) do
20+
underscored = property.name |> Macro.underscore() |> String.to_atom()
21+
type_call = Type.to_protocol(property.type, data_model, mappings)
22+
23+
if property.required? do
24+
quote(do: {unquote(underscored), unquote(type_call)})
25+
else
26+
quote(do: {unquote(underscored), optional(unquote(type_call))})
27+
end
28+
end
29+
30+
def references(%__MODULE__{} = property) do
31+
%type_module{} = property.type
32+
33+
property.type
34+
|> type_module.references()
35+
|> Enum.reject(fn name -> String.starts_with?(name, "LSP") end)
36+
end
37+
end
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
defmodule Mix.Tasks.Lsp.DataModel.Request do
2+
defstruct [:method, :result, :direction, :params]
3+
end

0 commit comments

Comments
 (0)