ruby-lsp 0.20.0 → 0.21.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 +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/exe/ruby-lsp +24 -3
- data/exe/ruby-lsp-launcher +127 -0
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +63 -12
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -2
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +21 -6
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -21
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +2 -2
- data/lib/ruby_indexer/test/enhancements_test.rb +51 -19
- data/lib/ruby_indexer/test/index_test.rb +91 -2
- data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
- data/lib/ruby_indexer/test/method_test.rb +26 -0
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
- data/lib/ruby_lsp/addon.rb +9 -2
- data/lib/ruby_lsp/base_server.rb +14 -5
- data/lib/ruby_lsp/client_capabilities.rb +60 -0
- data/lib/ruby_lsp/document.rb +1 -1
- data/lib/ruby_lsp/global_state.rb +20 -19
- data/lib/ruby_lsp/internal.rb +2 -0
- data/lib/ruby_lsp/listeners/completion.rb +62 -0
- data/lib/ruby_lsp/listeners/definition.rb +48 -13
- data/lib/ruby_lsp/listeners/hover.rb +52 -0
- data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/completion.rb +7 -1
- data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
- data/lib/ruby_lsp/requests/definition.rb +26 -11
- data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
- data/lib/ruby_lsp/requests/hover.rb +24 -6
- data/lib/ruby_lsp/requests/references.rb +2 -0
- data/lib/ruby_lsp/requests/rename.rb +3 -1
- data/lib/ruby_lsp/requests/request.rb +1 -1
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +11 -1
- data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
- data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
- data/lib/ruby_lsp/server.rb +54 -16
- data/lib/ruby_lsp/setup_bundler.rb +132 -24
- data/lib/ruby_lsp/utils.rb +8 -0
- metadata +8 -3
@@ -3,10 +3,14 @@
|
|
3
3
|
|
4
4
|
require "sorbet-runtime"
|
5
5
|
require "bundler"
|
6
|
+
require "bundler/cli"
|
7
|
+
require "bundler/cli/install"
|
8
|
+
require "bundler/cli/update"
|
6
9
|
require "fileutils"
|
7
10
|
require "pathname"
|
8
11
|
require "digest"
|
9
12
|
require "time"
|
13
|
+
require "uri"
|
10
14
|
|
11
15
|
# This file is a script that will configure a custom bundle for the Ruby LSP. The custom bundle allows developers to use
|
12
16
|
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to the
|
@@ -27,6 +31,8 @@ module RubyLsp
|
|
27
31
|
def initialize(project_path, **options)
|
28
32
|
@project_path = project_path
|
29
33
|
@branch = T.let(options[:branch], T.nilable(String))
|
34
|
+
@launcher = T.let(options[:launcher], T.nilable(T::Boolean))
|
35
|
+
patch_thor_to_print_progress_to_stderr! if @launcher
|
30
36
|
|
31
37
|
# Regular bundle paths
|
32
38
|
@gemfile = T.let(
|
@@ -47,8 +53,11 @@ module RubyLsp
|
|
47
53
|
@custom_lockfile = T.let(@custom_dir + (@lockfile&.basename || "Gemfile.lock"), Pathname)
|
48
54
|
@lockfile_hash_path = T.let(@custom_dir + "main_lockfile_hash", Pathname)
|
49
55
|
@last_updated_path = T.let(@custom_dir + "last_updated", Pathname)
|
56
|
+
@error_path = T.let(@custom_dir + "install_error", Pathname)
|
50
57
|
|
51
|
-
|
58
|
+
dependencies, bundler_version = load_dependencies
|
59
|
+
@dependencies = T.let(dependencies, T::Hash[String, T.untyped])
|
60
|
+
@bundler_version = T.let(bundler_version, T.nilable(Gem::Version))
|
52
61
|
@rails_app = T.let(rails_app?, T::Boolean)
|
53
62
|
@retry = T.let(false, T::Boolean)
|
54
63
|
end
|
@@ -57,7 +66,12 @@ module RubyLsp
|
|
57
66
|
# used for running the server
|
58
67
|
sig { returns(T::Hash[String, String]) }
|
59
68
|
def setup!
|
60
|
-
raise BundleNotLocked if @gemfile&.exist? && !@lockfile&.exist?
|
69
|
+
raise BundleNotLocked if !@launcher && @gemfile&.exist? && !@lockfile&.exist?
|
70
|
+
|
71
|
+
# Automatically create and ignore the .ruby-lsp folder for users
|
72
|
+
@custom_dir.mkpath unless @custom_dir.exist?
|
73
|
+
ignore_file = @custom_dir + ".gitignore"
|
74
|
+
ignore_file.write("*") unless ignore_file.exist?
|
61
75
|
|
62
76
|
# Do not set up a custom bundle if LSP dependencies are already in the Gemfile
|
63
77
|
if @dependencies["ruby-lsp"] &&
|
@@ -67,19 +81,9 @@ module RubyLsp
|
|
67
81
|
"Ruby LSP> Skipping custom bundle setup since LSP dependencies are already in #{@gemfile}",
|
68
82
|
)
|
69
83
|
|
70
|
-
# If the user decided to add `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) to their Gemfile after
|
71
|
-
# having already run the Ruby LSP, then we need to remove the `.ruby-lsp` folder, otherwise we will run `bundle
|
72
|
-
# install` for the top level and try to execute the Ruby LSP using the custom bundle, which will fail since the
|
73
|
-
# gems are not installed there
|
74
|
-
@custom_dir.rmtree if @custom_dir.exist?
|
75
84
|
return run_bundle_install
|
76
85
|
end
|
77
86
|
|
78
|
-
# Automatically create and ignore the .ruby-lsp folder for users
|
79
|
-
@custom_dir.mkpath unless @custom_dir.exist?
|
80
|
-
ignore_file = @custom_dir + ".gitignore"
|
81
|
-
ignore_file.write("*") unless ignore_file.exist?
|
82
|
-
|
83
87
|
write_custom_gemfile
|
84
88
|
|
85
89
|
unless @gemfile&.exist? && @lockfile&.exist?
|
@@ -109,17 +113,19 @@ module RubyLsp
|
|
109
113
|
def custom_bundle_dependencies
|
110
114
|
@custom_bundle_dependencies ||= T.let(
|
111
115
|
begin
|
116
|
+
original_bundle_gemfile = ENV["BUNDLE_GEMFILE"]
|
117
|
+
|
112
118
|
if @custom_lockfile.exist?
|
113
119
|
ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
|
114
120
|
Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
|
115
121
|
else
|
116
122
|
{}
|
117
123
|
end
|
124
|
+
ensure
|
125
|
+
ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
|
118
126
|
end,
|
119
127
|
T.nilable(T::Hash[String, T.untyped]),
|
120
128
|
)
|
121
|
-
ensure
|
122
|
-
ENV.delete("BUNDLE_GEMFILE")
|
123
129
|
end
|
124
130
|
|
125
131
|
sig { void }
|
@@ -132,7 +138,7 @@ module RubyLsp
|
|
132
138
|
|
133
139
|
# If there's a top level Gemfile, we want to evaluate from the custom bundle. We get the source from the top level
|
134
140
|
# Gemfile, so if there isn't one we need to add a default source
|
135
|
-
if @gemfile&.exist?
|
141
|
+
if @gemfile&.exist? && @lockfile&.exist?
|
136
142
|
parts << "eval_gemfile(File.expand_path(\"../#{@gemfile_name}\", __dir__))"
|
137
143
|
else
|
138
144
|
parts.unshift('source "https://rubygems.org"')
|
@@ -156,14 +162,15 @@ module RubyLsp
|
|
156
162
|
@custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
|
157
163
|
end
|
158
164
|
|
159
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
165
|
+
sig { returns([T::Hash[String, T.untyped], T.nilable(Gem::Version)]) }
|
160
166
|
def load_dependencies
|
161
|
-
return {} unless @lockfile&.exist?
|
167
|
+
return [{}, nil] unless @lockfile&.exist?
|
162
168
|
|
163
169
|
# We need to parse the Gemfile.lock manually here. If we try to do `bundler/setup` to use something more
|
164
170
|
# convenient, we may end up with issues when the globally installed `ruby-lsp` version mismatches the one included
|
165
171
|
# in the `Gemfile`
|
166
|
-
|
172
|
+
lockfile_parser = Bundler::LockfileParser.new(@lockfile.read)
|
173
|
+
dependencies = lockfile_parser.dependencies
|
167
174
|
|
168
175
|
# When working on a gem, the `ruby-lsp` might be listed as a dependency in the gemspec. We need to make sure we
|
169
176
|
# check those as well or else we may get version mismatch errors. Notice that bundler allows more than one
|
@@ -172,7 +179,7 @@ module RubyLsp
|
|
172
179
|
dependencies.merge!(Bundler.load_gemspec(path).dependencies.to_h { |dep| [dep.name, dep] })
|
173
180
|
end
|
174
181
|
|
175
|
-
dependencies
|
182
|
+
[dependencies, lockfile_parser.bundler_version]
|
176
183
|
end
|
177
184
|
|
178
185
|
sig { params(bundle_gemfile: T.nilable(Pathname)).returns(T::Hash[String, String]) }
|
@@ -188,6 +195,54 @@ module RubyLsp
|
|
188
195
|
env["BUNDLE_PATH"] = File.expand_path(env["BUNDLE_PATH"], @project_path)
|
189
196
|
end
|
190
197
|
|
198
|
+
return run_bundle_install_through_command(env) unless @launcher
|
199
|
+
|
200
|
+
# This same check happens conditionally when running through the command. For invoking the CLI directly, it's
|
201
|
+
# important that we ensure the Bundler version is set to avoid restarts
|
202
|
+
if @bundler_version
|
203
|
+
env["BUNDLER_VERSION"] = @bundler_version.to_s
|
204
|
+
install_bundler_if_needed
|
205
|
+
end
|
206
|
+
|
207
|
+
begin
|
208
|
+
run_bundle_install_directly(env)
|
209
|
+
# If no error occurred, then clear previous errors
|
210
|
+
@error_path.delete if @error_path.exist?
|
211
|
+
$stderr.puts("Ruby LSP> Composed bundle installation complete")
|
212
|
+
rescue => e
|
213
|
+
# Write the error object to a file so that we can read it from the parent process
|
214
|
+
@error_path.write(Marshal.dump(e))
|
215
|
+
end
|
216
|
+
|
217
|
+
env
|
218
|
+
end
|
219
|
+
|
220
|
+
sig { params(env: T::Hash[String, String]).returns(T::Hash[String, String]) }
|
221
|
+
def run_bundle_install_directly(env)
|
222
|
+
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
223
|
+
T.unsafe(ENV).merge!(env)
|
224
|
+
|
225
|
+
unless should_bundle_update?
|
226
|
+
Bundler::CLI::Install.new({}).run
|
227
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
228
|
+
return env
|
229
|
+
end
|
230
|
+
|
231
|
+
# Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
|
232
|
+
# source control changes
|
233
|
+
gems = ["ruby-lsp", "debug", "prism"].reject { |dep| @dependencies[dep] }
|
234
|
+
gems << "ruby-lsp-rails" if @rails_app && !@dependencies["ruby-lsp-rails"]
|
235
|
+
|
236
|
+
Bundler::CLI::Update.new({ conservative: true }, gems).run
|
237
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
238
|
+
@last_updated_path.write(Time.now.iso8601)
|
239
|
+
env
|
240
|
+
end
|
241
|
+
|
242
|
+
sig { params(env: T::Hash[String, String]).returns(T::Hash[String, String]) }
|
243
|
+
def run_bundle_install_through_command(env)
|
244
|
+
base_bundle = base_bundle_command(env)
|
245
|
+
|
191
246
|
# If `ruby-lsp` and `debug` (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't try
|
192
247
|
# to upgrade them or else we'll produce undesired source control changes. If the custom bundle was just created
|
193
248
|
# and any of `ruby-lsp`, `ruby-lsp-rails` or `debug` weren't a part of the Gemfile, then we need to run `bundle
|
@@ -196,13 +251,13 @@ module RubyLsp
|
|
196
251
|
|
197
252
|
# When not updating, we run `(bundle check || bundle install)`
|
198
253
|
# When updating, we run `((bundle check && bundle update ruby-lsp debug) || bundle install)`
|
199
|
-
command = +"(
|
254
|
+
command = +"(#{base_bundle} check"
|
200
255
|
|
201
256
|
if should_bundle_update?
|
202
257
|
# If any of `ruby-lsp`, `ruby-lsp-rails` or `debug` are not in the Gemfile, try to update them to the latest
|
203
258
|
# version
|
204
259
|
command.prepend("(")
|
205
|
-
command << " &&
|
260
|
+
command << " && #{base_bundle} update "
|
206
261
|
command << "ruby-lsp " unless @dependencies["ruby-lsp"]
|
207
262
|
command << "debug " unless @dependencies["debug"]
|
208
263
|
command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
|
@@ -212,7 +267,7 @@ module RubyLsp
|
|
212
267
|
@last_updated_path.write(Time.now.iso8601)
|
213
268
|
end
|
214
269
|
|
215
|
-
command << " ||
|
270
|
+
command << " || #{base_bundle} install) "
|
216
271
|
|
217
272
|
# Redirect stdout to stderr to prevent going into an infinite loop. The extension might confuse stdout output with
|
218
273
|
# responses
|
@@ -225,7 +280,7 @@ module RubyLsp
|
|
225
280
|
# Try to run the bundle install or update command. If that fails, it normally means that the custom lockfile is in
|
226
281
|
# a bad state that no longer reflects the top level one. In that case, we can remove the whole directory, try
|
227
282
|
# another time and give up if it fails again
|
228
|
-
if !system(env, command) && !@retry && @
|
283
|
+
if !system(env, command) && !@retry && @custom_gemfile.exist?
|
229
284
|
@retry = true
|
230
285
|
@custom_dir.rmtree
|
231
286
|
$stderr.puts("Ruby LSP> Running bundle install failed. Trying to re-generate the custom bundle from scratch")
|
@@ -259,6 +314,15 @@ module RubyLsp
|
|
259
314
|
end
|
260
315
|
end
|
261
316
|
|
317
|
+
sig { void }
|
318
|
+
def install_bundler_if_needed
|
319
|
+
# Try to find the bundler version specified in the lockfile in installed gems. If not found, install it
|
320
|
+
requirement = Gem::Requirement.new(@bundler_version.to_s)
|
321
|
+
return if Gem::Specification.any? { |s| s.name == "bundler" && requirement =~ s.version }
|
322
|
+
|
323
|
+
Gem.install("bundler", @bundler_version.to_s)
|
324
|
+
end
|
325
|
+
|
262
326
|
sig { returns(T::Boolean) }
|
263
327
|
def should_bundle_update?
|
264
328
|
# If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
|
@@ -290,7 +354,7 @@ module RubyLsp
|
|
290
354
|
|
291
355
|
# We should only apply the correction if the remote is a relative path. It might also be a URI, like
|
292
356
|
# `https://rubygems.org` or an absolute path, in which case we shouldn't do anything
|
293
|
-
if path
|
357
|
+
if path && !URI(path).scheme
|
294
358
|
"remote: #{File.expand_path(path, T.must(@gemfile).dirname)}"
|
295
359
|
else
|
296
360
|
match
|
@@ -309,5 +373,49 @@ module RubyLsp
|
|
309
373
|
|
310
374
|
/class .* < (::)?Rails::Application/.match?(application_contents)
|
311
375
|
end
|
376
|
+
|
377
|
+
# Returns the base bundle command we should use for this project, which will be:
|
378
|
+
# - `bundle` if there's no locked Bundler version and no `bin/bundle` binstub in the $PATH
|
379
|
+
# - `bundle _<version>_` if there's a locked Bundler version
|
380
|
+
# - `bin/bundle` if there's a `bin/bundle` binstub in the $PATH
|
381
|
+
sig { params(env: T::Hash[String, String]).returns(String) }
|
382
|
+
def base_bundle_command(env)
|
383
|
+
path_parts = if Gem.win_platform?
|
384
|
+
ENV["Path"] || ENV["PATH"] || ENV["path"] || ""
|
385
|
+
else
|
386
|
+
ENV["PATH"] || ""
|
387
|
+
end.split(File::PATH_SEPARATOR)
|
388
|
+
|
389
|
+
bin_dir = File.expand_path("bin", @project_path)
|
390
|
+
bundle_binstub = File.join(@project_path, "bin", "bundle")
|
391
|
+
|
392
|
+
if File.exist?(bundle_binstub) && path_parts.any? { |path| File.expand_path(path, @project_path) == bin_dir }
|
393
|
+
return bundle_binstub
|
394
|
+
end
|
395
|
+
|
396
|
+
if @bundler_version
|
397
|
+
env["BUNDLER_VERSION"] = @bundler_version.to_s
|
398
|
+
install_bundler_if_needed
|
399
|
+
return "bundle _#{@bundler_version}_"
|
400
|
+
end
|
401
|
+
|
402
|
+
"bundle"
|
403
|
+
end
|
404
|
+
|
405
|
+
sig { void }
|
406
|
+
def patch_thor_to_print_progress_to_stderr!
|
407
|
+
return unless defined?(Bundler::Thor::Shell::Basic)
|
408
|
+
|
409
|
+
Bundler::Thor::Shell::Basic.prepend(Module.new do
|
410
|
+
extend T::Sig
|
411
|
+
|
412
|
+
sig { returns(IO) }
|
413
|
+
def stdout
|
414
|
+
$stderr
|
415
|
+
end
|
416
|
+
end)
|
417
|
+
|
418
|
+
Bundler.ui.level = :info
|
419
|
+
end
|
312
420
|
end
|
313
421
|
end
|
data/lib/ruby_lsp/utils.rb
CHANGED
@@ -79,6 +79,14 @@ module RubyLsp
|
|
79
79
|
params: Interface::LogMessageParams.new(type: type, message: message),
|
80
80
|
)
|
81
81
|
end
|
82
|
+
|
83
|
+
sig { params(data: T::Hash[Symbol, T.untyped]).returns(Notification) }
|
84
|
+
def telemetry(data)
|
85
|
+
new(
|
86
|
+
method: "telemetry/event",
|
87
|
+
params: data,
|
88
|
+
)
|
89
|
+
end
|
82
90
|
end
|
83
91
|
|
84
92
|
extend T::Sig
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-10-
|
11
|
+
date: 2024-10-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: language_server-protocol
|
@@ -84,6 +84,7 @@ email:
|
|
84
84
|
executables:
|
85
85
|
- ruby-lsp
|
86
86
|
- ruby-lsp-check
|
87
|
+
- ruby-lsp-launcher
|
87
88
|
extensions: []
|
88
89
|
extra_rdoc_files: []
|
89
90
|
files:
|
@@ -92,6 +93,7 @@ files:
|
|
92
93
|
- VERSION
|
93
94
|
- exe/ruby-lsp
|
94
95
|
- exe/ruby-lsp-check
|
96
|
+
- exe/ruby-lsp-launcher
|
95
97
|
- lib/core_ext/uri.rb
|
96
98
|
- lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb
|
97
99
|
- lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb
|
@@ -121,6 +123,7 @@ files:
|
|
121
123
|
- lib/ruby_indexer/test/test_case.rb
|
122
124
|
- lib/ruby_lsp/addon.rb
|
123
125
|
- lib/ruby_lsp/base_server.rb
|
126
|
+
- lib/ruby_lsp/client_capabilities.rb
|
124
127
|
- lib/ruby_lsp/document.rb
|
125
128
|
- lib/ruby_lsp/erb_document.rb
|
126
129
|
- lib/ruby_lsp/global_state.rb
|
@@ -183,6 +186,8 @@ files:
|
|
183
186
|
- lib/ruby_lsp/response_builders/signature_help.rb
|
184
187
|
- lib/ruby_lsp/ruby_document.rb
|
185
188
|
- lib/ruby_lsp/scope.rb
|
189
|
+
- lib/ruby_lsp/scripts/compose_bundle.rb
|
190
|
+
- lib/ruby_lsp/scripts/compose_bundle_windows.rb
|
186
191
|
- lib/ruby_lsp/server.rb
|
187
192
|
- lib/ruby_lsp/setup_bundler.rb
|
188
193
|
- lib/ruby_lsp/static_docs.rb
|
@@ -212,7 +217,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
217
|
- !ruby/object:Gem::Version
|
213
218
|
version: '0'
|
214
219
|
requirements: []
|
215
|
-
rubygems_version: 3.5.
|
220
|
+
rubygems_version: 3.5.22
|
216
221
|
signing_key:
|
217
222
|
specification_version: 4
|
218
223
|
summary: An opinionated language server for Ruby
|