solargraph 0.20.0 → 0.21.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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/solargraph.rb +2 -3
  3. data/lib/solargraph/api_map.rb +6 -6
  4. data/lib/solargraph/api_map/source_to_yard.rb +1 -1
  5. data/lib/solargraph/language_server/host.rb +69 -10
  6. data/lib/solargraph/language_server/message.rb +1 -2
  7. data/lib/solargraph/language_server/message/initialize.rb +83 -15
  8. data/lib/solargraph/language_server/message/initialized.rb +7 -0
  9. data/lib/solargraph/language_server/message/text_document/formatting.rb +4 -1
  10. data/lib/solargraph/language_server/message/text_document/on_type_formatting.rb +20 -19
  11. data/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +19 -1
  12. data/lib/solargraph/language_server/request.rb +1 -1
  13. data/lib/solargraph/language_server/transport.rb +1 -0
  14. data/lib/solargraph/language_server/transport/data_reader.rb +66 -0
  15. data/lib/solargraph/language_server/transport/socket.rb +8 -32
  16. data/lib/solargraph/library.rb +11 -6
  17. data/lib/solargraph/page.rb +1 -1
  18. data/lib/solargraph/pin/base.rb +8 -7
  19. data/lib/solargraph/pin/method_parameter.rb +2 -15
  20. data/lib/solargraph/pin/yard_object.rb +1 -1
  21. data/lib/solargraph/plugin/runtime.rb +0 -2
  22. data/lib/solargraph/shell.rb +0 -24
  23. data/lib/solargraph/source.rb +5 -169
  24. data/lib/solargraph/source/mapper.rb +8 -3
  25. data/lib/solargraph/version.rb +1 -1
  26. data/lib/solargraph/workspace.rb +3 -3
  27. data/lib/solargraph/workspace/config.rb +7 -9
  28. data/lib/solargraph/yard_map.rb +9 -48
  29. metadata +17 -74
  30. data/lib/solargraph/server.rb +0 -212
  31. data/lib/solargraph/suggestion.rb +0 -178
@@ -7,7 +7,7 @@ module Solargraph
7
7
  end
8
8
 
9
9
  def process result
10
- @block.call(result)
10
+ @block.call(result) unless @block.nil?
11
11
  end
12
12
  end
13
13
  end
@@ -1,6 +1,7 @@
1
1
  module Solargraph
2
2
  module LanguageServer
3
3
  module Transport
4
+ autoload :DataReader, 'solargraph/language_server/transport/data_reader'
4
5
  autoload :Socket, 'solargraph/language_server/transport/socket'
5
6
  end
6
7
  end
@@ -0,0 +1,66 @@
1
+ module Solargraph
2
+ module LanguageServer
3
+ module Transport
4
+ class DataReader
5
+ def initialize
6
+ @in_header = true
7
+ @content_length = 0
8
+ @buffer = ''
9
+ end
10
+
11
+ # Declare a block to be executed for each message received from the
12
+ # client.
13
+ #
14
+ # @yieldparam [Hash] The message received from the client
15
+ def set_message_handler &block
16
+ @message_handler = block
17
+ end
18
+
19
+ # Process raw data received from the client. The data will be parsed
20
+ # into messages based on the JSON-RPC protocol. Each message will be
21
+ # passed to the block declared via set_message_handler. Incomplete data
22
+ # will be buffered and subsequent data will be appended to the buffer.
23
+ #
24
+ # @param data [String]
25
+ def receive data
26
+ data.each_char do |char|
27
+ @buffer.concat char
28
+ if @in_header
29
+ prepare_to_parse_message if @buffer.end_with?("\r\n\r\n")
30
+ else
31
+ parse_message_from_buffer if @buffer.bytesize == @content_length
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def prepare_to_parse_message
39
+ @in_header = false
40
+ @buffer.each_line do |line|
41
+ parts = line.split(':').map(&:strip)
42
+ if parts[0] == 'Content-Length'
43
+ @content_length = parts[1].to_i
44
+ break
45
+ end
46
+ end
47
+ @buffer.clear
48
+ end
49
+
50
+ def parse_message_from_buffer
51
+ begin
52
+ msg = JSON.parse(@buffer)
53
+ @message_handler.call msg unless @message_handler.nil?
54
+ rescue JSON::ParserError => e
55
+ STDERR.puts "Failed to parse request: #{e.message}"
56
+ STDERR.puts "Buffer: #{@buffer}"
57
+ ensure
58
+ @buffer.clear
59
+ @in_header = true
60
+ @content_length = 0
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -11,50 +11,26 @@ module Solargraph
11
11
  @content_length = 0
12
12
  @buffer = ''
13
13
  @host = Solargraph::LanguageServer::Host.new
14
+ @data_reader = Solargraph::LanguageServer::Transport::DataReader.new
15
+ @data_reader.set_message_handler do |message|
16
+ process message
17
+ end
14
18
  start_timers
15
19
  end
16
20
 
17
- def process request
21
+ def process message
18
22
  Thread.new do
19
- message = @host.start(request)
23
+ message = @host.start(message)
20
24
  message.send
21
25
  tmp = @host.flush
22
26
  send_data tmp unless tmp.empty?
23
- GC.start unless request['method'] == 'textDocument/didChange'
27
+ GC.start unless message['method'] == 'textDocument/didChange'
24
28
  end
25
29
  end
26
30
 
27
31
  # @param data [String]
28
32
  def receive_data data
29
- data.each_char do |char|
30
- @buffer.concat char
31
- if @in_header
32
- if @buffer.end_with?("\r\n\r\n")
33
- @in_header = false
34
- @buffer.each_line do |line|
35
- parts = line.split(':').map(&:strip)
36
- if parts[0] == 'Content-Length'
37
- @content_length = parts[1].to_i
38
- break
39
- end
40
- end
41
- @buffer.clear
42
- end
43
- else
44
- if @buffer.bytesize == @content_length
45
- begin
46
- process JSON.parse(@buffer)
47
- rescue JSON::ParserError => e
48
- STDERR.puts "Failed to parse request: #{e.message}"
49
- STDERR.puts "Buffer: #{@buffer}"
50
- ensure
51
- @buffer.clear
52
- @in_header = true
53
- @content_length = 0
54
- end
55
- end
56
- end
57
- end
33
+ @data_reader.receive data
58
34
  end
59
35
 
60
36
  private
@@ -162,7 +162,7 @@ module Solargraph
162
162
  # will be removed from the ApiMap. Only one file can be checked out
163
163
  # (virtualized) at a time.
164
164
  #
165
- # @raise [FileNotFoundError] if the file is not in the library.
165
+ # @raise [FileNotFoundError] if the file does not exist.
166
166
  #
167
167
  # @param filename [String]
168
168
  # @return [Source]
@@ -256,14 +256,19 @@ module Solargraph
256
256
  @workspace
257
257
  end
258
258
 
259
- # @raise [FileNotFoundError] if the file is not open
259
+ # Get the source for an open file or create a new source if the file
260
+ # exists on disk. Sources created from disk are not added to the open
261
+ # workspace files, i.e., the version on disk remains the authoritative
262
+ # version.
263
+ #
264
+ # @raise [FileNotFoundError] if the file does not exist
260
265
  # @param filename [String]
261
266
  # @return [Solargraph::Source]
262
267
  def read filename
263
- source = source_hash[filename]
264
- raise FileNotFoundError, "Source not found for #{filename}" if source.nil?
265
- # api_map.virtualize source
266
- source
268
+ return source_hash[filename] if open?(filename)
269
+ return workspace.source(filename) if workspace.has_file?(filename)
270
+ raise FileNotFoundError, "File not found: #{filename}" unless File.file?(filename)
271
+ Solargraph::Source.load(filename)
267
272
  end
268
273
  end
269
274
  end
@@ -14,7 +14,7 @@ module Solargraph
14
14
  end
15
15
  define_singleton_method :erb do |template, layout: false, locals: {}|
16
16
  render_method.call(template, layout: layout, locals: locals)
17
- end
17
+ end
18
18
  end
19
19
 
20
20
  def htmlify text
@@ -6,20 +6,25 @@ module Solargraph
6
6
  include Conversions
7
7
  include Documenting
8
8
 
9
+ # @return [Solargraph::Source::Location]
9
10
  attr_reader :location
10
11
 
11
12
  # @return [String]
12
13
  attr_reader :namespace
13
14
 
15
+ # @return [String]
14
16
  attr_reader :name
15
17
 
18
+ # @return [YARD::Docstring]
16
19
  attr_reader :docstring
17
20
 
18
21
  # @return [String]
19
22
  attr_reader :return_type
20
23
 
24
+ # @return [Integer]
21
25
  attr_reader :kind
22
26
 
27
+ # @return [String]
23
28
  attr_reader :path
24
29
 
25
30
  def initialize location, namespace, name, docstring
@@ -29,18 +34,12 @@ module Solargraph
29
34
  @docstring = docstring
30
35
  end
31
36
 
37
+ # @return [String]
32
38
  def filename
33
39
  location.filename
34
40
  end
35
41
 
36
- # @return [String]
37
- def path
38
- end
39
-
40
42
  # @return [Integer]
41
- def kind
42
- end
43
-
44
43
  def completion_item_kind
45
44
  LanguageServer::CompletionItemKinds::KEYWORD
46
45
  end
@@ -49,6 +48,7 @@ module Solargraph
49
48
  name.to_s
50
49
  end
51
50
 
51
+ # @return [String]
52
52
  def identifier
53
53
  @identifier ||= "#{path}|#{name}"
54
54
  end
@@ -57,6 +57,7 @@ module Solargraph
57
57
  false
58
58
  end
59
59
 
60
+ # @return [String]
60
61
  def named_context
61
62
  namespace
62
63
  end
@@ -1,20 +1,6 @@
1
1
  module Solargraph
2
2
  module Pin
3
- class MethodParameter < Base
4
- include Localized
5
-
6
- attr_reader :block
7
-
8
- def initialize location, namespace, name, docstring, block
9
- super(location, namespace, name, docstring)
10
- @block = block
11
- @presence = block.location.range
12
- end
13
-
14
- def completion_item_kind
15
- Solargraph::LanguageServer::CompletionItemKinds::VARIABLE
16
- end
17
-
3
+ class MethodParameter < LocalVariable
18
4
  def return_type
19
5
  if @return_type.nil? and !block.docstring.nil?
20
6
  found = nil
@@ -25,6 +11,7 @@ module Solargraph
25
11
  end
26
12
  @return_type = found.types[0] unless found.nil? or found.types.nil?
27
13
  end
14
+ super
28
15
  @return_type
29
16
  end
30
17
  end
@@ -88,7 +88,7 @@ module Solargraph
88
88
  end
89
89
 
90
90
  def visibility
91
- :public #@todo Resolve this
91
+ @visibility ||= (code_object.respond_to?(:visibility) ? code_object.visibility : :public)
92
92
  end
93
93
 
94
94
  def scope
@@ -44,7 +44,6 @@ module Solargraph
44
44
  false
45
45
  else
46
46
  if @current_required != api_map.required
47
- STDERR.puts "Restarting #{self.class} process"
48
47
  @io.close unless @io.nil? or @io.closed?
49
48
  start_process
50
49
  true
@@ -70,7 +69,6 @@ module Solargraph
70
69
  unless api_map.nil? or api_map.workspace.nil?
71
70
  dir = api_map.workspace
72
71
  end
73
- STDERR.puts "Starting #{self.class} process in #{dir}"
74
72
  Dir.chdir(dir) do
75
73
  @io = IO.popen(executable, 'r+')
76
74
  end
@@ -16,30 +16,6 @@ module Solargraph
16
16
  puts Solargraph::VERSION
17
17
  end
18
18
 
19
- desc 'server', 'Start a Solargraph server'
20
- option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7657
21
- option :views, type: :string, aliases: :v, desc: 'The view template directory', default: nil
22
- option :files, type: :string, aliases: :f, desc: 'The public files directory', default: nil
23
- def server
24
- port = options[:port]
25
- port = available_port if port.zero?
26
- Solargraph::Server.set :port, port
27
- Solargraph::Server.set :views, options[:views] unless options[:views].nil?
28
- Solargraph::Server.set :public_folder, options[:files] unless options[:files].nil?
29
- my_pid = nil
30
- Solargraph::Server.run! do
31
- # This line should not be necessary with WEBrick
32
- #STDERR.puts "Solargraph server pid=#{Process.pid} port=#{port}"
33
- my_pid = Process.pid
34
- Signal.trap("INT") do
35
- Solargraph::Server.stop!
36
- end
37
- Signal.trap("TERM") do
38
- Solargraph::Server.stop!
39
- end
40
- end
41
- end
42
-
43
19
  desc 'socket', 'Run a Solargraph socket server'
44
20
  option :host, type: :string, aliases: :h, desc: 'The server host', default: '127.0.0.1'
45
21
  option :port, type: :numeric, aliases: :p, desc: 'The server port', default: 7658
@@ -58,7 +58,7 @@ module Solargraph
58
58
  @version = 0
59
59
  begin
60
60
  parse
61
- rescue Parser::SyntaxError
61
+ rescue Parser::SyntaxError, EncodingError
62
62
  hard_fix_node
63
63
  end
64
64
  end
@@ -79,7 +79,6 @@ module Solargraph
79
79
 
80
80
  # @return [Array<String>]
81
81
  def namespaces
82
- # @namespaces ||= namespace_pin_map.keys
83
82
  @namespaces ||= pins.select{|pin| pin.kind == Pin::NAMESPACE}.map(&:path)
84
83
  end
85
84
 
@@ -156,25 +155,6 @@ module Solargraph
156
155
  found || pins.first
157
156
  end
158
157
 
159
- # @return [YARD::Docstring]
160
- def docstring_for node
161
- return @docstring_hash[node.loc] if node.respond_to?(:loc)
162
- nil
163
- end
164
-
165
- # @return [String]
166
- def code_for node
167
- b = node.location.expression.begin.begin_pos
168
- e = node.location.expression.end.end_pos
169
- frag = code[b..e-1].to_s
170
- frag.strip.gsub(/,$/, '')
171
- end
172
-
173
- # @param node [Parser::AST::Node]
174
- def tree_for node
175
- @node_tree[node.object_id] || []
176
- end
177
-
178
158
  # Get the nearest node that contains the specified index.
179
159
  #
180
160
  # @param index [Integer]
@@ -203,6 +183,7 @@ module Solargraph
203
183
  end
204
184
 
205
185
  def inner_tree_at node, offset, stack
186
+ return if node.nil?
206
187
  stack.unshift node
207
188
  node.children.each do |c|
208
189
  next unless c.is_a?(AST::Node)
@@ -214,47 +195,6 @@ module Solargraph
214
195
  end
215
196
  end
216
197
 
217
- # Find the nearest parent node from the specified index. If one or more
218
- # types are provided, find the nearest node whose type is in the list.
219
- #
220
- # @param index [Integer]
221
- # @param types [Array<Symbol>]
222
- # @return [AST::Node]
223
- def parent_node_from(line, column, *types)
224
- arr = tree_at(line, column)
225
- arr.each { |a|
226
- if a.kind_of?(AST::Node) and (types.empty? or types.include?(a.type))
227
- return a
228
- end
229
- }
230
- nil
231
- end
232
-
233
- # @return [String]
234
- def namespace_for node
235
- parts = []
236
- ([node] + (@node_tree[node.object_id] || [])).each do |n|
237
- next unless n.kind_of?(AST::Node)
238
- if n.type == :class or n.type == :module
239
- parts.unshift unpack_name(n.children[0])
240
- end
241
- end
242
- parts.join('::')
243
- end
244
-
245
- def path_for node
246
- path = namespace_for(node) || ''
247
- mp = (method_pins + attribute_pins).select{|p| p.node == node}.first
248
- unless mp.nil?
249
- path += (mp.scope == :instance ? '#' : '.') + mp.name
250
- end
251
- path
252
- end
253
-
254
- def include? node
255
- node_object_ids.include? node.object_id
256
- end
257
-
258
198
  def synchronize updater
259
199
  raise 'Invalid synchronization' unless updater.filename == filename
260
200
  original_code = @code
@@ -266,11 +206,11 @@ module Solargraph
266
206
  begin
267
207
  parse
268
208
  @fixed = @code
269
- rescue Parser::SyntaxError => e
209
+ rescue Parser::SyntaxError, EncodingError => e
270
210
  @fixed = updater.repair(original_fixed)
271
211
  begin
272
212
  parse
273
- rescue Parser::SyntaxError => e
213
+ rescue Parser::SyntaxError, EncodingError => e
274
214
  hard_fix_node
275
215
  end
276
216
  end
@@ -284,7 +224,6 @@ module Solargraph
284
224
 
285
225
  def all_symbols
286
226
  result = []
287
- # result.concat namespace_pin_map.values.flatten
288
227
  result.concat namespace_pins.reject{ |pin| pin.name.empty? }
289
228
  result.concat method_pins
290
229
  result.concat constant_pins
@@ -301,14 +240,6 @@ module Solargraph
301
240
  Fragment.new(self, line, column)
302
241
  end
303
242
 
304
- def fragment_for node
305
- inside = tree_for(node)
306
- return nil if inside.empty?
307
- line = node.loc.expression.last_line - 1
308
- column = node.loc.expression.last_column
309
- Fragment.new(self, line, column, inside)
310
- end
311
-
312
243
  def parsed?
313
244
  @parsed
314
245
  end
@@ -337,7 +268,7 @@ module Solargraph
337
268
  parser.diagnostics.all_errors_are_fatal = true
338
269
  parser.diagnostics.ignore_warnings = true
339
270
  buffer = Parser::Source::Buffer.new(filename, 1)
340
- buffer.source = code.force_encoding(Encoding::UTF_8)
271
+ buffer.source = code.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: ' ')
341
272
  parser.parse_with_comments(buffer)
342
273
  end
343
274
 
@@ -346,59 +277,6 @@ module Solargraph
346
277
  @stime = Time.now
347
278
  end
348
279
 
349
- def associate_comments node, comments
350
- return nil if comments.nil?
351
- comment_hash = Parser::Source::Comment.associate_locations(node, comments)
352
- yard_hash = {}
353
- comment_hash.each_pair { |k, v|
354
- ctxt = ''
355
- num = nil
356
- started = false
357
- v.each { |l|
358
- # Trim the comment and minimum leading whitespace
359
- p = l.text.gsub(/^#/, '')
360
- if num.nil? and !p.strip.empty?
361
- num = p.index(/[^ ]/)
362
- started = true
363
- elsif started and !p.strip.empty?
364
- cur = p.index(/[^ ]/)
365
- num = cur if cur < num
366
- end
367
- if started
368
- ctxt += "#{p[num..-1]}\n"
369
- end
370
- }
371
- parse = YARD::Docstring.parser.parse(ctxt)
372
- unless parse.directives.empty?
373
- @directives[k] ||= []
374
- @directives[k].concat parse.directives
375
- end
376
- yard_hash[k] = parse.to_docstring
377
- }
378
- yard_hash
379
- end
380
-
381
- def find_parent(stack, *types)
382
- stack.reverse.each { |p|
383
- return p if types.include?(p.type)
384
- }
385
- nil
386
- end
387
-
388
- def node_object_ids
389
- @node_object_ids ||= @all_nodes.map(&:object_id)
390
- end
391
-
392
- # @return [Hash<String, Solargraph::Pin::Namespace>]
393
- def namespace_pin_map
394
- @namespace_pin_map ||= {}
395
- end
396
-
397
- # @return [Hash<String, Solargraph::Pin::Namespace>]
398
- def method_pin_map
399
- @method_pin_map ||= {}
400
- end
401
-
402
280
  class << self
403
281
  # @return [Solargraph::Source]
404
282
  def load filename
@@ -410,48 +288,6 @@ module Solargraph
410
288
  def load_string code, filename = nil
411
289
  Source.new code, filename
412
290
  end
413
-
414
- def fix code, filename = nil, offset = nil
415
- tries = 0
416
- offset = Source.get_offset(code, offset[0], offset[1]) if offset.kind_of?(Array)
417
- pos = nil
418
- pos = get_position_at(code, offset) unless offset.nil?
419
- stubs = []
420
- fixed_position = false
421
- tmp = code.sub(/\.(\s*\z)$/, ' \1')
422
- begin
423
- node, comments = Source.parse(tmp, filename)
424
- Source.new(code, node, comments, filename, stubs)
425
- rescue Parser::SyntaxError => e
426
- if tries < 10
427
- tries += 1
428
- # Stub periods before the offset to retain the expected node tree
429
- if !offset.nil? and ['.', '{', '('].include?(tmp[offset-1])
430
- tmp = tmp[0, offset-1] + ';' + tmp[offset..-1]
431
- elsif !fixed_position and !offset.nil?
432
- fixed_position = true
433
- beg = beginning_of_line_from(tmp, offset)
434
- tmp = "#{tmp[0, beg]}##{tmp[beg+1..-1]}"
435
- stubs.push(pos[0])
436
- elsif e.message.include?('token $end')
437
- tmp += "\nend"
438
- elsif e.message.include?("unexpected `@'")
439
- tmp = tmp[0, e.diagnostic.location.begin_pos] + '_' + tmp[e.diagnostic.location.begin_pos+1..-1]
440
- end
441
- retry
442
- end
443
- STDERR.puts "Unable to parse file #{filename.nil? ? 'undefined' : filename}: #{e.message}"
444
- node, comments = parse(code.gsub(/[^\s]/, ' '), filename)
445
- Source.new(code, node, comments, filename)
446
- end
447
- end
448
-
449
- def beginning_of_line_from str, i
450
- while i > 0 and str[i-1] != "\n"
451
- i -= 1
452
- end
453
- i
454
- end
455
291
  end
456
292
  end
457
293
  end