Skip to content

Commit dfd1af3

Browse files
committed
Initial implementation of new protocol / store / server state
1 parent d64f237 commit dfd1af3

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+3077
-45
lines changed

apps/language_server/.formatter.exs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,19 @@ impossible_to_format = [
33
"test/fixtures/project_with_tests/test/error_test.exs"
44
]
55

6+
proto_dsl = [
7+
defenum: 1,
8+
defnotification: 2,
9+
defrequest: 2,
10+
defresponse: 1,
11+
deftype: 1
12+
]
13+
614
[
15+
export: [
16+
locals_without_parens: proto_dsl
17+
],
18+
locals_without_parens: proto_dsl,
719
inputs:
820
Enum.flat_map(
921
[

apps/language_server/lib/language_server.ex

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,31 @@ defmodule ElixirLS.LanguageServer do
44
"""
55
use Application
66

7+
alias ElixirLS.LanguageServer
8+
alias ElixirLS.LanguageServer.Experimental
9+
710
@impl Application
811
def start(_type, _args) do
912
children = [
13+
Experimental.SourceFile.Store,
1014
{ElixirLS.LanguageServer.Server, ElixirLS.LanguageServer.Server},
11-
{ElixirLS.LanguageServer.JsonRpc, name: ElixirLS.LanguageServer.JsonRpc},
15+
Experimental.Server,
16+
{ElixirLS.LanguageServer.PacketRouter, [LanguageServer.Server, Experimental.Server]},
17+
{ElixirLS.LanguageServer.JsonRpc,
18+
name: ElixirLS.LanguageServer.JsonRpc, language_server: LanguageServer.PacketRouter},
1219
{ElixirLS.LanguageServer.Providers.WorkspaceSymbols, []},
1320
{ElixirLS.LanguageServer.Tracer, []},
1421
{ElixirLS.LanguageServer.ExUnitTestTracer, []}
1522
]
1623

17-
opts = [strategy: :one_for_one, name: ElixirLS.LanguageServer.Supervisor, max_restarts: 0]
24+
opts = [strategy: :one_for_one, name: LanguageServer.Supervisor, max_restarts: 0]
1825
Supervisor.start_link(children, opts)
1926
end
2027

2128
@impl Application
2229
def stop(_state) do
2330
if ElixirLS.Utils.WireProtocol.io_intercepted?() do
24-
ElixirLS.LanguageServer.JsonRpc.show_message(
31+
LanguageServer.JsonRpc.show_message(
2532
:error,
2633
"ElixirLS has crashed. See Output panel."
2734
)

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

Whitespace-only changes.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Protocol.Notifications do
2+
alias ElixirLS.LanguageServer.Experimental.Protocol.Proto
3+
alias ElixirLS.LanguageServer.Experimental.Protocol.Types
4+
5+
defmodule Cancel do
6+
use Proto
7+
8+
defnotification "$/cancelRequest", id: integer()
9+
end
10+
11+
defmodule DidOpen do
12+
use Proto
13+
14+
defnotification "textDocument/didOpen", text_document: Types.TextDocument
15+
end
16+
17+
defmodule DidClose do
18+
use Proto
19+
20+
defnotification "textDocument/didClose", text_document: Types.TextDocument.Identifier
21+
end
22+
23+
defmodule DidChange do
24+
use Proto
25+
26+
defnotification "textDocument/didChange",
27+
text_document: Types.TextDocument.VersionedIdentifier,
28+
content_changes: list_of(Types.TextDocument.ContentChangeEvent)
29+
end
30+
31+
defmodule DidChangeConfiguration do
32+
use Proto
33+
34+
defnotification "workspace/didChangeConfiguration", settings: map_of(any())
35+
end
36+
37+
defmodule DidChangeWatchedFiles do
38+
use Proto
39+
40+
defnotification "workspace/didChangeWatchedFiles", changes: list_of(Types.FileEvent)
41+
end
42+
43+
defmodule DidSave do
44+
use Proto
45+
46+
defnotification "textDocument/didSave", text_document: Types.TextDocument.Identifier
47+
end
48+
49+
use Proto, decoders: :notifications
50+
end
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto do
2+
alias ElixirLS.LanguageServer.Experimental.Protocol.Proto.Decoders
3+
4+
defmacro __using__([]) do
5+
quote location: :keep do
6+
alias ElixirLS.LanguageServer.Experimental.Protocol.Proto
7+
alias ElixirLS.LanguageServer.Experimental.Protocol.Proto.LspTypes
8+
import ElixirLS.LanguageServer.Experimental.Protocol.Proto.TypeFunctions
9+
10+
import Proto.Enum, only: [defenum: 1]
11+
import Proto.Notification, only: [defnotification: 2]
12+
import Proto.Request, only: [defrequest: 2]
13+
import Proto.Response, only: [defresponse: 1]
14+
import Proto.Type, only: [deftype: 1]
15+
end
16+
end
17+
18+
defmacro __using__(opts) when is_list(opts) do
19+
function_name =
20+
case Keyword.get(opts, :decoders) do
21+
:notifications ->
22+
:for_notifications
23+
24+
:requests ->
25+
:for_requests
26+
27+
_ ->
28+
invalid_decoder!(__CALLER__)
29+
end
30+
31+
quote do
32+
@before_compile {Decoders, unquote(function_name)}
33+
end
34+
end
35+
36+
defmacro __using__(_) do
37+
invalid_decoder!(__CALLER__)
38+
end
39+
40+
defp invalid_decoder!(caller) do
41+
raise CompileError.exception(
42+
description: "Invalid decoder type. Must be either :notifications or :requests",
43+
file: caller.file,
44+
line: caller.line
45+
)
46+
end
47+
end
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.CompileMetadata do
2+
@moduledoc """
3+
Compile-time storage of protocol metadata
4+
"""
5+
6+
@notification_modules_key {__MODULE__, :notification_modules}
7+
@type_modules_key {__MODULE__, :type_modules}
8+
@request_modules_key {__MODULE__, :request_modules}
9+
@response_modules_key {__MODULE__, :response_modules}
10+
11+
def notification_modules do
12+
:persistent_term.get(@notification_modules_key, [])
13+
end
14+
15+
def request_modules do
16+
:persistent_term.get(@request_modules_key, [])
17+
end
18+
19+
def response_modules do
20+
:persistent_term.get(@response_modules_key, [])
21+
end
22+
23+
def type_modules do
24+
:persistent_term.get(@type_modules_key)
25+
end
26+
27+
def add_notification_module(module) do
28+
add_module(@notification_modules_key, module)
29+
end
30+
31+
def add_request_module(module) do
32+
add_module(@request_modules_key, module)
33+
end
34+
35+
def add_response_module(module) do
36+
add_module(@response_modules_key, module)
37+
end
38+
39+
def add_type_module(module) do
40+
add_module(@type_modules_key, module)
41+
end
42+
43+
defp update(key, initial_value, update_fn) do
44+
case :persistent_term.get(key, :not_found) do
45+
:not_found -> :persistent_term.put(key, initial_value)
46+
found -> :persistent_term.put(key, update_fn.(found))
47+
end
48+
end
49+
50+
defp add_module(key, module) do
51+
update(key, [module], fn old_list -> [module | old_list] end)
52+
end
53+
end
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Decoders do
2+
alias ElixirLS.LanguageServer.Experimental.Protocol.Proto.CompileMetadata
3+
4+
defmacro for_notifications(_) do
5+
notification_modules = CompileMetadata.notification_modules()
6+
notification_matchers = Enum.map(notification_modules, &build_notification_matcher_macro/1)
7+
notification_decoders = Enum.map(notification_modules, &build_notifications_decoder/1)
8+
9+
quote do
10+
defmacro notification(method) do
11+
quote do
12+
%{"method" => unquote(method), "jsonrpc" => "2.0"}
13+
end
14+
end
15+
16+
defmacro notification(method, params) do
17+
quote do
18+
%{"method" => unquote(method), "params" => unquote(params), "jsonrpc" => "2.0"}
19+
end
20+
end
21+
22+
unquote(build_typespec(:notification, notification_modules))
23+
24+
unquote_splicing(notification_matchers)
25+
26+
@spec decode(String.t(), map()) :: {:ok, notification} | {:error, any}
27+
unquote_splicing(notification_decoders)
28+
29+
def decode(method, _) do
30+
{:error, {:unknown_notification, method}}
31+
end
32+
33+
def __meta__(:events) do
34+
unquote(notification_modules)
35+
end
36+
37+
def __meta__(:notifications) do
38+
unquote(notification_modules)
39+
end
40+
end
41+
end
42+
43+
defmacro for_requests(_) do
44+
request_modules = CompileMetadata.request_modules()
45+
request_matchers = Enum.map(request_modules, &build_request_matcher_macro/1)
46+
request_decoders = Enum.map(request_modules, &build_request_decoder/1)
47+
48+
quote do
49+
def __meta__(:requests) do
50+
unquote(request_modules)
51+
end
52+
53+
defmacro request(id, method) do
54+
quote do
55+
%{"method" => unquote(method), "id" => unquote(id), "jsonrpc" => "2.0"}
56+
end
57+
end
58+
59+
defmacro request(id, method, params) do
60+
quote do
61+
%{"method" => unquote(method), "id" => unquote(id), "params" => unquote(params)}
62+
end
63+
end
64+
65+
unquote(build_typespec(:request, request_modules))
66+
67+
unquote_splicing(request_matchers)
68+
69+
@spec decode(String.t(), map()) :: {:ok, request} | {:error, any}
70+
unquote_splicing(request_decoders)
71+
72+
def decode(method, _) do
73+
{:error, {:unknown_request, method}}
74+
end
75+
end
76+
end
77+
78+
defp build_notification_matcher_macro(notification_module) do
79+
macro_name = module_to_macro_name(notification_module)
80+
method_name = notification_module.__meta__(:method_name)
81+
82+
quote do
83+
defmacro unquote(macro_name)() do
84+
method_name = unquote(method_name)
85+
86+
quote do
87+
%{"method" => unquote(method_name), "jsonrpc" => "2.0"}
88+
end
89+
end
90+
end
91+
end
92+
93+
defp build_notifications_decoder(notification_module) do
94+
method_name = notification_module.__meta__(:method_name)
95+
96+
quote do
97+
def decode(unquote(method_name), request) do
98+
unquote(notification_module).parse(request)
99+
end
100+
end
101+
end
102+
103+
defp build_request_matcher_macro(notification_module) do
104+
macro_name = module_to_macro_name(notification_module)
105+
method_name = notification_module.__meta__(:method_name)
106+
107+
quote do
108+
defmacro unquote(macro_name)(id) do
109+
method_name = unquote(method_name)
110+
111+
quote do
112+
%{"method" => unquote(method_name), "id" => unquote(id), "jsonrpc" => "2.0"}
113+
end
114+
end
115+
end
116+
end
117+
118+
defp build_request_decoder(request_module) do
119+
method_name = request_module.__meta__(:method_name)
120+
121+
quote do
122+
def decode(unquote(method_name), request) do
123+
unquote(request_module).parse(request)
124+
end
125+
end
126+
end
127+
128+
def build_typespec(type_name, modules) do
129+
spec_name = {type_name, [], nil}
130+
131+
spec =
132+
Enum.reduce(modules, nil, fn
133+
module, nil ->
134+
quote do
135+
unquote(module).t()
136+
end
137+
138+
module, spec ->
139+
quote do
140+
unquote(module).t() | unquote(spec)
141+
end
142+
end)
143+
144+
quote do
145+
@type unquote(spec_name) :: unquote(spec)
146+
end
147+
end
148+
149+
defp module_to_macro_name(module) do
150+
module
151+
|> Module.split()
152+
|> List.last()
153+
|> Macro.underscore()
154+
|> String.to_atom()
155+
end
156+
end

0 commit comments

Comments
 (0)