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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +24 -3
  5. data/exe/ruby-lsp-launcher +127 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +63 -12
  7. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -2
  8. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +21 -6
  9. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
  10. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +15 -21
  11. data/lib/ruby_indexer/test/classes_and_modules_test.rb +2 -2
  12. data/lib/ruby_indexer/test/enhancements_test.rb +51 -19
  13. data/lib/ruby_indexer/test/index_test.rb +91 -2
  14. data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
  15. data/lib/ruby_indexer/test/method_test.rb +26 -0
  16. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  17. data/lib/ruby_lsp/addon.rb +9 -2
  18. data/lib/ruby_lsp/base_server.rb +14 -5
  19. data/lib/ruby_lsp/client_capabilities.rb +60 -0
  20. data/lib/ruby_lsp/document.rb +1 -1
  21. data/lib/ruby_lsp/global_state.rb +20 -19
  22. data/lib/ruby_lsp/internal.rb +2 -0
  23. data/lib/ruby_lsp/listeners/completion.rb +62 -0
  24. data/lib/ruby_lsp/listeners/definition.rb +48 -13
  25. data/lib/ruby_lsp/listeners/hover.rb +52 -0
  26. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  27. data/lib/ruby_lsp/requests/completion.rb +7 -1
  28. data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
  29. data/lib/ruby_lsp/requests/definition.rb +26 -11
  30. data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
  31. data/lib/ruby_lsp/requests/hover.rb +24 -6
  32. data/lib/ruby_lsp/requests/references.rb +2 -0
  33. data/lib/ruby_lsp/requests/rename.rb +3 -1
  34. data/lib/ruby_lsp/requests/request.rb +1 -1
  35. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +11 -1
  36. data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
  37. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
  38. data/lib/ruby_lsp/server.rb +54 -16
  39. data/lib/ruby_lsp/setup_bundler.rb +132 -24
  40. data/lib/ruby_lsp/utils.rb +8 -0
  41. metadata +8 -3
@@ -904,7 +904,7 @@ module RubyIndexer
904
904
  assert_equal(14, entry.location.start_line)
905
905
  end
906
906
 
907
- def test_resolving_inherited_alised_namespace
907
+ def test_resolving_inherited_aliased_namespace
908
908
  index(<<~RUBY)
909
909
  module Bar
910
910
  TARGET = 123
@@ -1490,7 +1490,7 @@ module RubyIndexer
1490
1490
  assert_kind_of(Entry::UnresolvedMethodAlias, entry)
1491
1491
  end
1492
1492
 
1493
- def test_unresolable_method_aliases
1493
+ def test_unresolvable_method_aliases
1494
1494
  index(<<~RUBY)
1495
1495
  class Foo
1496
1496
  alias bar baz
@@ -1934,5 +1934,94 @@ module RubyIndexer
1934
1934
  real_namespace = @index.follow_aliased_namespace("Namespace::Second")
1935
1935
  assert_equal("First::Second", real_namespace)
1936
1936
  end
1937
+
1938
+ def test_resolving_alias_to_non_existing_namespace
1939
+ index(<<~RUBY)
1940
+ module Namespace
1941
+ class Foo
1942
+ module InnerNamespace
1943
+ Constants = Namespace::Foo::Constants
1944
+ end
1945
+ end
1946
+ end
1947
+ RUBY
1948
+
1949
+ entry = @index.resolve("Constants", ["Namespace", "Foo", "InnerNamespace"])&.first
1950
+ assert_instance_of(Entry::UnresolvedConstantAlias, entry)
1951
+
1952
+ entry = @index.resolve("Namespace::Foo::Constants", ["Namespace", "Foo", "InnerNamespace"])&.first
1953
+ assert_nil(entry)
1954
+ end
1955
+
1956
+ def test_resolving_alias_to_existing_constant_from_inner_namespace
1957
+ index(<<~RUBY)
1958
+ module Parent
1959
+ CONST = 123
1960
+ end
1961
+
1962
+ module First
1963
+ module Namespace
1964
+ class Foo
1965
+ include Parent
1966
+
1967
+ module InnerNamespace
1968
+ Constants = Namespace::Foo::CONST
1969
+ end
1970
+ end
1971
+ end
1972
+ end
1973
+ RUBY
1974
+
1975
+ entry = @index.resolve("Namespace::Foo::CONST", ["First", "Namespace", "Foo", "InnerNamespace"])&.first
1976
+ assert_equal("Parent::CONST", entry.name)
1977
+ assert_instance_of(Entry::Constant, entry)
1978
+ end
1979
+
1980
+ def test_build_non_redundant_name
1981
+ assert_equal(
1982
+ "Namespace::Foo::Constants",
1983
+ @index.send(
1984
+ :build_non_redundant_full_name,
1985
+ "Namespace::Foo::Constants",
1986
+ ["Namespace", "Foo", "InnerNamespace"],
1987
+ ),
1988
+ )
1989
+
1990
+ assert_equal(
1991
+ "Namespace::Foo::Constants",
1992
+ @index.send(
1993
+ :build_non_redundant_full_name,
1994
+ "Namespace::Foo::Constants",
1995
+ ["Namespace", "Foo"],
1996
+ ),
1997
+ )
1998
+
1999
+ assert_equal(
2000
+ "Namespace::Foo::Constants",
2001
+ @index.send(
2002
+ :build_non_redundant_full_name,
2003
+ "Foo::Constants",
2004
+ ["Namespace", "Foo"],
2005
+ ),
2006
+ )
2007
+
2008
+ assert_equal(
2009
+ "Bar::Namespace::Foo::Constants",
2010
+ @index.send(
2011
+ :build_non_redundant_full_name,
2012
+ "Namespace::Foo::Constants",
2013
+ ["Bar"],
2014
+ ),
2015
+ )
2016
+
2017
+ assert_equal(
2018
+ "First::Namespace::Foo::Constants",
2019
+ @index.send(
2020
+ :build_non_redundant_full_name,
2021
+ "Namespace::Foo::Constants",
2022
+ ["First", "Namespace", "Foo", "InnerNamespace"],
2023
+ ),
2024
+ )
2025
+ end
1937
2026
  end
1938
2027
  end
@@ -209,7 +209,7 @@ module RubyIndexer
209
209
  end
210
210
  RUBY
211
211
 
212
- # If the surrounding method is beind defined on any dynamic value that isn't `self`, then we attribute the
212
+ # If the surrounding method is being defined on any dynamic value that isn't `self`, then we attribute the
213
213
  # instance variable to the wrong owner since there's no way to understand that statically
214
214
  entry = T.must(@index["@a"]&.first)
215
215
  owner = T.must(entry.owner)
@@ -123,6 +123,32 @@ module RubyIndexer
123
123
  assert_entry("baz", Entry::Method, "/fake/path/foo.rb:9-2:9-14", visibility: Entry::Visibility::PRIVATE)
124
124
  end
125
125
 
126
+ def test_visibility_tracking_with_module_function
127
+ index(<<~RUBY)
128
+ module Test
129
+ def foo; end
130
+ def bar; end
131
+ module_function :foo, "bar"
132
+ end
133
+ RUBY
134
+
135
+ ["foo", "bar"].each do |keyword|
136
+ entries = T.must(@index[keyword])
137
+ # should receive two entries because module_function creates a singleton method
138
+ # for the Test module and a private method for classes include the Test module
139
+ assert_equal(entries.size, 2)
140
+ first_entry, second_entry = *entries
141
+ # The first entry points to the location of the module_function call
142
+ assert_equal("Test", first_entry.owner.name)
143
+ assert_instance_of(Entry::Module, first_entry.owner)
144
+ assert_equal(Entry::Visibility::PRIVATE, first_entry.visibility)
145
+ # The second entry points to the public singleton method
146
+ assert_equal("Test::<Class:Test>", second_entry.owner.name)
147
+ assert_instance_of(Entry::SingletonClass, second_entry.owner)
148
+ assert_equal(Entry::Visibility::PUBLIC, second_entry.visibility)
149
+ end
150
+ end
151
+
126
152
  def test_method_with_parameters
127
153
  index(<<~RUBY)
128
154
  class Foo
@@ -100,7 +100,7 @@ module RubyIndexer
100
100
  end
101
101
 
102
102
  def test_location_and_name_location_are_the_same
103
- # NOTE: RBS does not store the name location for classes, modules or methods. This behaviour is not exactly what
103
+ # NOTE: RBS does not store the name location for classes, modules or methods. This behavior is not exactly what
104
104
  # we would like, but for now we assign the same location to both
105
105
 
106
106
  entries = @index["Array"]
@@ -46,7 +46,7 @@ module RubyLsp
46
46
  sig { returns(T::Array[T.class_of(Addon)]) }
47
47
  attr_reader :addon_classes
48
48
 
49
- # Automatically track and instantiate addon classes
49
+ # Automatically track and instantiate add-on classes
50
50
  sig { params(child_class: T.class_of(Addon)).void }
51
51
  def inherited(child_class)
52
52
  addon_classes << child_class
@@ -82,7 +82,7 @@ module RubyLsp
82
82
  e
83
83
  end
84
84
 
85
- # Instantiate all discovered addon classes
85
+ # Instantiate all discovered add-on classes
86
86
  self.addons = addon_classes.map(&:new)
87
87
  self.file_watcher_addons = addons.select { |addon| addon.respond_to?(:workspace_did_change_watched_files) }
88
88
 
@@ -194,6 +194,13 @@ module RubyLsp
194
194
  sig { abstract.returns(String) }
195
195
  def version; end
196
196
 
197
+ # Handle a response from a window/showMessageRequest request. Add-ons must include the addon_name as part of the
198
+ # original request so that the response is delegated to the correct add-on and must override this method to handle
199
+ # the response
200
+ # https://microsoft.github.io/language-server-protocol/specification#window_showMessageRequest
201
+ sig { overridable.params(title: String).void }
202
+ def handle_window_show_message_response(title); end
203
+
197
204
  # Creates a new CodeLens listener. This method is invoked on every CodeLens request
198
205
  sig do
199
206
  overridable.params(
@@ -8,9 +8,11 @@ module RubyLsp
8
8
 
9
9
  abstract!
10
10
 
11
- sig { params(test_mode: T::Boolean).void }
12
- def initialize(test_mode: false)
13
- @test_mode = T.let(test_mode, T::Boolean)
11
+ sig { params(options: T.untyped).void }
12
+ def initialize(**options)
13
+ @test_mode = T.let(options[:test_mode], T.nilable(T::Boolean))
14
+ @setup_error = T.let(options[:setup_error], T.nilable(StandardError))
15
+ @install_error = T.let(options[:install_error], T.nilable(StandardError))
14
16
  @writer = T.let(Transport::Stdio::Writer.new, Transport::Stdio::Writer)
15
17
  @reader = T.let(Transport::Stdio::Reader.new, Transport::Stdio::Reader)
16
18
  @incoming_queue = T.let(Thread::Queue.new, Thread::Queue)
@@ -22,7 +24,7 @@ module RubyLsp
22
24
  @store = T.let(Store.new, Store)
23
25
  @outgoing_dispatcher = T.let(
24
26
  Thread.new do
25
- unless test_mode
27
+ unless @test_mode
26
28
  while (message = @outgoing_queue.pop)
27
29
  @mutex.synchronize { @writer.write(message.to_hash) }
28
30
  end
@@ -33,6 +35,11 @@ module RubyLsp
33
35
 
34
36
  @global_state = T.let(GlobalState.new, GlobalState)
35
37
  Thread.main.priority = 1
38
+
39
+ # We read the initialize request in `exe/ruby-lsp` to be able to determine the workspace URI where Bundler should
40
+ # be set up
41
+ initialize_request = options[:initialize_request]
42
+ process_message(initialize_request) if initialize_request
36
43
  end
37
44
 
38
45
  sig { void }
@@ -59,7 +66,9 @@ module RubyLsp
59
66
  # If the client supports request delegation and we're working with an ERB document and there was
60
67
  # something to parse, then we have to maintain the client updated about the virtual state of the host
61
68
  # language source
62
- if document.parse! && @global_state.supports_request_delegation && document.is_a?(ERBDocument)
69
+ if document.parse! && @global_state.client_capabilities.supports_request_delegation &&
70
+ document.is_a?(ERBDocument)
71
+
63
72
  send_message(
64
73
  Notification.new(
65
74
  method: "delegate/textDocument/virtualState",
@@ -0,0 +1,60 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ # This class stores all client capabilities that the Ruby LSP and its add-ons depend on to ensure that we're
6
+ # not enabling functionality unsupported by the editor connecting to the server
7
+ class ClientCapabilities
8
+ extend T::Sig
9
+
10
+ sig { returns(T::Boolean) }
11
+ attr_reader :supports_watching_files,
12
+ :supports_request_delegation,
13
+ :window_show_message_supports_extra_properties
14
+
15
+ sig { void }
16
+ def initialize
17
+ # The editor supports watching files. This requires two capabilities: dynamic registration and relative pattern
18
+ # support
19
+ @supports_watching_files = T.let(false, T::Boolean)
20
+
21
+ # The editor supports request delegation. This is an experimental capability since request delegation has not been
22
+ # standardized into the LSP spec yet
23
+ @supports_request_delegation = T.let(false, T::Boolean)
24
+
25
+ # The editor supports extra arbitrary properties for `window/showMessageRequest`. Necessary for add-ons to show
26
+ # dialogs with user interactions
27
+ @window_show_message_supports_extra_properties = T.let(false, T::Boolean)
28
+
29
+ # Which resource operations the editor supports, like renaming files
30
+ @supported_resource_operations = T.let([], T::Array[String])
31
+ end
32
+
33
+ sig { params(capabilities: T::Hash[Symbol, T.untyped]).void }
34
+ def apply_client_capabilities(capabilities)
35
+ workspace_capabilities = capabilities[:workspace] || {}
36
+
37
+ file_watching_caps = workspace_capabilities[:didChangeWatchedFiles]
38
+ if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
39
+ @supports_watching_files = true
40
+ end
41
+
42
+ @supports_request_delegation = capabilities.dig(:experimental, :requestDelegation) || false
43
+ supported_resource_operations = workspace_capabilities.dig(:workspaceEdit, :resourceOperations)
44
+ @supported_resource_operations = supported_resource_operations if supported_resource_operations
45
+
46
+ supports_additional_properties = capabilities.dig(
47
+ :window,
48
+ :showMessage,
49
+ :messageActionItem,
50
+ :additionalPropertiesSupport,
51
+ )
52
+ @window_show_message_supports_extra_properties = supports_additional_properties || false
53
+ end
54
+
55
+ sig { returns(T::Boolean) }
56
+ def supports_rename?
57
+ @supported_resource_operations.include?("rename")
58
+ end
59
+ end
60
+ end
@@ -63,7 +63,7 @@ module RubyLsp
63
63
  sig { abstract.returns(LanguageId) }
64
64
  def language_id; end
65
65
 
66
- # TODO: remove this method once all nonpositional requests have been migrated to the listener pattern
66
+ # TODO: remove this method once all non-positional requests have been migrated to the listener pattern
67
67
  sig do
68
68
  type_parameters(:T)
69
69
  .params(
@@ -21,14 +21,14 @@ module RubyLsp
21
21
  attr_reader :encoding
22
22
 
23
23
  sig { returns(T::Boolean) }
24
- attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
25
-
26
- sig { returns(T::Array[String]) }
27
- attr_reader :supported_resource_operations
24
+ attr_reader :experimental_features, :top_level_bundle
28
25
 
29
26
  sig { returns(TypeInferrer) }
30
27
  attr_reader :type_inferrer
31
28
 
29
+ sig { returns(ClientCapabilities) }
30
+ attr_reader :client_capabilities
31
+
32
32
  sig { void }
33
33
  def initialize
34
34
  @workspace_uri = T.let(URI::Generic.from_path(path: Dir.pwd), URI::Generic)
@@ -40,12 +40,19 @@ module RubyLsp
40
40
  @has_type_checker = T.let(true, T::Boolean)
41
41
  @index = T.let(RubyIndexer::Index.new, RubyIndexer::Index)
42
42
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
43
- @supports_watching_files = T.let(false, T::Boolean)
44
43
  @experimental_features = T.let(false, T::Boolean)
45
44
  @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
46
45
  @addon_settings = T.let({}, T::Hash[String, T.untyped])
47
- @supports_request_delegation = T.let(false, T::Boolean)
48
- @supported_resource_operations = T.let([], T::Array[String])
46
+ @top_level_bundle = T.let(
47
+ begin
48
+ Bundler.with_original_env { Bundler.default_gemfile }
49
+ true
50
+ rescue Bundler::GemfileNotFound, Bundler::GitError
51
+ false
52
+ end,
53
+ T::Boolean,
54
+ )
55
+ @client_capabilities = T.let(ClientCapabilities.new, ClientCapabilities)
49
56
  end
50
57
 
51
58
  sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
@@ -123,12 +130,8 @@ module RubyLsp
123
130
  end
124
131
  @index.configuration.encoding = @encoding
125
132
 
126
- file_watching_caps = options.dig(:capabilities, :workspace, :didChangeWatchedFiles)
127
- if file_watching_caps&.dig(:dynamicRegistration) && file_watching_caps&.dig(:relativePatternSupport)
128
- @supports_watching_files = true
129
- end
130
-
131
133
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
134
+ @client_capabilities.apply_client_capabilities(options[:capabilities]) if options[:capabilities]
132
135
 
133
136
  addon_settings = options.dig(:initializationOptions, :addonSettings)
134
137
  if addon_settings
@@ -136,10 +139,6 @@ module RubyLsp
136
139
  @addon_settings.merge!(addon_settings)
137
140
  end
138
141
 
139
- @supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
140
- supported_resource_operations = options.dig(:capabilities, :workspace, :workspaceEdit, :resourceOperations)
141
- @supported_resource_operations = supported_resource_operations if supported_resource_operations
142
-
143
142
  notifications
144
143
  end
145
144
 
@@ -231,14 +230,16 @@ module RubyLsp
231
230
  sig { returns(T::Array[String]) }
232
231
  def gather_direct_dependencies
233
232
  Bundler.with_original_env { Bundler.default_gemfile }
234
- Bundler.locked_gems.dependencies.keys + gemspec_dependencies
233
+
234
+ dependencies = Bundler.locked_gems&.dependencies&.keys || []
235
+ dependencies + gemspec_dependencies
235
236
  rescue Bundler::GemfileNotFound
236
237
  []
237
238
  end
238
239
 
239
240
  sig { returns(T::Array[String]) }
240
241
  def gemspec_dependencies
241
- Bundler.locked_gems.sources
242
+ (Bundler.locked_gems&.sources || [])
242
243
  .grep(Bundler::Source::Gemspec)
243
244
  .flat_map { _1.gemspec&.dependencies&.map(&:name) }
244
245
  end
@@ -246,7 +247,7 @@ module RubyLsp
246
247
  sig { returns(T::Array[String]) }
247
248
  def gather_direct_and_indirect_dependencies
248
249
  Bundler.with_original_env { Bundler.default_gemfile }
249
- Bundler.locked_gems.specs.map(&:name)
250
+ Bundler.locked_gems&.specs&.map(&:name) || []
250
251
  rescue Bundler::GemfileNotFound
251
252
  []
252
253
  end
@@ -12,6 +12,7 @@ require "sorbet-runtime"
12
12
  require "bundler"
13
13
  Bundler.ui.level = :silent
14
14
 
15
+ require "json"
15
16
  require "uri"
16
17
  require "cgi"
17
18
  require "set"
@@ -28,6 +29,7 @@ require "core_ext/uri"
28
29
  require "ruby_lsp/utils"
29
30
  require "ruby_lsp/static_docs"
30
31
  require "ruby_lsp/scope"
32
+ require "ruby_lsp/client_capabilities"
31
33
  require "ruby_lsp/global_state"
32
34
  require "ruby_lsp/server"
33
35
  require "ruby_lsp/type_inferrer"
@@ -85,6 +85,12 @@ module RubyLsp
85
85
  :on_constant_path_node_enter,
86
86
  :on_constant_read_node_enter,
87
87
  :on_call_node_enter,
88
+ :on_global_variable_and_write_node_enter,
89
+ :on_global_variable_operator_write_node_enter,
90
+ :on_global_variable_or_write_node_enter,
91
+ :on_global_variable_read_node_enter,
92
+ :on_global_variable_target_node_enter,
93
+ :on_global_variable_write_node_enter,
88
94
  :on_instance_variable_read_node_enter,
89
95
  :on_instance_variable_write_node_enter,
90
96
  :on_instance_variable_and_write_node_enter,
@@ -180,6 +186,36 @@ module RubyLsp
180
186
  end
181
187
  end
182
188
 
189
+ sig { params(node: Prism::GlobalVariableAndWriteNode).void }
190
+ def on_global_variable_and_write_node_enter(node)
191
+ handle_global_variable_completion(node.name.to_s, node.name_loc)
192
+ end
193
+
194
+ sig { params(node: Prism::GlobalVariableOperatorWriteNode).void }
195
+ def on_global_variable_operator_write_node_enter(node)
196
+ handle_global_variable_completion(node.name.to_s, node.name_loc)
197
+ end
198
+
199
+ sig { params(node: Prism::GlobalVariableOrWriteNode).void }
200
+ def on_global_variable_or_write_node_enter(node)
201
+ handle_global_variable_completion(node.name.to_s, node.name_loc)
202
+ end
203
+
204
+ sig { params(node: Prism::GlobalVariableReadNode).void }
205
+ def on_global_variable_read_node_enter(node)
206
+ handle_global_variable_completion(node.name.to_s, node.location)
207
+ end
208
+
209
+ sig { params(node: Prism::GlobalVariableTargetNode).void }
210
+ def on_global_variable_target_node_enter(node)
211
+ handle_global_variable_completion(node.name.to_s, node.location)
212
+ end
213
+
214
+ sig { params(node: Prism::GlobalVariableWriteNode).void }
215
+ def on_global_variable_write_node_enter(node)
216
+ handle_global_variable_completion(node.name.to_s, node.name_loc)
217
+ end
218
+
183
219
  sig { params(node: Prism::InstanceVariableReadNode).void }
184
220
  def on_instance_variable_read_node_enter(node)
185
221
  handle_instance_variable_completion(node.name.to_s, node.location)
@@ -267,6 +303,29 @@ module RubyLsp
267
303
  end
268
304
  end
269
305
 
306
+ sig { params(name: String, location: Prism::Location).void }
307
+ def handle_global_variable_completion(name, location)
308
+ candidates = @index.prefix_search(name)
309
+
310
+ return if candidates.none?
311
+
312
+ range = range_from_location(location)
313
+
314
+ candidates.flatten.uniq(&:name).each do |entry|
315
+ entry_name = entry.name
316
+
317
+ @response_builder << Interface::CompletionItem.new(
318
+ label: entry_name,
319
+ filter_text: entry_name,
320
+ label_details: Interface::CompletionItemLabelDetails.new(
321
+ description: entry.file_name,
322
+ ),
323
+ text_edit: Interface::TextEdit.new(range: range, new_text: entry_name),
324
+ kind: Constant::CompletionItemKind::VARIABLE,
325
+ )
326
+ end
327
+ end
328
+
270
329
  sig { params(name: String, location: Prism::Location).void }
271
330
  def handle_instance_variable_completion(name, location)
272
331
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
@@ -381,8 +440,11 @@ module RubyLsp
381
440
  return unless range
382
441
 
383
442
  guessed_type = type.is_a?(TypeInferrer::GuessedType) && type.name
443
+ external_references = @node_context.fully_qualified_name != type.name
384
444
 
385
445
  @index.method_completion_candidates(method_name, type.name).each do |entry|
446
+ next if entry.visibility != RubyIndexer::Entry::Visibility::PUBLIC && external_references
447
+
386
448
  entry_name = entry.name
387
449
  owner_name = entry.owner&.name
388
450
 
@@ -39,7 +39,12 @@ module RubyLsp
39
39
  :on_block_argument_node_enter,
40
40
  :on_constant_read_node_enter,
41
41
  :on_constant_path_node_enter,
42
+ :on_global_variable_and_write_node_enter,
43
+ :on_global_variable_operator_write_node_enter,
44
+ :on_global_variable_or_write_node_enter,
42
45
  :on_global_variable_read_node_enter,
46
+ :on_global_variable_target_node_enter,
47
+ :on_global_variable_write_node_enter,
43
48
  :on_instance_variable_read_node_enter,
44
49
  :on_instance_variable_write_node_enter,
45
50
  :on_instance_variable_and_write_node_enter,
@@ -121,23 +126,34 @@ module RubyLsp
121
126
  find_in_index(name)
122
127
  end
123
128
 
129
+ sig { params(node: Prism::GlobalVariableAndWriteNode).void }
130
+ def on_global_variable_and_write_node_enter(node)
131
+ handle_global_variable_definition(node.name.to_s)
132
+ end
133
+
134
+ sig { params(node: Prism::GlobalVariableOperatorWriteNode).void }
135
+ def on_global_variable_operator_write_node_enter(node)
136
+ handle_global_variable_definition(node.name.to_s)
137
+ end
138
+
139
+ sig { params(node: Prism::GlobalVariableOrWriteNode).void }
140
+ def on_global_variable_or_write_node_enter(node)
141
+ handle_global_variable_definition(node.name.to_s)
142
+ end
143
+
124
144
  sig { params(node: Prism::GlobalVariableReadNode).void }
125
145
  def on_global_variable_read_node_enter(node)
126
- entries = @index[node.name.to_s]
127
-
128
- return unless entries
146
+ handle_global_variable_definition(node.name.to_s)
147
+ end
129
148
 
130
- entries.each do |entry|
131
- location = entry.location
149
+ sig { params(node: Prism::GlobalVariableTargetNode).void }
150
+ def on_global_variable_target_node_enter(node)
151
+ handle_global_variable_definition(node.name.to_s)
152
+ end
132
153
 
133
- @response_builder << Interface::Location.new(
134
- uri: URI::Generic.from_path(path: entry.file_path).to_s,
135
- range: Interface::Range.new(
136
- start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
137
- end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
138
- ),
139
- )
140
- end
154
+ sig { params(node: Prism::GlobalVariableWriteNode).void }
155
+ def on_global_variable_write_node_enter(node)
156
+ handle_global_variable_definition(node.name.to_s)
141
157
  end
142
158
 
143
159
  sig { params(node: Prism::InstanceVariableReadNode).void }
@@ -197,6 +213,25 @@ module RubyLsp
197
213
  )
198
214
  end
199
215
 
216
+ sig { params(name: String).void }
217
+ def handle_global_variable_definition(name)
218
+ entries = @index[name]
219
+
220
+ return unless entries
221
+
222
+ entries.each do |entry|
223
+ location = entry.location
224
+
225
+ @response_builder << Interface::Location.new(
226
+ uri: URI::Generic.from_path(path: entry.file_path).to_s,
227
+ range: Interface::Range.new(
228
+ start: Interface::Position.new(line: location.start_line - 1, character: location.start_column),
229
+ end: Interface::Position.new(line: location.end_line - 1, character: location.end_column),
230
+ ),
231
+ )
232
+ end
233
+ end
234
+
200
235
  sig { params(name: String).void }
201
236
  def handle_instance_variable_definition(name)
202
237
  # Sorbet enforces that all instance variables be declared on typed strict or higher, which means it will be able
@@ -13,6 +13,12 @@ module RubyLsp
13
13
  Prism::ConstantReadNode,
14
14
  Prism::ConstantWriteNode,
15
15
  Prism::ConstantPathNode,
16
+ Prism::GlobalVariableAndWriteNode,
17
+ Prism::GlobalVariableOperatorWriteNode,
18
+ Prism::GlobalVariableOrWriteNode,
19
+ Prism::GlobalVariableReadNode,
20
+ Prism::GlobalVariableTargetNode,
21
+ Prism::GlobalVariableWriteNode,
16
22
  Prism::InstanceVariableReadNode,
17
23
  Prism::InstanceVariableAndWriteNode,
18
24
  Prism::InstanceVariableOperatorWriteNode,
@@ -62,6 +68,12 @@ module RubyLsp
62
68
  :on_constant_write_node_enter,
63
69
  :on_constant_path_node_enter,
64
70
  :on_call_node_enter,
71
+ :on_global_variable_and_write_node_enter,
72
+ :on_global_variable_operator_write_node_enter,
73
+ :on_global_variable_or_write_node_enter,
74
+ :on_global_variable_read_node_enter,
75
+ :on_global_variable_target_node_enter,
76
+ :on_global_variable_write_node_enter,
65
77
  :on_instance_variable_read_node_enter,
66
78
  :on_instance_variable_write_node_enter,
67
79
  :on_instance_variable_and_write_node_enter,
@@ -128,6 +140,36 @@ module RubyLsp
128
140
  handle_method_hover(message)
129
141
  end
130
142
 
143
+ sig { params(node: Prism::GlobalVariableAndWriteNode).void }
144
+ def on_global_variable_and_write_node_enter(node)
145
+ handle_global_variable_hover(node.name.to_s)
146
+ end
147
+
148
+ sig { params(node: Prism::GlobalVariableOperatorWriteNode).void }
149
+ def on_global_variable_operator_write_node_enter(node)
150
+ handle_global_variable_hover(node.name.to_s)
151
+ end
152
+
153
+ sig { params(node: Prism::GlobalVariableOrWriteNode).void }
154
+ def on_global_variable_or_write_node_enter(node)
155
+ handle_global_variable_hover(node.name.to_s)
156
+ end
157
+
158
+ sig { params(node: Prism::GlobalVariableReadNode).void }
159
+ def on_global_variable_read_node_enter(node)
160
+ handle_global_variable_hover(node.name.to_s)
161
+ end
162
+
163
+ sig { params(node: Prism::GlobalVariableTargetNode).void }
164
+ def on_global_variable_target_node_enter(node)
165
+ handle_global_variable_hover(node.name.to_s)
166
+ end
167
+
168
+ sig { params(node: Prism::GlobalVariableWriteNode).void }
169
+ def on_global_variable_write_node_enter(node)
170
+ handle_global_variable_hover(node.name.to_s)
171
+ end
172
+
131
173
  sig { params(node: Prism::InstanceVariableReadNode).void }
132
174
  def on_instance_variable_read_node_enter(node)
133
175
  handle_instance_variable_hover(node.name.to_s)
@@ -265,6 +307,16 @@ module RubyLsp
265
307
  # If by any chance we haven't indexed the owner, then there's no way to find the right declaration
266
308
  end
267
309
 
310
+ sig { params(name: String).void }
311
+ def handle_global_variable_hover(name)
312
+ entries = @index[name]
313
+ return unless entries
314
+
315
+ categorized_markdown_from_index_entries(name, entries).each do |category, content|
316
+ @response_builder.push(content, category: category)
317
+ end
318
+ end
319
+
268
320
  sig { params(name: String, location: Prism::Location).void }
269
321
  def generate_hover(name, location)
270
322
  entries = @index.resolve(name, @node_context.nesting)