cosmicgraph 0.49.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.github/FUNDING.yml +1 -0
  3. data/.github/workflows/rspec.yml +41 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +2 -0
  6. data/.yardopts +2 -0
  7. data/CHANGELOG.md +1150 -0
  8. data/Gemfile +7 -0
  9. data/LICENSE +21 -0
  10. data/README.md +136 -0
  11. data/Rakefile +25 -0
  12. data/SPONSORS.md +15 -0
  13. data/bin/solargraph +5 -0
  14. data/cosmicgraph.gemspec +44 -0
  15. data/lib/.rubocop.yml +22 -0
  16. data/lib/solargraph/api_map/bundler_methods.rb +22 -0
  17. data/lib/solargraph/api_map/cache.rb +70 -0
  18. data/lib/solargraph/api_map/source_to_yard.rb +81 -0
  19. data/lib/solargraph/api_map/store.rb +268 -0
  20. data/lib/solargraph/api_map.rb +704 -0
  21. data/lib/solargraph/bench.rb +27 -0
  22. data/lib/solargraph/cache.rb +51 -0
  23. data/lib/solargraph/complex_type/type_methods.rb +134 -0
  24. data/lib/solargraph/complex_type/unique_type.rb +132 -0
  25. data/lib/solargraph/complex_type.rb +254 -0
  26. data/lib/solargraph/convention/base.rb +33 -0
  27. data/lib/solargraph/convention/gemfile.rb +15 -0
  28. data/lib/solargraph/convention/gemspec.rb +22 -0
  29. data/lib/solargraph/convention/rakefile.rb +17 -0
  30. data/lib/solargraph/convention/rspec.rb +30 -0
  31. data/lib/solargraph/convention.rb +49 -0
  32. data/lib/solargraph/converters/dd.rb +12 -0
  33. data/lib/solargraph/converters/dl.rb +12 -0
  34. data/lib/solargraph/converters/dt.rb +12 -0
  35. data/lib/solargraph/converters/misc.rb +1 -0
  36. data/lib/solargraph/diagnostics/base.rb +29 -0
  37. data/lib/solargraph/diagnostics/require_not_found.rb +53 -0
  38. data/lib/solargraph/diagnostics/rubocop.rb +112 -0
  39. data/lib/solargraph/diagnostics/rubocop_helpers.rb +65 -0
  40. data/lib/solargraph/diagnostics/severities.rb +15 -0
  41. data/lib/solargraph/diagnostics/type_check.rb +54 -0
  42. data/lib/solargraph/diagnostics/update_errors.rb +41 -0
  43. data/lib/solargraph/diagnostics.rb +55 -0
  44. data/lib/solargraph/documentor.rb +76 -0
  45. data/lib/solargraph/environ.rb +45 -0
  46. data/lib/solargraph/language_server/completion_item_kinds.rb +35 -0
  47. data/lib/solargraph/language_server/error_codes.rb +20 -0
  48. data/lib/solargraph/language_server/host/cataloger.rb +56 -0
  49. data/lib/solargraph/language_server/host/diagnoser.rb +89 -0
  50. data/lib/solargraph/language_server/host/dispatch.rb +111 -0
  51. data/lib/solargraph/language_server/host/message_worker.rb +59 -0
  52. data/lib/solargraph/language_server/host/sources.rb +156 -0
  53. data/lib/solargraph/language_server/host.rb +869 -0
  54. data/lib/solargraph/language_server/message/base.rb +89 -0
  55. data/lib/solargraph/language_server/message/cancel_request.rb +13 -0
  56. data/lib/solargraph/language_server/message/client/register_capability.rb +15 -0
  57. data/lib/solargraph/language_server/message/client.rb +11 -0
  58. data/lib/solargraph/language_server/message/completion_item/resolve.rb +58 -0
  59. data/lib/solargraph/language_server/message/completion_item.rb +11 -0
  60. data/lib/solargraph/language_server/message/exit_notification.rb +13 -0
  61. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +100 -0
  62. data/lib/solargraph/language_server/message/extended/document.rb +20 -0
  63. data/lib/solargraph/language_server/message/extended/document_gems.rb +32 -0
  64. data/lib/solargraph/language_server/message/extended/download_core.rb +19 -0
  65. data/lib/solargraph/language_server/message/extended/environment.rb +25 -0
  66. data/lib/solargraph/language_server/message/extended/search.rb +20 -0
  67. data/lib/solargraph/language_server/message/extended.rb +21 -0
  68. data/lib/solargraph/language_server/message/initialize.rb +164 -0
  69. data/lib/solargraph/language_server/message/initialized.rb +27 -0
  70. data/lib/solargraph/language_server/message/method_not_found.rb +16 -0
  71. data/lib/solargraph/language_server/message/method_not_implemented.rb +14 -0
  72. data/lib/solargraph/language_server/message/shutdown.rb +13 -0
  73. data/lib/solargraph/language_server/message/text_document/base.rb +19 -0
  74. data/lib/solargraph/language_server/message/text_document/code_action.rb +17 -0
  75. data/lib/solargraph/language_server/message/text_document/completion.rb +59 -0
  76. data/lib/solargraph/language_server/message/text_document/definition.rb +38 -0
  77. data/lib/solargraph/language_server/message/text_document/did_change.rb +15 -0
  78. data/lib/solargraph/language_server/message/text_document/did_close.rb +15 -0
  79. data/lib/solargraph/language_server/message/text_document/did_open.rb +15 -0
  80. data/lib/solargraph/language_server/message/text_document/did_save.rb +17 -0
  81. data/lib/solargraph/language_server/message/text_document/document_highlight.rb +16 -0
  82. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +23 -0
  83. data/lib/solargraph/language_server/message/text_document/folding_range.rb +26 -0
  84. data/lib/solargraph/language_server/message/text_document/formatting.rb +126 -0
  85. data/lib/solargraph/language_server/message/text_document/hover.rb +56 -0
  86. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +34 -0
  87. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +11 -0
  88. data/lib/solargraph/language_server/message/text_document/references.rb +16 -0
  89. data/lib/solargraph/language_server/message/text_document/rename.rb +19 -0
  90. data/lib/solargraph/language_server/message/text_document/signature_help.rb +24 -0
  91. data/lib/solargraph/language_server/message/text_document.rb +28 -0
  92. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +30 -0
  93. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +40 -0
  94. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -0
  95. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +23 -0
  96. data/lib/solargraph/language_server/message/workspace.rb +14 -0
  97. data/lib/solargraph/language_server/message.rb +93 -0
  98. data/lib/solargraph/language_server/message_types.rb +14 -0
  99. data/lib/solargraph/language_server/request.rb +24 -0
  100. data/lib/solargraph/language_server/symbol_kinds.rb +36 -0
  101. data/lib/solargraph/language_server/transport/adapter.rb +53 -0
  102. data/lib/solargraph/language_server/transport/data_reader.rb +72 -0
  103. data/lib/solargraph/language_server/transport.rb +13 -0
  104. data/lib/solargraph/language_server/uri_helpers.rb +49 -0
  105. data/lib/solargraph/language_server.rb +19 -0
  106. data/lib/solargraph/library.rb +547 -0
  107. data/lib/solargraph/location.rb +37 -0
  108. data/lib/solargraph/logging.rb +27 -0
  109. data/lib/solargraph/page.rb +83 -0
  110. data/lib/solargraph/parser/comment_ripper.rb +52 -0
  111. data/lib/solargraph/parser/legacy/class_methods.rb +135 -0
  112. data/lib/solargraph/parser/legacy/flawed_builder.rb +16 -0
  113. data/lib/solargraph/parser/legacy/node_chainer.rb +148 -0
  114. data/lib/solargraph/parser/legacy/node_methods.rb +325 -0
  115. data/lib/solargraph/parser/legacy/node_processors/alias_node.rb +23 -0
  116. data/lib/solargraph/parser/legacy/node_processors/args_node.rb +35 -0
  117. data/lib/solargraph/parser/legacy/node_processors/begin_node.rb +15 -0
  118. data/lib/solargraph/parser/legacy/node_processors/block_node.rb +42 -0
  119. data/lib/solargraph/parser/legacy/node_processors/casgn_node.rb +35 -0
  120. data/lib/solargraph/parser/legacy/node_processors/cvasgn_node.rb +23 -0
  121. data/lib/solargraph/parser/legacy/node_processors/def_node.rb +63 -0
  122. data/lib/solargraph/parser/legacy/node_processors/defs_node.rb +36 -0
  123. data/lib/solargraph/parser/legacy/node_processors/gvasgn_node.rb +23 -0
  124. data/lib/solargraph/parser/legacy/node_processors/ivasgn_node.rb +38 -0
  125. data/lib/solargraph/parser/legacy/node_processors/lvasgn_node.rb +28 -0
  126. data/lib/solargraph/parser/legacy/node_processors/namespace_node.rb +39 -0
  127. data/lib/solargraph/parser/legacy/node_processors/orasgn_node.rb +16 -0
  128. data/lib/solargraph/parser/legacy/node_processors/resbody_node.rb +36 -0
  129. data/lib/solargraph/parser/legacy/node_processors/sclass_node.rb +42 -0
  130. data/lib/solargraph/parser/legacy/node_processors/send_node.rb +257 -0
  131. data/lib/solargraph/parser/legacy/node_processors/sym_node.rb +18 -0
  132. data/lib/solargraph/parser/legacy/node_processors.rb +54 -0
  133. data/lib/solargraph/parser/legacy.rb +12 -0
  134. data/lib/solargraph/parser/node_methods.rb +43 -0
  135. data/lib/solargraph/parser/node_processor/base.rb +77 -0
  136. data/lib/solargraph/parser/node_processor.rb +43 -0
  137. data/lib/solargraph/parser/region.rb +66 -0
  138. data/lib/solargraph/parser/rubyvm/class_methods.rb +149 -0
  139. data/lib/solargraph/parser/rubyvm/node_chainer.rb +160 -0
  140. data/lib/solargraph/parser/rubyvm/node_methods.rb +315 -0
  141. data/lib/solargraph/parser/rubyvm/node_processors/alias_node.rb +23 -0
  142. data/lib/solargraph/parser/rubyvm/node_processors/args_node.rb +85 -0
  143. data/lib/solargraph/parser/rubyvm/node_processors/begin_node.rb +15 -0
  144. data/lib/solargraph/parser/rubyvm/node_processors/block_node.rb +42 -0
  145. data/lib/solargraph/parser/rubyvm/node_processors/casgn_node.rb +33 -0
  146. data/lib/solargraph/parser/rubyvm/node_processors/cvasgn_node.rb +23 -0
  147. data/lib/solargraph/parser/rubyvm/node_processors/def_node.rb +75 -0
  148. data/lib/solargraph/parser/rubyvm/node_processors/defs_node.rb +68 -0
  149. data/lib/solargraph/parser/rubyvm/node_processors/gvasgn_node.rb +23 -0
  150. data/lib/solargraph/parser/rubyvm/node_processors/ivasgn_node.rb +38 -0
  151. data/lib/solargraph/parser/rubyvm/node_processors/kw_arg_node.rb +39 -0
  152. data/lib/solargraph/parser/rubyvm/node_processors/lit_node.rb +20 -0
  153. data/lib/solargraph/parser/rubyvm/node_processors/lvasgn_node.rb +27 -0
  154. data/lib/solargraph/parser/rubyvm/node_processors/namespace_node.rb +39 -0
  155. data/lib/solargraph/parser/rubyvm/node_processors/opt_arg_node.rb +26 -0
  156. data/lib/solargraph/parser/rubyvm/node_processors/orasgn_node.rb +15 -0
  157. data/lib/solargraph/parser/rubyvm/node_processors/resbody_node.rb +45 -0
  158. data/lib/solargraph/parser/rubyvm/node_processors/sclass_node.rb +32 -0
  159. data/lib/solargraph/parser/rubyvm/node_processors/scope_node.rb +15 -0
  160. data/lib/solargraph/parser/rubyvm/node_processors/send_node.rb +279 -0
  161. data/lib/solargraph/parser/rubyvm/node_processors/sym_node.rb +18 -0
  162. data/lib/solargraph/parser/rubyvm/node_processors.rb +63 -0
  163. data/lib/solargraph/parser/rubyvm/node_wrapper.rb +47 -0
  164. data/lib/solargraph/parser/rubyvm.rb +40 -0
  165. data/lib/solargraph/parser/snippet.rb +13 -0
  166. data/lib/solargraph/parser.rb +26 -0
  167. data/lib/solargraph/pin/base.rb +299 -0
  168. data/lib/solargraph/pin/base_variable.rb +84 -0
  169. data/lib/solargraph/pin/block.rb +73 -0
  170. data/lib/solargraph/pin/class_variable.rb +8 -0
  171. data/lib/solargraph/pin/closure.rb +37 -0
  172. data/lib/solargraph/pin/common.rb +70 -0
  173. data/lib/solargraph/pin/constant.rb +43 -0
  174. data/lib/solargraph/pin/conversions.rb +92 -0
  175. data/lib/solargraph/pin/documenting.rb +105 -0
  176. data/lib/solargraph/pin/duck_method.rb +16 -0
  177. data/lib/solargraph/pin/global_variable.rb +8 -0
  178. data/lib/solargraph/pin/instance_variable.rb +30 -0
  179. data/lib/solargraph/pin/keyword.rb +15 -0
  180. data/lib/solargraph/pin/keyword_param.rb +8 -0
  181. data/lib/solargraph/pin/local_variable.rb +55 -0
  182. data/lib/solargraph/pin/method.rb +335 -0
  183. data/lib/solargraph/pin/method_alias.rb +31 -0
  184. data/lib/solargraph/pin/namespace.rb +94 -0
  185. data/lib/solargraph/pin/parameter.rb +206 -0
  186. data/lib/solargraph/pin/proxy_type.rb +29 -0
  187. data/lib/solargraph/pin/reference/extend.rb +10 -0
  188. data/lib/solargraph/pin/reference/include.rb +10 -0
  189. data/lib/solargraph/pin/reference/override.rb +29 -0
  190. data/lib/solargraph/pin/reference/prepend.rb +10 -0
  191. data/lib/solargraph/pin/reference/require.rb +14 -0
  192. data/lib/solargraph/pin/reference/superclass.rb +10 -0
  193. data/lib/solargraph/pin/reference.rb +14 -0
  194. data/lib/solargraph/pin/search.rb +56 -0
  195. data/lib/solargraph/pin/signature.rb +23 -0
  196. data/lib/solargraph/pin/singleton.rb +11 -0
  197. data/lib/solargraph/pin/symbol.rb +47 -0
  198. data/lib/solargraph/pin.rb +38 -0
  199. data/lib/solargraph/position.rb +100 -0
  200. data/lib/solargraph/range.rb +95 -0
  201. data/lib/solargraph/rbs_map/conversions.rb +394 -0
  202. data/lib/solargraph/rbs_map/core_fills.rb +61 -0
  203. data/lib/solargraph/rbs_map/core_map.rb +38 -0
  204. data/lib/solargraph/rbs_map/core_signs.rb +33 -0
  205. data/lib/solargraph/rbs_map/stdlib_map.rb +36 -0
  206. data/lib/solargraph/rbs_map.rb +73 -0
  207. data/lib/solargraph/server_methods.rb +16 -0
  208. data/lib/solargraph/shell.rb +234 -0
  209. data/lib/solargraph/source/chain/block_variable.rb +13 -0
  210. data/lib/solargraph/source/chain/call.rb +215 -0
  211. data/lib/solargraph/source/chain/class_variable.rb +13 -0
  212. data/lib/solargraph/source/chain/constant.rb +75 -0
  213. data/lib/solargraph/source/chain/global_variable.rb +13 -0
  214. data/lib/solargraph/source/chain/hash.rb +28 -0
  215. data/lib/solargraph/source/chain/head.rb +19 -0
  216. data/lib/solargraph/source/chain/instance_variable.rb +13 -0
  217. data/lib/solargraph/source/chain/link.rb +71 -0
  218. data/lib/solargraph/source/chain/literal.rb +23 -0
  219. data/lib/solargraph/source/chain/or.rb +23 -0
  220. data/lib/solargraph/source/chain/q_call.rb +11 -0
  221. data/lib/solargraph/source/chain/variable.rb +13 -0
  222. data/lib/solargraph/source/chain/z_super.rb +30 -0
  223. data/lib/solargraph/source/chain.rb +179 -0
  224. data/lib/solargraph/source/change.rb +79 -0
  225. data/lib/solargraph/source/cursor.rb +164 -0
  226. data/lib/solargraph/source/encoding_fixes.rb +23 -0
  227. data/lib/solargraph/source/source_chainer.rb +191 -0
  228. data/lib/solargraph/source/updater.rb +54 -0
  229. data/lib/solargraph/source.rb +522 -0
  230. data/lib/solargraph/source_map/clip.rb +229 -0
  231. data/lib/solargraph/source_map/completion.rb +23 -0
  232. data/lib/solargraph/source_map/mapper.rb +241 -0
  233. data/lib/solargraph/source_map.rb +180 -0
  234. data/lib/solargraph/type_checker/checks.rb +112 -0
  235. data/lib/solargraph/type_checker/param_def.rb +35 -0
  236. data/lib/solargraph/type_checker/problem.rb +32 -0
  237. data/lib/solargraph/type_checker/rules.rb +57 -0
  238. data/lib/solargraph/type_checker.rb +549 -0
  239. data/lib/solargraph/version.rb +5 -0
  240. data/lib/solargraph/views/_method.erb +62 -0
  241. data/lib/solargraph/views/_name_type_tag.erb +10 -0
  242. data/lib/solargraph/views/_namespace.erb +24 -0
  243. data/lib/solargraph/views/document.erb +23 -0
  244. data/lib/solargraph/views/environment.erb +58 -0
  245. data/lib/solargraph/views/layout.erb +44 -0
  246. data/lib/solargraph/views/search.erb +11 -0
  247. data/lib/solargraph/workspace/config.rb +231 -0
  248. data/lib/solargraph/workspace.rb +212 -0
  249. data/lib/solargraph/yard_map/cache.rb +19 -0
  250. data/lib/solargraph/yard_map/helpers.rb +16 -0
  251. data/lib/solargraph/yard_map/mapper/to_constant.rb +25 -0
  252. data/lib/solargraph/yard_map/mapper/to_method.rb +81 -0
  253. data/lib/solargraph/yard_map/mapper/to_namespace.rb +27 -0
  254. data/lib/solargraph/yard_map/mapper.rb +77 -0
  255. data/lib/solargraph/yard_map/to_method.rb +79 -0
  256. data/lib/solargraph/yard_map.rb +301 -0
  257. data/lib/solargraph.rb +69 -0
  258. data/lib/yard-solargraph.rb +33 -0
  259. metadata +587 -0
@@ -0,0 +1,869 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diff/lcs'
4
+ require 'observer'
5
+ require 'securerandom'
6
+ require 'set'
7
+
8
+ module Solargraph
9
+ module LanguageServer
10
+ # The language server protocol's data provider. Hosts are responsible for
11
+ # querying the library and processing messages. They also provide thread
12
+ # safety for multi-threaded transports.
13
+ #
14
+ class Host
15
+ autoload :Diagnoser, 'solargraph/language_server/host/diagnoser'
16
+ autoload :Cataloger, 'solargraph/language_server/host/cataloger'
17
+ autoload :Sources, 'solargraph/language_server/host/sources'
18
+ autoload :Dispatch, 'solargraph/language_server/host/dispatch'
19
+ autoload :MessageWorker, 'solargraph/language_server/host/message_worker'
20
+
21
+ include UriHelpers
22
+ include Logging
23
+ include Dispatch
24
+ include Observable
25
+
26
+ attr_writer :client_capabilities
27
+
28
+ def initialize
29
+ @cancel_semaphore = Mutex.new
30
+ @buffer_semaphore = Mutex.new
31
+ @request_mutex = Mutex.new
32
+ @cancel = []
33
+ @buffer = String.new
34
+ @stopped = true
35
+ @next_request_id = 1
36
+ @dynamic_capabilities = Set.new
37
+ @registered_capabilities = Set.new
38
+ end
39
+
40
+ # Start asynchronous process handling.
41
+ #
42
+ # @return [void]
43
+ def start
44
+ return unless stopped?
45
+ @stopped = false
46
+ diagnoser.start
47
+ cataloger.start
48
+ sources.start
49
+ message_worker.start
50
+ end
51
+
52
+ # Update the configuration options with the provided hash.
53
+ #
54
+ # @param update [Hash]
55
+ # @return [void]
56
+ def configure update
57
+ return if update.nil?
58
+ options.merge! update
59
+ logger.level = LOG_LEVELS[options['logLevel']] || DEFAULT_LOG_LEVEL
60
+ end
61
+
62
+ # @return [Hash]
63
+ def options
64
+ @options ||= default_configuration
65
+ end
66
+
67
+ # Cancel the method with the specified ID.
68
+ #
69
+ # @param id [Integer]
70
+ # @return [void]
71
+ def cancel id
72
+ @cancel_semaphore.synchronize { @cancel.push id }
73
+ end
74
+
75
+ # True if the host received a request to cancel the method with the
76
+ # specified ID.
77
+ #
78
+ # @param id [Integer]
79
+ # @return [Boolean]
80
+ def cancel? id
81
+ result = false
82
+ @cancel_semaphore.synchronize { result = @cancel.include? id }
83
+ result
84
+ end
85
+
86
+ # Delete the specified ID from the list of cancelled IDs if it exists.
87
+ #
88
+ # @param id [Integer]
89
+ # @return [void]
90
+ def clear id
91
+ @cancel_semaphore.synchronize { @cancel.delete id }
92
+ end
93
+
94
+ # Called by adapter, to handle the request
95
+ # @param request [Hash]
96
+ # @return [void]
97
+ def process request
98
+ message_worker.queue(request)
99
+ end
100
+
101
+ # Start processing a request from the client. After the message is
102
+ # processed, caller is responsible for sending the response.
103
+ #
104
+ # @param request [Hash] The contents of the message.
105
+ # @return [Solargraph::LanguageServer::Message::Base] The message handler.
106
+ def receive request
107
+ if request['method']
108
+ logger.info "Server received #{request['method']}"
109
+ logger.debug request
110
+ message = Message.select(request['method']).new(self, request)
111
+ begin
112
+ message.process
113
+ rescue StandardError => e
114
+ logger.warn "Error processing request: [#{e.class}] #{e.message}"
115
+ logger.warn e.backtrace.join("\n")
116
+ message.set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, "[#{e.class}] #{e.message}"
117
+ end
118
+ message
119
+ elsif request['id']
120
+ if requests[request['id']]
121
+ requests[request['id']].process(request['result'])
122
+ requests.delete request['id']
123
+ else
124
+ logger.warn "Discarding client response to unrecognized message #{request['id']}"
125
+ nil
126
+ end
127
+ else
128
+ logger.warn "Invalid message received."
129
+ logger.debug request
130
+ end
131
+ end
132
+
133
+ # Respond to a notification that files were created in the workspace.
134
+ # The libraries will determine whether the files should be merged; see
135
+ # Solargraph::Library#create_from_disk.
136
+ #
137
+ # @param uris [Array<String>] The URIs of the files.
138
+ # @return [Boolean] True if at least one library accepted at least one file.
139
+ def create *uris
140
+ filenames = uris.map { |uri| uri_to_file(uri) }
141
+ result = false
142
+ libraries.each do |lib|
143
+ result = true if lib.create_from_disk(*filenames)
144
+ end
145
+ uris.each do |uri|
146
+ diagnoser.schedule uri if open?(uri)
147
+ end
148
+ result
149
+ end
150
+
151
+ # Delete the specified files from the library.
152
+ #
153
+ # @param uris [Array<String>] The file uris.
154
+ # @return [void]
155
+ def delete *uris
156
+ filenames = uris.map { |uri| uri_to_file(uri) }
157
+ libraries.each do |lib|
158
+ lib.delete(*filenames)
159
+ end
160
+ uris.each do |uri|
161
+ send_notification "textDocument/publishDiagnostics", {
162
+ uri: uri,
163
+ diagnostics: []
164
+ }
165
+ end
166
+ end
167
+
168
+ # Open the specified file in the library.
169
+ #
170
+ # @param uri [String] The file uri.
171
+ # @param text [String] The contents of the file.
172
+ # @param version [Integer] A version number.
173
+ # @return [void]
174
+ def open uri, text, version
175
+ src = sources.open(uri, text, version)
176
+ libraries.each do |lib|
177
+ lib.merge src
178
+ end
179
+ diagnoser.schedule uri
180
+ end
181
+
182
+ # @param uri [String]
183
+ # @return [void]
184
+ def open_from_disk uri
185
+ sources.open_from_disk(uri)
186
+ diagnoser.schedule uri
187
+ end
188
+
189
+ # True if the specified file is currently open in the library.
190
+ #
191
+ # @param uri [String]
192
+ # @return [Boolean]
193
+ def open? uri
194
+ sources.include? uri
195
+ end
196
+
197
+ # Close the file specified by the URI.
198
+ #
199
+ # @param uri [String]
200
+ # @return [void]
201
+ def close uri
202
+ logger.info "Closing #{uri}"
203
+ sources.close uri
204
+ diagnoser.schedule uri
205
+ end
206
+
207
+ # @param uri [String]
208
+ # @return [void]
209
+ def diagnose uri
210
+ if sources.include?(uri)
211
+ library = library_for(uri)
212
+ if library.mapped? && library.synchronized?
213
+ logger.info "Diagnosing #{uri}"
214
+ begin
215
+ results = library.diagnose uri_to_file(uri)
216
+ send_notification "textDocument/publishDiagnostics", {
217
+ uri: uri,
218
+ diagnostics: results
219
+ }
220
+ rescue DiagnosticsError => e
221
+ logger.warn "Error in diagnostics: #{e.message}"
222
+ options['diagnostics'] = false
223
+ send_notification 'window/showMessage', {
224
+ type: LanguageServer::MessageTypes::ERROR,
225
+ message: "Error in diagnostics: #{e.message}"
226
+ }
227
+ rescue FileNotFoundError => e
228
+ # @todo This appears to happen when an external file is open and
229
+ # scheduled for diagnosis, but the file was closed (i.e., the
230
+ # editor moved to a different file) before diagnosis started
231
+ logger.warn "Unable to diagnose #{uri} : #{e.message}"
232
+ send_notification 'textDocument/publishDiagnostics', {
233
+ uri: uri,
234
+ diagnostics: []
235
+ }
236
+ end
237
+ else
238
+ logger.info "Deferring diagnosis of #{uri}"
239
+ diagnoser.schedule uri
240
+ end
241
+ else
242
+ send_notification 'textDocument/publishDiagnostics', {
243
+ uri: uri,
244
+ diagnostics: []
245
+ }
246
+ end
247
+ end
248
+
249
+ # Update a document from the parameters of a textDocument/didChange
250
+ # method.
251
+ #
252
+ # @param params [Hash]
253
+ # @return [void]
254
+ def change params
255
+ updater = generate_updater(params)
256
+ sources.async_update params['textDocument']['uri'], updater
257
+ diagnoser.schedule params['textDocument']['uri']
258
+ end
259
+
260
+ # Queue a message to be sent to the client.
261
+ #
262
+ # @param message [String] The message to send.
263
+ # @return [void]
264
+ def queue message
265
+ @buffer_semaphore.synchronize { @buffer += message }
266
+ changed
267
+ notify_observers
268
+ end
269
+
270
+ # Clear the message buffer and return the most recent data.
271
+ #
272
+ # @return [String] The most recent data or an empty string.
273
+ def flush
274
+ tmp = ''
275
+ @buffer_semaphore.synchronize do
276
+ tmp = @buffer.clone
277
+ @buffer.clear
278
+ end
279
+ tmp
280
+ end
281
+
282
+ # Prepare a library for the specified directory.
283
+ #
284
+ # @param directory [String]
285
+ # @param name [String, nil]
286
+ # @return [void]
287
+ def prepare directory, name = nil
288
+ # No need to create a library without a directory. The generic library
289
+ # will handle it.
290
+ return if directory.nil?
291
+ logger.info "Preparing library for #{directory}"
292
+ path = ''
293
+ path = normalize_separators(directory) unless directory.nil?
294
+ begin
295
+ lib = Solargraph::Library.load(path, name)
296
+ libraries.push lib
297
+ async_library_map lib
298
+ rescue WorkspaceTooLargeError => e
299
+ send_notification 'window/showMessage', {
300
+ 'type' => Solargraph::LanguageServer::MessageTypes::WARNING,
301
+ 'message' => e.message
302
+ }
303
+ end
304
+ end
305
+
306
+ # Prepare multiple folders.
307
+ #
308
+ # @param array [Array<Hash{String => String}>]
309
+ # @return [void]
310
+ def prepare_folders array
311
+ return if array.nil?
312
+ array.each do |folder|
313
+ prepare uri_to_file(folder['uri']), folder['name']
314
+ end
315
+ end
316
+
317
+ # Remove a directory.
318
+ #
319
+ # @param directory [String]
320
+ # @return [void]
321
+ def remove directory
322
+ logger.info "Removing library for #{directory}"
323
+ # @param lib [Library]
324
+ libraries.delete_if do |lib|
325
+ next false if lib.workspace.directory != directory
326
+ true
327
+ end
328
+ end
329
+
330
+ # @param array [Array<Hash>]
331
+ # @return [void]
332
+ def remove_folders array
333
+ array.each do |folder|
334
+ remove uri_to_file(folder['uri'])
335
+ end
336
+ end
337
+
338
+ # @return [Array<String>]
339
+ def folders
340
+ libraries.map { |lib| lib.workspace.directory }
341
+ end
342
+
343
+ # Send a notification to the client.
344
+ #
345
+ # @param method [String] The message method
346
+ # @param params [Hash] The method parameters
347
+ # @return [void]
348
+ def send_notification method, params
349
+ response = {
350
+ jsonrpc: "2.0",
351
+ method: method,
352
+ params: params
353
+ }
354
+ json = response.to_json
355
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
356
+ queue envelope
357
+ logger.info "Server sent #{method}"
358
+ logger.debug params
359
+ end
360
+
361
+ # Send a request to the client and execute the provided block to process
362
+ # the response. If an ID is not provided, the host will use an auto-
363
+ # incrementing integer.
364
+ #
365
+ # @param method [String] The message method
366
+ # @param params [Hash] The method parameters
367
+ # @param block [Proc] The block that processes the response
368
+ # @yieldparam [Hash] The result sent by the client
369
+ # @return [void]
370
+ def send_request method, params, &block
371
+ @request_mutex.synchronize do
372
+ message = {
373
+ jsonrpc: "2.0",
374
+ method: method,
375
+ params: params,
376
+ id: @next_request_id
377
+ }
378
+ json = message.to_json
379
+ requests[@next_request_id] = Request.new(@next_request_id, &block)
380
+ envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}"
381
+ queue envelope
382
+ @next_request_id += 1
383
+ logger.info "Server sent #{method}"
384
+ logger.debug params
385
+ end
386
+ end
387
+
388
+ # Register the methods as capabilities with the client.
389
+ # This method will avoid duplicating registrations and ignore methods
390
+ # that were not flagged for dynamic registration by the client.
391
+ #
392
+ # @param methods [Array<String>] The methods to register
393
+ # @return [void]
394
+ def register_capabilities methods
395
+ logger.debug "Registering capabilities: #{methods}"
396
+ registrations = methods.select { |m| can_register?(m) and !registered?(m) }.map do |m|
397
+ @registered_capabilities.add m
398
+ {
399
+ id: m,
400
+ method: m,
401
+ registerOptions: dynamic_capability_options[m]
402
+ }
403
+ end
404
+ return if registrations.empty?
405
+ send_request 'client/registerCapability', { registrations: registrations }
406
+ end
407
+
408
+ # Unregister the methods with the client.
409
+ # This method will avoid duplicating unregistrations and ignore methods
410
+ # that were not flagged for dynamic registration by the client.
411
+ #
412
+ # @param methods [Array<String>] The methods to unregister
413
+ # @return [void]
414
+ def unregister_capabilities methods
415
+ logger.debug "Unregistering capabilities: #{methods}"
416
+ unregisterations = methods.select{|m| registered?(m)}.map{ |m|
417
+ @registered_capabilities.delete m
418
+ {
419
+ id: m,
420
+ method: m
421
+ }
422
+ }
423
+ return if unregisterations.empty?
424
+ send_request 'client/unregisterCapability', { unregisterations: unregisterations }
425
+ end
426
+
427
+ # Flag a method as available for dynamic registration.
428
+ #
429
+ # @param method [String] The method name, e.g., 'textDocument/completion'
430
+ # @return [void]
431
+ def allow_registration method
432
+ @dynamic_capabilities.add method
433
+ end
434
+
435
+ # True if the specified LSP method can be dynamically registered.
436
+ #
437
+ # @param method [String]
438
+ # @return [Boolean]
439
+ def can_register? method
440
+ @dynamic_capabilities.include?(method)
441
+ end
442
+
443
+ # True if the specified method has been registered.
444
+ #
445
+ # @param method [String] The method name, e.g., 'textDocument/completion'
446
+ # @return [Boolean]
447
+ def registered? method
448
+ @registered_capabilities.include?(method)
449
+ end
450
+
451
+ def synchronizing?
452
+ !libraries.all?(&:synchronized?)
453
+ end
454
+
455
+ # @return [void]
456
+ def stop
457
+ return if @stopped
458
+ @stopped = true
459
+ message_worker.stop
460
+ cataloger.stop
461
+ diagnoser.stop
462
+ sources.stop
463
+ changed
464
+ notify_observers
465
+ end
466
+
467
+ def stopped?
468
+ @stopped
469
+ end
470
+
471
+ # Locate multiple pins that match a completion item. The first match is
472
+ # based on the corresponding location in a library source if available.
473
+ # Subsequent matches are based on path.
474
+ #
475
+ # @param params [Hash] A hash representation of a completion item
476
+ # @return [Array<Pin::Base>]
477
+ def locate_pins params
478
+ return [] unless params['data'] && params['data']['uri']
479
+ library = library_for(params['data']['uri'])
480
+ result = []
481
+ if params['data']['location']
482
+ location = Location.new(
483
+ params['data']['location']['filename'],
484
+ Range.from_to(
485
+ params['data']['location']['range']['start']['line'],
486
+ params['data']['location']['range']['start']['character'],
487
+ params['data']['location']['range']['end']['line'],
488
+ params['data']['location']['range']['end']['character']
489
+ )
490
+ )
491
+ result.concat library.locate_pins(location).select{ |pin| pin.name == params['label'] }
492
+ end
493
+ if params['data']['path']
494
+ result.concat library.path_pins(params['data']['path'])
495
+ end
496
+ # Selecting by both location and path can result in duplicate pins
497
+ result.uniq { |p| [p.path, p.location] }
498
+ end
499
+
500
+ # @param uri [String]
501
+ # @return [String]
502
+ def read_text uri
503
+ library = library_for(uri)
504
+ filename = uri_to_file(uri)
505
+ library.read_text(filename)
506
+ end
507
+
508
+ def formatter_config uri
509
+ library = library_for(uri)
510
+ library.workspace.config.formatter
511
+ end
512
+
513
+ # @param uri [String]
514
+ # @param line [Integer]
515
+ # @param column [Integer]
516
+ # @return [Solargraph::SourceMap::Completion]
517
+ def completions_at uri, line, column
518
+ library = library_for(uri)
519
+ library.completions_at uri_to_file(uri), line, column
520
+ end
521
+
522
+ # @return [Bool] if has pending completion request
523
+ def has_pending_completions?
524
+ message_worker.messages.reverse_each.any? { |req| req['method'] == 'textDocument/completion' }
525
+ end
526
+
527
+ # @param uri [String]
528
+ # @param line [Integer]
529
+ # @param column [Integer]
530
+ # @return [Array<Solargraph::Pin::Base>]
531
+ def definitions_at uri, line, column
532
+ library = library_for(uri)
533
+ library.definitions_at(uri_to_file(uri), line, column)
534
+ end
535
+
536
+ # @param uri [String]
537
+ # @param line [Integer]
538
+ # @param column [Integer]
539
+ # @return [Array<Solargraph::Pin::Base>]
540
+ def signatures_at uri, line, column
541
+ library = library_for(uri)
542
+ library.signatures_at(uri_to_file(uri), line, column)
543
+ end
544
+
545
+ # @param uri [String]
546
+ # @param line [Integer]
547
+ # @param column [Integer]
548
+ # @param strip [Boolean] Strip special characters from variable names
549
+ # @param only [Boolean] If true, search current file only
550
+ # @return [Array<Solargraph::Range>]
551
+ def references_from uri, line, column, strip: true, only: false
552
+ library = library_for(uri)
553
+ library.references_from(uri_to_file(uri), line, column, strip: strip, only: only)
554
+ end
555
+
556
+ # @param query [String]
557
+ # @return [Array<Solargraph::Pin::Base>]
558
+ def query_symbols query
559
+ result = []
560
+ (libraries + [generic_library]).each { |lib| result.concat lib.query_symbols(query) }
561
+ result.uniq
562
+ end
563
+
564
+ # @param query [String]
565
+ # @return [Array<String>]
566
+ def search query
567
+ result = []
568
+ libraries.each { |lib| result.concat lib.search(query) }
569
+ result
570
+ end
571
+
572
+ # @param query [String]
573
+ # @return [Array]
574
+ def document query
575
+ result = []
576
+ libraries.each { |lib| result.concat lib.document(query) }
577
+ result
578
+ end
579
+
580
+ # @param uri [String]
581
+ # @return [Array<Solargraph::Pin::Base>]
582
+ def document_symbols uri
583
+ library = library_for(uri)
584
+ # At this level, document symbols should be unique; e.g., a
585
+ # module_function method should return the location for Module.method
586
+ # or Module#method, but not both.
587
+ library.document_symbols(uri_to_file(uri)).uniq(&:location)
588
+ end
589
+
590
+ # Send a notification to the client.
591
+ #
592
+ # @param text [String]
593
+ # @param type [Integer] A MessageType constant
594
+ # @return [void]
595
+ def show_message text, type = LanguageServer::MessageTypes::INFO
596
+ send_notification 'window/showMessage', {
597
+ type: type,
598
+ message: text
599
+ }
600
+ end
601
+
602
+ # Send a notification with optional responses.
603
+ #
604
+ # @param text [String]
605
+ # @param type [Integer] A MessageType constant
606
+ # @param actions [Array<String>] Response options for the client
607
+ # @param block The block that processes the response
608
+ # @yieldparam [String] The action received from the client
609
+ # @return [void]
610
+ def show_message_request text, type, actions, &block
611
+ send_request 'window/showMessageRequest', {
612
+ type: type,
613
+ message: text,
614
+ actions: actions
615
+ }, &block
616
+ end
617
+
618
+ # Get a list of IDs for server requests that are waiting for responses
619
+ # from the client.
620
+ #
621
+ # @return [Array<Integer>]
622
+ def pending_requests
623
+ requests.keys
624
+ end
625
+
626
+ # @return [Hash{String => Object}]
627
+ def default_configuration
628
+ {
629
+ 'completion' => true,
630
+ 'hover' => true,
631
+ 'symbols' => true,
632
+ 'definitions' => true,
633
+ 'rename' => true,
634
+ 'references' => true,
635
+ 'autoformat' => false,
636
+ 'diagnostics' => false,
637
+ 'formatting' => false,
638
+ 'folding' => true,
639
+ 'highlights' => true,
640
+ 'logLevel' => 'warn'
641
+ }
642
+ end
643
+
644
+ # @param uri [String]
645
+ # @return [Array<Range>]
646
+ def folding_ranges uri
647
+ sources.find(uri).folding_ranges
648
+ end
649
+
650
+ # @return [void]
651
+ def catalog
652
+ return unless libraries.all?(&:mapped?)
653
+ libraries.each(&:catalog)
654
+ end
655
+
656
+ def client_capabilities
657
+ @client_capabilities ||= {}
658
+ end
659
+
660
+ private
661
+
662
+ # @return [MessageWorker]
663
+ def message_worker
664
+ @message_worker ||= MessageWorker.new(self)
665
+ end
666
+
667
+ # @return [Diagnoser]
668
+ def diagnoser
669
+ @diagnoser ||= Diagnoser.new(self)
670
+ end
671
+
672
+ # @return [Cataloger]
673
+ def cataloger
674
+ @cataloger ||= Cataloger.new(self)
675
+ end
676
+
677
+ # A hash of client requests by ID. The host uses this to keep track of
678
+ # pending responses.
679
+ #
680
+ # @return [Hash{Integer => Hash}]
681
+ def requests
682
+ @requests ||= {}
683
+ end
684
+
685
+ # @param path [String]
686
+ # @return [String]
687
+ def normalize_separators path
688
+ return path if File::ALT_SEPARATOR.nil?
689
+ path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
690
+ end
691
+
692
+ # @param params [Hash]
693
+ # @return [Source::Updater]
694
+ def generate_updater params
695
+ changes = []
696
+ params['contentChanges'].each do |recvd|
697
+ chng = check_diff(params['textDocument']['uri'], recvd)
698
+ changes.push Solargraph::Source::Change.new(
699
+ (chng['range'].nil? ?
700
+ nil :
701
+ Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], chng['range']['end']['line'], chng['range']['end']['character'])
702
+ ),
703
+ chng['text']
704
+ )
705
+ end
706
+ Solargraph::Source::Updater.new(
707
+ uri_to_file(params['textDocument']['uri']),
708
+ params['textDocument']['version'],
709
+ changes
710
+ )
711
+ end
712
+
713
+ # @param uri [String]
714
+ # @param change [Hash]
715
+ # @return [Hash]
716
+ def check_diff uri, change
717
+ return change if change['range']
718
+ source = sources.find(uri)
719
+ return change if source.code.length + 1 != change['text'].length
720
+ diffs = Diff::LCS.diff(source.code, change['text'])
721
+ return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1
722
+ # @type [Diff::LCS::Change]
723
+ diff = diffs.first.first
724
+ return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element)
725
+ position = Solargraph::Position.from_offset(source.code, diff.position)
726
+ {
727
+ 'range' => {
728
+ 'start' => {
729
+ 'line' => position.line,
730
+ 'character' => position.character
731
+ },
732
+ 'end' => {
733
+ 'line' => position.line,
734
+ 'character' => position.character
735
+ }
736
+ },
737
+ 'text' => diff.element
738
+ }
739
+ rescue Solargraph::FileNotFoundError
740
+ change
741
+ end
742
+
743
+ # @return [Hash]
744
+ def dynamic_capability_options
745
+ @dynamic_capability_options ||= {
746
+ # textDocumentSync: 2, # @todo What should this be?
747
+ 'textDocument/completion' => {
748
+ resolveProvider: true,
749
+ triggerCharacters: ['.', ':', '@']
750
+ },
751
+ # hoverProvider: true,
752
+ # definitionProvider: true,
753
+ 'textDocument/signatureHelp' => {
754
+ triggerCharacters: ['(', ',', ' ']
755
+ },
756
+ # documentFormattingProvider: true,
757
+ 'textDocument/onTypeFormatting' => {
758
+ firstTriggerCharacter: '{',
759
+ moreTriggerCharacter: ['(']
760
+ },
761
+ # documentSymbolProvider: true,
762
+ # workspaceSymbolProvider: true,
763
+ # workspace: {
764
+ # workspaceFolders: {
765
+ # supported: true,
766
+ # changeNotifications: true
767
+ # }
768
+ # }
769
+ 'textDocument/definition' => {
770
+ definitionProvider: true
771
+ },
772
+ 'textDocument/references' => {
773
+ referencesProvider: true
774
+ },
775
+ 'textDocument/rename' => {
776
+ renameProvider: prepare_rename? ? { prepareProvider: true } : true
777
+ },
778
+ 'textDocument/documentSymbol' => {
779
+ documentSymbolProvider: true
780
+ },
781
+ 'workspace/symbol' => {
782
+ workspaceSymbolProvider: true
783
+ },
784
+ 'textDocument/formatting' => {
785
+ formattingProvider: true
786
+ },
787
+ 'textDocument/foldingRange' => {
788
+ foldingRangeProvider: true
789
+ },
790
+ 'textDocument/codeAction' => {
791
+ codeActionProvider: true
792
+ },
793
+ 'textDocument/documentHighlight' => {
794
+ documentHighlightProvider: true
795
+ }
796
+ }
797
+ end
798
+
799
+ def prepare_rename?
800
+ client_capabilities['rename'] && client_capabilities['rename']['prepareSupport']
801
+ end
802
+
803
+ def client_supports_progress?
804
+ client_capabilities['window'] && client_capabilities['window']['workDoneProgress']
805
+ end
806
+
807
+ # @param library [Library]
808
+ # @return [void]
809
+ def async_library_map library
810
+ return if library.mapped?
811
+ Thread.new do
812
+ if client_supports_progress?
813
+ uuid = SecureRandom.uuid
814
+ send_request 'window/workDoneProgress/create', {
815
+ token: uuid
816
+ } do |response|
817
+ do_async_library_map library, response.nil? ? uuid : nil
818
+ end
819
+ else
820
+ do_async_library_map library
821
+ end
822
+ end
823
+ end
824
+
825
+ def do_async_library_map library, uuid = nil
826
+ total = library.workspace.sources.length
827
+ if uuid
828
+ send_notification '$/progress', {
829
+ token: uuid,
830
+ value: {
831
+ kind: 'begin',
832
+ title: "Mapping workspace",
833
+ message: "0/#{total} files",
834
+ cancellable: false,
835
+ percentage: 0
836
+ }
837
+ }
838
+ end
839
+ pct = 0
840
+ mod = 10
841
+ while library.next_map
842
+ next unless uuid
843
+ cur = ((library.source_map_hash.keys.length.to_f / total.to_f) * 100).to_i
844
+ if cur > pct && cur % mod == 0
845
+ pct = cur
846
+ send_notification '$/progress', {
847
+ token: uuid,
848
+ value: {
849
+ kind: 'report',
850
+ cancellable: false,
851
+ message: "#{library.source_map_hash.keys.length}/#{total} files",
852
+ percentage: pct
853
+ }
854
+ }
855
+ end
856
+ end
857
+ if uuid
858
+ send_notification '$/progress', {
859
+ token: uuid,
860
+ value: {
861
+ kind: 'end',
862
+ message: 'Mapping complete'
863
+ }
864
+ }
865
+ end
866
+ end
867
+ end
868
+ end
869
+ end