ruby-lsp 0.24.1 → 0.25.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +10 -4
  4. data/exe/ruby-lsp-check +0 -4
  5. data/exe/ruby-lsp-launcher +18 -9
  6. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
  7. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +3 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +2 -2
  10. data/lib/ruby_indexer/test/configuration_test.rb +1 -1
  11. data/lib/ruby_indexer/test/rbs_indexer_test.rb +2 -2
  12. data/lib/ruby_lsp/addon.rb +20 -6
  13. data/lib/ruby_lsp/base_server.rb +9 -5
  14. data/lib/ruby_lsp/document.rb +151 -50
  15. data/lib/ruby_lsp/global_state.rb +21 -0
  16. data/lib/ruby_lsp/internal.rb +0 -2
  17. data/lib/ruby_lsp/listeners/hover.rb +7 -0
  18. data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
  19. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  20. data/lib/ruby_lsp/requests/code_lens.rb +9 -3
  21. data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
  22. data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
  23. data/lib/ruby_lsp/requests/references.rb +1 -1
  24. data/lib/ruby_lsp/requests/request.rb +3 -1
  25. data/lib/ruby_lsp/requests/support/formatter.rb +9 -3
  26. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -2
  27. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +9 -3
  28. data/lib/ruby_lsp/response_builders/response_builder.rb +3 -3
  29. data/lib/ruby_lsp/ruby_document.rb +1 -1
  30. data/lib/ruby_lsp/server.rb +43 -21
  31. data/lib/ruby_lsp/setup_bundler.rb +48 -22
  32. data/lib/ruby_lsp/static_docs.rb +1 -0
  33. data/lib/ruby_lsp/store.rb +0 -10
  34. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +13 -5
  35. data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +17 -4
  36. data/lib/ruby_lsp/utils.rb +44 -5
  37. data/static_docs/break.md +103 -0
  38. metadata +2 -16
  39. data/lib/ruby_lsp/load_sorbet.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 981f87e306aa96edd6d2e04fc274d5da24ede96d3e3333bcfdeb63039e86c9ab
4
- data.tar.gz: 5405216e985bed5ec6bce692baeeca9611980edc53fadf4655d00503488c277d
3
+ metadata.gz: 7637d6f68616069c2f8c0d870f3daf31808a1cccd8ab32d2bdaf1f67274f9b59
4
+ data.tar.gz: 7ea1d75ad921adbf01f69a0f5d2fba905673311c13b0999d44c08bdf54737053
5
5
  SHA512:
6
- metadata.gz: a749ebc7c81100f0c76431d68865960ad8aeb04ed3aad37dad18c008e885a50d224157a2fdcd44b15abc2d5d5f8d7a4a2345f187662707478dbc66d191c214a2
7
- data.tar.gz: 19a288a8121a4cf86b5fd60b9c6d11c4fd3cb26330f7667ac59b4742dbe91328b9f742dca5fb371bb58ad565afb9ec58516eb6b68dd85e641a800f89a23f81e9
6
+ metadata.gz: a354535a3853b0826635408fc46dc85cb7cee97c4993012dd983af11b8917121962d97bddf0853406b54f619faa8c2383cccca9638b4a0624309fb1e4c838249
7
+ data.tar.gz: 9e49e2436c63d90fc373b52ac9f6b65e83964fe108abd0a18fd46b655cd99bd55c471dd198624bb8701890f62175af2c9fcb50fcfa2bbf69e706406b9fa1e472
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.24.1
1
+ 0.25.0
data/exe/ruby-lsp CHANGED
@@ -88,13 +88,17 @@ if ENV["BUNDLE_GEMFILE"].nil?
88
88
  exit exec(env, "#{base_command} exec ruby-lsp #{original_args.join(" ")}".strip)
89
89
  end
90
90
 
91
+ $stdin.sync = true
92
+ $stdout.sync = true
93
+ $stderr.sync = true
94
+ $stdin.binmode
95
+ $stdout.binmode
96
+ $stderr.binmode
97
+
91
98
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
92
99
 
93
- require "ruby_lsp/load_sorbet"
94
100
  require "ruby_lsp/internal"
95
101
 
96
- T::Utils.run_all_sig_blocks
97
-
98
102
  if options[:debug]
99
103
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
100
104
  $stderr.puts "Debugging is not supported on Windows"
@@ -147,8 +151,10 @@ if options[:doctor]
147
151
  return
148
152
  end
149
153
 
154
+ server = RubyLsp::Server.new
155
+
150
156
  # Ensure all output goes out stderr by default to allow puts/p/pp to work
151
157
  # without specifying output device.
152
158
  $> = $stderr
153
159
 
154
- RubyLsp::Server.new.start
160
+ server.start
data/exe/ruby-lsp-check CHANGED
@@ -3,13 +3,9 @@
3
3
 
4
4
  # This executable checks if all automatic LSP requests run successfully on every Ruby file under the current directory
5
5
 
6
- require "ruby_lsp/load_sorbet"
7
-
8
6
  $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
9
7
  require "ruby_lsp/internal"
10
8
 
11
- T::Utils.run_all_sig_blocks
12
-
13
9
  files = Dir.glob("#{Dir.pwd}/**/*.rb")
14
10
 
15
11
  puts "Verifying that all automatic LSP requests execute successfully. This may take a while..."
@@ -6,6 +6,13 @@
6
6
  # composed bundle
7
7
  # !!!!!!!
8
8
 
9
+ $stdin.sync = true
10
+ $stdout.sync = true
11
+ $stderr.sync = true
12
+ $stdin.binmode
13
+ $stdout.binmode
14
+ $stderr.binmode
15
+
9
16
  setup_error = nil
10
17
  install_error = nil
11
18
  reboot = false
@@ -28,7 +35,6 @@ else
28
35
  # Read the initialize request before even starting the server. We need to do this to figure out the workspace URI.
29
36
  # Editors are not required to spawn the language server process on the same directory as the workspace URI, so we need
30
37
  # to ensure that we're setting up the bundle in the right place
31
- $stdin.binmode
32
38
  headers = $stdin.gets("\r\n\r\n")
33
39
  content_length = headers[/Content-Length: (\d+)/i, 1].to_i
34
40
  $stdin.read(content_length)
@@ -125,11 +131,8 @@ end
125
131
  # Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
126
132
  # configured the load path using the version of the Ruby LSP present in the composed bundle. Do not push any Ruby LSP
127
133
  # paths into the load path manually or we may end up requiring the wrong version of the gem
128
- require "ruby_lsp/load_sorbet"
129
134
  require "ruby_lsp/internal"
130
135
 
131
- T::Utils.run_all_sig_blocks
132
-
133
136
  if ARGV.include?("--debug")
134
137
  if ["x64-mingw-ucrt", "x64-mingw32"].include?(RUBY_PLATFORM)
135
138
  $stderr.puts "Debugging is not supported on Windows"
@@ -143,22 +146,28 @@ if ARGV.include?("--debug")
143
146
  end
144
147
  end
145
148
 
146
- # Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
147
- $> = $stderr
148
-
149
149
  initialize_request = JSON.parse(raw_initialize, symbolize_names: true) if raw_initialize
150
150
 
151
151
  begin
152
- RubyLsp::Server.new(
152
+ server = RubyLsp::Server.new(
153
153
  install_error: install_error,
154
154
  setup_error: setup_error,
155
155
  initialize_request: initialize_request,
156
- ).start
156
+ )
157
+
158
+ # Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
159
+ $> = $stderr
160
+
161
+ server.start
157
162
  rescue ArgumentError
158
163
  # If the launcher is booting an outdated version of the server, then the initializer doesn't accept a keyword splat
159
164
  # and we already read the initialize request from the stdin pipe. In this case, we need to process the initialize
160
165
  # request manually and then start the main loop
161
166
  server = RubyLsp::Server.new
167
+
168
+ # Ensure all output goes out stderr by default to allow puts/p/pp to work without specifying output device.
169
+ $> = $stderr
170
+
162
171
  server.process_message(initialize_request)
163
172
  server.start
164
173
  end
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "rubocop"
5
- require "sorbet-runtime"
6
5
 
7
6
  module RuboCop
8
7
  module Cop
@@ -2,7 +2,6 @@
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "rubocop"
5
- require "sorbet-runtime"
6
5
 
7
6
  module RuboCop
8
7
  module Cop
@@ -294,7 +294,9 @@ module RubyIndexer
294
294
 
295
295
  # @abstract
296
296
  #: -> Array[Signature]
297
- def signatures; end
297
+ def signatures
298
+ raise AbstractMethodInvokedError
299
+ end
298
300
 
299
301
  #: -> String
300
302
  def decorated_parameters
@@ -266,7 +266,7 @@ module RubyIndexer
266
266
  def constant_completion_candidates(name, nesting)
267
267
  # If we have a top level reference, then we don't need to include completions inside the current nesting
268
268
  if name.start_with?("::")
269
- return @entries_tree.search(name.delete_prefix("::")) #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]] # rubocop:disable Layout/LineLength
269
+ return @entries_tree.search(name.delete_prefix("::")) #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
270
270
  end
271
271
 
272
272
  # Otherwise, we have to include every possible constant the user might be referring to. This is essentially the
@@ -292,7 +292,7 @@ module RubyIndexer
292
292
  # Top level constants
293
293
  entries.concat(@entries_tree.search(name))
294
294
  entries.uniq!
295
- entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]] # rubocop:disable Layout/LineLength
295
+ entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
296
296
  end
297
297
 
298
298
  # Resolve a constant to its declaration based on its name and the nesting where the reference was found. Parameter
@@ -20,7 +20,7 @@ module RubyIndexer
20
20
  assert(uris.none? { |uri| uri.full_path.include?("test/fixtures") })
21
21
  assert(uris.none? { |uri| uri.full_path.include?(bundle_path.join("minitest-reporters").to_s) })
22
22
  assert(uris.none? { |uri| uri.full_path.include?(bundle_path.join("ansi").to_s) })
23
- assert(uris.any? { |uri| uri.full_path.include?(bundle_path.join("sorbet-runtime").to_s) })
23
+ assert(uris.any? { |uri| uri.full_path.include?(bundle_path.join("prism").to_s) })
24
24
  assert(uris.none? { |uri| uri.full_path == __FILE__ })
25
25
  end
26
26
 
@@ -232,8 +232,8 @@ module RubyIndexer
232
232
  signatures = entry.signatures
233
233
  assert_equal(2, signatures.length)
234
234
 
235
- # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ] # rubocop:disable Layout/LineLength
236
- # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]? # rubocop:disable Layout/LineLength
235
+ # def self.select: [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array) -> [ Array[X], Array[Y], Array[Z] ]
236
+ # | [X, Y, Z] (::Array[X & io]? read_array, ?::Array[Y & io]? write_array, ?::Array[Z & io]? error_array, Time::_Timeout? timeout) -> [ Array[X], Array[Y], Array[Z] ]?
237
237
 
238
238
  parameters = signatures[0]&.parameters #: as !nil
239
239
  assert_equal([:read_array, :write_array, :error_array], parameters.map(&:name))
@@ -56,7 +56,13 @@ module RubyLsp
56
56
  addon_files = Gem.find_files("ruby_lsp/**/addon.rb")
57
57
 
58
58
  if include_project_addons
59
- addon_files.concat(Dir.glob(File.join(global_state.workspace_path, "**", "ruby_lsp/**/addon.rb")))
59
+ project_addons = Dir.glob("#{global_state.workspace_path}/**/ruby_lsp/**/addon.rb")
60
+
61
+ # Ignore add-ons from dependencies if the bundle is stored inside the project. We already found those with
62
+ # `Gem.find_files`
63
+ bundle_path = Bundler.bundle_path.to_s
64
+ project_addons.reject! { |path| path.start_with?(bundle_path) }
65
+ addon_files.concat(project_addons)
60
66
  end
61
67
 
62
68
  errors = addon_files.filter_map do |addon_path|
@@ -176,24 +182,32 @@ module RubyLsp
176
182
  # reading information into memory or even spawning a separate process
177
183
  # @abstract
178
184
  #: (GlobalState, Thread::Queue) -> void
179
- def activate(global_state, outgoing_queue); end
185
+ def activate(global_state, outgoing_queue)
186
+ raise AbstractMethodInvokedError
187
+ end
180
188
 
181
- # Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
189
+ # Each add-on must implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
182
190
  # child process
183
191
  # @abstract
184
192
  #: -> void
185
- def deactivate; end
193
+ def deactivate
194
+ raise AbstractMethodInvokedError
195
+ end
186
196
 
187
197
  # Add-ons should override the `name` method to return the add-on name
188
198
  # @abstract
189
199
  #: -> String
190
- def name; end
200
+ def name
201
+ raise AbstractMethodInvokedError
202
+ end
191
203
 
192
204
  # Add-ons should override the `version` method to return a semantic version string representing the add-on's
193
205
  # version. This is used for compatibility checks
194
206
  # @abstract
195
207
  #: -> String
196
- def version; end
208
+ def version
209
+ raise AbstractMethodInvokedError
210
+ end
197
211
 
198
212
  # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
199
213
  # original request so that the response is delegated to the correct add-on and must override this method to handle
@@ -6,11 +6,11 @@ module RubyLsp
6
6
  class BaseServer
7
7
  #: (**untyped options) -> void
8
8
  def initialize(**options)
9
+ @reader = MessageReader.new(options[:reader] || $stdin) #: MessageReader
10
+ @writer = MessageWriter.new(options[:writer] || $stdout) #: MessageWriter
9
11
  @test_mode = options[:test_mode] #: bool?
10
12
  @setup_error = options[:setup_error] #: StandardError?
11
13
  @install_error = options[:install_error] #: StandardError?
12
- @writer = Transport::Stdio::Writer.new #: Transport::Stdio::Writer
13
- @reader = Transport::Stdio::Reader.new #: Transport::Stdio::Reader
14
14
  @incoming_queue = Thread::Queue.new #: Thread::Queue
15
15
  @outgoing_queue = Thread::Queue.new #: Thread::Queue
16
16
  @cancelled_requests = [] #: Array[Integer]
@@ -36,7 +36,7 @@ module RubyLsp
36
36
 
37
37
  #: -> void
38
38
  def start
39
- @reader.read do |message|
39
+ @reader.each_message do |message|
40
40
  method = message[:method]
41
41
 
42
42
  # We must parse the document under a mutex lock or else we might switch threads and accept text edits in the
@@ -128,11 +128,15 @@ module RubyLsp
128
128
 
129
129
  # @abstract
130
130
  #: (Hash[Symbol, untyped] message) -> void
131
- def process_message(message); end
131
+ def process_message(message)
132
+ raise AbstractMethodInvokedError
133
+ end
132
134
 
133
135
  # @abstract
134
136
  #: -> void
135
- def shutdown; end
137
+ def shutdown
138
+ raise AbstractMethodInvokedError
139
+ end
136
140
 
137
141
  #: (Integer id, String message, ?type: Integer) -> void
138
142
  def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
@@ -5,7 +5,7 @@ module RubyLsp
5
5
  # @abstract
6
6
  #: [ParseResultType]
7
7
  class Document
8
- extend T::Generic
8
+ class InvalidLocationError < StandardError; end
9
9
 
10
10
  # This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
11
11
  # This is the same number used by the TypeScript extension in VS Code
@@ -60,7 +60,9 @@ module RubyLsp
60
60
 
61
61
  # @abstract
62
62
  #: -> Symbol
63
- def language_id; end
63
+ def language_id
64
+ raise AbstractMethodInvokedError
65
+ end
64
66
 
65
67
  #: [T] (String request_name) { (Document[ParseResultType] document) -> T } -> T
66
68
  def cache_fetch(request_name, &block)
@@ -120,11 +122,15 @@ module RubyLsp
120
122
  # Returns `true` if the document was parsed and `false` if nothing needed parsing
121
123
  # @abstract
122
124
  #: -> bool
123
- def parse!; end
125
+ def parse!
126
+ raise AbstractMethodInvokedError
127
+ end
124
128
 
125
129
  # @abstract
126
130
  #: -> bool
127
- def syntax_error?; end
131
+ def syntax_error?
132
+ raise AbstractMethodInvokedError
133
+ end
128
134
 
129
135
  #: -> bool
130
136
  def past_expensive_limit?
@@ -145,7 +151,14 @@ module RubyLsp
145
151
 
146
152
  #: -> Scanner
147
153
  def create_scanner
148
- Scanner.new(@source, @encoding)
154
+ case @encoding
155
+ when Encoding::UTF_8
156
+ Utf8Scanner.new(@source)
157
+ when Encoding::UTF_16LE
158
+ Utf16Scanner.new(@source)
159
+ else
160
+ Utf32Scanner.new(@source)
161
+ end
149
162
  end
150
163
 
151
164
  # @abstract
@@ -163,81 +176,169 @@ module RubyLsp
163
176
  class Replace < Edit; end
164
177
  class Delete < Edit; end
165
178
 
179
+ # Parent class for all position scanners. Scanners are used to translate a position given by the editor into a
180
+ # string index that we can use to find the right place in the document source. The logic for finding the correct
181
+ # index depends on the encoding negotiated with the editor, so we have different subclasses for each encoding.
182
+ # See https://microsoft.github.io/language-server-protocol/specification/#positionEncodingKind for more information
183
+ # @abstract
166
184
  class Scanner
167
- extend T::Sig
168
-
169
185
  LINE_BREAK = 0x0A #: Integer
170
186
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
171
187
  SURROGATE_PAIR_START = 0xFFFF #: Integer
172
188
 
173
- #: (String source, Encoding encoding) -> void
174
- def initialize(source, encoding)
189
+ #: -> void
190
+ def initialize
175
191
  @current_line = 0 #: Integer
176
192
  @pos = 0 #: Integer
177
- @bytes_or_codepoints = encoding == Encoding::UTF_8 ? source.bytes : source.codepoints #: Array[Integer]
178
- @encoding = encoding
179
193
  end
180
194
 
181
- # Finds the character index inside the source string for a given line and column
195
+ # Finds the character index inside the source string for a given line and column. This method always returns the
196
+ # character index regardless of whether we are searching positions based on bytes, code units, or codepoints.
197
+ # @abstract
182
198
  #: (Hash[Symbol, untyped] position) -> Integer
183
199
  def find_char_position(position)
184
- # Find the character index for the beginning of the requested line
200
+ raise AbstractMethodInvokedError
201
+ end
202
+ end
203
+
204
+ # For the UTF-8 encoding, positions correspond to bytes
205
+ class Utf8Scanner < Scanner
206
+ #: (String source) -> void
207
+ def initialize(source)
208
+ super()
209
+ @bytes = source.bytes #: Array[Integer]
210
+ @character_length = 0 #: Integer
211
+ end
212
+
213
+ # @override
214
+ #: (Hash[Symbol, untyped] position) -> Integer
215
+ def find_char_position(position)
216
+ # Each group of bytes is a character. We advance based on the number of bytes to count how many full characters
217
+ # we have in the requested offset
185
218
  until @current_line == position[:line]
186
- @pos += 1 until LINE_BREAK == @bytes_or_codepoints[@pos]
219
+ byte = @bytes[@pos] #: Integer?
220
+ raise InvalidLocationError unless byte
221
+
222
+ until LINE_BREAK == byte
223
+ @pos += character_byte_length(byte)
224
+ @character_length += 1
225
+ byte = @bytes[@pos]
226
+ raise InvalidLocationError unless byte
227
+ end
228
+
187
229
  @pos += 1
230
+ @character_length += 1
188
231
  @current_line += 1
189
232
  end
190
233
 
191
- # For UTF-8, the code unit length is the same as bytes, but we want to return the character index
192
- requested_position = if @encoding == Encoding::UTF_8
193
- character_offset = 0
194
- i = @pos
195
-
196
- # Each group of bytes is a character. We advance based on the number of bytes to count how many full
197
- # characters we have in the requested offset
198
- while i < @pos + position[:character] && i < @bytes_or_codepoints.length
199
- byte = @bytes_or_codepoints[i] #: as !nil
200
- i += if byte < 0x80 # 1-byte character
201
- 1
202
- elsif byte < 0xE0 # 2-byte character
203
- 2
204
- elsif byte < 0xF0 # 3-byte character
205
- 3
206
- else # 4-byte character
207
- 4
208
- end
209
-
210
- character_offset += 1
234
+ # @character_length has the number of characters until the beginning of the line. We don't accumulate on it for
235
+ # the character part because locating the same position twice must return the same value
236
+ line_byte_offset = 0
237
+ line_characters = 0
238
+
239
+ while line_byte_offset < position[:character]
240
+ byte = @bytes[@pos + line_byte_offset] #: Integer?
241
+ raise InvalidLocationError unless byte
242
+
243
+ line_byte_offset += character_byte_length(byte)
244
+ line_characters += 1
245
+ end
246
+
247
+ @character_length + line_characters
248
+ end
249
+
250
+ private
251
+
252
+ #: (Integer) -> Integer
253
+ def character_byte_length(byte)
254
+ if byte < 0x80 # 1-byte character
255
+ 1
256
+ elsif byte < 0xE0 # 2-byte character
257
+ 2
258
+ elsif byte < 0xF0 # 3-byte character
259
+ 3
260
+ else # 4-byte character
261
+ 4
262
+ end
263
+ end
264
+ end
265
+
266
+ # For the UTF-16 encoding, positions correspond to UTF-16 code units, which count characters beyond the surrogate
267
+ # pair as length 2
268
+ class Utf16Scanner < Scanner
269
+ #: (String) -> void
270
+ def initialize(source)
271
+ super()
272
+ @codepoints = source.codepoints #: Array[Integer]
273
+ end
274
+
275
+ # @override
276
+ #: (Hash[Symbol, untyped] position) -> Integer
277
+ def find_char_position(position)
278
+ # Find the character index for the beginning of the requested line
279
+ until @current_line == position[:line]
280
+ codepoint = @codepoints[@pos] #: Integer?
281
+ raise InvalidLocationError unless codepoint
282
+
283
+ until LINE_BREAK == @codepoints[@pos]
284
+ @pos += 1
285
+ codepoint = @codepoints[@pos] #: Integer?
286
+ raise InvalidLocationError unless codepoint
211
287
  end
212
288
 
213
- @pos + character_offset
214
- else
215
- @pos + position[:character]
289
+ @pos += 1
290
+ @current_line += 1
216
291
  end
217
292
 
218
293
  # The final position is the beginning of the line plus the requested column. If the encoding is UTF-16, we also
219
294
  # need to adjust for surrogate pairs
220
- if @encoding == Encoding::UTF_16LE
221
- requested_position -= utf_16_character_position_correction(@pos, requested_position)
295
+ line_characters = 0
296
+ line_code_units = 0
297
+
298
+ while line_code_units < position[:character]
299
+ code_point = @codepoints[@pos + line_characters]
300
+ raise InvalidLocationError unless code_point
301
+
302
+ line_code_units += if code_point > SURROGATE_PAIR_START
303
+ 2 # Surrogate pair, so we skip the next code unit
304
+ else
305
+ 1 # Single code unit character
306
+ end
307
+
308
+ line_characters += 1
222
309
  end
223
310
 
224
- requested_position
311
+ @pos + line_characters
312
+ end
313
+ end
314
+
315
+ # For the UTF-32 encoding, positions correspond directly to codepoints
316
+ class Utf32Scanner < Scanner
317
+ #: (String) -> void
318
+ def initialize(source)
319
+ super()
320
+ @codepoints = source.codepoints #: Array[Integer]
225
321
  end
226
322
 
227
- # Subtract 1 for each character after 0xFFFF in the current line from the column position, so that we hit the
228
- # right character in the UTF-8 representation
229
- #: (Integer current_position, Integer requested_position) -> Integer
230
- def utf_16_character_position_correction(current_position, requested_position)
231
- utf16_unicode_correction = 0
323
+ # @override
324
+ #: (Hash[Symbol, untyped] position) -> Integer
325
+ def find_char_position(position)
326
+ # Find the character index for the beginning of the requested line
327
+ until @current_line == position[:line]
328
+ codepoint = @codepoints[@pos] #: Integer?
329
+ raise InvalidLocationError unless codepoint
232
330
 
233
- until current_position == requested_position
234
- codepoint = @bytes_or_codepoints[current_position]
235
- utf16_unicode_correction += 1 if codepoint && codepoint > SURROGATE_PAIR_START
331
+ until LINE_BREAK == @codepoints[@pos]
332
+ @pos += 1
333
+ codepoint = @codepoints[@pos] #: Integer?
334
+ raise InvalidLocationError unless codepoint
335
+ end
236
336
 
237
- current_position += 1
337
+ @pos += 1
338
+ @current_line += 1
238
339
  end
239
340
 
240
- utf16_unicode_correction
341
+ @pos + position[:character]
241
342
  end
242
343
  end
243
344
  end
@@ -56,6 +56,17 @@ module RubyLsp
56
56
  @enabled_feature_flags = {} #: Hash[Symbol, bool]
57
57
  @mutex = Mutex.new #: Mutex
58
58
  @telemetry_machine_id = nil #: String?
59
+ @feature_configuration = {
60
+ inlayHint: RequestConfig.new({
61
+ enableAll: false,
62
+ implicitRescue: false,
63
+ implicitHashValue: false,
64
+ }),
65
+ codeLens: RequestConfig.new({
66
+ enableAll: false,
67
+ enableTestCodeLens: true,
68
+ }),
69
+ } #: Hash[Symbol, RequestConfig]
59
70
  end
60
71
 
61
72
  #: [T] { -> T } -> T
@@ -175,9 +186,19 @@ module RubyLsp
175
186
  @enabled_feature_flags = enabled_flags if enabled_flags
176
187
 
177
188
  @telemetry_machine_id = options.dig(:initializationOptions, :telemetryMachineId)
189
+
190
+ options.dig(:initializationOptions, :featuresConfiguration)&.each do |feature_name, config|
191
+ @feature_configuration[feature_name]&.merge!(config)
192
+ end
193
+
178
194
  notifications
179
195
  end
180
196
 
197
+ #: (Symbol) -> RequestConfig?
198
+ def feature_configuration(feature_name)
199
+ @feature_configuration[feature_name]
200
+ end
201
+
181
202
  #: (Symbol flag) -> bool?
182
203
  def enabled_feature?(flag)
183
204
  @enabled_feature_flags[:all] || @enabled_feature_flags[flag]
@@ -6,8 +6,6 @@
6
6
  yarp_require_paths = Gem.loaded_specs["yarp"]&.full_require_paths
7
7
  $LOAD_PATH.delete_if { |path| yarp_require_paths.include?(path) } if yarp_require_paths
8
8
 
9
- require "sorbet-runtime"
10
-
11
9
  # Set Bundler's UI level to silent as soon as possible to prevent any prints to STDOUT
12
10
  require "bundler"
13
11
  Bundler.ui.level = :silent
@@ -7,6 +7,7 @@ module RubyLsp
7
7
  include Requests::Support::Common
8
8
 
9
9
  ALLOWED_TARGETS = [
10
+ Prism::BreakNode,
10
11
  Prism::CallNode,
11
12
  Prism::ConstantReadNode,
12
13
  Prism::ConstantWriteNode,
@@ -54,6 +55,7 @@ module RubyLsp
54
55
 
55
56
  dispatcher.register(
56
57
  self,
58
+ :on_break_node_enter,
57
59
  :on_constant_read_node_enter,
58
60
  :on_constant_write_node_enter,
59
61
  :on_constant_path_node_enter,
@@ -84,6 +86,11 @@ module RubyLsp
84
86
  )
85
87
  end
86
88
 
89
+ #: (Prism::BreakNode node) -> void
90
+ def on_break_node_enter(node)
91
+ handle_keyword_documentation(node.keyword)
92
+ end
93
+
87
94
  #: (Prism::StringNode node) -> void
88
95
  def on_string_node_enter(node)
89
96
  if @path && File.basename(@path) == GEMFILE_NAME
@@ -8,10 +8,12 @@ module RubyLsp
8
8
 
9
9
  RESCUE_STRING_LENGTH = "rescue".length #: Integer
10
10
 
11
- #: (ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint] response_builder, RequestConfig hints_configuration, Prism::Dispatcher dispatcher) -> void
12
- def initialize(response_builder, hints_configuration, dispatcher)
11
+ #: (GlobalState, ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint], Prism::Dispatcher) -> void
12
+ def initialize(global_state, response_builder, dispatcher)
13
13
  @response_builder = response_builder
14
- @hints_configuration = hints_configuration
14
+ @hints_configuration = ( # rubocop:disable Style/RedundantParentheses
15
+ global_state.feature_configuration(:inlayHint) #: as !nil
16
+ ) #: RequestConfig
15
17
 
16
18
  dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
17
19
  end