Skip to content

Commit d749d1b

Browse files
committed
Add support for server addons
1 parent fc99832 commit d749d1b

File tree

4 files changed

+86
-4
lines changed

4 files changed

+86
-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("server_addon/register", 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: 42 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
def initialize(stdout: $stdout, override_default_output_device: true)
1348
# Grab references to the original pipes so that we can change the default output device further down
@@ -60,6 +95,13 @@ def execute(request, params)
6095
write_response(route_location(params.fetch(:name)))
6196
when "route_info"
6297
write_response(resolve_route_info(params))
98+
when "server_addon/register"
99+
require params[:server_addon_path]
100+
ServerAddon.finalize_registrations!
101+
when "server_addon/delegate"
102+
server_addon_name = params[:server_addon_name]
103+
request_name = params.delete(:request_name)
104+
write_response(ServerAddon.delegate(server_addon_name, request_name, params))
63105
end
64106
rescue => e
65107
write_response({ error: e.full_message(highlight: false) })

test/ruby_lsp_rails/server_test.rb

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

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

0 commit comments

Comments
 (0)