ruby-lsp 0.14.5 → 0.15.0

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/exe/ruby-lsp +1 -16
  4. data/exe/ruby-lsp-check +13 -22
  5. data/lib/ruby_indexer/lib/ruby_indexer/collector.rb +21 -0
  6. data/lib/ruby_indexer/lib/ruby_indexer/configuration.rb +12 -24
  7. data/lib/ruby_indexer/lib/ruby_indexer/entry.rb +16 -0
  8. data/lib/ruby_indexer/lib/ruby_indexer/index.rb +3 -2
  9. data/lib/ruby_indexer/test/classes_and_modules_test.rb +46 -0
  10. data/lib/ruby_indexer/test/configuration_test.rb +2 -11
  11. data/lib/ruby_indexer/test/index_test.rb +5 -0
  12. data/lib/ruby_lsp/addon.rb +18 -5
  13. data/lib/ruby_lsp/base_server.rb +147 -0
  14. data/lib/ruby_lsp/document.rb +0 -5
  15. data/lib/ruby_lsp/{requests/support/dependency_detector.rb → global_state.rb} +30 -9
  16. data/lib/ruby_lsp/internal.rb +2 -1
  17. data/lib/ruby_lsp/listeners/code_lens.rb +66 -18
  18. data/lib/ruby_lsp/listeners/completion.rb +13 -14
  19. data/lib/ruby_lsp/listeners/definition.rb +4 -3
  20. data/lib/ruby_lsp/listeners/document_symbol.rb +91 -3
  21. data/lib/ruby_lsp/listeners/hover.rb +6 -5
  22. data/lib/ruby_lsp/listeners/signature_help.rb +7 -4
  23. data/lib/ruby_lsp/load_sorbet.rb +62 -0
  24. data/lib/ruby_lsp/requests/code_lens.rb +4 -3
  25. data/lib/ruby_lsp/requests/completion.rb +15 -4
  26. data/lib/ruby_lsp/requests/completion_resolve.rb +56 -0
  27. data/lib/ruby_lsp/requests/definition.rb +18 -5
  28. data/lib/ruby_lsp/requests/document_symbol.rb +3 -3
  29. data/lib/ruby_lsp/requests/hover.rb +9 -5
  30. data/lib/ruby_lsp/requests/request.rb +51 -0
  31. data/lib/ruby_lsp/requests/signature_help.rb +4 -3
  32. data/lib/ruby_lsp/requests/support/common.rb +25 -5
  33. data/lib/ruby_lsp/requests/support/rubocop_runner.rb +4 -0
  34. data/lib/ruby_lsp/requests/workspace_symbol.rb +5 -4
  35. data/lib/ruby_lsp/requests.rb +2 -0
  36. data/lib/ruby_lsp/server.rb +756 -142
  37. data/lib/ruby_lsp/store.rb +0 -8
  38. data/lib/ruby_lsp/test_helper.rb +45 -0
  39. data/lib/ruby_lsp/utils.rb +68 -33
  40. metadata +8 -5
  41. data/lib/ruby_lsp/executor.rb +0 -612
@@ -22,16 +22,19 @@ module RubyLsp
22
22
  SUPPORTED_TEST_LIBRARIES = T.let(["minitest", "test-unit"], T::Array[String])
23
23
  DESCRIBE_KEYWORD = T.let(:describe, Symbol)
24
24
  IT_KEYWORD = T.let(:it, Symbol)
25
+ DYNAMIC_REFERENCE_MARKER = T.let("<dynamic_reference>", String)
25
26
 
26
27
  sig do
27
28
  params(
28
29
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
30
+ global_state: GlobalState,
29
31
  uri: URI::Generic,
30
32
  dispatcher: Prism::Dispatcher,
31
33
  ).void
32
34
  end
33
- def initialize(response_builder, uri, dispatcher)
35
+ def initialize(response_builder, global_state, uri, dispatcher)
34
36
  @response_builder = response_builder
37
+ @global_state = global_state
35
38
  @uri = T.let(uri, URI::Generic)
36
39
  @path = T.let(uri.to_standardized_path, T.nilable(String))
37
40
  # visibility_stack is a stack of [current_visibility, previous_visibility]
@@ -44,6 +47,8 @@ module RubyLsp
44
47
  self,
45
48
  :on_class_node_enter,
46
49
  :on_class_node_leave,
50
+ :on_module_node_enter,
51
+ :on_module_node_leave,
47
52
  :on_def_node_enter,
48
53
  :on_call_node_enter,
49
54
  :on_call_node_leave,
@@ -60,7 +65,7 @@ module RubyLsp
60
65
  add_test_code_lens(
61
66
  node,
62
67
  name: class_name,
63
- command: generate_test_command(group_name: class_name),
68
+ command: generate_test_command(group_stack: @group_stack),
64
69
  kind: :group,
65
70
  )
66
71
  end
@@ -88,13 +93,27 @@ module RubyLsp
88
93
  add_test_code_lens(
89
94
  node,
90
95
  name: method_name,
91
- command: generate_test_command(method_name: method_name, group_name: class_name),
96
+ command: generate_test_command(method_name: method_name, group_stack: @group_stack),
92
97
  kind: :example,
93
98
  )
94
99
  end
95
100
  end
96
101
  end
97
102
 
103
+ sig { params(node: Prism::ModuleNode).void }
104
+ def on_module_node_enter(node)
105
+ if (path = namespace_constant_name(node))
106
+ @group_stack.push(path)
107
+ else
108
+ @group_stack.push(DYNAMIC_REFERENCE_MARKER)
109
+ end
110
+ end
111
+
112
+ sig { params(node: Prism::ModuleNode).void }
113
+ def on_module_node_leave(node)
114
+ @group_stack.pop
115
+ end
116
+
98
117
  sig { params(node: Prism::CallNode).void }
99
118
  def on_call_node_enter(node)
100
119
  name = node.name
@@ -139,7 +158,7 @@ module RubyLsp
139
158
  sig { params(node: Prism::Node, name: String, command: String, kind: Symbol).void }
140
159
  def add_test_code_lens(node, name:, command:, kind:)
141
160
  # don't add code lenses if the test library is not supported or unknown
142
- return unless SUPPORTED_TEST_LIBRARIES.include?(DependencyDetector.instance.detected_test_library) && @path
161
+ return unless SUPPORTED_TEST_LIBRARIES.include?(@global_state.test_library) && @path
143
162
 
144
163
  arguments = [
145
164
  @path,
@@ -181,18 +200,45 @@ module RubyLsp
181
200
  )
182
201
  end
183
202
 
184
- sig { params(group_name: String, method_name: T.nilable(String)).returns(String) }
185
- def generate_test_command(group_name:, method_name: nil)
203
+ sig do
204
+ params(
205
+ group_stack: T::Array[String],
206
+ spec_name: T.nilable(String),
207
+ method_name: T.nilable(String),
208
+ ).returns(String)
209
+ end
210
+ def generate_test_command(group_stack: [], spec_name: nil, method_name: nil)
186
211
  command = BASE_COMMAND + T.must(@path)
187
212
 
188
- case DependencyDetector.instance.detected_test_library
213
+ case @global_state.test_library
189
214
  when "minitest"
190
- command += if method_name
191
- " --name " + "/#{Shellwords.escape(group_name + "#" + method_name)}/"
215
+ last_dynamic_reference_index = group_stack.rindex(DYNAMIC_REFERENCE_MARKER)
216
+ command += if last_dynamic_reference_index
217
+ # In cases where the test path looks like `foo::Bar`
218
+ # the best we can do is match everything to the right of it.
219
+ # Tests are classes, dynamic references are only a thing for modules,
220
+ # so there must be something to the left of the available path.
221
+ group_stack = T.must(group_stack[last_dynamic_reference_index + 1..])
222
+ if method_name
223
+ " --name " + "/::#{Shellwords.escape(group_stack.join("::") + "#" + method_name)}$/"
224
+ else
225
+ # When clicking on a CodeLens for `Test`, `(#|::)` will match all tests
226
+ # that are registered on the class itself (matches after `#`) and all tests
227
+ # that are nested inside of that class in other modules/classes (matches after `::`)
228
+ " --name " + "\"/::#{Shellwords.escape(group_stack.join("::"))}(#|::)/\""
229
+ end
230
+ elsif method_name
231
+ # We know the entire path, do an exact match
232
+ " --name " + Shellwords.escape(group_stack.join("::") + "#" + method_name)
233
+ elsif spec_name
234
+ " --name " + "/#{Shellwords.escape(spec_name)}/"
192
235
  else
193
- " --name " + "/#{Shellwords.escape(group_name)}/"
236
+ # Execute all tests of the selected class and tests in
237
+ # modules/classes nested inside of that class
238
+ " --name " + "\"/^#{Shellwords.escape(group_stack.join("::"))}(#|::)/\""
194
239
  end
195
240
  when "test-unit"
241
+ group_name = T.must(group_stack.last)
196
242
  command += " --testcase " + "/#{Shellwords.escape(group_name)}/"
197
243
 
198
244
  if method_name
@@ -214,18 +260,20 @@ module RubyLsp
214
260
  name = case first_argument
215
261
  when Prism::StringNode
216
262
  first_argument.content
217
- when Prism::ConstantReadNode
218
- first_argument.full_name
263
+ when Prism::ConstantReadNode, Prism::ConstantPathNode
264
+ constant_name(first_argument)
219
265
  end
220
266
 
221
267
  return unless name
222
268
 
223
- add_test_code_lens(
224
- node,
225
- name: name,
226
- command: generate_test_command(group_name: name),
227
- kind: kind,
228
- )
269
+ if @path
270
+ add_test_code_lens(
271
+ node,
272
+ name: name,
273
+ command: generate_test_command(spec_name: name),
274
+ kind: kind,
275
+ )
276
+ end
229
277
  end
230
278
  end
231
279
  end
@@ -10,16 +10,17 @@ module RubyLsp
10
10
  sig do
11
11
  params(
12
12
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
13
- index: RubyIndexer::Index,
13
+ global_state: GlobalState,
14
14
  nesting: T::Array[String],
15
15
  typechecker_enabled: T::Boolean,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  uri: URI::Generic,
18
18
  ).void
19
19
  end
20
- def initialize(response_builder, index, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
20
+ def initialize(response_builder, global_state, nesting, typechecker_enabled, dispatcher, uri) # rubocop:disable Metrics/ParameterLists
21
21
  @response_builder = response_builder
22
- @index = index
22
+ @global_state = global_state
23
+ @index = T.let(global_state.index, RubyIndexer::Index)
23
24
  @nesting = nesting
24
25
  @typechecker_enabled = typechecker_enabled
25
26
  @uri = uri
@@ -35,7 +36,7 @@ module RubyLsp
35
36
  # Handle completion on regular constant references (e.g. `Bar`)
36
37
  sig { params(node: Prism::ConstantReadNode).void }
37
38
  def on_constant_read_node_enter(node)
38
- return if DependencyDetector.instance.typechecker
39
+ return if @global_state.typechecker
39
40
 
40
41
  name = constant_name(node)
41
42
  return if name.nil?
@@ -56,7 +57,7 @@ module RubyLsp
56
57
  # Handle completion on namespaced constant references (e.g. `Foo::Bar`)
57
58
  sig { params(node: Prism::ConstantPathNode).void }
58
59
  def on_constant_path_node_enter(node)
59
- return if DependencyDetector.instance.typechecker
60
+ return if @global_state.typechecker
60
61
 
61
62
  name = constant_name(node)
62
63
  return if name.nil?
@@ -77,7 +78,10 @@ module RubyLsp
77
78
 
78
79
  real_namespace = @index.follow_aliased_namespace(T.must(namespace_entries.first).name)
79
80
 
80
- candidates = @index.prefix_search("#{real_namespace}::#{incomplete_name}", top_level_reference ? [] : @nesting)
81
+ candidates = @index.prefix_search(
82
+ "#{real_namespace}::#{incomplete_name}",
83
+ top_level_reference ? [] : @nesting,
84
+ )
81
85
  candidates.each do |entries|
82
86
  # The only time we may have a private constant reference from outside of the namespace is if we're dealing
83
87
  # with ConstantPath and the entry name doesn't start with the current nesting
@@ -124,7 +128,9 @@ module RubyLsp
124
128
 
125
129
  return unless path_node_to_complete.is_a?(Prism::StringNode)
126
130
 
127
- @index.search_require_paths(path_node_to_complete.content).map!(&:require_path).sort!.each do |path|
131
+ matched_indexable_paths = @index.search_require_paths(path_node_to_complete.content)
132
+
133
+ matched_indexable_paths.map!(&:require_path).sort!.each do |path|
128
134
  @response_builder << build_completion(T.must(path), path_node_to_complete)
129
135
  end
130
136
  end
@@ -284,13 +290,6 @@ module RubyLsp
284
290
  new_text: insertion_text,
285
291
  ),
286
292
  kind: kind,
287
- label_details: Interface::CompletionItemLabelDetails.new(
288
- description: entries.map(&:file_name).join(","),
289
- ),
290
- documentation: Interface::MarkupContent.new(
291
- kind: "markdown",
292
- value: markdown_from_index_entries(real_name, entries),
293
- ),
294
293
  )
295
294
  end
296
295
 
@@ -10,18 +10,19 @@ module RubyLsp
10
10
  sig do
11
11
  params(
12
12
  response_builder: ResponseBuilders::CollectionResponseBuilder[Interface::Location],
13
+ global_state: GlobalState,
13
14
  uri: URI::Generic,
14
15
  nesting: T::Array[String],
15
- index: RubyIndexer::Index,
16
16
  dispatcher: Prism::Dispatcher,
17
17
  typechecker_enabled: T::Boolean,
18
18
  ).void
19
19
  end
20
- def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
20
+ def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
21
21
  @response_builder = response_builder
22
+ @global_state = global_state
23
+ @index = T.let(global_state.index, RubyIndexer::Index)
22
24
  @uri = uri
23
25
  @nesting = nesting
24
- @index = index
25
26
  @typechecker_enabled = typechecker_enabled
26
27
 
27
28
  dispatcher.register(
@@ -12,17 +12,20 @@ module RubyLsp
12
12
  sig do
13
13
  params(
14
14
  response_builder: ResponseBuilders::DocumentSymbol,
15
+ uri: URI::Generic,
15
16
  dispatcher: Prism::Dispatcher,
16
17
  ).void
17
18
  end
18
- def initialize(response_builder, dispatcher)
19
+ def initialize(response_builder, uri, dispatcher)
19
20
  @response_builder = response_builder
21
+ @uri = uri
20
22
 
21
23
  dispatcher.register(
22
24
  self,
23
25
  :on_class_node_enter,
24
26
  :on_class_node_leave,
25
27
  :on_call_node_enter,
28
+ :on_call_node_leave,
26
29
  :on_constant_path_write_node_enter,
27
30
  :on_constant_write_node_enter,
28
31
  :on_constant_path_or_write_node_enter,
@@ -79,10 +82,24 @@ module RubyLsp
79
82
 
80
83
  sig { params(node: Prism::CallNode).void }
81
84
  def on_call_node_enter(node)
82
- if ATTR_ACCESSORS.include?(node.name)
85
+ node_name = node.name
86
+ if ATTR_ACCESSORS.include?(node_name)
83
87
  handle_attr_accessor(node)
84
- elsif node.name == :alias_method
88
+ elsif node_name == :alias_method
85
89
  handle_alias_method(node)
90
+ elsif node_name == :namespace
91
+ handle_rake_namespace(node)
92
+ elsif node_name == :task
93
+ handle_rake_task(node)
94
+ end
95
+ end
96
+
97
+ sig { params(node: Prism::CallNode).void }
98
+ def on_call_node_leave(node)
99
+ return unless rake?
100
+
101
+ if node.name == :namespace && !node.receiver
102
+ @response_builder.pop
86
103
  end
87
104
  end
88
105
 
@@ -279,6 +296,7 @@ module RubyLsp
279
296
  ).returns(Interface::DocumentSymbol)
280
297
  end
281
298
  def create_document_symbol(name:, kind:, range_location:, selection_range_location:)
299
+ name = "<blank>" if name.empty?
282
300
  symbol = Interface::DocumentSymbol.new(
283
301
  name: name,
284
302
  kind: kind,
@@ -357,6 +375,76 @@ module RubyLsp
357
375
  )
358
376
  end
359
377
  end
378
+
379
+ sig { params(node: Prism::CallNode).void }
380
+ def handle_rake_namespace(node)
381
+ return unless rake?
382
+ return if node.receiver
383
+
384
+ arguments = node.arguments
385
+ return unless arguments
386
+
387
+ name_argument = arguments.arguments.first
388
+ return unless name_argument
389
+
390
+ name = case name_argument
391
+ when Prism::StringNode then name_argument.content
392
+ when Prism::SymbolNode then name_argument.value
393
+ end
394
+
395
+ return if name.nil? || name.empty?
396
+
397
+ @response_builder << create_document_symbol(
398
+ name: name,
399
+ kind: Constant::SymbolKind::MODULE,
400
+ range_location: name_argument.location,
401
+ selection_range_location: name_argument.location,
402
+ )
403
+ end
404
+
405
+ sig { params(node: Prism::CallNode).void }
406
+ def handle_rake_task(node)
407
+ return unless rake?
408
+ return if node.receiver
409
+
410
+ arguments = node.arguments
411
+ return unless arguments
412
+
413
+ name_argument = arguments.arguments.first
414
+ return unless name_argument
415
+
416
+ name = case name_argument
417
+ when Prism::StringNode then name_argument.content
418
+ when Prism::SymbolNode then name_argument.value
419
+ when Prism::KeywordHashNode
420
+ first_element = name_argument.elements.first
421
+ if first_element.is_a?(Prism::AssocNode)
422
+ key = first_element.key
423
+ case key
424
+ when Prism::StringNode then key.content
425
+ when Prism::SymbolNode then key.value
426
+ end
427
+ end
428
+ end
429
+
430
+ return if name.nil? || name.empty?
431
+
432
+ create_document_symbol(
433
+ name: name,
434
+ kind: Constant::SymbolKind::METHOD,
435
+ range_location: name_argument.location,
436
+ selection_range_location: name_argument.location,
437
+ )
438
+ end
439
+
440
+ sig { returns(T::Boolean) }
441
+ def rake?
442
+ if (path = @uri.to_standardized_path)
443
+ path.match?(/(Rakefile|\.rake)$/)
444
+ else
445
+ false
446
+ end
447
+ end
360
448
  end
361
449
  end
362
450
  end
@@ -28,18 +28,19 @@ module RubyLsp
28
28
  sig do
29
29
  params(
30
30
  response_builder: ResponseBuilders::Hover,
31
+ global_state: GlobalState,
31
32
  uri: URI::Generic,
32
33
  nesting: T::Array[String],
33
- index: RubyIndexer::Index,
34
34
  dispatcher: Prism::Dispatcher,
35
35
  typechecker_enabled: T::Boolean,
36
36
  ).void
37
37
  end
38
- def initialize(response_builder, uri, nesting, index, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
38
+ def initialize(response_builder, global_state, uri, nesting, dispatcher, typechecker_enabled) # rubocop:disable Metrics/ParameterLists
39
39
  @response_builder = response_builder
40
+ @global_state = global_state
41
+ @index = T.let(global_state.index, RubyIndexer::Index)
40
42
  @path = T.let(uri.to_standardized_path, T.nilable(String))
41
43
  @nesting = nesting
42
- @index = index
43
44
  @typechecker_enabled = typechecker_enabled
44
45
 
45
46
  dispatcher.register(
@@ -63,14 +64,14 @@ module RubyLsp
63
64
 
64
65
  sig { params(node: Prism::ConstantWriteNode).void }
65
66
  def on_constant_write_node_enter(node)
66
- return if DependencyDetector.instance.typechecker
67
+ return if @global_state.typechecker
67
68
 
68
69
  generate_hover(node.name.to_s, node.name_loc)
69
70
  end
70
71
 
71
72
  sig { params(node: Prism::ConstantPathNode).void }
72
73
  def on_constant_path_node_enter(node)
73
- return if DependencyDetector.instance.typechecker
74
+ return if @global_state.typechecker
74
75
 
75
76
  name = constant_name(node)
76
77
  return if name.nil?
@@ -10,21 +10,24 @@ module RubyLsp
10
10
  sig do
11
11
  params(
12
12
  response_builder: ResponseBuilders::SignatureHelp,
13
+ global_state: GlobalState,
13
14
  nesting: T::Array[String],
14
- index: RubyIndexer::Index,
15
15
  dispatcher: Prism::Dispatcher,
16
+ typechecker_enabled: T::Boolean,
16
17
  ).void
17
18
  end
18
- def initialize(response_builder, nesting, index, dispatcher)
19
+ def initialize(response_builder, global_state, nesting, dispatcher, typechecker_enabled)
20
+ @typechecker_enabled = typechecker_enabled
19
21
  @response_builder = response_builder
22
+ @global_state = global_state
23
+ @index = T.let(global_state.index, RubyIndexer::Index)
20
24
  @nesting = nesting
21
- @index = index
22
25
  dispatcher.register(self, :on_call_node_enter)
23
26
  end
24
27
 
25
28
  sig { params(node: Prism::CallNode).void }
26
29
  def on_call_node_enter(node)
27
- return if DependencyDetector.instance.typechecker
30
+ return if @typechecker_enabled
28
31
  return unless self_receiver?(node)
29
32
 
30
33
  message = node.message
@@ -0,0 +1,62 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "sorbet-runtime"
5
+
6
+ begin
7
+ T::Configuration.default_checked_level = :never
8
+ # Suppresses call validation errors
9
+ T::Configuration.call_validation_error_handler = ->(*) {}
10
+ # Suppresses errors caused by T.cast, T.let, T.must, etc.
11
+ T::Configuration.inline_type_error_handler = ->(*) {}
12
+ # Suppresses errors caused by incorrect parameter ordering
13
+ T::Configuration.sig_validation_error_handler = ->(*) {}
14
+ rescue
15
+ # Need this rescue so that if another gem has
16
+ # already set the checked level by the time we
17
+ # get to it, we don't fail outright.
18
+ nil
19
+ end
20
+
21
+ module RubyLsp
22
+ # No-op all inline type assertions defined in T
23
+ module InlineTypeAssertions
24
+ def absurd(value)
25
+ value
26
+ end
27
+
28
+ def any(type_a, type_b, *types)
29
+ T::Types::Union.new([type_a, type_b, *types])
30
+ end
31
+
32
+ def assert_type!(value, type, checked: true)
33
+ value
34
+ end
35
+
36
+ def bind(value, type, checked: true)
37
+ value
38
+ end
39
+
40
+ def cast(value, type, checked: true)
41
+ value
42
+ end
43
+
44
+ def let(value, type, checked: true)
45
+ value
46
+ end
47
+
48
+ def must(arg)
49
+ arg
50
+ end
51
+
52
+ def nilable(type)
53
+ T::Types::Union.new([type, T::Utils::Nilable::NIL_TYPE])
54
+ end
55
+
56
+ def unsafe(value)
57
+ value
58
+ end
59
+
60
+ T.singleton_class.prepend(self)
61
+ end
62
+ end
@@ -34,20 +34,21 @@ module RubyLsp
34
34
 
35
35
  sig do
36
36
  params(
37
+ global_state: GlobalState,
37
38
  uri: URI::Generic,
38
39
  dispatcher: Prism::Dispatcher,
39
40
  ).void
40
41
  end
41
- def initialize(uri, dispatcher)
42
+ def initialize(global_state, uri, dispatcher)
42
43
  @response_builder = T.let(
43
44
  ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens].new,
44
45
  ResponseBuilders::CollectionResponseBuilder[Interface::CodeLens],
45
46
  )
46
47
  super()
47
- Listeners::CodeLens.new(@response_builder, uri, dispatcher)
48
+ Listeners::CodeLens.new(@response_builder, global_state, uri, dispatcher)
48
49
 
49
50
  Addon.addons.each do |addon|
50
- addon.create_code_lens_listener(@response_builder, uri, dispatcher)
51
+ addon.create_code_lens_listener(@response_builder, global_state, uri, dispatcher)
51
52
  end
52
53
  end
53
54
 
@@ -33,7 +33,7 @@ module RubyLsp
33
33
  sig { returns(Interface::CompletionOptions) }
34
34
  def provider
35
35
  Interface::CompletionOptions.new(
36
- resolve_provider: false,
36
+ resolve_provider: true,
37
37
  trigger_characters: ["/", "\"", "'"],
38
38
  completion_item: {
39
39
  labelDetailsSupport: true,
@@ -45,13 +45,13 @@ module RubyLsp
45
45
  sig do
46
46
  params(
47
47
  document: Document,
48
- index: RubyIndexer::Index,
48
+ global_state: GlobalState,
49
49
  position: T::Hash[Symbol, T.untyped],
50
50
  typechecker_enabled: T::Boolean,
51
51
  dispatcher: Prism::Dispatcher,
52
52
  ).void
53
53
  end
54
- def initialize(document, index, position, typechecker_enabled, dispatcher)
54
+ def initialize(document, global_state, position, typechecker_enabled, dispatcher)
55
55
  super()
56
56
  @target = T.let(nil, T.nilable(Prism::Node))
57
57
  @dispatcher = dispatcher
@@ -68,7 +68,18 @@ module RubyLsp
68
68
  ResponseBuilders::CollectionResponseBuilder[Interface::CompletionItem],
69
69
  )
70
70
 
71
- Listeners::Completion.new(@response_builder, index, nesting, typechecker_enabled, dispatcher, document.uri)
71
+ Listeners::Completion.new(
72
+ @response_builder,
73
+ global_state,
74
+ nesting,
75
+ typechecker_enabled,
76
+ dispatcher,
77
+ document.uri,
78
+ )
79
+
80
+ Addon.addons.each do |addon|
81
+ addon.create_completion_listener(@response_builder, global_state, nesting, dispatcher, document.uri)
82
+ end
72
83
 
73
84
  return unless matched && parent
74
85
 
@@ -0,0 +1,56 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module RubyLsp
5
+ module Requests
6
+ # ![Completion resolve demo](../../completion_resolve.gif)
7
+ #
8
+ # The [completionItem/resolve](https://microsoft.github.io/language-server-protocol/specification#completionItem_resolve)
9
+ # request provides additional information about the currently selected completion. Specifically, the `labelDetails`
10
+ # and `documentation` fields are provided, which are omitted from the completion items returned by
11
+ # `textDocument/completion`.
12
+ #
13
+ # The `labelDetails` field lists the files where the completion item is defined, and the `documentation` field
14
+ # includes any available documentation for those definitions.
15
+ #
16
+ # At most 10 definitions are included, to ensure low latency during request processing and rendering the completion
17
+ # item.
18
+ #
19
+ # # Example
20
+ #
21
+ # ```ruby
22
+ # A # -> as the user cycles through completion items, the documentation will be resolved and displayed
23
+ # ```
24
+ class CompletionResolve < Request
25
+ extend T::Sig
26
+ include Requests::Support::Common
27
+
28
+ # set a limit on the number of documentation entries returned, to avoid rendering performance issues
29
+ # https://github.com/Shopify/ruby-lsp/pull/1798
30
+ MAX_DOCUMENTATION_ENTRIES = 10
31
+
32
+ sig { params(global_state: GlobalState, item: T::Hash[Symbol, T.untyped]).void }
33
+ def initialize(global_state, item)
34
+ super()
35
+ @index = T.let(global_state.index, RubyIndexer::Index)
36
+ @item = item
37
+ end
38
+
39
+ sig { override.returns(Interface::CompletionItem) }
40
+ def perform
41
+ label = @item[:label]
42
+ entries = @index[label] || []
43
+ Interface::CompletionItem.new(
44
+ label: label,
45
+ label_details: Interface::CompletionItemLabelDetails.new(
46
+ description: entries.take(MAX_DOCUMENTATION_ENTRIES).map(&:file_name).join(","),
47
+ ),
48
+ documentation: Interface::MarkupContent.new(
49
+ kind: "markdown",
50
+ value: markdown_from_index_entries(label, entries, MAX_DOCUMENTATION_ENTRIES),
51
+ ),
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end