ruby-lsp 0.24.2 → 0.26.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 (35) 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/exe/ruby-lsp-test-exec +3 -15
  7. data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +0 -1
  8. data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +0 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +4 -1
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +9 -0
  11. data/lib/ruby_indexer/test/configuration_test.rb +1 -2
  12. data/lib/ruby_indexer/test/index_test.rb +12 -0
  13. data/lib/ruby_lsp/addon.rb +32 -9
  14. data/lib/ruby_lsp/base_server.rb +31 -21
  15. data/lib/ruby_lsp/document.rb +17 -14
  16. data/lib/ruby_lsp/global_state.rb +21 -0
  17. data/lib/ruby_lsp/internal.rb +0 -2
  18. data/lib/ruby_lsp/listeners/completion.rb +5 -2
  19. data/lib/ruby_lsp/listeners/inlay_hints.rb +5 -3
  20. data/lib/ruby_lsp/listeners/test_style.rb +7 -5
  21. data/lib/ruby_lsp/requests/code_lens.rb +9 -3
  22. data/lib/ruby_lsp/requests/inlay_hints.rb +3 -3
  23. data/lib/ruby_lsp/requests/on_type_formatting.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 +8 -2
  28. data/lib/ruby_lsp/response_builders/response_builder.rb +3 -3
  29. data/lib/ruby_lsp/server.rb +83 -81
  30. data/lib/ruby_lsp/setup_bundler.rb +56 -22
  31. data/lib/ruby_lsp/store.rb +0 -10
  32. data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +13 -4
  33. data/lib/ruby_lsp/utils.rb +44 -5
  34. metadata +1 -16
  35. data/lib/ruby_lsp/load_sorbet.rb +0 -62
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11ac9bfd9d255db5feb97c0f1d88a98dd966572ca6d60b6007384169f1b060b2
4
- data.tar.gz: fbdc59855c67ba1741be48ccf49bd94359d5aa8aca2fb38f41e45174e4383988
3
+ metadata.gz: 3e8b51ca9a97aa0e855fbf1109f07733aae195eb25dba844f79840665b36ac9a
4
+ data.tar.gz: 79d5c3af7ba9fb03c2da197b4213a861e366b7be5a60e677ac6ca9ae8269dbea
5
5
  SHA512:
6
- metadata.gz: 0364d0f1049a03f9ade581ca98ddc209dd441f3e706561141ae1995903bf53e08e6d893f0650b31c7494a8333645030e1455023416b38a59002f177e3f024fcd
7
- data.tar.gz: d4298637f7c8ba3acc70859a922ed43e4daadb3ad8d8885eb7ddb1fbadbfe078e9afe6f4fc22e60f854d5d5a16b76b9013fa36f5e3cceb62b5032bf6b17f934d
6
+ metadata.gz: 8cb30f7427f02460320becb1b883c502ea0fbfd48f5148192e1339350e94836ed6ba825df4b4b0fe0ff8d32325d6196af6a26ef94d566e4e32ecd235d66ed821
7
+ data.tar.gz: 68d5712e3ae16ecaa602eeccadcab5108d89cce1f1a282b82bd3244b3111131442ee6feda63b1e7049027624c07be25e5f3ae39a00b9df25130f12f43c2dac80
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.24.2
1
+ 0.26.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
@@ -1,18 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- # Append to RUBYOPT the necessary requires to hook our custom test reporters so that results are automatically
5
- # reflected in the test explorer
6
- rubyopt = [
7
- *ENV["RUBYOPT"],
8
- "-rbundler/setup",
9
- "-r#{File.expand_path("../lib/ruby_lsp/test_reporters/minitest_reporter", __dir__)}",
10
- "-r#{File.expand_path("../lib/ruby_lsp/test_reporters/test_unit_reporter", __dir__)}",
11
- ].join(" ")
12
-
13
- # Replace this process with whatever command was passed. We only want to set RUBYOPT.
14
- # The way you use this executable is by prefixing your test command with `ruby-lsp-test-exec`, like so:
15
- # ruby-lsp-test-exec bundle exec ruby -Itest test/example_test.rb
16
- # ruby-lsp-test-exec bundle exec ruby -Ispec spec/example_spec.rb
17
- # ruby-lsp-test-exec bundle exec rspec spec/example_spec.rb
18
- exec({ "RUBYOPT" => rubyopt }, *ARGV)
4
+ # This executable will be removed thanks to the changes in https://github.com/Shopify/ruby-lsp/pull/3661.
5
+ # Remove this a few months after extension updates have rolled out
6
+ exec(*ARGV)
@@ -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
@@ -331,6 +333,7 @@ module RubyIndexer
331
333
  end
332
334
 
333
335
  class Method < Member
336
+ # @override
334
337
  #: Array[Signature]
335
338
  attr_reader :signatures
336
339
 
@@ -291,6 +291,15 @@ module RubyIndexer
291
291
 
292
292
  # Top level constants
293
293
  entries.concat(@entries_tree.search(name))
294
+
295
+ # Filter only constants since methods may have names that look like constants
296
+ entries.each do |definitions|
297
+ definitions.select! do |entry|
298
+ entry.is_a?(Entry::Constant) || entry.is_a?(Entry::ConstantAlias) ||
299
+ entry.is_a?(Entry::Namespace) || entry.is_a?(Entry::UnresolvedConstantAlias)
300
+ end
301
+ end
302
+
294
303
  entries.uniq!
295
304
  entries #: as Array[Array[Entry::Constant | Entry::ConstantAlias | Entry::Namespace | Entry::UnresolvedConstantAlias]]
296
305
  end
@@ -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
 
@@ -59,7 +59,6 @@ module RubyIndexer
59
59
 
60
60
  assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/pathname.rb")
61
61
  assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/ipaddr.rb")
62
- assert_includes(paths, "#{RbConfig::CONFIG["rubylibdir"]}/erb.rb")
63
62
  end
64
63
 
65
64
  def test_indexable_uris_includes_project_files
@@ -1983,6 +1983,18 @@ module RubyIndexer
1983
1983
  assert_equal(["XQRK"], result.map { |entries| entries.first&.name })
1984
1984
  end
1985
1985
 
1986
+ def test_constant_completion_does_not_confuse_uppercase_methods
1987
+ index(<<~RUBY)
1988
+ class Foo
1989
+ def Qux
1990
+ end
1991
+ end
1992
+ RUBY
1993
+
1994
+ candidates = @index.constant_completion_candidates("Q", [])
1995
+ refute_includes(candidates.flat_map { |entries| entries.map(&:name) }, "Qux")
1996
+ end
1997
+
1986
1998
  def test_constant_completion_candidates_for_empty_name
1987
1999
  index(<<~RUBY)
1988
2000
  module Foo
@@ -57,11 +57,26 @@ module RubyLsp
57
57
 
58
58
  if include_project_addons
59
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
60
  bundle_path = Bundler.bundle_path.to_s
64
- project_addons.reject! { |path| path.start_with?(bundle_path) }
61
+ gems_dir = Bundler.bundle_path.join("gems")
62
+
63
+ # Create an array of rejection glob patterns to ignore add-ons already discovered through Gem.find_files if
64
+ # they are also copied inside the workspace for whatever reason. We received reports of projects having gems
65
+ # installed in vendor/bundle despite BUNDLE_PATH pointing elsewhere. Without this mechanism, we will
66
+ # double-require the same add-on, potentially for different versions of the same gem, which leads to incorrect
67
+ # behavior
68
+ reject_glob_patterns = addon_files.map do |path|
69
+ relative_gem_path = Pathname.new(path).relative_path_from(gems_dir)
70
+ first_part, *parts = relative_gem_path.to_s.split(File::SEPARATOR)
71
+ first_part&.gsub!(/-([0-9.]+)$/, "*")
72
+ "**/#{first_part}/#{parts.join("/")}"
73
+ end
74
+
75
+ project_addons.reject! do |path|
76
+ path.start_with?(bundle_path) ||
77
+ reject_glob_patterns.any? { |pattern| File.fnmatch?(pattern, path, File::Constants::FNM_PATHNAME) }
78
+ end
79
+
65
80
  addon_files.concat(project_addons)
66
81
  end
67
82
 
@@ -182,24 +197,32 @@ module RubyLsp
182
197
  # reading information into memory or even spawning a separate process
183
198
  # @abstract
184
199
  #: (GlobalState, Thread::Queue) -> void
185
- def activate(global_state, outgoing_queue); end
200
+ def activate(global_state, outgoing_queue)
201
+ raise AbstractMethodInvokedError
202
+ end
186
203
 
187
- # Each add-on should implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
204
+ # Each add-on must implement `MyAddon#deactivate` and use to perform any clean up, like shutting down a
188
205
  # child process
189
206
  # @abstract
190
207
  #: -> void
191
- def deactivate; end
208
+ def deactivate
209
+ raise AbstractMethodInvokedError
210
+ end
192
211
 
193
212
  # Add-ons should override the `name` method to return the add-on name
194
213
  # @abstract
195
214
  #: -> String
196
- def name; end
215
+ def name
216
+ raise AbstractMethodInvokedError
217
+ end
197
218
 
198
219
  # Add-ons should override the `version` method to return a semantic version string representing the add-on's
199
220
  # version. This is used for compatibility checks
200
221
  # @abstract
201
222
  #: -> String
202
- def version; end
223
+ def version
224
+ raise AbstractMethodInvokedError
225
+ end
203
226
 
204
227
  # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
205
228
  # 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
@@ -83,8 +83,7 @@ module RubyLsp
83
83
  # The following requests need to be executed in the main thread directly to avoid concurrency issues. Everything
84
84
  # else is pushed into the incoming queue
85
85
  case method
86
- when "initialize", "initialized", "textDocument/didOpen", "textDocument/didClose", "textDocument/didChange",
87
- "rubyLsp/diagnoseState"
86
+ when "initialize", "initialized", "rubyLsp/diagnoseState"
88
87
  process_message(message)
89
88
  when "shutdown"
90
89
  @global_state.synchronize do
@@ -94,26 +93,13 @@ module RubyLsp
94
93
  @writer.write(Result.new(id: message[:id], response: nil).to_hash)
95
94
  end
96
95
  when "exit"
97
- @global_state.synchronize { exit(@incoming_queue.closed? ? 0 : 1) }
96
+ exit(@incoming_queue.closed? ? 0 : 1)
98
97
  else
99
98
  @incoming_queue << message
100
99
  end
101
100
  end
102
101
  end
103
102
 
104
- #: -> void
105
- def run_shutdown
106
- @incoming_queue.clear
107
- @outgoing_queue.clear
108
- @incoming_queue.close
109
- @outgoing_queue.close
110
- @cancelled_requests.clear
111
-
112
- @worker.terminate
113
- @outgoing_dispatcher.terminate
114
- @store.clear
115
- end
116
-
117
103
  # This method is only intended to be used in tests! Pops the latest response that would be sent to the client
118
104
  #: -> untyped
119
105
  def pop_response
@@ -128,11 +114,35 @@ module RubyLsp
128
114
 
129
115
  # @abstract
130
116
  #: (Hash[Symbol, untyped] message) -> void
131
- def process_message(message); end
117
+ def process_message(message)
118
+ raise AbstractMethodInvokedError
119
+ end
120
+
121
+ #: -> bool?
122
+ def test_mode?
123
+ @test_mode
124
+ end
125
+
126
+ #: -> void
127
+ def run_shutdown
128
+ @incoming_queue.clear
129
+ @outgoing_queue.clear
130
+ @incoming_queue.close
131
+ @outgoing_queue.close
132
+ @cancelled_requests.clear
133
+
134
+ @worker.terminate
135
+ @outgoing_dispatcher.terminate
136
+ @store.clear
137
+ end
138
+
139
+ private
132
140
 
133
141
  # @abstract
134
142
  #: -> void
135
- def shutdown; end
143
+ def shutdown
144
+ raise AbstractMethodInvokedError
145
+ end
136
146
 
137
147
  #: (Integer id, String message, ?type: Integer) -> void
138
148
  def fail_request_and_notify(id, message, type: Constant::MessageType::INFO)
@@ -5,9 +5,8 @@ module RubyLsp
5
5
  # @abstract
6
6
  #: [ParseResultType]
7
7
  class Document
8
- extend T::Generic
9
-
10
8
  class InvalidLocationError < StandardError; end
9
+
11
10
  # This maximum number of characters for providing expensive features, like semantic highlighting and diagnostics.
12
11
  # This is the same number used by the TypeScript extension in VS Code
13
12
  MAXIMUM_CHARACTERS_FOR_EXPENSIVE_FEATURES = 100_000
@@ -61,7 +60,9 @@ module RubyLsp
61
60
 
62
61
  # @abstract
63
62
  #: -> Symbol
64
- def language_id; end
63
+ def language_id
64
+ raise AbstractMethodInvokedError
65
+ end
65
66
 
66
67
  #: [T] (String request_name) { (Document[ParseResultType] document) -> T } -> T
67
68
  def cache_fetch(request_name, &block)
@@ -121,11 +122,15 @@ module RubyLsp
121
122
  # Returns `true` if the document was parsed and `false` if nothing needed parsing
122
123
  # @abstract
123
124
  #: -> bool
124
- def parse!; end
125
+ def parse!
126
+ raise AbstractMethodInvokedError
127
+ end
125
128
 
126
129
  # @abstract
127
130
  #: -> bool
128
- def syntax_error?; end
131
+ def syntax_error?
132
+ raise AbstractMethodInvokedError
133
+ end
129
134
 
130
135
  #: -> bool
131
136
  def past_expensive_limit?
@@ -134,12 +139,10 @@ module RubyLsp
134
139
 
135
140
  #: (Hash[Symbol, untyped] start_pos, ?Hash[Symbol, untyped]? end_pos) -> [Integer, Integer?]
136
141
  def find_index_by_position(start_pos, end_pos = nil)
137
- @global_state.synchronize do
138
- scanner = create_scanner
139
- start_index = scanner.find_char_position(start_pos)
140
- end_index = scanner.find_char_position(end_pos) if end_pos
141
- [start_index, end_index]
142
- end
142
+ scanner = create_scanner
143
+ start_index = scanner.find_char_position(start_pos)
144
+ end_index = scanner.find_char_position(end_pos) if end_pos
145
+ [start_index, end_index]
143
146
  end
144
147
 
145
148
  private
@@ -177,8 +180,6 @@ module RubyLsp
177
180
  # See https://microsoft.github.io/language-server-protocol/specification/#positionEncodingKind for more information
178
181
  # @abstract
179
182
  class Scanner
180
- extend T::Sig
181
-
182
183
  LINE_BREAK = 0x0A #: Integer
183
184
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
184
185
  SURROGATE_PAIR_START = 0xFFFF #: Integer
@@ -193,7 +194,9 @@ module RubyLsp
193
194
  # character index regardless of whether we are searching positions based on bytes, code units, or codepoints.
194
195
  # @abstract
195
196
  #: (Hash[Symbol, untyped] position) -> Integer
196
- def find_char_position(position); end
197
+ def find_char_position(position)
198
+ raise AbstractMethodInvokedError
199
+ end
197
200
  end
198
201
 
199
202
  # For the UTF-8 encoding, positions correspond to bytes
@@ -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
@@ -445,11 +445,14 @@ module RubyLsp
445
445
  return unless arguments_node
446
446
 
447
447
  path_node_to_complete = arguments_node.arguments.first
448
-
449
448
  return unless path_node_to_complete.is_a?(Prism::StringNode)
450
449
 
451
- origin_dir = Pathname.new(@uri.to_standardized_path).dirname
450
+ # If the file is unsaved (e.g.: untitled:Untitled-1), we can't provide relative completion as we don't know
451
+ # where the user intends to save it
452
+ full_path = @uri.to_standardized_path
453
+ return unless full_path
452
454
 
455
+ origin_dir = Pathname.new(full_path).dirname
453
456
  content = path_node_to_complete.content
454
457
  # if the path is not a directory, glob all possible next characters
455
458
  # for example ../somethi| (where | is the cursor position)
@@ -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
@@ -75,8 +75,9 @@ module RubyLsp
75
75
 
76
76
  unless full_files.empty?
77
77
  specs, tests = full_files.partition { |path| spec?(path) }
78
- commands << "#{BASE_COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
79
- commands << "#{BASE_COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
78
+
79
+ commands << "#{COMMAND} -Itest -e \"ARGV.each { |f| require f }\" #{tests.join(" ")}" if tests.any?
80
+ commands << "#{COMMAND} -Ispec -e \"ARGV.each { |f| require f }\" #{specs.join(" ")}" if specs.any?
80
81
  end
81
82
 
82
83
  commands
@@ -113,7 +114,7 @@ module RubyLsp
113
114
  end
114
115
 
115
116
  load_path = spec?(file_path) ? "-Ispec" : "-Itest"
116
- "#{BASE_COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
117
+ "#{COMMAND} #{load_path} #{file_path} --name \"/#{regex}/\""
117
118
  end
118
119
 
119
120
  #: (String, Hash[String, Hash[Symbol, untyped]]) -> Array[String]
@@ -124,7 +125,7 @@ module RubyLsp
124
125
  Shellwords.escape(TestDiscovery::DYNAMIC_REFERENCE_MARKER),
125
126
  ".*",
126
127
  )
127
- command = +"#{BASE_COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\""
128
+ command = +"#{COMMAND} -Itest #{file_path} --testcase \"/^#{group_regex}\\$/\""
128
129
 
129
130
  unless examples.empty?
130
131
  command << if examples.length == 1
@@ -143,13 +144,14 @@ module RubyLsp
143
144
 
144
145
  MINITEST_REPORTER_PATH = File.expand_path("../test_reporters/minitest_reporter.rb", __dir__) #: String
145
146
  TEST_UNIT_REPORTER_PATH = File.expand_path("../test_reporters/test_unit_reporter.rb", __dir__) #: String
146
- ACCESS_MODIFIERS = [:public, :private, :protected].freeze
147
147
  BASE_COMMAND = begin
148
148
  Bundler.with_original_env { Bundler.default_lockfile }
149
149
  "bundle exec ruby"
150
150
  rescue Bundler::GemfileNotFound
151
151
  "ruby"
152
152
  end #: String
153
+ COMMAND = "#{BASE_COMMAND} -r#{MINITEST_REPORTER_PATH} -r#{TEST_UNIT_REPORTER_PATH}" #: String
154
+ ACCESS_MODIFIERS = [:public, :private, :protected].freeze
153
155
 
154
156
  #: (ResponseBuilders::TestCollection, GlobalState, Prism::Dispatcher, URI::Generic) -> void
155
157
  def initialize(response_builder, global_state, dispatcher, uri)
@@ -27,10 +27,16 @@ module RubyLsp
27
27
  @document = document
28
28
  @test_builder = ResponseBuilders::TestCollection.new #: ResponseBuilders::TestCollection
29
29
  uri = document.uri
30
+ file_path = uri.full_path
31
+ code_lens_config = global_state.feature_configuration(:codeLens)
32
+ test_lenses_enabled = (!code_lens_config || code_lens_config.enabled?(:enableTestCodeLens)) &&
33
+ file_path && File.fnmatch?(TEST_PATH_PATTERN, file_path, File::FNM_PATHNAME | File::FNM_EXTGLOB)
30
34
 
31
35
  if global_state.enabled_feature?(:fullTestDiscovery)
32
- Listeners::TestStyle.new(@test_builder, global_state, dispatcher, uri)
33
- Listeners::SpecStyle.new(@test_builder, global_state, dispatcher, uri)
36
+ if test_lenses_enabled
37
+ Listeners::TestStyle.new(@test_builder, global_state, dispatcher, uri)
38
+ Listeners::SpecStyle.new(@test_builder, global_state, dispatcher, uri)
39
+ end
34
40
  else
35
41
  Listeners::CodeLens.new(@response_builder, global_state, uri, dispatcher)
36
42
  end
@@ -38,7 +44,7 @@ module RubyLsp
38
44
  Addon.addons.each do |addon|
39
45
  addon.create_code_lens_listener(@response_builder, uri, dispatcher)
40
46
 
41
- if global_state.enabled_feature?(:fullTestDiscovery)
47
+ if global_state.enabled_feature?(:fullTestDiscovery) && test_lenses_enabled
42
48
  addon.create_discover_tests_listener(@test_builder, dispatcher, uri)
43
49
  end
44
50
  end
@@ -16,13 +16,13 @@ module RubyLsp
16
16
  end
17
17
  end
18
18
 
19
- #: ((RubyDocument | ERBDocument) document, RequestConfig hints_configuration, Prism::Dispatcher dispatcher) -> void
20
- def initialize(document, hints_configuration, dispatcher)
19
+ #: (GlobalState, (RubyDocument | ERBDocument), Prism::Dispatcher) -> void
20
+ def initialize(global_state, document, dispatcher)
21
21
  super()
22
22
 
23
23
  @response_builder = ResponseBuilders::CollectionResponseBuilder
24
24
  .new #: ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint]
25
- Listeners::InlayHints.new(@response_builder, hints_configuration, dispatcher)
25
+ Listeners::InlayHints.new(global_state, @response_builder, dispatcher)
26
26
  end
27
27
 
28
28
  # @override