neovim 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -1
  3. data/CHANGELOG.md +9 -3
  4. data/CODE_OF_CONDUCT.md +46 -0
  5. data/README.md +2 -2
  6. data/Rakefile +6 -6
  7. data/lib/neovim.rb +8 -7
  8. data/lib/neovim/api.rb +87 -0
  9. data/lib/neovim/buffer.rb +7 -1
  10. data/lib/neovim/client.rb +32 -6
  11. data/lib/neovim/event_loop.rb +114 -0
  12. data/lib/neovim/event_loop/connection.rb +78 -0
  13. data/lib/neovim/event_loop/message_builder.rb +127 -0
  14. data/lib/neovim/event_loop/serializer.rb +37 -0
  15. data/lib/neovim/host.rb +28 -28
  16. data/lib/neovim/logging.rb +41 -19
  17. data/lib/neovim/plugin/dsl.rb +6 -6
  18. data/lib/neovim/remote_object.rb +2 -2
  19. data/lib/neovim/ruby_provider.rb +8 -6
  20. data/lib/neovim/ruby_provider/vim.rb +2 -2
  21. data/lib/neovim/session.rb +42 -79
  22. data/lib/neovim/tabpage.rb +1 -1
  23. data/lib/neovim/version.rb +1 -1
  24. data/lib/neovim/window.rb +1 -1
  25. data/script/dump_api +4 -2
  26. data/script/generate_docs +2 -1
  27. data/spec/{integration → acceptance}/rplugin_autocmd_spec.vim +1 -1
  28. data/spec/{integration → acceptance}/rplugin_command_spec.vim +6 -2
  29. data/spec/{integration → acceptance}/rplugin_function_spec.vim +5 -2
  30. data/spec/{integration → acceptance}/ruby_spec.vim +18 -0
  31. data/spec/{integration → acceptance}/rubydo_spec.vim +0 -0
  32. data/spec/{integration → acceptance}/rubyfile/call_foo.rb +0 -0
  33. data/spec/{integration → acceptance}/rubyfile/curbuf_ivar_get.rb +0 -0
  34. data/spec/{integration → acceptance}/rubyfile/curbuf_ivar_set.rb +0 -0
  35. data/spec/{integration → acceptance}/rubyfile/define_foo.rb +0 -0
  36. data/spec/acceptance/rubyfile/nested.rb +1 -0
  37. data/spec/acceptance/rubyfile/nested_inner.rb +1 -0
  38. data/spec/{integration → acceptance}/rubyfile/raise_standard_error.rb +0 -0
  39. data/spec/{integration → acceptance}/rubyfile/raise_syntax_error.rb +0 -0
  40. data/spec/acceptance/rubyfile/ruby_interface.rb +8 -0
  41. data/spec/{integration → acceptance}/rubyfile/set_pwd_after.rb +0 -0
  42. data/spec/{integration → acceptance}/rubyfile/set_pwd_before.rb +0 -0
  43. data/spec/{integration → acceptance}/rubyfile_spec.vim +9 -0
  44. data/spec/acceptance/runtime/init.vim +8 -0
  45. data/spec/acceptance/runtime/rplugin.vim +37 -0
  46. data/spec/{integration → acceptance}/runtime/rplugin/ruby/autocmds.rb +1 -1
  47. data/spec/{integration → acceptance}/runtime/rplugin/ruby/commands.rb +9 -1
  48. data/spec/{integration → acceptance}/runtime/rplugin/ruby/functions.rb +9 -1
  49. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader.vim +0 -0
  50. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader/assert.vim +0 -0
  51. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader/helper.vim +0 -0
  52. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader/parser.vim +0 -0
  53. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader/syntax.vim +0 -0
  54. data/spec/{integration → acceptance}/runtime/vader.vim/autoload/vader/window.vim +0 -0
  55. data/spec/{integration → acceptance}/runtime/vader.vim/plugin/vader.vim +0 -0
  56. data/spec/acceptance_spec.rb +74 -0
  57. data/spec/helper.rb +2 -32
  58. data/spec/neovim/api_spec.rb +59 -0
  59. data/spec/neovim/buffer_spec.rb +161 -2
  60. data/spec/neovim/client_spec.rb +18 -4
  61. data/spec/neovim/event_loop/connection_spec.rb +63 -0
  62. data/spec/neovim/event_loop/message_builder_spec.rb +105 -0
  63. data/spec/neovim/event_loop/serializer_spec.rb +63 -0
  64. data/spec/neovim/event_loop_spec.rb +81 -0
  65. data/spec/neovim/host/loader_spec.rb +0 -1
  66. data/spec/neovim/host_spec.rb +130 -161
  67. data/spec/neovim/logging_spec.rb +77 -5
  68. data/spec/neovim/session_spec.rb +54 -127
  69. data/spec/neovim/window_spec.rb +46 -0
  70. metadata +81 -81
  71. data/lib/neovim/session/api.rb +0 -95
  72. data/lib/neovim/session/event_loop.rb +0 -100
  73. data/lib/neovim/session/notification.rb +0 -19
  74. data/lib/neovim/session/request.rb +0 -31
  75. data/lib/neovim/session/rpc.rb +0 -95
  76. data/lib/neovim/session/serializer.rb +0 -62
  77. data/spec/integration/ruby_buffer_spec.rb +0 -151
  78. data/spec/integration/ruby_vim_spec.rb +0 -27
  79. data/spec/integration/ruby_window_spec.rb +0 -56
  80. data/spec/integration/runtime/init.vim +0 -9
  81. data/spec/integration_spec.rb +0 -119
  82. data/spec/neovim/session/api_spec.rb +0 -70
  83. data/spec/neovim/session/event_loop_spec.rb +0 -152
  84. data/spec/neovim/session/notification_spec.rb +0 -20
  85. data/spec/neovim/session/request_spec.rb +0 -36
  86. data/spec/neovim/session/rpc_spec.rb +0 -120
  87. data/spec/neovim/session/serializer_spec.rb +0 -62
@@ -0,0 +1,78 @@
1
+ require "neovim/logging"
2
+ require "socket"
3
+
4
+ module Neovim
5
+ class EventLoop
6
+ # The lowest level interface to reading from and writing to +nvim+.
7
+ #
8
+ # @api private
9
+ class Connection
10
+ include Logging
11
+
12
+ def self.tcp(host, port)
13
+ socket = Socket.tcp(host, port)
14
+ new(socket)
15
+ end
16
+
17
+ def self.unix(path)
18
+ socket = Socket.unix(path)
19
+ new(socket)
20
+ end
21
+
22
+ def self.child(_argv)
23
+ argv = _argv.include?("--embed") ? _argv : _argv + ["--embed"]
24
+
25
+ io = ::IO.popen(argv, "rb+").tap do |_io|
26
+ Process.detach(_io.pid)
27
+ end
28
+
29
+ new(io)
30
+ end
31
+
32
+ def self.stdio
33
+ new(STDIN, STDOUT)
34
+ end
35
+
36
+ def initialize(rd, wr=rd)
37
+ @rd, @wr = rd, wr
38
+ @running = false
39
+ end
40
+
41
+ # Write data to the underlying +IO+. This will block until all the
42
+ # data has been written.
43
+ def write(data)
44
+ written = 0
45
+ total = data.bytesize
46
+ log(:debug) { {:bytes => total} }
47
+
48
+ begin
49
+ while written < total
50
+ written += @wr.write_nonblock(data[written..-1])
51
+ end
52
+ rescue ::IO::WaitWritable
53
+ ::IO.select(nil, [@wr], nil, 1)
54
+ retry
55
+ ensure
56
+ @wr.flush
57
+ end
58
+ end
59
+
60
+ def read
61
+ @rd.readpartial(1024 * 16).tap do |bytes|
62
+ log(:debug) { {:bytes => bytes.bytesize} }
63
+ yield bytes
64
+ end
65
+ end
66
+
67
+ # Close underlying +IO+s.
68
+ def close
69
+ [@rd, @wr].each do |io|
70
+ begin
71
+ io.close
72
+ rescue ::IOError
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,127 @@
1
+ require "neovim/logging"
2
+
3
+ module Neovim
4
+ class EventLoop
5
+ # Handles formatting RPC messages and registering response callbacks for
6
+ # requests.
7
+ #
8
+ # @api private
9
+ class MessageBuilder
10
+ module StructToH
11
+ def to_h
12
+ each_pair.inject({}) { |acc, (k, v)| acc.merge(k => v) }
13
+ end
14
+ end
15
+
16
+ class Request < Struct.new(:id, :method_name, :arguments)
17
+ include StructToH
18
+
19
+ def sync?
20
+ true
21
+ end
22
+
23
+ def to_h
24
+ super.merge(:type => :request)
25
+ end
26
+ end
27
+
28
+ class Notification < Struct.new(:method_name, :arguments)
29
+ include StructToH
30
+
31
+ def sync?
32
+ false
33
+ end
34
+
35
+ def to_h
36
+ super.merge(:type => :notification)
37
+ end
38
+ end
39
+
40
+ class Response < Struct.new(:request_id, :value, :error)
41
+ include StructToH
42
+
43
+ def value!
44
+ error ? raise(error) : value
45
+ end
46
+
47
+ def to_h
48
+ super.merge(:type => :response)
49
+ end
50
+ end
51
+
52
+ include Logging
53
+
54
+ def initialize
55
+ @request_id = 0
56
+ @pending_requests = {}
57
+ end
58
+
59
+ def write(type, *write_args)
60
+ case type
61
+ when :request
62
+ method, args, response_handler = write_args
63
+
64
+ @request_id += 1
65
+ @pending_requests[@request_id] = response_handler
66
+
67
+ log(:debug) do
68
+ {
69
+ :type => type,
70
+ :request_id => @request_id,
71
+ :method_name => method,
72
+ :arguments => args,
73
+ }
74
+ end
75
+
76
+ yield [0, @request_id, method, args]
77
+ when :response
78
+ reqid, value, error = write_args
79
+
80
+ log(:debug) do
81
+ {
82
+ :type => type,
83
+ :request_id => reqid,
84
+ :value => value,
85
+ :error => error,
86
+ }
87
+ end
88
+
89
+ yield [1, reqid, error, value]
90
+ when :notification
91
+ method, args = write_args
92
+
93
+ log(:debug) do
94
+ {
95
+ :type => type,
96
+ :method_name => method,
97
+ :arguments => args,
98
+ }
99
+ end
100
+
101
+ yield [2, method, args]
102
+ else
103
+ raise "Unknown RPC message type #{type}"
104
+ end
105
+ end
106
+
107
+ def read((kind, *payload))
108
+ case kind
109
+ when 0
110
+ message = Request.new(*payload)
111
+ log(:debug) { message.to_h }
112
+ yield message
113
+ when 2
114
+ message = Notification.new(*payload)
115
+ log(:debug) { message.to_h }
116
+ yield message
117
+ when 1
118
+ reqid, (_, error), result = payload
119
+ handler = @pending_requests.delete(reqid) || Proc.new {}
120
+ message = Response.new(reqid, result, error)
121
+ log(:debug) { message.to_h }
122
+ handler.call(message)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,37 @@
1
+ require "neovim/logging"
2
+ require "msgpack"
3
+
4
+ module Neovim
5
+ class EventLoop
6
+ # Handles serializing RPC messages to and from MessagePack
7
+ #
8
+ # @api private
9
+ class Serializer
10
+ include Logging
11
+
12
+ def initialize(unpacker = MessagePack::Unpacker.new)
13
+ @unpacker = unpacker
14
+ end
15
+
16
+ # Serialize an RPC message
17
+ def write(obj)
18
+ log(:debug) { {:object => obj} }
19
+ yield MessagePack.pack(obj)
20
+ end
21
+
22
+ def read(bytes)
23
+ @unpacker.feed_each(bytes) do |obj|
24
+ log(:debug) { {:object => obj} }
25
+ yield obj
26
+ end
27
+ end
28
+
29
+ def register_type(id, &block)
30
+ @unpacker.register_type(id) do |data|
31
+ index = MessagePack.unpack(data)
32
+ block.call(index)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -1,4 +1,6 @@
1
1
  require "neovim"
2
+ require "neovim/client"
3
+ require "neovim/event_loop"
2
4
  require "neovim/host/loader"
3
5
 
4
6
  module Neovim
@@ -6,23 +8,20 @@ module Neovim
6
8
  class Host
7
9
  include Logging
8
10
 
9
- attr_reader :handlers, :specs
10
-
11
11
  # Start a plugin host. This is called by the +nvim-ruby-host+ executable,
12
12
  # which is spawned by +nvim+ to discover and run Ruby plugins, and acts as
13
13
  # the bridge between +nvim+ and the plugin.
14
- def self.run(rplugin_paths, options={})
15
- session = options.fetch(:session) { Session.stdio }
16
- client = options.fetch(:client) { Client.new(session) }
14
+ def self.run(rplugin_paths, event_loop=EventLoop.stdio)
15
+ client = Client.from_event_loop(event_loop)
17
16
 
18
- new(session, client).tap do |host|
17
+ new(client).tap do |host|
19
18
  Loader.new(host).load(rplugin_paths)
20
19
  end.run
21
20
  end
22
21
 
23
- def initialize(session, client)
24
- @session = session
22
+ def initialize(client)
25
23
  @client = client
24
+ @session = client.session
26
25
  @handlers = {"poll" => poll_handler, "specs" => specs_handler}
27
26
  @specs = {}
28
27
  end
@@ -40,67 +39,68 @@ module Neovim
40
39
  # Run the event loop, passing received messages to the appropriate handler.
41
40
  def run
42
41
  @session.run { |msg| handle(msg) }
43
- rescue => e
44
- fatal("got unexpected error #{e.inspect}")
45
- debug(e.backtrace.join("\n"))
42
+ ensure
43
+ @client.shutdown
44
+ @session.shutdown
46
45
  end
47
46
 
48
47
  # Handle messages received from the host. Sends a +Neovim::Client+ along
49
48
  # with the message to be used in plugin callbacks.
50
49
  def handle(message)
51
- debug("received #{message.inspect}")
50
+ log(:debug) { message.to_h }
52
51
 
53
52
  @handlers.
54
53
  fetch(message.method_name, default_handler).
55
54
  call(@client, message)
56
- rescue => e
57
- fatal("got unexpected error #{e.inspect}")
58
- debug(e.backtrace.join("\n"))
55
+ rescue SignalException => ex
56
+ log_exception(:debug, ex, __method__)
57
+ raise ex
58
+ rescue => ex
59
+ log_exception(:fatal, ex, __method__)
59
60
  end
60
61
 
61
62
  private
62
63
 
63
64
  def poll_handler
64
65
  @poll_handler ||= Proc.new do |_, req|
65
- debug("received 'poll' request #{req.inspect}")
66
- req.respond("ok")
66
+ @session.respond(req.id, "ok")
67
67
  end
68
68
  end
69
69
 
70
70
  def specs_handler
71
71
  @specs_handler ||= Proc.new do |_, req|
72
- debug("received 'specs' request #{req.inspect}")
73
72
  source = req.arguments.fetch(0)
74
73
 
75
74
  if @specs.key?(source)
76
- req.respond(@specs.fetch(source))
75
+ @session.respond(req.id, @specs.fetch(source))
77
76
  else
78
- req.error("Unknown plugin #{source}")
77
+ @session.respond(req.id, nil, "Unknown plugin #{source}")
79
78
  end
80
79
  end
81
80
  end
82
81
 
83
82
  def default_handler
84
83
  @default_handler ||= Proc.new do |_, message|
85
- if message.sync?
86
- message.error("Unknown request #{message.method_name}")
87
- end
84
+ next unless message.sync?
85
+ @session.respond(message.id, nil, "Unknown request #{message.method_name}")
88
86
  end
89
87
  end
90
88
 
91
89
  def wrap_plugin_handler(handler)
92
90
  Proc.new do |client, message|
93
91
  begin
94
- debug("received #{message.inspect}")
95
92
  args = message.arguments.flatten(1)
96
93
  result = handler.call(client, *args)
97
94
 
98
- message.respond(result) if message.sync?
95
+ @session.respond(message.id, result) if message.sync?
99
96
  rescue => e
100
- warn("got unexpected error #{e.inspect}")
101
- debug(e.backtrace.join("\n"))
97
+ log_exception(:error, e, __method__)
102
98
 
103
- message.error(e.message) if message.sync?
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
104
104
  end
105
105
  end
106
106
  end
@@ -1,17 +1,16 @@
1
1
  require "logger"
2
+ require "json"
2
3
 
3
4
  module Neovim
4
5
  # Mixed into classes for unified logging helper methods.
5
6
  #
6
7
  # @api private
7
8
  module Logging
8
- class << self
9
- attr_writer :logger
10
- end
9
+ TIMESTAMP_FORMAT = "%Y-%m-%dT%H:%M:%S.%6N".freeze
11
10
 
12
11
  # Return the value of @logger, or construct it from the environment.
13
12
  # $NVIM_RUBY_LOG_FILE specifies a file to log to (default +STDERR+), while
14
- # NVIM_RUBY_LOG_LEVEL specifies the level (default +WARN+)
13
+ # $NVIM_RUBY_LOG_LEVEL specifies the level (default +WARN+)
15
14
  def self.logger(env=ENV)
16
15
  return @logger if instance_variable_defined?(:@logger)
17
16
 
@@ -31,34 +30,57 @@ module Neovim
31
30
  @logger.level = Logger::WARN
32
31
  end
33
32
 
33
+ @logger.formatter = json_formatter
34
34
  @logger
35
35
  end
36
36
 
37
+ def self.logger=(logger)
38
+ logger.formatter = json_formatter
39
+ @logger = logger
40
+ end
41
+
37
42
  def self.included(base)
38
43
  base.send(:include, Helpers)
39
44
  end
40
45
 
41
- module Helpers
42
- private
43
-
44
- def fatal(msg)
45
- logger.fatal(self.class) { msg }
46
+ def self.json_formatter
47
+ Proc.new do |level, time, _, fields|
48
+ JSON.generate(
49
+ {
50
+ :_level => level,
51
+ :_time => time.strftime(TIMESTAMP_FORMAT)
52
+ }.merge!(fields)
53
+ ) << "\n"
46
54
  end
55
+ end
56
+ private_class_method :json_formatter
47
57
 
48
- def warn(msg)
49
- logger.warn(self.class) { msg }
50
- end
58
+ module Helpers
59
+ private
51
60
 
52
- def info(msg)
53
- logger.info(self.class) { msg }
61
+ def log(level, _method=nil, &block)
62
+ begin
63
+ Logging.logger.public_send(level) do
64
+ {
65
+ :_class => self.class,
66
+ :_method => _method || block.binding.eval("__method__"),
67
+ }.merge!(block.call)
68
+ end
69
+ rescue => ex
70
+ Logging.logger.error("failed to log: #{ex.inspect}")
71
+ end
72
+ rescue
73
+ # Inability to log shouldn't abort process
54
74
  end
55
75
 
56
- def debug(msg)
57
- logger.debug(self.class) { msg }
58
- end
76
+ def log_exception(level, ex, _method)
77
+ log(level, _method) do
78
+ {:exception => ex.class, :message => ex.message}
79
+ end
59
80
 
60
- def logger
61
- Logging.logger
81
+ log(:debug, _method) do
82
+ {:exception => ex.class, :message => ex.message, :backtrace => ex.backtrace}
83
+ end
62
84
  end
63
85
  end
64
86
  end