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,419 +1,421 @@
1
- module Solargraph
2
- # A Library handles coordination between a Workspace and an ApiMap.
3
- #
4
- class Library
5
- include Logging
6
-
7
- # @return [Solargraph::Workspace]
8
- attr_reader :workspace
9
-
10
- # @return [String, nil]
11
- attr_reader :name
12
-
13
- # @param workspace [Solargraph::Workspace]
14
- # @param name [String, nil]
15
- def initialize workspace = Solargraph::Workspace.new, name = nil
16
- @workspace = workspace
17
- @name = name
18
- api_map.catalog bundle
19
- @synchronized = true
20
- @catalog_mutex = Mutex.new
21
- end
22
-
23
- def inspect
24
- # Let's not deal with insane data dumps in spec failures
25
- to_s
26
- end
27
-
28
- # True if the ApiMap is up to date with the library's workspace and open
29
- # files.
30
- #
31
- # @return [Boolean]
32
- def synchronized?
33
- @synchronized
34
- end
35
-
36
- # Open a file from disk and try to merge it into the workspace.
37
- #
38
- # @param filename [String]
39
- # @return [Boolean] True if the file was merged into the source.
40
- def open_from_disk filename
41
- source = Solargraph::Source.load(filename)
42
- merge source
43
- end
44
-
45
- # Attach a source to the library.
46
- #
47
- # The attached source does not need to be a part of the workspace. The
48
- # library will include it in the ApiMap while it's attached. Only one
49
- # source can be attached to the library at a time.
50
- #
51
- # @param source [Source, nil]
52
- # @return [void]
53
- def attach source
54
- mutex.synchronize do
55
- @synchronized = (@current == source) if synchronized?
56
- @current = source
57
- end
58
- end
59
-
60
- # True if the specified file is currently attached.
61
- #
62
- # @param filename [String]
63
- # @return [Boolean]
64
- def attached? filename
65
- !@current.nil? && @current.filename == filename
66
- end
67
- alias open? attached?
68
-
69
- # Detach the specified file if it is currently attached to the library.
70
- #
71
- # @param filename [String]
72
- # @return [Boolean] True if the specified file was detached
73
- def detach filename
74
- return false if @current.nil? || @current.filename != filename
75
- attach nil
76
- true
77
- end
78
-
79
- # True if the specified file is included in the workspace (but not
80
- # necessarily open).
81
- #
82
- # @param filename [String]
83
- # @return [Boolean]
84
- def contain? filename
85
- workspace.has_file?(filename)
86
- end
87
-
88
- # Create a source to be added to the workspace. The file is ignored if it is
89
- # neither open in the library nor included in the workspace.
90
- #
91
- # @param filename [String]
92
- # @param text [String] The contents of the file
93
- # @return [Boolean] True if the file was added to the workspace.
94
- def create filename, text
95
- result = false
96
- mutex.synchronize do
97
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
98
- @synchronized = false
99
- source = Solargraph::Source.load_string(text, filename)
100
- workspace.merge(source)
101
- result = true
102
- end
103
- result
104
- end
105
-
106
- # Create a file source from a file on disk. The file is ignored if it is
107
- # neither open in the library nor included in the workspace.
108
- #
109
- # @param filename [String]
110
- # @return [Boolean] True if the file was added to the workspace.
111
- def create_from_disk filename
112
- result = false
113
- mutex.synchronize do
114
- next if File.directory?(filename) || !File.exist?(filename)
115
- next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
116
- @synchronized = false
117
- source = Solargraph::Source.load_string(File.read(filename), filename)
118
- workspace.merge(source)
119
- result = true
120
- end
121
- result
122
- end
123
-
124
- # Delete a file from the library. Deleting a file will make it unavailable
125
- # for checkout and optionally remove it from the workspace unless the
126
- # workspace configuration determines that it should still exist.
127
- #
128
- # @param filename [String]
129
- # @return [void]
130
- def delete filename
131
- detach filename
132
- mutex.synchronize do
133
- result = workspace.remove(filename)
134
- @synchronized = !result if synchronized?
135
- end
136
- end
137
-
138
- # Close a file in the library. Closing a file will make it unavailable for
139
- # checkout although it may still exist in the workspace.
140
- #
141
- # @param filename [String]
142
- # @return [void]
143
- def close filename
144
- mutex.synchronize do
145
- @synchronized = false
146
- @current = nil if @current && @current.filename == filename
147
- catalog
148
- end
149
- end
150
-
151
- # Get completion suggestions at the specified file and location.
152
- #
153
- # @param filename [String] The file to analyze
154
- # @param line [Integer] The zero-based line number
155
- # @param column [Integer] The zero-based column number
156
- # @return [SourceMap::Completion]
157
- # @todo Take a Location instead of filename/line/column
158
- def completions_at filename, line, column
159
- position = Position.new(line, column)
160
- cursor = Source::Cursor.new(checkout(filename), position)
161
- api_map.clip(cursor).complete
162
- end
163
-
164
- # Get definition suggestions for the expression at the specified file and
165
- # location.
166
- #
167
- # @param filename [String] The file to analyze
168
- # @param line [Integer] The zero-based line number
169
- # @param column [Integer] The zero-based column number
170
- # @return [Array<Solargraph::Pin::Base>]
171
- # @todo Take filename/position instead of filename/line/column
172
- def definitions_at filename, line, column
173
- position = Position.new(line, column)
174
- cursor = Source::Cursor.new(checkout(filename), position)
175
- api_map.clip(cursor).define
176
- end
177
-
178
- # Get signature suggestions for the method at the specified file and
179
- # location.
180
- #
181
- # @param filename [String] The file to analyze
182
- # @param line [Integer] The zero-based line number
183
- # @param column [Integer] The zero-based column number
184
- # @return [Array<Solargraph::Pin::Base>]
185
- # @todo Take filename/position instead of filename/line/column
186
- def signatures_at filename, line, column
187
- position = Position.new(line, column)
188
- cursor = Source::Cursor.new(checkout(filename), position)
189
- api_map.clip(cursor).signify
190
- end
191
-
192
- # @param filename [String]
193
- # @param line [Integer]
194
- # @param column [Integer]
195
- # @param strip [Boolean] Strip special characters from variable names
196
- # @return [Array<Solargraph::Range>]
197
- # @todo Take a Location instead of filename/line/column
198
- def references_from filename, line, column, strip: false
199
- checkout filename
200
- cursor = api_map.cursor_at(filename, Position.new(line, column))
201
- clip = api_map.clip(cursor)
202
- pins = clip.define
203
- return [] if pins.empty?
204
- result = []
205
- pins.uniq.each do |pin|
206
- (workspace.sources + (@current ? [@current] : [])).uniq(&:filename).each do |source|
207
- found = source.references(pin.name)
208
- found.select! do |loc|
209
- referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
210
- # HACK: The additional location comparison is necessary because
211
- # Clip#define can return proxies for parameter pins
212
- referenced.any?{|r| r == pin || r.location == pin.location}
213
- end
214
- # HACK: for language clients that exclude special characters from the start of variable names
215
- if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
216
- found.map! do |loc|
217
- Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
218
- end
219
- end
220
- result.concat(found.sort do |a, b|
221
- a.range.start.line <=> b.range.start.line
222
- end)
223
- end
224
- end
225
- result.uniq
226
- end
227
-
228
- # Get the pin at the specified location or nil if the pin does not exist.
229
- #
230
- # @param location [Location]
231
- # @return [Solargraph::Pin::Base]
232
- def locate_pins location
233
- api_map.locate_pins location
234
- end
235
-
236
- # Get an array of pins that match a path.
237
- #
238
- # @param path [String]
239
- # @return [Array<Solargraph::Pin::Base>]
240
- def get_path_pins path
241
- api_map.get_path_suggestions(path)
242
- end
243
-
244
- # Check a file out of the library. If the file is not part of the
245
- # workspace, the ApiMap will virtualize it for mapping purposes. If
246
- # filename is nil, any source currently checked out of the library
247
- # will be removed from the ApiMap. Only one file can be checked out
248
- # (virtualized) at a time.
249
- #
250
- # @raise [FileNotFoundError] if the file does not exist.
251
- #
252
- # @param filename [String]
253
- # @return [Source]
254
- def checkout filename
255
- checked = read(filename)
256
- @synchronized = (checked == @current) if synchronized?
257
- @current = checked
258
- # Cataloging is necessary to avoid FileNotFoundErrors when the file is
259
- # not in the workspace. Otherwise it should be safe to defer
260
- # synchronization.
261
- catalog unless workspace.has_file?(filename)
262
- @current
263
- end
264
-
265
- # @param query [String]
266
- # @return [Array<YARD::CodeObject::Base>]
267
- def document query
268
- catalog
269
- api_map.document query
270
- end
271
-
272
- # @param query [String]
273
- # @return [Array<String>]
274
- def search query
275
- catalog
276
- api_map.search query
277
- end
278
-
279
- # Get an array of all symbols in the workspace that match the query.
280
- #
281
- # @param query [String]
282
- # @return [Array<Pin::Base>]
283
- def query_symbols query
284
- catalog
285
- api_map.query_symbols query
286
- end
287
-
288
- # Get an array of document symbols.
289
- #
290
- # Document symbols are composed of namespace, method, and constant pins.
291
- # The results of this query are appropriate for building the response to a
292
- # textDocument/documentSymbol message in the language server protocol.
293
- #
294
- # @param filename [String]
295
- # @return [Array<Solargraph::Pin::Base>]
296
- def document_symbols filename
297
- checkout filename
298
- api_map.document_symbols(filename)
299
- end
300
-
301
- # @param path [String]
302
- # @return [Array<Solargraph::Pin::Base>]
303
- def path_pins path
304
- catalog
305
- api_map.get_path_suggestions(path)
306
- end
307
-
308
- # Get the current text of a file in the library.
309
- #
310
- # @param filename [String]
311
- # @return [String]
312
- def read_text filename
313
- source = read(filename)
314
- source.code
315
- end
316
-
317
- # Get diagnostics about a file.
318
- #
319
- # @param filename [String]
320
- # @return [Array<Hash>]
321
- def diagnose filename
322
- # @todo Only open files get diagnosed. Determine whether anything or
323
- # everything in the workspace should get diagnosed, or if there should
324
- # be an option to do so.
325
- #
326
- return [] unless open?(filename)
327
- catalog
328
- result = []
329
- source = read(filename)
330
- workspace.config.reporters.each do |name|
331
- reporter = Diagnostics.reporter(name)
332
- raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
333
- result.concat reporter.new.diagnose(source, api_map)
334
- end
335
- result
336
- end
337
-
338
- # Update the ApiMap from the library's workspace and open files.
339
- #
340
- # @return [void]
341
- def catalog
342
- @catalog_mutex.synchronize do
343
- break if synchronized?
344
- logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
345
- api_map.catalog bundle
346
- @synchronized = true
347
- logger.info "Catalog complete (#{api_map.pins.length} pins)"
348
- end
349
- end
350
-
351
- # Get an array of foldable ranges for the specified file.
352
- #
353
- # @deprecated The library should not need to handle folding ranges. The
354
- # source itself has all the information it needs.
355
- #
356
- # @param filename [String]
357
- # @return [Array<Range>]
358
- def folding_ranges filename
359
- read(filename).folding_ranges
360
- end
361
-
362
- # Create a library from a directory.
363
- #
364
- # @param directory [String] The path to be used for the workspace
365
- # @param name [String, nil]
366
- # @return [Solargraph::Library]
367
- def self.load directory = '', name = nil
368
- Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
369
- end
370
-
371
- # Try to merge a source into the library's workspace. If the workspace is
372
- # not configured to include the source, it gets ignored.
373
- #
374
- # @param source [Source]
375
- # @return [Boolean] True if the source was merged into the workspace.
376
- def merge source
377
- result = nil
378
- mutex.synchronize do
379
- result = workspace.merge(source)
380
- @synchronized = !result if synchronized?
381
- end
382
- result
383
- end
384
-
385
- private
386
-
387
- # @return [Mutex]
388
- def mutex
389
- @mutex ||= Mutex.new
390
- end
391
-
392
- # @return [ApiMap]
393
- def api_map
394
- @api_map ||= Solargraph::ApiMap.new
395
- end
396
-
397
- # @return [Bundle]
398
- def bundle
399
- Bundle.new(
400
- workspace: workspace,
401
- opened: @current ? [@current] : []
402
- )
403
- end
404
-
405
- # Get the source for an open file or create a new source if the file
406
- # exists on disk. Sources created from disk are not added to the open
407
- # workspace files, i.e., the version on disk remains the authoritative
408
- # version.
409
- #
410
- # @raise [FileNotFoundError] if the file does not exist
411
- # @param filename [String]
412
- # @return [Solargraph::Source]
413
- def read filename
414
- return @current if @current && @current.filename == filename
415
- raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
416
- workspace.source(filename)
417
- end
418
- end
419
- end
1
+ module Solargraph
2
+ # A Library handles coordination between a Workspace and an ApiMap.
3
+ #
4
+ class Library
5
+ include Logging
6
+
7
+ # @return [Solargraph::Workspace]
8
+ attr_reader :workspace
9
+
10
+ # @return [String, nil]
11
+ attr_reader :name
12
+
13
+ # @param workspace [Solargraph::Workspace]
14
+ # @param name [String, nil]
15
+ def initialize workspace = Solargraph::Workspace.new, name = nil
16
+ @workspace = workspace
17
+ @name = name
18
+ api_map.catalog bundle
19
+ @synchronized = true
20
+ @catalog_mutex = Mutex.new
21
+ end
22
+
23
+ def inspect
24
+ # Let's not deal with insane data dumps in spec failures
25
+ to_s
26
+ end
27
+
28
+ # True if the ApiMap is up to date with the library's workspace and open
29
+ # files.
30
+ #
31
+ # @return [Boolean]
32
+ def synchronized?
33
+ @synchronized
34
+ end
35
+
36
+ # Open a file from disk and try to merge it into the workspace.
37
+ #
38
+ # @param filename [String]
39
+ # @return [Boolean] True if the file was merged into the source.
40
+ def open_from_disk filename
41
+ source = Solargraph::Source.load(filename)
42
+ merge source
43
+ end
44
+
45
+ # Attach a source to the library.
46
+ #
47
+ # The attached source does not need to be a part of the workspace. The
48
+ # library will include it in the ApiMap while it's attached. Only one
49
+ # source can be attached to the library at a time.
50
+ #
51
+ # @param source [Source, nil]
52
+ # @return [void]
53
+ def attach source
54
+ mutex.synchronize do
55
+ @synchronized = (@current == source) if synchronized?
56
+ @current = source
57
+ end
58
+ end
59
+
60
+ # True if the specified file is currently attached.
61
+ #
62
+ # @param filename [String]
63
+ # @return [Boolean]
64
+ def attached? filename
65
+ !@current.nil? && @current.filename == filename
66
+ end
67
+ alias open? attached?
68
+
69
+ # Detach the specified file if it is currently attached to the library.
70
+ #
71
+ # @param filename [String]
72
+ # @return [Boolean] True if the specified file was detached
73
+ def detach filename
74
+ return false if @current.nil? || @current.filename != filename
75
+ attach nil
76
+ true
77
+ end
78
+
79
+ # True if the specified file is included in the workspace (but not
80
+ # necessarily open).
81
+ #
82
+ # @param filename [String]
83
+ # @return [Boolean]
84
+ def contain? filename
85
+ workspace.has_file?(filename)
86
+ end
87
+
88
+ # Create a source to be added to the workspace. The file is ignored if it is
89
+ # neither open in the library nor included in the workspace.
90
+ #
91
+ # @param filename [String]
92
+ # @param text [String] The contents of the file
93
+ # @return [Boolean] True if the file was added to the workspace.
94
+ def create filename, text
95
+ result = false
96
+ mutex.synchronize do
97
+ next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
98
+ @synchronized = false
99
+ source = Solargraph::Source.load_string(text, filename)
100
+ workspace.merge(source)
101
+ result = true
102
+ end
103
+ result
104
+ end
105
+
106
+ # Create a file source from a file on disk. The file is ignored if it is
107
+ # neither open in the library nor included in the workspace.
108
+ #
109
+ # @param filename [String]
110
+ # @return [Boolean] True if the file was added to the workspace.
111
+ def create_from_disk filename
112
+ result = false
113
+ mutex.synchronize do
114
+ next if File.directory?(filename) || !File.exist?(filename)
115
+ next unless contain?(filename) || open?(filename) || workspace.would_merge?(filename)
116
+ @synchronized = false
117
+ source = Solargraph::Source.load_string(File.read(filename), filename)
118
+ workspace.merge(source)
119
+ result = true
120
+ end
121
+ result
122
+ end
123
+
124
+ # Delete a file from the library. Deleting a file will make it unavailable
125
+ # for checkout and optionally remove it from the workspace unless the
126
+ # workspace configuration determines that it should still exist.
127
+ #
128
+ # @param filename [String]
129
+ # @return [Boolean] True if the file was deleted
130
+ def delete filename
131
+ detach filename
132
+ result = false
133
+ mutex.synchronize do
134
+ result = workspace.remove(filename)
135
+ @synchronized = !result if synchronized?
136
+ end
137
+ result
138
+ end
139
+
140
+ # Close a file in the library. Closing a file will make it unavailable for
141
+ # checkout although it may still exist in the workspace.
142
+ #
143
+ # @param filename [String]
144
+ # @return [void]
145
+ def close filename
146
+ mutex.synchronize do
147
+ @synchronized = false
148
+ @current = nil if @current && @current.filename == filename
149
+ catalog
150
+ end
151
+ end
152
+
153
+ # Get completion suggestions at the specified file and location.
154
+ #
155
+ # @param filename [String] The file to analyze
156
+ # @param line [Integer] The zero-based line number
157
+ # @param column [Integer] The zero-based column number
158
+ # @return [SourceMap::Completion]
159
+ # @todo Take a Location instead of filename/line/column
160
+ def completions_at filename, line, column
161
+ position = Position.new(line, column)
162
+ cursor = Source::Cursor.new(checkout(filename), position)
163
+ api_map.clip(cursor).complete
164
+ end
165
+
166
+ # Get definition suggestions for the expression at the specified file and
167
+ # location.
168
+ #
169
+ # @param filename [String] The file to analyze
170
+ # @param line [Integer] The zero-based line number
171
+ # @param column [Integer] The zero-based column number
172
+ # @return [Array<Solargraph::Pin::Base>]
173
+ # @todo Take filename/position instead of filename/line/column
174
+ def definitions_at filename, line, column
175
+ position = Position.new(line, column)
176
+ cursor = Source::Cursor.new(checkout(filename), position)
177
+ api_map.clip(cursor).define
178
+ end
179
+
180
+ # Get signature suggestions for the method at the specified file and
181
+ # location.
182
+ #
183
+ # @param filename [String] The file to analyze
184
+ # @param line [Integer] The zero-based line number
185
+ # @param column [Integer] The zero-based column number
186
+ # @return [Array<Solargraph::Pin::Base>]
187
+ # @todo Take filename/position instead of filename/line/column
188
+ def signatures_at filename, line, column
189
+ position = Position.new(line, column)
190
+ cursor = Source::Cursor.new(checkout(filename), position)
191
+ api_map.clip(cursor).signify
192
+ end
193
+
194
+ # @param filename [String]
195
+ # @param line [Integer]
196
+ # @param column [Integer]
197
+ # @param strip [Boolean] Strip special characters from variable names
198
+ # @return [Array<Solargraph::Range>]
199
+ # @todo Take a Location instead of filename/line/column
200
+ def references_from filename, line, column, strip: false
201
+ checkout filename
202
+ cursor = api_map.cursor_at(filename, Position.new(line, column))
203
+ clip = api_map.clip(cursor)
204
+ pins = clip.define
205
+ return [] if pins.empty?
206
+ result = []
207
+ pins.uniq.each do |pin|
208
+ (workspace.sources + (@current ? [@current] : [])).uniq(&:filename).each do |source|
209
+ found = source.references(pin.name)
210
+ found.select! do |loc|
211
+ referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
212
+ # HACK: The additional location comparison is necessary because
213
+ # Clip#define can return proxies for parameter pins
214
+ referenced.any?{|r| r == pin || r.location == pin.location}
215
+ end
216
+ # HACK: for language clients that exclude special characters from the start of variable names
217
+ if strip && match = cursor.word.match(/^[^a-z0-9_]+/i)
218
+ found.map! do |loc|
219
+ Solargraph::Location.new(loc.filename, Solargraph::Range.from_to(loc.range.start.line, loc.range.start.column + match[0].length, loc.range.ending.line, loc.range.ending.column))
220
+ end
221
+ end
222
+ result.concat(found.sort do |a, b|
223
+ a.range.start.line <=> b.range.start.line
224
+ end)
225
+ end
226
+ end
227
+ result.uniq
228
+ end
229
+
230
+ # Get the pin at the specified location or nil if the pin does not exist.
231
+ #
232
+ # @param location [Location]
233
+ # @return [Solargraph::Pin::Base]
234
+ def locate_pins location
235
+ api_map.locate_pins location
236
+ end
237
+
238
+ # Get an array of pins that match a path.
239
+ #
240
+ # @param path [String]
241
+ # @return [Array<Solargraph::Pin::Base>]
242
+ def get_path_pins path
243
+ api_map.get_path_suggestions(path)
244
+ end
245
+
246
+ # Check a file out of the library. If the file is not part of the
247
+ # workspace, the ApiMap will virtualize it for mapping purposes. If
248
+ # filename is nil, any source currently checked out of the library
249
+ # will be removed from the ApiMap. Only one file can be checked out
250
+ # (virtualized) at a time.
251
+ #
252
+ # @raise [FileNotFoundError] if the file does not exist.
253
+ #
254
+ # @param filename [String]
255
+ # @return [Source]
256
+ def checkout filename
257
+ checked = read(filename)
258
+ @synchronized = (checked == @current) if synchronized?
259
+ @current = checked
260
+ # Cataloging is necessary to avoid FileNotFoundErrors when the file is
261
+ # not in the workspace. Otherwise it should be safe to defer
262
+ # synchronization.
263
+ catalog unless workspace.has_file?(filename)
264
+ @current
265
+ end
266
+
267
+ # @param query [String]
268
+ # @return [Array<YARD::CodeObject::Base>]
269
+ def document query
270
+ catalog
271
+ api_map.document query
272
+ end
273
+
274
+ # @param query [String]
275
+ # @return [Array<String>]
276
+ def search query
277
+ catalog
278
+ api_map.search query
279
+ end
280
+
281
+ # Get an array of all symbols in the workspace that match the query.
282
+ #
283
+ # @param query [String]
284
+ # @return [Array<Pin::Base>]
285
+ def query_symbols query
286
+ catalog
287
+ api_map.query_symbols query
288
+ end
289
+
290
+ # Get an array of document symbols.
291
+ #
292
+ # Document symbols are composed of namespace, method, and constant pins.
293
+ # The results of this query are appropriate for building the response to a
294
+ # textDocument/documentSymbol message in the language server protocol.
295
+ #
296
+ # @param filename [String]
297
+ # @return [Array<Solargraph::Pin::Base>]
298
+ def document_symbols filename
299
+ checkout filename
300
+ api_map.document_symbols(filename)
301
+ end
302
+
303
+ # @param path [String]
304
+ # @return [Array<Solargraph::Pin::Base>]
305
+ def path_pins path
306
+ catalog
307
+ api_map.get_path_suggestions(path)
308
+ end
309
+
310
+ # Get the current text of a file in the library.
311
+ #
312
+ # @param filename [String]
313
+ # @return [String]
314
+ def read_text filename
315
+ source = read(filename)
316
+ source.code
317
+ end
318
+
319
+ # Get diagnostics about a file.
320
+ #
321
+ # @param filename [String]
322
+ # @return [Array<Hash>]
323
+ def diagnose filename
324
+ # @todo Only open files get diagnosed. Determine whether anything or
325
+ # everything in the workspace should get diagnosed, or if there should
326
+ # be an option to do so.
327
+ #
328
+ return [] unless open?(filename)
329
+ catalog
330
+ result = []
331
+ source = read(filename)
332
+ workspace.config.reporters.each do |name|
333
+ reporter = Diagnostics.reporter(name)
334
+ raise DiagnosticsError, "Diagnostics reporter #{name} does not exist" if reporter.nil?
335
+ result.concat reporter.new.diagnose(source, api_map)
336
+ end
337
+ result
338
+ end
339
+
340
+ # Update the ApiMap from the library's workspace and open files.
341
+ #
342
+ # @return [void]
343
+ def catalog
344
+ @catalog_mutex.synchronize do
345
+ break if synchronized?
346
+ logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
347
+ api_map.catalog bundle
348
+ @synchronized = true
349
+ logger.info "Catalog complete (#{api_map.pins.length} pins)"
350
+ end
351
+ end
352
+
353
+ # Get an array of foldable ranges for the specified file.
354
+ #
355
+ # @deprecated The library should not need to handle folding ranges. The
356
+ # source itself has all the information it needs.
357
+ #
358
+ # @param filename [String]
359
+ # @return [Array<Range>]
360
+ def folding_ranges filename
361
+ read(filename).folding_ranges
362
+ end
363
+
364
+ # Create a library from a directory.
365
+ #
366
+ # @param directory [String] The path to be used for the workspace
367
+ # @param name [String, nil]
368
+ # @return [Solargraph::Library]
369
+ def self.load directory = '', name = nil
370
+ Solargraph::Library.new(Solargraph::Workspace.new(directory), name)
371
+ end
372
+
373
+ # Try to merge a source into the library's workspace. If the workspace is
374
+ # not configured to include the source, it gets ignored.
375
+ #
376
+ # @param source [Source]
377
+ # @return [Boolean] True if the source was merged into the workspace.
378
+ def merge source
379
+ result = nil
380
+ mutex.synchronize do
381
+ result = workspace.merge(source)
382
+ @synchronized = !result if synchronized?
383
+ end
384
+ result
385
+ end
386
+
387
+ private
388
+
389
+ # @return [Mutex]
390
+ def mutex
391
+ @mutex ||= Mutex.new
392
+ end
393
+
394
+ # @return [ApiMap]
395
+ def api_map
396
+ @api_map ||= Solargraph::ApiMap.new
397
+ end
398
+
399
+ # @return [Bundle]
400
+ def bundle
401
+ Bundle.new(
402
+ workspace: workspace,
403
+ opened: @current ? [@current] : []
404
+ )
405
+ end
406
+
407
+ # Get the source for an open file or create a new source if the file
408
+ # exists on disk. Sources created from disk are not added to the open
409
+ # workspace files, i.e., the version on disk remains the authoritative
410
+ # version.
411
+ #
412
+ # @raise [FileNotFoundError] if the file does not exist
413
+ # @param filename [String]
414
+ # @return [Solargraph::Source]
415
+ def read filename
416
+ return @current if @current && @current.filename == filename
417
+ raise FileNotFoundError, "File not found: #{filename}" unless workspace.has_file?(filename)
418
+ workspace.source(filename)
419
+ end
420
+ end
421
+ end