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.
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)