solargraph 0.30.2 → 0.31.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +7 -0
  3. data/lib/solargraph/api_map.rb +31 -38
  4. data/lib/solargraph/api_map/store.rb +7 -1
  5. data/lib/solargraph/diagnostics/require_not_found.rb +2 -1
  6. data/lib/solargraph/language_server/host.rb +34 -83
  7. data/lib/solargraph/language_server/host/cataloger.rb +17 -7
  8. data/lib/solargraph/language_server/host/diagnoser.rb +19 -10
  9. data/lib/solargraph/language_server/host/dispatch.rb +110 -0
  10. data/lib/solargraph/language_server/host/sources.rb +100 -1
  11. data/lib/solargraph/language_server/message/base.rb +15 -11
  12. data/lib/solargraph/language_server/message/extended/check_gem_version.rb +1 -1
  13. data/lib/solargraph/language_server/message/initialize.rb +32 -27
  14. data/lib/solargraph/language_server/message/text_document/completion.rb +1 -8
  15. data/lib/solargraph/language_server/transport/adapter.rb +26 -15
  16. data/lib/solargraph/language_server/transport/data_reader.rb +2 -2
  17. data/lib/solargraph/library.rb +30 -58
  18. data/lib/solargraph/live_map.rb +1 -1
  19. data/lib/solargraph/pin.rb +1 -0
  20. data/lib/solargraph/pin/base.rb +1 -1
  21. data/lib/solargraph/pin/base_method.rb +1 -1
  22. data/lib/solargraph/pin/method.rb +1 -1
  23. data/lib/solargraph/pin/method_alias.rb +15 -4
  24. data/lib/solargraph/plugin/process.rb +1 -1
  25. data/lib/solargraph/position.rb +1 -2
  26. data/lib/solargraph/server_methods.rb +1 -0
  27. data/lib/solargraph/shell.rb +0 -28
  28. data/lib/solargraph/source.rb +116 -20
  29. data/lib/solargraph/source/encoding_fixes.rb +1 -1
  30. data/lib/solargraph/source/source_chainer.rb +16 -8
  31. data/lib/solargraph/source_map.rb +11 -2
  32. data/lib/solargraph/source_map/clip.rb +1 -1
  33. data/lib/solargraph/source_map/mapper.rb +8 -5
  34. data/lib/solargraph/version.rb +1 -1
  35. data/lib/solargraph/views/environment.erb +3 -0
  36. data/lib/solargraph/workspace.rb +17 -14
  37. data/lib/solargraph/workspace/config.rb +1 -1
  38. data/lib/solargraph/yard_map.rb +6 -5
  39. data/lib/solargraph/yard_map/core_docs.rb +68 -18
  40. data/lib/solargraph/yard_map/core_gen.rb +47 -0
  41. data/lib/yard-coregen.rb +16 -0
  42. data/lib/yard-solargraph.rb +10 -1
  43. metadata +21 -4
@@ -4,13 +4,6 @@ module Solargraph
4
4
  module TextDocument
5
5
  class Completion < Base
6
6
  def process
7
- inner_process
8
- end
9
-
10
- private
11
-
12
- # @return [void]
13
- def inner_process
14
7
  filename = uri_to_file(params['textDocument']['uri'])
15
8
  line = params['position']['line']
16
9
  col = params['position']['character']
@@ -39,7 +32,7 @@ module Solargraph
39
32
  items: items
40
33
  )
41
34
  rescue InvalidOffsetError => e
42
- STDERR.puts "Skipping invalid offset: #{filename}, line #{line}, character #{col}"
35
+ Logging.logger.info "Completion ignored invalid offset: #{filename}, line #{line}, character #{col}"
43
36
  set_result empty_result
44
37
  end
45
38
  end
@@ -4,20 +4,20 @@ module Solargraph
4
4
  # A common module for running language servers in Backport.
5
5
  #
6
6
  module Adapter
7
+ @@timer_is_running = false
8
+
7
9
  def opening
8
10
  @host = Solargraph::LanguageServer::Host.new
11
+ @host.start
9
12
  @data_reader = Solargraph::LanguageServer::Transport::DataReader.new
10
13
  @data_reader.set_message_handler do |message|
11
14
  process message
12
15
  end
13
- start_timers
16
+ start_timer
14
17
  end
15
18
 
16
- def process request
17
- message = @host.start(request)
18
- message.send_response
19
- tmp = @host.flush
20
- write tmp unless tmp.empty?
19
+ def closing
20
+ @host.stop
21
21
  end
22
22
 
23
23
  # @param data [String]
@@ -27,19 +27,30 @@ module Solargraph
27
27
 
28
28
  private
29
29
 
30
- def start_timers
31
- Backport.prepare_interval 0.1 do
32
- tmp = @host.flush
33
- write tmp unless tmp.empty?
30
+ # @param request [String]
31
+ # @return [void]
32
+ def process request
33
+ message = @host.receive(request)
34
+ message.send_response
35
+ tmp = @host.flush
36
+ write tmp unless tmp.empty?
37
+ end
38
+
39
+ def start_timer
40
+ Backport.prepare_interval 0.1 do |server|
34
41
  if @host.stopped?
35
- if @host.options['transport'] == 'external'
36
- @host = Solargraph::LanguageServer::Host.new
37
- else
38
- Backport.stop
39
- end
42
+ server.stop
43
+ shutdown
44
+ else
45
+ tmp = @host.flush
46
+ write tmp unless tmp.empty?
40
47
  end
41
48
  end
42
49
  end
50
+
51
+ def shutdown
52
+ Backport.stop unless @host.options['transport'] == 'external'
53
+ end
43
54
  end
44
55
  end
45
56
  end
@@ -52,8 +52,8 @@ module Solargraph
52
52
  msg = JSON.parse(@buffer)
53
53
  @message_handler.call msg unless @message_handler.nil?
54
54
  rescue JSON::ParserError => e
55
- STDERR.puts "Failed to parse request: #{e.message}"
56
- STDERR.puts "Buffer: #{@buffer}"
55
+ Solargraph::Logging.logger.info "Failed to parse request: #{e.message}"
56
+ Solargraph::Logging.logger.debug "Buffer: #{@buffer}"
57
57
  ensure
58
58
  @buffer.clear
59
59
  @in_header = true
@@ -20,6 +20,11 @@ module Solargraph
20
20
  @catalog_mutex = Mutex.new
21
21
  end
22
22
 
23
+ def inspect
24
+ # Let's not deal with insane data dumps in spec failures
25
+ to_s
26
+ end
27
+
23
28
  # True if the ApiMap is up to date with the library's workspace and open
24
29
  # files.
25
30
  #
@@ -28,23 +33,6 @@ module Solargraph
28
33
  @synchronized
29
34
  end
30
35
 
31
- # Open a file in the library. Opening a file will make it available for
32
- # checkout and merge it into the workspace if applicable.
33
- #
34
- # @deprecated The library should not be responsible for this. Instead, it
35
- # should accept a source and determine whether or not to merge it.
36
- #
37
- # @param filename [String]
38
- # @param text [String]
39
- # @param version [Integer]
40
- # @return [void]
41
- def open filename, text, version
42
- logger.warn "Library#open is deprecated"
43
- source = Solargraph::Source.load_string(text, filename, version)
44
- merge source
45
- attach source
46
- end
47
-
48
36
  # Open a file from disk and try to merge it into the workspace.
49
37
  #
50
38
  # @param filename [String]
@@ -60,7 +48,7 @@ module Solargraph
60
48
  # library will include it in the ApiMap while it's attached. Only one
61
49
  # source can be attached to the library at a time.
62
50
  #
63
- # @param source [Source]
51
+ # @param source [Source, nil]
64
52
  # @return [void]
65
53
  def attach source
66
54
  mutex.synchronize do
@@ -78,6 +66,16 @@ module Solargraph
78
66
  end
79
67
  alias open? attached?
80
68
 
69
+ # Detach the specified file if it is currently attached to the library.
70
+ #
71
+ # @param filename [String]
72
+ # @return [Boolean] True if the specified file was detached
73
+ def detach filename
74
+ return false if @current.nil? || @current.filename != filename
75
+ attach nil
76
+ true
77
+ end
78
+
81
79
  # True if the specified file is included in the workspace (but not
82
80
  # necessarily open).
83
81
  #
@@ -130,11 +128,10 @@ module Solargraph
130
128
  # @param filename [String]
131
129
  # @return [void]
132
130
  def delete filename
131
+ detach filename
133
132
  mutex.synchronize do
134
- @synchronized = false
135
- @current = nil if @current && @current.filename == filename
136
- workspace.remove filename
137
- # catalog
133
+ result = workspace.remove(filename)
134
+ @synchronized = !result if synchronized?
138
135
  end
139
136
  end
140
137
 
@@ -206,7 +203,7 @@ module Solargraph
206
203
  return [] if pins.empty?
207
204
  result = []
208
205
  pins.uniq.each do |pin|
209
- (workspace.sources + (@current ? [@current] : [])).uniq.each do |source|
206
+ (workspace.sources + (@current ? [@current] : [])).uniq(&:filename).each do |source|
210
207
  found = source.references(pin.name)
211
208
  found.select! do |loc|
212
209
  referenced = definitions_at(loc.filename, loc.range.ending.line, loc.range.ending.character)
@@ -223,7 +220,7 @@ module Solargraph
223
220
  end)
224
221
  end
225
222
  end
226
- result
223
+ result.uniq
227
224
  end
228
225
 
229
226
  # Get the pin at the specified location or nil if the pin does not exist.
@@ -256,7 +253,10 @@ module Solargraph
256
253
  checked = read(filename)
257
254
  @synchronized = (checked == @current) if synchronized?
258
255
  @current = checked
259
- catalog
256
+ # Cataloging is necessary to avoid FileNotFoundErrors when the file is
257
+ # not in the workspace. Otherwise it should be safe to defer
258
+ # synchronization.
259
+ catalog unless workspace.has_file?(filename)
260
260
  @current
261
261
  end
262
262
 
@@ -303,30 +303,6 @@ module Solargraph
303
303
  api_map.get_path_suggestions(path)
304
304
  end
305
305
 
306
- # Update a source in the library from the provided updater.
307
- #
308
- # @note This method will not update the library's ApiMap. See
309
- # Library#synchronized? and Library#catalog for more information.
310
- #
311
- # @deprecated The library should not be responsible for this. Instead, it
312
- # should accept a source and determine whether or not to merge it.
313
- #
314
- # @raise [FileNotFoundError] if the updater's file is not available.
315
- # @param updater [Solargraph::Source::Updater]
316
- # @return [void]
317
- def update updater
318
- logger.warn 'Library#update is deprecated'
319
- mutex.synchronize do
320
- if workspace.has_file?(updater.filename)
321
- workspace.synchronize!(updater)
322
- @current = workspace.source(updater.filename) if @current && @current.filename == updater.filename
323
- elsif @current && @current.filename == updater.filename
324
- @current = @current.synchronize(updater)
325
- end
326
- @synchronized = false
327
- end
328
- end
329
-
330
306
  # Get the current text of a file in the library.
331
307
  #
332
308
  # @param filename [String]
@@ -366,25 +342,21 @@ module Solargraph
366
342
  logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
367
343
  api_map.catalog bundle
368
344
  @synchronized = true
345
+ logger.info "Catalog complete (#{api_map.pins.length} pins)"
369
346
  end
370
347
  end
371
348
 
372
349
  # Get an array of foldable ranges for the specified file.
373
350
  #
351
+ # @deprecated The library should not need to handle folding ranges. The
352
+ # source itself has all the information it needs.
353
+ #
374
354
  # @param filename [String]
375
355
  # @return [Array<Range>]
376
356
  def folding_ranges filename
377
357
  read(filename).folding_ranges
378
358
  end
379
359
 
380
- # @deprecated Libraries are no longer responsible for tracking open files.
381
- #
382
- # @return [Array<Source>]
383
- def open_sources
384
- logger.warn 'Library#open_sources is deprecated'
385
- @current ? [@current] : []
386
- end
387
-
388
360
  # Create a library from a directory.
389
361
  #
390
362
  # @param directory [String] The path to be used for the workspace
@@ -403,7 +375,7 @@ module Solargraph
403
375
  result = nil
404
376
  mutex.synchronize do
405
377
  result = workspace.merge(source)
406
- @synchronized = result if synchronized?
378
+ @synchronized = !result if synchronized?
407
379
  end
408
380
  result
409
381
  end
@@ -89,7 +89,7 @@ module Solargraph
89
89
  changed ||= p.refresh
90
90
  end
91
91
  if changed
92
- STDERR.puts "Resetting LiveMap cache"
92
+ Solargraph::Logging.logger.debug "Resetting LiveMap cache"
93
93
  cache.clear
94
94
  get_constants('')
95
95
  get_methods('', '', 'class')
@@ -43,6 +43,7 @@ module Solargraph
43
43
  SUPERCLASS_REFERENCE = 14
44
44
  INCLUDE_REFERENCE = 15
45
45
  EXTEND_REFERENCE = 16
46
+ METHOD_ALIAS = 17
46
47
 
47
48
  ROOT_PIN = Pin::Namespace.new(nil, '', '', '', :class, :public)
48
49
  end
@@ -167,7 +167,7 @@ module Solargraph
167
167
  # @param api_map [ApiMap]
168
168
  # @return [ComplexType]
169
169
  def infer api_map
170
- STDERR.puts "WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead."
170
+ Solargraph::Logging.logger.warn "WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead."
171
171
  type = typify(api_map)
172
172
  return type unless type.undefined?
173
173
  probe api_map
@@ -34,7 +34,7 @@ module Solargraph
34
34
  begin
35
35
  ComplexType.parse *tag.types
36
36
  rescue Solargraph::ComplexTypeError => e
37
- STDERR.puts e.message
37
+ Solargraph::Logging.logger.warn e.message
38
38
  ComplexType::UNDEFINED
39
39
  end
40
40
  end
@@ -82,7 +82,7 @@ module Solargraph
82
82
 
83
83
  # @deprecated Use #typify and/or #probe instead
84
84
  def infer api_map
85
- STDERR.puts 'WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead.'
85
+ Solargraph::Logging.logger.warn 'WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead.'
86
86
  type = typify(api_map)
87
87
  return type unless type.undefined?
88
88
  probe api_map
@@ -1,19 +1,30 @@
1
1
  module Solargraph
2
2
  module Pin
3
3
  # Use this class to track method aliases for later remapping. Common
4
- # examples are aliases for superclass methods or methods from included
5
- # modules.
4
+ # examples that defer mapping are aliases for superclass methods or
5
+ # methods from included modules.
6
6
  #
7
- class MethodAlias < Method
7
+ class MethodAlias < Base
8
+ attr_reader :scope
9
+
8
10
  attr_reader :original
9
11
 
10
12
  def initialize location, namespace, name, scope, original
11
13
  # @todo Determine how to handle these parameters. Among other things,
12
14
  # determine if the visibility is defined by the location of the
13
15
  # alias call or the original method.
14
- super(location, namespace, name, '', scope, :public, [])
16
+ super(location, namespace, name, '')
17
+ @scope = scope
15
18
  @original = original
16
19
  end
20
+
21
+ def kind
22
+ Pin::METHOD_ALIAS
23
+ end
24
+
25
+ def path
26
+ @path ||= namespace + (scope == :instance ? '#' : '.') + name
27
+ end
17
28
  end
18
29
  end
19
30
  end
@@ -48,7 +48,7 @@ module Solargraph
48
48
  require p
49
49
  @required.push p
50
50
  rescue Exception => e
51
- STDERR.puts "Failed to require #{p}: #{e.message}"
51
+ Solargraph::Logging.logger.info "Failed to require #{p}: #{e.message}"
52
52
  errors.push "Failed to require #{p}: #{e.class} #{e.message}"
53
53
  end
54
54
  end
@@ -44,7 +44,6 @@ module Solargraph
44
44
  column = position.character
45
45
  text.lines.each do |l|
46
46
  line_length = l.length
47
- char_length = l.chomp.length
48
47
  if feed == line
49
48
  result += column
50
49
  break
@@ -85,7 +84,7 @@ module Solargraph
85
84
  cursor += line_length
86
85
  line += 1
87
86
  end
88
- character = 0 if character.nil? and offset == cursor
87
+ character = 0 if character.nil? and (cursor - offset).between?(0, 1)
89
88
  raise InvalidOffsetError if character.nil?
90
89
  Position.new(line, character)
91
90
  end
@@ -2,6 +2,7 @@ require 'socket'
2
2
 
3
3
  module Solargraph
4
4
  module ServerMethods
5
+ # @return [Integer]
5
6
  def available_port
6
7
  socket = Socket.new(:INET, :STREAM, 0)
7
8
  socket.bind(Addrinfo.tcp("127.0.0.1", 0))
@@ -48,34 +48,6 @@ module Solargraph
48
48
  end
49
49
  end
50
50
 
51
- desc 'suggest', 'Get code suggestions for the provided input'
52
- long_desc <<-LONGDESC
53
- Analyze a Ruby file and output a list of code suggestions in JSON format.
54
- LONGDESC
55
- option :line, type: :numeric, aliases: :l, desc: 'Zero-based line number', required: true
56
- option :column, type: :numeric, aliases: [:c, :col], desc: 'Zero-based column number', required: true
57
- option :filename, type: :string, aliases: :f, desc: 'File name', required: false
58
- def suggest(*filenames)
59
- STDERR.puts "WARNING: The `solargraph suggest` command is a candidate for deprecation. It will either change drastically or not exist in a future version."
60
- # HACK: The ARGV array needs to be manipulated for ARGF.read to work
61
- ARGV.clear
62
- ARGV.concat filenames
63
- text = ARGF.read
64
- filename = options[:filename] || filenames[0]
65
- begin
66
- code_map = CodeMap.new(code: text, filename: filename)
67
- offset = code_map.get_offset(options[:line], options[:column])
68
- sugg = code_map.suggest_at(offset, filtered: true)
69
- result = { "status" => "ok", "suggestions" => sugg }.to_json
70
- STDOUT.puts result
71
- rescue Exception => e
72
- STDERR.puts e
73
- STDERR.puts e.backtrace.join("\n")
74
- result = { "status" => "err", "message" => e.message + "\n" + e.backtrace.join("\n") }.to_json
75
- STDOUT.puts result
76
- end
77
- end
78
-
79
51
  desc 'config [DIRECTORY]', 'Create or overwrite a default configuration file'
80
52
  option :extensions, type: :boolean, aliases: :e, desc: 'Add installed extensions', default: true
81
53
  def config(directory = '.')
@@ -18,6 +18,9 @@ module Solargraph
18
18
  include EncodingFixes
19
19
  include NodeMethods
20
20
 
21
+ # @return [String]
22
+ attr_reader :filename
23
+
21
24
  # @return [String]
22
25
  attr_reader :code
23
26
 
@@ -27,9 +30,6 @@ module Solargraph
27
30
  # @return [Array<Parser::Source::Comment>]
28
31
  attr_reader :comments
29
32
 
30
- # @return [String]
31
- attr_reader :filename
32
-
33
33
  # @todo Deprecate?
34
34
  # @return [Integer]
35
35
  attr_reader :version
@@ -64,10 +64,16 @@ module Solargraph
64
64
  end
65
65
 
66
66
  # @param range [Solargraph::Range]
67
+ # @return [String]
67
68
  def at range
68
69
  from_to range.start.line, range.start.character, range.ending.line, range.ending.character
69
70
  end
70
71
 
72
+ # @param l1 [Integer]
73
+ # @param c1 [Integer]
74
+ # @param l2 [Integer]
75
+ # @param c2 [Integer]
76
+ # @return [String]
71
77
  def from_to l1, c1, l2, c2
72
78
  b = Solargraph::Position.line_char_to_offset(@code, l1, c1)
73
79
  e = Solargraph::Position.line_char_to_offset(@code, l2, c2)
@@ -97,8 +103,53 @@ module Solargraph
97
103
  stack
98
104
  end
99
105
 
106
+ # Start synchronizing the source. This method updates the code without
107
+ # parsing a new AST. The resulting Source object will be marked not
108
+ # synchronized (#synchronized? == false).
109
+ #
110
+ # @param updater [Source::Updater]
111
+ # @return [Source]
112
+ def start_synchronize updater
113
+ raise 'Invalid synchronization' unless updater.filename == filename
114
+ real_code = updater.write(@code)
115
+ src = Source.allocate
116
+ src.filename = filename
117
+ src.code = real_code
118
+ src.version = updater.version
119
+ src.parsed = parsed?
120
+ src.repaired = updater.repair(@repaired)
121
+ src.synchronized = false
122
+ src.node = @node
123
+ src.comments = @comments
124
+ src.error_ranges = error_ranges
125
+ src.last_updater = updater
126
+ src
127
+ end
128
+
129
+ # Finish synchronizing a source that was updated via #start_synchronize.
130
+ # This method returns self if the source is already synchronized. Otherwise
131
+ # it parses the AST and returns a new synchronized Source.
132
+ #
133
+ # @return [Source]
134
+ def finish_synchronize
135
+ return self if synchronized?
136
+ synced = Source.new(@code, filename)
137
+ if synced.parsed?
138
+ synced.version = version
139
+ return synced
140
+ end
141
+ synced = Source.new(@repaired, filename)
142
+ synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range))
143
+ synced.code = @code
144
+ synced.synchronized = true
145
+ synced.version = version
146
+ synced
147
+ end
148
+
149
+ # Synchronize the Source with an update. This method applies changes to the
150
+ # code, parses the new code's AST, and returns the resulting Source object.
151
+ #
100
152
  # @param updater [Source::Updater]
101
- # @param reparse [Boolean]
102
153
  # @return [Source]
103
154
  def synchronize updater
104
155
  raise 'Invalid synchronization' unless updater.filename == filename
@@ -177,11 +228,9 @@ module Solargraph
177
228
  @error_ranges ||= []
178
229
  end
179
230
 
231
+ # @param node [Parser::AST::Node]
232
+ # @return [String]
180
233
  def code_for(node)
181
- # @todo Using node locations on code with converted EOLs seems
182
- # slightly more efficient than calculating offsets.
183
- # b = node.location.expression.begin.begin_pos
184
- # e = node.location.expression.end.end_pos
185
234
  b = Position.line_char_to_offset(@code, node.location.line, node.location.column)
186
235
  e = Position.line_char_to_offset(@code, node.location.last_line, node.location.last_column)
187
236
  frag = code[b..e-1].to_s
@@ -191,8 +240,10 @@ module Solargraph
191
240
  # @param node [Parser::AST::Node]
192
241
  # @return [String]
193
242
  def comments_for node
194
- arr = associated_comments[node.loc.line]
195
- arr ? stringify_comment_array(arr) : nil
243
+ stringified_comments[node.loc.line] ||= begin
244
+ arr = associated_comments[node.loc.line]
245
+ arr ? stringify_comment_array(arr) : nil
246
+ end
196
247
  end
197
248
 
198
249
  # A location representing the file in its entirety.
@@ -219,11 +270,16 @@ module Solargraph
219
270
  @folding_ranges ||= begin
220
271
  result = []
221
272
  inner_folding_ranges node, result
222
- result.concat comment_block_ranges
273
+ result.concat foldable_comment_block_ranges
223
274
  result
224
275
  end
225
276
  end
226
277
 
278
+ def synchronized?
279
+ @synchronized = true if @synchronized.nil?
280
+ @synchronized
281
+ end
282
+
227
283
  private
228
284
 
229
285
  # @param top [Parser::AST::Node]
@@ -249,7 +305,7 @@ module Solargraph
249
305
  @associated_comments ||= begin
250
306
  result = {}
251
307
  Parser::Source::Comment.associate_locations(node, comments).each_pair do |loc, all|
252
- block = all.select{ |l| l.document? || code.lines[l.loc.line].strip.start_with?('#')}
308
+ block = all #.select{ |l| l.document? || code.lines[l.loc.line].strip.start_with?('#')}
253
309
  next if block.empty?
254
310
  result[loc.line] ||= []
255
311
  result[loc.line].concat block
@@ -266,6 +322,7 @@ module Solargraph
266
322
  ctxt = ''
267
323
  num = nil
268
324
  started = false
325
+ last_line = nil
269
326
  comments.each { |l|
270
327
  # Trim the comment and minimum leading whitespace
271
328
  p = l.text.gsub(/^#/, '')
@@ -276,11 +333,21 @@ module Solargraph
276
333
  cur = p.index(/[^ ]/)
277
334
  num = cur if cur < num
278
335
  end
336
+ # Include blank lines between comments
337
+ ctxt += ("\n" * (l.loc.first_line - last_line - 1)) unless last_line.nil?
279
338
  ctxt += "#{p[num..-1]}\n" if started
339
+ last_line = l.loc.last_line
280
340
  }
281
341
  ctxt
282
342
  end
283
343
 
344
+ # A hash of line numbers and their associated comments.
345
+ #
346
+ # @return [Hash{Integer => Array<String>}]
347
+ def stringified_comments
348
+ @stringified_comments ||= {}
349
+ end
350
+
284
351
  # @return [Array<Range>]
285
352
  def string_ranges
286
353
  @string_ranges ||= string_ranges_in(@node)
@@ -293,7 +360,12 @@ module Solargraph
293
360
  end
294
361
  end
295
362
 
296
- def comment_block_ranges
363
+ # Get an array of foldable comment block ranges. Blocks are excluded if
364
+ # they are less than 3 lines long.
365
+ #
366
+ # @return [Array<Range>]
367
+ def foldable_comment_block_ranges
368
+ return [] unless synchronized?
297
369
  result = []
298
370
  grouped = []
299
371
  # @param cmnt [Parser::Source::Comment]
@@ -304,13 +376,17 @@ module Solargraph
304
376
  if grouped.empty? || cmnt.loc.expression.line == grouped.last.loc.expression.line + 1
305
377
  grouped.push cmnt
306
378
  else
307
- result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.empty?
379
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
308
380
  grouped = [cmnt]
309
381
  end
310
382
  else
311
- result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.empty?
383
+ unless grouped.length < 3
384
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0)
385
+ end
386
+ grouped.clear
312
387
  end
313
388
  end
389
+ result.push Range.from_to(grouped.first.loc.expression.line, 0, grouped.last.loc.expression.line, 0) unless grouped.length < 3
314
390
  result
315
391
  end
316
392
 
@@ -339,12 +415,13 @@ module Solargraph
339
415
  end
340
416
  end
341
417
 
418
+ # @param name [String]
419
+ # @param top [AST::Node]
420
+ # @return [Array<AST::Node>]
342
421
  def inner_node_references name, top
343
422
  result = []
344
- if top.kind_of?(AST::Node)
345
- if top.children.any?{|c| c.to_s == name}
346
- result.push top
347
- end
423
+ if top.is_a?(AST::Node)
424
+ result.push top if top.children.any? { |c| c.to_s == name }
348
425
  top.children.each { |c| result.concat inner_node_references(name, c) }
349
426
  end
350
427
  result
@@ -352,18 +429,36 @@ module Solargraph
352
429
 
353
430
  protected
354
431
 
432
+ # @return [String]
433
+ attr_writer :filename
434
+
355
435
  # @return [Integer]
356
436
  attr_writer :version
357
437
 
358
438
  # @return [String]
359
439
  attr_writer :code
360
440
 
441
+ # @return [Parser::AST::Node]
442
+ attr_writer :node
443
+
444
+ # @return [Array<Range>]
445
+ attr_writer :error_ranges
446
+
361
447
  # @return [String]
362
448
  attr_accessor :repaired
363
449
 
364
450
  # @return [Boolean]
365
451
  attr_writer :parsed
366
452
 
453
+ # @return [Array<Parser::Source::Comment>]
454
+ attr_writer :comments
455
+
456
+ # @return [Boolean]
457
+ attr_writer :synchronized
458
+
459
+ # @return [Source::Updater]
460
+ attr_accessor :last_updater
461
+
367
462
  class << self
368
463
  # @param filename [String]
369
464
  # @return [Solargraph::Source]
@@ -392,7 +487,8 @@ module Solargraph
392
487
  end
393
488
 
394
489
  # @param code [String]
395
- # @param filename [String]
490
+ # @param filename [String, nil]
491
+ # @param line [Integer]
396
492
  # @return [Parser::AST::Node]
397
493
  def parse code, filename = nil, line = 0
398
494
  buffer = Parser::Source::Buffer.new(filename, line)