solargraph 0.32.1 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +25 -0
  6. data/EXAMPLES.md +76 -0
  7. data/Gemfile +3 -0
  8. data/LANGUAGE_SERVER.md +51 -0
  9. data/LICENSE +21 -0
  10. data/OVERVIEW.md +37 -0
  11. data/README.md +106 -0
  12. data/Rakefile +14 -0
  13. data/SERVER.md +95 -0
  14. data/bin/solargraph +0 -0
  15. data/bin/solargraph-runtime +5 -5
  16. data/lib/solargraph.rb +54 -54
  17. data/lib/solargraph/api_map.rb +659 -659
  18. data/lib/solargraph/api_map/cache.rb +49 -49
  19. data/lib/solargraph/api_map/source_to_yard.rb +67 -67
  20. data/lib/solargraph/api_map/store.rb +201 -201
  21. data/lib/solargraph/bundle.rb +24 -24
  22. data/lib/solargraph/complex_type.rb +150 -150
  23. data/lib/solargraph/complex_type/type_methods.rb +124 -124
  24. data/lib/solargraph/complex_type/unique_type.rb +44 -44
  25. data/lib/solargraph/core_fills.rb +37 -37
  26. data/lib/solargraph/diagnostics.rb +52 -52
  27. data/lib/solargraph/diagnostics/base.rb +20 -20
  28. data/lib/solargraph/diagnostics/require_not_found.rb +28 -28
  29. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  30. data/lib/solargraph/diagnostics/rubocop_helpers.rb +46 -46
  31. data/lib/solargraph/diagnostics/type_not_defined.rb +108 -108
  32. data/lib/solargraph/diagnostics/update_errors.rb +38 -38
  33. data/lib/solargraph/language_server/completion_item_kinds.rb +33 -33
  34. data/lib/solargraph/language_server/error_codes.rb +18 -18
  35. data/lib/solargraph/language_server/host.rb +684 -681
  36. data/lib/solargraph/language_server/host/cataloger.rb +54 -79
  37. data/lib/solargraph/language_server/host/diagnoser.rb +80 -80
  38. data/lib/solargraph/language_server/host/dispatch.rb +112 -113
  39. data/lib/solargraph/language_server/host/sources.rb +138 -138
  40. data/lib/solargraph/language_server/message.rb +90 -90
  41. data/lib/solargraph/language_server/message/base.rb +83 -83
  42. data/lib/solargraph/language_server/message/completion_item/resolve.rb +40 -40
  43. data/lib/solargraph/language_server/message/exit_notification.rb +11 -11
  44. data/lib/solargraph/language_server/message/extended.rb +19 -19
  45. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +86 -86
  46. data/lib/solargraph/language_server/message/extended/document.rb +18 -18
  47. data/lib/solargraph/language_server/message/extended/document_gems.rb +30 -30
  48. data/lib/solargraph/language_server/message/extended/environment.rb +20 -20
  49. data/lib/solargraph/language_server/message/extended/search.rb +18 -18
  50. data/lib/solargraph/language_server/message/initialize.rb +141 -141
  51. data/lib/solargraph/language_server/message/initialized.rb +23 -23
  52. data/lib/solargraph/language_server/message/shutdown.rb +11 -11
  53. data/lib/solargraph/language_server/message/text_document.rb +25 -25
  54. data/lib/solargraph/language_server/message/text_document/completion.rb +51 -51
  55. data/lib/solargraph/language_server/message/text_document/definition.rb +18 -18
  56. data/lib/solargraph/language_server/message/text_document/did_change.rb +13 -13
  57. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +21 -21
  58. data/lib/solargraph/language_server/message/text_document/folding_range.rb +24 -24
  59. data/lib/solargraph/language_server/message/text_document/formatting.rb +50 -50
  60. data/lib/solargraph/language_server/message/text_document/hover.rb +31 -31
  61. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +32 -32
  62. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +9 -9
  63. data/lib/solargraph/language_server/message/text_document/references.rb +14 -14
  64. data/lib/solargraph/language_server/message/text_document/rename.rb +17 -17
  65. data/lib/solargraph/language_server/message/text_document/signature_help.rb +19 -19
  66. data/lib/solargraph/language_server/message/workspace.rb +12 -12
  67. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -29
  68. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +29 -27
  69. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  70. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +21 -21
  71. data/lib/solargraph/language_server/request.rb +22 -22
  72. data/lib/solargraph/language_server/symbol_kinds.rb +34 -34
  73. data/lib/solargraph/language_server/transport.rb +11 -11
  74. data/lib/solargraph/language_server/transport/adapter.rb +60 -60
  75. data/lib/solargraph/language_server/transport/data_reader.rb +66 -66
  76. data/lib/solargraph/language_server/uri_helpers.rb +25 -25
  77. data/lib/solargraph/library.rb +421 -419
  78. data/lib/solargraph/live_map.rb +126 -126
  79. data/lib/solargraph/live_map/cache.rb +38 -38
  80. data/lib/solargraph/location.rb +31 -31
  81. data/lib/solargraph/logging.rb +25 -25
  82. data/lib/solargraph/page.rb +68 -68
  83. data/lib/solargraph/pin.rb +50 -50
  84. data/lib/solargraph/pin/attribute.rb +41 -41
  85. data/lib/solargraph/pin/base.rb +280 -280
  86. data/lib/solargraph/pin/base_method.rb +76 -76
  87. data/lib/solargraph/pin/base_variable.rb +72 -72
  88. data/lib/solargraph/pin/block.rb +32 -32
  89. data/lib/solargraph/pin/block_parameter.rb +103 -103
  90. data/lib/solargraph/pin/class_variable.rb +9 -9
  91. data/lib/solargraph/pin/constant.rb +30 -30
  92. data/lib/solargraph/pin/conversions.rb +79 -79
  93. data/lib/solargraph/pin/documenting.rb +41 -41
  94. data/lib/solargraph/pin/duck_method.rb +14 -14
  95. data/lib/solargraph/pin/global_variable.rb +9 -9
  96. data/lib/solargraph/pin/instance_variable.rb +9 -9
  97. data/lib/solargraph/pin/keyword.rb +17 -17
  98. data/lib/solargraph/pin/local_variable.rb +23 -23
  99. data/lib/solargraph/pin/localized.rb +22 -22
  100. data/lib/solargraph/pin/method.rb +126 -126
  101. data/lib/solargraph/pin/method_alias.rb +30 -30
  102. data/lib/solargraph/pin/method_parameter.rb +40 -40
  103. data/lib/solargraph/pin/namespace.rb +54 -54
  104. data/lib/solargraph/pin/plugin/method.rb +25 -25
  105. data/lib/solargraph/pin/proxy_type.rb +35 -35
  106. data/lib/solargraph/pin/reference.rb +22 -22
  107. data/lib/solargraph/pin/reference/extend.rb +11 -11
  108. data/lib/solargraph/pin/reference/include.rb +11 -11
  109. data/lib/solargraph/pin/reference/require.rb +15 -15
  110. data/lib/solargraph/pin/reference/superclass.rb +11 -11
  111. data/lib/solargraph/pin/symbol.rb +44 -44
  112. data/lib/solargraph/pin/yard_pin.rb +10 -10
  113. data/lib/solargraph/pin/yard_pin/constant.rb +14 -14
  114. data/lib/solargraph/pin/yard_pin/method.rb +35 -35
  115. data/lib/solargraph/pin/yard_pin/namespace.rb +19 -19
  116. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +14 -14
  117. data/lib/solargraph/plugin.rb +8 -8
  118. data/lib/solargraph/plugin/base.rb +41 -41
  119. data/lib/solargraph/plugin/canceler.rb +11 -11
  120. data/lib/solargraph/plugin/process.rb +172 -172
  121. data/lib/solargraph/plugin/runtime.rb +134 -134
  122. data/lib/solargraph/position.rb +110 -110
  123. data/lib/solargraph/range.rb +83 -83
  124. data/lib/solargraph/server_methods.rb +14 -14
  125. data/lib/solargraph/shell.rb +102 -102
  126. data/lib/solargraph/source.rb +521 -521
  127. data/lib/solargraph/source/chain.rb +120 -120
  128. data/lib/solargraph/source/chain/call.rb +107 -107
  129. data/lib/solargraph/source/chain/class_variable.rb +11 -11
  130. data/lib/solargraph/source/chain/constant.rb +30 -30
  131. data/lib/solargraph/source/chain/global_variable.rb +11 -11
  132. data/lib/solargraph/source/chain/head.rb +33 -33
  133. data/lib/solargraph/source/chain/instance_variable.rb +11 -11
  134. data/lib/solargraph/source/chain/link.rb +33 -33
  135. data/lib/solargraph/source/chain/literal.rb +21 -21
  136. data/lib/solargraph/source/chain/variable.rb +11 -11
  137. data/lib/solargraph/source/change.rb +77 -77
  138. data/lib/solargraph/source/cursor.rb +157 -157
  139. data/lib/solargraph/source/node_chainer.rb +96 -96
  140. data/lib/solargraph/source/node_methods.rb +225 -225
  141. data/lib/solargraph/source/source_chainer.rb +183 -183
  142. data/lib/solargraph/source_map.rb +169 -169
  143. data/lib/solargraph/source_map/clip.rb +145 -145
  144. data/lib/solargraph/source_map/completion.rb +21 -21
  145. data/lib/solargraph/source_map/mapper.rb +149 -149
  146. data/lib/solargraph/source_map/node_processor.rb +78 -78
  147. data/lib/solargraph/source_map/node_processor/alias_node.rb +19 -19
  148. data/lib/solargraph/source_map/node_processor/args_node.rb +28 -28
  149. data/lib/solargraph/source_map/node_processor/base.rb +68 -68
  150. data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -11
  151. data/lib/solargraph/source_map/node_processor/block_node.rb +14 -14
  152. data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -14
  153. data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -14
  154. data/lib/solargraph/source_map/node_processor/def_node.rb +54 -54
  155. data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -21
  156. data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -12
  157. data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -18
  158. data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -16
  159. data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -26
  160. data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -12
  161. data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -11
  162. data/lib/solargraph/source_map/node_processor/send_node.rb +162 -162
  163. data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -11
  164. data/lib/solargraph/source_map/region.rb +58 -58
  165. data/lib/solargraph/version.rb +3 -3
  166. data/lib/solargraph/views/environment.erb +53 -53
  167. data/lib/solargraph/workspace.rb +183 -183
  168. data/lib/solargraph/workspace/config.rb +170 -170
  169. data/lib/solargraph/yard_map.rb +298 -298
  170. data/lib/solargraph/yard_map/cache.rb +17 -17
  171. data/lib/solargraph/yard_map/core_docs.rb +163 -163
  172. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  173. data/lib/yard-coregen.rb +16 -16
  174. data/lib/yard-solargraph.rb +18 -18
  175. data/solargraph.gemspec +37 -0
  176. data/travis-bundler.rb +10 -0
  177. metadata +19 -6
@@ -1,18 +1,18 @@
1
- module Solargraph
2
- module LanguageServer
3
- # The ErrorCode constants for the language server protocol.
4
- #
5
- module ErrorCodes
6
- PARSE_ERROR = -32700
7
- INVALID_REQUEST = -32600
8
- METHOD_NOT_FOUND = -32601
9
- INVALID_PARAMS = -32602
10
- INTERNAL_ERROR = -32603
11
- SERVER_ERROR_START = -32099
12
- SERVER_ERROR_END = -32000
13
- SERVER_NOT_INITIALIZED = -32002
14
- UNKNOWN_ERROR_CODE = -32001
15
- REQUEST_CANCELLED = -32800
16
- end
17
- end
18
- end
1
+ module Solargraph
2
+ module LanguageServer
3
+ # The ErrorCode constants for the language server protocol.
4
+ #
5
+ module ErrorCodes
6
+ PARSE_ERROR = -32700
7
+ INVALID_REQUEST = -32600
8
+ METHOD_NOT_FOUND = -32601
9
+ INVALID_PARAMS = -32602
10
+ INTERNAL_ERROR = -32603
11
+ SERVER_ERROR_START = -32099
12
+ SERVER_ERROR_END = -32000
13
+ SERVER_NOT_INITIALIZED = -32002
14
+ UNKNOWN_ERROR_CODE = -32001
15
+ REQUEST_CANCELLED = -32800
16
+ end
17
+ end
18
+ end
@@ -1,681 +1,684 @@
1
- require 'thread'
2
- require 'set'
3
-
4
- module Solargraph
5
- module LanguageServer
6
- # The language server protocol's data provider. Hosts are responsible for
7
- # querying the library and processing messages. They also provide thread
8
- # safety for multi-threaded transports.
9
- #
10
- class Host
11
- autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
12
- autoload :Cataloger, 'solargraph/language_server/host/cataloger'
13
- autoload :Sources, 'solargraph/language_server/host/sources'
14
- autoload :Dispatch, 'solargraph/language_server/host/dispatch'
15
-
16
- include UriHelpers
17
- include Logging
18
- include Dispatch
19
-
20
- def initialize
21
- @cancel_semaphore = Mutex.new
22
- @buffer_semaphore = Mutex.new
23
- @register_semaphore = Mutex.new
24
- @cancel = []
25
- @buffer = ''
26
- @stopped = true
27
- @next_request_id = 0
28
- @dynamic_capabilities = Set.new
29
- @registered_capabilities = Set.new
30
- end
31
-
32
- # Start asynchronous process handling.
33
- #
34
- # @return [void]
35
- def start
36
- return unless stopped?
37
- @stopped = false
38
- diagnoser.start
39
- cataloger.start
40
- sources.start
41
- end
42
-
43
- # Update the configuration options with the provided hash.
44
- #
45
- # @param update [Hash]
46
- def configure update
47
- return if update.nil?
48
- options.merge! update
49
- logger.level = LOG_LEVELS[options['logLevel']] || DEFAULT_LOG_LEVEL
50
- end
51
-
52
- # @return [Hash]
53
- def options
54
- @options ||= default_configuration
55
- end
56
-
57
- # Cancel the method with the specified ID.
58
- #
59
- # @param id [Integer]
60
- def cancel id
61
- @cancel_semaphore.synchronize { @cancel.push id }
62
- end
63
-
64
- # True if the host received a request to cancel the method with the
65
- # specified ID.
66
- #
67
- # @param id [Integer]
68
- # @return [Boolean]
69
- def cancel? id
70
- result = false
71
- @cancel_semaphore.synchronize { result = @cancel.include? id }
72
- result
73
- end
74
-
75
- # Delete the specified ID from the list of cancelled IDs if it exists.
76
- #
77
- # @param id [Integer]
78
- # @return [void]
79
- def clear id
80
- @cancel_semaphore.synchronize { @cancel.delete id }
81
- end
82
-
83
- # Start processing a request from the client. After the message is
84
- # processed, the transport is responsible for sending the response.
85
- #
86
- # @param request [Hash] The contents of the message.
87
- # @return [Solargraph::LanguageServer::Message::Base] The message handler.
88
- def receive request
89
- if request['method']
90
- logger.info "Server received #{request['method']}"
91
- logger.debug request
92
- message = Message.select(request['method']).new(self, request)
93
- begin
94
- message.process
95
- rescue Exception => e
96
- logger.warn "Error processing request: [#{e.class}] #{e.message}"
97
- logger.warn e.backtrace
98
- message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}"
99
- end
100
- message
101
- elsif request['id']
102
- # @todo What if the id is invalid?
103
- requests[request['id']].process(request['result'])
104
- requests.delete request['id']
105
- else
106
- logger.warn "Invalid message received."
107
- logger.debug request
108
- end
109
- end
110
-
111
- # Respond to a notification that a file was created in the workspace.
112
- # The libraries will determine whether the file should be merged; see
113
- # Solargraph::Library#create_from_disk.
114
- #
115
- # @param uri [String] The file uri.
116
- # @return [Boolean] True if a library accepted the file.
117
- def create uri
118
- filename = uri_to_file(uri)
119
- result = false
120
- libraries.each do |lib|
121
- result = true if lib.create_from_disk filename
122
- end
123
- diagnoser.schedule uri if open?(uri)
124
- result
125
- end
126
-
127
- # Delete the specified file from the library.
128
- #
129
- # @param uri [String] The file uri.
130
- # @return [void]
131
- def delete uri
132
- # sources.close uri # @todo It's possible for a deleted file to be open in an editor
133
- filename = uri_to_file(uri)
134
- libraries.each do |lib|
135
- # lib.delete filename
136
- lib.detach filename
137
- end
138
- send_notification "textDocument/publishDiagnostics", {
139
- uri: uri,
140
- diagnostics: []
141
- }
142
- end
143
-
144
- # Open the specified file in the library.
145
- #
146
- # @param uri [String] The file uri.
147
- # @param text [String] The contents of the file.
148
- # @param version [Integer] A version number.
149
- # @return [void]
150
- def open uri, text, version
151
- src = sources.open(uri, text, version)
152
- libraries.each do |lib|
153
- lib.merge src
154
- end
155
- diagnoser.schedule uri
156
- end
157
-
158
- def open_from_disk uri
159
- library = library_for(uri)
160
- library.open_from_disk uri_to_file(uri)
161
- diagnoser.schedule uri
162
- end
163
-
164
- # True if the specified file is currently open in the library.
165
- #
166
- # @param uri [String]
167
- # @return [Boolean]
168
- def open? uri
169
- sources.include? uri
170
- end
171
-
172
- # Close the file specified by the URI.
173
- #
174
- # @param uri [String]
175
- # @return [void]
176
- def close uri
177
- logger.info "Closing #{uri}"
178
- sources.close uri
179
- diagnoser.schedule uri
180
- end
181
-
182
- # @param uri [String]
183
- # @return [void]
184
- def diagnose uri
185
- if sources.include?(uri)
186
- logger.info "Diagnosing #{uri}"
187
- library = library_for(uri)
188
- library.catalog
189
- begin
190
- results = library.diagnose uri_to_file(uri)
191
- send_notification "textDocument/publishDiagnostics", {
192
- uri: uri,
193
- diagnostics: results
194
- }
195
- rescue DiagnosticsError => e
196
- logger.warn "Error in diagnostics: #{e.message}"
197
- options['diagnostics'] = false
198
- send_notification 'window/showMessage', {
199
- type: LanguageServer::MessageTypes::ERROR,
200
- message: "Error in diagnostics: #{e.message}"
201
- }
202
- end
203
- else
204
- send_notification 'textDocument/publishDiagnostics', {
205
- uri: uri,
206
- diagnostics: []
207
- }
208
- end
209
- end
210
-
211
- # Update a document from the parameters of a textDocument/didChange
212
- # method.
213
- #
214
- # @param params [Hash]
215
- # @return [void]
216
- def change params
217
- updater = generate_updater(params)
218
- sources.async_update params['textDocument']['uri'], updater
219
- end
220
-
221
- # Queue a message to be sent to the client.
222
- #
223
- # @param message [String] The message to send.
224
- def queue message
225
- @buffer_semaphore.synchronize do
226
- @buffer += message
227
- end
228
- end
229
-
230
- # Clear the message buffer and return the most recent data.
231
- #
232
- # @return [String] The most recent data or an empty string.
233
- def flush
234
- tmp = nil
235
- @buffer_semaphore.synchronize do
236
- tmp = @buffer.clone
237
- @buffer.clear
238
- end
239
- tmp
240
- end
241
-
242
- # Prepare a library for the specified directory.
243
- #
244
- # @param directory [String]
245
- # @param name [String, nil]
246
- # @return [void]
247
- def prepare directory, name = nil
248
- # No need to create a library without a directory. The generic library
249
- # will handle it.
250
- return if directory.nil?
251
- logger.info "Preparing library for #{directory}"
252
- path = ''
253
- path = normalize_separators(directory) unless directory.nil?
254
- begin
255
- lib = Solargraph::Library.load(path, name)
256
- libraries.push lib
257
- rescue WorkspaceTooLargeError => e
258
- send_notification 'window/showMessage', {
259
- 'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
260
- 'message' => e.message
261
- }
262
- end
263
- end
264
-
265
- # Prepare multiple folders.
266
- #
267
- # @param array [Array<Hash{String => String}>]
268
- # @return [void]
269
- def prepare_folders array
270
- return if array.nil?
271
- array.each do |folder|
272
- prepare uri_to_file(folder['uri']), folder['name']
273
- end
274
- end
275
-
276
- # Remove a directory.
277
- #
278
- # @param directory [String]
279
- # @return [void]
280
- def remove directory
281
- logger.info "Removing library for #{directory}"
282
- # @param lib [Library]
283
- libraries.delete_if do |lib|
284
- next false if lib.workspace.directory != directory
285
- true
286
- end
287
- end
288
-
289
- def remove_folders array
290
- array.each do |folder|
291
- remove uri_to_file(folder['uri'])
292
- end
293
- end
294
-
295
- def folders
296
- libraries.map { |lib| lib.workspace.directory }
297
- end
298
-
299
- # Send a notification to the client.
300
- #
301
- # @param method [String] The message method
302
- # @param params [Hash] The method parameters
303
- def send_notification method, params
304
- response = {
305
- jsonrpc: "2.0",
306
- method: method,
307
- params: params
308
- }
309
- json = response.to_json
310
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
311
- queue envelope
312
- logger.info "Server sent #{method}"
313
- logger.debug params
314
- end
315
-
316
- # Send a request to the client and execute the provided block to process
317
- # the response. If an ID is not provided, the host will use an auto-
318
- # incrementing integer.
319
- #
320
- # @param method [String] The message method
321
- # @param params [Hash] The method parameters
322
- # @param id [String] An optional ID
323
- # @yieldparam [Hash] The result sent by the client
324
- def send_request method, params, &block
325
- message = {
326
- jsonrpc: "2.0",
327
- method: method,
328
- params: params,
329
- id: @next_request_id
330
- }
331
- json = message.to_json
332
- requests[@next_request_id] = Request.new(@next_request_id, &block)
333
- envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
334
- queue envelope
335
- @next_request_id += 1
336
- logger.info "Server sent #{method}"
337
- logger.debug params
338
- end
339
-
340
- # Register the methods as capabilities with the client.
341
- # This method will avoid duplicating registrations and ignore methods
342
- # that were not flagged for dynamic registration by the client.
343
- #
344
- # @param methods [Array<String>] The methods to register
345
- # @return [void]
346
- def register_capabilities methods
347
- logger.debug "Registering capabilities: #{methods}"
348
- @register_semaphore.synchronize do
349
- send_request 'client/registerCapability', {
350
- registrations: methods.select{|m| can_register?(m) and !registered?(m)}.map { |m|
351
- @registered_capabilities.add m
352
- {
353
- id: m,
354
- method: m,
355
- registerOptions: dynamic_capability_options[m]
356
- }
357
- }
358
- }
359
- end
360
- end
361
-
362
- # Unregister the methods with the client.
363
- # This method will avoid duplicating unregistrations and ignore methods
364
- # that were not flagged for dynamic registration by the client.
365
- #
366
- # @param methods [Array<String>] The methods to unregister
367
- # @return [void]
368
- def unregister_capabilities methods
369
- logger.debug "Unregistering capabilities: #{methods}"
370
- @register_semaphore.synchronize do
371
- send_request 'client/unregisterCapability', {
372
- unregisterations: methods.select{|m| registered?(m)}.map{ |m|
373
- @registered_capabilities.delete m
374
- {
375
- id: m,
376
- method: m
377
- }
378
- }
379
- }
380
- end
381
- end
382
-
383
- # Flag a method as available for dynamic registration.
384
- #
385
- # @param method [String] The method name, e.g., 'textDocument/completion'
386
- # @return [void]
387
- def allow_registration method
388
- @register_semaphore.synchronize do
389
- @dynamic_capabilities.add method
390
- end
391
- end
392
-
393
- # True if the specified LSP method can be dynamically registered.
394
- #
395
- # @param method [String]
396
- # @return [Boolean]
397
- def can_register? method
398
- @dynamic_capabilities.include?(method)
399
- end
400
-
401
- # True if the specified method has been registered.
402
- #
403
- # @param method [String] The method name, e.g., 'textDocument/completion'
404
- # @return [Boolean]
405
- def registered? method
406
- @registered_capabilities.include?(method)
407
- end
408
-
409
- def synchronizing?
410
- cataloger.synchronizing?
411
- end
412
-
413
- # @return [void]
414
- def stop
415
- return if @stopped
416
- @stopped = true
417
- cataloger.stop
418
- diagnoser.stop
419
- sources.stop
420
- end
421
-
422
- def stopped?
423
- @stopped
424
- end
425
-
426
- # Locate multiple pins that match a completion item. The first match is
427
- # based on the corresponding location in a library source if available.
428
- # Subsequent matches are based on path.
429
- #
430
- # @param params [Hash] A hash representation of a completion item
431
- # @return [Array<Pin::Base>]
432
- def locate_pins params
433
- return [] unless params['data'] && params['data']['uri']
434
- library = library_for(params['data']['uri'])
435
- result = []
436
- if params['data']['location']
437
- location = Location.new(
438
- params['data']['location']['filename'],
439
- Range.from_to(
440
- params['data']['location']['range']['start']['line'],
441
- params['data']['location']['range']['start']['character'],
442
- params['data']['location']['range']['end']['line'],
443
- params['data']['location']['range']['end']['character']
444
- )
445
- )
446
- result.concat library.locate_pins(location).select{ |pin| pin.name == params['label'] }
447
- end
448
- if params['data']['path']
449
- result.concat library.path_pins(params['data']['path'])
450
- end
451
- result.uniq
452
- end
453
-
454
- # @param uri [String]
455
- # @return [String]
456
- def read_text uri
457
- library = library_for(uri)
458
- filename = uri_to_file(uri)
459
- library.read_text(filename)
460
- end
461
-
462
- # @param uri [String]
463
- # @param line [Integer]
464
- # @param column [Integer]
465
- # @return [Solargraph::ApiMap::Completion]
466
- def completions_at uri, line, column
467
- library = library_for(uri)
468
- library.completions_at uri_to_file(uri), line, column
469
- end
470
-
471
- # @param uri [String]
472
- # @param line [Integer]
473
- # @param column [Integer]
474
- # @return [Array<Solargraph::Pin::Base>]
475
- def definitions_at uri, line, column
476
- library = library_for(uri)
477
- library.definitions_at(uri_to_file(uri), line, column)
478
- end
479
-
480
- # @param uri [String]
481
- # @param line [Integer]
482
- # @param column [Integer]
483
- # @return [Array<Solargraph::Pin::Base>]
484
- def signatures_at uri, line, column
485
- library = library_for(uri)
486
- library.signatures_at(uri_to_file(uri), line, column)
487
- end
488
-
489
- # @param uri [String]
490
- # @param line [Integer]
491
- # @param column [Integer]
492
- # @param strip [Boolean] Strip special characters from variable names
493
- # @return [Array<Solargraph::Range>]
494
- def references_from uri, line, column, strip: true
495
- library = library_for(uri)
496
- library.references_from(uri_to_file(uri), line, column, strip: strip)
497
- end
498
-
499
- # @param query [String]
500
- # @return [Array<Solargraph::Pin::Base>]
501
- def query_symbols query
502
- result = []
503
- (libraries + [generic_library]).each { |lib| result.concat lib.query_symbols(query) }
504
- result.uniq
505
- end
506
-
507
- # @param query [String]
508
- # @return [Array<String>]
509
- def search query
510
- result = []
511
- libraries.each { |lib| result.concat lib.search(query) }
512
- result
513
- end
514
-
515
- # @param query [String]
516
- # @return [String]
517
- def document query
518
- result = []
519
- libraries.each { |lib| result.concat lib.document(query) }
520
- result
521
- end
522
-
523
- # @param uri [String]
524
- # @return [Array<Solargraph::Pin::Base>]
525
- def document_symbols uri
526
- library = library_for(uri)
527
- # At this level, document symbols should be unique; e.g., a
528
- # module_function method should return the location for Module.method
529
- # or Module#method, but not both.
530
- library.document_symbols(uri_to_file(uri)).uniq(&:location)
531
- end
532
-
533
- # Send a notification to the client.
534
- #
535
- # @param text [String]
536
- # @param type [Integer] A MessageType constant
537
- # @return [void]
538
- def show_message text, type = LanguageServer::MessageTypes::INFO
539
- send_notification 'window/showMessage', {
540
- type: type,
541
- message: text
542
- }
543
- end
544
-
545
- # Send a notification with optional responses.
546
- #
547
- # @param text [String]
548
- # @param type [Integer] A MessageType constant
549
- # @param actions [Array<String>] Response options for the client
550
- # @param &block The block that processes the response
551
- # @yieldparam [String] The action received from the client
552
- # @return [void]
553
- def show_message_request text, type, actions, &block
554
- send_request 'window/showMessageRequest', {
555
- type: type,
556
- message: text,
557
- actions: actions
558
- }, &block
559
- end
560
-
561
- # Get a list of IDs for server requests that are waiting for responses
562
- # from the client.
563
- #
564
- # @return [Array<Integer>]
565
- def pending_requests
566
- requests.keys
567
- end
568
-
569
- # @return [Hash{String => Object}]
570
- def default_configuration
571
- {
572
- 'completion' => true,
573
- 'hover' => true,
574
- 'symbols' => true,
575
- 'definitions' => true,
576
- 'rename' => true,
577
- 'references' => true,
578
- 'autoformat' => false,
579
- 'diagnostics' => false,
580
- 'formatting' => false,
581
- 'folding' => true,
582
- 'logLevel' => 'warn'
583
- }
584
- end
585
-
586
- # @param uri [String]
587
- # @return [Array<Range>]
588
- def folding_ranges uri
589
- sources.find(uri).folding_ranges
590
- end
591
-
592
- private
593
-
594
- # @return [Diagnoser]
595
- def diagnoser
596
- @diagnoser ||= Diagnoser.new(self)
597
- end
598
-
599
- # @return [Cataloger]
600
- def cataloger
601
- @cataloger ||= Cataloger.new(self)
602
- end
603
-
604
- def requests
605
- @requests ||= {}
606
- end
607
-
608
- def normalize_separators path
609
- return path if File::ALT_SEPARATOR.nil?
610
- path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
611
- end
612
-
613
- def generate_updater params
614
- changes = []
615
- params['contentChanges'].each do |chng|
616
- changes.push Solargraph::Source::Change.new(
617
- (chng['range'].nil? ?
618
- nil :
619
- Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], chng['range']['end']['line'], chng['range']['end']['character'])
620
- ),
621
- chng['text']
622
- )
623
- end
624
- Solargraph::Source::Updater.new(
625
- uri_to_file(params['textDocument']['uri']),
626
- params['textDocument']['version'],
627
- changes
628
- )
629
- end
630
-
631
- def dynamic_capability_options
632
- @dynamic_capability_options ||= {
633
- # textDocumentSync: 2, # @todo What should this be?
634
- 'textDocument/completion' => {
635
- resolveProvider: true,
636
- triggerCharacters: ['.', ':', '@']
637
- },
638
- # hoverProvider: true,
639
- # definitionProvider: true,
640
- 'textDocument/signatureHelp' => {
641
- triggerCharacters: ['(', ',']
642
- },
643
- # documentFormattingProvider: true,
644
- 'textDocument/onTypeFormatting' => {
645
- firstTriggerCharacter: '{',
646
- moreTriggerCharacter: ['(']
647
- },
648
- # documentSymbolProvider: true,
649
- # workspaceSymbolProvider: true,
650
- # workspace: {
651
- # workspaceFolders: {
652
- # supported: true,
653
- # changeNotifications: true
654
- # }
655
- # }
656
- 'textDocument/definition' => {
657
- definitionProvider: true
658
- },
659
- 'textDocument/references' => {
660
- referencesProvider: true
661
- },
662
- 'textDocument/rename' => {
663
- renameProvider: {prepareProvider: true}
664
- },
665
- 'textDocument/documentSymbol' => {
666
- documentSymbolProvider: true
667
- },
668
- 'workspace/symbol' => {
669
- workspaceSymbolProvider: true
670
- },
671
- 'textDocument/formatting' => {
672
- formattingProvider: true
673
- },
674
- 'textDocument/foldingRange' => {
675
- foldingRangeProvider: true
676
- }
677
- }
678
- end
679
- end
680
- end
681
- end
1
+ require 'thread'
2
+ require 'set'
3
+
4
+ module Solargraph
5
+ module LanguageServer
6
+ # The language server protocol's data provider. Hosts are responsible for
7
+ # querying the library and processing messages. They also provide thread
8
+ # safety for multi-threaded transports.
9
+ #
10
+ class Host
11
+ autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
12
+ autoload :Cataloger, 'solargraph/language_server/host/cataloger'
13
+ autoload :Sources, 'solargraph/language_server/host/sources'
14
+ autoload :Dispatch, 'solargraph/language_server/host/dispatch'
15
+
16
+ include UriHelpers
17
+ include Logging
18
+ include Dispatch
19
+
20
+ def initialize
21
+ @cancel_semaphore = Mutex.new
22
+ @buffer_semaphore = Mutex.new
23
+ @register_semaphore = Mutex.new
24
+ @cancel = []
25
+ @buffer = ''
26
+ @stopped = true
27
+ @next_request_id = 0
28
+ @dynamic_capabilities = Set.new
29
+ @registered_capabilities = Set.new
30
+ end
31
+
32
+ # Start asynchronous process handling.
33
+ #
34
+ # @return [void]
35
+ def start
36
+ return unless stopped?
37
+ @stopped = false
38
+ diagnoser.start
39
+ cataloger.start
40
+ sources.start
41
+ end
42
+
43
+ # Update the configuration options with the provided hash.
44
+ #
45
+ # @param update [Hash]
46
+ def configure update
47
+ return if update.nil?
48
+ options.merge! update
49
+ logger.level = LOG_LEVELS[options['logLevel']] || DEFAULT_LOG_LEVEL
50
+ end
51
+
52
+ # @return [Hash]
53
+ def options
54
+ @options ||= default_configuration
55
+ end
56
+
57
+ # Cancel the method with the specified ID.
58
+ #
59
+ # @param id [Integer]
60
+ def cancel id
61
+ @cancel_semaphore.synchronize { @cancel.push id }
62
+ end
63
+
64
+ # True if the host received a request to cancel the method with the
65
+ # specified ID.
66
+ #
67
+ # @param id [Integer]
68
+ # @return [Boolean]
69
+ def cancel? id
70
+ result = false
71
+ @cancel_semaphore.synchronize { result = @cancel.include? id }
72
+ result
73
+ end
74
+
75
+ # Delete the specified ID from the list of cancelled IDs if it exists.
76
+ #
77
+ # @param id [Integer]
78
+ # @return [void]
79
+ def clear id
80
+ @cancel_semaphore.synchronize { @cancel.delete id }
81
+ end
82
+
83
+ # Start processing a request from the client. After the message is
84
+ # processed, the transport is responsible for sending the response.
85
+ #
86
+ # @param request [Hash] The contents of the message.
87
+ # @return [Solargraph::LanguageServer::Message::Base] The message handler.
88
+ def receive request
89
+ if request['method']
90
+ logger.info "Server received #{request['method']}"
91
+ logger.debug request
92
+ message = Message.select(request['method']).new(self, request)
93
+ begin
94
+ message.process
95
+ rescue Exception => e
96
+ logger.warn "Error processing request: [#{e.class}] #{e.message}"
97
+ logger.warn e.backtrace
98
+ message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}"
99
+ end
100
+ message
101
+ elsif request['id']
102
+ # @todo What if the id is invalid?
103
+ requests[request['id']].process(request['result'])
104
+ requests.delete request['id']
105
+ else
106
+ logger.warn "Invalid message received."
107
+ logger.debug request
108
+ end
109
+ end
110
+
111
+ # Respond to a notification that a file was created in the workspace.
112
+ # The libraries will determine whether the file should be merged; see
113
+ # Solargraph::Library#create_from_disk.
114
+ #
115
+ # @param uri [String] The file uri.
116
+ # @return [Boolean] True if a library accepted the file.
117
+ def create uri
118
+ filename = uri_to_file(uri)
119
+ result = false
120
+ libraries.each do |lib|
121
+ result = true if lib.create_from_disk(filename)
122
+ end
123
+ diagnoser.schedule uri if open?(uri)
124
+ result
125
+ end
126
+
127
+ # Delete the specified file from the library.
128
+ #
129
+ # @param uri [String] The file uri.
130
+ # @return [void]
131
+ def delete uri
132
+ # sources.close uri # @todo It's possible for a deleted file to be open in an editor
133
+ filename = uri_to_file(uri)
134
+ libraries.each do |lib|
135
+ lib.delete(filename)
136
+ end
137
+ send_notification "textDocument/publishDiagnostics", {
138
+ uri: uri,
139
+ diagnostics: []
140
+ }
141
+ end
142
+
143
+ # Open the specified file in the library.
144
+ #
145
+ # @param uri [String] The file uri.
146
+ # @param text [String] The contents of the file.
147
+ # @param version [Integer] A version number.
148
+ # @return [void]
149
+ def open uri, text, version
150
+ src = sources.open(uri, text, version)
151
+ libraries.each do |lib|
152
+ lib.merge src
153
+ end
154
+ diagnoser.schedule uri
155
+ end
156
+
157
+ def open_from_disk uri
158
+ library = library_for(uri)
159
+ library.open_from_disk uri_to_file(uri)
160
+ diagnoser.schedule uri
161
+ end
162
+
163
+ # True if the specified file is currently open in the library.
164
+ #
165
+ # @param uri [String]
166
+ # @return [Boolean]
167
+ def open? uri
168
+ sources.include? uri
169
+ end
170
+
171
+ # Close the file specified by the URI.
172
+ #
173
+ # @param uri [String]
174
+ # @return [void]
175
+ def close uri
176
+ logger.info "Closing #{uri}"
177
+ sources.close uri
178
+ diagnoser.schedule uri
179
+ end
180
+
181
+ # @param uri [String]
182
+ # @return [void]
183
+ def diagnose uri
184
+ if sources.include?(uri)
185
+ logger.info "Diagnosing #{uri}"
186
+ library = library_for(uri)
187
+ library.catalog
188
+ begin
189
+ results = library.diagnose uri_to_file(uri)
190
+ send_notification "textDocument/publishDiagnostics", {
191
+ uri: uri,
192
+ diagnostics: results
193
+ }
194
+ rescue DiagnosticsError => e
195
+ logger.warn "Error in diagnostics: #{e.message}"
196
+ options['diagnostics'] = false
197
+ send_notification 'window/showMessage', {
198
+ type: LanguageServer::MessageTypes::ERROR,
199
+ message: "Error in diagnostics: #{e.message}"
200
+ }
201
+ end
202
+ else
203
+ send_notification 'textDocument/publishDiagnostics', {
204
+ uri: uri,
205
+ diagnostics: []
206
+ }
207
+ end
208
+ end
209
+
210
+ # Update a document from the parameters of a textDocument/didChange
211
+ # method.
212
+ #
213
+ # @param params [Hash]
214
+ # @return [void]
215
+ def change params
216
+ updater = generate_updater(params)
217
+ sources.async_update params['textDocument']['uri'], updater
218
+ end
219
+
220
+ # Queue a message to be sent to the client.
221
+ #
222
+ # @param message [String] The message to send.
223
+ def queue message
224
+ @buffer_semaphore.synchronize do
225
+ @buffer += message
226
+ end
227
+ end
228
+
229
+ # Clear the message buffer and return the most recent data.
230
+ #
231
+ # @return [String] The most recent data or an empty string.
232
+ def flush
233
+ tmp = nil
234
+ @buffer_semaphore.synchronize do
235
+ tmp = @buffer.clone
236
+ @buffer.clear
237
+ end
238
+ tmp
239
+ end
240
+
241
+ # Prepare a library for the specified directory.
242
+ #
243
+ # @param directory [String]
244
+ # @param name [String, nil]
245
+ # @return [void]
246
+ def prepare directory, name = nil
247
+ # No need to create a library without a directory. The generic library
248
+ # will handle it.
249
+ return if directory.nil?
250
+ logger.info "Preparing library for #{directory}"
251
+ path = ''
252
+ path = normalize_separators(directory) unless directory.nil?
253
+ begin
254
+ lib = Solargraph::Library.load(path, name)
255
+ libraries.push lib
256
+ rescue WorkspaceTooLargeError => e
257
+ send_notification 'window/showMessage', {
258
+ 'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
259
+ 'message' => e.message
260
+ }
261
+ end
262
+ end
263
+
264
+ # Prepare multiple folders.
265
+ #
266
+ # @param array [Array<Hash{String => String}>]
267
+ # @return [void]
268
+ def prepare_folders array
269
+ return if array.nil?
270
+ array.each do |folder|
271
+ prepare uri_to_file(folder['uri']), folder['name']
272
+ end
273
+ end
274
+
275
+ # Remove a directory.
276
+ #
277
+ # @param directory [String]
278
+ # @return [void]
279
+ def remove directory
280
+ logger.info "Removing library for #{directory}"
281
+ # @param lib [Library]
282
+ libraries.delete_if do |lib|
283
+ next false if lib.workspace.directory != directory
284
+ true
285
+ end
286
+ end
287
+
288
+ def remove_folders array
289
+ array.each do |folder|
290
+ remove uri_to_file(folder['uri'])
291
+ end
292
+ end
293
+
294
+ def folders
295
+ libraries.map { |lib| lib.workspace.directory }
296
+ end
297
+
298
+ # Send a notification to the client.
299
+ #
300
+ # @param method [String] The message method
301
+ # @param params [Hash] The method parameters
302
+ def send_notification method, params
303
+ response = {
304
+ jsonrpc: "2.0",
305
+ method: method,
306
+ params: params
307
+ }
308
+ json = response.to_json
309
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
310
+ queue envelope
311
+ logger.info "Server sent #{method}"
312
+ logger.debug params
313
+ end
314
+
315
+ # Send a request to the client and execute the provided block to process
316
+ # the response. If an ID is not provided, the host will use an auto-
317
+ # incrementing integer.
318
+ #
319
+ # @param method [String] The message method
320
+ # @param params [Hash] The method parameters
321
+ # @param id [String] An optional ID
322
+ # @yieldparam [Hash] The result sent by the client
323
+ def send_request method, params, &block
324
+ message = {
325
+ jsonrpc: "2.0",
326
+ method: method,
327
+ params: params,
328
+ id: @next_request_id
329
+ }
330
+ json = message.to_json
331
+ requests[@next_request_id] = Request.new(@next_request_id, &block)
332
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
333
+ queue envelope
334
+ @next_request_id += 1
335
+ logger.info "Server sent #{method}"
336
+ logger.debug params
337
+ end
338
+
339
+ # Register the methods as capabilities with the client.
340
+ # This method will avoid duplicating registrations and ignore methods
341
+ # that were not flagged for dynamic registration by the client.
342
+ #
343
+ # @param methods [Array<String>] The methods to register
344
+ # @return [void]
345
+ def register_capabilities methods
346
+ logger.debug "Registering capabilities: #{methods}"
347
+ @register_semaphore.synchronize do
348
+ send_request 'client/registerCapability', {
349
+ registrations: methods.select{|m| can_register?(m) and !registered?(m)}.map { |m|
350
+ @registered_capabilities.add m
351
+ {
352
+ id: m,
353
+ method: m,
354
+ registerOptions: dynamic_capability_options[m]
355
+ }
356
+ }
357
+ }
358
+ end
359
+ end
360
+
361
+ # Unregister the methods with the client.
362
+ # This method will avoid duplicating unregistrations and ignore methods
363
+ # that were not flagged for dynamic registration by the client.
364
+ #
365
+ # @param methods [Array<String>] The methods to unregister
366
+ # @return [void]
367
+ def unregister_capabilities methods
368
+ logger.debug "Unregistering capabilities: #{methods}"
369
+ @register_semaphore.synchronize do
370
+ send_request 'client/unregisterCapability', {
371
+ unregisterations: methods.select{|m| registered?(m)}.map{ |m|
372
+ @registered_capabilities.delete m
373
+ {
374
+ id: m,
375
+ method: m
376
+ }
377
+ }
378
+ }
379
+ end
380
+ end
381
+
382
+ # Flag a method as available for dynamic registration.
383
+ #
384
+ # @param method [String] The method name, e.g., 'textDocument/completion'
385
+ # @return [void]
386
+ def allow_registration method
387
+ @register_semaphore.synchronize do
388
+ @dynamic_capabilities.add method
389
+ end
390
+ end
391
+
392
+ # True if the specified LSP method can be dynamically registered.
393
+ #
394
+ # @param method [String]
395
+ # @return [Boolean]
396
+ def can_register? method
397
+ @dynamic_capabilities.include?(method)
398
+ end
399
+
400
+ # True if the specified method has been registered.
401
+ #
402
+ # @param method [String] The method name, e.g., 'textDocument/completion'
403
+ # @return [Boolean]
404
+ def registered? method
405
+ @registered_capabilities.include?(method)
406
+ end
407
+
408
+ def synchronizing?
409
+ !libraries.all?(&:synchronized?)
410
+ end
411
+
412
+ # @return [void]
413
+ def stop
414
+ return if @stopped
415
+ @stopped = true
416
+ cataloger.stop
417
+ diagnoser.stop
418
+ sources.stop
419
+ end
420
+
421
+ def stopped?
422
+ @stopped
423
+ end
424
+
425
+ # Locate multiple pins that match a completion item. The first match is
426
+ # based on the corresponding location in a library source if available.
427
+ # Subsequent matches are based on path.
428
+ #
429
+ # @param params [Hash] A hash representation of a completion item
430
+ # @return [Array<Pin::Base>]
431
+ def locate_pins params
432
+ return [] unless params['data'] && params['data']['uri']
433
+ library = library_for(params['data']['uri'])
434
+ result = []
435
+ if params['data']['location']
436
+ location = Location.new(
437
+ params['data']['location']['filename'],
438
+ Range.from_to(
439
+ params['data']['location']['range']['start']['line'],
440
+ params['data']['location']['range']['start']['character'],
441
+ params['data']['location']['range']['end']['line'],
442
+ params['data']['location']['range']['end']['character']
443
+ )
444
+ )
445
+ result.concat library.locate_pins(location).select{ |pin| pin.name == params['label'] }
446
+ end
447
+ if params['data']['path']
448
+ result.concat library.path_pins(params['data']['path'])
449
+ end
450
+ result.uniq
451
+ end
452
+
453
+ # @param uri [String]
454
+ # @return [String]
455
+ def read_text uri
456
+ library = library_for(uri)
457
+ filename = uri_to_file(uri)
458
+ library.read_text(filename)
459
+ end
460
+
461
+ # @param uri [String]
462
+ # @param line [Integer]
463
+ # @param column [Integer]
464
+ # @return [Solargraph::ApiMap::Completion]
465
+ def completions_at uri, line, column
466
+ library = library_for(uri)
467
+ library.completions_at uri_to_file(uri), line, column
468
+ end
469
+
470
+ # @param uri [String]
471
+ # @param line [Integer]
472
+ # @param column [Integer]
473
+ # @return [Array<Solargraph::Pin::Base>]
474
+ def definitions_at uri, line, column
475
+ library = library_for(uri)
476
+ library.definitions_at(uri_to_file(uri), line, column)
477
+ end
478
+
479
+ # @param uri [String]
480
+ # @param line [Integer]
481
+ # @param column [Integer]
482
+ # @return [Array<Solargraph::Pin::Base>]
483
+ def signatures_at uri, line, column
484
+ library = library_for(uri)
485
+ library.signatures_at(uri_to_file(uri), line, column)
486
+ end
487
+
488
+ # @param uri [String]
489
+ # @param line [Integer]
490
+ # @param column [Integer]
491
+ # @param strip [Boolean] Strip special characters from variable names
492
+ # @return [Array<Solargraph::Range>]
493
+ def references_from uri, line, column, strip: true
494
+ library = library_for(uri)
495
+ library.references_from(uri_to_file(uri), line, column, strip: strip)
496
+ end
497
+
498
+ # @param query [String]
499
+ # @return [Array<Solargraph::Pin::Base>]
500
+ def query_symbols query
501
+ result = []
502
+ (libraries + [generic_library]).each { |lib| result.concat lib.query_symbols(query) }
503
+ result.uniq
504
+ end
505
+
506
+ # @param query [String]
507
+ # @return [Array<String>]
508
+ def search query
509
+ result = []
510
+ libraries.each { |lib| result.concat lib.search(query) }
511
+ result
512
+ end
513
+
514
+ # @param query [String]
515
+ # @return [String]
516
+ def document query
517
+ result = []
518
+ libraries.each { |lib| result.concat lib.document(query) }
519
+ result
520
+ end
521
+
522
+ # @param uri [String]
523
+ # @return [Array<Solargraph::Pin::Base>]
524
+ def document_symbols uri
525
+ library = library_for(uri)
526
+ # At this level, document symbols should be unique; e.g., a
527
+ # module_function method should return the location for Module.method
528
+ # or Module#method, but not both.
529
+ library.document_symbols(uri_to_file(uri)).uniq(&:location)
530
+ end
531
+
532
+ # Send a notification to the client.
533
+ #
534
+ # @param text [String]
535
+ # @param type [Integer] A MessageType constant
536
+ # @return [void]
537
+ def show_message text, type = LanguageServer::MessageTypes::INFO
538
+ send_notification 'window/showMessage', {
539
+ type: type,
540
+ message: text
541
+ }
542
+ end
543
+
544
+ # Send a notification with optional responses.
545
+ #
546
+ # @param text [String]
547
+ # @param type [Integer] A MessageType constant
548
+ # @param actions [Array<String>] Response options for the client
549
+ # @param &block The block that processes the response
550
+ # @yieldparam [String] The action received from the client
551
+ # @return [void]
552
+ def show_message_request text, type, actions, &block
553
+ send_request 'window/showMessageRequest', {
554
+ type: type,
555
+ message: text,
556
+ actions: actions
557
+ }, &block
558
+ end
559
+
560
+ # Get a list of IDs for server requests that are waiting for responses
561
+ # from the client.
562
+ #
563
+ # @return [Array<Integer>]
564
+ def pending_requests
565
+ requests.keys
566
+ end
567
+
568
+ # @return [Hash{String => Object}]
569
+ def default_configuration
570
+ {
571
+ 'completion' => true,
572
+ 'hover' => true,
573
+ 'symbols' => true,
574
+ 'definitions' => true,
575
+ 'rename' => true,
576
+ 'references' => true,
577
+ 'autoformat' => false,
578
+ 'diagnostics' => false,
579
+ 'formatting' => false,
580
+ 'folding' => true,
581
+ 'logLevel' => 'warn'
582
+ }
583
+ end
584
+
585
+ # @param uri [String]
586
+ # @return [Array<Range>]
587
+ def folding_ranges uri
588
+ sources.find(uri).folding_ranges
589
+ end
590
+
591
+ def catalog
592
+ libraries.each(&:catalog)
593
+ end
594
+
595
+ private
596
+
597
+ # @return [Diagnoser]
598
+ def diagnoser
599
+ @diagnoser ||= Diagnoser.new(self)
600
+ end
601
+
602
+ # @return [Cataloger]
603
+ def cataloger
604
+ @cataloger ||= Cataloger.new(self)
605
+ end
606
+
607
+ def requests
608
+ @requests ||= {}
609
+ end
610
+
611
+ def normalize_separators path
612
+ return path if File::ALT_SEPARATOR.nil?
613
+ path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
614
+ end
615
+
616
+ def generate_updater params
617
+ changes = []
618
+ params['contentChanges'].each do |chng|
619
+ changes.push Solargraph::Source::Change.new(
620
+ (chng['range'].nil? ?
621
+ nil :
622
+ Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], chng['range']['end']['line'], chng['range']['end']['character'])
623
+ ),
624
+ chng['text']
625
+ )
626
+ end
627
+ Solargraph::Source::Updater.new(
628
+ uri_to_file(params['textDocument']['uri']),
629
+ params['textDocument']['version'],
630
+ changes
631
+ )
632
+ end
633
+
634
+ def dynamic_capability_options
635
+ @dynamic_capability_options ||= {
636
+ # textDocumentSync: 2, # @todo What should this be?
637
+ 'textDocument/completion' => {
638
+ resolveProvider: true,
639
+ triggerCharacters: ['.', ':', '@']
640
+ },
641
+ # hoverProvider: true,
642
+ # definitionProvider: true,
643
+ 'textDocument/signatureHelp' => {
644
+ triggerCharacters: ['(', ',']
645
+ },
646
+ # documentFormattingProvider: true,
647
+ 'textDocument/onTypeFormatting' => {
648
+ firstTriggerCharacter: '{',
649
+ moreTriggerCharacter: ['(']
650
+ },
651
+ # documentSymbolProvider: true,
652
+ # workspaceSymbolProvider: true,
653
+ # workspace: {
654
+ # workspaceFolders: {
655
+ # supported: true,
656
+ # changeNotifications: true
657
+ # }
658
+ # }
659
+ 'textDocument/definition' => {
660
+ definitionProvider: true
661
+ },
662
+ 'textDocument/references' => {
663
+ referencesProvider: true
664
+ },
665
+ 'textDocument/rename' => {
666
+ renameProvider: {prepareProvider: true}
667
+ },
668
+ 'textDocument/documentSymbol' => {
669
+ documentSymbolProvider: true
670
+ },
671
+ 'workspace/symbol' => {
672
+ workspaceSymbolProvider: true
673
+ },
674
+ 'textDocument/formatting' => {
675
+ formattingProvider: true
676
+ },
677
+ 'textDocument/foldingRange' => {
678
+ foldingRangeProvider: true
679
+ }
680
+ }
681
+ end
682
+ end
683
+ end
684
+ end