ruby-lsp 0.20.0 → 0.21.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|