ruby-lsp 0.24.2 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11ac9bfd9d255db5feb97c0f1d88a98dd966572ca6d60b6007384169f1b060b2
4
- data.tar.gz: fbdc59855c67ba1741be48ccf49bd94359d5aa8aca2fb38f41e45174e4383988
3
+ metadata.gz: 7637d6f68616069c2f8c0d870f3daf31808a1cccd8ab32d2bdaf1f67274f9b59
4
+ data.tar.gz: 7ea1d75ad921adbf01f69a0f5d2fba905673311c13b0999d44c08bdf54737053
5
5
  SHA512:
6
- metadata.gz: 0364d0f1049a03f9ade581ca98ddc209dd441f3e706561141ae1995903bf53e08e6d893f0650b31c7494a8333645030e1455023416b38a59002f177e3f024fcd
7
- data.tar.gz: d4298637f7c8ba3acc70859a922ed43e4daadb3ad8d8885eb7ddb1fbadbfe078e9afe6f4fc22e60f854d5d5a16b76b9013fa36f5e3cceb62b5032bf6b17f934d
6
+ metadata.gz: a354535a3853b0826635408fc46dc85cb7cee97c4993012dd983af11b8917121962d97bddf0853406b54f619faa8c2383cccca9638b4a0624309fb1e4c838249
7
+ data.tar.gz: 9e49e2436c63d90fc373b52ac9f6b65e83964fe108abd0a18fd46b655cd99bd55c471dd198624bb8701890f62175af2c9fcb50fcfa2bbf69e706406b9fa1e472
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.24.2
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
@@ -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
 
@@ -182,24 +182,32 @@ module RubyLsp
182
182
  # reading information into memory or even spawning a separate process
183
183
  # @abstract
184
184
  #: (GlobalState, Thread::Queue) -> void
185
- def activate(global_state, outgoing_queue); end
185
+ def activate(global_state, outgoing_queue)
186
+ raise AbstractMethodInvokedError
187
+ end
186
188
 
187
- # 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
188
190
  # child process
189
191
  # @abstract
190
192
  #: -> void
191
- def deactivate; end
193
+ def deactivate
194
+ raise AbstractMethodInvokedError
195
+ end
192
196
 
193
197
  # Add-ons should override the `name` method to return the add-on name
194
198
  # @abstract
195
199
  #: -> String
196
- def name; end
200
+ def name
201
+ raise AbstractMethodInvokedError
202
+ end
197
203
 
198
204
  # Add-ons should override the `version` method to return a semantic version string representing the add-on's
199
205
  # version. This is used for compatibility checks
200
206
  # @abstract
201
207
  #: -> String
202
- def version; end
208
+ def version
209
+ raise AbstractMethodInvokedError
210
+ end
203
211
 
204
212
  # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
205
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,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?
@@ -177,8 +182,6 @@ module RubyLsp
177
182
  # See https://microsoft.github.io/language-server-protocol/specification/#positionEncodingKind for more information
178
183
  # @abstract
179
184
  class Scanner
180
- extend T::Sig
181
-
182
185
  LINE_BREAK = 0x0A #: Integer
183
186
  # After character 0xFFFF, UTF-16 considers characters to have length 2 and we have to account for that
184
187
  SURROGATE_PAIR_START = 0xFFFF #: Integer
@@ -193,7 +196,9 @@ module RubyLsp
193
196
  # character index regardless of whether we are searching positions based on bytes, code units, or codepoints.
194
197
  # @abstract
195
198
  #: (Hash[Symbol, untyped] position) -> Integer
196
- def find_char_position(position); end
199
+ def find_char_position(position)
200
+ raise AbstractMethodInvokedError
201
+ end
197
202
  end
198
203
 
199
204
  # 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
@@ -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
@@ -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
@@ -162,7 +162,7 @@ module RubyLsp
162
162
 
163
163
  #: (Integer line, Integer character) -> void
164
164
  def move_cursor_to(line, character)
165
- return unless /Visual Studio Code|Cursor|VSCodium/.match?(@client_name)
165
+ return unless /Visual Studio Code|Cursor|VSCodium|Windsurf/.match?(@client_name)
166
166
 
167
167
  position = Interface::Position.new(
168
168
  line: line,
@@ -9,7 +9,9 @@ module RubyLsp
9
9
 
10
10
  # @abstract
11
11
  #: -> untyped
12
- def perform; end
12
+ def perform
13
+ raise AbstractMethodInvokedError
14
+ end
13
15
 
14
16
  private
15
17
 
@@ -9,15 +9,21 @@ module RubyLsp
9
9
  module Formatter
10
10
  # @abstract
11
11
  #: (URI::Generic, RubyLsp::RubyDocument) -> String?
12
- def run_formatting(uri, document); end
12
+ def run_formatting(uri, document)
13
+ raise AbstractMethodInvokedError
14
+ end
13
15
 
14
16
  # @abstract
15
17
  #: (URI::Generic, String, Integer) -> String?
16
- def run_range_formatting(uri, source, base_indentation); end
18
+ def run_range_formatting(uri, source, base_indentation)
19
+ raise AbstractMethodInvokedError
20
+ end
17
21
 
18
22
  # @abstract
19
23
  #: (URI::Generic, RubyLsp::RubyDocument) -> Array[Interface::Diagnostic]?
20
- def run_diagnostic(uri, document); end
24
+ def run_diagnostic(uri, document)
25
+ raise AbstractMethodInvokedError
26
+ end
21
27
  end
22
28
  end
23
29
  end
@@ -24,7 +24,7 @@ module RubyLsp
24
24
  filename = uri.to_standardized_path || uri.opaque #: as !nil
25
25
 
26
26
  # Invoke RuboCop with just this file in `paths`
27
- @format_runner.run(filename, document.source)
27
+ @format_runner.run(filename, document.source, document.parse_result)
28
28
  @format_runner.formatted_source
29
29
  end
30
30
 
@@ -40,7 +40,7 @@ module RubyLsp
40
40
  def run_diagnostic(uri, document)
41
41
  filename = uri.to_standardized_path || uri.opaque #: as !nil
42
42
  # Invoke RuboCop with just this file in `paths`
43
- @diagnostic_runner.run(filename, document.source)
43
+ @diagnostic_runner.run(filename, document.source, document.parse_result)
44
44
 
45
45
  @diagnostic_runner.offenses.map do |offense|
46
46
  Support::RuboCopDiagnostic.new(
@@ -81,6 +81,7 @@ module RubyLsp
81
81
  @offenses = [] #: Array[::RuboCop::Cop::Offense]
82
82
  @errors = [] #: Array[String]
83
83
  @warnings = [] #: Array[String]
84
+ @prism_result = nil #: Prism::ParseLexResult?
84
85
 
85
86
  args += DEFAULT_ARGS
86
87
  rubocop_options = ::RuboCop::Options.new.parse(args).first
@@ -92,14 +93,15 @@ module RubyLsp
92
93
  super(rubocop_options, config_store)
93
94
  end
94
95
 
95
- #: (String path, String contents) -> void
96
- def run(path, contents)
96
+ #: (String, String, Prism::ParseLexResult) -> void
97
+ def run(path, contents, prism_result)
97
98
  # Clear Runner state between runs since we get a single instance of this class
98
99
  # on every use site.
99
100
  @errors = []
100
101
  @warnings = []
101
102
  @offenses = []
102
103
  @options[:stdin] = contents
104
+ @prism_result = prism_result
103
105
 
104
106
  super([path])
105
107
 
@@ -5,11 +5,11 @@ module RubyLsp
5
5
  module ResponseBuilders
6
6
  # @abstract
7
7
  class ResponseBuilder
8
- extend T::Generic
9
-
10
8
  # @abstract
11
9
  #: -> top
12
- def response; end
10
+ def response
11
+ raise AbstractMethodInvokedError
12
+ end
13
13
  end
14
14
  end
15
15
  end
@@ -94,7 +94,13 @@ module RubyLsp
94
94
  id: message[:id],
95
95
  response:
96
96
  Addon.addons.map do |addon|
97
- { name: addon.name, version: addon.version, errored: addon.error? }
97
+ version = begin
98
+ addon.version
99
+ rescue AbstractMethodInvokedError
100
+ nil
101
+ end
102
+
103
+ { name: addon.name, version: version, errored: addon.error? }
98
104
  end,
99
105
  ),
100
106
  )
@@ -203,10 +209,6 @@ module RubyLsp
203
209
 
204
210
  configured_features = options.dig(:initializationOptions, :enabledFeatures)
205
211
 
206
- configured_hints = options.dig(:initializationOptions, :featuresConfiguration, :inlayHint)
207
- @store.features_configuration.dig(:inlayHint) #: as !nil
208
- .configuration.merge!(configured_hints) if configured_hints
209
-
210
212
  enabled_features = case configured_features
211
213
  when Array
212
214
  # If the configuration is using an array, then absent features are disabled and present ones are enabled. That's
@@ -360,6 +362,7 @@ module RubyLsp
360
362
 
361
363
  perform_initial_indexing
362
364
  check_formatter_is_available
365
+ update_server if @global_state.enabled_feature?(:launcher)
363
366
  end
364
367
 
365
368
  #: (Hash[Symbol, untyped] message) -> void
@@ -473,8 +476,8 @@ module RubyLsp
473
476
  document_symbol = Requests::DocumentSymbol.new(uri, dispatcher)
474
477
  document_link = Requests::DocumentLink.new(uri, parse_result.comments, dispatcher)
475
478
  inlay_hint = Requests::InlayHints.new(
479
+ @global_state,
476
480
  document,
477
- @store.features_configuration.dig(:inlayHint), #: as !nil
478
481
  dispatcher,
479
482
  )
480
483
 
@@ -821,7 +824,6 @@ module RubyLsp
821
824
  return
822
825
  end
823
826
 
824
- hints_configurations = @store.features_configuration.dig(:inlayHint) #: as !nil
825
827
  dispatcher = Prism::Dispatcher.new
826
828
 
827
829
  unless document.is_a?(RubyDocument) || document.is_a?(ERBDocument)
@@ -829,7 +831,7 @@ module RubyLsp
829
831
  return
830
832
  end
831
833
 
832
- request = Requests::InlayHints.new(document, hints_configurations, dispatcher)
834
+ request = Requests::InlayHints.new(@global_state, document, dispatcher)
833
835
  dispatcher.visit(document.ast)
834
836
  result = request.perform
835
837
  document.cache_set("textDocument/inlayHint", result)
@@ -1405,8 +1407,38 @@ module RubyLsp
1405
1407
 
1406
1408
  # We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
1407
1409
  # we return the response back to the editor, then the restart is triggered
1410
+ launch_bundle_compose("Recomposing the bundle ahead of restart") do |stderr, status|
1411
+ if status&.exitstatus == 0
1412
+ # Create a signal for the restart that it can skip composing the bundle and launch directly
1413
+ FileUtils.touch(already_composed_path)
1414
+ send_message(Result.new(id: id, response: { success: true }))
1415
+ else
1416
+ # This special error code makes the extension avoid restarting in case we already know that the composed
1417
+ # bundle is not valid
1418
+ send_message(
1419
+ Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1420
+ )
1421
+ end
1422
+ end
1423
+ end
1424
+
1425
+ #: -> void
1426
+ def update_server
1427
+ return unless File.exist?(File.join(@global_state.workspace_path, ".ruby-lsp", "needs_update"))
1428
+
1429
+ launch_bundle_compose("Trying to update server") do |stderr, status|
1430
+ if status&.exitstatus == 0
1431
+ send_log_message("Successfully updated the server")
1432
+ else
1433
+ send_log_message("Failed to update server\n#{stderr}", type: Constant::MessageType::ERROR)
1434
+ end
1435
+ end
1436
+ end
1437
+
1438
+ #: (String) { (IO, Process::Status?) -> void } -> Thread
1439
+ def launch_bundle_compose(log, &block)
1408
1440
  Thread.new do
1409
- send_log_message("Recomposing the bundle ahead of restart")
1441
+ send_log_message(log)
1410
1442
 
1411
1443
  _stdout, stderr, status = Bundler.with_unbundled_env do
1412
1444
  Open3.capture3(
@@ -1421,17 +1453,7 @@ module RubyLsp
1421
1453
  )
1422
1454
  end
1423
1455
 
1424
- if status&.exitstatus == 0
1425
- # Create a signal for the restart that it can skip composing the bundle and launch directly
1426
- FileUtils.touch(already_composed_path)
1427
- send_message(Result.new(id: id, response: { success: true }))
1428
- else
1429
- # This special error code makes the extension avoid restarting in case we already know that the composed
1430
- # bundle is not valid
1431
- send_message(
1432
- Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
1433
- )
1434
- end
1456
+ block.call(stderr, status)
1435
1457
  end
1436
1458
  end
1437
1459
 
@@ -1,7 +1,6 @@
1
1
  # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
- require "sorbet-runtime"
5
4
  require "bundler"
6
5
  require "bundler/cli"
7
6
  require "bundler/cli/install"
@@ -20,11 +19,16 @@ Bundler.ui.level = :silent
20
19
 
21
20
  module RubyLsp
22
21
  class SetupBundler
23
- extend T::Sig
24
-
25
22
  class BundleNotLocked < StandardError; end
26
23
  class BundleInstallFailure < StandardError; end
27
24
 
25
+ module ThorPatch
26
+ #: -> IO
27
+ def stdout
28
+ $stderr
29
+ end
30
+ end
31
+
28
32
  FOUR_HOURS = 4 * 60 * 60 #: Integer
29
33
 
30
34
  #: (String project_path, **untyped options) -> void
@@ -61,6 +65,7 @@ module RubyLsp
61
65
  @bundler_version = bundler_version #: Gem::Version?
62
66
  @rails_app = rails_app? #: bool
63
67
  @retry = false #: bool
68
+ @needs_update_path = @custom_dir + "needs_update" #: Pathname
64
69
  end
65
70
 
66
71
  # Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
@@ -256,19 +261,50 @@ module RubyLsp
256
261
  #: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
257
262
  def run_bundle_install_directly(env, force_install: false)
258
263
  RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
264
+ return update(env) if @needs_update_path.exist?
259
265
 
260
266
  # The ENV can only be merged after checking if an update is required because we depend on the original value of
261
267
  # ENV["BUNDLE_GEMFILE"], which gets overridden after the merge
262
- should_update = should_bundle_update?
263
- ENV #: as untyped
264
- .merge!(env)
268
+ FileUtils.touch(@needs_update_path) if should_bundle_update?
269
+ ENV.merge!(env)
265
270
 
266
- unless should_update && !force_install
267
- Bundler::CLI::Install.new({ "no-cache" => true }).run
268
- correct_relative_remote_paths if @custom_lockfile.exist?
269
- return env
271
+ $stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
272
+ missing_gems = bundle_check
273
+
274
+ unless missing_gems.empty?
275
+ $stderr.puts(<<~MESSAGE)
276
+ Ruby LSP> Running bundle install because the following gems are not installed:
277
+ #{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
278
+ MESSAGE
279
+
280
+ bundle_install
270
281
  end
271
282
 
283
+ $stderr.puts("Ruby LSP> Bundle already satisfied")
284
+ env
285
+ rescue => e
286
+ $stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
287
+ bundle_install
288
+ env
289
+ end
290
+
291
+ # Essentially the same as bundle check, but simplified
292
+ #: -> Array[Gem::Specification]
293
+ def bundle_check
294
+ definition = Bundler.definition
295
+ definition.validate_runtime!
296
+ definition.check!
297
+ definition.missing_specs
298
+ end
299
+
300
+ #: -> void
301
+ def bundle_install
302
+ Bundler::CLI::Install.new({ "no-cache" => true }).run
303
+ correct_relative_remote_paths if @custom_lockfile.exist?
304
+ end
305
+
306
+ #: (Hash[String, String]) -> Hash[String, String]
307
+ def update(env)
272
308
  # Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
273
309
  # source control changes
274
310
  gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
@@ -276,11 +312,9 @@ module RubyLsp
276
312
 
277
313
  Bundler::CLI::Update.new({ conservative: true }, gems).run
278
314
  correct_relative_remote_paths if @custom_lockfile.exist?
315
+ @needs_update_path.delete
279
316
  @last_updated_path.write(Time.now.iso8601)
280
317
  env
281
- rescue Bundler::GemNotFound, Bundler::GitError
282
- # If a gem is not installed, skip the upgrade and try to install it with a single retry
283
- @retry ? env : run_bundle_install_directly(env, force_install: true)
284
318
  end
285
319
 
286
320
  #: (Hash[String, String] env) -> Hash[String, String]
@@ -440,15 +474,7 @@ module RubyLsp
440
474
  def patch_thor_to_print_progress_to_stderr!
441
475
  return unless defined?(Bundler::Thor::Shell::Basic)
442
476
 
443
- Bundler::Thor::Shell::Basic.prepend(Module.new do
444
- extend T::Sig
445
-
446
- sig { returns(IO) }
447
- def stdout
448
- $stderr
449
- end
450
- end)
451
-
477
+ Bundler::Thor::Shell::Basic.prepend(ThorPatch)
452
478
  Bundler.ui.level = :info
453
479
  end
454
480
  end
@@ -5,9 +5,6 @@ module RubyLsp
5
5
  class Store
6
6
  class NonExistingDocumentError < StandardError; end
7
7
 
8
- #: Hash[Symbol, RequestConfig]
9
- attr_accessor :features_configuration
10
-
11
8
  #: String
12
9
  attr_accessor :client_name
13
10
 
@@ -15,13 +12,6 @@ module RubyLsp
15
12
  def initialize(global_state)
16
13
  @global_state = global_state
17
14
  @state = {} #: Hash[String, Document[untyped]]
18
- @features_configuration = {
19
- inlayHint: RequestConfig.new({
20
- enableAll: false,
21
- implicitRescue: false,
22
- implicitHashValue: false,
23
- }),
24
- } #: Hash[Symbol, RequestConfig]
25
15
  @client_name = "Unknown" #: String
26
16
  end
27
17
 
@@ -12,6 +12,18 @@ module RubyLsp
12
12
  class LspReporter
13
13
  include Singleton
14
14
 
15
+ # https://code.visualstudio.com/api/references/vscode-api#Position
16
+ #: type position = { line: Integer, character: Integer }
17
+
18
+ # https://code.visualstudio.com/api/references/vscode-api#Range
19
+ #: type range = { start: position, end: position }
20
+
21
+ # https://code.visualstudio.com/api/references/vscode-api#BranchCoverage
22
+ #: type branch_coverage = { executed: Integer, label: String, location: range }
23
+
24
+ # https://code.visualstudio.com/api/references/vscode-api#StatementCoverage
25
+ #: type statement_coverage = { executed: Integer, location: position, branches: Array[branch_coverage] }
26
+
15
27
  #: bool
16
28
  attr_reader :invoked_shutdown
17
29
 
@@ -125,7 +137,7 @@ module RubyLsp
125
137
  # ["Foo", :bar, 6, 21, 6, 65] => 0
126
138
  # }
127
139
  # }
128
- #: -> Hash[String, StatementCoverage]
140
+ #: -> Hash[String, statement_coverage]
129
141
  def gather_coverage_results
130
142
  # Ignore coverage results inside dependencies
131
143
  bundle_path = Bundler.bundle_path.to_s
@@ -5,7 +5,6 @@ module RubyLsp
5
5
  # rubocop:disable RubyLsp/UseLanguageServerAliases
6
6
  Interface = LanguageServer::Protocol::Interface
7
7
  Constant = LanguageServer::Protocol::Constant
8
- Transport = LanguageServer::Protocol::Transport
9
8
  # rubocop:enable RubyLsp/UseLanguageServerAliases
10
9
 
11
10
  # Used to indicate that a request shouldn't return a response
@@ -20,6 +19,7 @@ module RubyLsp
20
19
  "Gemfile"
21
20
  end #: String
22
21
  GUESSED_TYPES_URL = "https://shopify.github.io/ruby-lsp/#guessed-types"
22
+ TEST_PATH_PATTERN = "**/{test,spec,features}/**/{*_test.rb,test_*.rb,*_spec.rb,*.feature}"
23
23
 
24
24
  # Request delegation for embedded languages is not yet standardized into the language server specification. Here we
25
25
  # use this custom error class as a way to return a signal to the client that the request should be delegated to the
@@ -31,6 +31,8 @@ module RubyLsp
31
31
  CODE = -32900
32
32
  end
33
33
 
34
+ class AbstractMethodInvokedError < StandardError; end
35
+
34
36
  BUNDLE_COMPOSE_FAILED_CODE = -33000
35
37
 
36
38
  # A notification to be sent to the client
@@ -50,7 +52,9 @@ module RubyLsp
50
52
 
51
53
  # @abstract
52
54
  #: -> Hash[Symbol, untyped]
53
- def to_hash; end
55
+ def to_hash
56
+ raise AbstractMethodInvokedError
57
+ end
54
58
  end
55
59
 
56
60
  class Notification < Message
@@ -243,9 +247,6 @@ module RubyLsp
243
247
 
244
248
  # A request configuration, to turn on/off features
245
249
  class RequestConfig
246
- #: Hash[Symbol, bool]
247
- attr_accessor :configuration
248
-
249
250
  #: (Hash[Symbol, bool] configuration) -> void
250
251
  def initialize(configuration)
251
252
  @configuration = configuration
@@ -255,6 +256,11 @@ module RubyLsp
255
256
  def enabled?(feature)
256
257
  @configuration[:enableAll] || @configuration[feature]
257
258
  end
259
+
260
+ #: (Hash[Symbol, bool]) -> void
261
+ def merge!(hash)
262
+ @configuration.merge!(hash)
263
+ end
258
264
  end
259
265
 
260
266
  class SorbetLevel
@@ -299,4 +305,37 @@ module RubyLsp
299
305
  #: -> bool
300
306
  def true_or_higher? = @level == :true || @level == :strict
301
307
  end
308
+
309
+ # Reads JSON RPC messages from the given IO in a loop
310
+ class MessageReader
311
+ #: (IO) -> void
312
+ def initialize(io)
313
+ @io = io
314
+ end
315
+
316
+ #: () { (Hash[Symbol, untyped]) -> void } -> void
317
+ def each_message(&block)
318
+ while (headers = @io.gets("\r\n\r\n"))
319
+ raw_message = @io.read(headers[/Content-Length: (\d+)/i, 1].to_i) #: as !nil
320
+ block.call(JSON.parse(raw_message, symbolize_names: true))
321
+ end
322
+ end
323
+ end
324
+
325
+ # Writes JSON RPC messages to the given IO
326
+ class MessageWriter
327
+ #: (IO) -> void
328
+ def initialize(io)
329
+ @io = io
330
+ end
331
+
332
+ #: (Hash[Symbol, untyped]) -> void
333
+ def write(message)
334
+ message[:jsonrpc] = "2.0"
335
+ json_message = message.to_json
336
+
337
+ @io.write("Content-Length: #{json_message.bytesize}\r\n\r\n#{json_message}")
338
+ @io.flush
339
+ end
340
+ end
302
341
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-lsp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.24.2
4
+ version: 0.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
@@ -63,20 +63,6 @@ dependencies:
63
63
  - - "<"
64
64
  - !ruby/object:Gem::Version
65
65
  version: '5'
66
- - !ruby/object:Gem::Dependency
67
- name: sorbet-runtime
68
- requirement: !ruby/object:Gem::Requirement
69
- requirements:
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- version: 0.5.10782
73
- type: :runtime
74
- prerelease: false
75
- version_requirements: !ruby/object:Gem::Requirement
76
- requirements:
77
- - - ">="
78
- - !ruby/object:Gem::Version
79
- version: 0.5.10782
80
66
  description: An opinionated language server for Ruby
81
67
  email:
82
68
  - ruby@shopify.com
@@ -145,7 +131,6 @@ files:
145
131
  - lib/ruby_lsp/listeners/spec_style.rb
146
132
  - lib/ruby_lsp/listeners/test_discovery.rb
147
133
  - lib/ruby_lsp/listeners/test_style.rb
148
- - lib/ruby_lsp/load_sorbet.rb
149
134
  - lib/ruby_lsp/node_context.rb
150
135
  - lib/ruby_lsp/rbs_document.rb
151
136
  - lib/ruby_lsp/requests/code_action_resolve.rb
@@ -1,62 +0,0 @@
1
- # typed: true
2
- # frozen_string_literal: true
3
-
4
- require "sorbet-runtime"
5
-
6
- begin
7
- T::Configuration.default_checked_level = :never
8
- # Suppresses call validation errors
9
- T::Configuration.call_validation_error_handler = ->(*arg) {}
10
- # Suppresses errors caused by T.cast, T.let, T.must, etc.
11
- T::Configuration.inline_type_error_handler = ->(*arg) {}
12
- # Suppresses errors caused by incorrect parameter ordering
13
- T::Configuration.sig_validation_error_handler = ->(*arg) {}
14
- rescue
15
- # Need this rescue so that if another gem has
16
- # already set the checked level by the time we
17
- # get to it, we don't fail outright.
18
- nil
19
- end
20
-
21
- module RubyLsp
22
- # No-op all inline type assertions defined in T
23
- module InlineTypeAssertions
24
- def absurd(value)
25
- value
26
- end
27
-
28
- def any(type_a, type_b, *types)
29
- T::Types::Union.new([type_a, type_b, *types])
30
- end
31
-
32
- def assert_type!(value, type, checked: true)
33
- value
34
- end
35
-
36
- def bind(value, type, checked: true)
37
- value
38
- end
39
-
40
- def cast(value, type, checked: true)
41
- value
42
- end
43
-
44
- def let(value, type, checked: true)
45
- value
46
- end
47
-
48
- def must(arg)
49
- arg
50
- end
51
-
52
- def nilable(type)
53
- T::Types::Union.new([type, T::Utils::Nilable::NIL_TYPE])
54
- end
55
-
56
- def unsafe(value)
57
- value
58
- end
59
-
60
- T.singleton_class.prepend(self)
61
- end
62
- end