Skip to content

Commit f10be92

Browse files
committed
Add support for server addons
1 parent 77dc960 commit f10be92

File tree

4 files changed

+89
-4
lines changed

4 files changed

+89
-4
lines changed

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,31 @@ def initialize
2828
# the real client is initialized, features that depend on it will not be blocked by using the NullClient
2929
@rails_runner_client = T.let(NullClient.new, RunnerClient)
3030
@global_state = T.let(nil, T.nilable(GlobalState))
31+
@addon_mutex = T.let(Mutex.new, Mutex)
32+
@client_mutex = T.let(Mutex.new, Mutex)
33+
@client_mutex.lock
34+
35+
Thread.new do
36+
@addon_mutex.lock
37+
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client }
38+
@addon_mutex.unlock
39+
end
3140
end
3241

3342
sig { returns(RunnerClient) }
34-
attr_reader :rails_runner_client
43+
def rails_runner_client
44+
@addon_mutex.synchronize { @rails_runner_client }
45+
end
3546

3647
sig { override.params(global_state: GlobalState, message_queue: Thread::Queue).void }
3748
def activate(global_state, message_queue)
3849
@global_state = global_state
3950
$stderr.puts("Activating Ruby LSP Rails addon v#{VERSION}")
40-
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
41-
Thread.new { @rails_runner_client = RunnerClient.create_client }
4251
register_additional_file_watchers(global_state: global_state, message_queue: message_queue)
43-
4452
@global_state.index.register_enhancement(IndexingEnhancement.new)
53+
54+
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
55+
@client_mutex.unlock
4556
end
4657

4758
sig { override.void }

lib/ruby_lsp/ruby_lsp_rails/runner_client.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,14 @@ def initialize
9696
raise InitializationError, @stderr.read
9797
end
9898

99+
sig { params(server_addon_path: String).void }
100+
def register_server_addon(server_addon_path)
101+
send_notification("register_server_addon", server_addon_path: server_addon_path)
102+
rescue IncompleteMessageError
103+
$stderr.puts("Ruby LSP Rails failed to register server addon #{server_addon_path}")
104+
nil
105+
end
106+
99107
sig { params(name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
100108
def model(name)
101109
make_request("model", name: name)

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,41 @@
88

99
module RubyLsp
1010
module Rails
11+
class ServerAddon
12+
@server_addon_classes = []
13+
@server_addons = {}
14+
15+
class << self
16+
# We keep track of runtime server addons the same way we track other addons, by storing classes that inherit
17+
# from the base one
18+
def inherited(child)
19+
@server_addon_classes << child
20+
super
21+
end
22+
23+
# Delegate `request` with `params` to the server addon with the given `name`
24+
def delegate(name, request, params)
25+
@server_addons[name]&.execute(request, params)
26+
end
27+
28+
# Instantiate all server addons and store them in a hash for easy access after we have discovered the classes
29+
def finalize_registrations!
30+
until @server_addon_classes.empty?
31+
addon = @server_addon_classes.shift.new
32+
@server_addons[addon.name] = addon
33+
end
34+
end
35+
end
36+
37+
def name
38+
raise NotImplementedError, "Not implemented!"
39+
end
40+
41+
def execute(request, params)
42+
raise NotImplementedError, "Not implemented!"
43+
end
44+
end
45+
1146
class Server
1247
VOID = Object.new
1348

@@ -68,6 +103,14 @@ def execute(request, params)
68103
route_location(params.fetch(:name))
69104
when "route_info"
70105
resolve_route_info(params)
106+
when "register_server_addon"
107+
require params[:server_addon_path]
108+
ServerAddon.finalize_registrations!
109+
VOID
110+
when "server_addon/delegate"
111+
server_addon_name = params[:server_addon_name]
112+
request_name = params.delete(:request_name)
113+
ServerAddon.delegate(server_addon_name, request_name, params)
71114
else
72115
VOID
73116
end

test/ruby_lsp_rails/server_test.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,29 @@ def <(other)
135135
assert_equal "/users(.:format)", result[:path]
136136
end
137137

138+
test "server addons" do
139+
File.write("server_addon.rb", <<~RUBY)
140+
class TapiocaServerAddon < RubyLsp::Rails::ServerAddon
141+
def name
142+
"Tapioca"
143+
end
144+
145+
def execute(request, params)
146+
{ request:, params: }
147+
end
148+
end
149+
RUBY
150+
151+
@server.execute("register_server_addon", server_addon_path: File.expand_path("server_addon.rb"))
152+
153+
assert_equal(
154+
{ params: { server_addon_name: "Tapioca" }, request: "dsl" },
155+
@server.execute("server_addon/delegate", server_addon_name: "Tapioca", request_name: "dsl"),
156+
)
157+
ensure
158+
FileUtils.rm("server_addon.rb")
159+
end
160+
138161
test "prints in the Rails application or server are automatically redirected to stderr" do
139162
server = RubyLsp::Rails::Server.new
140163

0 commit comments

Comments
 (0)