solargraph 0.54.0 → 0.54.1

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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/lib/solargraph/api_map/store.rb +6 -2
  4. data/lib/solargraph/api_map.rb +15 -8
  5. data/lib/solargraph/complex_type/type_methods.rb +10 -11
  6. data/lib/solargraph/complex_type/unique_type.rb +72 -9
  7. data/lib/solargraph/complex_type.rb +66 -17
  8. data/lib/solargraph/language_server/host/message_worker.rb +29 -3
  9. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  10. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  11. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  12. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  13. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  14. data/lib/solargraph/language_server/progress.rb +19 -2
  15. data/lib/solargraph/library.rb +30 -39
  16. data/lib/solargraph/location.rb +14 -1
  17. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -7
  18. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -1
  19. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  20. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  21. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
  22. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
  23. data/lib/solargraph/parser.rb +2 -5
  24. data/lib/solargraph/pin/base.rb +15 -1
  25. data/lib/solargraph/pin/base_variable.rb +1 -1
  26. data/lib/solargraph/pin/block.rb +5 -23
  27. data/lib/solargraph/pin/callable.rb +147 -0
  28. data/lib/solargraph/pin/closure.rb +8 -3
  29. data/lib/solargraph/pin/common.rb +2 -6
  30. data/lib/solargraph/pin/conversions.rb +3 -2
  31. data/lib/solargraph/pin/instance_variable.rb +2 -2
  32. data/lib/solargraph/pin/method.rb +51 -31
  33. data/lib/solargraph/pin/namespace.rb +4 -4
  34. data/lib/solargraph/pin/parameter.rb +9 -11
  35. data/lib/solargraph/pin/proxy_type.rb +1 -1
  36. data/lib/solargraph/pin/signature.rb +3 -129
  37. data/lib/solargraph/pin.rb +4 -1
  38. data/lib/solargraph/range.rb +2 -4
  39. data/lib/solargraph/rbs_map/conversions.rb +70 -37
  40. data/lib/solargraph/rbs_map/core_fills.rb +6 -6
  41. data/lib/solargraph/shell.rb +17 -2
  42. data/lib/solargraph/source/chain/array.rb +6 -5
  43. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  44. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  45. data/lib/solargraph/source/chain/call.rb +78 -50
  46. data/lib/solargraph/source/chain/link.rb +9 -0
  47. data/lib/solargraph/source/chain/or.rb +1 -1
  48. data/lib/solargraph/source/chain.rb +41 -17
  49. data/lib/solargraph/source/cursor.rb +13 -2
  50. data/lib/solargraph/source.rb +102 -85
  51. data/lib/solargraph/source_map/clip.rb +4 -4
  52. data/lib/solargraph/source_map/data.rb +30 -0
  53. data/lib/solargraph/source_map.rb +28 -16
  54. data/lib/solargraph/type_checker/rules.rb +6 -1
  55. data/lib/solargraph/type_checker.rb +7 -7
  56. data/lib/solargraph/version.rb +1 -1
  57. data/lib/solargraph/views/environment.erb +3 -5
  58. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 39ad27afe3afb59299f8701f9a7ea3f25a9f95379ef68e53ac0e22eb60f50e21
4
- data.tar.gz: 1f01a9d5cdf4aec5b50c245a25b1f7d83e5d6762e5b8f65bc9ce3555654e21ed
3
+ metadata.gz: d6619265fc1ba2cab9154e70c5f4a6626999b685d58e26a805ec606d6804f17b
4
+ data.tar.gz: cfac32a13734ba9b392d27fcb8da7c76b180f0bf64189d17698b2246ce6e3773
5
5
  SHA512:
6
- metadata.gz: 788f17e92a54e782ea1dcf03cb44245df823ba33b4fe866d1afa5d06c7171b40a4e1f93c97c8b26c13b58be7478b9b07cd75d6735da65ecef21ab6a4a61dbb0e
7
- data.tar.gz: 742f43fcdce4a592a80ebe8e3acbd020f39f569cf323621a527e626ae8c6154f7dedb32739b8b62f38e98997ed414ec30471160332ecfb4a2fe5d5052e871333
6
+ metadata.gz: 8d1d40bf5d19b37f6a2ee8cce73e36027140ae402d27326c15e39a961217153e8b269722503ca0a8a3b592bee50cb6227fffcd812f62a1ec8a1bf94219270eea
7
+ data.tar.gz: 627df7fb0bd23b641f0b1a40434a6384def8f32bf318b4f92d243266ee0c3145059adc29ea487dcdc30c914045e5737bca92bff6bd67fd06527f97c2e4690fb8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## 0.54.1 - April 26, 2025
2
+ - Retire more RubyVM-specific code (#797)
3
+ - Add additional docs for key classes, modules and methods (#802)
4
+ - Populate location information from RBS files (#768)
5
+ - Consolidate parameter handling into Pin::Callable (#844)
6
+ - Adjust local variable presence to start after assignment, not before (#864)
7
+ - Resolve params from ref tags (#872)
8
+ - Reduce use of ComplexType.parse() to preserve rooted? information (#870)
9
+ - Ensure yield return types are qualified (#886)
10
+ - Understand type of 'def foo; @foo ||= bar; end' reader methods (#888)
11
+ - Improvements to #inspect output on pins and chains (#895)
12
+ - Block method resolution improvements (#885)
13
+ - Understand mass assignment into instance variables (#893)
14
+ - Library sync and cache invalidation (#903)
15
+ - Handle super and yield scenarios from blocks (#891)
16
+ - Allow core and stdlib documentation to be uncached (#899)
17
+ - Surface variable names in LSP, e.g., textDocument/hover (#910)
18
+ - Keep idle progress notifications alive (#911)
19
+
1
20
  ## 0.54.0 - April 14, 2025
2
21
  - Add support for simple block argument destructuring (#821)
3
22
  - Benchmark the typecheck command (#852)
@@ -3,6 +3,9 @@
3
3
 
4
4
  module Solargraph
5
5
  class ApiMap
6
+ # Queryable collection of Pins representing a Workspace, gems and the Ruby
7
+ # core.
8
+ #
6
9
  class Store
7
10
  # @return [Array<Solargraph::Pin::Base>]
8
11
  attr_reader :pins
@@ -35,6 +38,7 @@ module Solargraph
35
38
  # @param fqns [String]
36
39
  # @return [String, nil]
37
40
  def get_superclass fqns
41
+ raise "Do not prefix fully qualified namespaces with '::' - #{fqns.inspect}" if fqns.start_with?('::')
38
42
  return superclass_references[fqns].first if superclass_references.key?(fqns)
39
43
  return 'Object' if fqns != 'BasicObject' && namespace_exists?(fqns)
40
44
  return 'Object' if fqns == 'Boolean'
@@ -100,7 +104,7 @@ module Solargraph
100
104
  @namespaces ||= Set.new
101
105
  end
102
106
 
103
- # @return [Array<Solargraph::Pin::Base>]
107
+ # @return [Enumerable<Solargraph::Pin::Base>]
104
108
  def namespace_pins
105
109
  pins_by_class(Solargraph::Pin::Namespace)
106
110
  end
@@ -146,7 +150,7 @@ module Solargraph
146
150
 
147
151
  # @generic T
148
152
  # @param klass [Class<T>]
149
- # @return [Array<T>]
153
+ # @return [Set<T>]
150
154
  def pins_by_class klass
151
155
  # @type [Set<Solargraph::Pin::Base>]
152
156
  s = Set.new
@@ -5,7 +5,7 @@ require 'yard'
5
5
  require 'solargraph/yard_tags'
6
6
 
7
7
  module Solargraph
8
- # An aggregate provider for information about workspaces, sources, gems, and
8
+ # An aggregate provider for information about Workspaces, Sources, gems, and
9
9
  # the Ruby core.
10
10
  #
11
11
  class ApiMap
@@ -250,18 +250,20 @@ module Solargraph
250
250
  # @param tag [String, nil] The namespace to
251
251
  # match, complete with generic parameters set to appropriate
252
252
  # values if available
253
- # @param context_tag [String] The context in which the tag was
254
- # referenced; start from here to resolve the name
253
+ # @param context_tag [String] The fully qualified context in which
254
+ # the tag was referenced; start from here to resolve the name.
255
+ # Should not be prefixed with '::'.
255
256
  # @return [String, nil] fully qualified tag
256
257
  def qualify tag, context_tag = ''
257
258
  return tag if ['self', nil].include?(tag)
258
- context_type = ComplexType.try_parse(context_tag)
259
+
260
+ context_type = ComplexType.parse(context_tag).force_rooted
259
261
  return unless context_type
260
262
 
261
263
  type = ComplexType.try_parse(tag)
262
264
  return unless type
263
265
 
264
- fqns = qualify_namespace(type.rooted_namespace, context_type.rooted_namespace)
266
+ fqns = qualify_namespace(type.rooted_namespace, context_type.namespace)
265
267
  return unless fqns
266
268
 
267
269
  fqns + type.substring
@@ -326,6 +328,11 @@ module Solargraph
326
328
  store.pins_by_class(Pin::GlobalVariable)
327
329
  end
328
330
 
331
+ # @return [Enumerable<Solargraph::Pin::Block>]
332
+ def get_block_pins
333
+ store.pins_by_class(Pin::Block)
334
+ end
335
+
329
336
  # Get an array of methods available in a particular context.
330
337
  #
331
338
  # @param rooted_tag [String] The fully qualified namespace to search for methods
@@ -591,7 +598,7 @@ module Solargraph
591
598
  # @param no_core [Boolean] Skip core classes if true
592
599
  # @return [Array<Pin::Base>]
593
600
  def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
594
- rooted_type = ComplexType.parse(rooted_tag)
601
+ rooted_type = ComplexType.parse(rooted_tag).force_rooted
595
602
  fqns = rooted_type.namespace
596
603
  fqns_generic_params = rooted_type.all_params
597
604
  return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/
@@ -629,7 +636,7 @@ module Solargraph
629
636
  # @todo perform the same translation in the other areas
630
637
  # here after adding a spec and handling things correctly
631
638
  # in ApiMap::Store and RbsMap::Conversions
632
- resolved_include_type = ComplexType.parse(rooted_include_tag).resolve_generics(namespace_pin, rooted_type)
639
+ resolved_include_type = ComplexType.parse(rooted_include_tag).force_rooted.resolve_generics(namespace_pin, rooted_type)
633
640
  methods = inner_get_methods(resolved_include_type.tag, scope, visibility, deep, skip, true)
634
641
  result.concat methods
635
642
  end
@@ -690,7 +697,7 @@ module Solargraph
690
697
 
691
698
  # @param namespace [String]
692
699
  # @param context [String]
693
- # @return [String]
700
+ # @return [String, nil]
694
701
  def qualify_lower namespace, context
695
702
  qualify namespace, context.split('::')[0..-2].join('::')
696
703
  end
@@ -12,12 +12,20 @@ module Solargraph
12
12
  # @rooted: boolish
13
13
  # methods:
14
14
  # transform()
15
+ # all_params()
16
+ # rooted?()
17
+ # can_root_name?()
15
18
  module TypeMethods
16
19
  # @!method transform(new_name = nil, &transform_type)
17
20
  # @param new_name [String, nil]
18
21
  # @yieldparam t [UniqueType]
19
22
  # @yieldreturn [UniqueType]
20
23
  # @return [UniqueType, nil]
24
+ # @!method all_params
25
+ # @return [Array<ComplexType>]
26
+ # @!method rooted?
27
+ # @!method can_root_name?(name_to_check = nil)
28
+ # @param name_to_check [String, nil]
21
29
 
22
30
  # @return [String]
23
31
  attr_reader :name
@@ -45,11 +53,6 @@ module Solargraph
45
53
  @nil_type ||= (name.casecmp('nil') == 0)
46
54
  end
47
55
 
48
- # @return [Boolean]
49
- def parameters?
50
- !substring.empty?
51
- end
52
-
53
56
  def tuple?
54
57
  @tuple_type ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?)
55
58
  end
@@ -128,13 +131,13 @@ module Solargraph
128
131
 
129
132
  # @return [String]
130
133
  def rooted_namespace
131
- return namespace unless rooted?
134
+ return namespace unless rooted? && can_root_name?(namespace)
132
135
  "::#{namespace}"
133
136
  end
134
137
 
135
138
  # @return [String]
136
139
  def rooted_name
137
- return name unless rooted?
140
+ return name unless @rooted && can_root_name?
138
141
  "::#{name}"
139
142
  end
140
143
 
@@ -177,10 +180,6 @@ module Solargraph
177
180
  tag == other.tag
178
181
  end
179
182
 
180
- def rooted?
181
- @rooted
182
- end
183
-
184
183
  # Generate a ComplexType that fully qualifies this type's namespaces.
185
184
  #
186
185
  # @param api_map [ApiMap] The ApiMap that performs qualification
@@ -26,6 +26,8 @@ module Solargraph
26
26
  if name.start_with?('::')
27
27
  name = name[2..-1]
28
28
  rooted = true
29
+ elsif !can_root_name?(name)
30
+ rooted = true
29
31
  else
30
32
  rooted = false
31
33
  end
@@ -63,7 +65,7 @@ module Solargraph
63
65
  if parameters_type.nil?
64
66
  raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty?
65
67
  end
66
- raise "Please remove leading :: and set rooted instead - #{name}" if name.start_with?('::')
68
+ raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::')
67
69
  @name = name
68
70
  @key_types = key_types
69
71
  @subtypes = subtypes
@@ -78,6 +80,24 @@ module Solargraph
78
80
  tag
79
81
  end
80
82
 
83
+ def eql?(other)
84
+ self.class == other.class &&
85
+ @name == other.name &&
86
+ @key_types == other.key_types &&
87
+ @subtypes == other.subtypes &&
88
+ @rooted == other.rooted? &&
89
+ @all_params == other.all_params &&
90
+ @parameters_type == other.parameters_type
91
+ end
92
+
93
+ def ==(other)
94
+ eql?(other)
95
+ end
96
+
97
+ def hash
98
+ [self.class, @name, @key_types, @sub_types, @rooted, @all_params, @parameters_type].hash
99
+ end
100
+
81
101
  # @return [Array<UniqueType>]
82
102
  def items
83
103
  [self]
@@ -108,7 +128,11 @@ module Solargraph
108
128
  # tuples don't have a name; they're just [foo, bar, baz].
109
129
  if substring == '()'
110
130
  # but there are no zero element tuples, so we go with an array
111
- 'Array[]'
131
+ if rooted?
132
+ '::Array[]'
133
+ else
134
+ 'Array[]'
135
+ end
112
136
  else
113
137
  # already generated surrounded by []
114
138
  parameters_as_rbs
@@ -238,6 +262,7 @@ module Solargraph
238
262
  # @return [self]
239
263
  def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil)
240
264
  raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::')
265
+
241
266
  new_name ||= name
242
267
  new_key_types ||= @key_types
243
268
  new_subtypes ||= @subtypes
@@ -278,17 +303,26 @@ module Solargraph
278
303
  new_key_types = @key_types.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
279
304
  new_subtypes = @subtypes.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } }
280
305
  end
281
- new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes)
306
+ new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes, make_rooted: @rooted)
282
307
  yield new_type
283
308
  end
284
309
 
285
- # Transform references to the 'self' type to the specified concrete namespace
286
- # @param dst [String]
287
- # @return [UniqueType]
288
- def self_to dst
310
+ # Generate a ComplexType that fully qualifies this type's namespaces.
311
+ #
312
+ # @param api_map [ApiMap] The ApiMap that performs qualification
313
+ # @param context [String] The namespace from which to resolve names
314
+ # @return [self, ComplexType, UniqueType] The generated ComplexType
315
+ def qualify api_map, context = ''
289
316
  transform do |t|
290
- next t if t.name != 'self'
291
- t.recreate(new_name: dst, new_key_types: [], new_subtypes: [])
317
+ next t if t.name == GENERIC_TAG_NAME
318
+ next t if t.duck_type? || t.void? || t.undefined?
319
+ recon = (t.rooted? ? '' : context)
320
+ fqns = api_map.qualify(t.name, recon)
321
+ if fqns.nil?
322
+ next UniqueType::BOOLEAN if t.tag == 'Boolean'
323
+ next UniqueType::UNDEFINED
324
+ end
325
+ t.recreate(new_name: fqns, make_rooted: true)
292
326
  end
293
327
  end
294
328
 
@@ -296,6 +330,35 @@ module Solargraph
296
330
  @name == 'self' || @key_types.any?(&:selfy?) || @subtypes.any?(&:selfy?)
297
331
  end
298
332
 
333
+ # @param dst [ComplexType]
334
+ # @return [self]
335
+ def self_to_type dst
336
+ object_type_dst = dst.reduce_class_type
337
+ transform do |t|
338
+ next t if t.name != 'self'
339
+ object_type_dst
340
+ end
341
+ end
342
+
343
+ def all_rooted?
344
+ return true if name == GENERIC_TAG_NAME
345
+ rooted? && all_params.all?(&:rooted?)
346
+ end
347
+
348
+ def rooted?
349
+ !can_root_name? || @rooted
350
+ end
351
+
352
+ def can_root_name?(name_to_check = name)
353
+ self.class.can_root_name?(name_to_check)
354
+ end
355
+
356
+ # @param name [String]
357
+ def self.can_root_name?(name)
358
+ # name is not lowercase
359
+ !name.empty? && name != name.downcase
360
+ end
361
+
299
362
  UNDEFINED = UniqueType.new('undefined', rooted: false)
300
363
  BOOLEAN = UniqueType.new('Boolean', rooted: true)
301
364
  end
@@ -18,13 +18,27 @@ module Solargraph
18
18
  @items = types.flat_map(&:items).uniq(&:to_s)
19
19
  end
20
20
 
21
+ def eql?(other)
22
+ self.class == other.class &&
23
+ @items == other.items
24
+ end
25
+
26
+ def ==(other)
27
+ self.eql?(other)
28
+ end
29
+
30
+ def hash
31
+ [self.class, @items].hash
32
+ end
33
+
21
34
  # @param api_map [ApiMap]
22
35
  # @param context [String]
23
36
  # @return [ComplexType]
24
37
  def qualify api_map, context = ''
25
38
  red = reduce_object
26
39
  types = red.items.map do |t|
27
- next t if ['Boolean', 'nil', 'void', 'undefined'].include?(t.name)
40
+ next t if ['nil', 'void', 'undefined'].include?(t.name)
41
+ next t if ['::Boolean'].include?(t.rooted_name)
28
42
  t.qualify api_map, context
29
43
  end
30
44
  ComplexType.new(types).reduce_object
@@ -52,6 +66,16 @@ module Solargraph
52
66
  (@items.length > 1 ? ')' : ''))
53
67
  end
54
68
 
69
+ # @param dst [ComplexType]
70
+ # @return [ComplexType]
71
+ def self_to_type dst
72
+ object_type_dst = dst.reduce_class_type
73
+ transform do |t|
74
+ next t if t.name != 'self'
75
+ object_type_dst
76
+ end
77
+ end
78
+
55
79
  # @yieldparam [UniqueType]
56
80
  # @return [Array]
57
81
  def map &block
@@ -171,16 +195,7 @@ module Solargraph
171
195
  # @return [ComplexType]
172
196
  def resolve_generics definitions, context_type
173
197
  result = @items.map { |i| i.resolve_generics(definitions, context_type) }
174
- ComplexType.try_parse(*result.map(&:tag))
175
- end
176
-
177
- # @param dst [String]
178
- # @return [ComplexType]
179
- def self_to dst
180
- return self unless selfy?
181
- red = reduce_class(dst)
182
- result = @items.map { |i| i.self_to red }
183
- ComplexType.try_parse(*result.map(&:tag))
198
+ ComplexType.new(result)
184
199
  end
185
200
 
186
201
  def nullable?
@@ -192,14 +207,43 @@ module Solargraph
192
207
  @items.first.all_params || []
193
208
  end
194
209
 
210
+ # @return [ComplexType]
211
+ def reduce_class_type
212
+ new_items = items.flat_map do |type|
213
+ next type unless ['Module', 'Class'].include?(type.name)
214
+
215
+ type.all_params
216
+ end
217
+ ComplexType.new(new_items)
218
+ end
219
+
220
+ # every type and subtype in this union have been resolved to be
221
+ # fully qualified
222
+ def all_rooted?
223
+ all?(&:all_rooted?)
224
+ end
225
+
226
+ # every top-level type has resolved to be fully qualified; see
227
+ # #all_rooted? to check their subtypes as well
228
+ def rooted?
229
+ all?(&:rooted?)
230
+ end
231
+
195
232
  attr_reader :items
196
233
 
234
+ def rooted?
235
+ @items.all?(&:rooted?)
236
+ end
237
+
197
238
  protected
198
239
 
199
240
  # @return [ComplexType]
200
241
  def reduce_object
201
- return self if name != 'Object' || subtypes.empty?
202
- ComplexType.try_parse(reduce_class(subtypes.join(', ')))
242
+ new_items = items.flat_map do |ut|
243
+ next [ut] if ut.name != 'Object' || ut.subtypes.empty?
244
+ ut.subtypes
245
+ end
246
+ ComplexType.new(new_items)
203
247
  end
204
248
 
205
249
  def bottom?
@@ -221,10 +265,15 @@ module Solargraph
221
265
  #
222
266
  # @param *strings [Array<String>] The type definitions to parse
223
267
  # @return [ComplexType]
224
- # @overload parse(*strings, partial: false)
225
- # @todo Need ability to use a literal true as a type below
226
- # @param partial [Boolean] True if the string is part of a another type
227
- # @return [Array<UniqueType>]
268
+ # # @overload parse(*strings, partial: false)
269
+ # # @todo Need ability to use a literal true as a type below
270
+ # # @param partial [Boolean] True if the string is part of a another type
271
+ # # @return [Array<UniqueType>]
272
+ # @sg-ignore
273
+ # @todo To be able to select the right signature above,
274
+ # Chain::Call needs to know the decl type (:arg, :optarg,
275
+ # :kwarg, etc) of the arguments given, instead of just having
276
+ # an array of Chains as the arguments.
228
277
  def parse *strings, partial: false
229
278
  # @type [Hash{Array<String> => ComplexType}]
230
279
  @cache ||= {}
@@ -6,7 +6,10 @@ module Solargraph
6
6
  # A serial worker Thread to handle message.
7
7
  #
8
8
  # this make check pending message possible, and maybe cancelled to speedup process
9
+ #
9
10
  class MessageWorker
11
+ UPDATE_METHODS = ['textDocument/didOpen', 'textDocument/didChange', 'workspace/didChangeWatchedFiles'].freeze
12
+
10
13
  # @param host [Host]
11
14
  def initialize(host)
12
15
  @host = host
@@ -30,7 +33,7 @@ module Solargraph
30
33
  @stopped = true
31
34
  end
32
35
 
33
- # @param message [Hash] The message should be handle. will pass back to Host#receive
36
+ # @param message [Hash] The message to handle. Will be forwarded to Host#receive
34
37
  # @return [void]
35
38
  def queue(message)
36
39
  @mutex.synchronize do
@@ -52,10 +55,33 @@ module Solargraph
52
55
  def tick
53
56
  message = @mutex.synchronize do
54
57
  @resource.wait(@mutex) if messages.empty?
55
- messages.shift
58
+ next_message
56
59
  end
57
60
  handler = @host.receive(message)
58
- handler && handler.send_response
61
+ handler&.send_response
62
+ end
63
+
64
+ private
65
+
66
+ def next_message
67
+ # Prioritize updates and version-dependent messages for performance
68
+ idx = messages.find_index do |msg|
69
+ UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
70
+ end
71
+ # @todo We might want to clear duplicate instances of this message
72
+ # that occur before the next update
73
+ return messages.shift unless idx
74
+
75
+ msg = messages[idx]
76
+ messages.delete_at idx
77
+ msg
78
+ end
79
+
80
+ # True if the message requires a previous update to have executed in
81
+ # order to work correctly.
82
+ #
83
+ def version_dependent? msg
84
+ msg['textDocument'] && msg['position']
59
85
  end
60
86
  end
61
87
  end
@@ -13,10 +13,10 @@ module Solargraph::LanguageServer::Message::TextDocument
13
13
  def code_location
14
14
  suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column)
15
15
  return nil if suggestions.empty?
16
- suggestions.reject { |pin| pin.location.nil? || pin.location.filename.nil? }.map do |pin|
16
+ suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin|
17
17
  {
18
- uri: file_to_uri(pin.location.filename),
19
- range: pin.location.range.to_hash
18
+ uri: file_to_uri(pin.best_location.filename),
19
+ range: pin.best_location.range.to_hash
20
20
  }
21
21
  end
22
22
  end
@@ -6,15 +6,15 @@ class Solargraph::LanguageServer::Message::TextDocument::DocumentSymbol < Solarg
6
6
  def process
7
7
  pins = host.document_symbols params['textDocument']['uri']
8
8
  info = pins.map do |pin|
9
- next nil unless pin.location&.filename
9
+ next nil unless pin.best_location&.filename
10
10
 
11
11
  result = {
12
12
  name: pin.name,
13
13
  containerName: pin.namespace,
14
14
  kind: pin.symbol_kind,
15
15
  location: {
16
- uri: file_to_uri(pin.location.filename),
17
- range: pin.location.range.to_hash
16
+ uri: file_to_uri(pin.best_location.filename),
17
+ range: pin.best_location.range.to_hash
18
18
  },
19
19
  deprecated: pin.deprecated?
20
20
  }
@@ -21,7 +21,7 @@ module Solargraph
21
21
  parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty?
22
22
  unless parts.empty?
23
23
  data = parts.join("\n\n")
24
- next if contents.last && contents.last.end_with?(data)
24
+ next if contents.last&.end_with?(data)
25
25
  contents.push data
26
26
  end
27
27
  last_link = this_link unless this_link.nil?
@@ -13,10 +13,10 @@ module Solargraph::LanguageServer::Message::TextDocument
13
13
  def code_location
14
14
  suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column)
15
15
  return nil if suggestions.empty?
16
- suggestions.reject { |pin| pin.location.nil? || pin.location.filename.nil? }.map do |pin|
16
+ suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin|
17
17
  {
18
- uri: file_to_uri(pin.location.filename),
19
- range: pin.location.range.to_hash
18
+ uri: file_to_uri(pin.best_location.filename),
19
+ range: pin.best_location.range.to_hash
20
20
  }
21
21
  end
22
22
  end
@@ -6,14 +6,14 @@ class Solargraph::LanguageServer::Message::Workspace::WorkspaceSymbol < Solargra
6
6
  def process
7
7
  pins = host.query_symbols(params['query'])
8
8
  info = pins.map do |pin|
9
- uri = file_to_uri(pin.location.filename)
9
+ uri = file_to_uri(pin.best_location.filename)
10
10
  {
11
11
  name: pin.path,
12
12
  containerName: pin.namespace,
13
13
  kind: pin.symbol_kind,
14
14
  location: {
15
15
  uri: uri,
16
- range: pin.location.range.to_hash
16
+ range: pin.best_location.range.to_hash
17
17
  },
18
18
  deprecated: pin.deprecated?
19
19
  }
@@ -66,10 +66,10 @@ module Solargraph
66
66
  return unless host.client_supports_progress? && !finished?
67
67
 
68
68
  message = build
69
-
70
- create(host) unless created?
69
+ create(host)
71
70
  host.send_notification '$/progress', message
72
71
  @status = FINISHED if kind == 'end'
72
+ keep_alive host
73
73
  end
74
74
 
75
75
  def created?
@@ -113,6 +113,23 @@ module Solargraph
113
113
  raise "Invalid progress kind #{kind}"
114
114
  end
115
115
  end
116
+
117
+ # @param host [Host]
118
+ def keep_alive host
119
+ mutex.synchronize { @last = Time.now }
120
+ @keep_alive ||= Thread.new do
121
+ until finished?
122
+ sleep 10
123
+ break if finished?
124
+ next if mutex.synchronize { Time.now - @last < 10 }
125
+ send host
126
+ end
127
+ end
128
+ end
129
+
130
+ def mutex
131
+ @mutex ||= Mutex.new
132
+ end
116
133
  end
117
134
  end
118
135
  end