Skip to content

Commit 38ddfa8

Browse files
authored
Start passing relevant capabilities to runtime server (#560)
To be able to support server initiated progress, it will need to know if the editor connecting to the LSP actually supports it, otherwise we risk sending notifications that the client cannot handle. My proposal is that we start passing a JSON containing only the relevant client capabilities to the server, so that we can check what is enabled.
1 parent 7fe045a commit 38ddfa8

File tree

5 files changed

+63
-14
lines changed

5 files changed

+63
-14
lines changed

lib/ruby_lsp/ruby_lsp_rails/addon.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ def initialize
3939
Thread.new do
4040
@addon_mutex.synchronize do
4141
# We need to ensure the Rails client is fully loaded before we activate the server addons
42-
@client_mutex.synchronize { @rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue)) }
42+
@client_mutex.synchronize do
43+
@rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue), T.must(@global_state))
44+
end
4345
offer_to_run_pending_migrations
4446
end
4547
end

lib/ruby_lsp/ruby_lsp_rails/runner_client.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ class RunnerClient
1010
class << self
1111
extend T::Sig
1212

13-
sig { params(outgoing_queue: Thread::Queue).returns(RunnerClient) }
14-
def create_client(outgoing_queue)
13+
sig { params(outgoing_queue: Thread::Queue, global_state: RubyLsp::GlobalState).returns(RunnerClient) }
14+
def create_client(outgoing_queue, global_state)
1515
if File.exist?("bin/rails")
16-
new(outgoing_queue)
16+
new(outgoing_queue, global_state)
1717
else
1818
unless outgoing_queue.closed?
1919
outgoing_queue << RubyLsp::Notification.window_log_message(
@@ -51,8 +51,8 @@ class EmptyMessageError < MessageError; end
5151
sig { returns(String) }
5252
attr_reader :rails_root
5353

54-
sig { params(outgoing_queue: Thread::Queue).void }
55-
def initialize(outgoing_queue)
54+
sig { params(outgoing_queue: Thread::Queue, global_state: RubyLsp::GlobalState).void }
55+
def initialize(outgoing_queue, global_state)
5656
@outgoing_queue = T.let(outgoing_queue, Thread::Queue)
5757
@mutex = T.let(Mutex.new, Mutex)
5858
# Spring needs a Process session ID. It uses this ID to "attach" itself to the parent process, so that when the
@@ -71,7 +71,15 @@ def initialize(outgoing_queue)
7171
log_message("Ruby LSP Rails booting server")
7272

7373
stdin, stdout, stderr, wait_thread = Bundler.with_original_env do
74-
Open3.popen3("bundle", "exec", "rails", "runner", "#{__dir__}/server.rb", "start")
74+
Open3.popen3(
75+
"bundle",
76+
"exec",
77+
"rails",
78+
"runner",
79+
"#{__dir__}/server.rb",
80+
"start",
81+
server_relevant_capabilities(global_state),
82+
)
7583
end
7684

7785
@stdin = T.let(stdin, IO)
@@ -359,6 +367,13 @@ def read_notification
359367

360368
JSON.parse(raw_content, symbolize_names: true)
361369
end
370+
371+
sig { params(global_state: GlobalState).returns(String) }
372+
def server_relevant_capabilities(global_state)
373+
{
374+
supports_progress: global_state.client_capabilities.supports_progress,
375+
}.to_json
376+
end
362377
end
363378

364379
class NullClient < RunnerClient

lib/ruby_lsp/ruby_lsp_rails/server.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def execute(request, params)
110110
class Server
111111
include Common
112112

113-
def initialize(stdout: $stdout, stderr: $stderr, override_default_output_device: true)
113+
def initialize(stdout: $stdout, stderr: $stderr, override_default_output_device: true, capabilities: {})
114114
# Grab references to the original pipes so that we can change the default output device further down
115115
@stdin = $stdin
116116
@stdout = stdout
@@ -122,6 +122,9 @@ def initialize(stdout: $stdout, stderr: $stderr, override_default_output_device:
122122
@stdout.binmode
123123
@stderr.binmode
124124

125+
# A hash containing the capabilities of the editor that may be relevant for the runtime server
126+
@capabilities = capabilities
127+
125128
# # Set the default output device to be $stderr. This means that using `puts` by itself will default to printing
126129
# # to $stderr and only explicit `$stdout.puts` will go to $stdout. This reduces the chance that output coming
127130
# # from the Rails app will be accidentally sent to the client
@@ -326,4 +329,6 @@ def clear_file_system_resolver_hooks
326329
end
327330
end
328331

329-
RubyLsp::Rails::Server.new.start if ARGV.first == "start"
332+
if ARGV.first == "start"
333+
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
334+
end

test/ruby_lsp_rails/launch_test.rb

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,34 @@ module Rails
88
class LaunchTest < ActiveSupport::TestCase
99
test "launching the client succeeds" do
1010
outgoing_queue = Thread::Queue.new
11+
global_state = GlobalState.new
1112

12-
client = RunnerClient.create_client(outgoing_queue)
13+
client = RunnerClient.create_client(outgoing_queue, global_state)
14+
refute_instance_of(NullClient, client)
15+
16+
first = pop_log_notification(outgoing_queue, Constant::MessageType::LOG)
17+
assert_equal("Ruby LSP Rails booting server", first.params.message)
18+
19+
second = pop_log_notification(outgoing_queue, Constant::MessageType::LOG)
20+
assert_match("Finished booting Ruby LSP Rails server", second.params.message)
21+
22+
client.shutdown
23+
assert_predicate(client, :stopped?)
24+
outgoing_queue.close
25+
end
26+
27+
test "launching with client capabilities succeeds" do
28+
outgoing_queue = Thread::Queue.new
29+
global_state = GlobalState.new
30+
global_state.apply_options({
31+
capabilities: {
32+
window: {
33+
workDoneProgress: true,
34+
},
35+
},
36+
})
37+
38+
client = RunnerClient.create_client(outgoing_queue, global_state)
1339
refute_instance_of(NullClient, client)
1440

1541
first = pop_log_notification(outgoing_queue, Constant::MessageType::LOG)

test/ruby_lsp_rails/runner_client_test.rb

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ module Rails
99
class RunnerClientTest < ActiveSupport::TestCase
1010
setup do
1111
@outgoing_queue = Thread::Queue.new
12-
@client = T.let(RunnerClient.new(@outgoing_queue), RunnerClient)
12+
@global_state = GlobalState.new
13+
@client = T.let(RunnerClient.new(@outgoing_queue, @global_state), RunnerClient)
1314
end
1415

1516
teardown do
@@ -48,7 +49,7 @@ class RunnerClientTest < ActiveSupport::TestCase
4849
FileUtils.mv("bin/rails", "bin/rails_backup")
4950

5051
outgoing_queue = Thread::Queue.new
51-
client = RunnerClient.create_client(outgoing_queue)
52+
client = RunnerClient.create_client(outgoing_queue, @global_state)
5253

5354
assert_instance_of(NullClient, client)
5455
assert_nil(client.model("User"))
@@ -66,7 +67,7 @@ class RunnerClientTest < ActiveSupport::TestCase
6667
FileUtils.mv("test/dummy/config/application.rb", "test/dummy/config/application.rb.bak")
6768

6869
outgoing_queue = Thread::Queue.new
69-
client = RunnerClient.create_client(outgoing_queue)
70+
client = RunnerClient.create_client(outgoing_queue, @global_state)
7071

7172
assert_instance_of(NullClient, client)
7273
assert_nil(client.model("User"))
@@ -88,7 +89,7 @@ class RunnerClientTest < ActiveSupport::TestCase
8889
File.write("test/dummy/config/application.rb", content + junk)
8990

9091
outgoing_queue = Thread::Queue.new
91-
client = RunnerClient.create_client(outgoing_queue)
92+
client = RunnerClient.create_client(outgoing_queue, @global_state)
9293
response = client.model("User")
9394

9495
begin

0 commit comments

Comments
 (0)