solargraph 0.18.2 → 0.18.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +33 -28
  3. data/lib/solargraph/api_map.rb +997 -1044
  4. data/lib/solargraph/api_map/source_to_yard.rb +4 -3
  5. data/lib/solargraph/diagnostics/rubocop.rb +4 -3
  6. data/lib/solargraph/language_server/host.rb +140 -70
  7. data/lib/solargraph/language_server/message/base.rb +1 -0
  8. data/lib/solargraph/language_server/message/client.rb +6 -2
  9. data/lib/solargraph/language_server/message/text_document/completion.rb +34 -39
  10. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  11. data/lib/solargraph/language_server/message/text_document/did_close.rb +1 -0
  12. data/lib/solargraph/language_server/message/text_document/did_save.rb +1 -3
  13. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +1 -1
  14. data/lib/solargraph/language_server/message/text_document/hover.rb +25 -30
  15. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +1 -1
  16. data/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +8 -7
  17. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +1 -1
  18. data/lib/solargraph/language_server/transport/socket.rb +15 -17
  19. data/lib/solargraph/library.rb +34 -16
  20. data/lib/solargraph/node_methods.rb +96 -96
  21. data/lib/solargraph/pin.rb +1 -0
  22. data/lib/solargraph/pin/base.rb +2 -1
  23. data/lib/solargraph/pin/base_variable.rb +45 -5
  24. data/lib/solargraph/pin/block_parameter.rb +5 -2
  25. data/lib/solargraph/pin/method.rb +22 -0
  26. data/lib/solargraph/pin/namespace.rb +32 -2
  27. data/lib/solargraph/pin/reference.rb +21 -0
  28. data/lib/solargraph/pin/yard_object.rb +9 -0
  29. data/lib/solargraph/shell.rb +136 -136
  30. data/lib/solargraph/source.rb +134 -188
  31. data/lib/solargraph/source/change.rb +70 -0
  32. data/lib/solargraph/source/fragment.rb +120 -66
  33. data/lib/solargraph/source/position.rb +41 -0
  34. data/lib/solargraph/source/updater.rb +48 -0
  35. data/lib/solargraph/version.rb +3 -3
  36. data/lib/solargraph/workspace/config.rb +4 -9
  37. data/lib/solargraph/yard_map/core_docs.rb +0 -1
  38. metadata +5 -2
@@ -0,0 +1,70 @@
1
+ module Solargraph
2
+ class Source
3
+ # A change to be applied to text.
4
+ #
5
+ class Change
6
+ # @return [Range]
7
+ attr_reader :range
8
+
9
+ # @return [String]
10
+ attr_reader :new_text
11
+
12
+ # @param range [Range] The starting and ending positions of the change.
13
+ # If nil, the original text will be overwritten.
14
+ # @param new_text [String] The text to be changed.
15
+ def initialize range, new_text
16
+ @range = range
17
+ @new_text = new_text
18
+ end
19
+
20
+ # Write the change to the specified text.
21
+ #
22
+ # @param text [String] The text to be changed.
23
+ # @param nullable [Boolean] If true, minor changes that could generate
24
+ # syntax errors will be repaired.
25
+ # @return [String] The updated text.
26
+ def write text, nullable = false
27
+ if nullable and !range.nil? and new_text.match(/[\.\[\{\(@\$:]$/)
28
+ commit text, "#{new_text[0..-2]} "
29
+ elsif range.nil?
30
+ new_text
31
+ else
32
+ commit text, new_text
33
+ end
34
+ end
35
+
36
+ # Repair an update by replacing the new text with similarly formatted
37
+ # whitespace.
38
+ #
39
+ # @param text [String] The text to be changed.
40
+ # @return [String] The updated text.
41
+ def repair text
42
+ fixed = new_text.gsub(/[^\s]/, ' ')
43
+ if range.nil?
44
+ fixed
45
+ else
46
+ commit text, fixed
47
+ end
48
+ end
49
+
50
+ private
51
+
52
+ def commit text, insert
53
+ start_offset = get_offset(text, range.start.line, range.start.character)
54
+ end_offset = get_offset(text, range.end.line, range.end.character)
55
+ (start_offset == 0 ? '' : text[0..start_offset-1].to_s) + insert.force_encoding('utf-8') + text[end_offset..-1].to_s
56
+ end
57
+
58
+ def get_offset text, line, column
59
+ offset = 0
60
+ feed = 0
61
+ text.lines.each do |l|
62
+ break if line == feed
63
+ offset += l.length
64
+ feed += 1
65
+ end
66
+ offset + column
67
+ end
68
+ end
69
+ end
70
+ end
@@ -33,11 +33,6 @@ module Solargraph
33
33
  #
34
34
  # @return [String]
35
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
41
36
  if @namespace.nil?
42
37
  parts = []
43
38
  @tree.each do |n|
@@ -56,6 +51,17 @@ module Solargraph
56
51
  @argument ||= !signature_position.nil?
57
52
  end
58
53
 
54
+ def chained?
55
+ if @chained.nil?
56
+ @chained = false
57
+ @tree.each do |n|
58
+ @chained = true if n.type == :send
59
+ break
60
+ end
61
+ end
62
+ @chained
63
+ end
64
+
59
65
  # @return [Fragment]
60
66
  def recipient
61
67
  return nil if signature_position.nil?
@@ -90,6 +96,14 @@ module Solargraph
90
96
  @signature ||= signature_data[1]
91
97
  end
92
98
 
99
+ def valid?
100
+ @source.parsed?
101
+ end
102
+
103
+ def broken?
104
+ !valid?
105
+ end
106
+
93
107
  # Get the signature before the current word. Given the signature
94
108
  # `String.new.split`, the base is `String.new`.
95
109
  #
@@ -116,6 +130,26 @@ module Solargraph
116
130
  @base
117
131
  end
118
132
 
133
+ # @return [String]
134
+ def root
135
+ @root ||= signature.split('.').first
136
+ end
137
+
138
+ # @return [String]
139
+ def chain
140
+ @chain ||= signature.split('.')[1..-1].join('.')
141
+ end
142
+
143
+ # @return [String]
144
+ def base_chain
145
+ @base_chain ||= signature.split('.')[1..-2].join('.')
146
+ end
147
+
148
+ # @return [String]
149
+ def whole_chain
150
+ @whole_chain ||= whole_signature.split('.')[1..-1].join('.')
151
+ end
152
+
119
153
  # Get the remainder of the word after the current offset. Given the text
120
154
  # `foobar` with an offset of 3, the remainder is `bar`.
121
155
  #
@@ -159,8 +193,6 @@ module Solargraph
159
193
  #
160
194
  # @return [Boolean]
161
195
  def string?
162
- # @string = @source.string_at?(offset) if @string.nil?
163
- # @string
164
196
  @string ||= (node.type == :str or node.type == :dstr)
165
197
  end
166
198
 
@@ -191,25 +223,91 @@ module Solargraph
191
223
  # from the current offset.
192
224
  #
193
225
  # @return [Array<Solargraph::Pin::LocalVariable>]
194
- def local_variable_pins
195
- @local_variable_pins ||= @source.local_variable_pins.select{|pin| pin.visible_from?(node)}
226
+ def local_variable_pins name = nil
227
+ @local_variable_pins ||= prefer_non_nil_variables(@source.local_variable_pins.select{|pin| pin.visible_from?(node)})
228
+ return @local_variable_pins if name.nil?
229
+ @local_variable_pins.select{|pin| pin.name == name}
230
+ end
231
+
232
+ def calculated_signature
233
+ @calculated_signature ||= calculate
234
+ end
235
+
236
+ def calculated_whole_signature
237
+ @calculated_whole_signature ||= calculated_signature + remainder
238
+ end
239
+
240
+ def calculated_base
241
+ if @calculated_base.nil?
242
+ @calculated_base = calculated_signature[0..-2] if calculated_signature.end_with?('.')
243
+ @calculated_base ||= calculated_signature.split('.')[0..-2].join('.')
244
+ end
245
+ @calculated_base
196
246
  end
197
247
 
198
248
  private
199
249
 
250
+ def calculate
251
+ return signature if signature.empty? or signature.nil?
252
+ if signature.start_with?('.')
253
+ return signature if column < 2
254
+ # @todo Smelly exceptional case for arrays
255
+ return signature.sub(/^\.\[\]/, 'Array.new') if signature.start_with?('.[].')
256
+ pn = @source.node_at(line, column - 2)
257
+ unless pn.nil?
258
+ literal = infer_literal_node_type(pn)
259
+ unless literal.nil?
260
+ return "#{literal}.new#{signature}"
261
+ end
262
+ end
263
+ return signature
264
+ end
265
+ # @todo Smelly exceptional case for integers
266
+ base, rest = signature.split('.', 2)
267
+ base.sub!(/^[0-9]+?$/, 'Integer.new')
268
+ var = local_variable_pins(base).first
269
+ unless var.nil?
270
+ done = []
271
+ until var.nil?
272
+ break if done.include?(var)
273
+ done.push var
274
+ type = var.calculated_signature
275
+ break if type.nil?
276
+ base = type
277
+ var = local_variable_pins(base).first
278
+ end
279
+ end
280
+ base = @source.qualify(base, namespace)
281
+ base + (rest.nil? ? '' : ".#{rest}")
282
+ end
283
+
284
+ # @todo DRY this method. It exists in ApiMap.
285
+ # @return [Array<Solargraph::Pin::Base>]
286
+ def prefer_non_nil_variables pins
287
+ result = []
288
+ nil_pins = []
289
+ pins.each do |pin|
290
+ if pin.nil_assignment? and pin.return_type.nil?
291
+ nil_pins.push pin
292
+ else
293
+ result.push pin
294
+ end
295
+ end
296
+ result + nil_pins
297
+ end
298
+
200
299
  # @return [Integer]
201
300
  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
301
+ @offset ||= get_offset(line, column)
302
+ end
303
+
304
+ def get_offset line, column
305
+ Position.line_char_to_offset(@code, line, column)
306
+ end
307
+
308
+ def get_position_at(offset)
309
+ pos = Position.from_offset(@code, offset)
310
+ [pos.line, pos.character]
213
311
  end
214
312
 
215
313
  def signature_data
@@ -228,6 +326,7 @@ module Solargraph
228
326
  unless !in_whitespace and string?
229
327
  break if brackets > 0 or parens > 0 or squares > 0
230
328
  char = @code[index, 1]
329
+ break if char.nil? # @todo Is this the right way to handle this?
231
330
  if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char)
232
331
  in_whitespace = true
233
332
  else
@@ -267,22 +366,7 @@ module Solargraph
267
366
  end
268
367
  index -= 1
269
368
  end
270
- if signature.start_with?('.')
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
283
- end
284
- end
285
- end
369
+ # @todo Smelly exceptional case for numbers
286
370
  [index + 1, signature]
287
371
  end
288
372
 
@@ -364,36 +448,6 @@ module Solargraph
364
448
  @code[index..cursor-1]
365
449
  end
366
450
 
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
451
  def signature_position
398
452
  if @signature_position.nil?
399
453
  open_parens = 0
@@ -1,4 +1,5 @@
1
1
  module Solargraph
2
+
2
3
  class Source
3
4
  class Position
4
5
  # @return [Integer]
@@ -21,6 +22,46 @@ module Solargraph
21
22
  character: character
22
23
  }
23
24
  end
25
+
26
+ def self.to_offset text, position
27
+ result = 0
28
+ feed = 0
29
+ line = position.line
30
+ column = position.character
31
+ text.lines.each do |l|
32
+ line_length = l.length
33
+ char_length = l.chomp.length
34
+ if feed == line
35
+ result += column
36
+ break
37
+ end
38
+ result += line_length
39
+ feed += 1
40
+ end
41
+ result
42
+ end
43
+
44
+ def self.line_char_to_offset text, line, character
45
+ to_offset(text, Position.new(line, character))
46
+ end
47
+
48
+ def self.from_offset text, offset
49
+ cursor = 0
50
+ line = 0
51
+ character = nil
52
+ text.lines.each do |l|
53
+ line_length = l.length
54
+ char_length = l.chomp.length
55
+ if cursor + char_length >= offset
56
+ character = offset - cursor
57
+ break
58
+ end
59
+ cursor += line_length
60
+ line += 1
61
+ end
62
+ raise InvalidOffsetError if character.nil?
63
+ Position.new(line, character)
64
+ end
24
65
  end
25
66
  end
26
67
  end
@@ -0,0 +1,48 @@
1
+ module Solargraph
2
+ class Source
3
+ # Updaters contain changes to be applied to a source. The source applies
4
+ # the update via the Source#synchronize method.
5
+ #
6
+ class Updater
7
+ # @return [String]
8
+ attr_reader :filename
9
+
10
+ # @return [Integer]
11
+ attr_reader :version
12
+
13
+ # @return [Array<Change>]
14
+ attr_reader :changes
15
+
16
+ # @param filename [String] The file to update.
17
+ # @param version [Integer] A version number associated with this update.
18
+ # @param changes [Array<Solargraph::Source::Change>] The changes.
19
+ def initialize filename, version, changes
20
+ @filename = filename
21
+ @version = version
22
+ @changes = changes
23
+ @input = nil
24
+ @did_nullify = nil
25
+ @output = nil
26
+ end
27
+
28
+ def write text, nullable = false
29
+ can_nullify = (nullable and changes.length == 1)
30
+ return @output if @input == text and can_nullify == @did_nullify
31
+ @input = text
32
+ @output = text
33
+ @did_nullify = can_nullify
34
+ changes.each do |ch|
35
+ @output = ch.write(@output, can_nullify)
36
+ end
37
+ @output
38
+ end
39
+
40
+ def repair text
41
+ changes.each do |ch|
42
+ text = ch.repair(text)
43
+ end
44
+ text
45
+ end
46
+ end
47
+ end
48
+ end
@@ -1,3 +1,3 @@
1
- module Solargraph
2
- VERSION = '0.18.2'
3
- end
1
+ module Solargraph
2
+ VERSION = '0.18.3'
3
+ end
@@ -16,15 +16,10 @@ module Solargraph
16
16
  unless @workspace.nil?
17
17
  sfile = File.join(@workspace, '.solargraph.yml')
18
18
  if File.file?(sfile)
19
- begin
20
- @raw_data = YAML.load(File.read(sfile))
21
- conf = YAML.load(File.read(sfile))
22
- include_globs = conf['include'] || include_globs
23
- exclude_globs = conf['exclude'] || []
24
- rescue Exception => e
25
- STDERR.puts "Unable to read .solargraph.yml: #{e.class} #{e.message}"
26
- @raw_data = {}
27
- end
19
+ @raw_data = YAML.load(File.read(sfile))
20
+ conf = YAML.load(File.read(sfile))
21
+ include_globs = conf['include'] || include_globs
22
+ exclude_globs = conf['exclude'] || []
28
23
  end
29
24
  end
30
25
  @raw_data ||= {}