Skip to content

Commit eadcac8

Browse files
authored
feat: opaque assigns structure (#66)
This hides the implementation of assigns behind a function `assigns/1`. This also implements a backend for this new opaque structure that is backed by another process, in this case an Agent (not tied on agent, but was the closest thing to what currently exists.
1 parent 374b05b commit eadcac8

7 files changed

Lines changed: 70 additions & 23 deletions

File tree

lib/gen_lsp.ex

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,10 @@ defmodule GenLSP do
170170
type: {:or, [:pid, :atom]},
171171
doc: "The `t:pid/0` or name of the `GenLSP.Buffer` process."
172172
],
173+
assigns: [
174+
type: {:or, [:pid, :atom]},
175+
doc: "The `t:pid/0` or name of the `GenLSP.Assigns` process."
176+
],
173177
name: [
174178
type: :atom,
175179
doc:
@@ -188,15 +192,16 @@ defmodule GenLSP do
188192
opts = NimbleOptions.validate!(opts, @options_schema)
189193

190194
:proc_lib.start_link(__MODULE__, :init, [
191-
{module, init_args, Keyword.take(opts, [:name, :buffer]), self()}
195+
{module, init_args, Keyword.take(opts, [:name, :buffer, :assigns]), self()}
192196
])
193197
end
194198

195199
@doc false
196200
def init({module, init_args, opts, parent}) do
197201
me = self()
198202
buffer = opts[:buffer]
199-
lsp = %LSP{mod: module, pid: me, buffer: buffer}
203+
assigns = opts[:assigns]
204+
lsp = %LSP{mod: module, pid: me, buffer: buffer, assigns: assigns}
200205

201206
case module.init(lsp, init_args) do
202207
{:ok, %LSP{} = lsp} ->

lib/gen_lsp/assigns.ex

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
defmodule GenLSP.Assigns do
2+
use Agent
3+
4+
def start_link(opts \\ []) do
5+
Agent.start_link(fn -> Map.new() end, Keyword.take(opts, [:name]))
6+
end
7+
8+
def get(agent) do
9+
Agent.get(agent, & &1)
10+
end
11+
12+
def merge(agent, new_assigns) do
13+
Agent.update(agent, &Map.merge(&1, Map.new(new_assigns)))
14+
end
15+
16+
def update(agent, callback) do
17+
Agent.update(agent, fn assigns ->
18+
new_assigns = callback.(assigns)
19+
Map.merge(assigns, Map.new(new_assigns))
20+
end)
21+
end
22+
end

lib/gen_lsp/lsp.ex

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,26 @@ defmodule GenLSP.LSP do
66
"""
77
typedstruct do
88
field :mod, atom(), enforce: true
9-
field :assigns, map(), default: Map.new()
109
field :buffer, atom() | pid()
10+
field :assigns, atom() | pid()
1111
field :pid, pid()
1212
end
1313

14-
@spec assign(t(), Keyword.t()) :: t()
14+
@spec assign(t(), Keyword.t() | (map() -> keyword())) :: t()
1515
def assign(%__MODULE__{assigns: assigns} = lsp, new_assigns) when is_list(new_assigns) do
16-
%{lsp | assigns: Map.merge(assigns, Map.new(new_assigns))}
16+
GenLSP.Assigns.merge(assigns, new_assigns)
17+
18+
lsp
19+
end
20+
21+
def assign(%__MODULE__{assigns: assigns} = lsp, callback) when is_function(callback, 1) do
22+
GenLSP.Assigns.update(assigns, callback)
23+
24+
lsp
25+
end
26+
27+
@spec assigns(t()) :: map()
28+
def assigns(%__MODULE__{assigns: assigns}) do
29+
GenLSP.Assigns.get(assigns)
1730
end
1831
end

lib/gen_lsp/test.ex

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,32 @@ defmodule GenLSP.Test do
3838
@spec server(mod :: atom(), opts :: Keyword.t()) :: server()
3939
def server(mod, opts \\ []) do
4040
buffer_id = Keyword.get(opts, :buffer_id, :buffer)
41+
assigns_id = Keyword.get(opts, :assigns_id, :assigns)
4142
lsp_id = Keyword.get(opts, :lsp_id, :lsp)
4243

4344
buffer =
4445
start_supervised!({GenLSP.Buffer, communication: {GenLSP.Communication.TCP, [port: 0]}},
4546
id: buffer_id
4647
)
4748

49+
assigns =
50+
start_supervised!(GenLSP.Assigns, id: assigns_id)
51+
4852
{:ok, port} = :inet.port(GenLSP.Buffer.comm_state(buffer).lsocket)
4953

50-
lsp = start_supervised!({mod, Keyword.merge([buffer: buffer], opts)}, id: lsp_id)
54+
lsp =
55+
start_supervised!({mod, Keyword.merge([buffer: buffer, assigns: assigns], opts)},
56+
id: lsp_id
57+
)
5158

52-
%{lsp: lsp, buffer: buffer, port: port, buffer_id: buffer_id, lsp_id: lsp_id}
59+
%{
60+
lsp: lsp,
61+
buffer: buffer,
62+
assigns: assigns,
63+
port: port,
64+
buffer_id: buffer_id,
65+
lsp_id: lsp_id
66+
}
5367
end
5468

5569
@doc """

test/gen_lsp/communication/tcp_test.exs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ defmodule GenLSP.Communication.TCPTest do
2626
@string ~s|{"a":"‘","b":"#{String.duplicate("hello world! ", 5000)}"}|
2727
@length byte_size(@string)
2828

29-
@port 9000
29+
@port 5000
3030

3131
@connect_opts [:binary, packet: :raw, active: false]
3232

@@ -35,11 +35,10 @@ defmodule GenLSP.Communication.TCPTest do
3535
# the following match ensures that the script completes and does
3636
# not raise after stdin is closed.
3737
Task.start_link(fn ->
38-
{:ok, args} = GenLSP.Communication.TCP.init(port: @port)
39-
{:ok, args} = GenLSP.Communication.TCP.listen(args)
40-
send(me, {:done, args})
38+
{:ok, tcp} = GenLSP.Communication.TCP.init(port: @port)
39+
{:ok, tcp} = GenLSP.Communication.TCP.listen(tcp)
4140

42-
assert :eof = GenLSP.Support.Buffer.loop(args, me, "")
41+
assert :eof = GenLSP.Support.Buffer.loop(tcp, me, "")
4342
end)
4443

4544
assert {:ok, socket} = connect()

test/gen_lsp_test.exs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ defmodule GenLSPTest do
1616
test "stores the user state and internal state", %{server: server} do
1717
assert alive?(server)
1818

19-
assert %GenLSP.LSP{
20-
assigns: %{foo: :bar, test_pid: self()},
21-
buffer: server.buffer,
22-
pid: server.lsp,
23-
mod: GenLSPTest.ExampleServer
24-
} ==
25-
:sys.get_state(server.lsp)
19+
assert %{foo: :bar, test_pid: self()} == :sys.get_state(server.assigns)
2620
end
2721

2822
test "can receive and reply to a request", %{client: client} do

test/support/example_server.ex

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ defmodule GenLSPTest.ExampleServer do
7878
}
7979
})
8080

81-
send(lsp.assigns.test_pid, result)
81+
send(assigns(lsp).test_pid, result)
8282

8383
GenLSP.log(lsp, "done initializing")
8484

@@ -87,7 +87,7 @@ defmodule GenLSPTest.ExampleServer do
8787

8888
@impl true
8989
def handle_notification(%Notifications.TextDocumentDidOpen{} = notification, lsp) do
90-
send(lsp.assigns.test_pid, {:callback, notification})
90+
send(assigns(lsp).test_pid, {:callback, notification})
9191

9292
{:noreply, lsp}
9393
end
@@ -98,7 +98,7 @@ defmodule GenLSPTest.ExampleServer do
9898
} = notification,
9999
lsp
100100
) do
101-
send(lsp.assigns.test_pid, {:callback, notification})
101+
send(assigns(lsp).test_pid, {:callback, notification})
102102

103103
GenLSP.notify(lsp, %Notifications.TextDocumentPublishDiagnostics{
104104
params: %Structures.PublishDiagnosticsParams{
@@ -126,7 +126,7 @@ defmodule GenLSPTest.ExampleServer do
126126
end
127127

128128
def handle_info(_message, lsp) do
129-
send(lsp.assigns.test_pid, {:info, :ack})
129+
send(assigns(lsp).test_pid, {:info, :ack})
130130
{:noreply, lsp}
131131
end
132132
end

0 commit comments

Comments
 (0)