ruby-lsp-rails 0.3.31 → 0.4.1

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.
@@ -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]
@@ -241,6 +364,8 @@ module RubyLsp
241
364
  info = {
242
365
  columns: const.columns.map { |column| [column.name, column.type, column.default, column.null] },
243
366
  primary_keys: Array(const.primary_key),
367
+ foreign_keys: collect_model_foreign_keys(const),
368
+ indexes: collect_model_indexes(const),
244
369
  }
245
370
 
246
371
  if ActiveRecord::Tasks::DatabaseTasks.respond_to?(:schema_dump_path)
@@ -312,8 +437,40 @@ module RubyLsp
312
437
  ::ActionView::PathRegistry.file_system_resolver_hooks.clear
313
438
  end
314
439
  end
440
+
441
+ def collect_model_foreign_keys(model)
442
+ return [] unless model.connection.respond_to?(:supports_foreign_keys?) &&
443
+ model.connection.supports_foreign_keys?
444
+
445
+ model.connection.foreign_keys(model.table_name).map do |key_definition|
446
+ key_definition.options[:column]
447
+ end
448
+ end
449
+
450
+ def collect_model_indexes(model)
451
+ return [] unless database_supports_indexing?(model)
452
+
453
+ model.connection.indexes(model.table_name).map do |index_definition|
454
+ {
455
+ name: index_definition.name,
456
+ columns: index_definition.columns,
457
+ unique: index_definition.unique,
458
+ }
459
+ end
460
+ end
461
+
462
+ def database_supports_indexing?(model)
463
+ return @database_supports_indexing if instance_variable_defined?(:@database_supports_indexing)
464
+
465
+ model.connection.indexes(model.table_name)
466
+ @database_supports_indexing = true
467
+ rescue NotImplementedError
468
+ @database_supports_indexing = false
469
+ end
315
470
  end
316
471
  end
317
472
  end
318
473
 
319
- RubyLsp::Rails::Server.new.start if ARGV.first == "start"
474
+ if ARGV.first == "start"
475
+ RubyLsp::Rails::Server.new(capabilities: JSON.parse(ARGV[1], symbolize_names: true)).start
476
+ end
@@ -4,9 +4,7 @@
4
4
  module RubyLsp
5
5
  module Rails
6
6
  module ActiveSupportTestCaseHelper
7
- extend T::Sig
8
-
9
- sig { params(node: Prism::CallNode).returns(T.nilable(String)) }
7
+ #: (Prism::CallNode node) -> String?
10
8
  def extract_test_case_name(node)
11
9
  message_value = node.message
12
10
  return unless message_value == "test" || message_value == "it"
@@ -6,9 +6,7 @@ module RubyLsp
6
6
  module Support
7
7
  class LocationBuilder
8
8
  class << self
9
- extend T::Sig
10
-
11
- sig { params(location_string: String).returns(Interface::Location) }
9
+ #: (String location_string) -> Interface::Location
12
10
  def line_location_from_s(location_string)
13
11
  *file_parts, line = location_string.split(":")
14
12
  raise ArgumentError, "Invalid location string given" if file_parts.empty?
@@ -3,6 +3,6 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Rails
6
- VERSION = "0.3.31"
6
+ VERSION = "0.4.1"
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.1
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: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: ruby-lsp
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 0.23.0
18
+ version: 0.23.14
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: 0.24.0
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 0.23.0
28
+ version: 0.23.14
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: 0.24.0
@@ -47,6 +47,7 @@ files:
47
47
  - lib/ruby_lsp/ruby_lsp_rails/document_symbol.rb
48
48
  - lib/ruby_lsp/ruby_lsp_rails/hover.rb
49
49
  - lib/ruby_lsp/ruby_lsp_rails/indexing_enhancement.rb
50
+ - lib/ruby_lsp/ruby_lsp_rails/rails_test_style.rb
50
51
  - lib/ruby_lsp/ruby_lsp_rails/runner_client.rb
51
52
  - lib/ruby_lsp/ruby_lsp_rails/server.rb
52
53
  - lib/ruby_lsp/ruby_lsp_rails/support/active_support_test_case_helper.rb
@@ -78,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
78
79
  - !ruby/object:Gem::Version
79
80
  version: '0'
80
81
  requirements: []
81
- rubygems_version: 3.6.2
82
+ rubygems_version: 3.6.8
82
83
  specification_version: 4
83
84
  summary: A Ruby LSP addon for Rails
84
85
  test_files: []