solargraph 0.32.1 → 0.32.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (177) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +9 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.travis.yml +25 -0
  6. data/EXAMPLES.md +76 -0
  7. data/Gemfile +3 -0
  8. data/LANGUAGE_SERVER.md +51 -0
  9. data/LICENSE +21 -0
  10. data/OVERVIEW.md +37 -0
  11. data/README.md +106 -0
  12. data/Rakefile +14 -0
  13. data/SERVER.md +95 -0
  14. data/bin/solargraph +0 -0
  15. data/bin/solargraph-runtime +5 -5
  16. data/lib/solargraph.rb +54 -54
  17. data/lib/solargraph/api_map.rb +659 -659
  18. data/lib/solargraph/api_map/cache.rb +49 -49
  19. data/lib/solargraph/api_map/source_to_yard.rb +67 -67
  20. data/lib/solargraph/api_map/store.rb +201 -201
  21. data/lib/solargraph/bundle.rb +24 -24
  22. data/lib/solargraph/complex_type.rb +150 -150
  23. data/lib/solargraph/complex_type/type_methods.rb +124 -124
  24. data/lib/solargraph/complex_type/unique_type.rb +44 -44
  25. data/lib/solargraph/core_fills.rb +37 -37
  26. data/lib/solargraph/diagnostics.rb +52 -52
  27. data/lib/solargraph/diagnostics/base.rb +20 -20
  28. data/lib/solargraph/diagnostics/require_not_found.rb +28 -28
  29. data/lib/solargraph/diagnostics/rubocop.rb +98 -98
  30. data/lib/solargraph/diagnostics/rubocop_helpers.rb +46 -46
  31. data/lib/solargraph/diagnostics/type_not_defined.rb +108 -108
  32. data/lib/solargraph/diagnostics/update_errors.rb +38 -38
  33. data/lib/solargraph/language_server/completion_item_kinds.rb +33 -33
  34. data/lib/solargraph/language_server/error_codes.rb +18 -18
  35. data/lib/solargraph/language_server/host.rb +684 -681
  36. data/lib/solargraph/language_server/host/cataloger.rb +54 -79
  37. data/lib/solargraph/language_server/host/diagnoser.rb +80 -80
  38. data/lib/solargraph/language_server/host/dispatch.rb +112 -113
  39. data/lib/solargraph/language_server/host/sources.rb +138 -138
  40. data/lib/solargraph/language_server/message.rb +90 -90
  41. data/lib/solargraph/language_server/message/base.rb +83 -83
  42. data/lib/solargraph/language_server/message/completion_item/resolve.rb +40 -40
  43. data/lib/solargraph/language_server/message/exit_notification.rb +11 -11
  44. data/lib/solargraph/language_server/message/extended.rb +19 -19
  45. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +86 -86
  46. data/lib/solargraph/language_server/message/extended/document.rb +18 -18
  47. data/lib/solargraph/language_server/message/extended/document_gems.rb +30 -30
  48. data/lib/solargraph/language_server/message/extended/environment.rb +20 -20
  49. data/lib/solargraph/language_server/message/extended/search.rb +18 -18
  50. data/lib/solargraph/language_server/message/initialize.rb +141 -141
  51. data/lib/solargraph/language_server/message/initialized.rb +23 -23
  52. data/lib/solargraph/language_server/message/shutdown.rb +11 -11
  53. data/lib/solargraph/language_server/message/text_document.rb +25 -25
  54. data/lib/solargraph/language_server/message/text_document/completion.rb +51 -51
  55. data/lib/solargraph/language_server/message/text_document/definition.rb +18 -18
  56. data/lib/solargraph/language_server/message/text_document/did_change.rb +13 -13
  57. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +21 -21
  58. data/lib/solargraph/language_server/message/text_document/folding_range.rb +24 -24
  59. data/lib/solargraph/language_server/message/text_document/formatting.rb +50 -50
  60. data/lib/solargraph/language_server/message/text_document/hover.rb +31 -31
  61. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +32 -32
  62. data/lib/solargraph/language_server/message/text_document/prepare_rename.rb +9 -9
  63. data/lib/solargraph/language_server/message/text_document/references.rb +14 -14
  64. data/lib/solargraph/language_server/message/text_document/rename.rb +17 -17
  65. data/lib/solargraph/language_server/message/text_document/signature_help.rb +19 -19
  66. data/lib/solargraph/language_server/message/workspace.rb +12 -12
  67. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +29 -29
  68. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +29 -27
  69. data/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +24 -24
  70. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +21 -21
  71. data/lib/solargraph/language_server/request.rb +22 -22
  72. data/lib/solargraph/language_server/symbol_kinds.rb +34 -34
  73. data/lib/solargraph/language_server/transport.rb +11 -11
  74. data/lib/solargraph/language_server/transport/adapter.rb +60 -60
  75. data/lib/solargraph/language_server/transport/data_reader.rb +66 -66
  76. data/lib/solargraph/language_server/uri_helpers.rb +25 -25
  77. data/lib/solargraph/library.rb +421 -419
  78. data/lib/solargraph/live_map.rb +126 -126
  79. data/lib/solargraph/live_map/cache.rb +38 -38
  80. data/lib/solargraph/location.rb +31 -31
  81. data/lib/solargraph/logging.rb +25 -25
  82. data/lib/solargraph/page.rb +68 -68
  83. data/lib/solargraph/pin.rb +50 -50
  84. data/lib/solargraph/pin/attribute.rb +41 -41
  85. data/lib/solargraph/pin/base.rb +280 -280
  86. data/lib/solargraph/pin/base_method.rb +76 -76
  87. data/lib/solargraph/pin/base_variable.rb +72 -72
  88. data/lib/solargraph/pin/block.rb +32 -32
  89. data/lib/solargraph/pin/block_parameter.rb +103 -103
  90. data/lib/solargraph/pin/class_variable.rb +9 -9
  91. data/lib/solargraph/pin/constant.rb +30 -30
  92. data/lib/solargraph/pin/conversions.rb +79 -79
  93. data/lib/solargraph/pin/documenting.rb +41 -41
  94. data/lib/solargraph/pin/duck_method.rb +14 -14
  95. data/lib/solargraph/pin/global_variable.rb +9 -9
  96. data/lib/solargraph/pin/instance_variable.rb +9 -9
  97. data/lib/solargraph/pin/keyword.rb +17 -17
  98. data/lib/solargraph/pin/local_variable.rb +23 -23
  99. data/lib/solargraph/pin/localized.rb +22 -22
  100. data/lib/solargraph/pin/method.rb +126 -126
  101. data/lib/solargraph/pin/method_alias.rb +30 -30
  102. data/lib/solargraph/pin/method_parameter.rb +40 -40
  103. data/lib/solargraph/pin/namespace.rb +54 -54
  104. data/lib/solargraph/pin/plugin/method.rb +25 -25
  105. data/lib/solargraph/pin/proxy_type.rb +35 -35
  106. data/lib/solargraph/pin/reference.rb +22 -22
  107. data/lib/solargraph/pin/reference/extend.rb +11 -11
  108. data/lib/solargraph/pin/reference/include.rb +11 -11
  109. data/lib/solargraph/pin/reference/require.rb +15 -15
  110. data/lib/solargraph/pin/reference/superclass.rb +11 -11
  111. data/lib/solargraph/pin/symbol.rb +44 -44
  112. data/lib/solargraph/pin/yard_pin.rb +10 -10
  113. data/lib/solargraph/pin/yard_pin/constant.rb +14 -14
  114. data/lib/solargraph/pin/yard_pin/method.rb +35 -35
  115. data/lib/solargraph/pin/yard_pin/namespace.rb +19 -19
  116. data/lib/solargraph/pin/yard_pin/yard_mixin.rb +14 -14
  117. data/lib/solargraph/plugin.rb +8 -8
  118. data/lib/solargraph/plugin/base.rb +41 -41
  119. data/lib/solargraph/plugin/canceler.rb +11 -11
  120. data/lib/solargraph/plugin/process.rb +172 -172
  121. data/lib/solargraph/plugin/runtime.rb +134 -134
  122. data/lib/solargraph/position.rb +110 -110
  123. data/lib/solargraph/range.rb +83 -83
  124. data/lib/solargraph/server_methods.rb +14 -14
  125. data/lib/solargraph/shell.rb +102 -102
  126. data/lib/solargraph/source.rb +521 -521
  127. data/lib/solargraph/source/chain.rb +120 -120
  128. data/lib/solargraph/source/chain/call.rb +107 -107
  129. data/lib/solargraph/source/chain/class_variable.rb +11 -11
  130. data/lib/solargraph/source/chain/constant.rb +30 -30
  131. data/lib/solargraph/source/chain/global_variable.rb +11 -11
  132. data/lib/solargraph/source/chain/head.rb +33 -33
  133. data/lib/solargraph/source/chain/instance_variable.rb +11 -11
  134. data/lib/solargraph/source/chain/link.rb +33 -33
  135. data/lib/solargraph/source/chain/literal.rb +21 -21
  136. data/lib/solargraph/source/chain/variable.rb +11 -11
  137. data/lib/solargraph/source/change.rb +77 -77
  138. data/lib/solargraph/source/cursor.rb +157 -157
  139. data/lib/solargraph/source/node_chainer.rb +96 -96
  140. data/lib/solargraph/source/node_methods.rb +225 -225
  141. data/lib/solargraph/source/source_chainer.rb +183 -183
  142. data/lib/solargraph/source_map.rb +169 -169
  143. data/lib/solargraph/source_map/clip.rb +145 -145
  144. data/lib/solargraph/source_map/completion.rb +21 -21
  145. data/lib/solargraph/source_map/mapper.rb +149 -149
  146. data/lib/solargraph/source_map/node_processor.rb +78 -78
  147. data/lib/solargraph/source_map/node_processor/alias_node.rb +19 -19
  148. data/lib/solargraph/source_map/node_processor/args_node.rb +28 -28
  149. data/lib/solargraph/source_map/node_processor/base.rb +68 -68
  150. data/lib/solargraph/source_map/node_processor/begin_node.rb +11 -11
  151. data/lib/solargraph/source_map/node_processor/block_node.rb +14 -14
  152. data/lib/solargraph/source_map/node_processor/casgn_node.rb +14 -14
  153. data/lib/solargraph/source_map/node_processor/cvasgn_node.rb +14 -14
  154. data/lib/solargraph/source_map/node_processor/def_node.rb +54 -54
  155. data/lib/solargraph/source_map/node_processor/defs_node.rb +21 -21
  156. data/lib/solargraph/source_map/node_processor/gvasgn_node.rb +12 -12
  157. data/lib/solargraph/source_map/node_processor/ivasgn_node.rb +18 -18
  158. data/lib/solargraph/source_map/node_processor/lvasgn_node.rb +16 -16
  159. data/lib/solargraph/source_map/node_processor/namespace_node.rb +26 -26
  160. data/lib/solargraph/source_map/node_processor/orasgn_node.rb +12 -12
  161. data/lib/solargraph/source_map/node_processor/sclass_node.rb +11 -11
  162. data/lib/solargraph/source_map/node_processor/send_node.rb +162 -162
  163. data/lib/solargraph/source_map/node_processor/sym_node.rb +11 -11
  164. data/lib/solargraph/source_map/region.rb +58 -58
  165. data/lib/solargraph/version.rb +3 -3
  166. data/lib/solargraph/views/environment.erb +53 -53
  167. data/lib/solargraph/workspace.rb +183 -183
  168. data/lib/solargraph/workspace/config.rb +170 -170
  169. data/lib/solargraph/yard_map.rb +298 -298
  170. data/lib/solargraph/yard_map/cache.rb +17 -17
  171. data/lib/solargraph/yard_map/core_docs.rb +163 -163
  172. data/lib/solargraph/yard_map/core_gen.rb +76 -76
  173. data/lib/yard-coregen.rb +16 -16
  174. data/lib/yard-solargraph.rb +18 -18
  175. data/solargraph.gemspec +37 -0
  176. data/travis-bundler.rb +10 -0
  177. metadata +19 -6
@@ -1,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