ruby-lsp 0.20.1 → 0.21.1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +18 -3
  5. data/exe/ruby-lsp-launcher +127 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +56 -2
  7. data/lib/ruby_indexer/lib/ruby_indexer/enhancement.rb +21 -6
  8. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +1 -1
  9. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +5 -5
  10. data/lib/ruby_indexer/test/classes_and_modules_test.rb +2 -2
  11. data/lib/ruby_indexer/test/enhancements_test.rb +51 -19
  12. data/lib/ruby_indexer/test/index_test.rb +2 -2
  13. data/lib/ruby_indexer/test/instance_variables_test.rb +1 -1
  14. data/lib/ruby_indexer/test/method_test.rb +26 -0
  15. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  16. data/lib/ruby_lsp/addon.rb +9 -2
  17. data/lib/ruby_lsp/base_server.rb +14 -5
  18. data/lib/ruby_lsp/client_capabilities.rb +60 -0
  19. data/lib/ruby_lsp/document.rb +1 -1
  20. data/lib/ruby_lsp/global_state.rb +25 -19
  21. data/lib/ruby_lsp/internal.rb +2 -0
  22. data/lib/ruby_lsp/listeners/completion.rb +62 -0
  23. data/lib/ruby_lsp/listeners/definition.rb +48 -13
  24. data/lib/ruby_lsp/listeners/hover.rb +52 -0
  25. data/lib/ruby_lsp/requests/code_action_resolve.rb +1 -1
  26. data/lib/ruby_lsp/requests/completion.rb +7 -1
  27. data/lib/ruby_lsp/requests/completion_resolve.rb +1 -1
  28. data/lib/ruby_lsp/requests/definition.rb +26 -11
  29. data/lib/ruby_lsp/requests/document_symbol.rb +2 -1
  30. data/lib/ruby_lsp/requests/hover.rb +24 -6
  31. data/lib/ruby_lsp/requests/rename.rb +1 -1
  32. data/lib/ruby_lsp/requests/request.rb +1 -1
  33. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +11 -1
  34. data/lib/ruby_lsp/scripts/compose_bundle.rb +20 -0
  35. data/lib/ruby_lsp/scripts/compose_bundle_windows.rb +8 -0
  36. data/lib/ruby_lsp/server.rb +54 -16
  37. data/lib/ruby_lsp/setup_bundler.rb +111 -22
  38. data/lib/ruby_lsp/utils.rb +8 -0
  39. metadata +8 -3
@@ -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
 
@@ -160,6 +159,11 @@ module RubyLsp
160
159
  end
161
160
  end
162
161
 
162
+ sig { returns(T::Boolean) }
163
+ def supports_watching_files
164
+ @client_capabilities.supports_watching_files
165
+ end
166
+
163
167
  private
164
168
 
165
169
  sig { params(direct_dependencies: T::Array[String], all_dependencies: T::Array[String]).returns(String) }
@@ -231,14 +235,16 @@ module RubyLsp
231
235
  sig { returns(T::Array[String]) }
232
236
  def gather_direct_dependencies
233
237
  Bundler.with_original_env { Bundler.default_gemfile }
234
- Bundler.locked_gems.dependencies.keys + gemspec_dependencies
238
+
239
+ dependencies = Bundler.locked_gems&.dependencies&.keys || []
240
+ dependencies + gemspec_dependencies
235
241
  rescue Bundler::GemfileNotFound
236
242
  []
237
243
  end
238
244
 
239
245
  sig { returns(T::Array[String]) }
240
246
  def gemspec_dependencies
241
- Bundler.locked_gems.sources
247
+ (Bundler.locked_gems&.sources || [])
242
248
  .grep(Bundler::Source::Gemspec)
243
249
  .flat_map { _1.gemspec&.dependencies&.map(&:name) }
244
250
  end
@@ -246,7 +252,7 @@ module RubyLsp
246
252
  sig { returns(T::Array[String]) }
247
253
  def gather_direct_and_indirect_dependencies
248
254
  Bundler.with_original_env { Bundler.default_gemfile }
249
- Bundler.locked_gems.specs.map(&:name)
255
+ Bundler.locked_gems&.specs&.map(&:name) || []
250
256
  rescue Bundler::GemfileNotFound
251
257
  []
252
258
  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)
@@ -121,7 +121,7 @@ module RubyLsp
121
121
  return Error::InvalidTargetRange if closest_node.is_a?(Prism::MissingNode)
122
122
 
123
123
  closest_node_loc = closest_node.location
124
- # If the parent expression is a single line block, then we have to extract it inside of the oneline block
124
+ # If the parent expression is a single line block, then we have to extract it inside of the one-line block
125
125
  if parent_statements.is_a?(Prism::BlockNode) &&
126
126
  parent_statements.location.start_line == parent_statements.location.end_line
127
127
 
@@ -17,7 +17,7 @@ module RubyLsp
17
17
  def provider
18
18
  Interface::CompletionOptions.new(
19
19
  resolve_provider: true,
20
- trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<"],
20
+ trigger_characters: ["/", "\"", "'", ":", "@", ".", "=", "<", "$"],
21
21
  completion_item: {
22
22
  labelDetailsSupport: true,
23
23
  },
@@ -50,6 +50,12 @@ module RubyLsp
50
50
  Prism::CallNode,
51
51
  Prism::ConstantReadNode,
52
52
  Prism::ConstantPathNode,
53
+ Prism::GlobalVariableAndWriteNode,
54
+ Prism::GlobalVariableOperatorWriteNode,
55
+ Prism::GlobalVariableOrWriteNode,
56
+ Prism::GlobalVariableReadNode,
57
+ Prism::GlobalVariableTargetNode,
58
+ Prism::GlobalVariableWriteNode,
53
59
  Prism::InstanceVariableReadNode,
54
60
  Prism::InstanceVariableAndWriteNode,
55
61
  Prism::InstanceVariableOperatorWriteNode,
@@ -34,7 +34,7 @@ module RubyLsp
34
34
 
35
35
  # Based on the spec https://microsoft.github.io/language-server-protocol/specification#textDocument_completion,
36
36
  # a completion resolve request must always return the original completion item without modifying ANY fields
37
- # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behaviour might
37
+ # other than detail and documentation (NOT labelDetails). If we modify anything, the completion behavior might
38
38
  # be broken.
39
39
  #
40
40
  # For example, forgetting to return the `insertText` included in the original item will make the editor use the
@@ -12,12 +12,6 @@ module RubyLsp
12
12
  extend T::Sig
13
13
  extend T::Generic
14
14
 
15
- SPECIAL_METHOD_CALLS = [
16
- :require,
17
- :require_relative,
18
- :autoload,
19
- ].freeze
20
-
21
15
  sig do
22
16
  params(
23
17
  document: T.any(RubyDocument, ERBDocument),
@@ -46,7 +40,12 @@ module RubyLsp
46
40
  Prism::ConstantReadNode,
47
41
  Prism::ConstantPathNode,
48
42
  Prism::BlockArgumentNode,
43
+ Prism::GlobalVariableAndWriteNode,
44
+ Prism::GlobalVariableOperatorWriteNode,
45
+ Prism::GlobalVariableOrWriteNode,
49
46
  Prism::GlobalVariableReadNode,
47
+ Prism::GlobalVariableTargetNode,
48
+ Prism::GlobalVariableWriteNode,
50
49
  Prism::InstanceVariableReadNode,
51
50
  Prism::InstanceVariableAndWriteNode,
52
51
  Prism::InstanceVariableOperatorWriteNode,
@@ -72,11 +71,7 @@ module RubyLsp
72
71
  parent,
73
72
  position,
74
73
  )
75
- elsif target.is_a?(Prism::CallNode) && !SPECIAL_METHOD_CALLS.include?(target.message) && !covers_position?(
76
- target.message_loc, position
77
- )
78
- # If the target is a method call, we need to ensure that the requested position is exactly on top of the
79
- # method identifier. Otherwise, we risk showing definitions for unrelated things
74
+ elsif position_outside_target?(position, target)
80
75
  target = nil
81
76
  # For methods with block arguments using symbol-to-proc
82
77
  elsif target.is_a?(Prism::SymbolNode) && parent.is_a?(Prism::BlockArgumentNode)
@@ -107,6 +102,26 @@ module RubyLsp
107
102
  @dispatcher.dispatch_once(@target) if @target
108
103
  @response_builder.response
109
104
  end
105
+
106
+ private
107
+
108
+ sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
109
+ def position_outside_target?(position, target)
110
+ case target
111
+ when Prism::GlobalVariableAndWriteNode,
112
+ Prism::GlobalVariableOperatorWriteNode,
113
+ Prism::GlobalVariableOrWriteNode,
114
+ Prism::GlobalVariableWriteNode,
115
+ Prism::InstanceVariableAndWriteNode,
116
+ Prism::InstanceVariableOperatorWriteNode,
117
+ Prism::InstanceVariableOrWriteNode,
118
+ Prism::InstanceVariableWriteNode
119
+
120
+ !covers_position?(target.name_loc, position)
121
+ else
122
+ false
123
+ end
124
+ end
110
125
  end
111
126
  end
112
127
  end
@@ -10,7 +10,8 @@ module RubyLsp
10
10
  # informs the editor of all the important symbols, such as classes, variables, and methods, defined in a file. With
11
11
  # this information, the editor can populate breadcrumbs, file outline and allow for fuzzy symbol searches.
12
12
  #
13
- # In VS Code, fuzzy symbol search can be accessed by opening the command palette and inserting an `@` symbol.
13
+ # In VS Code, symbol search known as 'Go To Symbol in Editor' and can be accessed with Ctrl/Cmd-Shift-O,
14
+ # or by opening the command palette and inserting an `@` symbol.
14
15
  class DocumentSymbol < Request
15
16
  extend T::Sig
16
17
 
@@ -46,17 +46,13 @@ module RubyLsp
46
46
  target = node_context.node
47
47
  parent = node_context.parent
48
48
 
49
- if (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
50
- !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
51
- (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
49
+ if should_refine_target?(parent, target)
52
50
  target = determine_target(
53
51
  T.must(target),
54
52
  T.must(parent),
55
53
  position,
56
54
  )
57
- elsif target.is_a?(Prism::CallNode) && target.name != :require && target.name != :require_relative &&
58
- !covers_position?(target.message_loc, position)
59
-
55
+ elsif position_outside_target?(position, target)
60
56
  target = nil
61
57
  end
62
58
 
@@ -89,6 +85,28 @@ module RubyLsp
89
85
  ),
90
86
  )
91
87
  end
88
+
89
+ private
90
+
91
+ sig { params(parent: T.nilable(Prism::Node), target: T.nilable(Prism::Node)).returns(T::Boolean) }
92
+ def should_refine_target?(parent, target)
93
+ (Listeners::Hover::ALLOWED_TARGETS.include?(parent.class) &&
94
+ !Listeners::Hover::ALLOWED_TARGETS.include?(target.class)) ||
95
+ (parent.is_a?(Prism::ConstantPathNode) && target.is_a?(Prism::ConstantReadNode))
96
+ end
97
+
98
+ sig { params(position: T::Hash[Symbol, T.untyped], target: T.nilable(Prism::Node)).returns(T::Boolean) }
99
+ def position_outside_target?(position, target)
100
+ case target
101
+ when Prism::GlobalVariableAndWriteNode,
102
+ Prism::GlobalVariableOperatorWriteNode,
103
+ Prism::GlobalVariableOrWriteNode,
104
+ Prism::GlobalVariableWriteNode
105
+ !covers_position?(target.name_loc, position)
106
+ else
107
+ false
108
+ end
109
+ end
92
110
  end
93
111
  end
94
112
  end
@@ -72,7 +72,7 @@ module RubyLsp
72
72
 
73
73
  # If the client doesn't support resource operations, such as renaming files, then we can only return the basic
74
74
  # text changes
75
- unless @global_state.supported_resource_operations.include?("rename")
75
+ unless @global_state.client_capabilities.supports_rename?
76
76
  return Interface::WorkspaceEdit.new(changes: changes)
77
77
  end
78
78