ruby-lsp-rails 0.3.31 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1d9f0d516dcad6d833e05e05defd6ba7ea0bbb3fb2b0c48e7edb9709306d854
4
- data.tar.gz: 41b2f9dec0e1ba1d3d4d92a5eb1b4ad5dfd6b6d8c3e62b69822ac156f3232bfd
3
+ metadata.gz: 6995848edfaf04a9ff896c3e5b26704ff27e32e459154da3cf5d1340acb032b3
4
+ data.tar.gz: 9af25b875fe37dab802e813a7018bef86cc9b5cad0163d8d2d7031f40083779f
5
5
  SHA512:
6
- metadata.gz: f4421fb81f4cffaa89abe05244ec8bcc2da8ffa0e27b5f7c310c49747d7b2e20a6eec85b58dfe77f5107bd8281ca9d2e61a5f70b2d100c5938f4a2932deedfd0
7
- data.tar.gz: aa8476c6f72a66782d9d8b3225944c862f708cd9fc60fd17cf871d9f3332885bd0b7060cbf778710e9213686d5e24c1e415327c14a00a738e28f934dfae156c7
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 { @rails_runner_client = RunnerClient.create_client(T.must(@outgoing_queue)) }
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
- CodeLens.new(@rails_runner_client, T.must(@global_state), response_builder, uri, dispatcher)
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
- Hover.new(@rails_runner_client, response_builder, node_context, T.must(@global_state), dispatcher)
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
- index = T.must(@global_state).index
120
- Definition.new(@rails_runner_client, response_builder, node_context, index, dispatcher)
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("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
+ )
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
- @logger_thread = T.let(
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
- while (content = @stderr.gets("\n"))
106
- log_message(content, type: RubyLsp::Constant::MessageType::LOG)
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
- # Write a message to the client. Can be used for sending notifications to the editor
11
- def send_message(message)
12
- json_message = message.to_json
13
- @stdout.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
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
- def log_message(message)
18
- $stderr.puts(message)
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 = $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
- $> = $stderr if override_default_output_device
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
- with_notification_error_handling(request) do
159
- ::Rails.application.reloader.reload!
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
- RubyLsp::Rails::Server.new.start if ARGV.first == "start"
442
+ if ARGV.first == "start"
443
+ RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
444
+ end
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.31"
6
+ VERSION = "0.4.0"
7
7
  end
8
8
  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.3.31
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-01-23 00:00:00.000000000 Z
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.2
81
+ rubygems_version: 3.6.3
82
82
  specification_version: 4
83
83
  summary: A Ruby LSP addon for Rails
84
84
  test_files: []