ruby-lsp 0.20.1 → 0.21.1

Sign up to get free protection for your applications and to get access to all the features.
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