neovim 0.6.2 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -1
  3. data/.travis.yml +2 -3
  4. data/CHANGELOG.md +29 -0
  5. data/Gemfile +0 -11
  6. data/README.md +3 -3
  7. data/Rakefile +1 -1
  8. data/appveyor.yml +9 -1
  9. data/lib/neovim.rb +3 -3
  10. data/lib/neovim/client.rb +2 -3
  11. data/lib/neovim/connection.rb +69 -0
  12. data/lib/neovim/event_loop.rb +34 -34
  13. data/lib/neovim/host.rb +47 -51
  14. data/lib/neovim/host/loader.rb +1 -4
  15. data/lib/neovim/logging.rb +8 -8
  16. data/lib/neovim/message.rb +70 -0
  17. data/lib/neovim/plugin.rb +0 -3
  18. data/lib/neovim/plugin/handler.rb +6 -6
  19. data/lib/neovim/remote_object.rb +1 -1
  20. data/lib/neovim/ruby_provider.rb +25 -14
  21. data/lib/neovim/session.rb +36 -43
  22. data/lib/neovim/version.rb +1 -1
  23. data/neovim.gemspec +4 -0
  24. data/spec/acceptance/runtime/rplugin/ruby/autocmds.rb +3 -3
  25. data/spec/acceptance/runtime/rplugin/ruby/commands.rb +15 -15
  26. data/spec/acceptance/runtime/rplugin/ruby/functions.rb +5 -5
  27. data/spec/acceptance_spec.rb +19 -16
  28. data/spec/helper.rb +15 -0
  29. data/spec/neovim/connection_spec.rb +79 -0
  30. data/spec/neovim/event_loop_spec.rb +36 -50
  31. data/spec/neovim/host/loader_spec.rb +16 -9
  32. data/spec/neovim/host_spec.rb +82 -92
  33. data/spec/neovim/logging_spec.rb +6 -6
  34. data/spec/neovim/message_spec.rb +119 -0
  35. data/spec/neovim/plugin_spec.rb +31 -31
  36. data/spec/neovim/session_spec.rb +1 -1
  37. metadata +38 -15
  38. data/lib/neovim/event_loop/connection.rb +0 -78
  39. data/lib/neovim/event_loop/message_builder.rb +0 -127
  40. data/lib/neovim/event_loop/serializer.rb +0 -37
  41. data/spec/acceptance/runtime/rplugin.vim +0 -37
  42. data/spec/neovim/event_loop/connection_spec.rb +0 -89
  43. data/spec/neovim/event_loop/message_builder_spec.rb +0 -105
  44. data/spec/neovim/event_loop/serializer_spec.rb +0 -63
@@ -8,67 +8,57 @@ module Neovim
8
8
  class Host
9
9
  include Logging
10
10
 
11
- # Start a plugin host. This is called by the +nvim-ruby-host+ executable,
12
- # which is spawned by +nvim+ to discover and run Ruby plugins, and acts as
13
- # the bridge between +nvim+ and the plugin.
14
- def self.run(rplugin_paths, event_loop=EventLoop.stdio)
15
- client = Client.from_event_loop(event_loop)
11
+ attr_reader :plugins
16
12
 
17
- new(client).tap do |host|
13
+ def self.run(rplugin_paths, event_loop=EventLoop.stdio)
14
+ new(event_loop).tap do |host|
18
15
  Loader.new(host).load(rplugin_paths)
19
16
  end.run
20
17
  end
21
18
 
22
- def initialize(client)
23
- @client = client
24
- @session = client.session
19
+ def initialize(event_loop)
20
+ @event_loop = event_loop
21
+ @session = Session.new(event_loop)
25
22
  @handlers = {"poll" => poll_handler, "specs" => specs_handler}
23
+ @plugins = []
26
24
  @specs = {}
27
25
  end
28
26
 
29
- # Register a +Plugin+ to receive +Host+ messages.
30
- def register(plugin)
31
- plugin.handlers.each do |handler|
32
- @handlers[handler.qualified_name] = wrap_plugin_handler(handler)
33
- end
34
-
35
- plugin.setup(@client)
36
- @specs[plugin.source] = plugin.specs
37
- end
38
-
39
- # Run the event loop, passing received messages to the appropriate handler.
40
27
  def run
41
28
  @session.run { |msg| handle(msg) }
42
- ensure
43
- @client.shutdown
44
- @session.shutdown
45
29
  end
46
30
 
47
- # Handle messages received from the host. Sends a +Neovim::Client+ along
48
- # with the message to be used in plugin callbacks.
49
31
  def handle(message)
50
32
  log(:debug) { message.to_h }
51
33
 
52
34
  @handlers.
53
35
  fetch(message.method_name, default_handler).
54
36
  call(@client, message)
55
- rescue SignalException => ex
56
- log_exception(:debug, ex, __method__)
57
- raise ex
58
- rescue => ex
59
- log_exception(:fatal, ex, __method__)
37
+ rescue Exception => e
38
+ log_exception(:error, e, __method__)
39
+
40
+ if message.sync?
41
+ @session.respond(message.id, nil, e.message)
42
+ else
43
+ @client.err_writeln("Exception handling #{message.method_name}: (#{e.class}) #{e.message}")
44
+ end
45
+
46
+ raise unless StandardError === e
60
47
  end
61
48
 
62
49
  private
63
50
 
64
51
  def poll_handler
65
- @poll_handler ||= Proc.new do |_, req|
52
+ @poll_handler ||= -> (_, req) {
53
+ initialize_client(req.id)
54
+ initialize_plugins
55
+
66
56
  @session.respond(req.id, "ok")
67
- end
57
+ }
68
58
  end
69
59
 
70
60
  def specs_handler
71
- @specs_handler ||= Proc.new do |_, req|
61
+ @specs_handler ||= -> (_, req) {
72
62
  source = req.arguments.fetch(0)
73
63
 
74
64
  if @specs.key?(source)
@@ -76,33 +66,39 @@ module Neovim
76
66
  else
77
67
  @session.respond(req.id, nil, "Unknown plugin #{source}")
78
68
  end
79
- end
69
+ }
80
70
  end
81
71
 
82
72
  def default_handler
83
- @default_handler ||= Proc.new do |_, message|
73
+ @default_handler ||= -> (_, message) {
84
74
  next unless message.sync?
85
75
  @session.respond(message.id, nil, "Unknown request #{message.method_name}")
86
- end
76
+ }
87
77
  end
88
78
 
89
- def wrap_plugin_handler(handler)
90
- Proc.new do |client, message|
91
- begin
92
- args = message.arguments.flatten(1)
93
- result = handler.call(client, *args)
94
-
95
- @session.respond(message.id, result) if message.sync?
96
- rescue => e
97
- log_exception(:error, e, __method__)
98
-
99
- if message.sync?
100
- @session.respond(message.id, nil, e.message)
101
- else
102
- client.err_writeln("#{handler.qualified_name}: (#{e.class}) #{e.message}")
103
- end
79
+ def initialize_client(request_id)
80
+ @session.request_id = request_id
81
+ @client = Client.from_event_loop(@event_loop, @session)
82
+ end
83
+
84
+ def initialize_plugins
85
+ @plugins.each do |plugin|
86
+ plugin.handlers.each do |handler|
87
+ @handlers[handler.qualified_name] = wrap_plugin_handler(handler)
104
88
  end
89
+
90
+ plugin.setup(@client)
91
+ @specs[plugin.source] = plugin.specs
105
92
  end
106
93
  end
94
+
95
+ def wrap_plugin_handler(handler)
96
+ -> (client, message) {
97
+ args = message.arguments.flatten(1)
98
+ result = handler.call(client, *args)
99
+
100
+ @session.respond(message.id, result) if message.sync?
101
+ }
102
+ end
107
103
  end
108
104
  end
@@ -8,9 +8,6 @@ module Neovim
8
8
  @host = host
9
9
  end
10
10
 
11
- # Load the provided Ruby files while temporarily overriding
12
- # +Neovim.plugin+ to expose the remote plugin DSL and register the result
13
- # to the host.
14
11
  def load(paths)
15
12
  paths.each do |path|
16
13
  override_plugin_method(path) do
@@ -27,7 +24,7 @@ module Neovim
27
24
 
28
25
  Neovim.define_singleton_method(:plugin) do |&block|
29
26
  plugin = Plugin.from_config_block(path, &block)
30
- at_host.register(plugin)
27
+ at_host.plugins << plugin
31
28
  end
32
29
 
33
30
  yield
@@ -43,16 +43,16 @@ module Neovim
43
43
  end
44
44
 
45
45
  def self.json_formatter
46
- Proc.new do |level, time, _, fields|
46
+ -> (level, time, _, fields) {
47
47
  require "multi_json"
48
48
 
49
49
  MultiJson.encode(
50
50
  {
51
- :_level => level,
52
- :_time => time.strftime(TIMESTAMP_FORMAT)
51
+ _level: level,
52
+ _time: time.strftime(TIMESTAMP_FORMAT)
53
53
  }.merge!(fields)
54
54
  ) << "\n"
55
- end
55
+ }
56
56
  end
57
57
  private_class_method :json_formatter
58
58
 
@@ -63,8 +63,8 @@ module Neovim
63
63
  begin
64
64
  Logging.logger.public_send(level) do
65
65
  {
66
- :_class => self.class,
67
- :_method => _method || block.binding.eval("__method__"),
66
+ _class: self.class,
67
+ _method: _method || block.binding.eval("__method__"),
68
68
  }.merge!(block.call)
69
69
  end
70
70
  rescue => ex
@@ -76,11 +76,11 @@ module Neovim
76
76
 
77
77
  def log_exception(level, ex, _method)
78
78
  log(level, _method) do
79
- {:exception => ex.class, :message => ex.message}
79
+ {exception: ex.class, message: ex.message}
80
80
  end
81
81
 
82
82
  log(:debug, _method) do
83
- {:exception => ex.class, :message => ex.message, :backtrace => ex.backtrace}
83
+ {exception: ex.class, message: ex.message, backtrace: ex.backtrace}
84
84
  end
85
85
  end
86
86
  end
@@ -0,0 +1,70 @@
1
+ require "neovim/logging"
2
+
3
+ module Neovim
4
+ # @api private
5
+ class Message
6
+ def self.from_array((kind, *payload))
7
+ case kind
8
+ when 0
9
+ request(*payload)
10
+ when 1
11
+ reqid, (_, error), value = payload
12
+ response(reqid, error, value)
13
+ when 2
14
+ notification(*payload)
15
+ else
16
+ raise "Unknown message type #{kind.inspect}"
17
+ end
18
+ end
19
+
20
+ def self.request(id, method, args)
21
+ Request.new(id, method, args)
22
+ end
23
+
24
+ def self.response(request_id, error, value)
25
+ Response.new(request_id, error, value)
26
+ end
27
+
28
+ def self.notification(method, args)
29
+ Notification.new(method, args)
30
+ end
31
+
32
+ class Request < Struct.new(:id, :method_name, :arguments)
33
+ def to_a
34
+ [0, id, method_name, arguments]
35
+ end
36
+
37
+ def received(_, &block)
38
+ block.call(self)
39
+ end
40
+
41
+ def sync?
42
+ true
43
+ end
44
+ end
45
+
46
+ class Response < Struct.new(:request_id, :error, :value)
47
+ def to_a
48
+ [1, request_id, error, value]
49
+ end
50
+
51
+ def received(handlers)
52
+ handlers[request_id].call(self)
53
+ end
54
+ end
55
+
56
+ class Notification < Struct.new(:method_name, :arguments)
57
+ def to_a
58
+ [2, method_name, arguments]
59
+ end
60
+
61
+ def received(_, &block)
62
+ block.call(self)
63
+ end
64
+
65
+ def sync?
66
+ false
67
+ end
68
+ end
69
+ end
70
+ end
@@ -6,7 +6,6 @@ module Neovim
6
6
  attr_accessor :handlers, :setup_blocks
7
7
  attr_reader :source
8
8
 
9
- # Entrypoint to the +Neovim.plugin+ DSL.
10
9
  def self.from_config_block(source)
11
10
  new(source).tap do |instance|
12
11
  yield DSL.new(instance) if block_given?
@@ -19,14 +18,12 @@ module Neovim
19
18
  @setup_blocks = []
20
19
  end
21
20
 
22
- # Return specs used by +nvim+ to register plugins.
23
21
  def specs
24
22
  @handlers.inject([]) do |acc, handler|
25
23
  handler.qualified? ? acc + [handler.to_spec] : acc
26
24
  end
27
25
  end
28
26
 
29
- # Run all registered setup blocks.
30
27
  def setup(client)
31
28
  @setup_blocks.each { |bl| bl.call(client) }
32
29
  end
@@ -5,7 +5,7 @@ module Neovim
5
5
  attr_reader :block
6
6
 
7
7
  def self.unqualified(name, block)
8
- new(nil, nil, name, true, {:qualified => false}, block)
8
+ new(nil, nil, name, true, {qualified: false}, block)
9
9
  end
10
10
 
11
11
  def initialize(source, type, name, sync, options, block)
@@ -14,7 +14,7 @@ module Neovim
14
14
  @name = name.to_s
15
15
  @sync = !!sync
16
16
  @options = options
17
- @block = block || Proc.new {}
17
+ @block = block || -> {}
18
18
  @qualified =
19
19
  options.key?(:qualified) ? options.delete(:qualified) : true
20
20
  end
@@ -40,10 +40,10 @@ module Neovim
40
40
 
41
41
  def to_spec
42
42
  {
43
- :type => @type,
44
- :name => @name,
45
- :sync => @sync,
46
- :opts => @options,
43
+ type: @type,
44
+ name: @name,
45
+ sync: @sync,
46
+ opts: @options,
47
47
  }
48
48
  end
49
49
 
@@ -31,7 +31,7 @@ module Neovim
31
31
  end
32
32
 
33
33
  # Extend +respond_to?+ to support RPC methods.
34
- def respond_to?(method_name)
34
+ def respond_to?(method_name, *)
35
35
  super || rpc_methods.include?(method_name.to_sym)
36
36
  end
37
37
 
@@ -27,14 +27,6 @@ module Neovim
27
27
  # 2. Define the +DirChanged+ event to update the provider's pwd.
28
28
  def self.__define_setup(plug)
29
29
  plug.__send__(:setup) do |client|
30
- $stdout.define_singleton_method(:write) do |string|
31
- client.out_write(string + "\n")
32
- end
33
-
34
- $stderr.define_singleton_method(:write) do |string|
35
- client.err_writeln(string)
36
- end
37
-
38
30
  begin
39
31
  cid = client.api.channel_id
40
32
  client.command("au DirChanged * call rpcrequest(#{cid}, 'ruby_chdir', v:event)")
@@ -107,7 +99,9 @@ module Neovim
107
99
  Vim.__refresh_globals(client)
108
100
 
109
101
  __with_exception_handling(client) do
110
- yield
102
+ __with_std_streams(client) do
103
+ yield
104
+ end
111
105
  end
112
106
  nil
113
107
  end
@@ -116,13 +110,30 @@ module Neovim
116
110
  def self.__with_exception_handling(client)
117
111
  begin
118
112
  yield
119
- rescue SignalException => sig
120
- raise sig
121
- rescue Exception => e
122
- msg = [e.class, e.message.gsub("\n", " ")].join(": ")
123
- client.err_writeln(msg.lines.first.strip)
113
+ rescue ScriptError, StandardError => e
114
+ msg = [e.class, e.message].join(": ")
115
+ client.err_writeln(msg)
116
+ end
117
+ end
118
+ private_class_method :__with_exception_handling
119
+
120
+ def self.__with_std_streams(client)
121
+ old_stdout = $stdout.dup
122
+ old_stderr = $stderr.dup
123
+
124
+ $stdout, $stderr = StringIO.new, StringIO.new
125
+
126
+ begin
127
+ yield
128
+
129
+ client.out_write($stdout.string + $/) if $stdout.length > 0
130
+ client.err_writeln($stderr.string) if $stderr.length > 0
131
+ ensure
132
+ $stdout = old_stdout
133
+ $stderr = old_stderr
124
134
  end
125
135
  end
136
+ private_class_method :__with_std_streams
126
137
 
127
138
  def self.__update_lines_in_chunks(buffer, start, stop, size)
128
139
  (start..stop).each_slice(size) do |linenos|
@@ -8,28 +8,29 @@ module Neovim
8
8
  class Session
9
9
  include Logging
10
10
 
11
+ attr_writer :request_id
12
+
11
13
  def initialize(event_loop)
12
14
  @event_loop = event_loop
13
- @pending_messages = []
14
15
  @main_thread = Thread.current
15
16
  @main_fiber = Fiber.current
17
+ @response_handlers = Hash.new(-> {})
18
+ @pending_messages = []
19
+ @request_id = 0
16
20
  end
17
21
 
18
- # Run the event loop, handling messages in a +Fiber+.
19
- def run
22
+ def run(&block)
20
23
  @running = true
21
24
 
22
- while pending = @pending_messages.shift
23
- Fiber.new { yield pending if block_given? }.resume
25
+ while message = @pending_messages.shift
26
+ Fiber.new { message.received(@response_handlers, &block) }.resume
24
27
  end
25
28
 
26
29
  return unless @running
27
30
 
28
31
  @event_loop.run do |message|
29
- Fiber.new { yield message if block_given? }.resume
32
+ Fiber.new { message.received(@response_handlers, &block) }.resume
30
33
  end
31
- ensure
32
- @event_loop.shutdown
33
34
  end
34
35
 
35
36
  # Make an RPC request and return its response.
@@ -44,13 +45,21 @@ module Neovim
44
45
  # in the meantime are enqueued to be handled later.
45
46
  def request(method, *args)
46
47
  main_thread_only do
47
- if Fiber.current == @main_fiber
48
- response = blocking_request(method, *args)
49
- else
50
- response = yielding_request(method, *args)
48
+ @request_id += 1
49
+ blocking = Fiber.current == @main_fiber
50
+
51
+ log(:debug) do
52
+ {
53
+ method_name: method,
54
+ request_id: @request_id,
55
+ blocking: blocking,
56
+ arguments: args
57
+ }
51
58
  end
52
59
 
53
- response.value!
60
+ @event_loop.request(@request_id, method, *args)
61
+ response = blocking ? blocking_response : yielding_response
62
+ response.error ? raise(response.error) : response.value
54
63
  end
55
64
  end
56
65
 
@@ -58,7 +67,6 @@ module Neovim
58
67
  @event_loop.respond(request_id, value, error)
59
68
  end
60
69
 
61
- # Make an RPC notification. +nvim+ will not block waiting for a response.
62
70
  def notify(method, *args)
63
71
  @event_loop.notify(method, *args)
64
72
  end
@@ -75,43 +83,28 @@ module Neovim
75
83
 
76
84
  private
77
85
 
78
- def yielding_request(method, *args)
79
- log(:debug) do
80
- {
81
- :method_name => method,
82
- :arguments => args,
83
- }
84
- end
85
-
86
- fiber = Fiber.current
87
- @event_loop.request(method, *args) do |response|
88
- fiber.resume(response)
89
- end
90
- Fiber.yield
91
- end
92
-
93
- def blocking_request(method, *args)
94
- log(:debug) do
95
- {
96
- :method_name => method,
97
- :arguments => args,
98
- }
99
- end
100
-
86
+ def blocking_response
101
87
  response = nil
102
88
 
103
- @event_loop.request(method, *args) do |res|
89
+ @response_handlers[@request_id] = -> (res) {
104
90
  response = res
105
91
  stop
106
- end
107
-
108
- @event_loop.run do |message|
109
- @pending_messages << message
110
- end
92
+ }
111
93
 
94
+ run { |message| @pending_messages << message }
112
95
  response
113
96
  end
114
97
 
98
+ def yielding_response
99
+ fiber = Fiber.current
100
+
101
+ @response_handlers[@request_id] = -> (response) {
102
+ fiber.resume(response)
103
+ }
104
+
105
+ Fiber.yield
106
+ end
107
+
115
108
  def main_thread_only
116
109
  if Thread.current == @main_thread
117
110
  yield if block_given?