solargraph 0.32.1 → 0.32.2

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 (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