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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a227df553c29a37fd42a7e7c1ed0a41f2c064dc2
4
- data.tar.gz: f57a9a06b0495086c981cb2f8bbf29ffaab2c3a3
3
+ metadata.gz: 642ac02a775d6f81c76137c676057b4fefe524c2
4
+ data.tar.gz: 79357368d35da0297313c3085aa9127f333d14b3
5
5
  SHA512:
6
- metadata.gz: 9e8831205c0924300465d6b40ed210bfc865ba1315ef98ab2d7bfe74f0b56b0e0386a43e0a17cf131a60e1abe745a4355b463f33e2ae0ec5fec98ab4d7f2bcac
7
- data.tar.gz: ee879308f132f039e0721afe9218d51566402227409e7e86034bb918bf0f1ada9503ae1912d0b1b2c733ba922bc8087b52db7177918ff025598147fe8bc69c8b
6
+ metadata.gz: f4a69583c1cf3962a447cfb2e7e512de1046ca013aab9ebfffed46d87f4551dec1b9aacbcc9d04dc41c9fd61dcdad0383ffeecf8cb6d0818f781d04630b29893
7
+ data.tar.gz: f47ff7d37744cfba9572aee11539710d886c4e1e7a6299f82a977d1e2d036a7398ad1d0b622fe0f456c355efe1fde94d4774e0493c91ba46202437c50f961351
@@ -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 (@ivar_pins[namespace] || []).select{ |pin| pin.scope == scope }
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
- matches = []
306
+ return @virtual_source if !@virtual_source.nil? and @virtual_source.include?(node)
307
307
  @sources.each do |source|
308
- matches.push source if source.include?(node)
308
+ return source if source.include?(node)
309
309
  end
310
- matches.first
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 [] if pins.empty?
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
- # Identify the variable, constant, or method call at the fragment's location.
442
- #
443
- # @param fragment [Solargraph::Source::Fragment]
444
- # @return [Array<Solargraph::Pin::Base>]
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 nil if type.nil?
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
@@ -0,0 +1,5 @@
1
+ module Solargraph
2
+ module Diagnostics
3
+ autoload :Rubocop, 'solargraph/diagnostics/rubocop'
4
+ end
5
+ 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
- resp = read_diagnostics(current)
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.synchronize do
14
- if host.changing?(params['textDocument']['uri'])
15
- # STDERR.puts "Waiting..."
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
- set_error ErrorCodes::INTERNAL_ERROR, e.message
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.library.completions_at(filename, line, col)
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
- filename = uri_to_file(params['textDocument']['uri'])
7
- line = params['position']['line']
8
- col = params['position']['character']
9
- suggestions = host.library.definitions_at(filename, line, col)
10
- contents = suggestions.map(&:hover)
11
- set_result(
12
- contents: {
13
- kind: 'markdown',
14
- value: contents.join("\n\n")
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.library.signatures_at(filename, line, col)
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
 
@@ -74,7 +74,7 @@ module Solargraph
74
74
  # @type [Solargraph::Source]
75
75
  source = nil
76
76
  source = read(filename)
77
- fragment = Solargraph::Source::Fragment.new(source, source.get_offset(line, column))
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 = Solargraph::Source::Fragment.new(source, source.get_offset(line, column))
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 = Solargraph::Source::Fragment.new(source, signature_index_before(source, source.get_offset(line, column)))
105
- api_map.define(fragment).select{|pin| pin.method?}
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
@@ -34,7 +34,7 @@ module Solargraph
34
34
  end
35
35
 
36
36
  def nil_assignment?
37
- assignment_node.type == :nil
37
+ assignment_node.nil? or assignment_node.type == :nil
38
38
  end
39
39
 
40
40
  def signature
@@ -26,6 +26,7 @@ module Solargraph
26
26
  completion_item.merge(extra)
27
27
  end
28
28
 
29
+ # @todo Candidate for deprecation
29
30
  # @param api_map [Solargraph::ApiMap]
30
31
  def hover
31
32
  info = ''
@@ -11,8 +11,8 @@ module Solargraph
11
11
  end
12
12
  end
13
13
 
14
- def visible_from? node
15
- parents = [node] + (source.tree_for(node) || [])
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 offset [Integer]
11
- def initialize source, offset
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
- @offset = offset
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 ||= @source.node_at(@offset)
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
- base = @source.parent_node_from(@offset, :class, :module, :def, :defs)
32
- @namespace ||= @source.namespace_for(base)
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 = @source.tree_at(@offset)
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?(@offset)}.first
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(@offset)
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]..@offset]
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(@offset)
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?(@offset) if @string.nil?
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(@offset) if @comment.nil?
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(@offset, false)
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(@offset, true)
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(@offset)
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
- pn = @source.node_at(index)
228
- unless pn.nil?
229
- literal = infer_literal_node_type(pn)
230
- unless literal.nil?
231
- signature = "#{literal}.new#{signature}"
232
- # @todo Determine the index from the beginning of the literal node?
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 = Solargraph::Source.get_position_at(@source.code, index)
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 = Solargraph::Source.get_position_at(@code, start_offset)
302
- end_pos = Solargraph::Source.get_position_at(@code, end_offset)
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
@@ -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(index)
158
- tree_at(index).first
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(index)
167
- arr = []
168
- arr.push @node
169
- inner_node_at(index, @node, arr)
170
- arr
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(index, *types)
180
- arr = tree_at(index)
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, get_offset(line, column))
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 inner_node_at(index, node, arr)
267
- node.children.each do |c|
268
- if c.kind_of?(AST::Node) and c.respond_to?(:loc)
269
- unless c.loc.expression.nil?
270
- if index >= c.loc.expression.begin_pos
271
- if c.respond_to?(:end)
272
- if index < c.end.end_pos
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 = Source.get_offset(@code, change['range']['start']['line'], change['range']['start']['character'])
288
- end_offset = Source.get_offset(@code, change['range']['end']['line'], change['range']['end']['character'])
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
- tmp = (start_offset == 0 ? '' : @fixed[0..start_offset-1].to_s) + change['text'].gsub(/[^\s]/, ' ') + @fixed[end_offset..-1].to_s
294
- again = false
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
- cursor = 0
628
- line = 0
629
- col = nil
630
- code.each_line do |l|
631
- if cursor + l.length > offset
632
- col = offset - cursor
633
- break
634
- end
635
- if cursor + l.length == offset
636
- if l.end_with?("\n")
637
- col = 0
638
- line += 1
639
- break
640
- else
641
- col = l.length
642
- break
643
- end
644
- end
645
- # if cursor + l.length - 1 == offset and !l.end_with?("\n")
646
- # col = l.length - 1
647
- # break
648
- # end
649
- cursor += l.length
650
- line += 1
651
- end
652
- raise "Invalid offset" if col.nil?
653
- [line, col]
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)
@@ -1,3 +1,3 @@
1
1
  module Solargraph
2
- VERSION = '0.18.1'
2
+ VERSION = '0.18.2'
3
3
  end
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.1
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-05 00:00:00.000000000 Z
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