Skip to content

Commit 53f66f4

Browse files
authored
Merge pull request #1196 from elixir-lsp/typed-protocols
Typed protocols
2 parents fc7a53c + 32a600d commit 53f66f4

File tree

700 files changed

+33267
-3780
lines changed

Some content is hidden

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

700 files changed

+33267
-3780
lines changed

.github/workflows/ci.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,24 +90,34 @@ jobs:
9090
include:
9191
- elixir: 1.14.x
9292
otp: 23.x
93+
tests_may_fail: false
9394
- elixir: 1.14.x
9495
otp: 24.x
96+
tests_may_fail: false
9597
- elixir: 1.14.x
9698
otp: 25.x
99+
tests_may_fail: false
97100
- elixir: 1.14.x
98101
otp: 26.x
102+
tests_may_fail: false
99103
- elixir: 1.15.x
100104
otp: 24.x
105+
tests_may_fail: false
101106
- elixir: 1.15.x
102107
otp: 25.x
108+
tests_may_fail: false
103109
- elixir: 1.15.x
104110
otp: 26.x
111+
tests_may_fail: false
105112
- elixir: 1.16.x
106113
otp: 24.x
114+
tests_may_fail: false
107115
- elixir: 1.16.x
108116
otp: 25.x
117+
tests_may_fail: false
109118
- elixir: 1.16.x
110119
otp: 26.x
120+
tests_may_fail: false
111121
- elixir: 1.17.x
112122
otp: 25.x
113123
tests_may_fail: false

apps/debug_adapter/lib/debug_adapter/breakpoint_condition.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
3030
)
3131
end
3232

33-
@spec unregister_condition(module, module, non_neg_integer) :: :ok
33+
@spec unregister_condition(module, module, [non_neg_integer]) :: :ok
3434
def unregister_condition(name \\ __MODULE__, module, line) do
3535
GenServer.cast(name, {:unregister_condition, {module, line}})
3636
end
@@ -41,7 +41,7 @@ defmodule ElixirLS.DebugAdapter.BreakpointCondition do
4141
end
4242

4343
@spec get_condition(module, non_neg_integer) ::
44-
{String.t(), String.t(), non_neg_integer, non_neg_integer}
44+
{Macro.Env.t(), String.t(), String.t(), String.t(), non_neg_integer}
4545
def get_condition(name \\ __MODULE__, number) do
4646
GenServer.call(name, {:get_condition, number})
4747
end

apps/debug_adapter/lib/debug_adapter/completions.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule ElixirLS.DebugAdapter.Completions do
1010
snippet: snippet
1111
})
1212
when type in [:function, :macro] do
13-
%{
13+
%GenDAP.Structures.CompletionItem{
1414
type: "function",
1515
detail: Atom.to_string(type),
1616
label: "#{name}/#{arity}",
@@ -29,7 +29,7 @@ defmodule ElixirLS.DebugAdapter.Completions do
2929
other -> other
3030
end
3131

32-
%{
32+
%GenDAP.Structures.CompletionItem{
3333
type: "module",
3434
detail: if(subtype != nil, do: Atom.to_string(subtype)),
3535
label: name,
@@ -41,7 +41,7 @@ defmodule ElixirLS.DebugAdapter.Completions do
4141
type: :variable,
4242
name: name
4343
}) do
44-
%{
44+
%GenDAP.Structures.CompletionItem{
4545
type: "variable",
4646
label: name
4747
}
@@ -58,7 +58,7 @@ defmodule ElixirLS.DebugAdapter.Completions do
5858
:map_key -> "map key"
5959
end
6060

61-
%{
61+
%GenDAP.Structures.CompletionItem{
6262
type: "field",
6363
detail: detail,
6464
label: name
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
defmodule ElixirLS.DebugAdapter.IdManager do
2+
@moduledoc """
3+
Global ID manager for Debug Adapter Protocol objects.
4+
5+
Uses ERTS :atomics module for thread-safe, shared mutable counters
6+
that can be accessed from any process without blocking.
7+
"""
8+
9+
@counter_ref :dap_id_counter
10+
11+
@doc """
12+
Initializes the global ID counter.
13+
Should be called once during server startup.
14+
"""
15+
def init do
16+
# Create a single atomic counter starting at 1
17+
counter = :atomics.new(1, [])
18+
:atomics.put(counter, 1, 1)
19+
:persistent_term.put(@counter_ref, counter)
20+
:ok
21+
end
22+
23+
@doc """
24+
Gets the next unique ID atomically.
25+
This is thread-safe and can be called from any process.
26+
"""
27+
def next_id do
28+
case :persistent_term.get(@counter_ref, nil) do
29+
nil ->
30+
raise "IdManager not initialized. Call IdManager.init/0 first."
31+
32+
counter ->
33+
:atomics.add_get(counter, 1, 1)
34+
end
35+
end
36+
37+
@doc """
38+
Cleans up the global ID counter.
39+
Should be called during server shutdown.
40+
"""
41+
def cleanup do
42+
:persistent_term.erase(@counter_ref)
43+
:ok
44+
end
45+
46+
@doc """
47+
Gets the current ID value without incrementing.
48+
Mainly for testing/debugging purposes.
49+
"""
50+
def current_id do
51+
case :persistent_term.get(@counter_ref, nil) do
52+
nil ->
53+
raise "IdManager not initialized. Call IdManager.init/0 first."
54+
55+
counter ->
56+
:atomics.get(counter, 1)
57+
end
58+
end
59+
end

apps/debug_adapter/lib/debug_adapter/output.ex

Lines changed: 72 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ defmodule ElixirLS.DebugAdapter.Output do
99
"""
1010
alias ElixirLS.Utils.WireProtocol
1111
use GenServer
12-
use ElixirLS.DebugAdapter.Protocol
12+
import ElixirLS.DebugAdapter.Protocol.Basic
1313

1414
## Client API
1515

@@ -38,31 +38,46 @@ defmodule ElixirLS.DebugAdapter.Output do
3838
)
3939
end
4040

41-
def send_event(server \\ __MODULE__, event, body) do
42-
GenServer.call(server, {:send_event, event, body}, :infinity)
41+
def send_event(server \\ __MODULE__, body) do
42+
GenServer.call(server, {:send_event, body}, :infinity)
4343
end
4444

4545
def debugger_console(server \\ __MODULE__, str) when is_binary(str) do
46-
send_event(server, "output", %{"category" => "console", "output" => maybe_append_newline(str)})
46+
send_event(server, %GenDAP.Events.OutputEvent{
47+
seq: nil,
48+
body: %{category: "console", output: maybe_append_newline(str)}
49+
})
4750
end
4851

4952
def debugger_important(server \\ __MODULE__, str) when is_binary(str) do
50-
send_event(server, "output", %{
51-
"category" => "important",
52-
"output" => maybe_append_newline(str)
53+
send_event(server, %GenDAP.Events.OutputEvent{
54+
seq: nil,
55+
body: %{
56+
category: "important",
57+
output: maybe_append_newline(str)
58+
}
5359
})
5460
end
5561

5662
def debuggee_out(server \\ __MODULE__, str) when is_binary(str) do
57-
send_event(server, "output", %{"category" => "stdout", "output" => maybe_append_newline(str)})
63+
send_event(server, %GenDAP.Events.OutputEvent{
64+
seq: nil,
65+
body: %{category: "stdout", output: maybe_append_newline(str)}
66+
})
5867
end
5968

6069
def debuggee_err(server \\ __MODULE__, str) when is_binary(str) do
61-
send_event(server, "output", %{"category" => "stderr", "output" => maybe_append_newline(str)})
70+
send_event(server, %GenDAP.Events.OutputEvent{
71+
seq: nil,
72+
body: %{category: "stderr", output: maybe_append_newline(str)}
73+
})
6274
end
6375

6476
def ex_unit_event(server \\ __MODULE__, data) when is_map(data) do
65-
send_event(server, "output", %{"category" => "ex_unit", "output" => "", "data" => data})
77+
send_event(server, %GenDAP.Events.OutputEvent{
78+
seq: nil,
79+
body: %{category: "ex_unit", output: "", data: data}
80+
})
6681
end
6782

6883
def telemetry(server \\ __MODULE__, event, properties, measurements)
@@ -82,13 +97,16 @@ defmodule ElixirLS.DebugAdapter.Output do
8297
"elixir_ls.mix_target" => Mix.target()
8398
}
8499

85-
send_event(server, "output", %{
86-
"category" => "telemetry",
87-
"output" => event,
88-
"data" => %{
89-
"name" => event,
90-
"properties" => Map.merge(common_properties, properties),
91-
"measurements" => measurements
100+
send_event(server, %GenDAP.Events.OutputEvent{
101+
seq: nil,
102+
body: %{
103+
category: "telemetry",
104+
output: event,
105+
data: %{
106+
"name" => event,
107+
"properties" => Map.merge(common_properties, properties),
108+
"measurements" => measurements
109+
}
92110
}
93111
})
94112
end
@@ -109,6 +127,15 @@ defmodule ElixirLS.DebugAdapter.Output do
109127
end
110128

111129
@impl GenServer
130+
def handle_call({:send_response, request_packet, body = %struct{}}, _from, seq) do
131+
{:ok, dumped_body} =
132+
SchematicV.dump(struct.schematic(), %{body | seq: seq, request_seq: request_packet["seq"]})
133+
134+
res = WireProtocol.send(dumped_body)
135+
136+
{:reply, res, seq + 1}
137+
end
138+
112139
def handle_call({:send_response, request_packet, body}, _from, seq) do
113140
res = WireProtocol.send(response(seq, request_packet["seq"], request_packet["command"], body))
114141
{:reply, res, seq + 1}
@@ -120,25 +147,39 @@ defmodule ElixirLS.DebugAdapter.Output do
120147
_from,
121148
seq
122149
) do
123-
res =
124-
WireProtocol.send(
125-
error_response(
126-
seq,
127-
request_packet["seq"],
128-
request_packet["command"],
129-
message,
130-
format,
131-
variables,
132-
send_telemetry,
133-
show_user
134-
)
150+
{:ok, dumped_error} =
151+
SchematicV.dump(
152+
GenDAP.Structures.ErrorResponse.schematic(),
153+
%GenDAP.Structures.ErrorResponse{
154+
seq: seq,
155+
request_seq: request_packet["seq"],
156+
command: request_packet["command"],
157+
type: "response",
158+
success: false,
159+
message: message,
160+
body: %{
161+
error: %GenDAP.Structures.Message{
162+
# TODO unique ids
163+
id: 1,
164+
format: format,
165+
variables: variables,
166+
send_telemetry: send_telemetry,
167+
show_user: show_user
168+
}
169+
}
170+
}
135171
)
136172

173+
res = WireProtocol.send(dumped_error)
174+
137175
{:reply, res, seq + 1}
138176
end
139177

140-
def handle_call({:send_event, event, body}, _from, seq) do
141-
res = WireProtocol.send(event(seq, event, body))
178+
def handle_call({:send_event, body = %struct{seq: _}}, _from, seq) do
179+
# IO.warn(inspect(%{body | seq: seq}))
180+
{:ok, dumped_event} = SchematicV.dump(struct.schematic(), %{body | seq: seq})
181+
# IO.warn(inspect(dumped_event))
182+
res = WireProtocol.send(dumped_event)
142183
{:reply, res, seq + 1}
143184
end
144185
end

apps/debug_adapter/lib/debug_adapter/protocol.basic.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ defmodule ElixirLS.DebugAdapter.Protocol.Basic do
2323
end
2424
end
2525

26+
defmacro response(seq, request_seq, command) do
27+
quote do
28+
%{
29+
"type" => "response",
30+
"command" => unquote(command),
31+
"seq" => unquote(seq),
32+
"request_seq" => unquote(request_seq),
33+
"success" => true
34+
}
35+
end
36+
end
37+
2638
defmacro response(seq, request_seq, command, body) do
2739
quote do
2840
%{
@@ -77,4 +89,14 @@ defmodule ElixirLS.DebugAdapter.Protocol.Basic do
7789
}
7890
end
7991
end
92+
93+
defmacro event(seq, event) do
94+
quote do
95+
%{
96+
"type" => "event",
97+
"event" => unquote(event),
98+
"seq" => unquote(seq)
99+
}
100+
end
101+
end
80102
end

0 commit comments

Comments
 (0)