neovim 0.7.1 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/docs.yml +39 -0
  3. data/.github/workflows/tests.yml +64 -0
  4. data/.gitignore +2 -0
  5. data/CHANGELOG.md +22 -0
  6. data/CODE_OF_CONDUCT.md +3 -3
  7. data/README.md +19 -20
  8. data/Rakefile +21 -11
  9. data/exe/neovim-ruby-host +5 -0
  10. data/lib/neovim/buffer.rb +120 -41
  11. data/lib/neovim/client.rb +304 -39
  12. data/lib/neovim/client_info.rb +46 -0
  13. data/lib/neovim/connection.rb +7 -2
  14. data/lib/neovim/event_loop.rb +8 -4
  15. data/lib/neovim/host/cli.rb +41 -0
  16. data/lib/neovim/host.rb +3 -0
  17. data/lib/neovim/logging.rb +1 -1
  18. data/lib/neovim/message.rb +1 -1
  19. data/lib/neovim/plugin/dsl.rb +6 -0
  20. data/lib/neovim/plugin.rb +7 -2
  21. data/lib/neovim/remote_object.rb +5 -2
  22. data/lib/neovim/ruby_provider/object_ext.rb +5 -0
  23. data/lib/neovim/ruby_provider/vim.rb +6 -5
  24. data/lib/neovim/ruby_provider.rb +29 -10
  25. data/lib/neovim/session.rb +21 -16
  26. data/lib/neovim/tabpage.rb +8 -15
  27. data/lib/neovim/version.rb +1 -1
  28. data/lib/neovim/window.rb +45 -33
  29. data/lib/neovim.rb +12 -3
  30. data/neovim.gemspec +4 -5
  31. data/script/ci/download_nvim.sh +40 -0
  32. data/script/dump_api.rb +1 -1
  33. data/script/generate_docs.rb +6 -7
  34. data/script/run_acceptance.rb +15 -11
  35. data/spec/acceptance/client_info_spec.vim +42 -0
  36. data/spec/acceptance/rplugin_command_spec.vim +2 -2
  37. data/spec/acceptance/ruby_spec.vim +18 -11
  38. data/spec/acceptance/rubydo_spec.vim +9 -0
  39. data/spec/acceptance/rubyeval_spec.vim +22 -0
  40. data/spec/acceptance/rubyfile/curbuf_ivar_get.rb +1 -1
  41. data/spec/acceptance/rubyfile/curbuf_ivar_set.rb +1 -1
  42. data/spec/acceptance/rubyfile/define_foo.rb +1 -1
  43. data/spec/acceptance/rubyfile/nested_inner.rb +1 -1
  44. data/spec/acceptance/rubyfile/set_pwd_after.rb +1 -1
  45. data/spec/acceptance/rubyfile/set_pwd_before.rb +1 -1
  46. data/spec/acceptance/rubyfile_spec.vim +19 -19
  47. data/spec/acceptance/runtime/init.vim +2 -2
  48. data/spec/acceptance/runtime/rplugin/ruby/commands.rb +2 -2
  49. data/spec/helper.rb +25 -7
  50. data/spec/neovim/api_spec.rb +1 -1
  51. data/spec/neovim/buffer_spec.rb +10 -6
  52. data/spec/neovim/client_info_spec.rb +77 -0
  53. data/spec/neovim/client_spec.rb +9 -2
  54. data/spec/neovim/connection_spec.rb +32 -4
  55. data/spec/neovim/current_spec.rb +1 -2
  56. data/spec/neovim/event_loop_spec.rb +16 -0
  57. data/spec/neovim/host/cli_spec.rb +94 -0
  58. data/spec/neovim/host_spec.rb +16 -14
  59. data/spec/neovim/line_range_spec.rb +1 -3
  60. data/spec/neovim/remote_object_spec.rb +1 -2
  61. data/spec/neovim/ruby_provider/buffer_ext_spec.rb +6 -7
  62. data/spec/neovim/ruby_provider/object_ext_spec.rb +10 -0
  63. data/spec/neovim/ruby_provider/vim_spec.rb +1 -1
  64. data/spec/neovim/ruby_provider/window_ext_spec.rb +7 -10
  65. data/spec/neovim/session_spec.rb +13 -40
  66. data/spec/neovim/window_spec.rb +1 -1
  67. data/spec/neovim_spec.rb +28 -51
  68. data/spec/support.rb +27 -1
  69. metadata +26 -44
  70. data/.coveralls.yml +0 -1
  71. data/.rubocop.yml +0 -118
  72. data/.travis.yml +0 -22
  73. data/appveyor.yml +0 -31
  74. data/bin/neovim-ruby-host +0 -18
  75. data/script/validate_docs.rb +0 -29
@@ -0,0 +1,46 @@
1
+ require "neovim/version"
2
+
3
+ module Neovim
4
+ # @api private
5
+ class ClientInfo
6
+ HOST_METHOD_SPEC = {poll: {}, specs: {nargs: 1}}.freeze
7
+
8
+ ATTRIBUTES = {
9
+ website: "https://github.com/neovim/neovim-ruby",
10
+ license: "MIT"
11
+ }.freeze
12
+
13
+ def self.for_host(host)
14
+ name = host.plugins.map(&:script_host?) == [true] ?
15
+ "ruby-script-host" :
16
+ "ruby-rplugin-host"
17
+
18
+ new(name, :host, HOST_METHOD_SPEC, ATTRIBUTES)
19
+ end
20
+
21
+ def self.for_client
22
+ new("ruby-client", :remote, {}, ATTRIBUTES)
23
+ end
24
+
25
+ def initialize(name, type, method_spec, attributes)
26
+ @name = name
27
+ @type = type
28
+ @method_spec = method_spec
29
+ @attributes = attributes
30
+
31
+ @version = ["major", "minor", "patch"]
32
+ .zip(Neovim::VERSION.segments)
33
+ .to_h
34
+ end
35
+
36
+ def to_args
37
+ [
38
+ @name,
39
+ @version,
40
+ @type,
41
+ @method_spec,
42
+ @attributes
43
+ ]
44
+ end
45
+ end
46
+ end
@@ -35,12 +35,12 @@ module Neovim
35
35
 
36
36
  @unpacker = MessagePack::Unpacker.new(@rd)
37
37
  @packer = MessagePack::Packer.new(@wr)
38
- @running = false
39
38
  end
40
39
 
41
40
  def write(object)
42
41
  log(:debug) { {object: object} }
43
- @packer.write(object).flush
42
+ @packer.write(object)
43
+ self
44
44
  end
45
45
 
46
46
  def read
@@ -49,6 +49,11 @@ module Neovim
49
49
  end
50
50
  end
51
51
 
52
+ def flush
53
+ @packer.flush
54
+ self
55
+ end
56
+
52
57
  def register_type(id)
53
58
  @unpacker.register_type(id) do |data|
54
59
  index = MessagePack.unpack(data)
@@ -34,8 +34,9 @@ module Neovim
34
34
  end
35
35
 
36
36
  def shutdown
37
- stop
37
+ @running = false
38
38
  @shutdown = true
39
+ @connection.close
39
40
  end
40
41
 
41
42
  def request(request_id, method, *args)
@@ -69,20 +70,23 @@ module Neovim
69
70
 
70
71
  def run
71
72
  @running = true
73
+ last_value = nil
72
74
 
73
75
  loop do
74
76
  break unless @running
75
77
  break if @shutdown
76
78
 
77
79
  begin
78
- yield read
79
- rescue EOFError => e
80
+ last_value = yield(read)
81
+ rescue EOFError, Errno::EPIPE => e
80
82
  log_exception(:debug, e, __method__)
81
83
  shutdown
82
84
  rescue => e
83
85
  log_exception(:error, e, __method__)
84
86
  end
85
87
  end
88
+
89
+ last_value
86
90
  ensure
87
91
  @connection.close if @shutdown
88
92
  end
@@ -102,7 +106,7 @@ module Neovim
102
106
  private
103
107
 
104
108
  def read
105
- array = @connection.read
109
+ array = @connection.flush.read
106
110
  Message.from_array(array)
107
111
  end
108
112
 
@@ -0,0 +1,41 @@
1
+ require "neovim/connection"
2
+ require "neovim/event_loop"
3
+ require "neovim/host"
4
+ require "neovim/version"
5
+ require "optparse"
6
+
7
+ module Neovim
8
+ class Host
9
+ # @api private
10
+ class CLI
11
+ def self.run(path, argv, inn, out, err)
12
+ cmd = File.basename(path)
13
+
14
+ OptionParser.new do |opts|
15
+ opts.on("-V", "--version") do
16
+ out.puts Neovim::VERSION
17
+ exit(0)
18
+ end
19
+
20
+ opts.on("-h", "--help") do
21
+ out.puts "Usage: #{cmd} [-hV] rplugin_path ..."
22
+ exit(0)
23
+ end
24
+ end.order!(argv)
25
+
26
+ if inn.tty?
27
+ err.puts("Can't run #{cmd} interactively.")
28
+ exit(1)
29
+ else
30
+ conn = Connection.new(inn, out)
31
+ event_loop = EventLoop.new(conn)
32
+
33
+ Host.run(argv, event_loop)
34
+ end
35
+ rescue OptionParser::InvalidOption => e
36
+ err.puts(e.message)
37
+ exit(1)
38
+ end
39
+ end
40
+ end
41
+ end
data/lib/neovim/host.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "neovim"
2
2
  require "neovim/client"
3
+ require "neovim/client_info"
3
4
  require "neovim/event_loop"
4
5
  require "neovim/host/loader"
5
6
 
@@ -78,6 +79,8 @@ module Neovim
78
79
 
79
80
  def initialize_client(request_id)
80
81
  @session.request_id = request_id
82
+ @session.notify(:nvim_set_client_info, *ClientInfo.for_host(self).to_args)
83
+
81
84
  @client = Client.from_event_loop(@event_loop, @session)
82
85
  end
83
86
 
@@ -18,7 +18,7 @@ module Neovim
18
18
 
19
19
  @logger = Logger.new(env_file || STDERR)
20
20
 
21
- if env_level
21
+ if /\S+/.match?(env_level)
22
22
  begin
23
23
  @logger.level = Integer(env_level)
24
24
  rescue ArgumentError
@@ -49,7 +49,7 @@ module Neovim
49
49
  end
50
50
 
51
51
  def received(handlers)
52
- handlers[request_id].call(self)
52
+ handlers.delete(request_id).call(self)
53
53
  end
54
54
  end
55
55
 
@@ -68,6 +68,12 @@ module Neovim
68
68
 
69
69
  private
70
70
 
71
+ # Mark this plugin as the Ruby script host started by nvim. Should only
72
+ # be used in +Neovim::RubyProvider+.
73
+ def script_host!
74
+ @plugin.script_host = true
75
+ end
76
+
71
77
  # Register a setup block to run once before the host starts. The block
72
78
  # should expect to receive a single argument, a +Neovim::Client+.
73
79
  #
data/lib/neovim/plugin.rb CHANGED
@@ -3,7 +3,7 @@ require "neovim/plugin/dsl"
3
3
  module Neovim
4
4
  # @api private
5
5
  class Plugin
6
- attr_accessor :handlers, :setup_blocks
6
+ attr_accessor :handlers, :setup_blocks, :script_host
7
7
  attr_reader :source
8
8
 
9
9
  def self.from_config_block(source)
@@ -13,9 +13,10 @@ module Neovim
13
13
  end
14
14
 
15
15
  def initialize(source)
16
- @handlers = []
17
16
  @source = source
17
+ @handlers = []
18
18
  @setup_blocks = []
19
+ @script_host = false
19
20
  end
20
21
 
21
22
  def specs
@@ -27,5 +28,9 @@ module Neovim
27
28
  def setup(client)
28
29
  @setup_blocks.each { |bl| bl.call(client) }
29
30
  end
31
+
32
+ def script_host?
33
+ !!@script_host
34
+ end
30
35
  end
31
36
  end
@@ -1,3 +1,5 @@
1
+ require "set"
2
+
1
3
  module Neovim
2
4
  # @abstract Superclass for all +nvim+ remote objects.
3
5
  #
@@ -37,7 +39,7 @@ module Neovim
37
39
 
38
40
  # Extend +methods+ to include RPC methods
39
41
  def methods(*args)
40
- super | rpc_methods
42
+ super | rpc_methods.to_a
41
43
  end
42
44
 
43
45
  # Extend +==+ to only look at class and index.
@@ -48,7 +50,8 @@ module Neovim
48
50
  private
49
51
 
50
52
  def rpc_methods
51
- @api.functions_for_object(self).map(&:method_name)
53
+ @rpc_methods ||=
54
+ @api.functions_for_object(self).map(&:method_name).to_set
52
55
  end
53
56
  end
54
57
  end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def to_msgpack(packer)
3
+ packer.pack(to_s)
4
+ end
5
+ end
@@ -15,7 +15,7 @@ module Vim
15
15
 
16
16
  # Delegate all method calls to the underlying +Neovim::Client+ object.
17
17
  def self.method_missing(method, *args, &block)
18
- if @__client.respond_to?(method)
18
+ if @__client
19
19
  @__client.public_send(method, *args, &block).tap do
20
20
  __refresh_globals(@__client)
21
21
  end
@@ -33,13 +33,14 @@ module Vim
33
33
  end
34
34
 
35
35
  def self.__refresh_globals(client)
36
- bufnr = client.evaluate("bufnr('%')")
36
+ bufid, winid = client.evaluate("[nvim_get_current_buf(), nvim_get_current_win()]")
37
+ session, api = client.session, client.api
37
38
 
38
- $curbuf = @__buffer_cache.fetch(bufnr) do
39
- @__buffer_cache[bufnr] = client.get_current_buf
39
+ $curbuf = @__buffer_cache.fetch(bufid) do
40
+ @__buffer_cache[bufid] = Buffer.new(bufid, session, api)
40
41
  end
41
42
 
42
- $curwin = client.get_current_win
43
+ $curwin = Window.new(winid, session, api)
43
44
  end
44
45
  end
45
46
 
@@ -1,4 +1,5 @@
1
1
  require "neovim/ruby_provider/vim"
2
+ require "neovim/ruby_provider/object_ext"
2
3
  require "neovim/ruby_provider/buffer_ext"
3
4
  require "neovim/ruby_provider/window_ext"
4
5
  require "stringio"
@@ -14,8 +15,11 @@ module Neovim
14
15
  Thread.abort_on_exception = true
15
16
 
16
17
  Neovim.plugin do |plug|
18
+ plug.__send__(:script_host!)
19
+
17
20
  __define_setup(plug)
18
21
  __define_ruby_execute(plug)
22
+ __define_ruby_eval(plug)
19
23
  __define_ruby_execute_file(plug)
20
24
  __define_ruby_do_range(plug)
21
25
  __define_ruby_chdir(plug)
@@ -42,12 +46,26 @@ module Neovim
42
46
  def self.__define_ruby_execute(plug)
43
47
  plug.__send__(:rpc, :ruby_execute) do |nvim, ruby|
44
48
  __wrap_client(nvim) do
45
- eval(ruby, TOPLEVEL_BINDING, "eval")
49
+ eval(ruby, TOPLEVEL_BINDING, "ruby_execute")
46
50
  end
51
+ nil
47
52
  end
48
53
  end
49
54
  private_class_method :__define_ruby_execute
50
55
 
56
+ # Evaluate the provided Ruby code, exposing the +Vim+ constant for
57
+ # interactions with the editor and returning the value.
58
+ #
59
+ # This is used by the +:rubyeval+ command.
60
+ def self.__define_ruby_eval(plug)
61
+ plug.__send__(:rpc, :ruby_eval) do |nvim, ruby|
62
+ __wrap_client(nvim) do
63
+ eval(ruby, TOPLEVEL_BINDING, "ruby_eval")
64
+ end
65
+ end
66
+ end
67
+ private_class_method :__define_ruby_eval
68
+
51
69
  # Evaluate the provided Ruby file, exposing the +Vim+ constant for
52
70
  # interactions with the editor.
53
71
  #
@@ -55,6 +73,7 @@ module Neovim
55
73
  def self.__define_ruby_execute_file(plug)
56
74
  plug.__send__(:rpc, :ruby_execute_file) do |nvim, path|
57
75
  __wrap_client(nvim) { load(path) }
76
+ nil
58
77
  end
59
78
  end
60
79
  private_class_method :__define_ruby_execute_file
@@ -73,14 +92,15 @@ module Neovim
73
92
  __start, __stop, __ruby = __args
74
93
  __buffer = __nvim.get_current_buf
75
94
 
76
- __update_lines_in_chunks(__buffer, __start, __stop, 5000) do |__lines|
95
+ __update_lines_in_chunks(__buffer, __start, __stop, 1_000) do |__lines|
77
96
  __lines.map do |__line|
78
97
  $_ = __line
79
- eval(__ruby, binding, "eval")
98
+ eval(__ruby, binding, "ruby_do_range")
80
99
  $_
81
100
  end
82
101
  end
83
102
  end
103
+ nil
84
104
  end
85
105
  end
86
106
  private_class_method :__define_ruby_do_range
@@ -101,7 +121,6 @@ module Neovim
101
121
  yield
102
122
  end
103
123
  end
104
- nil
105
124
  end
106
125
  private_class_method :__wrap_client
107
126
 
@@ -120,10 +139,10 @@ module Neovim
120
139
  $stdout, $stderr = StringIO.new, StringIO.new
121
140
 
122
141
  begin
123
- yield
124
-
125
- client.out_write($stdout.string + $/) if $stdout.size > 0
126
- client.err_writeln($stderr.string) if $stderr.size > 0
142
+ yield.tap do
143
+ client.out_write($stdout.string + $/) if $stdout.size > 0
144
+ client.err_writeln($stderr.string) if $stderr.size > 0
145
+ end
127
146
  ensure
128
147
  $stdout = old_stdout
129
148
  $stderr = old_stderr
@@ -134,9 +153,9 @@ module Neovim
134
153
  def self.__update_lines_in_chunks(buffer, start, stop, size)
135
154
  (start..stop).each_slice(size) do |linenos|
136
155
  start, stop = linenos[0] - 1, linenos[-1]
137
- lines = buffer.get_lines(start, stop, true)
156
+ lines = buffer.get_lines(start, stop, false)
138
157
 
139
- buffer.set_lines(start, stop, true, yield(lines))
158
+ buffer.set_lines(start, stop, false, yield(lines))
140
159
  end
141
160
  end
142
161
  private_class_method :__update_lines_in_chunks
@@ -11,6 +11,13 @@ module Neovim
11
11
 
12
12
  attr_writer :request_id
13
13
 
14
+ # @api private
15
+ class Disconnected < RuntimeError
16
+ def initialize
17
+ super("Disconnected from nvim process")
18
+ end
19
+ end
20
+
14
21
  def initialize(event_loop)
15
22
  @event_loop = event_loop
16
23
  @main_thread = Thread.current
@@ -21,19 +28,21 @@ module Neovim
21
28
  end
22
29
 
23
30
  def run(&block)
24
- @running = true
31
+ block ||= ->(msg) { @pending_messages << msg }
25
32
 
26
- while (pending = @pending_messages.shift)
27
- Fiber.new { pending.received(@response_handlers, &block) }.resume
28
- end
29
-
30
- return unless @running
33
+ @running = true
31
34
 
32
35
  @event_loop.run do |message|
33
36
  Fiber.new { message.received(@response_handlers, &block) }.resume
34
37
  end
35
38
  end
36
39
 
40
+ def next
41
+ return @pending_messages.shift if @pending_messages.any?
42
+
43
+ run { |msg| stop; msg }
44
+ end
45
+
37
46
  # Make an RPC request and return its response.
38
47
  #
39
48
  # If this method is called inside a callback, we are already inside a
@@ -60,7 +69,10 @@ module Neovim
60
69
 
61
70
  @event_loop.request(@request_id, method, *args)
62
71
  response = blocking ? blocking_response : yielding_response
63
- response.error ? raise(response.error) : response.value
72
+
73
+ raise(Disconnected) if response.nil?
74
+ raise(response.error) if response.error
75
+ response.value
64
76
  end
65
77
  end
66
78
 
@@ -85,15 +97,8 @@ module Neovim
85
97
  private
86
98
 
87
99
  def blocking_response
88
- response = nil
89
-
90
- @response_handlers[@request_id] = lambda do |res|
91
- response = res
92
- stop
93
- end
94
-
95
- run { |message| @pending_messages << message }
96
- response
100
+ @response_handlers[@request_id] = ->(res) { stop; res }
101
+ run
97
102
  end
98
103
 
99
104
  def yielding_response