solargraph 0.18.1 → 0.18.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|