ruby-lsp-rails 0.3.31 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/ruby_lsp/ruby_lsp_rails/addon.rb +22 -5
- data/lib/ruby_lsp/ruby_lsp_rails/runner_client.rb +57 -11
- data/lib/ruby_lsp/ruby_lsp_rails/server.rb +142 -17
- data/lib/ruby_lsp_rails/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6995848edfaf04a9ff896c3e5b26704ff27e32e459154da3cf5d1340acb032b3
|
4
|
+
data.tar.gz: 9af25b875fe37dab802e813a7018bef86cc9b5cad0163d8d2d7031f40083779f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b1cffaabe1cfe7db5c063d79cefdedaf4c00f79d9f1af561a8dc45976e8b731b750642722cdd89a84891e28f721e8074f86a58934e2e728e60863f2122ba7a4
|
7
|
+
data.tar.gz: 617030501d5184109d42400bed761b213dcc8b66ccd9528bae1ab795c2f96e220710de3df94e4425e9e76c4a9584514bff67e220c8bdae27004b1fc001724165
|
@@ -32,6 +32,12 @@ module RubyLsp
|
|
32
32
|
@rails_runner_client = T.let(NullClient.new, RunnerClient)
|
33
33
|
@global_state = T.let(nil, T.nilable(GlobalState))
|
34
34
|
@outgoing_queue = T.let(nil, T.nilable(Thread::Queue))
|
35
|
+
@settings = T.let(
|
36
|
+
{
|
37
|
+
enablePendingMigrationsPrompt: true,
|
38
|
+
},
|
39
|
+
T::Hash[Symbol, T.untyped],
|
40
|
+
)
|
35
41
|
@addon_mutex = T.let(Mutex.new, Mutex)
|
36
42
|
@client_mutex = T.let(Mutex.new, Mutex)
|
37
43
|
@client_mutex.lock
|
@@ -39,7 +45,9 @@ module RubyLsp
|
|
39
45
|
Thread.new do
|
40
46
|
@addon_mutex.synchronize do
|
41
47
|
# We need to ensure the Rails client is fully loaded before we activate the server addons
|
42
|
-
@client_mutex.synchronize
|
48
|
+
@client_mutex.synchronize do
|
49
|
+
@rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue), T.must(@global_state))
|
50
|
+
end
|
43
51
|
offer_to_run_pending_migrations
|
44
52
|
end
|
45
53
|
end
|
@@ -56,6 +64,9 @@ module RubyLsp
|
|
56
64
|
@outgoing_queue = outgoing_queue
|
57
65
|
@outgoing_queue << Notification.window_log_message("Activating Ruby LSP Rails add-on v#{VERSION}")
|
58
66
|
|
67
|
+
addon_settings = @global_state.settings_for_addon(name)
|
68
|
+
@settings.merge!(addon_settings) if addon_settings
|
69
|
+
|
59
70
|
register_additional_file_watchers(global_state: global_state, outgoing_queue: outgoing_queue)
|
60
71
|
|
61
72
|
# Start booting the real client in a background thread. Until this completes, the client will be a NullClient
|
@@ -81,7 +92,9 @@ module RubyLsp
|
|
81
92
|
).void
|
82
93
|
end
|
83
94
|
def create_code_lens_listener(response_builder, uri, dispatcher)
|
84
|
-
|
95
|
+
return unless @global_state
|
96
|
+
|
97
|
+
CodeLens.new(@rails_runner_client, @global_state, response_builder, uri, dispatcher)
|
85
98
|
end
|
86
99
|
|
87
100
|
sig do
|
@@ -92,7 +105,9 @@ module RubyLsp
|
|
92
105
|
).void
|
93
106
|
end
|
94
107
|
def create_hover_listener(response_builder, node_context, dispatcher)
|
95
|
-
|
108
|
+
return unless @global_state
|
109
|
+
|
110
|
+
Hover.new(@rails_runner_client, response_builder, node_context, @global_state, dispatcher)
|
96
111
|
end
|
97
112
|
|
98
113
|
sig do
|
@@ -116,8 +131,9 @@ module RubyLsp
|
|
116
131
|
).void
|
117
132
|
end
|
118
133
|
def create_definition_listener(response_builder, uri, node_context, dispatcher)
|
119
|
-
|
120
|
-
|
134
|
+
return unless @global_state
|
135
|
+
|
136
|
+
Definition.new(@rails_runner_client, response_builder, node_context, @global_state.index, dispatcher)
|
121
137
|
end
|
122
138
|
|
123
139
|
sig do
|
@@ -251,6 +267,7 @@ module RubyLsp
|
|
251
267
|
def offer_to_run_pending_migrations
|
252
268
|
return unless @outgoing_queue
|
253
269
|
return unless @global_state&.client_capabilities&.window_show_message_supports_extra_properties
|
270
|
+
return unless @settings[:enablePendingMigrationsPrompt]
|
254
271
|
|
255
272
|
migration_message = @rails_runner_client.pending_migrations_message
|
256
273
|
return unless migration_message
|
@@ -10,10 +10,10 @@ module RubyLsp
|
|
10
10
|
class << self
|
11
11
|
extend T::Sig
|
12
12
|
|
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)
|
15
15
|
if File.exist?("bin/rails")
|
16
|
-
new(outgoing_queue)
|
16
|
+
new(outgoing_queue, global_state)
|
17
17
|
else
|
18
18
|
unless outgoing_queue.closed?
|
19
19
|
outgoing_queue << RubyLsp::Notification.window_log_message(
|
@@ -51,8 +51,8 @@ module RubyLsp
|
|
51
51
|
sig { returns(String) }
|
52
52
|
attr_reader :rails_root
|
53
53
|
|
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)
|
56
56
|
@outgoing_queue = T.let(outgoing_queue, Thread::Queue)
|
57
57
|
@mutex = T.let(Mutex.new, Mutex)
|
58
58
|
# 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 @@ module RubyLsp
|
|
71
71
|
log_message("Ruby LSP Rails booting server")
|
72
72
|
|
73
73
|
stdin, stdout, stderr, wait_thread = Bundler.with_original_env do
|
74
|
-
Open3.popen3(
|
74
|
+
Open3.popen3(
|
75
|
+
"bundle",
|
76
|
+
"exec",
|
77
|
+
"rails",
|
78
|
+
"runner",
|
79
|
+
"#{__dir__}/server.rb",
|
80
|
+
"start",
|
81
|
+
server_relevant_capabilities(global_state),
|
82
|
+
)
|
75
83
|
end
|
76
84
|
|
77
85
|
@stdin = T.let(stdin, IO)
|
@@ -100,10 +108,16 @@ module RubyLsp
|
|
100
108
|
end
|
101
109
|
end
|
102
110
|
|
103
|
-
|
111
|
+
# Responsible for transmitting notifications coming from the server to the outgoing queue, so that we can do
|
112
|
+
# things such as showing progress notifications initiated by the server
|
113
|
+
@notifier_thread = T.let(
|
104
114
|
Thread.new do
|
105
|
-
|
106
|
-
|
115
|
+
until @stderr.closed?
|
116
|
+
notification = read_notification
|
117
|
+
|
118
|
+
unless @outgoing_queue.closed? || !notification
|
119
|
+
@outgoing_queue << notification
|
120
|
+
end
|
107
121
|
end
|
108
122
|
rescue IOError
|
109
123
|
# The server was shutdown and stderr is already closed
|
@@ -259,6 +273,13 @@ module RubyLsp
|
|
259
273
|
[@stdin, @stdout, @stderr].all?(&:closed?) && !@wait_thread.alive?
|
260
274
|
end
|
261
275
|
|
276
|
+
sig { returns(T::Boolean) }
|
277
|
+
def connected?
|
278
|
+
true
|
279
|
+
end
|
280
|
+
|
281
|
+
private
|
282
|
+
|
262
283
|
sig do
|
263
284
|
params(
|
264
285
|
request: String,
|
@@ -274,8 +295,6 @@ module RubyLsp
|
|
274
295
|
sig { params(request: String, params: T.untyped).void }
|
275
296
|
def send_notification(request, **params) = send_message(request, **params)
|
276
297
|
|
277
|
-
private
|
278
|
-
|
279
298
|
sig { overridable.params(request: String, params: T.untyped).void }
|
280
299
|
def send_message(request, **params)
|
281
300
|
message = { method: request }
|
@@ -338,6 +357,28 @@ module RubyLsp
|
|
338
357
|
|
339
358
|
length.to_i
|
340
359
|
end
|
360
|
+
|
361
|
+
# Read a server notification from stderr. Only intended to be used by notifier thread
|
362
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
363
|
+
def read_notification
|
364
|
+
headers = @stderr.gets("\r\n\r\n")
|
365
|
+
return unless headers
|
366
|
+
|
367
|
+
length = headers[/Content-Length: (\d+)/i, 1]
|
368
|
+
return unless length
|
369
|
+
|
370
|
+
raw_content = @stderr.read(length.to_i)
|
371
|
+
return unless raw_content
|
372
|
+
|
373
|
+
JSON.parse(raw_content, symbolize_names: true)
|
374
|
+
end
|
375
|
+
|
376
|
+
sig { params(global_state: GlobalState).returns(String) }
|
377
|
+
def server_relevant_capabilities(global_state)
|
378
|
+
{
|
379
|
+
supports_progress: global_state.client_capabilities.supports_progress,
|
380
|
+
}.to_json
|
381
|
+
end
|
341
382
|
end
|
342
383
|
|
343
384
|
class NullClient < RunnerClient
|
@@ -362,6 +403,11 @@ module RubyLsp
|
|
362
403
|
Dir.pwd
|
363
404
|
end
|
364
405
|
|
406
|
+
sig { returns(T::Boolean) }
|
407
|
+
def connected?
|
408
|
+
false
|
409
|
+
end
|
410
|
+
|
365
411
|
private
|
366
412
|
|
367
413
|
sig { params(message: ::String, type: ::Integer).void }
|
@@ -3,19 +3,42 @@
|
|
3
3
|
|
4
4
|
require "json"
|
5
5
|
require "open3"
|
6
|
+
require "delegate"
|
6
7
|
|
7
8
|
module RubyLsp
|
8
9
|
module Rails
|
9
10
|
module Common
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
class Progress
|
12
|
+
def initialize(stderr, id, supports_progress)
|
13
|
+
@stderr = stderr
|
14
|
+
@id = id
|
15
|
+
@supports_progress = supports_progress
|
16
|
+
end
|
17
|
+
|
18
|
+
def report(percentage: nil, message: nil)
|
19
|
+
return unless @supports_progress
|
20
|
+
return unless percentage || message
|
21
|
+
|
22
|
+
json_message = {
|
23
|
+
method: "$/progress",
|
24
|
+
params: {
|
25
|
+
token: @id,
|
26
|
+
value: {
|
27
|
+
kind: "report",
|
28
|
+
percentage: percentage,
|
29
|
+
message: message,
|
30
|
+
},
|
31
|
+
},
|
32
|
+
}.to_json
|
33
|
+
|
34
|
+
@stderr.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
35
|
+
end
|
14
36
|
end
|
15
37
|
|
16
|
-
# Log a message to the editor's output panel
|
17
|
-
|
18
|
-
|
38
|
+
# Log a message to the editor's output panel. The type is the number of the message type, which can be found in
|
39
|
+
# the specification https://microsoft.github.io/language-server-protocol/specification/#messageType
|
40
|
+
def log_message(message, type: 4)
|
41
|
+
send_notification({ method: "window/logMessage", params: { type: type, message: message } })
|
19
42
|
end
|
20
43
|
|
21
44
|
# Sends an error result to a request, if the request failed. DO NOT INVOKE THIS METHOD FOR NOTIFICATIONS! Use
|
@@ -54,6 +77,82 @@ module RubyLsp
|
|
54
77
|
rescue => e
|
55
78
|
log_message("Request #{notification_name} failed:\n#{e.full_message(highlight: false)}")
|
56
79
|
end
|
80
|
+
|
81
|
+
def begin_progress(id, title, percentage: nil, message: nil)
|
82
|
+
return unless @capabilities[:supports_progress]
|
83
|
+
|
84
|
+
# This is actually a request, but it is sent asynchronously and we do not return the response back to the
|
85
|
+
# server, so we consider it a notification from the perspective of the client/runtime server dynamic
|
86
|
+
send_notification({
|
87
|
+
id: "progress-request-#{id}",
|
88
|
+
method: "window/workDoneProgress/create",
|
89
|
+
params: { token: id },
|
90
|
+
})
|
91
|
+
|
92
|
+
send_notification({
|
93
|
+
method: "$/progress",
|
94
|
+
params: {
|
95
|
+
token: id,
|
96
|
+
value: {
|
97
|
+
kind: "begin",
|
98
|
+
title: title,
|
99
|
+
percentage: percentage,
|
100
|
+
message: message,
|
101
|
+
},
|
102
|
+
},
|
103
|
+
})
|
104
|
+
end
|
105
|
+
|
106
|
+
def report_progress(id, percentage: nil, message: nil)
|
107
|
+
return unless @capabilities[:supports_progress]
|
108
|
+
|
109
|
+
send_notification({
|
110
|
+
method: "$/progress",
|
111
|
+
params: {
|
112
|
+
token: id,
|
113
|
+
value: {
|
114
|
+
kind: "report",
|
115
|
+
percentage: percentage,
|
116
|
+
message: message,
|
117
|
+
},
|
118
|
+
},
|
119
|
+
})
|
120
|
+
end
|
121
|
+
|
122
|
+
def end_progress(id)
|
123
|
+
return unless @capabilities[:supports_progress]
|
124
|
+
|
125
|
+
send_notification({
|
126
|
+
method: "$/progress",
|
127
|
+
params: {
|
128
|
+
token: id,
|
129
|
+
value: { kind: "end" },
|
130
|
+
},
|
131
|
+
})
|
132
|
+
end
|
133
|
+
|
134
|
+
def with_progress(id, title, percentage: nil, message: nil, &block)
|
135
|
+
progress_block = Progress.new(@stderr, id, @capabilities[:supports_progress])
|
136
|
+
return block.call(progress_block) unless @capabilities[:supports_progress]
|
137
|
+
|
138
|
+
begin_progress(id, title, percentage: percentage, message: message)
|
139
|
+
block.call(progress_block)
|
140
|
+
end_progress(id)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
# Write a response message back to the client
|
146
|
+
def send_message(message)
|
147
|
+
json_message = message.to_json
|
148
|
+
@stdout.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
149
|
+
end
|
150
|
+
|
151
|
+
# Write a notification to the client to be transmitted to the editor
|
152
|
+
def send_notification(message)
|
153
|
+
json_message = message.to_json
|
154
|
+
@stderr.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
155
|
+
end
|
57
156
|
end
|
58
157
|
|
59
158
|
class ServerAddon
|
@@ -76,16 +175,18 @@ module RubyLsp
|
|
76
175
|
end
|
77
176
|
|
78
177
|
# Instantiate all server addons and store them in a hash for easy access after we have discovered the classes
|
79
|
-
def finalize_registrations!(stdout)
|
178
|
+
def finalize_registrations!(stdout, stderr, capabilities)
|
80
179
|
until @server_addon_classes.empty?
|
81
|
-
addon = @server_addon_classes.shift.new(stdout)
|
180
|
+
addon = @server_addon_classes.shift.new(stdout, stderr, capabilities)
|
82
181
|
@server_addons[addon.name] = addon
|
83
182
|
end
|
84
183
|
end
|
85
184
|
end
|
86
185
|
|
87
|
-
def initialize(stdout)
|
186
|
+
def initialize(stdout, stderr, capabilities)
|
88
187
|
@stdout = stdout
|
188
|
+
@stderr = stderr
|
189
|
+
@capabilities = capabilities
|
89
190
|
end
|
90
191
|
|
91
192
|
def name
|
@@ -97,14 +198,31 @@ module RubyLsp
|
|
97
198
|
end
|
98
199
|
end
|
99
200
|
|
201
|
+
class IOWrapper < SimpleDelegator
|
202
|
+
def puts(*args)
|
203
|
+
args.each { |arg| log("#{arg}\n") }
|
204
|
+
end
|
205
|
+
|
206
|
+
def print(*args)
|
207
|
+
args.each { |arg| log(arg.to_s) }
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
|
212
|
+
def log(message)
|
213
|
+
json_message = { method: "window/logMessage", params: { type: 4, message: message } }.to_json
|
214
|
+
write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
100
218
|
class Server
|
101
219
|
include Common
|
102
220
|
|
103
|
-
def initialize(stdout: $stdout, override_default_output_device: true)
|
221
|
+
def initialize(stdout: $stdout, stderr: $stderr, override_default_output_device: true, capabilities: {})
|
104
222
|
# Grab references to the original pipes so that we can change the default output device further down
|
105
223
|
@stdin = $stdin
|
106
224
|
@stdout = stdout
|
107
|
-
@stderr =
|
225
|
+
@stderr = stderr
|
108
226
|
@stdin.sync = true
|
109
227
|
@stdout.sync = true
|
110
228
|
@stderr.sync = true
|
@@ -112,10 +230,13 @@ module RubyLsp
|
|
112
230
|
@stdout.binmode
|
113
231
|
@stderr.binmode
|
114
232
|
|
233
|
+
# A hash containing the capabilities of the editor that may be relevant for the runtime server
|
234
|
+
@capabilities = capabilities
|
235
|
+
|
115
236
|
# # Set the default output device to be $stderr. This means that using `puts` by itself will default to printing
|
116
237
|
# # to $stderr and only explicit `$stdout.puts` will go to $stdout. This reduces the chance that output coming
|
117
238
|
# # from the Rails app will be accidentally sent to the client
|
118
|
-
$> =
|
239
|
+
$> = IOWrapper.new(@stderr) if override_default_output_device
|
119
240
|
|
120
241
|
@running = true
|
121
242
|
end
|
@@ -155,8 +276,10 @@ module RubyLsp
|
|
155
276
|
send_result(run_migrations)
|
156
277
|
end
|
157
278
|
when "reload"
|
158
|
-
|
159
|
-
|
279
|
+
with_progress("rails-reload", "Reloading Ruby LSP Rails instance") do
|
280
|
+
with_notification_error_handling(request) do
|
281
|
+
::Rails.application.reloader.reload!
|
282
|
+
end
|
160
283
|
end
|
161
284
|
when "route_location"
|
162
285
|
with_request_error_handling(request) do
|
@@ -169,7 +292,7 @@ module RubyLsp
|
|
169
292
|
when "server_addon/register"
|
170
293
|
with_notification_error_handling(request) do
|
171
294
|
require params[:server_addon_path]
|
172
|
-
ServerAddon.finalize_registrations!(@stdout)
|
295
|
+
ServerAddon.finalize_registrations!(@stdout, @stderr, @capabilities)
|
173
296
|
end
|
174
297
|
when "server_addon/delegate"
|
175
298
|
server_addon_name = params[:server_addon_name]
|
@@ -316,4 +439,6 @@ module RubyLsp
|
|
316
439
|
end
|
317
440
|
end
|
318
441
|
|
319
|
-
|
442
|
+
if ARGV.first == "start"
|
443
|
+
RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
|
444
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-02-04 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: ruby-lsp
|
@@ -78,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
78
78
|
- !ruby/object:Gem::Version
|
79
79
|
version: '0'
|
80
80
|
requirements: []
|
81
|
-
rubygems_version: 3.6.
|
81
|
+
rubygems_version: 3.6.3
|
82
82
|
specification_version: 4
|
83
83
|
summary: A Ruby LSP addon for Rails
|
84
84
|
test_files: []
|