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 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