ruby-lsp 0.17.17 → 0.18.3

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