solargraph 0.20.0 → 0.21.0

Sign up to get free protection for your applications and to get access to all the features.
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