solargraph 0.23.6 → 0.24.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.
- checksums.yaml +4 -4
- data/lib/solargraph.rb +1 -0
- data/lib/solargraph/api_map.rb +30 -7
- data/lib/solargraph/api_map/probe.rb +15 -2
- data/lib/solargraph/complex_type.rb +90 -0
- data/lib/solargraph/diagnostics.rb +2 -0
- data/lib/solargraph/diagnostics/base.rb +5 -2
- data/lib/solargraph/diagnostics/type_not_defined.rb +110 -0
- data/lib/solargraph/language_server/host.rb +9 -3
- data/lib/solargraph/language_server/message/exit_notification.rb +0 -1
- data/lib/solargraph/language_server/message/shutdown.rb +1 -1
- data/lib/solargraph/library.rb +12 -3
- data/lib/solargraph/pin.rb +1 -0
- data/lib/solargraph/pin/attribute.rb +12 -5
- data/lib/solargraph/pin/base.rb +47 -3
- data/lib/solargraph/pin/base_variable.rb +25 -4
- data/lib/solargraph/pin/block_parameter.rb +20 -11
- data/lib/solargraph/pin/duck_method.rb +15 -0
- data/lib/solargraph/pin/method.rb +16 -26
- data/lib/solargraph/pin/method_parameter.rb +12 -9
- data/lib/solargraph/pin/namespace.rb +2 -2
- data/lib/solargraph/pin/proxy_method.rb +5 -6
- data/lib/solargraph/pin/yard_object.rb +6 -2
- data/lib/solargraph/source.rb +26 -3
- data/lib/solargraph/source/change.rb +6 -0
- data/lib/solargraph/source/fragment.rb +31 -7
- data/lib/solargraph/source/location.rb +7 -0
- data/lib/solargraph/source/mapper.rb +28 -3
- data/lib/solargraph/source/position.rb +5 -0
- data/lib/solargraph/source/range.rb +5 -0
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/yard_map.rb +26 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 102d3ab02b96835a5e3efc7a5ec5f07b2bd53c94660d16905f6c7264e6ac520a
|
4
|
+
data.tar.gz: 29aab3c067c6e9e95eeb5cf108ae5f71560f325aa02612bb69957840706e9d43
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eed6ec2beee0afb8bcb8fc976f1d1487b5d79cd44acc1c27c89751f896da3ede2f8ec33da4d14278ea597e3308c3770cf6952d57dabf19de67d759b0a1464c1a
|
7
|
+
data.tar.gz: c7e7b79cf903a07e1237598a3d0f645aff86602128703af72531ee12d8bb7c1bf85a9445a097aec9f90ac8bd5dab9a9fcb24aa95fc6935250f24d017e8b85872
|
data/lib/solargraph.rb
CHANGED
@@ -36,6 +36,7 @@ module Solargraph
|
|
36
36
|
autoload :Page, 'solargraph/page'
|
37
37
|
autoload :Library, 'solargraph/library'
|
38
38
|
autoload :Diagnostics, 'solargraph/diagnostics'
|
39
|
+
autoload :ComplexType, 'solargraph/complex_type'
|
39
40
|
|
40
41
|
YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
|
41
42
|
YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
|
data/lib/solargraph/api_map.rb
CHANGED
@@ -3,6 +3,9 @@ require 'set'
|
|
3
3
|
require 'time'
|
4
4
|
|
5
5
|
module Solargraph
|
6
|
+
# An aggregate provider for information about workspaces, sources, gems, and
|
7
|
+
# the Ruby core.
|
8
|
+
#
|
6
9
|
class ApiMap
|
7
10
|
autoload :Cache, 'solargraph/api_map/cache'
|
8
11
|
autoload :SourceToYard, 'solargraph/api_map/source_to_yard'
|
@@ -120,13 +123,15 @@ module Solargraph
|
|
120
123
|
virtualize source
|
121
124
|
end
|
122
125
|
|
123
|
-
# Refresh the ApiMap.
|
126
|
+
# Refresh the ApiMap. This method checks for pending changes before
|
127
|
+
# performing the refresh unless the `force` parameter is true.
|
124
128
|
#
|
125
129
|
# @param force [Boolean] Perform a refresh even if the map is not "stale."
|
130
|
+
# @return [Boolean] True if a refresh was performed.
|
126
131
|
def refresh force = false
|
127
|
-
return unless
|
132
|
+
return false unless force or changed?
|
128
133
|
if force
|
129
|
-
|
134
|
+
refresh_store_and_maps
|
130
135
|
else
|
131
136
|
store.remove *(current_workspace_sources.reject{ |s| workspace.sources.include?(s) })
|
132
137
|
@sources = workspace.sources
|
@@ -134,6 +139,7 @@ module Solargraph
|
|
134
139
|
store.update *(@sources.select{ |s| @stime.nil? or s.stime > @stime })
|
135
140
|
end
|
136
141
|
@stime = Time.new
|
142
|
+
true
|
137
143
|
end
|
138
144
|
|
139
145
|
# True if a workspace file has been created, modified, or deleted since
|
@@ -319,10 +325,26 @@ module Solargraph
|
|
319
325
|
elsif fragment.signature.include?('::') and !fragment.signature.include?('.')
|
320
326
|
result.concat get_constants(fragment.base, fragment.namespace)
|
321
327
|
else
|
322
|
-
|
323
|
-
unless
|
324
|
-
|
325
|
-
|
328
|
+
pins = probe.infer_signature_pins(fragment.base, fragment.named_path, fragment.locals)
|
329
|
+
unless pins.empty?
|
330
|
+
pin = pins.first
|
331
|
+
if pin.return_complex_types.any? and pin.return_complex_types.first.duck_type?
|
332
|
+
pin.return_complex_types.each do |t|
|
333
|
+
next unless t.duck_type?
|
334
|
+
result.push Pin::DuckMethod.new(pin.location, t.tag[1..-1])
|
335
|
+
end
|
336
|
+
result.concat(get_methods('Object', scope: :instance))
|
337
|
+
end
|
338
|
+
if result.empty?
|
339
|
+
pins.each do |pin|
|
340
|
+
type = pin.return_type
|
341
|
+
unless type.nil?
|
342
|
+
namespace, scope = extract_namespace_and_scope(type)
|
343
|
+
result.concat get_methods(namespace, scope: scope)
|
344
|
+
break
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
326
348
|
end
|
327
349
|
end
|
328
350
|
end
|
@@ -428,6 +450,7 @@ module Solargraph
|
|
428
450
|
|
429
451
|
# Get an array of all symbols in the workspace that match the query.
|
430
452
|
#
|
453
|
+
# @param query [String]
|
431
454
|
# @return [Array<Pin::Base>]
|
432
455
|
def query_symbols query
|
433
456
|
result = []
|
@@ -167,7 +167,8 @@ module Solargraph
|
|
167
167
|
def virtual_new_pin new_pin, context_pin
|
168
168
|
pin = Pin::Method.new(new_pin.location, new_pin.namespace, new_pin.name, new_pin.docstring, new_pin.scope, new_pin.visibility, new_pin.parameters)
|
169
169
|
# @todo Smelly instance variable access.
|
170
|
-
pin.instance_variable_set(:@return_type, context_pin.path)
|
170
|
+
# pin.instance_variable_set(:@return_type, context_pin.path)
|
171
|
+
pin.instance_variable_set(:@return_complex_types, ComplexType.parse(context_pin.path))
|
171
172
|
pin
|
172
173
|
end
|
173
174
|
|
@@ -175,9 +176,9 @@ module Solargraph
|
|
175
176
|
# @param locals [Array<Solargraph::Pin::Base>]
|
176
177
|
# @return [String]
|
177
178
|
def resolve_pin_type pin, locals
|
178
|
-
pin.return_type
|
179
179
|
return pin.return_type unless pin.return_type.nil?
|
180
180
|
return resolve_block_parameter(pin, locals) if pin.kind == Pin::BLOCK_PARAMETER
|
181
|
+
return resolve_method_parameter(pin) if pin.is_a?(Pin::MethodParameter)
|
181
182
|
return resolve_variable(pin, locals) if pin.variable?
|
182
183
|
nil
|
183
184
|
end
|
@@ -210,6 +211,18 @@ module Solargraph
|
|
210
211
|
nil
|
211
212
|
end
|
212
213
|
|
214
|
+
def resolve_method_parameter pin
|
215
|
+
matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.context.name}
|
216
|
+
matches.each do |m|
|
217
|
+
next unless pin.context.parameters == m.parameters
|
218
|
+
next if m.docstring.nil?
|
219
|
+
tag = m.docstring.tags(:param).select{|t| t.name == pin.name}.first
|
220
|
+
next if tag.nil? or tag.types.nil?
|
221
|
+
return tag.types[0]
|
222
|
+
end
|
223
|
+
nil
|
224
|
+
end
|
225
|
+
|
213
226
|
def resolve_variable(pin, locals)
|
214
227
|
return nil if pin.nil_assignment?
|
215
228
|
# @todo Do we need the locals here?
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Solargraph
|
2
|
+
class ComplexType
|
3
|
+
# @return [String]
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
# @return [String]
|
7
|
+
attr_reader :substring
|
8
|
+
|
9
|
+
# @return [String]
|
10
|
+
attr_reader :tag
|
11
|
+
|
12
|
+
# @return [Array<ComplexType>]
|
13
|
+
attr_reader :subtypes
|
14
|
+
|
15
|
+
# Create a ComplexType with the specified name and an optional substring.
|
16
|
+
# The substring is parameter of a parameterized type, e.g., for the type
|
17
|
+
# `Array<String>`, the name is `Array` and the substring is `String`.
|
18
|
+
#
|
19
|
+
# @param name [String] The name of the type
|
20
|
+
# @param substring [String] The substring of the type
|
21
|
+
def initialize name, substring = ''
|
22
|
+
@name = name
|
23
|
+
@substring = substring
|
24
|
+
@tag = name
|
25
|
+
@tag += "<#{substring}>" unless substring.empty?
|
26
|
+
@subtypes = []
|
27
|
+
@subtypes.concat(ComplexType.parse(substring)) unless substring.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Boolean]
|
31
|
+
def duck_type?
|
32
|
+
@duck_type ||= name.start_with?('#')
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Boolean]
|
36
|
+
def nil_type?
|
37
|
+
@nil_type ||= (name.downcase == 'nil')
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [String]
|
41
|
+
def namespace
|
42
|
+
@namespace ||= 'Object' if duck_type?
|
43
|
+
@namespace ||= 'NilClass' if nil_type?
|
44
|
+
@namespace ||= ((name == 'Class' or name == 'Module') and !subtypes.empty?) ? subtypes.first.name : name
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [Symbol] :class or :instance
|
48
|
+
def scope
|
49
|
+
@scope ||= :instance if duck_type? or nil_type?
|
50
|
+
@scope ||= ((name == 'Class' or name == 'Module') and !subtypes.empty?) ? :class : :instance
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
# @param *strings [Array<String>] The type definitions to parse
|
55
|
+
# @return [Array<ComplexType>]
|
56
|
+
def parse *strings
|
57
|
+
types = []
|
58
|
+
strings.each do |type_string|
|
59
|
+
point_stack = 0
|
60
|
+
base = ''
|
61
|
+
subtype_string = ''
|
62
|
+
type_string.each_char do |char|
|
63
|
+
if char == '<'
|
64
|
+
point_stack += 1
|
65
|
+
next if point_stack == 1
|
66
|
+
elsif char == '>'
|
67
|
+
point_stack -= 1
|
68
|
+
raise 'Invalid close in type' if point_stack < 0
|
69
|
+
elsif char == ',' and point_stack == 0
|
70
|
+
types.push ComplexType.new base.strip, subtype_string.strip
|
71
|
+
base = ''
|
72
|
+
subtype_string = ''
|
73
|
+
next
|
74
|
+
end
|
75
|
+
if point_stack == 0 and char != '>'
|
76
|
+
base += char
|
77
|
+
elsif point_stack != 0
|
78
|
+
subtype_string += char
|
79
|
+
end
|
80
|
+
end
|
81
|
+
base.strip!
|
82
|
+
subtype_string.strip!
|
83
|
+
raise 'Unclosed subtype' if point_stack != 0
|
84
|
+
types.push ComplexType.new base, subtype_string
|
85
|
+
end
|
86
|
+
types
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -7,6 +7,7 @@ module Solargraph
|
|
7
7
|
autoload :Severities, 'solargraph/diagnostics/severities'
|
8
8
|
autoload :Rubocop, 'solargraph/diagnostics/rubocop'
|
9
9
|
autoload :RequireNotFound, 'solargraph/diagnostics/require_not_found'
|
10
|
+
autoload :TypeNotDefined, 'solargraph/diagnostics/type_not_defined'
|
10
11
|
|
11
12
|
class << self
|
12
13
|
# Add a reporter with a name to identify it in .solargraph.yml files.
|
@@ -42,5 +43,6 @@ module Solargraph
|
|
42
43
|
|
43
44
|
register 'rubocop', Rubocop
|
44
45
|
register 'require_not_found', RequireNotFound
|
46
|
+
register 'type_not_defined', TypeNotDefined
|
45
47
|
end
|
46
48
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
module Solargraph
|
2
2
|
module Diagnostics
|
3
3
|
class Base
|
4
|
-
#
|
5
|
-
#
|
4
|
+
# Perform a diagnosis on a Source within the context of an ApiMap.
|
5
|
+
# The result is an array of hash objects that conform to the LSP's
|
6
|
+
# Diagnostic specification.
|
6
7
|
#
|
8
|
+
# @param source [Solargraph::Source]
|
9
|
+
# @param api_map [Solargraph::ApiMap]
|
7
10
|
# @return [Array<Hash>]
|
8
11
|
def diagnose source, api_map
|
9
12
|
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Solargraph
|
2
|
+
module Diagnostics
|
3
|
+
# TypeNotDefined reports methods with undefined return types, untagged
|
4
|
+
# parameters, and invalid param tags.
|
5
|
+
#
|
6
|
+
class TypeNotDefined < Base
|
7
|
+
def diagnose source, api_map
|
8
|
+
result = []
|
9
|
+
source.pins.select{|p| p.kind == Pin::METHOD or p.kind == Pin::ATTRIBUTE}.each do |pin|
|
10
|
+
result.concat check_return_type(pin, api_map, source)
|
11
|
+
result.concat check_param_types(pin, api_map, source)
|
12
|
+
result.concat check_param_tags(pin, api_map, source)
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def check_return_type pin, api_map, source
|
20
|
+
return [] if (pin.name == 'initialize' and pin.scope == :instance) or (pin.name == 'new' and pin.scope == :class)
|
21
|
+
result = []
|
22
|
+
unless defined_return_type?(pin, api_map)
|
23
|
+
result.push(
|
24
|
+
range: extract_first_line(pin, source),
|
25
|
+
severity: Diagnostics::Severities::WARNING,
|
26
|
+
source: 'Solargraph',
|
27
|
+
message: "Method `#{pin.name}` has undefined return type."
|
28
|
+
)
|
29
|
+
end
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def check_param_types pin, api_map, source
|
34
|
+
return [] if pin.name == 'new' and pin.scope == :class
|
35
|
+
result = []
|
36
|
+
pin.parameter_names.each do |par|
|
37
|
+
next if defined_param_type?(pin, par, api_map)
|
38
|
+
result.push(
|
39
|
+
range: extract_first_line(pin, source),
|
40
|
+
severity: Diagnostics::Severities::WARNING,
|
41
|
+
source: 'Solargraph',
|
42
|
+
message: "Method `#{pin.name}` has undefined param `#{par}`."
|
43
|
+
)
|
44
|
+
end
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def check_param_tags pin, api_map, source
|
49
|
+
result = []
|
50
|
+
unless pin.docstring.nil?
|
51
|
+
pin.docstring.tags(:param).each do |par|
|
52
|
+
next if pin.parameter_names.include?(par.name)
|
53
|
+
result.push(
|
54
|
+
range: extract_first_line(pin, source),
|
55
|
+
severity: Diagnostics::Severities::WARNING,
|
56
|
+
source: 'Solargraph',
|
57
|
+
message: "Method `#{pin.name}` has mistagged param `#{par.name}`."
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
result
|
62
|
+
end
|
63
|
+
|
64
|
+
def extract_first_line pin, source
|
65
|
+
{
|
66
|
+
start: {
|
67
|
+
line: pin.location.range.start.line,
|
68
|
+
character: pin.location.range.start.character
|
69
|
+
},
|
70
|
+
end: {
|
71
|
+
line: pin.location.range.start.line,
|
72
|
+
character: last_character(pin.location.range.start, source)
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
def defined_return_type? pin, api_map
|
78
|
+
return true unless pin.return_type.nil?
|
79
|
+
matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.name}
|
80
|
+
matches.shift
|
81
|
+
matches.any?{|m| !m.return_type.nil?}
|
82
|
+
end
|
83
|
+
|
84
|
+
def defined_param_type? pin, param, api_map
|
85
|
+
return true if param_in_docstring?(param, pin.docstring)
|
86
|
+
matches = api_map.get_methods(pin.namespace, scope: pin.scope, visibility: [:public, :private, :protected]).select{|p| p.name == pin.name}
|
87
|
+
matches.shift
|
88
|
+
matches.each do |m|
|
89
|
+
next unless pin.parameter_names == m.parameter_names
|
90
|
+
return true if param_in_docstring?(param, m.docstring)
|
91
|
+
end
|
92
|
+
false
|
93
|
+
end
|
94
|
+
|
95
|
+
def param_in_docstring? param, docstring
|
96
|
+
return false if docstring.nil?
|
97
|
+
tags = docstring.tags(:param)
|
98
|
+
tags.any?{|t| t.name == param}
|
99
|
+
end
|
100
|
+
|
101
|
+
# @param position [Solargraph::Source::Position]
|
102
|
+
# @param source [Solargraph::Source]
|
103
|
+
# @return [Integer]
|
104
|
+
def last_character position, source
|
105
|
+
cursor = Source::Position.to_offset(source.code, position)
|
106
|
+
source.code.index(/[\r\n]/, cursor) || source.code.length
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -477,12 +477,18 @@ module Solargraph
|
|
477
477
|
begin
|
478
478
|
changed = false
|
479
479
|
@change_queue.sort!{|a, b| a['textDocument']['version'] <=> b['textDocument']['version']}
|
480
|
+
pending = {}
|
481
|
+
@change_queue.each do |obj|
|
482
|
+
pending[obj['textDocument']['uri']] ||= 0
|
483
|
+
pending[obj['textDocument']['uri']] += 1
|
484
|
+
end
|
480
485
|
@change_queue.delete_if do |change|
|
481
486
|
filename = uri_to_file(change['textDocument']['uri'])
|
482
487
|
source = library.checkout(filename)
|
483
488
|
if change['textDocument']['version'] == source.version + change['contentChanges'].length
|
489
|
+
pending[change['textDocument']['uri']] -= 1
|
484
490
|
updater = generate_updater(change)
|
485
|
-
library.synchronize updater
|
491
|
+
library.synchronize updater, pending[change['textDocument']['uri']] == 0
|
486
492
|
@diagnostics_queue.push change['textDocument']['uri']
|
487
493
|
changed = true
|
488
494
|
next true
|
@@ -491,8 +497,9 @@ module Solargraph
|
|
491
497
|
# increments the version by one regardless of the number
|
492
498
|
# of changes
|
493
499
|
STDERR.puts "Warning: change applied to #{uri_to_file(change['textDocument']['uri'])} is possibly out of sync"
|
500
|
+
pending[change['textDocument']['uri']] -= 1
|
494
501
|
updater = generate_updater(change)
|
495
|
-
library.synchronize updater
|
502
|
+
library.synchronize updater, pending[change['textDocument']['uri']] == 0
|
496
503
|
@diagnostics_queue.push change['textDocument']['uri']
|
497
504
|
changed = true
|
498
505
|
next true
|
@@ -521,7 +528,6 @@ module Solargraph
|
|
521
528
|
|
522
529
|
def start_diagnostics_thread
|
523
530
|
Thread.new do
|
524
|
-
diagnoser = Diagnostics::Rubocop.new
|
525
531
|
until stopped?
|
526
532
|
sleep 0.1
|
527
533
|
if !options['diagnostics']
|
data/lib/solargraph/library.rb
CHANGED
@@ -2,7 +2,6 @@ module Solargraph
|
|
2
2
|
# A library handles coordination between a Workspace and an ApiMap.
|
3
3
|
#
|
4
4
|
class Library
|
5
|
-
|
6
5
|
# @param workspace [Solargraph::Workspace]
|
7
6
|
def initialize workspace = Solargraph::Workspace.new(nil)
|
8
7
|
@workspace = workspace
|
@@ -93,6 +92,8 @@ module Solargraph
|
|
93
92
|
end
|
94
93
|
end
|
95
94
|
|
95
|
+
# @param filename [String]
|
96
|
+
# @param version [Integer]
|
96
97
|
def overwrite filename, version
|
97
98
|
source = source_hash[filename]
|
98
99
|
return if source.nil?
|
@@ -206,14 +207,22 @@ module Solargraph
|
|
206
207
|
api_map.refresh force
|
207
208
|
end
|
208
209
|
|
210
|
+
# @param query [String]
|
211
|
+
# @return [Array<YARD::CodeObject::Base>]
|
209
212
|
def document query
|
210
213
|
api_map.document query
|
211
214
|
end
|
212
215
|
|
216
|
+
# @param query [String]
|
217
|
+
# @return [Array<String>]
|
213
218
|
def search query
|
214
219
|
api_map.search query
|
215
220
|
end
|
216
221
|
|
222
|
+
# Get an array of all symbols in the workspace that match the query.
|
223
|
+
#
|
224
|
+
# @param query [String]
|
225
|
+
# @return [Array<Pin::Base>]
|
217
226
|
def query_symbols query
|
218
227
|
api_map.query_symbols query
|
219
228
|
end
|
@@ -231,9 +240,9 @@ module Solargraph
|
|
231
240
|
end
|
232
241
|
|
233
242
|
# @param updater [Solargraph::Source::Updater]
|
234
|
-
def synchronize updater
|
243
|
+
def synchronize updater, reparse = true
|
235
244
|
source = read(updater.filename)
|
236
|
-
source.synchronize updater
|
245
|
+
source.synchronize updater, reparse
|
237
246
|
end
|
238
247
|
|
239
248
|
# Get the current text of a file in the library.
|