solargraph 0.54.0 → 0.55.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 (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/lib/solargraph/api_map/cache.rb +10 -1
  4. data/lib/solargraph/api_map/index.rb +167 -0
  5. data/lib/solargraph/api_map/store.rb +75 -122
  6. data/lib/solargraph/api_map.rb +109 -41
  7. data/lib/solargraph/bench.rb +17 -1
  8. data/lib/solargraph/complex_type/type_methods.rb +17 -11
  9. data/lib/solargraph/complex_type/unique_type.rb +181 -17
  10. data/lib/solargraph/complex_type.rb +103 -24
  11. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +51 -0
  12. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  13. data/lib/solargraph/convention/struct_definition.rb +101 -0
  14. data/lib/solargraph/convention.rb +2 -0
  15. data/lib/solargraph/doc_map.rb +43 -18
  16. data/lib/solargraph/equality.rb +33 -0
  17. data/lib/solargraph/language_server/host/message_worker.rb +51 -5
  18. data/lib/solargraph/language_server/host.rb +13 -11
  19. data/lib/solargraph/language_server/message/base.rb +19 -12
  20. data/lib/solargraph/language_server/message/initialize.rb +3 -1
  21. data/lib/solargraph/language_server/message/text_document/completion.rb +0 -3
  22. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  23. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  24. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -0
  25. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  26. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  27. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  28. data/lib/solargraph/language_server/progress.rb +19 -2
  29. data/lib/solargraph/library.rb +31 -41
  30. data/lib/solargraph/location.rb +29 -1
  31. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  32. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  33. data/lib/solargraph/parser/node_methods.rb +15 -1
  34. data/lib/solargraph/parser/parser_gem/class_methods.rb +11 -6
  35. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  36. data/lib/solargraph/parser/parser_gem/node_methods.rb +6 -4
  37. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  39. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  40. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  41. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
  42. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  43. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  44. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
  45. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  46. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  47. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  48. data/lib/solargraph/parser.rb +3 -5
  49. data/lib/solargraph/pin/base.rb +47 -17
  50. data/lib/solargraph/pin/base_variable.rb +11 -4
  51. data/lib/solargraph/pin/block.rb +8 -26
  52. data/lib/solargraph/pin/breakable.rb +9 -0
  53. data/lib/solargraph/pin/callable.rb +147 -0
  54. data/lib/solargraph/pin/closure.rb +8 -3
  55. data/lib/solargraph/pin/common.rb +2 -6
  56. data/lib/solargraph/pin/conversions.rb +3 -2
  57. data/lib/solargraph/pin/instance_variable.rb +2 -2
  58. data/lib/solargraph/pin/local_variable.rb +7 -1
  59. data/lib/solargraph/pin/method.rb +76 -48
  60. data/lib/solargraph/pin/namespace.rb +14 -11
  61. data/lib/solargraph/pin/parameter.rb +24 -17
  62. data/lib/solargraph/pin/proxy_type.rb +13 -7
  63. data/lib/solargraph/pin/signature.rb +3 -129
  64. data/lib/solargraph/pin/until.rb +18 -0
  65. data/lib/solargraph/pin/while.rb +18 -0
  66. data/lib/solargraph/pin.rb +7 -1
  67. data/lib/solargraph/position.rb +7 -0
  68. data/lib/solargraph/range.rb +9 -4
  69. data/lib/solargraph/rbs_map/conversions.rb +84 -45
  70. data/lib/solargraph/rbs_map/core_fills.rb +16 -9
  71. data/lib/solargraph/rbs_map.rb +1 -0
  72. data/lib/solargraph/shell.rb +19 -2
  73. data/lib/solargraph/source/chain/array.rb +10 -8
  74. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  75. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  76. data/lib/solargraph/source/chain/call.rb +125 -61
  77. data/lib/solargraph/source/chain/constant.rb +1 -1
  78. data/lib/solargraph/source/chain/hash.rb +8 -2
  79. data/lib/solargraph/source/chain/if.rb +5 -0
  80. data/lib/solargraph/source/chain/link.rb +28 -5
  81. data/lib/solargraph/source/chain/literal.rb +27 -2
  82. data/lib/solargraph/source/chain/or.rb +1 -1
  83. data/lib/solargraph/source/chain/z_super.rb +1 -1
  84. data/lib/solargraph/source/chain.rb +131 -63
  85. data/lib/solargraph/source/cursor.rb +3 -2
  86. data/lib/solargraph/source/source_chainer.rb +2 -2
  87. data/lib/solargraph/source.rb +104 -86
  88. data/lib/solargraph/source_map/clip.rb +8 -6
  89. data/lib/solargraph/source_map/data.rb +30 -0
  90. data/lib/solargraph/source_map.rb +28 -16
  91. data/lib/solargraph/type_checker/checks.rb +4 -0
  92. data/lib/solargraph/type_checker/rules.rb +6 -1
  93. data/lib/solargraph/type_checker.rb +48 -21
  94. data/lib/solargraph/version.rb +1 -1
  95. data/lib/solargraph/views/environment.erb +3 -5
  96. data/lib/solargraph/workspace/config.rb +7 -3
  97. data/lib/solargraph/workspace.rb +1 -1
  98. data/lib/solargraph/yard_map/mapper/to_constant.rb +1 -0
  99. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  100. data/lib/solargraph/yard_map/mapper/to_namespace.rb +1 -0
  101. data/lib/solargraph/yard_map/mapper.rb +1 -0
  102. data/lib/solargraph/yardoc.rb +1 -1
  103. data/lib/solargraph.rb +1 -0
  104. data/solargraph.gemspec +5 -5
  105. metadata +41 -25
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Convention
5
+ module StructDefinition
6
+ autoload :StructDefintionNode, 'solargraph/convention/struct_definition/struct_definition_node'
7
+ autoload :StructAssignmentNode, 'solargraph/convention/struct_definition/struct_assignment_node'
8
+
9
+ module NodeProcessors
10
+ class StructNode < Parser::NodeProcessor::Base
11
+ def process
12
+ return if struct_definition_node.nil?
13
+
14
+ loc = get_node_location(node)
15
+ nspin = Solargraph::Pin::Namespace.new(
16
+ type: :class,
17
+ location: loc,
18
+ closure: region.closure,
19
+ name: struct_definition_node.class_name,
20
+ comments: comments_for(node),
21
+ visibility: :public,
22
+ gates: region.closure.gates.freeze
23
+ )
24
+ pins.push nspin
25
+
26
+ # define initialize method
27
+ initialize_method_pin = Pin::Method.new(
28
+ name: 'initialize',
29
+ parameters: [],
30
+ scope: :instance,
31
+ location: get_node_location(node),
32
+ closure: nspin,
33
+ visibility: :private,
34
+ comments: comments_for(node)
35
+ )
36
+
37
+ pins.push initialize_method_pin
38
+
39
+ struct_definition_node.attributes.map do |attribute_node, attribute_name|
40
+ initialize_method_pin.parameters.push(
41
+ Pin::Parameter.new(
42
+ name: attribute_name,
43
+ decl: struct_definition_node.keyword_init? ? :kwarg : :arg,
44
+ location: get_node_location(attribute_node),
45
+ closure: initialize_method_pin
46
+ )
47
+ )
48
+ end
49
+
50
+ # define attribute accessors and instance variables
51
+ struct_definition_node.attributes.each do |attribute_node, attribute_name|
52
+ [attribute_name, "#{attribute_name}="].each do |name|
53
+ method_pin = Pin::Method.new(
54
+ name: name,
55
+ parameters: [],
56
+ scope: :instance,
57
+ location: get_node_location(attribute_node),
58
+ closure: nspin,
59
+ comments: attribute_comments(attribute_node, attribute_name),
60
+ visibility: :public
61
+ )
62
+
63
+ pins.push method_pin
64
+
65
+ next unless name.include?('=') # setter
66
+ pins.push Pin::InstanceVariable.new(name: "@#{attribute_name}",
67
+ closure: method_pin,
68
+ location: get_node_location(attribute_node),
69
+ comments: attribute_comments(attribute_node, attribute_name))
70
+ end
71
+ end
72
+
73
+ process_children region.update(closure: nspin, visibility: :public)
74
+ end
75
+
76
+ private
77
+
78
+ # @return [StructDefintionNode, nil]
79
+ def struct_definition_node
80
+ @struct_definition_node ||= if StructDefintionNode.valid?(node)
81
+ StructDefintionNode.new(node)
82
+ elsif StructAssignmentNode.valid?(node)
83
+ StructAssignmentNode.new(node)
84
+ end
85
+ end
86
+
87
+ # @param attribute_node [Parser::AST::Node]
88
+ # @return [String, nil]
89
+ def attribute_comments(attribute_node, attribute_name)
90
+ struct_comments = comments_for(attribute_node)
91
+ return if struct_comments.nil? || struct_comments.empty?
92
+
93
+ struct_comments.split("\n").find do |row|
94
+ row.include?(attribute_name)
95
+ end&.gsub('@param', '@return')&.gsub(attribute_name, '')
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
@@ -10,7 +10,9 @@ module Solargraph
10
10
  autoload :Gemfile, 'solargraph/convention/gemfile'
11
11
  autoload :Gemspec, 'solargraph/convention/gemspec'
12
12
  autoload :Rakefile, 'solargraph/convention/rakefile'
13
+ autoload :StructDefinition, 'solargraph/convention/struct_definition'
13
14
 
15
+ # @type [Set<Convention::Base>]
14
16
  @@conventions = Set.new
15
17
 
16
18
  # @param convention [Class<Convention::Base>]
@@ -4,6 +4,8 @@ module Solargraph
4
4
  # A collection of pins generated from required gems.
5
5
  #
6
6
  class DocMap
7
+ include Logging
8
+
7
9
  # @return [Array<String>]
8
10
  attr_reader :requires
9
11
 
@@ -28,12 +30,12 @@ module Solargraph
28
30
 
29
31
  # @return [Array<Gem::Specification>]
30
32
  def gemspecs
31
- @gemspecs ||= required_gem_map.values.compact
33
+ @gemspecs ||= required_gems_map.values.compact.flatten
32
34
  end
33
35
 
34
36
  # @return [Array<String>]
35
37
  def unresolved_requires
36
- @unresolved_requires ||= required_gem_map.select { |_, gemspec| gemspec.nil? }.keys
38
+ @unresolved_requires ||= required_gems_map.select { |_, gemspecs| gemspecs.nil? }.keys
37
39
  end
38
40
 
39
41
  # @return [Hash{Gem::Specification => Array[Pin::Base]}]
@@ -52,20 +54,22 @@ module Solargraph
52
54
  def generate
53
55
  @pins = []
54
56
  @uncached_gemspecs = []
55
- required_gem_map.each do |path, gemspec|
56
- if gemspec
57
- try_cache gemspec
58
- else
57
+ required_gems_map.each do |path, gemspecs|
58
+ if gemspecs.nil?
59
59
  try_stdlib_map path
60
+ else
61
+ gemspecs.each do |gemspec|
62
+ try_cache gemspec
63
+ end
60
64
  end
61
65
  end
62
66
  dependencies.each { |dep| try_cache dep }
63
67
  @uncached_gemspecs.uniq!
64
68
  end
65
69
 
66
- # @return [Hash{String => Gem::Specification, nil}]
67
- def required_gem_map
68
- @required_gem_map ||= requires.to_h { |path| [path, resolve_path_to_gemspec(path)] }
70
+ # @return [Hash{String => Array<Gem::Specification>}]
71
+ def required_gems_map
72
+ @required_gems_map ||= requires.to_h { |path| [path, resolve_path_to_gemspecs(path)] }
69
73
  end
70
74
 
71
75
  # @return [Hash{String => Gem::Specification}]
@@ -112,6 +116,7 @@ module Solargraph
112
116
  true
113
117
  end
114
118
 
119
+ # @param gemspec [Gem::Specification]
115
120
  def update_from_collection gemspec, gempins
116
121
  return gempins unless @rbs_path && File.directory?(@rbs_path)
117
122
  return gempins if RbsMap.new(gemspec.name, gemspec.version).resolved?
@@ -124,10 +129,26 @@ module Solargraph
124
129
  end
125
130
 
126
131
  # @param path [String]
127
- # @return [Gem::Specification, nil]
128
- def resolve_path_to_gemspec path
132
+ # @return [::Array<Gem::Specification>, nil]
133
+ def resolve_path_to_gemspecs path
129
134
  return nil if path.empty?
130
135
 
136
+ if path == 'bundler/require'
137
+ # find only the gems bundler is now using
138
+ gemspecs = Bundler.definition.locked_gems.specs.flat_map do |lazy_spec|
139
+ logger.info "Handling #{lazy_spec.name}:#{lazy_spec.version} from #{path}"
140
+ [Gem::Specification.find_by_name(lazy_spec.name, lazy_spec.version)]
141
+ rescue Gem::MissingSpecError => e
142
+ logger.info("Could not find #{lazy_spec.name}:#{lazy_spec.version} with find_by_name, falling back to guess")
143
+ # can happen in local filesystem references
144
+ specs = resolve_path_to_gemspecs lazy_spec.name
145
+ logger.info "Gem #{lazy_spec.name} #{lazy_spec.version} from bundle not found: #{e}" if specs.nil?
146
+ next specs
147
+ end.compact
148
+
149
+ return gemspecs
150
+ end
151
+
131
152
  gemspec = Gem::Specification.find_by_path(path)
132
153
  if gemspec.nil?
133
154
  gem_name_guess = path.split('/').first
@@ -141,16 +162,17 @@ module Solargraph
141
162
  gemspec = potential_gemspec if potential_gemspec.files.any? { |gemspec_file| file == gemspec_file }
142
163
  rescue Gem::MissingSpecError
143
164
  Solargraph.logger.debug "Require path #{path} could not be resolved to a gem via find_by_path or guess of #{gem_name_guess}"
144
- nil
165
+ []
145
166
  end
146
167
  end
147
- gemspec_or_preference gemspec
168
+ return nil if gemspec.nil?
169
+ [gemspec_or_preference(gemspec)]
148
170
  end
149
171
 
150
- # @param gemspec [Gem::Specification, nil]
151
- # @return [Gem::Specification, nil]
172
+ # @param gemspec [Gem::Specification]
173
+ # @return [Gem::Specification]
152
174
  def gemspec_or_preference gemspec
153
- return gemspec unless gemspec && preference_map.key?(gemspec.name)
175
+ return gemspec unless preference_map.key?(gemspec.name)
154
176
  return gemspec if gemspec.version == preference_map[gemspec.name].version
155
177
 
156
178
  change_gemspec_version gemspec, preference_map[by_path.name].version
@@ -169,12 +191,15 @@ module Solargraph
169
191
  # @param gemspec [Gem::Specification]
170
192
  # @return [Array<Gem::Specification>]
171
193
  def fetch_dependencies gemspec
194
+ # @param spec [Gem::Dependency]
172
195
  only_runtime_dependencies(gemspec).each_with_object(Set.new) do |spec, deps|
173
196
  Solargraph.logger.info "Adding #{spec.name} dependency for #{gemspec.name}"
174
- dep = Gem::Specification.find_by_name(spec.name, spec.requirement)
197
+ dep = Gem.loaded_specs[spec.name]
198
+ # @todo is next line necessary?
199
+ dep ||= Gem::Specification.find_by_name(spec.name, spec.requirement)
175
200
  deps.merge fetch_dependencies(dep) if deps.add?(dep)
176
201
  rescue Gem::MissingSpecError
177
- Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirements} for #{gemspec.name} not found."
202
+ Solargraph.logger.warn "Gem dependency #{spec.name} #{spec.requirement} for #{gemspec.name} not found."
178
203
  end.to_a
179
204
  end
180
205
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ # @abstract This mixin relies on these -
5
+ # methods:
6
+ # equality_fields()
7
+ module Equality
8
+ # @!method equality_fields
9
+ # @return [Array]
10
+
11
+ # @param other [Object]
12
+ # @return [Boolean]
13
+ def eql?(other)
14
+ self.class.eql?(other.class) &&
15
+ equality_fields.eql?(other.equality_fields)
16
+ end
17
+
18
+ # @param other [Object]
19
+ # @return [Boolean]
20
+ def ==(other)
21
+ self.eql?(other)
22
+ end
23
+
24
+ def hash
25
+ equality_fields.hash
26
+ end
27
+
28
+ def freeze
29
+ equality_fields.each(&:freeze)
30
+ super
31
+ end
32
+ end
33
+ end
@@ -3,10 +3,22 @@
3
3
  module Solargraph
4
4
  module LanguageServer
5
5
  class Host
6
- # A serial worker Thread to handle message.
6
+ # A serial worker Thread to handle incoming messages.
7
7
  #
8
- # this make check pending message possible, and maybe cancelled to speedup process
9
8
  class MessageWorker
9
+ UPDATE_METHODS = [
10
+ 'textDocument/didChange',
11
+ 'textDocument/didClose',
12
+ 'textDocument/didOpen',
13
+ 'textDocument/didSave',
14
+ 'workspace/didChangeConfiguration',
15
+ 'workspace/didChangeWatchedFiles',
16
+ 'workspace/didCreateFiles',
17
+ 'workspace/didChangeWorkspaceFolders',
18
+ 'workspace/didDeleteFiles',
19
+ 'workspace/didRenameFiles'
20
+ ].freeze
21
+
10
22
  # @param host [Host]
11
23
  def initialize(host)
12
24
  @host = host
@@ -30,7 +42,7 @@ module Solargraph
30
42
  @stopped = true
31
43
  end
32
44
 
33
- # @param message [Hash] The message should be handle. will pass back to Host#receive
45
+ # @param message [Hash] The message to handle. Will be forwarded to Host#receive
34
46
  # @return [void]
35
47
  def queue(message)
36
48
  @mutex.synchronize do
@@ -52,10 +64,44 @@ module Solargraph
52
64
  def tick
53
65
  message = @mutex.synchronize do
54
66
  @resource.wait(@mutex) if messages.empty?
55
- messages.shift
67
+ next_message
56
68
  end
57
69
  handler = @host.receive(message)
58
- handler && handler.send_response
70
+ handler&.send_response
71
+ end
72
+
73
+ private
74
+
75
+ def next_message
76
+ cancel_message || next_priority
77
+ end
78
+
79
+ def cancel_message
80
+ # Handle cancellations first
81
+ idx = messages.find_index { |msg| msg['method'] == '$/cancelRequest' }
82
+ return unless idx
83
+
84
+ msg = messages[idx]
85
+ messages.delete_at idx
86
+ msg
87
+ end
88
+
89
+ def next_priority
90
+ # Prioritize updates and version-dependent messages for performance
91
+ idx = messages.find_index do |msg|
92
+ UPDATE_METHODS.include?(msg['method']) || version_dependent?(msg)
93
+ end
94
+ idx ? messages.delete_at(idx) : messages.shift
95
+ end
96
+
97
+ # True if the message requires a previous update to have executed in
98
+ # order to work correctly.
99
+ #
100
+ # @param msg [Hash{String => Object}]
101
+ # @todo need boolish type from RBS
102
+ # @return [Object]
103
+ def version_dependent? msg
104
+ msg['textDocument'] && msg['position']
59
105
  end
60
106
  end
61
107
  end
@@ -24,10 +24,8 @@ module Solargraph
24
24
  attr_writer :client_capabilities
25
25
 
26
26
  def initialize
27
- @cancel_semaphore = Mutex.new
28
27
  @buffer_semaphore = Mutex.new
29
28
  @request_mutex = Mutex.new
30
- @cancel = []
31
29
  @buffer = String.new
32
30
  @stopped = true
33
31
  @next_request_id = 1
@@ -65,7 +63,7 @@ module Solargraph
65
63
  # @param id [Integer]
66
64
  # @return [void]
67
65
  def cancel id
68
- @cancel_semaphore.synchronize { @cancel.push id }
66
+ cancelled.push id
69
67
  end
70
68
 
71
69
  # True if the host received a request to cancel the method with the
@@ -74,9 +72,7 @@ module Solargraph
74
72
  # @param id [Integer]
75
73
  # @return [Boolean]
76
74
  def cancel? id
77
- result = false
78
- @cancel_semaphore.synchronize { result = @cancel.include? id }
79
- result
75
+ cancelled.include? id
80
76
  end
81
77
 
82
78
  # Delete the specified ID from the list of cancelled IDs if it exists.
@@ -84,7 +80,7 @@ module Solargraph
84
80
  # @param id [Integer]
85
81
  # @return [void]
86
82
  def clear id
87
- @cancel_semaphore.synchronize { @cancel.delete id }
83
+ cancelled.delete id
88
84
  end
89
85
 
90
86
  # Called by adapter, to handle the request
@@ -101,11 +97,11 @@ module Solargraph
101
97
  # @return [Solargraph::LanguageServer::Message::Base, nil] The message handler.
102
98
  def receive request
103
99
  if request['method']
104
- logger.info "Server received #{request['method']}"
100
+ logger.info "Host received ##{request['id']} #{request['method']}"
105
101
  logger.debug request
106
102
  message = Message.select(request['method']).new(self, request)
107
103
  begin
108
- message.process
104
+ message.process unless cancel?(request['id'])
109
105
  rescue StandardError => e
110
106
  logger.warn "Error processing request: [#{e.class}] #{e.message}"
111
107
  logger.warn e.backtrace.join("\n")
@@ -381,7 +377,6 @@ module Solargraph
381
377
  envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
382
378
  queue envelope
383
379
  @next_request_id += 1
384
- logger.info "Server sent #{method}"
385
380
  logger.debug params
386
381
  end
387
382
  end
@@ -476,6 +471,7 @@ module Solargraph
476
471
  def locate_pins params
477
472
  return [] unless params['data'] && params['data']['uri']
478
473
  library = library_for(params['data']['uri'])
474
+ # @type [Array<Pin::Base>]
479
475
  result = []
480
476
  if params['data']['location']
481
477
  location = Location.new(
@@ -575,7 +571,7 @@ module Solargraph
575
571
  # @param column [Integer]
576
572
  # @param strip [Boolean] Strip special characters from variable names
577
573
  # @param only [Boolean] If true, search current file only
578
- # @return [Array<Solargraph::Range>]
574
+ # @return [Array<Solargraph::Location>]
579
575
  def references_from uri, line, column, strip: true, only: false
580
576
  library = library_for(uri)
581
577
  library.references_from(uri_to_file(uri), line, column, strip: strip, only: only)
@@ -693,6 +689,11 @@ module Solargraph
693
689
 
694
690
  private
695
691
 
692
+ # @return [Array<Integer>]
693
+ def cancelled
694
+ @cancelled ||= []
695
+ end
696
+
696
697
  # @return [MessageWorker]
697
698
  def message_worker
698
699
  @message_worker ||= MessageWorker.new(self)
@@ -748,6 +749,7 @@ module Solargraph
748
749
  return change if source.code.length + 1 != change['text'].length
749
750
  diffs = Diff::LCS.diff(source.code, change['text'])
750
751
  return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
752
+ # @sg-ignore push this upstream
751
753
  # @type [Diff::LCS::Change]
752
754
  diff = diffs.first.first
753
755
  return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
@@ -16,7 +16,7 @@ module Solargraph
16
16
  # @return [String]
17
17
  attr_reader :method
18
18
 
19
- # @return [Hash{String => Array, Hash, String, Integer}]
19
+ # @return [Hash{String => Array<undefined>, Hash{String => undefined}, String, Integer}]
20
20
  attr_reader :params
21
21
 
22
22
  # @return [Hash, Array, nil]
@@ -61,18 +61,11 @@ module Solargraph
61
61
  # @return [void]
62
62
  def send_response
63
63
  return if id.nil?
64
- if host.cancel?(id)
65
- # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
66
- # cancel should send response RequestCancelled
67
- Solargraph::Logging.logger.info "Cancelled response to #{method}"
68
- set_result nil
69
- set_error ErrorCodes::REQUEST_CANCELLED, "cancelled by client"
70
- else
71
- Solargraph::Logging.logger.info "Sending response to #{method}"
72
- end
64
+
65
+ accept_or_cancel
73
66
  response = {
74
- jsonrpc: "2.0",
75
- id: id,
67
+ jsonrpc: '2.0',
68
+ id: id
76
69
  }
77
70
  response[:result] = result unless result.nil?
78
71
  response[:error] = error unless error.nil?
@@ -83,6 +76,20 @@ module Solargraph
83
76
  host.queue envelope
84
77
  host.clear id
85
78
  end
79
+
80
+ private
81
+
82
+ def accept_or_cancel
83
+ if host.cancel?(id)
84
+ # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#cancelRequest
85
+ # cancel should send response RequestCancelled
86
+ Solargraph::Logging.logger.info "Cancelled response to ##{id} #{method}"
87
+ set_result nil
88
+ set_error ErrorCodes::REQUEST_CANCELLED, 'Cancelled by client'
89
+ else
90
+ Solargraph::Logging.logger.info "Sending response to ##{id} #{method}"
91
+ end
92
+ end
86
93
  end
87
94
  end
88
95
  end
@@ -174,7 +174,9 @@ module Solargraph
174
174
 
175
175
  # @param section [String]
176
176
  # @param capability [String]
177
- # @return [Boolean]
177
+ # @todo Need support for RBS' boolish "type", which doesn't
178
+ # enforce strict true/false-ness
179
+ # @sg-ignore
178
180
  def dynamic_registration_for? section, capability
179
181
  result = (params['capabilities'] &&
180
182
  params['capabilities'][section] &&
@@ -12,9 +12,6 @@ module Solargraph
12
12
  col = params['position']['character']
13
13
  begin
14
14
  completion = host.completions_at(params['textDocument']['uri'], line, col)
15
- if host.cancel?(id)
16
- return set_result(empty_result) if host.cancel?(id)
17
- end
18
15
  items = []
19
16
  last_context = nil
20
17
  idx = -1
@@ -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
  }
@@ -33,6 +33,7 @@ module Solargraph
33
33
 
34
34
  private
35
35
 
36
+ # @param corrections [String]
36
37
  def log_corrections(corrections)
37
38
  corrections = corrections&.strip
38
39
  return if corrections&.empty?
@@ -51,6 +52,7 @@ module Solargraph
51
52
  conf['rubocop'] || {}
52
53
  end
53
54
 
55
+ # @param config [Hash{String => String}]
54
56
  def cli_args file_uri, config
55
57
  file = UriHelpers.uri_to_file(file_uri)
56
58
  args = [
@@ -68,6 +70,7 @@ module Solargraph
68
70
  args + [file]
69
71
  end
70
72
 
73
+ # @param config [Hash{String => String}]
71
74
  def formatter_class(config)
72
75
  if self.class.const_defined?('BlankRubocopFormatter')
73
76
  # @sg-ignore
@@ -79,6 +82,7 @@ module Solargraph
79
82
  end
80
83
  end
81
84
 
85
+ # @param value [Array, String]
82
86
  def cop_list(value)
83
87
  value = value.join(',') if value.respond_to?(:join)
84
88
  return nil if value == '' || !value.is_a?(String)
@@ -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