ruby-lsp 0.14.5 → 0.15.0

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