ruby-lsp 0.17.17 → 0.18.3

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -110
  3. data/VERSION +1 -1
  4. data/exe/ruby-lsp +5 -11
  5. data/lib/ruby_indexer/lib/ruby_indexer/declaration_listener.rb +14 -6
  6. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +162 -27
  7. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +110 -8
  8. data/lib/ruby_indexer/lib/ruby_indexer/rbs_indexer.rb +2 -2
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +24 -10
  10. data/lib/ruby_indexer/test/constant_test.rb +4 -4
  11. data/lib/ruby_indexer/test/enhancements_test.rb +2 -2
  12. data/lib/ruby_indexer/test/index_test.rb +68 -0
  13. data/lib/ruby_indexer/test/method_test.rb +257 -2
  14. data/lib/ruby_indexer/test/rbs_indexer_test.rb +1 -1
  15. data/lib/ruby_lsp/base_server.rb +21 -1
  16. data/lib/ruby_lsp/document.rb +5 -3
  17. data/lib/ruby_lsp/erb_document.rb +29 -10
  18. data/lib/ruby_lsp/global_state.rb +4 -3
  19. data/lib/ruby_lsp/internal.rb +40 -2
  20. data/lib/ruby_lsp/listeners/code_lens.rb +34 -5
  21. data/lib/ruby_lsp/listeners/completion.rb +20 -6
  22. data/lib/ruby_lsp/listeners/inlay_hints.rb +1 -16
  23. data/lib/ruby_lsp/listeners/signature_help.rb +55 -24
  24. data/lib/ruby_lsp/rbs_document.rb +5 -4
  25. data/lib/ruby_lsp/requests/code_action_resolve.rb +0 -15
  26. data/lib/ruby_lsp/requests/code_actions.rb +0 -10
  27. data/lib/ruby_lsp/requests/code_lens.rb +1 -11
  28. data/lib/ruby_lsp/requests/completion.rb +3 -20
  29. data/lib/ruby_lsp/requests/completion_resolve.rb +2 -10
  30. data/lib/ruby_lsp/requests/definition.rb +6 -20
  31. data/lib/ruby_lsp/requests/diagnostics.rb +0 -10
  32. data/lib/ruby_lsp/requests/document_highlight.rb +7 -14
  33. data/lib/ruby_lsp/requests/document_link.rb +0 -10
  34. data/lib/ruby_lsp/requests/document_symbol.rb +0 -17
  35. data/lib/ruby_lsp/requests/folding_ranges.rb +0 -10
  36. data/lib/ruby_lsp/requests/formatting.rb +0 -16
  37. data/lib/ruby_lsp/requests/hover.rb +9 -9
  38. data/lib/ruby_lsp/requests/inlay_hints.rb +2 -35
  39. data/lib/ruby_lsp/requests/on_type_formatting.rb +0 -10
  40. data/lib/ruby_lsp/requests/prepare_type_hierarchy.rb +0 -11
  41. data/lib/ruby_lsp/requests/request.rb +17 -1
  42. data/lib/ruby_lsp/requests/selection_ranges.rb +0 -10
  43. data/lib/ruby_lsp/requests/semantic_highlighting.rb +1 -23
  44. data/lib/ruby_lsp/requests/show_syntax_tree.rb +0 -11
  45. data/lib/ruby_lsp/requests/signature_help.rb +5 -20
  46. data/lib/ruby_lsp/requests/support/common.rb +1 -1
  47. data/lib/ruby_lsp/requests/support/rubocop_formatter.rb +2 -0
  48. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +2 -0
  49. data/lib/ruby_lsp/requests/type_hierarchy_supertypes.rb +0 -11
  50. data/lib/ruby_lsp/requests/workspace_symbol.rb +0 -12
  51. data/lib/ruby_lsp/ruby_document.rb +4 -3
  52. data/lib/ruby_lsp/server.rb +45 -11
  53. data/lib/ruby_lsp/setup_bundler.rb +33 -15
  54. data/lib/ruby_lsp/type_inferrer.rb +8 -10
  55. data/lib/ruby_lsp/utils.rb +11 -1
  56. metadata +3 -6
  57. data/lib/ruby_lsp/check_docs.rb +0 -130
  58. data/lib/ruby_lsp/requests.rb +0 -64
  59. data/lib/ruby_lsp/response_builders.rb +0 -13
@@ -31,6 +31,7 @@ module RubyLsp
31
31
  Thread,
32
32
  )
33
33
 
34
+ @global_state = T.let(GlobalState.new, GlobalState)
34
35
  Thread.main.priority = 1
35
36
  end
36
37
 
@@ -52,7 +53,26 @@ module RubyLsp
52
53
  message[:params][:textDocument][:uri] = parsed_uri
53
54
 
54
55
  # We don't want to try to parse documents on text synchronization notifications
55
- @store.get(parsed_uri).parse unless method.start_with?("textDocument/did")
56
+ unless method.start_with?("textDocument/did")
57
+ document = @store.get(parsed_uri)
58
+
59
+ # If the client supports request delegation and we're working with an ERB document and there was
60
+ # something to parse, then we have to maintain the client updated about the virtual state of the host
61
+ # language source
62
+ if document.parse! && @global_state.supports_request_delegation && document.is_a?(ERBDocument)
63
+ send_message(
64
+ Notification.new(
65
+ method: "delegate/textDocument/virtualState",
66
+ params: {
67
+ textDocument: {
68
+ uri: uri,
69
+ text: document.host_language_source,
70
+ },
71
+ },
72
+ ),
73
+ )
74
+ end
75
+ end
56
76
  rescue Store::NonExistingDocumentError
57
77
  # If we receive a request for a file that no longer exists, we don't want to fail
58
78
  end
@@ -51,7 +51,8 @@ module RubyLsp
51
51
  @version = T.let(version, Integer)
52
52
  @uri = T.let(uri, URI::Generic)
53
53
  @needs_parsing = T.let(true, T::Boolean)
54
- @parse_result = T.let(parse, ParseResultType)
54
+ @parse_result = T.let(T.unsafe(nil), ParseResultType)
55
+ parse!
55
56
  end
56
57
 
57
58
  sig { params(other: Document[T.untyped]).returns(T::Boolean) }
@@ -106,8 +107,9 @@ module RubyLsp
106
107
  @cache.clear
107
108
  end
108
109
 
109
- sig { abstract.returns(ParseResultType) }
110
- def parse; end
110
+ # Returns `true` if the document was parsed and `false` if nothing needed parsing
111
+ sig { abstract.returns(T::Boolean) }
112
+ def parse!; end
111
113
 
112
114
  sig { abstract.returns(T::Boolean) }
113
115
  def syntax_error?; end
@@ -6,17 +6,30 @@ module RubyLsp
6
6
  extend T::Sig
7
7
  extend T::Generic
8
8
 
9
+ sig { returns(String) }
10
+ attr_reader :host_language_source
11
+
9
12
  ParseResultType = type_member { { fixed: Prism::ParseResult } }
10
13
 
11
- sig { override.returns(ParseResultType) }
12
- def parse
13
- return @parse_result unless @needs_parsing
14
+ sig { params(source: String, version: Integer, uri: URI::Generic, encoding: Encoding).void }
15
+ def initialize(source:, version:, uri:, encoding: Encoding::UTF_8)
16
+ # This has to be initialized before calling super because we call `parse` in the parent constructor, which
17
+ # overrides this with the proper virtual host language source
18
+ @host_language_source = T.let("", String)
19
+ super
20
+ end
21
+
22
+ sig { override.returns(T::Boolean) }
23
+ def parse!
24
+ return false unless @needs_parsing
14
25
 
15
26
  @needs_parsing = false
16
27
  scanner = ERBScanner.new(@source)
17
28
  scanner.scan
29
+ @host_language_source = scanner.host_language
18
30
  # assigning empty scopes to turn Prism into eval mode
19
31
  @parse_result = Prism.parse(scanner.ruby, scopes: [[]])
32
+ true
20
33
  end
21
34
 
22
35
  sig { override.returns(T::Boolean) }
@@ -39,16 +52,22 @@ module RubyLsp
39
52
  RubyDocument.locate(@parse_result.value, create_scanner.find_char_position(position), node_types: node_types)
40
53
  end
41
54
 
55
+ sig { params(char_position: Integer).returns(T.nilable(T::Boolean)) }
56
+ def inside_host_language?(char_position)
57
+ char = @host_language_source[char_position]
58
+ char && char != " "
59
+ end
60
+
42
61
  class ERBScanner
43
62
  extend T::Sig
44
63
 
45
64
  sig { returns(String) }
46
- attr_reader :ruby, :html
65
+ attr_reader :ruby, :host_language
47
66
 
48
67
  sig { params(source: String).void }
49
68
  def initialize(source)
50
69
  @source = source
51
- @html = T.let(+"", String)
70
+ @host_language = T.let(+"", String)
52
71
  @ruby = T.let(+"", String)
53
72
  @current_pos = T.let(0, Integer)
54
73
  @inside_ruby = T.let(false, T::Boolean)
@@ -104,16 +123,16 @@ module RubyLsp
104
123
  end
105
124
  when "\r"
106
125
  @ruby << char
107
- @html << char
126
+ @host_language << char
108
127
 
109
128
  if next_char == "\n"
110
129
  @ruby << next_char
111
- @html << next_char
130
+ @host_language << next_char
112
131
  @current_pos += 1
113
132
  end
114
133
  when "\n"
115
134
  @ruby << char
116
- @html << char
135
+ @host_language << char
117
136
  else
118
137
  push_char(T.must(char))
119
138
  end
@@ -123,10 +142,10 @@ module RubyLsp
123
142
  def push_char(char)
124
143
  if @inside_ruby
125
144
  @ruby << char
126
- @html << " " * char.length
145
+ @host_language << " " * char.length
127
146
  else
128
147
  @ruby << " " * char.length
129
- @html << char
148
+ @host_language << char
130
149
  end
131
150
  end
132
151
 
@@ -21,7 +21,7 @@ module RubyLsp
21
21
  attr_reader :encoding
22
22
 
23
23
  sig { returns(T::Boolean) }
24
- attr_reader :supports_watching_files, :experimental_features
24
+ attr_reader :supports_watching_files, :experimental_features, :supports_request_delegation
25
25
 
26
26
  sig { returns(TypeInferrer) }
27
27
  attr_reader :type_inferrer
@@ -39,8 +39,9 @@ module RubyLsp
39
39
  @supported_formatters = T.let({}, T::Hash[String, Requests::Support::Formatter])
40
40
  @supports_watching_files = T.let(false, T::Boolean)
41
41
  @experimental_features = T.let(false, T::Boolean)
42
- @type_inferrer = T.let(TypeInferrer.new(@index, @experimental_features), TypeInferrer)
42
+ @type_inferrer = T.let(TypeInferrer.new(@index), TypeInferrer)
43
43
  @addon_settings = T.let({}, T::Hash[String, T.untyped])
44
+ @supports_request_delegation = T.let(false, T::Boolean)
44
45
  end
45
46
 
46
47
  sig { params(addon_name: String).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
@@ -123,7 +124,6 @@ module RubyLsp
123
124
  end
124
125
 
125
126
  @experimental_features = options.dig(:initializationOptions, :experimentalFeaturesEnabled) || false
126
- @type_inferrer.experimental_features = @experimental_features
127
127
 
128
128
  addon_settings = options.dig(:initializationOptions, :addonSettings)
129
129
  if addon_settings
@@ -131,6 +131,7 @@ module RubyLsp
131
131
  @addon_settings.merge!(addon_settings)
132
132
  end
133
133
 
134
+ @supports_request_delegation = options.dig(:capabilities, :experimental, :requestDelegation) || false
134
135
  notifications
135
136
  end
136
137
 
@@ -30,8 +30,6 @@ require "ruby_lsp/parameter_scope"
30
30
  require "ruby_lsp/global_state"
31
31
  require "ruby_lsp/server"
32
32
  require "ruby_lsp/type_inferrer"
33
- require "ruby_lsp/requests"
34
- require "ruby_lsp/response_builders"
35
33
  require "ruby_lsp/node_context"
36
34
  require "ruby_lsp/document"
37
35
  require "ruby_lsp/ruby_document"
@@ -39,6 +37,46 @@ require "ruby_lsp/erb_document"
39
37
  require "ruby_lsp/rbs_document"
40
38
  require "ruby_lsp/store"
41
39
  require "ruby_lsp/addon"
40
+
41
+ # Response builders
42
+ require "ruby_lsp/response_builders/response_builder"
43
+ require "ruby_lsp/response_builders/collection_response_builder"
44
+ require "ruby_lsp/response_builders/document_symbol"
45
+ require "ruby_lsp/response_builders/hover"
46
+ require "ruby_lsp/response_builders/semantic_highlighting"
47
+ require "ruby_lsp/response_builders/signature_help"
48
+
49
+ # Request support
50
+ require "ruby_lsp/requests/support/selection_range"
51
+ require "ruby_lsp/requests/support/annotation"
52
+ require "ruby_lsp/requests/support/sorbet"
53
+ require "ruby_lsp/requests/support/common"
54
+ require "ruby_lsp/requests/support/formatter"
42
55
  require "ruby_lsp/requests/support/rubocop_runner"
43
56
  require "ruby_lsp/requests/support/rubocop_formatter"
44
57
  require "ruby_lsp/requests/support/syntax_tree_formatter"
58
+
59
+ # Requests
60
+ require "ruby_lsp/requests/request"
61
+ require "ruby_lsp/requests/code_action_resolve"
62
+ require "ruby_lsp/requests/code_actions"
63
+ require "ruby_lsp/requests/code_lens"
64
+ require "ruby_lsp/requests/completion_resolve"
65
+ require "ruby_lsp/requests/completion"
66
+ require "ruby_lsp/requests/definition"
67
+ require "ruby_lsp/requests/diagnostics"
68
+ require "ruby_lsp/requests/document_highlight"
69
+ require "ruby_lsp/requests/document_link"
70
+ require "ruby_lsp/requests/document_symbol"
71
+ require "ruby_lsp/requests/folding_ranges"
72
+ require "ruby_lsp/requests/formatting"
73
+ require "ruby_lsp/requests/hover"
74
+ require "ruby_lsp/requests/inlay_hints"
75
+ require "ruby_lsp/requests/on_type_formatting"
76
+ require "ruby_lsp/requests/prepare_type_hierarchy"
77
+ require "ruby_lsp/requests/selection_ranges"
78
+ require "ruby_lsp/requests/semantic_highlighting"
79
+ require "ruby_lsp/requests/show_syntax_tree"
80
+ require "ruby_lsp/requests/signature_help"
81
+ require "ruby_lsp/requests/type_hierarchy_supertypes"
82
+ require "ruby_lsp/requests/workspace_symbol"
@@ -44,6 +44,7 @@ module RubyLsp
44
44
  @group_id_stack = T.let([], T::Array[Integer])
45
45
  # We want to avoid adding code lenses for nested definitions
46
46
  @def_depth = T.let(0, Integer)
47
+ @spec_id = T.let(0, Integer)
47
48
 
48
49
  dispatcher.register(
49
50
  self,
@@ -70,6 +71,7 @@ module RubyLsp
70
71
  name: class_name,
71
72
  command: generate_test_command(group_stack: @group_stack),
72
73
  kind: :group,
74
+ id: generate_fully_qualified_id(group_stack: @group_stack),
73
75
  )
74
76
 
75
77
  @group_id_stack.push(@group_id)
@@ -106,6 +108,7 @@ module RubyLsp
106
108
  name: method_name,
107
109
  command: generate_test_command(method_name: method_name, group_stack: @group_stack),
108
110
  kind: :example,
111
+ id: generate_fully_qualified_id(group_stack: @group_stack, method_name: method_name),
109
112
  )
110
113
  end
111
114
  end
@@ -166,19 +169,20 @@ module RubyLsp
166
169
  @visibility_stack.push([prev_visibility, prev_visibility])
167
170
  if node.name == DESCRIBE_KEYWORD
168
171
  @group_id_stack.pop
172
+ @group_stack.pop
169
173
  end
170
174
  end
171
175
 
172
176
  private
173
177
 
174
- sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
175
- def add_test_code_lens(node, name:, command:, kind:)
178
+ sig { params(node: Prism::Node, name: String, command: String, kind: Symbol, id: String).void }
179
+ def add_test_code_lens(node, name:, command:, kind:, id: name)
176
180
  # don't add code lenses if the test library is not supported or unknown
177
181
  return unless SUPPORTED_TEST_LIBRARIES.include?(@global_state.test_library) && @path
178
182
 
179
183
  arguments = [
180
184
  @path,
181
- name,
185
+ id,
182
186
  command,
183
187
  {
184
188
  start_line: node.location.start_line - 1,
@@ -186,6 +190,7 @@ module RubyLsp
186
190
  end_line: node.location.end_line - 1,
187
191
  end_column: node.location.end_column,
188
192
  },
193
+ name,
189
194
  ]
190
195
 
191
196
  grouping_data = { group_id: @group_id_stack.last, kind: kind }
@@ -247,7 +252,7 @@ module RubyLsp
247
252
  # We know the entire path, do an exact match
248
253
  " --name " + Shellwords.escape(group_stack.join("::")) + "#" + Shellwords.escape(method_name)
249
254
  elsif spec_name
250
- " --name " + "/#{Shellwords.escape(spec_name)}/"
255
+ " --name " + "\"/^#{Shellwords.escape(group_stack.join("::"))}##{Shellwords.escape(spec_name)}$/\""
251
256
  else
252
257
  # Execute all tests of the selected class and tests in
253
258
  # modules/classes nested inside of that class
@@ -282,15 +287,39 @@ module RubyLsp
282
287
 
283
288
  return unless name
284
289
 
290
+ if kind == :example
291
+ # Increment spec_id for each example
292
+ @spec_id += 1
293
+ else
294
+ # Reset spec_id when entering a new group
295
+ @spec_id = 0
296
+ @group_stack.push(name)
297
+ end
298
+
285
299
  if @path
300
+ method_name = format("test_%04d_%s", @spec_id, name) if kind == :example
286
301
  add_test_code_lens(
287
302
  node,
288
303
  name: name,
289
- command: generate_test_command(spec_name: name),
304
+ command: generate_test_command(group_stack: @group_stack, spec_name: method_name),
290
305
  kind: kind,
306
+ id: generate_fully_qualified_id(group_stack: @group_stack, method_name: method_name),
291
307
  )
292
308
  end
293
309
  end
310
+
311
+ sig { params(group_stack: T::Array[String], method_name: T.nilable(String)).returns(String) }
312
+ def generate_fully_qualified_id(group_stack:, method_name: nil)
313
+ if method_name
314
+ # For tests, this will be the test class and method name: `Foo::BarTest#test_baz`.
315
+ # For specs, this will be the nested descriptions and formatted test name: `a::b::c#test_001_foo`.
316
+ group_stack.join("::") + "#" + method_name
317
+ else
318
+ # For tests, this will be the test class: `Foo::BarTest`.
319
+ # For specs, this will be the nested descriptions: `a::b::c`.
320
+ group_stack.join("::")
321
+ end
322
+ end
294
323
  end
295
324
  end
296
325
  end
@@ -104,7 +104,7 @@ module RubyLsp
104
104
  name = constant_name(node)
105
105
  return if name.nil?
106
106
 
107
- candidates = @index.prefix_search(name, @node_context.nesting)
107
+ candidates = @index.constant_completion_candidates(name, @node_context.nesting)
108
108
  candidates.each do |entries|
109
109
  complete_name = T.must(entries.first).name
110
110
  @response_builder << build_entry_completion(
@@ -124,7 +124,13 @@ module RubyLsp
124
124
  # no sigil, Sorbet will still provide completion for constants
125
125
  return if @sorbet_level != RubyDocument::SorbetLevel::Ignore
126
126
 
127
- name = constant_name(node)
127
+ name = begin
128
+ node.full_name
129
+ rescue Prism::ConstantPathNode::MissingNodesInConstantPathError
130
+ node.slice
131
+ rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError
132
+ nil
133
+ end
128
134
  return if name.nil?
129
135
 
130
136
  constant_path_completion(name, range_from_location(node.location))
@@ -230,7 +236,7 @@ module RubyLsp
230
236
 
231
237
  real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
232
238
 
233
- candidates = @index.prefix_search(
239
+ candidates = @index.constant_completion_candidates(
234
240
  "#{real_namespace}::#{incomplete_name}",
235
241
  top_level_reference ? [] : nesting,
236
242
  )
@@ -240,8 +246,16 @@ module RubyLsp
240
246
  first_entry = T.must(entries.first)
241
247
  next if first_entry.private? && !first_entry.name.start_with?("#{nesting}::")
242
248
 
243
- constant_name = first_entry.name.delete_prefix("#{real_namespace}::")
244
- full_name = aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
249
+ entry_name = first_entry.name
250
+ full_name = if aliased_namespace != real_namespace
251
+ constant_name = entry_name.delete_prefix("#{real_namespace}::")
252
+ aliased_namespace.empty? ? constant_name : "#{aliased_namespace}::#{constant_name}"
253
+ elsif !entry_name.start_with?(aliased_namespace)
254
+ *_, short_name = entry_name.split("::")
255
+ "#{aliased_namespace}::#{short_name}"
256
+ else
257
+ entry_name
258
+ end
245
259
 
246
260
  @response_builder << build_entry_completion(
247
261
  full_name,
@@ -545,7 +559,7 @@ module RubyLsp
545
559
  sig { params(entry_name: String).returns(T::Boolean) }
546
560
  def top_level?(entry_name)
547
561
  nesting = @node_context.nesting
548
- nesting.length.downto(0).each do |i|
562
+ nesting.length.downto(0) do |i|
549
563
  prefix = T.must(nesting[0...i]).join("::")
550
564
  full_name = prefix.empty? ? entry_name : "#{prefix}::#{entry_name}"
551
565
  next if full_name == entry_name
@@ -12,14 +12,12 @@ module RubyLsp
12
12
  sig do
13
13
  params(
14
14
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::InlayHint],
15
- range: T::Range[Integer],
16
15
  hints_configuration: RequestConfig,
17
16
  dispatcher: Prism::Dispatcher,
18
17
  ).void
19
18
  end
20
- def initialize(response_builder, range, hints_configuration, dispatcher)
19
+ def initialize(response_builder, hints_configuration, dispatcher)
21
20
  @response_builder = response_builder
22
- @range = range
23
21
  @hints_configuration = hints_configuration
24
22
 
25
23
  dispatcher.register(self, :on_rescue_node_enter, :on_implicit_node_enter)
@@ -31,7 +29,6 @@ module RubyLsp
31
29
  return unless node.exceptions.empty?
32
30
 
33
31
  loc = node.location
34
- return unless visible?(node, @range)
35
32
 
36
33
  @response_builder << Interface::InlayHint.new(
37
34
  position: { line: loc.start_line - 1, character: loc.start_column + RESCUE_STRING_LENGTH },
@@ -44,7 +41,6 @@ module RubyLsp
44
41
  sig { params(node: Prism::ImplicitNode).void }
45
42
  def on_implicit_node_enter(node)
46
43
  return unless @hints_configuration.enabled?(:implicitHashValue)
47
- return unless visible?(node, @range)
48
44
 
49
45
  node_value = node.value
50
46
  loc = node.location
@@ -69,17 +65,6 @@ module RubyLsp
69
65
  tooltip: tooltip,
70
66
  )
71
67
  end
72
-
73
- private
74
-
75
- sig { params(node: T.nilable(Prism::Node), range: T.nilable(T::Range[Integer])).returns(T::Boolean) }
76
- def visible?(node, range)
77
- return true if range.nil?
78
- return false if node.nil?
79
-
80
- loc = node.location
81
- range.cover?(loc.start_line - 1) && range.cover?(loc.end_line - 1)
82
- end
83
68
  end
84
69
  end
85
70
  end
@@ -42,17 +42,46 @@ module RubyLsp
42
42
  target_method = methods.first
43
43
  return unless target_method
44
44
 
45
- parameters = target_method.parameters
46
- name = target_method.name
45
+ signatures = target_method.signatures
47
46
 
48
47
  # If the method doesn't have any parameters, there's no need to show signature help
49
- return if parameters.empty?
48
+ return if signatures.empty?
49
+
50
+ name = target_method.name
51
+ title = +""
52
+
53
+ extra_links = if type.is_a?(TypeInferrer::GuessedType)
54
+ title << "\n\nGuessed receiver: #{type.name}"
55
+ "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
56
+ end
50
57
 
51
- label = "#{name}(#{parameters.map(&:decorated_name).join(", ")})"
58
+ active_signature, active_parameter = determine_active_signature_and_parameter(node, signatures)
52
59
 
60
+ signature_help = Interface::SignatureHelp.new(
61
+ signatures: generate_signatures(signatures, name, methods, title, extra_links),
62
+ active_signature: active_signature,
63
+ active_parameter: active_parameter,
64
+ )
65
+ @response_builder.replace(signature_help)
66
+ end
67
+
68
+ private
69
+
70
+ sig do
71
+ params(node: Prism::CallNode, signatures: T::Array[RubyIndexer::Entry::Signature]).returns([Integer, Integer])
72
+ end
73
+ def determine_active_signature_and_parameter(node, signatures)
53
74
  arguments_node = node.arguments
54
75
  arguments = arguments_node&.arguments || []
55
- active_parameter = (arguments.length - 1).clamp(0, parameters.length - 1)
76
+
77
+ # Find the first signature that matches the current arguments. If the user is invoking a method incorrectly and
78
+ # none of the signatures match, we show the first one
79
+ active_sig_index = signatures.find_index do |signature|
80
+ signature.matches?(arguments)
81
+ end || 0
82
+
83
+ parameter_length = [T.must(signatures[active_sig_index]).parameters.length - 1, 0].max
84
+ active_parameter = (arguments.length - 1).clamp(0, parameter_length)
56
85
 
57
86
  # If there are arguments, then we need to check if there's a trailing comma after the end of the last argument
58
87
  # to advance the active parameter to the next one
@@ -61,27 +90,29 @@ module RubyLsp
61
90
  active_parameter += 1
62
91
  end
63
92
 
64
- title = +""
65
-
66
- extra_links = if type.is_a?(TypeInferrer::GuessedType)
67
- title << "\n\nGuessed receiver: #{type.name}"
68
- "[Learn more about guessed types](#{GUESSED_TYPES_URL})"
69
- end
93
+ [active_sig_index, active_parameter]
94
+ end
70
95
 
71
- signature_help = Interface::SignatureHelp.new(
72
- signatures: [
73
- Interface::SignatureInformation.new(
74
- label: label,
75
- parameters: parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
76
- documentation: Interface::MarkupContent.new(
77
- kind: "markdown",
78
- value: markdown_from_index_entries(title, methods, extra_links: extra_links),
79
- ),
96
+ sig do
97
+ params(
98
+ signatures: T::Array[RubyIndexer::Entry::Signature],
99
+ method_name: String,
100
+ methods: T::Array[RubyIndexer::Entry],
101
+ title: String,
102
+ extra_links: T.nilable(String),
103
+ ).returns(T::Array[Interface::SignatureInformation])
104
+ end
105
+ def generate_signatures(signatures, method_name, methods, title, extra_links)
106
+ signatures.map do |signature|
107
+ Interface::SignatureInformation.new(
108
+ label: "#{method_name}(#{signature.format})",
109
+ parameters: signature.parameters.map { |param| Interface::ParameterInformation.new(label: param.name) },
110
+ documentation: Interface::MarkupContent.new(
111
+ kind: "markdown",
112
+ value: markdown_from_index_entries(title, methods, extra_links: extra_links),
80
113
  ),
81
- ],
82
- active_parameter: active_parameter,
83
- )
84
- @response_builder.replace(signature_help)
114
+ )
115
+ end
85
116
  end
86
117
  end
87
118
  end
@@ -14,18 +14,19 @@ module RubyLsp
14
14
  super
15
15
  end
16
16
 
17
- sig { override.returns(ParseResultType) }
18
- def parse
19
- return @parse_result unless @needs_parsing
17
+ sig { override.returns(T::Boolean) }
18
+ def parse!
19
+ return false unless @needs_parsing
20
20
 
21
21
  @needs_parsing = false
22
22
 
23
23
  _, _, declarations = RBS::Parser.parse_signature(@source)
24
24
  @syntax_error = false
25
25
  @parse_result = declarations
26
+ true
26
27
  rescue RBS::ParsingError
27
28
  @syntax_error = true
28
- @parse_result
29
+ true
29
30
  end
30
31
 
31
32
  sig { override.returns(T::Boolean) }
@@ -3,24 +3,9 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Code action resolve demo](../../code_action_resolve.gif)
7
- #
8
6
  # The [code action resolve](https://microsoft.github.io/language-server-protocol/specification#codeAction_resolve)
9
7
  # request is used to to resolve the edit field for a given code action, if it is not already provided in the
10
8
  # textDocument/codeAction response. We can use it for scenarios that require more computation such as refactoring.
11
- #
12
- # # Example: Extract to variable
13
- #
14
- # ```ruby
15
- # # Before:
16
- # 1 + 1 # Select the text and use Refactor: Extract Variable
17
- #
18
- # # After:
19
- # new_variable = 1 + 1
20
- # new_variable
21
- #
22
- # ```
23
- #
24
9
  class CodeActionResolve < Request
25
10
  extend T::Sig
26
11
  include Support::Common
@@ -3,19 +3,9 @@
3
3
 
4
4
  module RubyLsp
5
5
  module Requests
6
- # ![Code actions demo](../../code_actions.gif)
7
- #
8
6
  # The [code actions](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeAction)
9
7
  # request informs the editor of RuboCop quick fixes that can be applied. These are accessible by hovering over a
10
8
  # specific diagnostic.
11
- #
12
- # # Example
13
- #
14
- # ```ruby
15
- # def say_hello
16
- # puts "Hello" # --> code action: quick fix indentation
17
- # end
18
- # ```
19
9
  class CodeActions < Request
20
10
  extend T::Sig
21
11
 
@@ -7,19 +7,9 @@ require "ruby_lsp/listeners/code_lens"
7
7
 
8
8
  module RubyLsp
9
9
  module Requests
10
- # ![Code lens demo](../../code_lens.gif)
11
- #
12
10
  # The
13
11
  # [code lens](https://microsoft.github.io/language-server-protocol/specification#textDocument_codeLens)
14
- # request informs the editor of runnable commands such as testing and debugging
15
- #
16
- # # Example
17
- #
18
- # ```ruby
19
- # # Run | Run in Terminal | Debug
20
- # class Test < Minitest::Test
21
- # end
22
- # ```
12
+ # request informs the editor of runnable commands such as testing and debugging.
23
13
  class CodeLens < Request
24
14
  extend T::Sig
25
15