solargraph 0.18.1 → 0.18.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.
- checksums.yaml +4 -4
- data/lib/solargraph/api_map.rb +14 -17
- data/lib/solargraph/diagnostics/rubocop.rb +66 -0
- data/lib/solargraph/diagnostics.rb +5 -0
- data/lib/solargraph/language_server/host.rb +40 -58
- data/lib/solargraph/language_server/message/completion_item/resolve.rb +2 -1
- data/lib/solargraph/language_server/message/text_document/completion.rb +16 -11
- data/lib/solargraph/language_server/message/text_document/hover.rb +40 -11
- data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +3 -0
- data/lib/solargraph/language_server/message/text_document/signature_help.rb +2 -5
- data/lib/solargraph/language_server/transport/socket.rb +8 -0
- data/lib/solargraph/library.rb +6 -22
- data/lib/solargraph/pin/base_variable.rb +1 -1
- data/lib/solargraph/pin/conversions.rb +1 -0
- data/lib/solargraph/pin/local_variable.rb +2 -2
- data/lib/solargraph/source/fragment.rb +131 -31
- data/lib/solargraph/source.rb +75 -85
- data/lib/solargraph/version.rb +1 -1
- data/lib/solargraph.rb +1 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 642ac02a775d6f81c76137c676057b4fefe524c2
|
4
|
+
data.tar.gz: 79357368d35da0297313c3085aa9127f333d14b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f4a69583c1cf3962a447cfb2e7e512de1046ca013aab9ebfffed46d87f4551dec1b9aacbcc9d04dc41c9fd61dcdad0383ffeecf8cb6d0818f781d04630b29893
|
7
|
+
data.tar.gz: f47ff7d37744cfba9572aee11539710d886c4e1e7a6299f82a977d1e2d036a7398ad1d0b622fe0f456c355efe1fde94d4774e0493c91ba46202437c50f961351
|
data/lib/solargraph/api_map.rb
CHANGED
@@ -276,7 +276,7 @@ module Solargraph
|
|
276
276
|
# @param scope [Symbol] :instance or :class
|
277
277
|
# @return [Array<Solargraph::Pin::InstanceVariable>]
|
278
278
|
def get_instance_variable_pins(namespace, scope = :instance)
|
279
|
-
suggest_unique_variables
|
279
|
+
suggest_unique_variables((@ivar_pins[namespace] || []).select{ |pin| pin.scope == scope })
|
280
280
|
end
|
281
281
|
|
282
282
|
# Get an array of class variable pins for a namespace.
|
@@ -303,11 +303,11 @@ module Solargraph
|
|
303
303
|
|
304
304
|
# @return [Solargraph::Source]
|
305
305
|
def get_source_for(node)
|
306
|
-
|
306
|
+
return @virtual_source if !@virtual_source.nil? and @virtual_source.include?(node)
|
307
307
|
@sources.each do |source|
|
308
|
-
|
308
|
+
return source if source.include?(node)
|
309
309
|
end
|
310
|
-
|
310
|
+
nil
|
311
311
|
end
|
312
312
|
|
313
313
|
# @return [Array<Solargraph::Pin::GlobalVariable>]
|
@@ -399,13 +399,13 @@ module Solargraph
|
|
399
399
|
elsif fragment.signature.start_with?('@')
|
400
400
|
result.concat get_instance_variable_pins(fragment.namespace, fragment.scope)
|
401
401
|
elsif fragment.signature.start_with?('$')
|
402
|
-
result.concat get_global_variable_pins
|
402
|
+
result.concat suggest_unique_variables(get_global_variable_pins)
|
403
403
|
elsif fragment.signature.start_with?(':') and !fragment.signature.start_with?('::')
|
404
404
|
result.concat get_symbols
|
405
405
|
else
|
406
406
|
unless fragment.signature.include?('::')
|
407
|
-
result.concat fragment.local_variable_pins
|
408
|
-
result.concat get_type_methods(fragment.namespace, fragment.namespace)
|
407
|
+
result.concat suggest_unique_variables(fragment.local_variable_pins)
|
408
|
+
result.concat get_type_methods(combine_type(fragment.namespace, fragment.scope), fragment.namespace)
|
409
409
|
result.concat ApiMap.keywords
|
410
410
|
end
|
411
411
|
result.concat get_constants(fragment.base, fragment.namespace)
|
@@ -425,7 +425,7 @@ module Solargraph
|
|
425
425
|
def define fragment
|
426
426
|
return [] if fragment.string? or fragment.comment?
|
427
427
|
pins = infer_signature_pins fragment.whole_signature, fragment.namespace, fragment.scope, fragment.node
|
428
|
-
return
|
428
|
+
return pins if pins.empty?
|
429
429
|
if pins.first.variable?
|
430
430
|
result = []
|
431
431
|
pins.select{|pin| pin.variable?}.each do |pin|
|
@@ -438,13 +438,10 @@ module Solargraph
|
|
438
438
|
end
|
439
439
|
end
|
440
440
|
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
def identify fragment
|
446
|
-
pins = infer_signature_pins(fragment.whole_signature, fragment.namespace, fragment.scope, fragment.node)
|
447
|
-
pins.each { |pin| pin.resolve self }
|
441
|
+
def signify fragment
|
442
|
+
return [] unless fragment.argument?
|
443
|
+
pins = infer_signature_pins(fragment.recipient.whole_signature, fragment.recipient.namespace, fragment.recipient.scope, fragment.recipient.node)
|
444
|
+
# pins.each{|pin| pin.resolve self}
|
448
445
|
pins
|
449
446
|
end
|
450
447
|
|
@@ -480,7 +477,7 @@ module Solargraph
|
|
480
477
|
end
|
481
478
|
source = get_source_for(call_node)
|
482
479
|
unless source.nil?
|
483
|
-
lvpins = source.local_variable_pins.select{|pin| pin.name == base and pin.visible_from?(call_node)}
|
480
|
+
lvpins = suggest_unique_variables(source.local_variable_pins.select{|pin| pin.name == base and pin.visible_from?(call_node)})
|
484
481
|
unless lvpins.empty?
|
485
482
|
if rest.nil?
|
486
483
|
return lvpins
|
@@ -886,7 +883,7 @@ module Solargraph
|
|
886
883
|
|
887
884
|
# @todo DRY this method. It's duplicated in CodeMap
|
888
885
|
def get_subtypes type
|
889
|
-
return
|
886
|
+
return [] if type.nil?
|
890
887
|
match = type.match(/<([a-z0-9_:, ]*)>/i)
|
891
888
|
return [] if match.nil?
|
892
889
|
match[1].split(',').map(&:strip)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module Solargraph
|
5
|
+
module Diagnostics
|
6
|
+
class Rubocop
|
7
|
+
def initialize
|
8
|
+
end
|
9
|
+
|
10
|
+
# @return [Array<Hash>]
|
11
|
+
def diagnose text, filename
|
12
|
+
begin
|
13
|
+
cmd = "rubocop -f j -s #{Shellwords.escape(filename)}"
|
14
|
+
o, e, s = Open3.capture3(cmd, stdin_data: text)
|
15
|
+
make_array text, JSON.parse(o)
|
16
|
+
rescue Exception => e
|
17
|
+
STDERR.puts "#{e}"
|
18
|
+
STDERR.puts "#{e.backtrace}"
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def make_array text, resp
|
26
|
+
severities = {
|
27
|
+
'refactor' => 4,
|
28
|
+
'convention' => 3,
|
29
|
+
'warning' => 2,
|
30
|
+
'error' => 1,
|
31
|
+
'fatal' => 1
|
32
|
+
}
|
33
|
+
diagnostics = []
|
34
|
+
resp['files'].each do |file|
|
35
|
+
file['offenses'].each do |off|
|
36
|
+
if off['location']['start_line'] != off['location']['last_line']
|
37
|
+
last_line = off['location']['start_line']
|
38
|
+
last_column = 0
|
39
|
+
else
|
40
|
+
last_line = off['location']['last_line'] - 1
|
41
|
+
last_column = off['location']['last_column']
|
42
|
+
end
|
43
|
+
diag = {
|
44
|
+
range: {
|
45
|
+
start: {
|
46
|
+
line: off['location']['start_line'] - 1,
|
47
|
+
character: off['location']['start_column'] - 1
|
48
|
+
},
|
49
|
+
end: {
|
50
|
+
line: last_line,
|
51
|
+
character: last_column
|
52
|
+
}
|
53
|
+
},
|
54
|
+
# 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
|
55
|
+
severity: severities[off['severity']],
|
56
|
+
source: off['cop_name'],
|
57
|
+
message: off['message'].gsub(/^#{off['cop_name']}\:/, '')
|
58
|
+
}
|
59
|
+
diagnostics.push diag
|
60
|
+
end
|
61
|
+
end
|
62
|
+
diagnostics
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,5 +1,3 @@
|
|
1
|
-
require 'open3'
|
2
|
-
require 'shellwords'
|
3
1
|
# require 'rubocop'
|
4
2
|
require 'thread'
|
5
3
|
require 'set'
|
@@ -164,6 +162,31 @@ module Solargraph
|
|
164
162
|
library.read_text(filename)
|
165
163
|
end
|
166
164
|
|
165
|
+
def completions_at filename, line, column
|
166
|
+
results = nil
|
167
|
+
@change_semaphore.synchronize do
|
168
|
+
results = library.completions_at filename, line, column
|
169
|
+
end
|
170
|
+
results
|
171
|
+
end
|
172
|
+
|
173
|
+
# @return [Array<Solargraph::Pin::Base>]
|
174
|
+
def definitions_at filename, line, column
|
175
|
+
results = nil
|
176
|
+
@change_semaphore.synchronize do
|
177
|
+
results = library.definitions_at(filename, line, column)
|
178
|
+
end
|
179
|
+
results
|
180
|
+
end
|
181
|
+
|
182
|
+
def signatures_at filename, line, column
|
183
|
+
results = nil
|
184
|
+
@change_semaphore.synchronize do
|
185
|
+
results = library.signatures_at(filename, line, column)
|
186
|
+
end
|
187
|
+
results
|
188
|
+
end
|
189
|
+
|
167
190
|
private
|
168
191
|
|
169
192
|
def start_change_thread
|
@@ -212,6 +235,7 @@ module Solargraph
|
|
212
235
|
|
213
236
|
def start_diagnostics_thread
|
214
237
|
Thread.new do
|
238
|
+
diagnoser = Diagnostics::Rubocop.new
|
215
239
|
until stopped?
|
216
240
|
if options['diagnostics'] != 'rubocop'
|
217
241
|
@change_semaphore.synchronize { @diagnostics_queue.clear }
|
@@ -228,9 +252,21 @@ module Solargraph
|
|
228
252
|
already_changing = false
|
229
253
|
@change_semaphore.synchronize { already_changing = (changing?(current) or @diagnostics_queue.include?(current)) }
|
230
254
|
unless already_changing
|
231
|
-
|
255
|
+
filename = nil
|
256
|
+
text = nil
|
257
|
+
@change_semaphore.synchronize do
|
258
|
+
filename = uri_to_file(current)
|
259
|
+
text = library.read_text(filename)
|
260
|
+
end
|
261
|
+
results = diagnoser.diagnose text, filename
|
232
262
|
@change_semaphore.synchronize { already_changing = (changing?(current) or @diagnostics_queue.include?(current)) }
|
233
|
-
publish_diagnostics current, resp unless already_changing
|
263
|
+
# publish_diagnostics current, resp unless already_changing
|
264
|
+
unless already_changing
|
265
|
+
send_notification "textDocument/publishDiagnostics", {
|
266
|
+
uri: current,
|
267
|
+
diagnostics: results
|
268
|
+
}
|
269
|
+
end
|
234
270
|
end
|
235
271
|
end
|
236
272
|
rescue Exception => e
|
@@ -246,60 +282,6 @@ module Solargraph
|
|
246
282
|
return path if File::ALT_SEPARATOR.nil?
|
247
283
|
path.gsub(File::ALT_SEPARATOR, File::SEPARATOR)
|
248
284
|
end
|
249
|
-
|
250
|
-
def read_diagnostics uri
|
251
|
-
begin
|
252
|
-
filename = nil
|
253
|
-
text = nil
|
254
|
-
@change_semaphore.synchronize do
|
255
|
-
filename = uri_to_file(uri)
|
256
|
-
text = library.read_text(filename)
|
257
|
-
end
|
258
|
-
cmd = "rubocop -f j -s #{Shellwords.escape(filename)}"
|
259
|
-
o, e, s = Open3.capture3(cmd, stdin_data: text)
|
260
|
-
JSON.parse(o)
|
261
|
-
rescue Exception => e
|
262
|
-
STDERR.puts "#{e}"
|
263
|
-
STDERR.puts "#{e.backtrace}"
|
264
|
-
nil
|
265
|
-
end
|
266
|
-
end
|
267
|
-
|
268
|
-
def publish_diagnostics uri, resp
|
269
|
-
severities = {
|
270
|
-
'refactor' => 4,
|
271
|
-
'convention' => 3,
|
272
|
-
'warning' => 2,
|
273
|
-
'error' => 1,
|
274
|
-
'fatal' => 1
|
275
|
-
}
|
276
|
-
diagnostics = []
|
277
|
-
resp['files'].each do |file|
|
278
|
-
file['offenses'].each do |off|
|
279
|
-
diag = {
|
280
|
-
range: {
|
281
|
-
start: {
|
282
|
-
line: off['location']['start_line'] - 1,
|
283
|
-
character: off['location']['start_column'] - 1
|
284
|
-
},
|
285
|
-
end: {
|
286
|
-
line: off['location']['last_line'] - 1,
|
287
|
-
character: off['location']['last_column']
|
288
|
-
}
|
289
|
-
},
|
290
|
-
# 1 = Error, 2 = Warning, 3 = Information, 4 = Hint
|
291
|
-
severity: severities[off['severity']],
|
292
|
-
source: off['cop_name'],
|
293
|
-
message: off['message'].gsub(/^#{off['cop_name']}\:/, '')
|
294
|
-
}
|
295
|
-
diagnostics.push diag
|
296
|
-
end
|
297
|
-
end
|
298
|
-
send_notification "textDocument/publishDiagnostics", {
|
299
|
-
uri: uri,
|
300
|
-
diagnostics: diagnostics
|
301
|
-
}
|
302
|
-
end
|
303
285
|
end
|
304
286
|
end
|
305
287
|
end
|
@@ -9,7 +9,8 @@ module Solargraph
|
|
9
9
|
def process
|
10
10
|
pin = host.locate_pin params
|
11
11
|
if pin.nil?
|
12
|
-
set_error(Solargraph::LanguageServer::ErrorCodes::INVALID_REQUEST, "Completion item could not be resolved")
|
12
|
+
# set_error(Solargraph::LanguageServer::ErrorCodes::INVALID_REQUEST, "Completion item could not be resolved")
|
13
|
+
set_result params
|
13
14
|
else
|
14
15
|
set_result(
|
15
16
|
params.merge(pin.resolve_completion_item)
|
@@ -10,24 +10,29 @@ module Solargraph
|
|
10
10
|
start = Time.now
|
11
11
|
processed = false
|
12
12
|
until processed
|
13
|
-
host.
|
14
|
-
if
|
15
|
-
|
16
|
-
if Time.now - start > 1
|
17
|
-
set_result empty_result
|
18
|
-
processed = true
|
19
|
-
end
|
20
|
-
else
|
21
|
-
inner_process
|
13
|
+
if host.changing?(params['textDocument']['uri'])
|
14
|
+
if Time.now - start > 1
|
15
|
+
set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, 'Completion request timed out'
|
22
16
|
processed = true
|
23
17
|
end
|
18
|
+
else
|
19
|
+
inner_process
|
20
|
+
processed = true
|
24
21
|
end
|
25
22
|
sleep 0.1 unless processed
|
26
23
|
end
|
27
24
|
rescue Exception => e
|
28
25
|
STDERR.puts e.message
|
29
26
|
STDERR.puts e.backtrace
|
30
|
-
|
27
|
+
# Ignore 'Invalid offset' errors, since they usually just mean
|
28
|
+
# that the document is in the process of changing.
|
29
|
+
if e.message.include?('Invalid offset')
|
30
|
+
# @todo Should this result be marked as incomplete? It might
|
31
|
+
# be possible to resolve it after changes are finished.
|
32
|
+
set_result empty_result
|
33
|
+
else
|
34
|
+
set_error ErrorCodes::INTERNAL_ERROR, e.message
|
35
|
+
end
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
@@ -37,7 +42,7 @@ module Solargraph
|
|
37
42
|
filename = uri_to_file(params['textDocument']['uri'])
|
38
43
|
line = params['position']['line']
|
39
44
|
col = params['position']['character']
|
40
|
-
completion = host.
|
45
|
+
completion = host.completions_at(filename, line, col)
|
41
46
|
items = []
|
42
47
|
idx = 0
|
43
48
|
completion.pins.each do |pin|
|
@@ -3,17 +3,46 @@ require 'uri'
|
|
3
3
|
module Solargraph::LanguageServer::Message::TextDocument
|
4
4
|
class Hover < Base
|
5
5
|
def process
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
contents
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
begin
|
7
|
+
filename = uri_to_file(params['textDocument']['uri'])
|
8
|
+
line = params['position']['line']
|
9
|
+
col = params['position']['character']
|
10
|
+
suggestions = host.definitions_at(filename, line, col)
|
11
|
+
# contents = suggestions.map(&:hover)
|
12
|
+
contents = []
|
13
|
+
last_path = nil
|
14
|
+
suggestions.each do |pin|
|
15
|
+
parts = []
|
16
|
+
this_path = nil
|
17
|
+
if pin.kind_of?(Solargraph::Pin::BaseVariable)
|
18
|
+
this_path = pin.return_type
|
19
|
+
else
|
20
|
+
this_path = pin.path
|
21
|
+
end
|
22
|
+
if !this_path.nil? and this_path != last_path
|
23
|
+
parts.push link_documentation(this_path)
|
24
|
+
end
|
25
|
+
parts.push pin.documentation unless pin.documentation.nil? or pin.documentation.empty?
|
26
|
+
contents.push parts.join("\n\n") unless parts.empty?
|
27
|
+
last_path = this_path unless this_path.nil?
|
28
|
+
end
|
29
|
+
set_result(
|
30
|
+
contents: {
|
31
|
+
kind: 'markdown',
|
32
|
+
value: contents.join("\n\n")
|
33
|
+
}
|
34
|
+
)
|
35
|
+
rescue Exception => e
|
36
|
+
set_error Solargraph::LanguageServer::ErrorCodes::INTERNAL_ERROR, e.message
|
37
|
+
end
|
17
38
|
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @todo: DRY this method. It exists in Conversions
|
43
|
+
def link_documentation path
|
44
|
+
uri = "solargraph:/document?query=" + URI.encode(path)
|
45
|
+
"[#{path}](#{uri})"
|
46
|
+
end
|
18
47
|
end
|
19
48
|
end
|
@@ -4,6 +4,9 @@ module Solargraph
|
|
4
4
|
module TextDocument
|
5
5
|
class OnTypeFormatting < Base
|
6
6
|
def process
|
7
|
+
# @todo Temporarily disabled
|
8
|
+
set_result []
|
9
|
+
return
|
7
10
|
src = host.library.checkout(uri_to_file(params['textDocument']['uri']))
|
8
11
|
offset = src.get_offset(params['position']['line'], params['position']['character'])
|
9
12
|
if src.string_at?(offset-1) and params['ch'] == '{' and src.code[offset-2,2] == '#{'
|
@@ -7,11 +7,8 @@ module Solargraph
|
|
7
7
|
filename = uri_to_file(params['textDocument']['uri'])
|
8
8
|
line = params['position']['line']
|
9
9
|
col = params['position']['character']
|
10
|
-
suggestions = host.
|
11
|
-
info =
|
12
|
-
suggestions.each do |s|
|
13
|
-
info.push s.signature_help
|
14
|
-
end
|
10
|
+
suggestions = host.signatures_at(filename, line, col)
|
11
|
+
info = suggestions.map(&:signature_help)
|
15
12
|
set_result({
|
16
13
|
signatures: info
|
17
14
|
})
|
@@ -16,14 +16,22 @@ module Solargraph
|
|
16
16
|
send_data tmp unless tmp.empty?
|
17
17
|
EventMachine.stop if @host.stopped?
|
18
18
|
end
|
19
|
+
@message_semaphore = Mutex.new
|
20
|
+
@message_stack = 0
|
19
21
|
end
|
20
22
|
|
21
23
|
def process request
|
22
24
|
Thread.new do
|
25
|
+
@message_semaphore.synchronize do
|
26
|
+
@message_stack += 1
|
27
|
+
end
|
23
28
|
message = @host.start(request)
|
24
29
|
message.send
|
25
30
|
tmp = @host.flush
|
26
31
|
send_data tmp unless tmp.empty?
|
32
|
+
@message_semaphore.synchronize do
|
33
|
+
@message_stack -= 1
|
34
|
+
end
|
27
35
|
end
|
28
36
|
end
|
29
37
|
|
data/lib/solargraph/library.rb
CHANGED
@@ -74,7 +74,7 @@ module Solargraph
|
|
74
74
|
# @type [Solargraph::Source]
|
75
75
|
source = nil
|
76
76
|
source = read(filename)
|
77
|
-
fragment =
|
77
|
+
fragment = source.fragment_at(line, column)
|
78
78
|
api_map.complete(fragment)
|
79
79
|
end
|
80
80
|
|
@@ -87,7 +87,7 @@ module Solargraph
|
|
87
87
|
# @return [Array<Solargraph::Pin::Base>]
|
88
88
|
def definitions_at filename, line, column
|
89
89
|
source = read(filename)
|
90
|
-
fragment =
|
90
|
+
fragment = source.fragment_at(line, column)
|
91
91
|
result = api_map.define(fragment)
|
92
92
|
result
|
93
93
|
end
|
@@ -101,8 +101,8 @@ module Solargraph
|
|
101
101
|
# @return [Array<Solargraph::Pin::Base>]
|
102
102
|
def signatures_at filename, line, column
|
103
103
|
source = read(filename)
|
104
|
-
fragment =
|
105
|
-
api_map.
|
104
|
+
fragment = source.fragment_at(line, column)
|
105
|
+
api_map.signify(fragment)
|
106
106
|
end
|
107
107
|
|
108
108
|
# Get the pin at the specified location or nil if the pin does not exist.
|
@@ -191,12 +191,13 @@ module Solargraph
|
|
191
191
|
def api_map
|
192
192
|
@api_map ||= Solargraph::ApiMap.new(workspace)
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
# @return [Solargraph::Workspace]
|
196
196
|
def workspace
|
197
197
|
@workspace
|
198
198
|
end
|
199
199
|
|
200
|
+
# @param filename [String]
|
200
201
|
# @return [Solargraph::Source]
|
201
202
|
def read filename
|
202
203
|
source = source_hash[filename]
|
@@ -204,22 +205,5 @@ module Solargraph
|
|
204
205
|
api_map.virtualize source
|
205
206
|
source
|
206
207
|
end
|
207
|
-
|
208
|
-
def signature_index_before source, index
|
209
|
-
open_parens = 0
|
210
|
-
cursor = index - 1
|
211
|
-
while cursor >= 0
|
212
|
-
break if cursor < 0
|
213
|
-
if source.code[cursor] == ')'
|
214
|
-
open_parens -= 1
|
215
|
-
elsif source.code[cursor] == '('
|
216
|
-
open_parens += 1
|
217
|
-
end
|
218
|
-
break if open_parens == 1
|
219
|
-
cursor -= 1
|
220
|
-
end
|
221
|
-
cursor = 0 if cursor < 0
|
222
|
-
cursor
|
223
|
-
end
|
224
208
|
end
|
225
209
|
end
|
@@ -11,8 +11,8 @@ module Solargraph
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def visible_from?
|
15
|
-
parents = [
|
14
|
+
def visible_from? n
|
15
|
+
parents = [n] + (source.tree_for(n) || [])
|
16
16
|
parents.each do |p|
|
17
17
|
return true if @tree[0] == p
|
18
18
|
return false if [:def, :defs, :class, :module].include?(p.type)
|
@@ -1,51 +1,79 @@
|
|
1
1
|
module Solargraph
|
2
2
|
class Source
|
3
3
|
class Fragment
|
4
|
-
# @return [Integer]
|
5
|
-
attr_reader :offset
|
6
|
-
|
7
4
|
include NodeMethods
|
8
5
|
|
6
|
+
attr_reader :tree
|
7
|
+
|
8
|
+
attr_reader :line
|
9
|
+
|
10
|
+
attr_reader :column
|
11
|
+
|
9
12
|
# @param source [Solargraph::Source]
|
10
|
-
# @param
|
11
|
-
|
13
|
+
# @param line [Integer]
|
14
|
+
# @param column [Integer]
|
15
|
+
def initialize source, line, column, tree
|
12
16
|
# @todo Split this object from the source. The source can change; if
|
13
17
|
# it does, this object's data should not.
|
14
18
|
@source = source
|
15
19
|
@code = source.code
|
16
|
-
@
|
20
|
+
@line = line
|
21
|
+
@column = column
|
22
|
+
@tree = tree
|
17
23
|
end
|
18
24
|
|
19
25
|
# Get the node at the current offset.
|
20
26
|
#
|
21
27
|
# @return [Parser::AST::Node]
|
22
28
|
def node
|
23
|
-
@node ||= @
|
29
|
+
@node ||= @tree.first
|
24
30
|
end
|
25
31
|
|
26
32
|
# Get the fully qualified namespace at the current offset.
|
27
33
|
#
|
28
34
|
# @return [String]
|
29
35
|
def namespace
|
36
|
+
# if @namespace.nil?
|
37
|
+
# base = @source.parent_node_from(line, column, :class, :module, :def, :defs)
|
38
|
+
# @namespace ||= @source.namespace_for(base)
|
39
|
+
# end
|
40
|
+
# @namespace
|
30
41
|
if @namespace.nil?
|
31
|
-
|
32
|
-
@
|
42
|
+
parts = []
|
43
|
+
@tree.each do |n|
|
44
|
+
next unless n.kind_of?(AST::Node)
|
45
|
+
if n.type == :class or n.type == :module
|
46
|
+
parts.unshift unpack_name(n.children[0])
|
47
|
+
end
|
48
|
+
end
|
49
|
+
@namespace = parts.join('::')
|
33
50
|
end
|
34
51
|
@namespace
|
35
52
|
end
|
36
53
|
|
54
|
+
# @return [Boolean]
|
55
|
+
def argument?
|
56
|
+
@argument ||= !signature_position.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
# @return [Fragment]
|
60
|
+
def recipient
|
61
|
+
return nil if signature_position.nil?
|
62
|
+
@recipient ||= @source.fragment_at(*signature_position)
|
63
|
+
end
|
64
|
+
|
37
65
|
# Get the scope at the current offset.
|
38
66
|
#
|
39
67
|
# @return [Symbol] :class or :instance
|
40
68
|
def scope
|
41
69
|
if @scope.nil?
|
42
70
|
@scope = :class
|
43
|
-
tree = @
|
71
|
+
tree = @tree.clone
|
44
72
|
until tree.empty?
|
45
73
|
cursor = tree.shift
|
46
74
|
break if cursor.type == :class or cursor.type == :module
|
47
75
|
if cursor.type == :def
|
48
|
-
pin = @source.method_pins.select{|pin| pin.contain?(
|
76
|
+
pin = @source.method_pins.select{|pin| pin.contain?(offset)}.first
|
49
77
|
# @todo The pin should never be nil here, but we're guarding it just in case
|
50
78
|
@scope = (pin.nil? ? :instance : pin.scope)
|
51
79
|
end
|
@@ -93,7 +121,7 @@ module Solargraph
|
|
93
121
|
#
|
94
122
|
# @return [String]
|
95
123
|
def remainder
|
96
|
-
@remainder ||= remainder_at(
|
124
|
+
@remainder ||= remainder_at(offset)
|
97
125
|
end
|
98
126
|
|
99
127
|
# Get the whole word at the current offset, including the remainder.
|
@@ -118,28 +146,29 @@ module Solargraph
|
|
118
146
|
#
|
119
147
|
# @return [String]
|
120
148
|
def phrase
|
121
|
-
@phrase ||= @code[signature_data[0]
|
149
|
+
@phrase ||= @code[signature_data[0]..offset]
|
122
150
|
end
|
123
151
|
|
124
152
|
# Get the word before the current offset. Given the text `foo.bar`, the
|
125
153
|
# word at offset 6 is `ba`.
|
126
154
|
def word
|
127
|
-
@word ||= word_at(
|
155
|
+
@word ||= word_at(offset)
|
128
156
|
end
|
129
157
|
|
130
158
|
# True if the current offset is inside a string.
|
131
159
|
#
|
132
160
|
# @return [Boolean]
|
133
161
|
def string?
|
134
|
-
@string = @source.string_at?(
|
135
|
-
@string
|
162
|
+
# @string = @source.string_at?(offset) if @string.nil?
|
163
|
+
# @string
|
164
|
+
@string ||= (node.type == :str or node.type == :dstr)
|
136
165
|
end
|
137
166
|
|
138
167
|
# True if the current offset is inside a comment.
|
139
168
|
#
|
140
169
|
# @return [Boolean]
|
141
170
|
def comment?
|
142
|
-
@comment = get_comment_at(
|
171
|
+
@comment = get_comment_at(offset) if @comment.nil?
|
143
172
|
@comment
|
144
173
|
end
|
145
174
|
|
@@ -147,7 +176,7 @@ module Solargraph
|
|
147
176
|
#
|
148
177
|
# @return [Range]
|
149
178
|
def word_range
|
150
|
-
@word_range ||= word_range_at(
|
179
|
+
@word_range ||= word_range_at(offset, false)
|
151
180
|
end
|
152
181
|
|
153
182
|
# Get the range of the whole word at the current offset, including its
|
@@ -155,7 +184,7 @@ module Solargraph
|
|
155
184
|
#
|
156
185
|
# @return [Range]
|
157
186
|
def whole_word_range
|
158
|
-
@whole_word_range ||= word_range_at(
|
187
|
+
@whole_word_range ||= word_range_at(offset, true)
|
159
188
|
end
|
160
189
|
|
161
190
|
# Get an array of all the local variables in the source that are visible
|
@@ -168,8 +197,23 @@ module Solargraph
|
|
168
197
|
|
169
198
|
private
|
170
199
|
|
200
|
+
# @return [Integer]
|
201
|
+
def offset
|
202
|
+
if @offset.nil?
|
203
|
+
@offset = 0
|
204
|
+
feed = 0
|
205
|
+
@code.lines.each { |l|
|
206
|
+
break if line == feed
|
207
|
+
@offset += l.length
|
208
|
+
feed += 1
|
209
|
+
}
|
210
|
+
@offset += column
|
211
|
+
end
|
212
|
+
@offset
|
213
|
+
end
|
214
|
+
|
171
215
|
def signature_data
|
172
|
-
@signature_data ||= get_signature_data_at(
|
216
|
+
@signature_data ||= get_signature_data_at(offset)
|
173
217
|
end
|
174
218
|
|
175
219
|
def get_signature_data_at index
|
@@ -184,7 +228,7 @@ module Solargraph
|
|
184
228
|
unless !in_whitespace and string?
|
185
229
|
break if brackets > 0 or parens > 0 or squares > 0
|
186
230
|
char = @code[index, 1]
|
187
|
-
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\n", "\t"].include?(char)
|
231
|
+
if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
|
188
232
|
in_whitespace = true
|
189
233
|
else
|
190
234
|
if brackets.zero? and parens.zero? and squares.zero? and in_whitespace
|
@@ -224,24 +268,30 @@ module Solargraph
|
|
224
268
|
index -= 1
|
225
269
|
end
|
226
270
|
if signature.start_with?('.')
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
271
|
+
# @todo Smelly exceptional case for arrays
|
272
|
+
if signature.start_with?('.[].')
|
273
|
+
signature.sub!(/^\.\[\]/, 'Array.new')
|
274
|
+
else
|
275
|
+
line, col = get_position_at(index - 1)
|
276
|
+
pn = @source.node_at(line, col)
|
277
|
+
unless pn.nil?
|
278
|
+
literal = infer_literal_node_type(pn)
|
279
|
+
unless literal.nil?
|
280
|
+
signature = "#{literal}.new#{signature}"
|
281
|
+
# @todo Determine the index from the beginning of the literal node?
|
282
|
+
end
|
233
283
|
end
|
234
284
|
end
|
235
285
|
end
|
236
286
|
[index + 1, signature]
|
237
|
-
end
|
287
|
+
end
|
238
288
|
|
239
289
|
# Determine if the specified index is inside a comment.
|
240
290
|
#
|
241
291
|
# @return [Boolean]
|
242
292
|
def get_comment_at(index)
|
243
293
|
return false if string?
|
244
|
-
line, col =
|
294
|
+
# line, col = get_position_at(index)
|
245
295
|
@source.comments.each do |c|
|
246
296
|
return true if index > c.location.expression.begin_pos and index <= c.location.expression.end_pos
|
247
297
|
end
|
@@ -298,8 +348,8 @@ module Solargraph
|
|
298
348
|
end
|
299
349
|
end_offset = cursor
|
300
350
|
end_offset = start_offset if end_offset < start_offset
|
301
|
-
start_pos =
|
302
|
-
end_pos =
|
351
|
+
start_pos = get_position_at(start_offset)
|
352
|
+
end_pos = get_position_at(end_offset)
|
303
353
|
Solargraph::Source::Range.from_to(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
304
354
|
end
|
305
355
|
|
@@ -314,6 +364,56 @@ module Solargraph
|
|
314
364
|
@code[index..cursor-1]
|
315
365
|
end
|
316
366
|
|
367
|
+
def get_position_at(offset)
|
368
|
+
cursor = 0
|
369
|
+
line = 0
|
370
|
+
col = nil
|
371
|
+
@code.lines.each do |l|
|
372
|
+
if cursor + l.length > offset
|
373
|
+
col = offset - cursor
|
374
|
+
break
|
375
|
+
end
|
376
|
+
if cursor + l.length == offset
|
377
|
+
if l.end_with?("\n")
|
378
|
+
col = 0
|
379
|
+
line += 1
|
380
|
+
break
|
381
|
+
else
|
382
|
+
col = l.length
|
383
|
+
break
|
384
|
+
end
|
385
|
+
end
|
386
|
+
if cursor + l.length - 1 == offset and !l.end_with?("\n")
|
387
|
+
col = l.length - 1
|
388
|
+
break
|
389
|
+
end
|
390
|
+
cursor += l.length
|
391
|
+
line += 1
|
392
|
+
end
|
393
|
+
raise "Invalid offset" if col.nil?
|
394
|
+
[line, col]
|
395
|
+
end
|
396
|
+
|
397
|
+
def signature_position
|
398
|
+
if @signature_position.nil?
|
399
|
+
open_parens = 0
|
400
|
+
cursor = offset - 1
|
401
|
+
while cursor >= 0
|
402
|
+
break if cursor < 0
|
403
|
+
if @code[cursor] == ')'
|
404
|
+
open_parens -= 1
|
405
|
+
elsif @code[cursor] == '('
|
406
|
+
open_parens += 1
|
407
|
+
end
|
408
|
+
break if open_parens == 1
|
409
|
+
cursor -= 1
|
410
|
+
end
|
411
|
+
if cursor >= 0
|
412
|
+
@signature_position = get_position_at(cursor)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
@signature_position
|
416
|
+
end
|
317
417
|
end
|
318
418
|
end
|
319
419
|
end
|
data/lib/solargraph/source.rb
CHANGED
@@ -142,20 +142,12 @@ module Solargraph
|
|
142
142
|
@node_tree[node] || []
|
143
143
|
end
|
144
144
|
|
145
|
-
# Determine if the specified index is inside a string.
|
146
|
-
#
|
147
|
-
# @return [Boolean]
|
148
|
-
def string_at?(index)
|
149
|
-
n = node_at(index)
|
150
|
-
n.kind_of?(AST::Node) and (n.type == :str or n.type == :dstr)
|
151
|
-
end
|
152
|
-
|
153
145
|
# Get the nearest node that contains the specified index.
|
154
146
|
#
|
155
147
|
# @param index [Integer]
|
156
148
|
# @return [AST::Node]
|
157
|
-
def node_at(
|
158
|
-
tree_at(
|
149
|
+
def node_at(line, column)
|
150
|
+
tree_at(line, column).first
|
159
151
|
end
|
160
152
|
|
161
153
|
# Get an array of nodes containing the specified index, starting with the
|
@@ -163,11 +155,22 @@ module Solargraph
|
|
163
155
|
#
|
164
156
|
# @param index [Integer]
|
165
157
|
# @return [Array<AST::Node>]
|
166
|
-
def tree_at(
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
158
|
+
def tree_at(line, column)
|
159
|
+
offset = get_parsed_offset(line, column)
|
160
|
+
@all_nodes.reverse.each do |n|
|
161
|
+
if n.respond_to?(:loc)
|
162
|
+
if n.respond_to?(:begin) and n.respond_to?(:end)
|
163
|
+
if offset >= n.begin.begin_pos and offset < n.end.end_pos
|
164
|
+
return [n] + @node_tree[n]
|
165
|
+
end
|
166
|
+
elsif !n.loc.expression.nil?
|
167
|
+
if offset >= n.loc.expression.begin_pos and offset < n.loc.expression.end_pos
|
168
|
+
return [n] + @node_tree[n]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
[@node]
|
171
174
|
end
|
172
175
|
|
173
176
|
# Find the nearest parent node from the specified index. If one or more
|
@@ -176,8 +179,8 @@ module Solargraph
|
|
176
179
|
# @param index [Integer]
|
177
180
|
# @param types [Array<Symbol>]
|
178
181
|
# @return [AST::Node]
|
179
|
-
def parent_node_from(
|
180
|
-
arr = tree_at(
|
182
|
+
def parent_node_from(line, column, *types)
|
183
|
+
arr = tree_at(line, column)
|
181
184
|
arr.each { |a|
|
182
185
|
if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
|
183
186
|
return a
|
@@ -219,20 +222,6 @@ module Solargraph
|
|
219
222
|
self
|
220
223
|
end
|
221
224
|
|
222
|
-
def get_offset line, col
|
223
|
-
Source.get_offset(code, line, col)
|
224
|
-
end
|
225
|
-
|
226
|
-
def self.get_offset text, line, col
|
227
|
-
offset = 0
|
228
|
-
if line > 0
|
229
|
-
text.lines[0..line - 1].each { |l|
|
230
|
-
offset += l.length
|
231
|
-
}
|
232
|
-
end
|
233
|
-
offset + col
|
234
|
-
end
|
235
|
-
|
236
225
|
def overwrite text
|
237
226
|
reparse({'text' => text})
|
238
227
|
end
|
@@ -242,7 +231,7 @@ module Solargraph
|
|
242
231
|
down = query.downcase
|
243
232
|
all_symbols.select{|p| p.path.downcase.include?(down)}
|
244
233
|
end
|
245
|
-
|
234
|
+
|
246
235
|
def all_symbols
|
247
236
|
result = []
|
248
237
|
result.concat namespace_pins
|
@@ -258,43 +247,44 @@ module Solargraph
|
|
258
247
|
|
259
248
|
# @return [Solargraph::Source::Fragment]
|
260
249
|
def fragment_at line, column
|
261
|
-
Fragment.new(self,
|
250
|
+
# Fragment.new(self, line, column)
|
251
|
+
Fragment.new(self, line, column, tree_at(line, column))
|
262
252
|
end
|
263
253
|
|
264
254
|
private
|
265
255
|
|
266
|
-
def
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
arr.unshift c
|
274
|
-
end
|
275
|
-
elsif index < c.loc.expression.end_pos
|
276
|
-
arr.unshift c
|
277
|
-
end
|
278
|
-
end
|
279
|
-
end
|
280
|
-
inner_node_at(index, c, arr)
|
281
|
-
end
|
256
|
+
def get_offset line, column, parsed
|
257
|
+
offset = 0
|
258
|
+
feed = 0
|
259
|
+
(parsed ? @code.gsub(/\r\n/, "\n") : @code).lines.each do |l|
|
260
|
+
break if line == feed
|
261
|
+
offset += l.length
|
262
|
+
feed += 1
|
282
263
|
end
|
264
|
+
offset + column
|
265
|
+
end
|
266
|
+
|
267
|
+
def get_original_offset line, column
|
268
|
+
get_offset line, column, false
|
269
|
+
end
|
270
|
+
|
271
|
+
def get_parsed_offset line, column
|
272
|
+
get_offset line, column, true
|
283
273
|
end
|
284
274
|
|
285
275
|
def reparse change
|
286
276
|
if change['range']
|
287
|
-
start_offset =
|
288
|
-
end_offset =
|
277
|
+
start_offset = get_original_offset(change['range']['start']['line'], change['range']['start']['character'])
|
278
|
+
end_offset = get_original_offset(change['range']['end']['line'], change['range']['end']['character'])
|
289
279
|
rewrite = (start_offset == 0 ? '' : @code[0..start_offset-1].to_s) + change['text'].force_encoding('utf-8') + @code[end_offset..-1].to_s
|
290
280
|
# return if @code == rewrite
|
291
281
|
again = true
|
292
|
-
if change['text'].match(/^[^a-z0-9\s]+?$/i)
|
293
|
-
|
294
|
-
|
295
|
-
else
|
282
|
+
# if change['text'].match(/^[^a-z0-9\s]+?$/i)
|
283
|
+
# tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
|
284
|
+
# again = false
|
285
|
+
# else
|
296
286
|
tmp = rewrite
|
297
|
-
end
|
287
|
+
# end
|
298
288
|
@code = rewrite
|
299
289
|
begin
|
300
290
|
node, comments = Source.parse(tmp, filename)
|
@@ -623,35 +613,35 @@ module Solargraph
|
|
623
613
|
end
|
624
614
|
end
|
625
615
|
|
626
|
-
def get_position_at(code, offset)
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
end
|
616
|
+
# def get_position_at(code, offset)
|
617
|
+
# cursor = 0
|
618
|
+
# line = 0
|
619
|
+
# col = nil
|
620
|
+
# code.each_line do |l|
|
621
|
+
# if cursor + l.length > offset
|
622
|
+
# col = offset - cursor
|
623
|
+
# break
|
624
|
+
# end
|
625
|
+
# if cursor + l.length == offset
|
626
|
+
# if l.end_with?("\n")
|
627
|
+
# col = 0
|
628
|
+
# line += 1
|
629
|
+
# break
|
630
|
+
# else
|
631
|
+
# col = l.length
|
632
|
+
# break
|
633
|
+
# end
|
634
|
+
# end
|
635
|
+
# # if cursor + l.length - 1 == offset and !l.end_with?("\n")
|
636
|
+
# # col = l.length - 1
|
637
|
+
# # break
|
638
|
+
# # end
|
639
|
+
# cursor += l.length
|
640
|
+
# line += 1
|
641
|
+
# end
|
642
|
+
# raise "Invalid offset" if col.nil?
|
643
|
+
# [line, col]
|
644
|
+
# end
|
655
645
|
|
656
646
|
def parse code, filename = nil
|
657
647
|
parser = Parser::CurrentRuby.new(FlawedBuilder.new)
|
data/lib/solargraph/version.rb
CHANGED
data/lib/solargraph.rb
CHANGED
@@ -19,6 +19,7 @@ module Solargraph
|
|
19
19
|
autoload :Workspace, 'solargraph/workspace'
|
20
20
|
autoload :Page, 'solargraph/page'
|
21
21
|
autoload :Library, 'solargraph/library'
|
22
|
+
autoload :Diagnostics, 'solargraph/diagnostics'
|
22
23
|
|
23
24
|
YARDOC_PATH = File.join(File.realpath(File.dirname(__FILE__)), '..', 'yardoc')
|
24
25
|
YARD_EXTENSION_FILE = File.join(File.realpath(File.dirname(__FILE__)), 'yard-solargraph.rb')
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: solargraph
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.18.
|
4
|
+
version: 0.18.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fred Snyder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-04-
|
11
|
+
date: 2018-04-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: parser
|
@@ -287,6 +287,8 @@ files:
|
|
287
287
|
- lib/solargraph/api_map/completion.rb
|
288
288
|
- lib/solargraph/api_map/source_to_yard.rb
|
289
289
|
- lib/solargraph/core_fills.rb
|
290
|
+
- lib/solargraph/diagnostics.rb
|
291
|
+
- lib/solargraph/diagnostics/rubocop.rb
|
290
292
|
- lib/solargraph/language_server.rb
|
291
293
|
- lib/solargraph/language_server/completion_item_kinds.rb
|
292
294
|
- lib/solargraph/language_server/error_codes.rb
|