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 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: []