solargraph 0.30.2 → 0.31.0

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