ruby-lsp 0.23.7 → 0.23.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/exe/ruby-lsp-launcher +2 -2
- data/lib/ruby_indexer/lib/ruby_indexer/index.rb +51 -6
- data/lib/ruby_indexer/test/index_test.rb +69 -0
- data/lib/ruby_lsp/internal.rb +1 -0
- data/lib/ruby_lsp/requests/on_type_formatting.rb +1 -1
- data/lib/ruby_lsp/server.rb +27 -6
- data/lib/ruby_lsp/setup_bundler.rb +1 -2
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 918079e906832b0289a053cd4bfe849422082c25bd0f4bdada25cab3a532005f
|
4
|
+
data.tar.gz: 7935b2bdc52a2bb83264376fb98f7ef8d3bfc3c5cb9dadd23642a1ebafbca013
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9fb084d322308bb2e69c2c0d5cfed2d275fe53f5df40aa2a49a848aab4c771858dfa1b5d5df913516c1f1c90ce5101b2f461865093f5e6787c79886cadc40bdb
|
7
|
+
data.tar.gz: 06206026d180e9d044edda0a83018ae31895cce3e6d91cb9ae4bdb3c365565a27881dd17ffd6d8861d8126441e327cb8fdf91c41e6003c1d84a4038c8b9003ea
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.23.
|
1
|
+
0.23.8
|
data/exe/ruby-lsp-launcher
CHANGED
@@ -51,7 +51,7 @@ end
|
|
51
51
|
|
52
52
|
begin
|
53
53
|
# Wait until the composed Bundle is finished
|
54
|
-
Process.
|
54
|
+
_, status = Process.wait2(pid)
|
55
55
|
rescue Errno::ECHILD
|
56
56
|
# In theory, the child process can finish before we even get to the wait call, but that is not an error
|
57
57
|
end
|
@@ -105,7 +105,7 @@ end
|
|
105
105
|
# flow, we are not booting the LSP yet, just checking if the bundle is valid before rebooting
|
106
106
|
if reboot
|
107
107
|
# Use the exit status to signal to the server if composing the bundle succeeded
|
108
|
-
exit(install_error || setup_error ? 1 : 0)
|
108
|
+
exit(install_error || setup_error ? 1 : status&.exitstatus || 0)
|
109
109
|
end
|
110
110
|
|
111
111
|
# Now that the bundle is set up, we can begin actually launching the server. Note that `Bundler.setup` will have already
|
@@ -120,8 +120,16 @@ module RubyIndexer
|
|
120
120
|
)]))
|
121
121
|
end
|
122
122
|
def first_unqualified_const(name)
|
123
|
+
# Look for an exact match first
|
123
124
|
_name, entries = @entries.find do |const_name, _entries|
|
124
|
-
const_name.end_with?(name)
|
125
|
+
const_name == name || const_name.end_with?("::#{name}")
|
126
|
+
end
|
127
|
+
|
128
|
+
# If an exact match is not found, then try to find a constant that ends with the name
|
129
|
+
unless entries
|
130
|
+
_name, entries = @entries.find do |const_name, _entries|
|
131
|
+
const_name.end_with?(name)
|
132
|
+
end
|
125
133
|
end
|
126
134
|
|
127
135
|
T.cast(
|
@@ -593,7 +601,7 @@ module RubyIndexer
|
|
593
601
|
entries = self[variable_name]&.grep(Entry::ClassVariable)
|
594
602
|
return unless entries&.any?
|
595
603
|
|
596
|
-
ancestors =
|
604
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
597
605
|
return if ancestors.empty?
|
598
606
|
|
599
607
|
entries.select { |e| ancestors.include?(e.owner&.name) }
|
@@ -601,12 +609,33 @@ module RubyIndexer
|
|
601
609
|
|
602
610
|
# Returns a list of possible candidates for completion of instance variables for a given owner name. The name must
|
603
611
|
# include the `@` prefix
|
604
|
-
sig
|
612
|
+
sig do
|
613
|
+
params(name: String, owner_name: String).returns(T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
614
|
+
end
|
605
615
|
def instance_variable_completion_candidates(name, owner_name)
|
606
|
-
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::InstanceVariable])
|
616
|
+
entries = T.cast(prefix_search(name).flatten, T::Array[T.any(Entry::InstanceVariable, Entry::ClassVariable)])
|
617
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
618
|
+
return entries if entries.empty?
|
619
|
+
|
607
620
|
ancestors = linearized_ancestors_of(owner_name)
|
608
621
|
|
609
|
-
|
622
|
+
instance_variables, class_variables = entries.partition { |e| e.is_a?(Entry::InstanceVariable) }
|
623
|
+
variables = instance_variables.select { |e| ancestors.any?(e.owner&.name) }
|
624
|
+
|
625
|
+
# Class variables are only owned by the attached class in our representation. If the owner is in a singleton
|
626
|
+
# context, we have to search for ancestors of the attached class
|
627
|
+
if class_variables.any?
|
628
|
+
name_parts = owner_name.split("::")
|
629
|
+
|
630
|
+
if name_parts.last&.start_with?("<Class:")
|
631
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
632
|
+
attached_ancestors = linearized_ancestors_of(attached_name)
|
633
|
+
variables.concat(class_variables.select { |e| attached_ancestors.any?(e.owner&.name) })
|
634
|
+
else
|
635
|
+
variables.concat(class_variables.select { |e| ancestors.any?(e.owner&.name) })
|
636
|
+
end
|
637
|
+
end
|
638
|
+
|
610
639
|
variables.uniq!(&:name)
|
611
640
|
variables
|
612
641
|
end
|
@@ -614,8 +643,10 @@ module RubyIndexer
|
|
614
643
|
sig { params(name: String, owner_name: String).returns(T::Array[Entry::ClassVariable]) }
|
615
644
|
def class_variable_completion_candidates(name, owner_name)
|
616
645
|
entries = T.cast(prefix_search(name).flatten, T::Array[Entry::ClassVariable])
|
617
|
-
ancestors
|
646
|
+
# Avoid wasting time linearizing ancestors if we didn't find anything
|
647
|
+
return entries if entries.empty?
|
618
648
|
|
649
|
+
ancestors = linearized_attached_ancestors(owner_name)
|
619
650
|
variables = entries.select { |e| ancestors.any?(e.owner&.name) }
|
620
651
|
variables.uniq!(&:name)
|
621
652
|
variables
|
@@ -717,6 +748,20 @@ module RubyIndexer
|
|
717
748
|
|
718
749
|
private
|
719
750
|
|
751
|
+
# Always returns the linearized ancestors for the attached class, regardless of whether `name` refers to a singleton
|
752
|
+
# or attached namespace
|
753
|
+
sig { params(name: String).returns(T::Array[String]) }
|
754
|
+
def linearized_attached_ancestors(name)
|
755
|
+
name_parts = name.split("::")
|
756
|
+
|
757
|
+
if name_parts.last&.start_with?("<Class:")
|
758
|
+
attached_name = T.must(name_parts[0..-2]).join("::")
|
759
|
+
linearized_ancestors_of(attached_name)
|
760
|
+
else
|
761
|
+
linearized_ancestors_of(name)
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
720
765
|
# Runs the registered included hooks
|
721
766
|
sig { params(fully_qualified_name: String, nesting: T::Array[String]).void }
|
722
767
|
def run_included_hooks(fully_qualified_name, nesting)
|
@@ -1561,6 +1561,23 @@ module RubyIndexer
|
|
1561
1561
|
assert_equal("Foo::Bar", entry.name)
|
1562
1562
|
end
|
1563
1563
|
|
1564
|
+
def test_first_unqualified_const_prefers_exact_matches
|
1565
|
+
index(<<~RUBY)
|
1566
|
+
module Foo
|
1567
|
+
class ParseResultType
|
1568
|
+
end
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
module Namespace
|
1572
|
+
class Type
|
1573
|
+
end
|
1574
|
+
end
|
1575
|
+
RUBY
|
1576
|
+
|
1577
|
+
entry = T.must(@index.first_unqualified_const("Type")&.first)
|
1578
|
+
assert_equal("Namespace::Type", entry.name)
|
1579
|
+
end
|
1580
|
+
|
1564
1581
|
def test_completion_does_not_duplicate_overridden_methods
|
1565
1582
|
index(<<~RUBY)
|
1566
1583
|
class Foo
|
@@ -2092,5 +2109,57 @@ module RubyIndexer
|
|
2092
2109
|
refute_nil(entry, "Expected indexer to be able to handle unsaved URIs")
|
2093
2110
|
assert_equal("I added this comment!", entry.comments)
|
2094
2111
|
end
|
2112
|
+
|
2113
|
+
def test_instance_variable_completion_returns_class_variables_too
|
2114
|
+
index(<<~RUBY)
|
2115
|
+
class Parent
|
2116
|
+
@@abc = 123
|
2117
|
+
end
|
2118
|
+
|
2119
|
+
class Child < Parent
|
2120
|
+
@@adf = 123
|
2121
|
+
|
2122
|
+
def self.do
|
2123
|
+
end
|
2124
|
+
end
|
2125
|
+
RUBY
|
2126
|
+
|
2127
|
+
abc, adf = @index.instance_variable_completion_candidates("@", "Child::<Class:Child>")
|
2128
|
+
|
2129
|
+
refute_nil(abc)
|
2130
|
+
refute_nil(adf)
|
2131
|
+
|
2132
|
+
assert_equal("@@abc", abc.name)
|
2133
|
+
assert_equal("@@adf", adf.name)
|
2134
|
+
end
|
2135
|
+
|
2136
|
+
def test_class_variable_completion_from_singleton_context
|
2137
|
+
index(<<~RUBY)
|
2138
|
+
class Foo
|
2139
|
+
@@hello = 123
|
2140
|
+
|
2141
|
+
def self.do
|
2142
|
+
end
|
2143
|
+
end
|
2144
|
+
RUBY
|
2145
|
+
|
2146
|
+
candidates = @index.class_variable_completion_candidates("@@", "Foo::<Class:Foo>")
|
2147
|
+
refute_empty(candidates)
|
2148
|
+
|
2149
|
+
assert_equal("@@hello", candidates.first&.name)
|
2150
|
+
end
|
2151
|
+
|
2152
|
+
def test_resolve_class_variable_in_singleton_context
|
2153
|
+
index(<<~RUBY)
|
2154
|
+
class Foo
|
2155
|
+
@@hello = 123
|
2156
|
+
end
|
2157
|
+
RUBY
|
2158
|
+
|
2159
|
+
candidates = @index.resolve_class_variable("@@hello", "Foo::<Class:Foo>")
|
2160
|
+
refute_empty(candidates)
|
2161
|
+
|
2162
|
+
assert_equal("@@hello", candidates.first&.name)
|
2163
|
+
end
|
2095
2164
|
end
|
2096
2165
|
end
|
data/lib/ruby_lsp/internal.rb
CHANGED
@@ -170,7 +170,7 @@ module RubyLsp
|
|
170
170
|
|
171
171
|
sig { params(line: Integer, character: Integer).void }
|
172
172
|
def move_cursor_to(line, character)
|
173
|
-
return unless
|
173
|
+
return unless /Visual Studio Code|Cursor/.match?(@client_name)
|
174
174
|
|
175
175
|
position = Interface::Position.new(
|
176
176
|
line: line,
|
data/lib/ruby_lsp/server.rb
CHANGED
@@ -1036,6 +1036,10 @@ module RubyLsp
|
|
1036
1036
|
when Constant::FileChangeType::DELETED
|
1037
1037
|
index.delete(uri)
|
1038
1038
|
end
|
1039
|
+
rescue Errno::ENOENT
|
1040
|
+
# If a file is created and then delete immediately afterwards, we will process the created notification before we
|
1041
|
+
# receive the deleted one, but the file no longer exists. This may happen when running a test suite that creates
|
1042
|
+
# and deletes files automatically.
|
1039
1043
|
end
|
1040
1044
|
|
1041
1045
|
sig { params(uri: URI::Generic).void }
|
@@ -1286,25 +1290,40 @@ module RubyLsp
|
|
1286
1290
|
addon.handle_window_show_message_response(result[:title])
|
1287
1291
|
end
|
1288
1292
|
|
1289
|
-
|
1293
|
+
# NOTE: all servers methods are void because they can produce several messages for the client. The only reason this
|
1294
|
+
# method returns the created thread is to that we can join it in tests and avoid flakiness. The implementation is
|
1295
|
+
# not supposed to rely on the return of this method
|
1296
|
+
sig { params(message: T::Hash[Symbol, T.untyped]).returns(T.nilable(Thread)) }
|
1290
1297
|
def compose_bundle(message)
|
1291
1298
|
already_composed_path = File.join(@global_state.workspace_path, ".ruby-lsp", "bundle_is_composed")
|
1292
|
-
command = "#{Gem.ruby} #{File.expand_path("../../exe/ruby-lsp-launcher", __dir__)} #{@global_state.workspace_uri}"
|
1293
1299
|
id = message[:id]
|
1294
1300
|
|
1295
1301
|
begin
|
1296
|
-
Bundler
|
1302
|
+
Bundler.with_original_env do
|
1303
|
+
Bundler::LockfileParser.new(Bundler.default_lockfile.read)
|
1304
|
+
end
|
1297
1305
|
rescue Bundler::LockfileError => e
|
1298
1306
|
send_message(Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: e.message))
|
1299
1307
|
return
|
1308
|
+
rescue Bundler::GemfileNotFound, Errno::ENOENT
|
1309
|
+
# We still compose the bundle if there's no Gemfile or if the lockfile got deleted
|
1300
1310
|
end
|
1301
1311
|
|
1302
1312
|
# We compose the bundle in a thread so that the LSP continues to work while we're checking for its validity. Once
|
1303
1313
|
# we return the response back to the editor, then the restart is triggered
|
1304
1314
|
Thread.new do
|
1305
1315
|
send_log_message("Recomposing the bundle ahead of restart")
|
1306
|
-
|
1307
|
-
|
1316
|
+
|
1317
|
+
_stdout, stderr, status = Bundler.with_unbundled_env do
|
1318
|
+
Open3.capture3(
|
1319
|
+
Gem.ruby,
|
1320
|
+
"-I",
|
1321
|
+
File.dirname(T.must(__dir__)),
|
1322
|
+
File.expand_path("../../exe/ruby-lsp-launcher", __dir__),
|
1323
|
+
@global_state.workspace_uri.to_s,
|
1324
|
+
chdir: @global_state.workspace_path,
|
1325
|
+
)
|
1326
|
+
end
|
1308
1327
|
|
1309
1328
|
if status&.exitstatus == 0
|
1310
1329
|
# Create a signal for the restart that it can skip composing the bundle and launch directly
|
@@ -1313,7 +1332,9 @@ module RubyLsp
|
|
1313
1332
|
else
|
1314
1333
|
# This special error code makes the extension avoid restarting in case we already know that the composed
|
1315
1334
|
# bundle is not valid
|
1316
|
-
send_message(
|
1335
|
+
send_message(
|
1336
|
+
Error.new(id: id, code: BUNDLE_COMPOSE_FAILED_CODE, message: "Failed to compose bundle\n#{stderr}"),
|
1337
|
+
)
|
1317
1338
|
end
|
1318
1339
|
end
|
1319
1340
|
end
|
@@ -241,8 +241,7 @@ module RubyLsp
|
|
241
241
|
|
242
242
|
# If either the Gemfile or the lockfile have been modified during the process of setting up the bundle, retry
|
243
243
|
# composing the bundle from scratch
|
244
|
-
|
245
|
-
if @gemfile && @lockfile
|
244
|
+
if @gemfile&.exist? && @lockfile&.exist?
|
246
245
|
current_gemfile_hash = Digest::SHA256.hexdigest(@gemfile.read)
|
247
246
|
current_lockfile_hash = Digest::SHA256.hexdigest(@lockfile.read)
|
248
247
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby-lsp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.23.
|
4
|
+
version: 0.23.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shopify
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-01-
|
10
|
+
date: 2025-01-31 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: language_server-protocol
|