solargraph 0.53.4 → 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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +41 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +14 -5
  5. data/lib/solargraph/api_map.rb +67 -24
  6. data/lib/solargraph/complex_type/type_methods.rb +70 -39
  7. data/lib/solargraph/complex_type/unique_type.rb +187 -73
  8. data/lib/solargraph/complex_type.rb +105 -40
  9. data/lib/solargraph/doc_map.rb +19 -3
  10. data/lib/solargraph/gem_pins.rb +9 -1
  11. data/lib/solargraph/language_server/host/dispatch.rb +8 -1
  12. data/lib/solargraph/language_server/host/message_worker.rb +29 -3
  13. data/lib/solargraph/language_server/host/sources.rb +1 -61
  14. data/lib/solargraph/language_server/host.rb +21 -68
  15. data/lib/solargraph/language_server/message/base.rb +1 -1
  16. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  17. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  18. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  19. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  20. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  21. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  22. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  23. data/lib/solargraph/language_server/progress.rb +135 -0
  24. data/lib/solargraph/language_server.rb +1 -0
  25. data/lib/solargraph/library.rb +144 -113
  26. data/lib/solargraph/location.rb +14 -1
  27. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  28. data/lib/solargraph/parser/node_processor.rb +1 -0
  29. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  30. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -7
  31. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -5
  32. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  34. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +53 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +6 -4
  36. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  37. data/lib/solargraph/parser.rb +2 -5
  38. data/lib/solargraph/pin/base.rb +15 -1
  39. data/lib/solargraph/pin/base_variable.rb +35 -6
  40. data/lib/solargraph/pin/block.rb +48 -11
  41. data/lib/solargraph/pin/callable.rb +147 -0
  42. data/lib/solargraph/pin/closure.rb +8 -3
  43. data/lib/solargraph/pin/common.rb +2 -6
  44. data/lib/solargraph/pin/conversions.rb +3 -2
  45. data/lib/solargraph/pin/delegated_method.rb +5 -1
  46. data/lib/solargraph/pin/documenting.rb +2 -0
  47. data/lib/solargraph/pin/instance_variable.rb +2 -2
  48. data/lib/solargraph/pin/method.rb +54 -32
  49. data/lib/solargraph/pin/namespace.rb +4 -4
  50. data/lib/solargraph/pin/parameter.rb +14 -39
  51. data/lib/solargraph/pin/proxy_type.rb +1 -1
  52. data/lib/solargraph/pin/signature.rb +3 -129
  53. data/lib/solargraph/pin.rb +4 -1
  54. data/lib/solargraph/range.rb +2 -4
  55. data/lib/solargraph/rbs_map/conversions.rb +79 -42
  56. data/lib/solargraph/rbs_map/core_fills.rb +6 -6
  57. data/lib/solargraph/rbs_map.rb +11 -3
  58. data/lib/solargraph/shell.rb +35 -15
  59. data/lib/solargraph/source/chain/array.rb +6 -5
  60. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  61. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  62. data/lib/solargraph/source/chain/call.rb +78 -50
  63. data/lib/solargraph/source/chain/link.rb +9 -0
  64. data/lib/solargraph/source/chain/or.rb +1 -1
  65. data/lib/solargraph/source/chain.rb +60 -16
  66. data/lib/solargraph/source/cursor.rb +13 -2
  67. data/lib/solargraph/source/updater.rb +1 -0
  68. data/lib/solargraph/source.rb +102 -129
  69. data/lib/solargraph/source_map/clip.rb +4 -4
  70. data/lib/solargraph/source_map/data.rb +30 -0
  71. data/lib/solargraph/source_map/mapper.rb +3 -2
  72. data/lib/solargraph/source_map.rb +37 -15
  73. data/lib/solargraph/type_checker/rules.rb +6 -1
  74. data/lib/solargraph/type_checker.rb +50 -25
  75. data/lib/solargraph/version.rb +1 -1
  76. data/lib/solargraph/views/environment.erb +3 -5
  77. data/lib/solargraph/workspace/config.rb +2 -1
  78. data/lib/solargraph/workspace.rb +13 -0
  79. metadata +6 -3
  80. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
@@ -34,7 +34,7 @@ module Solargraph
34
34
  def update_libraries uri
35
35
  src = sources.find(uri)
36
36
  using = libraries.select { |lib| lib.contain?(src.filename) }
37
- using.push generic_library_for(uri) if using.empty?
37
+ using.push library_for(uri) if using.empty?
38
38
  using.each { |lib| lib.merge src }
39
39
  diagnoser.schedule uri
40
40
  end
@@ -114,6 +114,13 @@ module Solargraph
114
114
  # @return [Library]
115
115
  def generic_library
116
116
  @generic_library ||= Solargraph::Library.new(Solargraph::Workspace.new('', nil, options), nil)
117
+ .tap { |lib| lib.add_observer self }
118
+ end
119
+
120
+ # @param library [Solargraph::Library]
121
+ # @return [void]
122
+ def update progress
123
+ progress&.send(self)
117
124
  end
118
125
  end
119
126
  end
@@ -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
@@ -11,54 +11,10 @@ module Solargraph
11
11
  include Observable
12
12
  include UriHelpers
13
13
 
14
- def initialize
15
- @mutex = Mutex.new
16
- @stopped = true
17
- @has_uri = ConditionVariable.new
18
- end
19
-
20
- def stopped?
21
- @stopped
22
- end
23
-
24
- # @return [void]
25
- def start
26
- return unless @stopped
27
- @stopped = false
28
- Thread.new do
29
- tick until stopped?
30
- end
31
- end
32
-
33
- # @return [void]
34
- def tick
35
- uri = mutex.synchronize { next_uri }
36
-
37
- return if queue.include?(uri)
38
- mutex.synchronize do
39
- nxt = open_source_hash[uri].finish_synchronize
40
- open_source_hash[uri] = nxt
41
- changed
42
- notify_observers uri
43
- end
44
- end
45
-
46
14
  # @param uri [String]
47
15
  # @return [void]
48
16
  def add_uri(uri)
49
17
  queue.push(uri)
50
- @has_uri.signal
51
- end
52
-
53
- # @return [String]
54
- def next_uri
55
- @has_uri.wait(mutex) if queue.empty?
56
- queue.shift
57
- end
58
-
59
- # @return [void]
60
- def stop
61
- @stopped = true
62
18
  end
63
19
 
64
20
  # Open a source.
@@ -89,20 +45,7 @@ module Solargraph
89
45
  # @return [void]
90
46
  def update uri, updater
91
47
  src = find(uri)
92
- mutex.synchronize { open_source_hash[uri] = src.synchronize(updater) }
93
- changed
94
- notify_observers uri
95
- end
96
-
97
- # @param uri [String]
98
- # @param updater [Source::Updater]
99
- # @return [void]
100
- def async_update uri, updater
101
- src = find(uri)
102
- mutex.synchronize do
103
- open_source_hash[uri] = src.start_synchronize(updater)
104
- add_uri(uri)
105
- end
48
+ open_source_hash[uri] = src.synchronize(updater)
106
49
  changed
107
50
  notify_observers uri
108
51
  end
@@ -144,9 +87,6 @@ module Solargraph
144
87
  @open_source_hash ||= {}
145
88
  end
146
89
 
147
- # @return [Mutex]
148
- attr_reader :mutex
149
-
150
90
  # An array of source URIs that are waiting to finish synchronizing.
151
91
  #
152
92
  # @return [::Array<String>]
@@ -12,7 +12,6 @@ module Solargraph
12
12
  #
13
13
  class Host
14
14
  autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
15
- autoload :Cataloger, 'solargraph/language_server/host/cataloger'
16
15
  autoload :Sources, 'solargraph/language_server/host/sources'
17
16
  autoload :Dispatch, 'solargraph/language_server/host/dispatch'
18
17
  autoload :MessageWorker, 'solargraph/language_server/host/message_worker'
@@ -43,8 +42,6 @@ module Solargraph
43
42
  return unless stopped?
44
43
  @stopped = false
45
44
  diagnoser.start
46
- cataloger.start
47
- sources.start
48
45
  message_worker.start
49
46
  end
50
47
 
@@ -155,6 +152,7 @@ module Solargraph
155
152
  def delete *uris
156
153
  filenames = uris.map { |uri| uri_to_file(uri) }
157
154
  libraries.each do |lib|
155
+ lib.delete_observer self
158
156
  lib.delete(*filenames)
159
157
  end
160
158
  uris.each do |uri|
@@ -253,7 +251,7 @@ module Solargraph
253
251
  # @return [void]
254
252
  def change params
255
253
  updater = generate_updater(params)
256
- sources.async_update params['textDocument']['uri'], updater
254
+ sources.update params['textDocument']['uri'], updater
257
255
  diagnoser.schedule params['textDocument']['uri']
258
256
  end
259
257
 
@@ -294,8 +292,9 @@ module Solargraph
294
292
  begin
295
293
  workspace = Solargraph::Workspace.new(path, nil, options)
296
294
  lib = Solargraph::Library.new(workspace, name)
295
+ lib.add_observer self
297
296
  libraries.push lib
298
- async_library_map lib
297
+ library_map lib
299
298
  rescue WorkspaceTooLargeError => e
300
299
  send_notification 'window/showMessage', {
301
300
  'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
@@ -324,6 +323,7 @@ module Solargraph
324
323
  # @param lib [Library]
325
324
  libraries.delete_if do |lib|
326
325
  next false if lib.workspace.directory != directory
326
+ lib.delete_observer self
327
327
  true
328
328
  end
329
329
  end
@@ -458,9 +458,7 @@ module Solargraph
458
458
  return if @stopped
459
459
  @stopped = true
460
460
  message_worker.stop
461
- cataloger.stop
462
461
  diagnoser.stop
463
- sources.stop
464
462
  changed
465
463
  notify_observers
466
464
  end
@@ -684,11 +682,15 @@ module Solargraph
684
682
  libraries.each(&:catalog)
685
683
  end
686
684
 
687
- # @return [Hash{String => BasicObject}]
685
+ # @return [Hash{String => Hash{String => Boolean}}]
688
686
  def client_capabilities
689
687
  @client_capabilities ||= {}
690
688
  end
691
689
 
690
+ def client_supports_progress?
691
+ client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
692
+ end
693
+
692
694
  private
693
695
 
694
696
  # @return [MessageWorker]
@@ -701,11 +703,6 @@ module Solargraph
701
703
  @diagnoser ||= Diagnoser.new(self)
702
704
  end
703
705
 
704
- # @return [Cataloger]
705
- def cataloger
706
- @cataloger ||= Cataloger.new(self)
707
- end
708
-
709
706
  # A hash of client requests by ID. The host uses this to keep track of
710
707
  # pending responses.
711
708
  #
@@ -835,72 +832,28 @@ module Solargraph
835
832
  client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
836
833
  end
837
834
 
838
- def client_supports_progress?
839
- client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
840
- end
841
-
842
835
  # @param library [Library]
843
836
  # @return [void]
844
- def async_library_map library
837
+ def library_map library
845
838
  return if library.mapped?
846
- Thread.new do
847
- if client_supports_progress?
848
- uuid = SecureRandom.uuid
849
- send_request 'window/workDoneProgress/create', {
850
- token: uuid
851
- } do |response|
852
- do_async_library_map library, response.nil? ? uuid : nil
853
- end
854
- else
855
- do_async_library_map library
856
- end
857
- end
839
+ Thread.new { sync_library_map library }
858
840
  end
859
841
 
860
842
  # @param library [Library]
861
843
  # @param uuid [String, nil]
862
844
  # @return [void]
863
- def do_async_library_map library, uuid = nil
845
+ def sync_library_map library
864
846
  total = library.workspace.sources.length
865
- if uuid
866
- send_notification '$/progress', {
867
- token: uuid,
868
- value: {
869
- kind: 'begin',
870
- title: "Mapping workspace",
871
- message: "0/#{total} files",
872
- cancellable: false,
873
- percentage: 0
874
- }
875
- }
876
- end
877
- pct = 0
878
- mod = 10
847
+ progress = Progress.new('Mapping workspace')
848
+ progress.begin "0/#{total} files", 0
849
+ progress.send self
879
850
  while library.next_map
880
- next unless uuid
881
- cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
882
- if cur > pct && cur % mod == 0
883
- pct = cur
884
- send_notification '$/progress', {
885
- token: uuid,
886
- value: {
887
- kind: 'report',
888
- cancellable: false,
889
- message: "#{library.source_map_hash.keys.length}/#{total} files",
890
- percentage: pct
891
- }
892
- }
893
- end
894
- end
895
- if uuid
896
- send_notification '$/progress', {
897
- token: uuid,
898
- value: {
899
- kind: 'end',
900
- message: 'Mapping complete'
901
- }
902
- }
851
+ pct = ((library.source_map_hash.keys.length.to_f / total) * 100).to_i
852
+ progress.report "#{library.source_map_hash.keys.length}/#{total} files", pct
853
+ progress.send self
903
854
  end
855
+ progress.finish 'done'
856
+ progress.send self
904
857
  end
905
858
  end
906
859
  end
@@ -16,7 +16,7 @@ module Solargraph
16
16
  # @return [String]
17
17
  attr_reader :method
18
18
 
19
- # @return [Hash]
19
+ # @return [Hash{String => Array, Hash, String, Integer}]
20
20
  attr_reader :params
21
21
 
22
22
  # @return [Hash, Array, nil]
@@ -47,6 +47,8 @@ module Solargraph
47
47
 
48
48
  private
49
49
 
50
+ # @todo '?' methods should type like RBS 'boolish' rather than a strict true or false
51
+ # @sg-ignore
50
52
  def support_workspace_folders?
51
53
  params['capabilities'] &&
52
54
  params['capabilities']['workspace'] &&
@@ -82,6 +84,7 @@ module Solargraph
82
84
  }
83
85
  end
84
86
 
87
+ # @return [Hash{Symbol => Hash{Symbol => String, Array<String>}}]
85
88
  def static_on_type_formatting
86
89
  {
87
90
  documentOnTypeFormattingProvider: {
@@ -91,6 +94,7 @@ module Solargraph
91
94
  }
92
95
  end
93
96
 
97
+ # @return [Hash{Symbol => Boolean}]
94
98
  def static_hover
95
99
  return {} unless host.options['hover']
96
100
  {
@@ -98,6 +102,7 @@ module Solargraph
98
102
  }
99
103
  end
100
104
 
105
+ # @return [Hash{Symbol => Boolean}]
101
106
  def static_document_formatting
102
107
  return {} unless host.options['formatting']
103
108
  {
@@ -105,6 +110,7 @@ module Solargraph
105
110
  }
106
111
  end
107
112
 
113
+ # @return [Hash{Symbol => Boolean}]
108
114
  def static_document_symbols
109
115
  return {} unless host.options['symbols']
110
116
  {
@@ -112,12 +118,14 @@ module Solargraph
112
118
  }
113
119
  end
114
120
 
121
+ # @return [Hash{Symbol => Boolean}]
115
122
  def static_workspace_symbols
116
123
  {
117
124
  workspaceSymbolProvider: true
118
125
  }
119
126
  end
120
127
 
128
+ # @return [Hash{Symbol => Boolean}]
121
129
  def static_definitions
122
130
  return {} unless host.options['definitions']
123
131
  {
@@ -125,6 +133,7 @@ module Solargraph
125
133
  }
126
134
  end
127
135
 
136
+ # @return [Hash{Symbol => Boolean}]
128
137
  def static_type_definitions
129
138
  return {} unless host.options['typeDefinitions']
130
139
  {
@@ -132,12 +141,15 @@ module Solargraph
132
141
  }
133
142
  end
134
143
 
144
+ # @return [Hash{Symbol => Hash{Symbol => Boolean}}]
135
145
  def static_rename
136
146
  {
137
147
  renameProvider: {prepareProvider: true}
138
148
  }
139
149
  end
140
150
 
151
+
152
+ # @return [Hash{Symbol => Boolean}]
141
153
  def static_references
142
154
  return {} unless host.options['references']
143
155
  {
@@ -145,6 +157,7 @@ module Solargraph
145
157
  }
146
158
  end
147
159
 
160
+ # @return [Hash{Symbol => Boolean}]
148
161
  def static_folding_range
149
162
  return {} unless host.options['folding']
150
163
  {
@@ -152,6 +165,7 @@ module Solargraph
152
165
  }
153
166
  end
154
167
 
168
+ # @return [Hash{Symbol => Boolean}]
155
169
  def static_highlights
156
170
  {
157
171
  documentHighlightProvider: true
@@ -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
  }
@@ -70,6 +70,7 @@ module Solargraph
70
70
 
71
71
  def formatter_class(config)
72
72
  if self.class.const_defined?('BlankRubocopFormatter')
73
+ # @sg-ignore
73
74
  BlankRubocopFormatter
74
75
  else
75
76
  require_rubocop(config['version'])
@@ -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
  }
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'securerandom'
4
+
5
+ module Solargraph
6
+ module LanguageServer
7
+ # Progress notification handling for language server hosts.
8
+ #
9
+ class Progress
10
+ WAITING = :waiting
11
+ CREATED = :created
12
+ FINISHED = :finished
13
+
14
+ # @return [String]
15
+ attr_reader :uuid
16
+
17
+ # @return [String]
18
+ attr_reader :title
19
+
20
+ # @return [String, nil]
21
+ attr_reader :kind
22
+
23
+ # @return [String, nil]
24
+ attr_reader :message
25
+
26
+ # @return [Integer]
27
+ attr_reader :percentage
28
+
29
+ # @return [Symbol]
30
+ attr_reader :status
31
+
32
+ # @param title [String]
33
+ def initialize title
34
+ @title = title
35
+ @uuid = SecureRandom.uuid
36
+ @percentage = 0
37
+ @status = WAITING
38
+ end
39
+
40
+ # @param message [String]
41
+ # @param percentage [Integer]
42
+ def begin message, percentage
43
+ @kind = 'begin'
44
+ @message = message
45
+ @percentage = percentage
46
+ end
47
+
48
+ # @param message [String]
49
+ # @param percentage [Integer]
50
+ def report message, percentage
51
+ @kind = 'report'
52
+ @message = message
53
+ @percentage = percentage
54
+ end
55
+
56
+ # @param message [String]
57
+ def finish message
58
+ @kind = 'end'
59
+ @message = message
60
+ @percentage = 100
61
+ true
62
+ end
63
+
64
+ # @param host [Solargraph::LanguageServer::Host]
65
+ def send host
66
+ return unless host.client_supports_progress? && !finished?
67
+
68
+ message = build
69
+ create(host)
70
+ host.send_notification '$/progress', message
71
+ @status = FINISHED if kind == 'end'
72
+ keep_alive host
73
+ end
74
+
75
+ def created?
76
+ [CREATED, FINISHED].include?(status)
77
+ end
78
+
79
+ def finished?
80
+ status == FINISHED
81
+ end
82
+
83
+ private
84
+
85
+ # @param host [Solargraph::LanguageServer::Host]
86
+ # @return [void]
87
+ def create host
88
+ return if created?
89
+
90
+ host.send_request 'window/workDoneProgress/create', { token: uuid }
91
+ @status = CREATED
92
+ end
93
+
94
+ def build
95
+ {
96
+ token: uuid,
97
+ value: {
98
+ kind: kind,
99
+ cancellable: false
100
+ }.merge(build_value)
101
+ }
102
+ end
103
+
104
+ def build_value
105
+ case kind
106
+ when 'begin'
107
+ { title: title, message: message, percentage: percentage }
108
+ when 'report'
109
+ { message: message, percentage: percentage }
110
+ when 'end'
111
+ { message: message }
112
+ else
113
+ raise "Invalid progress kind #{kind}"
114
+ end
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
133
+ end
134
+ end
135
+ end
@@ -15,5 +15,6 @@ module Solargraph
15
15
  autoload :MessageTypes, 'solargraph/language_server/message_types'
16
16
  autoload :Request, 'solargraph/language_server/request'
17
17
  autoload :Transport, 'solargraph/language_server/transport'
18
+ autoload :Progress, 'solargraph/language_server/progress'
18
19
  end
19
20
  end