neovim 0.6.2 → 0.7.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 (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?