ruby-lsp 0.23.11 → 0.26.6
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 +10 -4
- data/exe/ruby-lsp-check +0 -4
- data/exe/ruby-lsp-launcher +46 -22
- data/exe/ruby-lsp-test-exec +6 -0
- data/lib/rubocop/cop/ruby_lsp/use_language_server_aliases.rb +1 -2
- data/lib/rubocop/cop/ruby_lsp/use_register_with_handler_method.rb +3 -6
- data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +85 -118
- data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +159 -183
- data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +10 -14
- data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +130 -253
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +189 -285
- data/lib/ruby_indexer/lib/ruby_indexer/location.rb +4 -27
- data/lib/ruby_indexer/lib/ruby_indexer/prefix_tree.rb +23 -27
- data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +31 -59
- data/lib/ruby_indexer/lib/ruby_indexer/reference_finder.rb +58 -68
- data/lib/ruby_indexer/lib/ruby_indexer/uri.rb +17 -19
- data/lib/ruby_indexer/lib/ruby_indexer/visibility_scope.rb +7 -11
- data/lib/ruby_lsp/addon.rb +88 -86
- data/lib/ruby_lsp/base_server.rb +79 -65
- data/lib/ruby_lsp/client_capabilities.rb +16 -13
- data/lib/ruby_lsp/document.rb +205 -104
- data/lib/ruby_lsp/erb_document.rb +45 -47
- data/lib/ruby_lsp/global_state.rb +134 -86
- data/lib/ruby_lsp/internal.rb +8 -3
- data/lib/ruby_lsp/listeners/code_lens.rb +82 -89
- data/lib/ruby_lsp/listeners/completion.rb +81 -76
- data/lib/ruby_lsp/listeners/definition.rb +78 -72
- data/lib/ruby_lsp/listeners/document_highlight.rb +149 -151
- data/lib/ruby_lsp/listeners/document_link.rb +93 -86
- data/lib/ruby_lsp/listeners/document_symbol.rb +38 -52
- data/lib/ruby_lsp/listeners/folding_ranges.rb +40 -43
- data/lib/ruby_lsp/listeners/hover.rb +109 -117
- data/lib/ruby_lsp/listeners/inlay_hints.rb +8 -13
- data/lib/ruby_lsp/listeners/semantic_highlighting.rb +54 -56
- data/lib/ruby_lsp/listeners/signature_help.rb +12 -27
- data/lib/ruby_lsp/listeners/spec_style.rb +231 -0
- data/lib/ruby_lsp/listeners/test_discovery.rb +107 -0
- data/lib/ruby_lsp/listeners/test_style.rb +207 -95
- data/lib/ruby_lsp/node_context.rb +12 -39
- data/lib/ruby_lsp/rbs_document.rb +10 -11
- data/lib/ruby_lsp/requests/code_action_resolve.rb +92 -66
- data/lib/ruby_lsp/requests/code_actions.rb +34 -31
- data/lib/ruby_lsp/requests/code_lens.rb +31 -21
- data/lib/ruby_lsp/requests/completion.rb +8 -21
- data/lib/ruby_lsp/requests/completion_resolve.rb +14 -12
- data/lib/ruby_lsp/requests/definition.rb +8 -20
- data/lib/ruby_lsp/requests/diagnostics.rb +8 -11
- data/lib/ruby_lsp/requests/discover_tests.rb +20 -7
- data/lib/ruby_lsp/requests/document_highlight.rb +6 -16
- data/lib/ruby_lsp/requests/document_link.rb +6 -17
- data/lib/ruby_lsp/requests/document_symbol.rb +5 -8
- data/lib/ruby_lsp/requests/folding_ranges.rb +7 -15
- data/lib/ruby_lsp/requests/formatting.rb +6 -9
- data/lib/ruby_lsp/requests/go_to_relevant_file.rb +139 -0
- data/lib/ruby_lsp/requests/hover.rb +12 -25
- data/lib/ruby_lsp/requests/inlay_hints.rb +8 -19
- data/lib/ruby_lsp/requests/on_type_formatting.rb +32 -40
- data/lib/ruby_lsp/requests/prepare_rename.rb +5 -10
- data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +5 -15
- data/lib/ruby_lsp/requests/range_formatting.rb +5 -6
- data/lib/ruby_lsp/requests/references.rb +17 -57
- data/lib/ruby_lsp/requests/rename.rb +27 -51
- data/lib/ruby_lsp/requests/request.rb +13 -25
- data/lib/ruby_lsp/requests/selection_ranges.rb +7 -7
- data/lib/ruby_lsp/requests/semantic_highlighting.rb +16 -35
- data/lib/ruby_lsp/requests/show_syntax_tree.rb +7 -8
- data/lib/ruby_lsp/requests/signature_help.rb +9 -27
- data/lib/ruby_lsp/requests/support/annotation.rb +4 -10
- data/lib/ruby_lsp/requests/support/common.rb +23 -61
- data/lib/ruby_lsp/requests/support/formatter.rb +16 -15
- data/lib/ruby_lsp/requests/support/package_url.rb +414 -0
- data/lib/ruby_lsp/requests/support/rubocop_diagnostic.rb +27 -35
- data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +13 -16
- data/lib/ruby_lsp/requests/support/rubocop_runner.rb +34 -36
- data/lib/ruby_lsp/requests/support/selection_range.rb +1 -3
- data/lib/ruby_lsp/requests/support/sorbet.rb +29 -38
- data/lib/ruby_lsp/requests/support/source_uri.rb +22 -33
- data/lib/ruby_lsp/requests/support/syntax_tree_formatter.rb +12 -19
- data/lib/ruby_lsp/requests/support/test_item.rb +16 -14
- data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +5 -6
- data/lib/ruby_lsp/requests/workspace_symbol.rb +24 -16
- data/lib/ruby_lsp/response_builders/collection_response_builder.rb +6 -9
- data/lib/ruby_lsp/response_builders/document_symbol.rb +15 -21
- data/lib/ruby_lsp/response_builders/hover.rb +12 -18
- data/lib/ruby_lsp/response_builders/response_builder.rb +6 -7
- data/lib/ruby_lsp/response_builders/semantic_highlighting.rb +62 -91
- data/lib/ruby_lsp/response_builders/signature_help.rb +6 -8
- data/lib/ruby_lsp/response_builders/test_collection.rb +35 -13
- data/lib/ruby_lsp/ruby_document.rb +32 -98
- data/lib/ruby_lsp/scope.rb +7 -11
- data/lib/ruby_lsp/scripts/compose_bundle.rb +7 -5
- data/lib/ruby_lsp/server.rb +305 -198
- data/lib/ruby_lsp/setup_bundler.rb +160 -97
- data/lib/ruby_lsp/static_docs.rb +12 -7
- data/lib/ruby_lsp/store.rb +21 -49
- data/lib/ruby_lsp/test_helper.rb +3 -16
- data/lib/ruby_lsp/test_reporters/lsp_reporter.rb +241 -0
- data/lib/ruby_lsp/test_reporters/minitest_reporter.rb +145 -0
- data/lib/ruby_lsp/test_reporters/test_unit_reporter.rb +92 -0
- data/lib/ruby_lsp/type_inferrer.rb +13 -14
- data/lib/ruby_lsp/utils.rb +138 -93
- data/static_docs/break.md +103 -0
- metadata +15 -34
- data/lib/ruby_indexer/test/class_variables_test.rb +0 -140
- data/lib/ruby_indexer/test/classes_and_modules_test.rb +0 -745
- data/lib/ruby_indexer/test/configuration_test.rb +0 -239
- data/lib/ruby_indexer/test/constant_test.rb +0 -402
- data/lib/ruby_indexer/test/enhancements_test.rb +0 -325
- data/lib/ruby_indexer/test/global_variable_test.rb +0 -49
- data/lib/ruby_indexer/test/index_test.rb +0 -2186
- data/lib/ruby_indexer/test/instance_variables_test.rb +0 -240
- data/lib/ruby_indexer/test/method_test.rb +0 -947
- data/lib/ruby_indexer/test/prefix_tree_test.rb +0 -150
- data/lib/ruby_indexer/test/rbs_indexer_test.rb +0 -386
- data/lib/ruby_indexer/test/reference_finder_test.rb +0 -330
- data/lib/ruby_indexer/test/test_case.rb +0 -51
- data/lib/ruby_indexer/test/uri_test.rb +0 -72
- data/lib/ruby_lsp/load_sorbet.rb +0 -62
|
@@ -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"
|
|
@@ -12,63 +11,70 @@ require "digest"
|
|
|
12
11
|
require "time"
|
|
13
12
|
require "uri"
|
|
14
13
|
|
|
15
|
-
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
|
16
|
-
# the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
|
17
|
-
# exact locked versions of dependencies.
|
|
14
|
+
# This file is a script that will configure a composed bundle for the Ruby LSP. The composed bundle allows developers to
|
|
15
|
+
# use the Ruby LSP without including the gem in their application's Gemfile while at the same time giving us access to
|
|
16
|
+
# the exact locked versions of dependencies.
|
|
18
17
|
|
|
19
18
|
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
|
|
|
28
|
-
|
|
25
|
+
module ThorPatch
|
|
26
|
+
#: -> IO
|
|
27
|
+
def stdout
|
|
28
|
+
$stderr
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
FOUR_HOURS = 4 * 60 * 60 #: Integer
|
|
29
33
|
|
|
30
|
-
|
|
34
|
+
# Gems that should be kept up to date in the composed bundle. When updating, any of these gems that are not
|
|
35
|
+
# already in the user's Gemfile will be updated together.
|
|
36
|
+
GEMS_TO_UPDATE = ["ruby-lsp", "debug", "prism", "rbs"].freeze #: Array[String]
|
|
37
|
+
|
|
38
|
+
#: (String project_path, **untyped options) -> void
|
|
31
39
|
def initialize(project_path, **options)
|
|
32
40
|
@project_path = project_path
|
|
33
|
-
@branch =
|
|
34
|
-
@launcher =
|
|
35
|
-
|
|
41
|
+
@branch = options[:branch] #: String?
|
|
42
|
+
@launcher = options[:launcher] #: bool?
|
|
43
|
+
force_output_to_stderr! if @launcher
|
|
36
44
|
|
|
37
45
|
# Regular bundle paths
|
|
38
|
-
@gemfile =
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
T.nilable(Pathname),
|
|
45
|
-
)
|
|
46
|
-
@lockfile = T.let(@gemfile ? Bundler.default_lockfile : nil, T.nilable(Pathname))
|
|
46
|
+
@gemfile = begin
|
|
47
|
+
Bundler.default_gemfile
|
|
48
|
+
rescue Bundler::GemfileNotFound
|
|
49
|
+
nil
|
|
50
|
+
end #: Pathname?
|
|
51
|
+
@lockfile = @gemfile ? Bundler.default_lockfile : nil #: Pathname?
|
|
47
52
|
|
|
48
|
-
@gemfile_hash =
|
|
49
|
-
@lockfile_hash =
|
|
53
|
+
@gemfile_hash = @gemfile ? Digest::SHA256.hexdigest(@gemfile.read) : nil #: String?
|
|
54
|
+
@lockfile_hash = @lockfile&.exist? ? Digest::SHA256.hexdigest(@lockfile.read) : nil #: String?
|
|
50
55
|
|
|
51
|
-
@gemfile_name =
|
|
56
|
+
@gemfile_name = @gemfile&.basename&.to_s || "Gemfile" #: String
|
|
52
57
|
|
|
53
58
|
# Custom bundle paths
|
|
54
|
-
@custom_dir =
|
|
55
|
-
@custom_gemfile =
|
|
56
|
-
@custom_lockfile =
|
|
57
|
-
@lockfile_hash_path =
|
|
58
|
-
@last_updated_path =
|
|
59
|
-
@error_path =
|
|
60
|
-
@already_composed_path =
|
|
59
|
+
@custom_dir = Pathname.new(".ruby-lsp").expand_path(@project_path) #: Pathname
|
|
60
|
+
@custom_gemfile = @custom_dir + @gemfile_name #: Pathname
|
|
61
|
+
@custom_lockfile = @custom_dir + (@lockfile&.basename || "Gemfile.lock") #: Pathname
|
|
62
|
+
@lockfile_hash_path = @custom_dir + "main_lockfile_hash" #: Pathname
|
|
63
|
+
@last_updated_path = @custom_dir + "last_updated" #: Pathname
|
|
64
|
+
@error_path = @custom_dir + "install_error" #: Pathname
|
|
65
|
+
@already_composed_path = @custom_dir + "bundle_is_composed" #: Pathname
|
|
61
66
|
|
|
62
67
|
dependencies, bundler_version = load_dependencies
|
|
63
|
-
@dependencies =
|
|
64
|
-
@bundler_version =
|
|
65
|
-
@rails_app =
|
|
66
|
-
@retry =
|
|
68
|
+
@dependencies = dependencies #: Hash[String, untyped]
|
|
69
|
+
@bundler_version = bundler_version #: Gem::Version?
|
|
70
|
+
@rails_app = rails_app? #: bool
|
|
71
|
+
@retry = false #: bool
|
|
72
|
+
@needs_update_path = @custom_dir + "needs_update" #: Pathname
|
|
67
73
|
end
|
|
68
74
|
|
|
69
75
|
# Sets up the composed bundle and returns the `BUNDLE_GEMFILE`, `BUNDLE_PATH` and `BUNDLE_APP_CONFIG` that should be
|
|
70
76
|
# used for running the server
|
|
71
|
-
|
|
77
|
+
#: -> Hash[String, String]
|
|
72
78
|
def setup!
|
|
73
79
|
raise BundleNotLocked if !@launcher && @gemfile&.exist? && !@lockfile&.exist?
|
|
74
80
|
|
|
@@ -128,26 +134,23 @@ module RubyLsp
|
|
|
128
134
|
|
|
129
135
|
private
|
|
130
136
|
|
|
131
|
-
|
|
137
|
+
#: -> Hash[String, untyped]
|
|
132
138
|
def composed_bundle_dependencies
|
|
133
|
-
@composed_bundle_dependencies ||=
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
end,
|
|
146
|
-
T.nilable(T::Hash[String, T.untyped]),
|
|
147
|
-
)
|
|
139
|
+
@composed_bundle_dependencies ||= begin
|
|
140
|
+
original_bundle_gemfile = ENV["BUNDLE_GEMFILE"]
|
|
141
|
+
|
|
142
|
+
if @custom_lockfile.exist?
|
|
143
|
+
ENV["BUNDLE_GEMFILE"] = @custom_gemfile.to_s
|
|
144
|
+
Bundler::LockfileParser.new(@custom_lockfile.read).dependencies
|
|
145
|
+
else
|
|
146
|
+
{}
|
|
147
|
+
end
|
|
148
|
+
ensure
|
|
149
|
+
ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
|
|
150
|
+
end #: Hash[String, untyped]?
|
|
148
151
|
end
|
|
149
152
|
|
|
150
|
-
|
|
153
|
+
#: -> void
|
|
151
154
|
def write_custom_gemfile
|
|
152
155
|
parts = [
|
|
153
156
|
"# This custom gemfile is automatically generated by the Ruby LSP.",
|
|
@@ -188,7 +191,7 @@ module RubyLsp
|
|
|
188
191
|
@custom_gemfile.write(content) unless @custom_gemfile.exist? && @custom_gemfile.read == content
|
|
189
192
|
end
|
|
190
193
|
|
|
191
|
-
|
|
194
|
+
#: -> [Hash[String, untyped], Gem::Version?]
|
|
192
195
|
def load_dependencies
|
|
193
196
|
return [{}, nil] unless @lockfile&.exist?
|
|
194
197
|
|
|
@@ -208,7 +211,7 @@ module RubyLsp
|
|
|
208
211
|
[dependencies, lockfile_parser.bundler_version]
|
|
209
212
|
end
|
|
210
213
|
|
|
211
|
-
|
|
214
|
+
#: (?Pathname? bundle_gemfile) -> Hash[String, String]
|
|
212
215
|
def run_bundle_install(bundle_gemfile = @gemfile)
|
|
213
216
|
env = bundler_settings_as_env
|
|
214
217
|
env["BUNDLE_GEMFILE"] = bundle_gemfile.to_s
|
|
@@ -235,6 +238,15 @@ module RubyLsp
|
|
|
235
238
|
# If no error occurred, then clear previous errors
|
|
236
239
|
@error_path.delete if @error_path.exist?
|
|
237
240
|
$stderr.puts("Ruby LSP> Composed bundle installation complete")
|
|
241
|
+
rescue Errno::EPIPE, Bundler::HTTPError
|
|
242
|
+
# There are cases where we expect certain errors to happen occasionally, and we don't want to write them to
|
|
243
|
+
# a file, which would report to telemetry on the next launch.
|
|
244
|
+
#
|
|
245
|
+
# - The $stderr pipe might be closed by the client, for example when closing the editor during running bundle
|
|
246
|
+
# install. This situation may happen because, while running bundle install, the server is not yet ready to
|
|
247
|
+
# receive shutdown requests and we may continue doing work until the process is killed.
|
|
248
|
+
# - Bundler might also encounter a network error.
|
|
249
|
+
@error_path.delete if @error_path.exist?
|
|
238
250
|
rescue => e
|
|
239
251
|
# Write the error object to a file so that we can read it from the parent process
|
|
240
252
|
@error_path.write(Marshal.dump(e))
|
|
@@ -259,45 +271,90 @@ module RubyLsp
|
|
|
259
271
|
env
|
|
260
272
|
end
|
|
261
273
|
|
|
262
|
-
|
|
274
|
+
#: (Hash[String, String] env, ?force_install: bool) -> Hash[String, String]
|
|
263
275
|
def run_bundle_install_directly(env, force_install: false)
|
|
264
276
|
RubyVM::YJIT.enable if defined?(RubyVM::YJIT.enable)
|
|
265
277
|
|
|
266
|
-
# The
|
|
267
|
-
#
|
|
268
|
-
|
|
269
|
-
|
|
278
|
+
# The should_bundle_update? check needs to run on the original Bundler environment, but everything else (like
|
|
279
|
+
# updating or running install) requires the modified environment. Here we compute the check ahead of time and
|
|
280
|
+
# merge the environment to ensure correct results.
|
|
281
|
+
#
|
|
282
|
+
# The symptoms of having these operations in the wrong order is seeing unwanted modifications in the application's
|
|
283
|
+
# main lockfile because we accidentally run update on the main bundle instead of the composed one.
|
|
284
|
+
needs_update = should_bundle_update?
|
|
285
|
+
ENV.merge!(env)
|
|
286
|
+
|
|
287
|
+
return update(env) if @needs_update_path.exist?
|
|
288
|
+
|
|
289
|
+
FileUtils.touch(@needs_update_path) if needs_update
|
|
270
290
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
291
|
+
$stderr.puts("Ruby LSP> Checking if the composed bundle is satisfied...")
|
|
292
|
+
|
|
293
|
+
begin
|
|
294
|
+
missing_gems = bundle_check
|
|
295
|
+
rescue Errno::EPIPE, Bundler::HTTPError
|
|
296
|
+
# These are errors cases where we cannot recover
|
|
297
|
+
raise
|
|
298
|
+
rescue => e
|
|
299
|
+
# If anything fails with bundle check, try to bundle install
|
|
300
|
+
$stderr.puts("Ruby LSP> Running bundle install because #{e.message}")
|
|
301
|
+
bundle_install
|
|
274
302
|
return env
|
|
275
303
|
end
|
|
276
304
|
|
|
305
|
+
if missing_gems.empty?
|
|
306
|
+
$stderr.puts("Ruby LSP> Bundle already satisfied")
|
|
307
|
+
else
|
|
308
|
+
$stderr.puts(<<~MESSAGE)
|
|
309
|
+
Ruby LSP> Running bundle install because the following gems are not installed:
|
|
310
|
+
#{missing_gems.map { |g| "#{g.name}: #{g.version}" }.join("\n")}
|
|
311
|
+
MESSAGE
|
|
312
|
+
|
|
313
|
+
bundle_install
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
env
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Essentially the same as bundle check, but simplified
|
|
320
|
+
#: -> Array[Gem::Specification]
|
|
321
|
+
def bundle_check
|
|
322
|
+
definition = Bundler.definition
|
|
323
|
+
definition.validate_runtime!
|
|
324
|
+
definition.check!
|
|
325
|
+
definition.missing_specs
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
#: -> void
|
|
329
|
+
def bundle_install
|
|
330
|
+
Bundler::CLI::Install.new({ "no-cache" => true }).run
|
|
331
|
+
correct_relative_remote_paths if @custom_lockfile.exist?
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
#: (Hash[String, String]) -> Hash[String, String]
|
|
335
|
+
def update(env)
|
|
277
336
|
# Try to auto upgrade the gems we depend on, unless they are in the Gemfile as that would result in undesired
|
|
278
337
|
# source control changes
|
|
279
|
-
gems =
|
|
338
|
+
gems = GEMS_TO_UPDATE.reject { |dep| @dependencies[dep] }
|
|
280
339
|
gems << "ruby-lsp-rails" if @rails_app && !@dependencies["ruby-lsp-rails"]
|
|
281
340
|
|
|
282
341
|
Bundler::CLI::Update.new({ conservative: true }, gems).run
|
|
283
342
|
correct_relative_remote_paths if @custom_lockfile.exist?
|
|
343
|
+
@needs_update_path.delete
|
|
284
344
|
@last_updated_path.write(Time.now.iso8601)
|
|
285
345
|
env
|
|
286
|
-
rescue Bundler::GemNotFound, Bundler::GitError
|
|
287
|
-
# If a gem is not installed, skip the upgrade and try to install it with a single retry
|
|
288
|
-
@retry ? env : run_bundle_install_directly(env, force_install: true)
|
|
289
346
|
end
|
|
290
347
|
|
|
291
|
-
|
|
348
|
+
#: (Hash[String, String] env) -> Hash[String, String]
|
|
292
349
|
def run_bundle_install_through_command(env)
|
|
293
|
-
# If
|
|
294
|
-
# to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just
|
|
295
|
-
# and any of
|
|
296
|
-
#
|
|
297
|
-
#
|
|
350
|
+
# If the gems in GEMS_TO_UPDATE (and potentially `ruby-lsp-rails`) are already in the Gemfile, then we shouldn't
|
|
351
|
+
# try to upgrade them or else we'll produce undesired source control changes. If the composed bundle was just
|
|
352
|
+
# created and any of those gems weren't a part of the Gemfile, then we need to run `bundle install` for the first
|
|
353
|
+
# time to generate the Gemfile.lock with them included or else Bundler will complain that they're missing. We can
|
|
354
|
+
# only update if the custom `.ruby-lsp/Gemfile.lock` already exists and includes all gems
|
|
298
355
|
|
|
299
356
|
# When not updating, we run `(bundle check || bundle install)`
|
|
300
|
-
# When updating, we run `((bundle check && bundle update
|
|
357
|
+
# When updating, we run `((bundle check && bundle update <GEMS_TO_UPDATE>) || bundle install)`
|
|
301
358
|
bundler_path = File.join(Gem.default_bindir, "bundle")
|
|
302
359
|
base_command = (!Gem.win_platform? && File.exist?(bundler_path) ? "#{Gem.ruby} #{bundler_path}" : "bundle").dup
|
|
303
360
|
|
|
@@ -308,12 +365,11 @@ module RubyLsp
|
|
|
308
365
|
command = +"(#{base_command} check"
|
|
309
366
|
|
|
310
367
|
if should_bundle_update?
|
|
311
|
-
# If any of
|
|
312
|
-
# version
|
|
368
|
+
# If any of the gems in GEMS_TO_UPDATE (or `ruby-lsp-rails` for Rails apps) are not in the Gemfile, try to
|
|
369
|
+
# update them to the latest version
|
|
313
370
|
command.prepend("(")
|
|
314
371
|
command << " && #{base_command} update "
|
|
315
|
-
command << "
|
|
316
|
-
command << "debug " unless @dependencies["debug"]
|
|
372
|
+
GEMS_TO_UPDATE.each { |gem| command << "#{gem} " unless @dependencies[gem] }
|
|
317
373
|
command << "ruby-lsp-rails " if @rails_app && !@dependencies["ruby-lsp-rails"]
|
|
318
374
|
command.delete_suffix!(" ")
|
|
319
375
|
command << ")"
|
|
@@ -345,7 +401,7 @@ module RubyLsp
|
|
|
345
401
|
end
|
|
346
402
|
|
|
347
403
|
# Gather all Bundler settings (global and local) and return them as a hash that can be used as the environment
|
|
348
|
-
|
|
404
|
+
#: -> Hash[String, String]
|
|
349
405
|
def bundler_settings_as_env
|
|
350
406
|
local_config_path = File.join(@project_path, ".bundle")
|
|
351
407
|
|
|
@@ -357,10 +413,17 @@ module RubyLsp
|
|
|
357
413
|
Bundler::Settings.new
|
|
358
414
|
end
|
|
359
415
|
|
|
416
|
+
# List of Bundler settings that don't make sense for the composed bundle and are better controlled manually by the
|
|
417
|
+
# user
|
|
418
|
+
ignored_settings = ["bin", "cache_all", "cache_all_platforms"]
|
|
419
|
+
|
|
360
420
|
# Map all settings to their environment variable names with `key_for` and their values. For example, the if the
|
|
361
421
|
# setting name `e` is `path` with a value of `vendor/bundle`, then it will return `"BUNDLE_PATH" =>
|
|
362
422
|
# "vendor/bundle"`
|
|
363
|
-
settings
|
|
423
|
+
settings
|
|
424
|
+
.all
|
|
425
|
+
.reject { |setting| ignored_settings.include?(setting) }
|
|
426
|
+
.to_h do |e|
|
|
364
427
|
key = settings.key_for(e)
|
|
365
428
|
value = Array(settings[e]).join(":").tr(" ", ":")
|
|
366
429
|
|
|
@@ -368,7 +431,7 @@ module RubyLsp
|
|
|
368
431
|
end
|
|
369
432
|
end
|
|
370
433
|
|
|
371
|
-
|
|
434
|
+
#: -> void
|
|
372
435
|
def install_bundler_if_needed
|
|
373
436
|
# Try to find the bundler version specified in the lockfile in installed gems. If not found, install it
|
|
374
437
|
requirement = Gem::Requirement.new(@bundler_version.to_s)
|
|
@@ -377,7 +440,7 @@ module RubyLsp
|
|
|
377
440
|
Gem.install("bundler", @bundler_version.to_s)
|
|
378
441
|
end
|
|
379
442
|
|
|
380
|
-
|
|
443
|
+
#: -> bool
|
|
381
444
|
def should_bundle_update?
|
|
382
445
|
# If `ruby-lsp`, `ruby-lsp-rails` and `debug` are in the Gemfile, then we shouldn't try to upgrade them or else it
|
|
383
446
|
# will produce version control changes
|
|
@@ -400,16 +463,19 @@ module RubyLsp
|
|
|
400
463
|
|
|
401
464
|
# When a lockfile has remote references based on relative file paths, we need to ensure that they are pointing to
|
|
402
465
|
# the correct place since after copying the relative path is no longer valid
|
|
403
|
-
|
|
466
|
+
#: -> void
|
|
404
467
|
def correct_relative_remote_paths
|
|
405
468
|
content = @custom_lockfile.read
|
|
406
469
|
content.gsub!(/remote: (.*)/) do |match|
|
|
407
|
-
|
|
470
|
+
last_match = Regexp.last_match #: as !nil
|
|
471
|
+
path = last_match[1]
|
|
408
472
|
|
|
409
473
|
# We should only apply the correction if the remote is a relative path. It might also be a URI, like
|
|
410
474
|
# `https://rubygems.org` or an absolute path, in which case we shouldn't do anything
|
|
411
475
|
if path && !URI(path).scheme
|
|
412
|
-
|
|
476
|
+
bundle_dir = @gemfile #: as !nil
|
|
477
|
+
.dirname
|
|
478
|
+
"remote: #{File.expand_path(path, bundle_dir)}"
|
|
413
479
|
else
|
|
414
480
|
match
|
|
415
481
|
end
|
|
@@ -422,7 +488,7 @@ module RubyLsp
|
|
|
422
488
|
end
|
|
423
489
|
|
|
424
490
|
# Detects if the project is a Rails app by looking if the superclass of the main class is `Rails::Application`
|
|
425
|
-
|
|
491
|
+
#: -> bool
|
|
426
492
|
def rails_app?
|
|
427
493
|
config = Pathname.new("config/application.rb").expand_path
|
|
428
494
|
application_contents = config.read(external_encoding: Encoding::UTF_8) if config.exist?
|
|
@@ -431,20 +497,17 @@ module RubyLsp
|
|
|
431
497
|
/class .* < (::)?Rails::Application/.match?(application_contents)
|
|
432
498
|
end
|
|
433
499
|
|
|
434
|
-
|
|
435
|
-
def
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
extend T::Sig
|
|
500
|
+
#: -> void
|
|
501
|
+
def force_output_to_stderr!
|
|
502
|
+
# Bundler and RubyGems have different UI objects used for printing. We need to ensure that both are configured to
|
|
503
|
+
# print only to stderr or else they'll break the connection with the editor
|
|
504
|
+
Gem::DefaultUserInteraction.ui = Gem::StreamUI.new($stdin, $stderr, $stderr, false)
|
|
440
505
|
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
end
|
|
445
|
-
end)
|
|
506
|
+
ui = Bundler.ui
|
|
507
|
+
ui.output_stream = :stderr if ui.respond_to?(:output_stream=)
|
|
508
|
+
ui.level = :info
|
|
446
509
|
|
|
447
|
-
Bundler.
|
|
510
|
+
Bundler::Thor::Shell::Basic.prepend(ThorPatch) if defined?(Bundler::Thor::Shell::Basic)
|
|
448
511
|
end
|
|
449
512
|
end
|
|
450
513
|
end
|
data/lib/ruby_lsp/static_docs.rb
CHANGED
|
@@ -3,13 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
5
|
# The path to the `static_docs` directory, where we keep long-form static documentation
|
|
6
|
-
STATIC_DOCS_PATH =
|
|
6
|
+
STATIC_DOCS_PATH = File.join(
|
|
7
|
+
File.dirname(
|
|
8
|
+
File.dirname(
|
|
9
|
+
__dir__, #: as !nil
|
|
10
|
+
),
|
|
11
|
+
),
|
|
12
|
+
"static_docs",
|
|
13
|
+
) #: String
|
|
7
14
|
|
|
8
15
|
# A map of keyword => short documentation to be displayed on hover or completion
|
|
9
|
-
KEYWORD_DOCS =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
T::Hash[String, String],
|
|
14
|
-
)
|
|
16
|
+
KEYWORD_DOCS = {
|
|
17
|
+
"break" => "Terminates the execution of a block or loop",
|
|
18
|
+
"yield" => "Invokes the passed block with the given arguments",
|
|
19
|
+
}.freeze #: Hash[String, String]
|
|
15
20
|
end
|
data/lib/ruby_lsp/store.rb
CHANGED
|
@@ -3,34 +3,19 @@
|
|
|
3
3
|
|
|
4
4
|
module RubyLsp
|
|
5
5
|
class Store
|
|
6
|
-
extend T::Sig
|
|
7
|
-
|
|
8
6
|
class NonExistingDocumentError < StandardError; end
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
attr_accessor :features_configuration
|
|
12
|
-
|
|
13
|
-
sig { returns(String) }
|
|
8
|
+
#: String
|
|
14
9
|
attr_accessor :client_name
|
|
15
10
|
|
|
16
|
-
|
|
11
|
+
#: (GlobalState global_state) -> void
|
|
17
12
|
def initialize(global_state)
|
|
18
13
|
@global_state = global_state
|
|
19
|
-
@state =
|
|
20
|
-
@
|
|
21
|
-
{
|
|
22
|
-
inlayHint: RequestConfig.new({
|
|
23
|
-
enableAll: false,
|
|
24
|
-
implicitRescue: false,
|
|
25
|
-
implicitHashValue: false,
|
|
26
|
-
}),
|
|
27
|
-
},
|
|
28
|
-
T::Hash[Symbol, RequestConfig],
|
|
29
|
-
)
|
|
30
|
-
@client_name = T.let("Unknown", String)
|
|
14
|
+
@state = {} #: Hash[String, Document[untyped]]
|
|
15
|
+
@client_name = "Unknown" #: String
|
|
31
16
|
end
|
|
32
17
|
|
|
33
|
-
|
|
18
|
+
#: (URI::Generic uri) -> Document[untyped]
|
|
34
19
|
def get(uri)
|
|
35
20
|
document = @state[uri.to_s]
|
|
36
21
|
return document unless document.nil?
|
|
@@ -43,78 +28,65 @@ module RubyLsp
|
|
|
43
28
|
ext = File.extname(path)
|
|
44
29
|
language_id = case ext
|
|
45
30
|
when ".erb", ".rhtml"
|
|
46
|
-
|
|
31
|
+
:erb
|
|
47
32
|
when ".rbs"
|
|
48
|
-
|
|
33
|
+
:rbs
|
|
49
34
|
else
|
|
50
|
-
|
|
35
|
+
:ruby
|
|
51
36
|
end
|
|
52
37
|
|
|
53
38
|
set(uri: uri, source: File.binread(path), version: 0, language_id: language_id)
|
|
54
|
-
|
|
39
|
+
@state[uri.to_s] #: as !nil
|
|
55
40
|
rescue Errno::ENOENT
|
|
56
41
|
raise NonExistingDocumentError, uri.to_s
|
|
57
42
|
end
|
|
58
43
|
|
|
59
|
-
|
|
60
|
-
params(
|
|
61
|
-
uri: URI::Generic,
|
|
62
|
-
source: String,
|
|
63
|
-
version: Integer,
|
|
64
|
-
language_id: Document::LanguageId,
|
|
65
|
-
).returns(Document[T.untyped])
|
|
66
|
-
end
|
|
44
|
+
#: (uri: URI::Generic, source: String, version: Integer, language_id: Symbol) -> Document[untyped]
|
|
67
45
|
def set(uri:, source:, version:, language_id:)
|
|
68
46
|
@state[uri.to_s] = case language_id
|
|
69
|
-
when
|
|
47
|
+
when :erb
|
|
70
48
|
ERBDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
|
71
|
-
when
|
|
49
|
+
when :rbs
|
|
72
50
|
RBSDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
|
73
51
|
else
|
|
74
52
|
RubyDocument.new(source: source, version: version, uri: uri, global_state: @global_state)
|
|
75
53
|
end
|
|
76
54
|
end
|
|
77
55
|
|
|
78
|
-
|
|
56
|
+
#: (uri: URI::Generic, edits: Array[Hash[Symbol, untyped]], version: Integer) -> void
|
|
79
57
|
def push_edits(uri:, edits:, version:)
|
|
80
|
-
|
|
58
|
+
@state[uri.to_s] #: as !nil
|
|
59
|
+
.push_edits(edits, version: version)
|
|
81
60
|
end
|
|
82
61
|
|
|
83
|
-
|
|
62
|
+
#: -> void
|
|
84
63
|
def clear
|
|
85
64
|
@state.clear
|
|
86
65
|
end
|
|
87
66
|
|
|
88
|
-
|
|
67
|
+
#: -> bool
|
|
89
68
|
def empty?
|
|
90
69
|
@state.empty?
|
|
91
70
|
end
|
|
92
71
|
|
|
93
|
-
|
|
72
|
+
#: (URI::Generic uri) -> void
|
|
94
73
|
def delete(uri)
|
|
95
74
|
@state.delete(uri.to_s)
|
|
96
75
|
end
|
|
97
76
|
|
|
98
|
-
|
|
77
|
+
#: (URI::Generic uri) -> bool
|
|
99
78
|
def key?(uri)
|
|
100
79
|
@state.key?(uri.to_s)
|
|
101
80
|
end
|
|
102
81
|
|
|
103
|
-
|
|
82
|
+
#: { (String uri, Document[untyped] document) -> void } -> void
|
|
104
83
|
def each(&block)
|
|
105
84
|
@state.each do |uri, document|
|
|
106
85
|
block.call(uri, document)
|
|
107
86
|
end
|
|
108
87
|
end
|
|
109
88
|
|
|
110
|
-
|
|
111
|
-
type_parameters(:T)
|
|
112
|
-
.params(
|
|
113
|
-
uri: URI::Generic,
|
|
114
|
-
request_name: String,
|
|
115
|
-
block: T.proc.params(document: Document[T.untyped]).returns(T.type_parameter(:T)),
|
|
116
|
-
).returns(T.type_parameter(:T))
|
|
117
|
-
end
|
|
89
|
+
#: [T] (URI::Generic uri, String request_name) { (Document[untyped] document) -> T } -> T
|
|
118
90
|
def cache_fetch(uri, request_name, &block)
|
|
119
91
|
get(uri).cache_fetch(request_name, &block)
|
|
120
92
|
end
|
data/lib/ruby_lsp/test_helper.rb
CHANGED
|
@@ -4,24 +4,11 @@
|
|
|
4
4
|
# NOTE: This module is intended to be used by addons for writing their own tests, so keep that in mind if changing.
|
|
5
5
|
|
|
6
6
|
module RubyLsp
|
|
7
|
+
# @requires_ancestor: Kernel
|
|
7
8
|
module TestHelper
|
|
8
9
|
class TestError < StandardError; end
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
extend T::Helpers
|
|
12
|
-
|
|
13
|
-
requires_ancestor { Kernel }
|
|
14
|
-
|
|
15
|
-
sig do
|
|
16
|
-
type_parameters(:T)
|
|
17
|
-
.params(
|
|
18
|
-
source: T.nilable(String),
|
|
19
|
-
uri: URI::Generic,
|
|
20
|
-
stub_no_typechecker: T::Boolean,
|
|
21
|
-
load_addons: T::Boolean,
|
|
22
|
-
block: T.proc.params(server: RubyLsp::Server, uri: URI::Generic).returns(T.type_parameter(:T)),
|
|
23
|
-
).returns(T.type_parameter(:T))
|
|
24
|
-
end
|
|
11
|
+
#: [T] (?String? source, ?URI::Generic uri, ?stub_no_typechecker: bool, ?load_addons: bool) { (RubyLsp::Server server, URI::Generic uri) -> T } -> T
|
|
25
12
|
def with_server(source = nil, uri = Kernel.URI("file:///fake.rb"), stub_no_typechecker: false, load_addons: true,
|
|
26
13
|
&block)
|
|
27
14
|
server = RubyLsp::Server.new(test_mode: true)
|
|
@@ -58,7 +45,7 @@ module RubyLsp
|
|
|
58
45
|
end
|
|
59
46
|
end
|
|
60
47
|
|
|
61
|
-
|
|
48
|
+
#: (RubyLsp::Server server) -> RubyLsp::Result
|
|
62
49
|
def pop_result(server)
|
|
63
50
|
result = server.pop_response
|
|
64
51
|
result = server.pop_response until result.is_a?(RubyLsp::Result) || result.is_a?(RubyLsp::Error)
|