Skip to content

Commit 6b5dd44

Browse files
authored
Merge pull request #764 from scohen/scohen/experimental-protocol
Scohen/experimental protocol
2 parents d64f237 + c4f0707 commit 6b5dd44

Some content is hidden

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

46 files changed

+3417
-94
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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
defmodule ElixirLS.LanguageServer.Experimental.Protocol.Proto.Convert do
2+
alias ElixirLS.LanguageServer.Experimental.SourceFile.Conversions
3+
alias ElixirLS.LanguageServer.Experimental.Protocol.Types
4+
alias ElixirLS.LanguageServer.Experimental.SourceFile
5+
6+
def to_elixir(%{text_document: _} = request) do
7+
with {:ok, source_file} <- fetch_source_file(request.lsp),
8+
{:ok, updates} <- convert(request.lsp, source_file) do
9+
updated_request =
10+
request
11+
|> Map.put(:source_file, source_file)
12+
|> Map.merge(updates)
13+
14+
{:ok, updated_request}
15+
end
16+
end
17+
18+
def to_elixir(request) do
19+
request = Map.merge(request, Map.from_struct(request.lsp))
20+
21+
{:ok, request}
22+
end
23+
24+
defp fetch_source_file(%{text_document: %Types.TextDocument{} = text_document}) do
25+
with {:ok, source_file} <- SourceFile.Store.fetch(text_document.uri) do
26+
{:ok, source_file}
27+
end
28+
end
29+
30+
defp fetch_source_file(%{source_file: %SourceFile{} = source_file}) do
31+
{:ok, source_file}
32+
end
33+
34+
defp fetch_source_file(_), do: :error
35+
36+
defp convert(%{range: range}, source_file) do
37+
with {:ok, ex_range} <- Conversions.to_elixir(range, source_file) do
38+
{:ok, %{range: ex_range}}
39+
end
40+
end
41+
42+
defp convert(%{position: position}, source_file) do
43+
with {:ok, ex_pos} <- Conversions.to_elixir(position, source_file) do
44+
{:ok, %{position: ex_pos}}
45+
end
46+
end
47+
48+
defp convert(_, _) do
49+
{:ok, %{}}
50+
end
51+
end

0 commit comments

Comments
 (0)