solargraph 0.26.1 → 0.27.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 +5 -2
- data/lib/solargraph/api_map.rb +236 -234
- data/lib/solargraph/api_map/store.rb +18 -53
- data/lib/solargraph/bundle.rb +22 -0
- data/lib/solargraph/complex_type.rb +9 -5
- data/lib/solargraph/complex_type/type_methods.rb +113 -0
- data/lib/solargraph/complex_type/unique_type.rb +35 -0
- data/lib/solargraph/core_fills.rb +1 -0
- data/lib/solargraph/diagnostics.rb +6 -4
- data/lib/solargraph/diagnostics/base.rb +3 -0
- data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
- data/lib/solargraph/diagnostics/rubocop.rb +21 -6
- data/lib/solargraph/diagnostics/type_not_defined.rb +4 -3
- data/lib/solargraph/diagnostics/update_errors.rb +18 -0
- data/lib/solargraph/language_server/host.rb +90 -222
- data/lib/solargraph/language_server/host/cataloger.rb +68 -0
- data/lib/solargraph/language_server/host/diagnoser.rb +85 -0
- data/lib/solargraph/language_server/message/extended/check_gem_version.rb +35 -24
- data/lib/solargraph/language_server/message/text_document/completion.rb +6 -8
- data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
- data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +0 -1
- data/lib/solargraph/language_server/transport/socket.rb +4 -6
- data/lib/solargraph/language_server/transport/stdio.rb +4 -6
- data/lib/solargraph/library.rb +152 -99
- data/lib/solargraph/live_map.rb +1 -1
- data/lib/solargraph/location.rb +28 -0
- data/lib/solargraph/pin.rb +2 -0
- data/lib/solargraph/pin/attribute.rb +26 -12
- data/lib/solargraph/pin/base.rb +15 -35
- data/lib/solargraph/pin/base_variable.rb +7 -15
- data/lib/solargraph/pin/block.rb +5 -9
- data/lib/solargraph/pin/block_parameter.rb +9 -7
- data/lib/solargraph/pin/conversions.rb +5 -5
- data/lib/solargraph/pin/duck_method.rb +1 -1
- data/lib/solargraph/pin/instance_variable.rb +0 -4
- data/lib/solargraph/pin/keyword.rb +4 -0
- data/lib/solargraph/pin/localized.rb +5 -3
- data/lib/solargraph/pin/method.rb +11 -0
- data/lib/solargraph/pin/namespace.rb +7 -3
- data/lib/solargraph/pin/proxy_type.rb +3 -7
- data/lib/solargraph/pin/reference.rb +2 -2
- data/lib/solargraph/pin/symbol.rb +1 -1
- data/lib/solargraph/pin/yard_pin/method.rb +2 -2
- data/lib/solargraph/pin/yard_pin/namespace.rb +16 -7
- data/lib/solargraph/position.rb +103 -0
- data/lib/solargraph/range.rb +70 -0
- data/lib/solargraph/source.rb +159 -328
- data/lib/solargraph/source/chain.rb +38 -55
- data/lib/solargraph/source/chain/call.rb +47 -29
- data/lib/solargraph/source/chain/class_variable.rb +2 -2
- data/lib/solargraph/source/chain/constant.rb +3 -3
- data/lib/solargraph/source/chain/definition.rb +7 -3
- data/lib/solargraph/source/chain/global_variable.rb +1 -1
- data/lib/solargraph/source/chain/head.rb +22 -9
- data/lib/solargraph/source/chain/instance_variable.rb +2 -2
- data/lib/solargraph/source/chain/link.rb +4 -4
- data/lib/solargraph/source/chain/literal.rb +1 -1
- data/lib/solargraph/source/chain/variable.rb +2 -2
- data/lib/solargraph/source/change.rb +0 -6
- data/lib/solargraph/source/cursor.rb +161 -0
- data/lib/solargraph/source/encoding_fixes.rb +1 -1
- data/lib/solargraph/source/node_chainer.rb +28 -21
- data/lib/solargraph/source/node_methods.rb +1 -1
- data/lib/solargraph/source/source_chainer.rb +217 -0
- data/lib/solargraph/source_map.rb +138 -0
- data/lib/solargraph/source_map/clip.rb +123 -0
- data/lib/solargraph/{source → source_map}/completion.rb +3 -3
- data/lib/solargraph/{source → source_map}/mapper.rb +143 -41
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph/workspace.rb +13 -20
- data/lib/solargraph/yard_map.rb +77 -48
- metadata +17 -11
- data/lib/solargraph/basic_type.rb +0 -33
- data/lib/solargraph/basic_type_methods.rb +0 -111
- data/lib/solargraph/source/call_chainer.rb +0 -273
- data/lib/solargraph/source/fragment.rb +0 -342
- data/lib/solargraph/source/location.rb +0 -23
- data/lib/solargraph/source/position.rb +0 -95
- data/lib/solargraph/source/range.rb +0 -64
@@ -1,273 +0,0 @@
|
|
1
|
-
module Solargraph
|
2
|
-
class Source
|
3
|
-
# Information about a location in a source, including the location's word
|
4
|
-
# and signature, literal values at the base of signatures, and whether the
|
5
|
-
# location is inside a string or comment. ApiMaps use Fragments to provide
|
6
|
-
# results for completion and definition queries.
|
7
|
-
#
|
8
|
-
class CallChainer
|
9
|
-
include NodeMethods
|
10
|
-
|
11
|
-
private_class_method :new
|
12
|
-
|
13
|
-
class << self
|
14
|
-
# @param source [Source]
|
15
|
-
# @param line [Integer]
|
16
|
-
# @param column [Integer]
|
17
|
-
# @return [Source::Chain]
|
18
|
-
def chain source, line, column
|
19
|
-
new(source, line, column).chain
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
# @param source [Solargraph::Source]
|
24
|
-
# @param line [Integer]
|
25
|
-
# @param column [Integer]
|
26
|
-
def initialize source, line, column
|
27
|
-
@source = source
|
28
|
-
# @source.code = source.code
|
29
|
-
@line = line
|
30
|
-
@column = column
|
31
|
-
@calculated_literal = false
|
32
|
-
end
|
33
|
-
|
34
|
-
# @return [Source::Chain]
|
35
|
-
def chain
|
36
|
-
links = []
|
37
|
-
# @todo Smelly colon handling
|
38
|
-
if @source.code[0..offset-1].end_with?(':') and !@source.code[0..offset-1].end_with?('::')
|
39
|
-
links.push Chain::Link.new
|
40
|
-
links.push Chain::Link.new
|
41
|
-
elsif @source.string_at?(line, column)
|
42
|
-
links.push Chain::Literal.new('String')
|
43
|
-
else
|
44
|
-
links.push Chain::Literal.new(base_literal) if base_literal?
|
45
|
-
sig = whole_signature
|
46
|
-
unless sig.empty?
|
47
|
-
sig = sig[1..-1] if sig.start_with?('.')
|
48
|
-
head = true
|
49
|
-
sig.split('.', -1).each do |word|
|
50
|
-
if word.include?('::')
|
51
|
-
# @todo Smelly way of handling constants
|
52
|
-
parts = (word.start_with?('::') ? word[2..-1] : word).split('::', -1)
|
53
|
-
last = parts.pop
|
54
|
-
links.push Chain::Constant.new(parts.join('::')) unless parts.empty?
|
55
|
-
links.push (last.nil? ? Chain::UNDEFINED_CONSTANT : Chain::Constant.new(last))
|
56
|
-
else
|
57
|
-
links.push word_to_link(word, head)
|
58
|
-
end
|
59
|
-
head = false
|
60
|
-
end
|
61
|
-
end
|
62
|
-
# Literal string hack
|
63
|
-
links.push Chain::UNDEFINED_CALL if base_literal? and @source.code[offset - 1] == '.' and links.length == 1
|
64
|
-
end
|
65
|
-
@chain ||= Chain.new(source.filename, links)
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
|
70
|
-
# The zero-based line number of the fragment's location.
|
71
|
-
#
|
72
|
-
# @return [Integer]
|
73
|
-
attr_reader :line
|
74
|
-
|
75
|
-
# The zero-based column number of the fragment's location.
|
76
|
-
#
|
77
|
-
# @return [Integer]
|
78
|
-
attr_reader :column
|
79
|
-
|
80
|
-
# @return [Solargraph::Source]
|
81
|
-
attr_reader :source
|
82
|
-
|
83
|
-
def word_to_link word, head
|
84
|
-
if word.start_with?('@@')
|
85
|
-
Chain::ClassVariable.new(word)
|
86
|
-
elsif word.start_with?('@')
|
87
|
-
Chain::InstanceVariable.new(word)
|
88
|
-
elsif word.start_with?('$')
|
89
|
-
Chain::GlobalVariable.new(word)
|
90
|
-
elsif word.end_with?(':')
|
91
|
-
Chain::Link.new
|
92
|
-
elsif word.empty?
|
93
|
-
Chain::UNDEFINED_CALL
|
94
|
-
elsif head and !@source.code[signature_data[0]..-1].match(/^[\s]*?#{word}[\s]*?\(/)
|
95
|
-
# The head needs to allow for ambiguous references to constants and
|
96
|
-
# methods. For example, `String` might be either. If the word is not
|
97
|
-
# followed by an open parenthesis, use Chain::Head for ambiguous
|
98
|
-
# results.
|
99
|
-
Chain::Head.new(word)
|
100
|
-
else
|
101
|
-
Chain::Call.new(word)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
# An alias for #column.
|
106
|
-
#
|
107
|
-
# @return [Integer]
|
108
|
-
def character
|
109
|
-
@column
|
110
|
-
end
|
111
|
-
|
112
|
-
# @return [Source::Position]
|
113
|
-
def position
|
114
|
-
@position ||= Position.new(line, column)
|
115
|
-
end
|
116
|
-
|
117
|
-
# Get the signature up to the current offset. Given the text `foo.bar`,
|
118
|
-
# the signature at offset 5 is `foo.b`.
|
119
|
-
#
|
120
|
-
# @return [String]
|
121
|
-
def signature
|
122
|
-
@signature ||= signature_data[1].to_s
|
123
|
-
end
|
124
|
-
|
125
|
-
# Get the remainder of the word after the current offset. Given the text
|
126
|
-
# `foobar` with an offset of 3, the remainder is `bar`.
|
127
|
-
#
|
128
|
-
# @return [String]
|
129
|
-
def remainder
|
130
|
-
@remainder ||= remainder_at(offset)
|
131
|
-
end
|
132
|
-
|
133
|
-
# Get the whole signature at the current offset, including the final
|
134
|
-
# word and its remainder.
|
135
|
-
#
|
136
|
-
# @return [String]
|
137
|
-
def whole_signature
|
138
|
-
@whole_signature ||= signature + remainder
|
139
|
-
end
|
140
|
-
|
141
|
-
# True if the current offset is inside a string.
|
142
|
-
#
|
143
|
-
# @return [Boolean]
|
144
|
-
def string?
|
145
|
-
# @string ||= (node.type == :str or node.type == :dstr)
|
146
|
-
@string ||= @source.string_at?(line, character)
|
147
|
-
end
|
148
|
-
|
149
|
-
# True if the fragment is a signature that stems from a literal value.
|
150
|
-
#
|
151
|
-
# @return [Boolean]
|
152
|
-
def base_literal?
|
153
|
-
!base_literal.nil?
|
154
|
-
end
|
155
|
-
|
156
|
-
# The type of literal value at the root of the signature (or nil).
|
157
|
-
#
|
158
|
-
# @return [String]
|
159
|
-
def base_literal
|
160
|
-
if @base_literal.nil? and !@calculated_literal
|
161
|
-
@calculated_literal = true
|
162
|
-
if signature.start_with?('.')
|
163
|
-
pn = @source.node_at(line, column - 2)
|
164
|
-
@base_literal = infer_literal_node_type(pn) unless pn.nil?
|
165
|
-
end
|
166
|
-
end
|
167
|
-
@base_literal
|
168
|
-
end
|
169
|
-
|
170
|
-
# @return [Integer]
|
171
|
-
def offset
|
172
|
-
@offset ||= get_offset(line, column)
|
173
|
-
end
|
174
|
-
|
175
|
-
def get_offset line, column
|
176
|
-
Position.line_char_to_offset(@source.code, line, column)
|
177
|
-
end
|
178
|
-
|
179
|
-
def signature_data
|
180
|
-
@signature_data ||= get_signature_data_at(offset)
|
181
|
-
end
|
182
|
-
|
183
|
-
def get_signature_data_at index
|
184
|
-
brackets = 0
|
185
|
-
squares = 0
|
186
|
-
parens = 0
|
187
|
-
signature = ''
|
188
|
-
index -=1
|
189
|
-
in_whitespace = false
|
190
|
-
while index >= 0
|
191
|
-
pos = Position.from_offset(@source.code, index)
|
192
|
-
break if index > 0 and @source.comment_at?(pos.line, pos.character)
|
193
|
-
unless !in_whitespace and string?
|
194
|
-
break if brackets > 0 or parens > 0 or squares > 0
|
195
|
-
char = @source.code[index, 1]
|
196
|
-
break if char.nil? # @todo Is this the right way to handle this?
|
197
|
-
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
|
198
|
-
in_whitespace = true
|
199
|
-
else
|
200
|
-
if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
|
201
|
-
unless char == '.' or @source.code[index+1..-1].strip.start_with?('.')
|
202
|
-
old = @source.code[index+1..-1]
|
203
|
-
nxt = @source.code[index+1..-1].lstrip
|
204
|
-
index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length)
|
205
|
-
break
|
206
|
-
end
|
207
|
-
end
|
208
|
-
if char == ')'
|
209
|
-
parens -=1
|
210
|
-
elsif char == ']'
|
211
|
-
squares -=1
|
212
|
-
elsif char == '}'
|
213
|
-
brackets -= 1
|
214
|
-
elsif char == '('
|
215
|
-
parens += 1
|
216
|
-
elsif char == '{'
|
217
|
-
brackets += 1
|
218
|
-
elsif char == '['
|
219
|
-
squares += 1
|
220
|
-
signature = ".[]#{signature}" if parens.zero? and brackets.zero? and squares.zero? and @source.code[index-2] != '%'
|
221
|
-
end
|
222
|
-
if brackets.zero? and parens.zero? and squares.zero?
|
223
|
-
break if ['"', "'", ',', ';', '%'].include?(char)
|
224
|
-
signature = char + signature if char.match(/[a-z0-9:\._@\$\?\!]/i) and @source.code[index - 1] != '%'
|
225
|
-
break if char == '$'
|
226
|
-
if char == '@'
|
227
|
-
signature = "@#{signature}" if @source.code[index-1, 1] == '@'
|
228
|
-
break
|
229
|
-
end
|
230
|
-
end
|
231
|
-
in_whitespace = false
|
232
|
-
end
|
233
|
-
end
|
234
|
-
index -= 1
|
235
|
-
end
|
236
|
-
# @todo Smelly exceptional case for integer literals
|
237
|
-
match = signature.match(/^[0-9]+/)
|
238
|
-
if match
|
239
|
-
index += match[0].length
|
240
|
-
signature = signature[match[0].length..-1].to_s
|
241
|
-
@base_literal = 'Integer'
|
242
|
-
# @todo Smelly exceptional case for array literals
|
243
|
-
elsif signature.start_with?('.[]')
|
244
|
-
index += 2
|
245
|
-
signature = signature[3..-1].to_s
|
246
|
-
@base_literal = 'Array'
|
247
|
-
elsif signature.start_with?('.')
|
248
|
-
pos = Position.from_offset(source.code, index)
|
249
|
-
node = source.node_at(pos.line, pos.character)
|
250
|
-
lit = infer_literal_node_type(node)
|
251
|
-
unless lit.nil?
|
252
|
-
signature = signature[1..-1].to_s
|
253
|
-
index += 1
|
254
|
-
@base_literal = lit
|
255
|
-
end
|
256
|
-
end
|
257
|
-
[index + 1, signature]
|
258
|
-
end
|
259
|
-
|
260
|
-
# @return [String]
|
261
|
-
def remainder_at index
|
262
|
-
cursor = index
|
263
|
-
while cursor < @source.code.length
|
264
|
-
char = @source.code[cursor, 1]
|
265
|
-
break if char.nil? or char == ''
|
266
|
-
break unless char.match(/[a-z0-9_\?\!]/i)
|
267
|
-
cursor += 1
|
268
|
-
end
|
269
|
-
@source.code[index..cursor-1].to_s
|
270
|
-
end
|
271
|
-
end
|
272
|
-
end
|
273
|
-
end
|
@@ -1,342 +0,0 @@
|
|
1
|
-
module Solargraph
|
2
|
-
class Source
|
3
|
-
# Information about a location in a source, including the location's word
|
4
|
-
# and signature, literal values at the base of signatures, and whether the
|
5
|
-
# location is inside a string or comment. ApiMaps use Fragments to provide
|
6
|
-
# results for completion and definition queries.
|
7
|
-
#
|
8
|
-
class Fragment
|
9
|
-
include NodeMethods
|
10
|
-
|
11
|
-
# The zero-based line number of the fragment's location.
|
12
|
-
#
|
13
|
-
# @return [Integer]
|
14
|
-
attr_reader :line
|
15
|
-
|
16
|
-
# The zero-based column number of the fragment's location.
|
17
|
-
#
|
18
|
-
# @return [Integer]
|
19
|
-
attr_reader :column
|
20
|
-
|
21
|
-
# @return [Solargraph::Source]
|
22
|
-
attr_reader :source
|
23
|
-
|
24
|
-
# @param source [Solargraph::Source]
|
25
|
-
# @param line [Integer]
|
26
|
-
# @param column [Integer]
|
27
|
-
def initialize source, line, column
|
28
|
-
@source = source
|
29
|
-
@code = source.code
|
30
|
-
@line = line
|
31
|
-
@column = column
|
32
|
-
@calculated_literal = false
|
33
|
-
end
|
34
|
-
|
35
|
-
# An alias for #column.
|
36
|
-
#
|
37
|
-
# @return [Integer]
|
38
|
-
def character
|
39
|
-
@column
|
40
|
-
end
|
41
|
-
|
42
|
-
# @return [Source::Position]
|
43
|
-
def position
|
44
|
-
@position ||= Position.new(line, column)
|
45
|
-
end
|
46
|
-
|
47
|
-
# Get the fully qualified namespace at the current offset.
|
48
|
-
#
|
49
|
-
# @return [String]
|
50
|
-
def namespace
|
51
|
-
if @namespace.nil?
|
52
|
-
pin = @source.locate_namespace_pin(line, character)
|
53
|
-
@namespace = (pin.nil? ? '' : pin.path)
|
54
|
-
end
|
55
|
-
@namespace
|
56
|
-
end
|
57
|
-
|
58
|
-
# True if the fragment is inside a method argument.
|
59
|
-
#
|
60
|
-
# @return [Boolean]
|
61
|
-
def argument?
|
62
|
-
@argument ||= !signature_position.nil?
|
63
|
-
end
|
64
|
-
|
65
|
-
# @return [Fragment]
|
66
|
-
def recipient
|
67
|
-
return nil if signature_position.nil?
|
68
|
-
@recipient ||= @source.fragment_at(*signature_position)
|
69
|
-
end
|
70
|
-
|
71
|
-
# Get the scope at the current offset.
|
72
|
-
#
|
73
|
-
# @return [Symbol] :class or :instance
|
74
|
-
def scope
|
75
|
-
if @scope.nil?
|
76
|
-
@scope = :class
|
77
|
-
@scope = :instance if named_path.kind == Pin::METHOD and named_path.scope == :instance
|
78
|
-
end
|
79
|
-
@scope
|
80
|
-
end
|
81
|
-
|
82
|
-
# Get the signature before the current word. Given the signature
|
83
|
-
# `String.new.split`, the base is `String.new`.
|
84
|
-
#
|
85
|
-
# @return [String]
|
86
|
-
def base
|
87
|
-
chain.links[0..-2].map(&:word).join('.')
|
88
|
-
end
|
89
|
-
|
90
|
-
# @return [Source::Chain]
|
91
|
-
def chain
|
92
|
-
@chain ||= generate_chain
|
93
|
-
end
|
94
|
-
|
95
|
-
# Get the whole signature at the current offset, including the final
|
96
|
-
# word and its remainder.
|
97
|
-
#
|
98
|
-
# @return [String]
|
99
|
-
def whole_signature
|
100
|
-
chain.links.reject{|l| l.word == '<undefined>'}.map(&:word).join('.')
|
101
|
-
end
|
102
|
-
|
103
|
-
# Get the word before the current offset. Given the text `foo.bar`, the
|
104
|
-
# word at offset 6 is `ba`.
|
105
|
-
#
|
106
|
-
# @return [String]
|
107
|
-
def start_of_word
|
108
|
-
@start_of_word ||= begin
|
109
|
-
match = @code[0..offset-1].to_s.match(start_word_pattern)
|
110
|
-
result = (match ? match[0] : '')
|
111
|
-
result = ":#{result}" if @code[0..offset-result.length].end_with?('::') and !@code[0..offset-result.length].end_with?('::')
|
112
|
-
result
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
def word
|
117
|
-
start_of_word
|
118
|
-
end
|
119
|
-
|
120
|
-
def end_of_word
|
121
|
-
@end_of_word ||= begin
|
122
|
-
match = @code[offset..-1].to_s.match(end_word_pattern)
|
123
|
-
match ? match[0] : ''
|
124
|
-
end
|
125
|
-
end
|
126
|
-
|
127
|
-
def remainder
|
128
|
-
end_of_word
|
129
|
-
end
|
130
|
-
|
131
|
-
def whole_word
|
132
|
-
start_of_word + end_of_word
|
133
|
-
end
|
134
|
-
|
135
|
-
# True if the current offset is inside a string.
|
136
|
-
#
|
137
|
-
# @return [Boolean]
|
138
|
-
def string?
|
139
|
-
@string ||= @source.string_at?(line, character)
|
140
|
-
end
|
141
|
-
|
142
|
-
# True if the current offset is inside a comment.
|
143
|
-
#
|
144
|
-
# @return [Boolean]
|
145
|
-
def comment?
|
146
|
-
@comment ||= @source.comment_at?(line, column)
|
147
|
-
end
|
148
|
-
|
149
|
-
# Get the range of the whole word at the current offset, including its
|
150
|
-
# remainder.
|
151
|
-
#
|
152
|
-
# @return [Range]
|
153
|
-
def word_range
|
154
|
-
@word_range ||= word_range_at(offset - start_of_word.length, offset + end_of_word.length)
|
155
|
-
end
|
156
|
-
|
157
|
-
# @return [Solargraph::Pin::Base]
|
158
|
-
def block
|
159
|
-
@block ||= @source.locate_block_pin(line, character)
|
160
|
-
end
|
161
|
-
|
162
|
-
# @return [Solargraph::Pin::Base]
|
163
|
-
def named_path
|
164
|
-
@named_path ||= @source.locate_named_path_pin(line, character)
|
165
|
-
end
|
166
|
-
|
167
|
-
# Get an array of all the locals that are visible from the fragment's
|
168
|
-
# position. Locals can be local variables, method parameters, or block
|
169
|
-
# parameters. The array starts with the nearest local pin.
|
170
|
-
#
|
171
|
-
# @return [Array<Solargraph::Pin::Base>]
|
172
|
-
def locals
|
173
|
-
@locals ||= @source.locals.select{|pin| pin.visible_from?(block, position)}.reverse
|
174
|
-
end
|
175
|
-
|
176
|
-
# True if the fragment is inside a literal value.
|
177
|
-
#
|
178
|
-
# @return [Boolean]
|
179
|
-
def literal?
|
180
|
-
!literal.nil?
|
181
|
-
end
|
182
|
-
|
183
|
-
# The fragment's literal type, or nil if the fragment is not inside a
|
184
|
-
# literal value.
|
185
|
-
#
|
186
|
-
# @return [String]
|
187
|
-
def literal
|
188
|
-
if @literal.nil? and !@calculated_actual_literal
|
189
|
-
@calculated_actual_literal = true
|
190
|
-
pn = @source.node_at(line, column)
|
191
|
-
@literal = infer_literal_node_type(pn)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
# Get a set of available completions for the specified fragment. The
|
196
|
-
# resulting Completion object contains an array of pins and the range of
|
197
|
-
# text to replace in the source.
|
198
|
-
#
|
199
|
-
# @param api_map [ApiMap]
|
200
|
-
# @return [Completion]
|
201
|
-
def complete api_map
|
202
|
-
return Completion.new([], word_range) if chain.literal? or comment?
|
203
|
-
result = []
|
204
|
-
type = infer_base_type(api_map)
|
205
|
-
if chain.tail.constant?
|
206
|
-
result.concat api_map.get_constants(type.namespace, namespace)
|
207
|
-
else
|
208
|
-
result.concat api_map.get_complex_type_methods(type, namespace, chain.links.length == 1)
|
209
|
-
if chain.links.length == 1
|
210
|
-
if word.start_with?('@@')
|
211
|
-
return package_completions(api_map.get_class_variable_pins(namespace))
|
212
|
-
elsif word.start_with?('@')
|
213
|
-
return package_completions(api_map.get_instance_variable_pins(namespace, scope))
|
214
|
-
elsif word.start_with?('$')
|
215
|
-
return package_completions(api_map.get_global_variable_pins)
|
216
|
-
elsif word.start_with?(':') and !word.start_with?('::')
|
217
|
-
return package_completions(api_map.get_symbols)
|
218
|
-
end
|
219
|
-
result.concat api_map.get_constants('', namespace)
|
220
|
-
result.concat prefer_non_nil_variables(locals)
|
221
|
-
result.concat api_map.get_methods(namespace, scope: scope, visibility: [:public, :private, :protected])
|
222
|
-
result.concat api_map.get_methods('Kernel')
|
223
|
-
result.concat ApiMap.keywords
|
224
|
-
end
|
225
|
-
end
|
226
|
-
package_completions(result)
|
227
|
-
end
|
228
|
-
|
229
|
-
def define api_map
|
230
|
-
return [] if chain.literal?
|
231
|
-
return [] if string? or comment? or literal?
|
232
|
-
# HACK: Checking for self first because it's also a keyword
|
233
|
-
return [] if ApiMap::KEYWORDS.include?(chain.links.first.word) and chain.links.first.word != 'self'
|
234
|
-
chain.define_with(api_map, named_path, locals)
|
235
|
-
end
|
236
|
-
|
237
|
-
# Get an array of pins that describe the method being called by the
|
238
|
-
# argument list where the fragment is located. This is useful for queries
|
239
|
-
# that need to know what parameters the current method expects to receive.
|
240
|
-
#
|
241
|
-
# If the fragment is not inside an argument list, return an empty array.
|
242
|
-
#
|
243
|
-
# @param api_map [Solargraph::Source::Fragment]
|
244
|
-
# @return [Array<Solargraph::Pin::Base>]
|
245
|
-
def signify api_map
|
246
|
-
return [] unless argument?
|
247
|
-
return [] if recipient.whole_signature.nil? or recipient.whole_signature.empty?
|
248
|
-
result = []
|
249
|
-
result.concat recipient.define(api_map)
|
250
|
-
result.select{ |pin| pin.kind == Pin::METHOD }
|
251
|
-
end
|
252
|
-
|
253
|
-
# @param api_map [ApiMap]
|
254
|
-
# @return [ComplexType]
|
255
|
-
def infer_base_type api_map
|
256
|
-
chain.infer_base_type_with(api_map, named_path, locals)
|
257
|
-
end
|
258
|
-
|
259
|
-
private
|
260
|
-
|
261
|
-
# @return [Integer]
|
262
|
-
def offset
|
263
|
-
@offset ||= get_offset(line, column)
|
264
|
-
end
|
265
|
-
|
266
|
-
def get_offset line, column
|
267
|
-
Position.line_char_to_offset(@code, line, column)
|
268
|
-
end
|
269
|
-
|
270
|
-
def get_position_at(offset)
|
271
|
-
pos = Position.from_offset(@code, offset)
|
272
|
-
[pos.line, pos.character]
|
273
|
-
end
|
274
|
-
|
275
|
-
# @return Solargraph::Source::Range
|
276
|
-
def word_range_at first, last
|
277
|
-
s = Position.from_offset(@source.code, first)
|
278
|
-
e = Position.from_offset(@source.code, last)
|
279
|
-
Solargraph::Source::Range.new(s, e)
|
280
|
-
end
|
281
|
-
|
282
|
-
def signature_position
|
283
|
-
if @signature_position.nil?
|
284
|
-
open_parens = 0
|
285
|
-
cursor = offset - 1
|
286
|
-
while cursor >= 0
|
287
|
-
break if cursor < 0
|
288
|
-
if @code[cursor] == ')'
|
289
|
-
open_parens -= 1
|
290
|
-
elsif @code[cursor] == '('
|
291
|
-
open_parens += 1
|
292
|
-
end
|
293
|
-
break if open_parens == 1
|
294
|
-
cursor -= 1
|
295
|
-
end
|
296
|
-
if cursor >= 0
|
297
|
-
@signature_position = get_position_at(cursor)
|
298
|
-
end
|
299
|
-
end
|
300
|
-
@signature_position
|
301
|
-
end
|
302
|
-
|
303
|
-
def generate_chain
|
304
|
-
CallChainer.chain(source, line, column)
|
305
|
-
end
|
306
|
-
|
307
|
-
def start_word_pattern
|
308
|
-
/(@{1,2}|\$)?([a-z0-9_]|[^\u0000-\u007F])*\z/i
|
309
|
-
end
|
310
|
-
|
311
|
-
def end_word_pattern
|
312
|
-
/^([a-z0-9_]|[^\u0000-\u007F])*[\?\!]?/i
|
313
|
-
end
|
314
|
-
|
315
|
-
# @param fragment [Source::Fragment]
|
316
|
-
# @param result [Array<Pin::Base>]
|
317
|
-
# @return [Completion]
|
318
|
-
def package_completions result
|
319
|
-
frag_start = word.to_s.downcase
|
320
|
-
filtered = result.uniq(&:identifier).select{|s| s.name.downcase.start_with?(frag_start) and (s.kind != Pin::METHOD or s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i))}.sort_by.with_index{ |x, idx| [x.name, idx] }
|
321
|
-
Completion.new(filtered, word_range)
|
322
|
-
end
|
323
|
-
|
324
|
-
# Sort an array of pins to put nil or undefined variables last.
|
325
|
-
#
|
326
|
-
# @param pins [Array<Solargraph::Pin::Base>]
|
327
|
-
# @return [Array<Solargraph::Pin::Base>]
|
328
|
-
def prefer_non_nil_variables pins
|
329
|
-
result = []
|
330
|
-
nil_pins = []
|
331
|
-
pins.each do |pin|
|
332
|
-
if pin.variable? and pin.nil_assignment?
|
333
|
-
nil_pins.push pin
|
334
|
-
else
|
335
|
-
result.push pin
|
336
|
-
end
|
337
|
-
end
|
338
|
-
result + nil_pins
|
339
|
-
end
|
340
|
-
end
|
341
|
-
end
|
342
|
-
end
|