New Upstream Release - ruby-neovim

Ready changes

Summary

Merged new upstream version: 0.9.1 (was: 0.9.0).

Resulting package

Built on 2023-08-20T16:48 (took 6m40s)

The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:

apt install -t fresh-releases ruby-neovim

Diff

diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 0000000..5830cb3
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,39 @@
+name: Docs
+on:
+  push:
+    branches: [main]
+  schedule:
+    - cron: '0 0 * * 0'
+jobs:
+  docs:
+    runs-on: ubuntu-latest
+    if: "!contains(github.event.head_commit.message, '[skip ci]')"
+    permissions:
+      contents: write
+      pull-requests: write
+    steps:
+      - uses: actions/checkout@v2
+        with:
+          repository: neovim/neovim-ruby
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: head
+      - name: Install libfuse
+        run: sudo apt install libfuse2
+      - name: Bundle install
+        run: |
+          bundle config set path vendor/bundle
+          bundle install --jobs 3 --retry 3
+      - name: Install Neovim
+        run: bundle exec rake ci:download_nvim
+      - name: Generate docs
+        env:
+          NVIM_EXECUTABLE: "_nvim/bin/nvim"
+        run: bundle exec rake docs:generate
+      - name: Open pull request
+        uses: peter-evans/create-pull-request@v3
+        with:
+          token: ${{ secrets.GITHUB_TOKEN }}
+          commit-message: "[skip ci] Update generated docs"
+          author: Docs Workflow <noreply@github.com>
+          title: "Update generated docs"
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..adb0b69
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,64 @@
+name: Tests
+on:
+  push:
+    branches: [main]
+  pull_request:
+    branches: [main]
+  schedule:
+    - cron: '0 0 * * 0'
+jobs:
+  linux_osx:
+    strategy:
+      fail-fast: false
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        ruby: [2.7, 3.0, 3.1, 3.2, head]
+    runs-on: ${{ matrix.os }}
+    if: "!contains(github.event.head_commit.message, '[skip ci]')"
+    steps:
+      - name: Fix up git URLs
+        run: echo -e '[url "https://github.com/"]\n  insteadOf = "git://github.com/"' >> ~/.gitconfig
+      - uses: actions/checkout@v2
+        with:
+          repository: neovim/neovim-ruby
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+          bundler-cache: true
+      - name: Install libfuse
+        run: sudo apt install libfuse2
+        if: matrix.os == 'ubuntu-latest'
+      - name: Install Neovim
+        run: bundle exec rake ci:download_nvim
+      - name: Run tests
+        env:
+          NVIM_EXECUTABLE: "_nvim/bin/nvim"
+        run: bundle exec rake spec
+  windows:
+    strategy:
+      fail-fast: false
+      matrix:
+        ruby: [2.7, 3.0, 3.1, 3.2]
+    runs-on: windows-latest
+    if: "!contains(github.event.head_commit.message, '[skip ci]')"
+    steps:
+      - name: Fix up git URLs
+        run: |
+          echo '[url "https://github.com/"]' >> ~/.gitconfig
+          echo '  insteadOf = "git://github.com/"' >> ~/.gitconfig
+      - uses: actions/checkout@v2
+        with:
+          repository: neovim/neovim-ruby
+      - uses: ruby/setup-ruby@v1
+        with:
+          ruby-version: ${{ matrix.ruby }}
+          bundler-cache: true
+      - name: Install Neovim
+        uses: crazy-max/ghaction-chocolatey@v1
+        with:
+          args: install neovim -fy --ignore-dependencies --ignore-checksums
+      - name: Run tests
+        env:
+          VIM_FLAVOR_HOME: 'D:\'
+          NVIM_EXECUTABLE: 'C:\tools\neovim\nvim-win64\bin\nvim'
+        run: bundle exec rake spec
diff --git a/.gitignore b/.gitignore
index ceb3c17..e6dc30c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,7 +18,7 @@ test/tmp
 test/version_tmp
 tmp
 spec/workspace
-spec/acceptance/runtime/flavors
+spec/acceptance/runtime/pack
 spec/acceptance/runtime/rplugin_manifest.vim
 vendor/bundle
 bin
diff --git a/.rubocop.yml b/.rubocop.yml
deleted file mode 100644
index c616e18..0000000
--- a/.rubocop.yml
+++ /dev/null
@@ -1,132 +0,0 @@
-AllCops:
-  Exclude:
-    - _neovim/**/*
-    - spec/acceptance/**/*
-    - spec/workspace/**/*
-    - vendor/**/*
-    - script/**/*
-    - bin/**/*
-
-Layout/EndOfLine:
-  EnforcedStyle: lf
-
-Layout/MultilineMethodCallIndentation:
-  EnforcedStyle: indented
-
-Layout/SpaceAroundEqualsInParameterDefault:
-  EnforcedStyle: no_space
-
-Layout/SpaceInsideHashLiteralBraces:
-  EnforcedStyle: no_space
-
-Lint/AmbiguousBlockAssociation:
-  Exclude:
-    - spec/**/*
-
-Lint/HandleExceptions:
-  Exclude:
-    - lib/neovim/ruby_provider.rb
-    - lib/neovim/logging.rb
-    - lib/neovim/connection.rb
-    - lib/neovim/line_range.rb
-
-Lint/RescueException:
-  Exclude:
-    - lib/neovim/host.rb
-
-Lint/UnderscorePrefixedVariableName:
-  Exclude:
-    - lib/neovim/ruby_provider.rb
-
-Lint/UselessAccessModifier:
-  Exclude:
-    - lib/neovim/buffer.rb
-    - lib/neovim/client.rb
-
-Lint/Void:
-  Exclude:
-    - lib/neovim/window.rb
-
-Metrics/AbcSize:
-  Max: 20
-
-Metrics/BlockLength:
-  Exclude:
-    - spec/**/*
-
-Metrics/ClassLength:
-  Max: 500
-
-Metrics/LineLength:
-  Max: 125
-
-Metrics/MethodLength:
-  Max: 50
-
-Metrics/ModuleLength:
-  Exclude:
-    - spec/**/*
-
-Metrics/ParameterLists:
-  Max: 6
-
-Naming/AccessorMethodName:
-  Exclude:
-    - lib/neovim/client.rb
-
-Naming/UncommunicativeMethodParamName:
-  Enabled: false
-
-Security/Eval:
-  Exclude:
-    - lib/neovim/ruby_provider.rb
-
-Style/BlockComments:
-  Exclude:
-    - lib/neovim/buffer.rb
-    - lib/neovim/client.rb
-    - lib/neovim/tabpage.rb
-    - lib/neovim/window.rb
-
-Style/ConditionalAssignment:
-  EnforcedStyle: assign_inside_condition
-  IncludeTernaryExpressions: false
-
-Style/DoubleNegation:
-  Enabled: false
-
-Style/FormatStringToken:
-  EnforcedStyle: unannotated
-
-Style/GlobalVars:
-  Exclude:
-    - lib/neovim/ruby_provider/vim.rb
-    - spec/neovim/ruby_provider/vim_spec.rb
-
-Style/MultilineTernaryOperator:
-  Enabled: false
-
-Style/ParallelAssignment:
-  Enabled: false
-
-Style/RescueStandardError:
-  EnforcedStyle: implicit
-
-Style/SpecialGlobalVars:
-  EnforcedStyle: use_perl_names
-
-Style/StringLiterals:
-  EnforcedStyle: double_quotes
-
-Style/StringLiteralsInInterpolation:
-  EnforcedStyle: double_quotes
-
-Style/SymbolArray:
-  EnforcedStyle: brackets
-
-Style/WordArray:
-  EnforcedStyle: brackets
-
-Style/ZeroLengthPredicate:
-  Exclude:
-    - lib/neovim/ruby_provider.rb
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index f6afb45..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,25 +0,0 @@
-language: ruby
-cache: bundler
-sudo: false
-dist: trusty
-
-branches:
-  only: master
-
-rvm:
-  - 2.2
-  - 2.3
-  - 2.4
-  - 2.5
-  - 2.6
-  - ruby-head
-
-before_install:
-  - eval "$(curl --connect-timeout 30 --retry 3 -Ss https://raw.githubusercontent.com/neovim/bot-ci/master/scripts/travis-setup.sh) nightly-x64"
-  - gem update --system --conservative || (gem install "rubygems-update:~>2.7" --no-document && update_rubygems)
-  - gem install --remote bundler || gem install --remote bundler -v "1.17.3"
-  - gem install --user-install executable-hooks
-  - bundle --version
-
-env: NVIM_RUBY_LOG_LEVEL=DEBUG NVIM_RUBY_LOG_FILE=ci.log
-script: bundle exec rake --trace
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 136863c..f808554 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,14 @@
+# 0.9.1
+
+- Fix bug where `Buffer#[]` with `0` returned the last line of the buffer
+  (https://github.com/neovim/neovim-ruby/issues/97)
+
+# 0.9.0
+
+- Add RPC support for `:rubyeval`.
+- Add `Neovim::Session#next`.
+- Rename `Neovim::Session::Exited` -> `Neovim::Session::Disconnected`.
+
 # 0.8.1
 
 - Set client info on host and client startup
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 2f64ac6..ce3ea11 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -40,7 +40,7 @@ Project maintainers who do not follow or enforce the Code of Conduct in good fai
 
 ## Attribution
 
-This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [https://contributor-covenant.org/version/1/4][version]
 
-[homepage]: http://contributor-covenant.org
-[version]: http://contributor-covenant.org/version/1/4/
+[homepage]: https://contributor-covenant.org
+[version]: https://contributor-covenant.org/version/1/4/
diff --git a/VimFlavor b/Flavorfile
similarity index 100%
rename from VimFlavor
rename to Flavorfile
diff --git a/Flavorfile.lock b/Flavorfile.lock
new file mode 100644
index 0000000..8033c65
--- /dev/null
+++ b/Flavorfile.lock
@@ -0,0 +1 @@
+thinca/vim-themis (737e5444713ba53a9dcfbe3b962239bd0bd9162e at master)
diff --git a/README.md b/README.md
index e237961..aa1fcf6 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,36 @@
 # Neovim Ruby
 
-[![Travis](https://travis-ci.org/neovim/neovim-ruby.svg?branch=master)](https://travis-ci.org/neovim/neovim-ruby)
-[![Build status](https://ci.appveyor.com/api/projects/status/y44k25tcdvvuq0ee?svg=true)](https://ci.appveyor.com/project/alexgenco/neovim-ruby)
+[![Build Status](https://github.com/neovim/neovim-ruby/workflows/Tests/badge.svg)](https://github.com/neovim/neovim-ruby/actions)
 [![Gem Version](https://badge.fury.io/rb/neovim.svg)](https://badge.fury.io/rb/neovim)
-[![Inline docs](http://inch-ci.org/github/neovim/neovim-ruby.svg?branch=master)](http://inch-ci.org/github/neovim/neovim-ruby)
 
-Ruby bindings for [Neovim](https://github.com/neovim/neovim).
-
-*Warning*: This project follows [Semantic Versioning](http://semver.org/), thus its API should be considered unstable until it reaches v1.0.0 ([spec](http://semver.org/#spec-item-4)).
+Ruby support for [Neovim](https://github.com/neovim/neovim).
 
 ## Installation
 
 Add this line to your application's Gemfile:
 
-    gem "neovim"
+```ruby
+gem "neovim"
+```
 
 And then execute:
 
-    $ bundle
+```shell
+bundle
+```
 
 Or install it yourself as:
 
-    $ gem install neovim
+```shell
+gem install neovim
+```
 
 ## Usage
 
-You can control a running `nvim` process by connecting to `$NVIM_LISTEN_ADDRESS`. For example, to connect to `nvim` over a UNIX domain socket, start it up like this:
+Neovim supports the `--listen` option for specifying an address to serve its RPC API. To connect to Neovim over a Unix socket, start it up like this:
 
 ```shell
-$ NVIM_LISTEN_ADDRESS=/tmp/nvim.sock nvim
+$ nvim --listen /tmp/nvim.sock
 ```
 
 You can then connect to that socket path to get a `Neovim::Client`:
@@ -38,7 +40,7 @@ require "neovim"
 client = Neovim.attach_unix("/tmp/nvim.sock")
 ```
 
-Refer to the [`Neovim` docs](http://www.rubydoc.info/github/neovim/neovim-ruby/master/Neovim) for other ways to connect to `nvim`, and the [`Neovim::Client` docs](http://www.rubydoc.info/github/neovim/neovim-ruby/master/Neovim/Client) for a summary of the client interface.
+Refer to the [`Neovim` docs](https://www.rubydoc.info/github/neovim/neovim-ruby/main/Neovim) for other ways to connect to `nvim`, and the [`Neovim::Client` docs](https://www.rubydoc.info/github/neovim/neovim-ruby/main/Neovim/Client) for a summary of the client interface.
 
 ### Plugins
 
@@ -70,7 +72,7 @@ end
 
 When you add or update a plugin, you will need to call `:UpdateRemotePlugins` to update the remote plugin manifest. See `:help remote-plugin-manifest` for more information.
 
-Refer to the [`Neovim::Plugin::DSL` docs](http://www.rubydoc.info/github/neovim/neovim-ruby/master/Neovim/Plugin/DSL) for a more complete overview of the `Neovim.plugin` DSL.
+Refer to the [`Neovim::Plugin::DSL` docs](https://www.rubydoc.info/github/neovim/neovim-ruby/main/Neovim/Plugin/DSL) for a more complete overview of the `Neovim.plugin` DSL.
 
 ### Vim Plugin Support
 
@@ -78,16 +80,16 @@ The Neovim gem also acts as a compatibility layer for Ruby plugins written for `
 
 ## Links
 
-* Source: <http://github.com/neovim/neovim-ruby>
-* Bugs:   <http://github.com/neovim/neovim-ruby/issues>
-* CI: <http://travis-ci.org/neovim/neovim-ruby>
+* Source: <https://github.com/neovim/neovim-ruby>
+* Bugs: <https://github.com/neovim/neovim-ruby/issues>
+* CI: <https://github.com/neovim/neovim-ruby/actions>
 * Documentation:
-    * Latest Gem: <http://rubydoc.info/gems/neovim>
-    * Master: <http://rubydoc.info/github/neovim/neovim-ruby/master/frames>
+  * Latest Gem: <https://rubydoc.info/gems/neovim>
+  * Main: <https://rubydoc.info/github/neovim/neovim-ruby/main>
 
 ## Contributing
 
-1. Fork it (http://github.com/neovim/neovim-ruby/fork)
+1. Fork it (<https://github.com/neovim/neovim-ruby/fork>)
 2. Create your feature branch (`git checkout -b my-new-feature`)
 3. Commit your changes (`git commit -am 'Add some feature'`)
 4. Push to the branch (`git push origin my-new-feature`)
diff --git a/Rakefile b/Rakefile
index f6335e5..85f8e5c 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,8 +1,7 @@
 require "bundler/gem_tasks"
 require "rspec/core/rake_task"
-require "rubocop/rake_task"
 
-RuboCop::RakeTask.new(:style)
+Bundler.setup
 
 namespace :spec do
   desc "Run functional specs"
@@ -10,15 +9,13 @@ namespace :spec do
 
   desc "Run acceptance specs"
   task acceptance: "acceptance:deps" do
-    run_script(:run_acceptance, "--reporter", "dot", "spec/acceptance")
+    run_script("run_acceptance.rb", "--reporter", "dot", "spec/acceptance")
   end
 
   namespace :acceptance do
     desc "Install acceptance spec dependencies"
     task :deps do
-      Bundler.with_clean_env do
-        sh "bundle exec vim-flavor update --vimfiles-path=spec/acceptance/runtime"
-      end
+      sh "vim-flavor update --vimfiles-path=spec/acceptance/runtime"
     end
   end
 end
@@ -26,30 +23,28 @@ end
 namespace :docs do
   desc "Generate Neovim remote API docs"
   task :generate do
-    run_script(:generate_docs)
-  end
-
-  desc "Validate generated documentation is up-to-date"
-  task :validate do
-    run_script(:validate_docs)
+    run_script("generate_docs.rb")
   end
 end
 
-namespace :scripts do
-  desc "Validate script syntax"
-  task :validate do
-    sh "ruby -c #{File.expand_path("script/*.rb", __dir__)}"
+namespace :ci do
+  task :download_nvim do
+    run_script("ci/download_nvim.sh")
   end
 end
 
-task default: [
-  :style,
-  :"docs:validate",
-  :"scripts:validate",
-  :"spec:functional",
-  :"spec:acceptance"
-]
+desc "Run specs"
+task spec: [:"spec:functional", :"spec:acceptance"]
+
+task default: :spec
 
-def run_script(script_name, *args)
-  ruby File.expand_path("script/#{script_name}.rb", __dir__), *args
+def run_script(relpath, *args)
+  path = File.expand_path("script/#{relpath}", __dir__)
+  cmd_handler = ->(ok, status) { ok || exit(status.exitstatus) }
+
+  if File.extname(path) == ".rb"
+    ruby(path, *args, &cmd_handler)
+  else
+    sh(path, *args, &cmd_handler)
+  end
 end
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 09bce7c..0000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-version: '{build}'
-
-environment:
-  matrix:
-    - RUBY_VERSION: 26
-    - RUBY_VERSION: 25
-    - RUBY_VERSION: 24
-    - RUBY_VERSION: 23
-    - RUBY_VERSION: 22
-
-cache:
-  - vendor/bundle
-
-install:
-  - set SSL_CERT_FILE=C:/ruby24-x64/ssl/cert.pem
-  - set PATH=C:\Ruby%RUBY_VERSION%\bin;C:\tools\neovim\Neovim\bin;%PATH%
-  - set NVIM_RUBY_LOG_LEVEL=DEBUG
-  - set NVIM_RUBY_LOG_FILE=%cd%\ci.log
-  - choco install neovim --pre -fy --ignore-dependencies --ignore-checksums
-  - bundle install --path vendor/bundle
-
-build: off
-
-branches:
-  only:
-    - master
-
-before_test:
-  - ruby -v
-  - gem -v
-  - bundle -v
-  - nvim -v
-
-test_script:
-  - bundle exec rake spec:functional spec:acceptance
diff --git a/debian/changelog b/debian/changelog
index d1fe46d..a4dcee9 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,4 +1,4 @@
-ruby-neovim (0.8.1-2) UNRELEASED; urgency=medium
+ruby-neovim (0.9.1-1) UNRELEASED; urgency=medium
 
   * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository,
     Repository-Browse.
@@ -11,8 +11,10 @@ ruby-neovim (0.8.1-2) UNRELEASED; urgency=medium
     line 3.
   * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository-Browse.
   * Update standards version to 4.6.2, no changes needed.
+  * New upstream release.
+  * New upstream release.
 
- -- Debian Janitor <janitor@jelmer.uk>  Sat, 14 Mar 2020 23:52:08 +0000
+ -- Debian Janitor <janitor@jelmer.uk>  Sun, 20 Aug 2023 16:42:44 -0000
 
 ruby-neovim (0.8.1-1) unstable; urgency=medium
 
diff --git a/debian/patches/remove_coveralls_pry_bundler.patch b/debian/patches/remove_coveralls_pry_bundler.patch
index 86a028c..c8e26c5 100644
--- a/debian/patches/remove_coveralls_pry_bundler.patch
+++ b/debian/patches/remove_coveralls_pry_bundler.patch
@@ -6,8 +6,10 @@ Description: Remove coveralls, pry and bundler/setup
 Author: Jason Pleau <jason@jpleau.ca>
 Last-Update: 2019-09-14
 
---- a/spec/helper.rb
-+++ b/spec/helper.rb
+Index: ruby-neovim.git/spec/helper.rb
+===================================================================
+--- ruby-neovim.git.orig/spec/helper.rb
++++ ruby-neovim.git/spec/helper.rb
 @@ -1,9 +1,9 @@
 -require "bundler/setup"
 +#require "bundler/setup"
diff --git a/exe/neovim-ruby-host b/exe/neovim-ruby-host
index 56905b4..11234ed 100755
--- a/exe/neovim-ruby-host
+++ b/exe/neovim-ruby-host
@@ -1,18 +1,5 @@
 #!/usr/bin/env ruby
 
-require "neovim/host"
+require "neovim/host/cli"
 
-ARGV.each do |arg|
-  break if arg == "--"
-
-  if ["--version", "-V"].include?(arg)
-    puts Neovim::VERSION
-    exit(0)
-  end
-end
-
-if STDIN.tty?
-  abort("Can't run neovim-ruby-host interactively.")
-else
-  Neovim::Host.run(ARGV)
-end
+Neovim::Host::CLI.run(__FILE__, ARGV, STDIN, STDOUT, STDERR)
diff --git a/lib/neovim.rb b/lib/neovim.rb
index a3058d0..8e201c7 100644
--- a/lib/neovim.rb
+++ b/lib/neovim.rb
@@ -10,11 +10,10 @@ require "neovim/version"
 # running +nvim+ instance programmatically or define a remote plugin to be
 # autoloaded by +nvim+.
 #
-# You can connect to a running +nvim+ instance by setting or inspecting the
-# +NVIM_LISTEN_ADDRESS+ environment variable and connecting via the appropriate
-# +attach_+ method. This is currently supported for both UNIX domain sockets
-# and TCP. You can also spawn and connect to an +nvim+ subprocess via
-# +Neovim.attach_child(argv)+.
+# You can connect to a running +nvim+ process using the appropriate +attach_+
+# method. This is currently supported for both UNIX domain sockets and TCP. You
+# can also spawn and connect to an +nvim+ subprocess using
+# +Neovim.attach_child+.
 #
 # You can define a remote plugin using the +Neovim.plugin+ DSL, which allows
 # you to register commands, functions, and autocmds. Plugins are autoloaded by
diff --git a/lib/neovim/buffer.rb b/lib/neovim/buffer.rb
index b73ec39..a2c3529 100644
--- a/lib/neovim/buffer.rb
+++ b/lib/neovim/buffer.rb
@@ -4,7 +4,7 @@ require "neovim/line_range"
 module Neovim
   # Class representing an +nvim+ buffer.
   #
-  # The methods documented here were generated using NVIM v0.3.7
+  # The methods documented here were generated using NVIM v0.9.1
   class Buffer < RemoteObject
     attr_reader :lines
 
@@ -54,7 +54,7 @@ module Neovim
     # @param index [Integer]
     # @return [String]
     def [](index)
-      check_index(index)
+      check_index(index, 1)
       @lines[index - 1]
     end
 
@@ -64,7 +64,7 @@ module Neovim
     # @param str [String]
     # @return [String]
     def []=(index, str)
-      check_index(index)
+      check_index(index, 1)
       @lines[index - 1] = str
     end
 
@@ -73,13 +73,16 @@ module Neovim
     # @param index [Integer]
     # @return [void]
     def delete(index)
-      check_index(index)
+      check_index(index, 1)
       @lines.delete(index - 1)
       nil
     end
 
     # Append a line after the given line (1-indexed).
     #
+    # Unlike the other methods, `0` is a valid index argument here, and inserts
+    # into the first line of the buffer.
+    #
     # To maintain backwards compatibility with +vim+, the cursor is forced back
     # to its previous position after inserting the line.
     #
@@ -87,7 +90,7 @@ module Neovim
     # @param str [String]
     # @return [String]
     def append(index, str)
-      check_index(index)
+      check_index(index, 0)
       window = @session.request(:nvim_get_current_win)
       cursor = window.cursor
 
@@ -127,8 +130,8 @@ module Neovim
 
     private
 
-    def check_index(index)
-      raise ArgumentError, "Index out of bounds" if index < 0
+    def check_index(index, min)
+      raise ArgumentError, "Index out of bounds" if index < min
     end
 
     public
@@ -164,6 +167,24 @@ module Neovim
   @param [Array<String>] replacement
   @return [void]
 
+@method set_text(start_row, start_col, end_row, end_col, replacement)
+  See +:h nvim_buf_set_text()+
+  @param [Integer] start_row
+  @param [Integer] start_col
+  @param [Integer] end_row
+  @param [Integer] end_col
+  @param [Array<String>] replacement
+  @return [void]
+
+@method get_text(start_row, start_col, end_row, end_col, opts)
+  See +:h nvim_buf_get_text()+
+  @param [Integer] start_row
+  @param [Integer] start_col
+  @param [Integer] end_row
+  @param [Integer] end_col
+  @param [Hash] opts
+  @return [Array<String>]
+
 @method get_offset(index)
   See +:h nvim_buf_get_offset()+
   @param [Integer] index
@@ -183,10 +204,19 @@ module Neovim
   @param [String] mode
   @return [Array<Hash>]
 
-@method get_commands(opts)
-  See +:h nvim_buf_get_commands()+
+@method set_keymap(mode, lhs, rhs, opts)
+  See +:h nvim_buf_set_keymap()+
+  @param [String] mode
+  @param [String] lhs
+  @param [String] rhs
   @param [Hash] opts
-  @return [Hash]
+  @return [void]
+
+@method del_keymap(mode, lhs)
+  See +:h nvim_buf_del_keymap()+
+  @param [String] mode
+  @param [String] lhs
+  @return [void]
 
 @method set_var(name, value)
   See +:h nvim_buf_set_var()+
@@ -199,21 +229,6 @@ module Neovim
   @param [String] name
   @return [void]
 
-@method get_option(name)
-  See +:h nvim_buf_get_option()+
-  @param [String] name
-  @return [Object]
-
-@method set_option(name, value)
-  See +:h nvim_buf_set_option()+
-  @param [String] name
-  @param [Object] value
-  @return [void]
-
-@method get_number
-  See +:h nvim_buf_get_number()+
-  @return [Integer]
-
 @method get_name
   See +:h nvim_buf_get_name()+
   @return [String]
@@ -231,11 +246,94 @@ module Neovim
   See +:h nvim_buf_is_valid()+
   @return [Boolean]
 
+@method del_mark(name)
+  See +:h nvim_buf_del_mark()+
+  @param [String] name
+  @return [Boolean]
+
+@method set_mark(name, line, col, opts)
+  See +:h nvim_buf_set_mark()+
+  @param [String] name
+  @param [Integer] line
+  @param [Integer] col
+  @param [Hash] opts
+  @return [Boolean]
+
 @method get_mark(name)
   See +:h nvim_buf_get_mark()+
   @param [String] name
   @return [Array<Integer>]
 
+@method call(fun)
+  See +:h nvim_buf_call()+
+  @param [LuaRef] fun
+  @return [Object]
+
+@method create_user_command(name, command, opts)
+  See +:h nvim_buf_create_user_command()+
+  @param [String] name
+  @param [Object] command
+  @param [Hash] opts
+  @return [void]
+
+@method del_user_command(name)
+  See +:h nvim_buf_del_user_command()+
+  @param [String] name
+  @return [void]
+
+@method get_commands(opts)
+  See +:h nvim_buf_get_commands()+
+  @param [Hash] opts
+  @return [Hash]
+
+@method get_number
+  See +:h nvim_buf_get_number()+
+  @return [Integer]
+
+@method clear_highlight(ns_id, line_start, line_end)
+  See +:h nvim_buf_clear_highlight()+
+  @param [Integer] ns_id
+  @param [Integer] line_start
+  @param [Integer] line_end
+  @return [void]
+
+@method set_virtual_text(src_id, line, chunks, opts)
+  See +:h nvim_buf_set_virtual_text()+
+  @param [Integer] src_id
+  @param [Integer] line
+  @param [Array] chunks
+  @param [Hash] opts
+  @return [Integer]
+
+@method get_extmark_by_id(ns_id, id, opts)
+  See +:h nvim_buf_get_extmark_by_id()+
+  @param [Integer] ns_id
+  @param [Integer] id
+  @param [Hash] opts
+  @return [Array<Integer>]
+
+@method get_extmarks(ns_id, start, end, opts)
+  See +:h nvim_buf_get_extmarks()+
+  @param [Integer] ns_id
+  @param [Object] start
+  @param [Object] end
+  @param [Hash] opts
+  @return [Array]
+
+@method set_extmark(ns_id, line, col, opts)
+  See +:h nvim_buf_set_extmark()+
+  @param [Integer] ns_id
+  @param [Integer] line
+  @param [Integer] col
+  @param [Hash] opts
+  @return [Integer]
+
+@method del_extmark(ns_id, id)
+  See +:h nvim_buf_del_extmark()+
+  @param [Integer] ns_id
+  @param [Integer] id
+  @return [Boolean]
+
 @method add_highlight(ns_id, hl_group, line, col_start, col_end)
   See +:h nvim_buf_add_highlight()+
   @param [Integer] ns_id
@@ -252,20 +350,16 @@ module Neovim
   @param [Integer] line_end
   @return [void]
 
-@method clear_highlight(ns_id, line_start, line_end)
-  See +:h nvim_buf_clear_highlight()+
-  @param [Integer] ns_id
-  @param [Integer] line_start
-  @param [Integer] line_end
-  @return [void]
+@method get_option(name)
+  See +:h nvim_buf_get_option()+
+  @param [String] name
+  @return [Object]
 
-@method set_virtual_text(ns_id, line, chunks, opts)
-  See +:h nvim_buf_set_virtual_text()+
-  @param [Integer] ns_id
-  @param [Integer] line
-  @param [Array] chunks
-  @param [Hash] opts
-  @return [Integer]
+@method set_option(name, value)
+  See +:h nvim_buf_set_option()+
+  @param [String] name
+  @param [Object] value
+  @return [void]
 
 =end
   end
diff --git a/lib/neovim/client.rb b/lib/neovim/client.rb
index 8743b69..06fd998 100644
--- a/lib/neovim/client.rb
+++ b/lib/neovim/client.rb
@@ -9,7 +9,7 @@ module Neovim
   # +RemoteObject+ subclasses (i.e. +Buffer+, +Window+, or +Tabpage+),
   # which similarly have dynamically generated interfaces.
   #
-  # The methods documented here were generated using NVIM v0.3.7
+  # The methods documented here were generated using NVIM v0.9.1
   #
   # @see Buffer
   # @see Window
@@ -117,6 +117,155 @@ module Neovim
 
 # The following methods are dynamically generated.
 =begin
+@method get_autocmds(opts)
+  See +:h nvim_get_autocmds()+
+  @param [Hash] opts
+  @return [Array]
+
+@method create_autocmd(event, opts)
+  See +:h nvim_create_autocmd()+
+  @param [Object] event
+  @param [Hash] opts
+  @return [Integer]
+
+@method del_autocmd(id)
+  See +:h nvim_del_autocmd()+
+  @param [Integer] id
+  @return [void]
+
+@method clear_autocmds(opts)
+  See +:h nvim_clear_autocmds()+
+  @param [Hash] opts
+  @return [void]
+
+@method create_augroup(name, opts)
+  See +:h nvim_create_augroup()+
+  @param [String] name
+  @param [Hash] opts
+  @return [Integer]
+
+@method del_augroup_by_id(id)
+  See +:h nvim_del_augroup_by_id()+
+  @param [Integer] id
+  @return [void]
+
+@method del_augroup_by_name(name)
+  See +:h nvim_del_augroup_by_name()+
+  @param [String] name
+  @return [void]
+
+@method exec_autocmds(event, opts)
+  See +:h nvim_exec_autocmds()+
+  @param [Object] event
+  @param [Hash] opts
+  @return [void]
+
+@method parse_cmd(str, opts)
+  See +:h nvim_parse_cmd()+
+  @param [String] str
+  @param [Hash] opts
+  @return [Hash]
+
+@method cmd(cmd, opts)
+  See +:h nvim_cmd()+
+  @param [Hash] cmd
+  @param [Hash] opts
+  @return [String]
+
+@method create_user_command(name, command, opts)
+  See +:h nvim_create_user_command()+
+  @param [String] name
+  @param [Object] command
+  @param [Hash] opts
+  @return [void]
+
+@method del_user_command(name)
+  See +:h nvim_del_user_command()+
+  @param [String] name
+  @return [void]
+
+@method get_commands(opts)
+  See +:h nvim_get_commands()+
+  @param [Hash] opts
+  @return [Hash]
+
+@method exec(src, output)
+  See +:h nvim_exec()+
+  @param [String] src
+  @param [Boolean] output
+  @return [String]
+
+@method command_output(command)
+  See +:h nvim_command_output()+
+  @param [String] command
+  @return [String]
+
+@method execute_lua(code, args)
+  See +:h nvim_execute_lua()+
+  @param [String] code
+  @param [Array] args
+  @return [Object]
+
+@method get_hl_by_id(hl_id, rgb)
+  See +:h nvim_get_hl_by_id()+
+  @param [Integer] hl_id
+  @param [Boolean] rgb
+  @return [Hash]
+
+@method get_hl_by_name(name, rgb)
+  See +:h nvim_get_hl_by_name()+
+  @param [String] name
+  @param [Boolean] rgb
+  @return [Hash]
+
+@method get_option_info(name)
+  See +:h nvim_get_option_info()+
+  @param [String] name
+  @return [Hash]
+
+@method create_namespace(name)
+  See +:h nvim_create_namespace()+
+  @param [String] name
+  @return [Integer]
+
+@method get_namespaces
+  See +:h nvim_get_namespaces()+
+  @return [Hash]
+
+@method set_decoration_provider(ns_id, opts)
+  See +:h nvim_set_decoration_provider()+
+  @param [Integer] ns_id
+  @param [Hash] opts
+  @return [void]
+
+@method get_option_value(name, opts)
+  See +:h nvim_get_option_value()+
+  @param [String] name
+  @param [Hash] opts
+  @return [Object]
+
+@method set_option_value(name, value, opts)
+  See +:h nvim_set_option_value()+
+  @param [String] name
+  @param [Object] value
+  @param [Hash] opts
+  @return [void]
+
+@method get_all_options_info
+  See +:h nvim_get_all_options_info()+
+  @return [Hash]
+
+@method get_option_info2(name, opts)
+  See +:h nvim_get_option_info2()+
+  @param [String] name
+  @param [Hash] opts
+  @return [Hash]
+
+@method get_option(name)
+  See +:h nvim_get_option()+
+  @param [String] name
+  @return [Object]
+
 @method ui_attach(width, height, options)
   See +:h nvim_ui_attach()+
   @param [Integer] width
@@ -124,6 +273,11 @@ module Neovim
   @param [Hash] options
   @return [void]
 
+@method ui_set_focus(gained)
+  See +:h nvim_ui_set_focus()+
+  @param [Boolean] gained
+  @return [void]
+
 @method ui_detach
   See +:h nvim_ui_detach()+
   @return [void]
@@ -140,28 +294,59 @@ module Neovim
   @param [Object] value
   @return [void]
 
-@method command(command)
-  See +:h nvim_command()+
-  @param [String] command
+@method ui_try_resize_grid(grid, width, height)
+  See +:h nvim_ui_try_resize_grid()+
+  @param [Integer] grid
+  @param [Integer] width
+  @param [Integer] height
   @return [void]
 
-@method get_hl_by_name(name, rgb)
-  See +:h nvim_get_hl_by_name()+
+@method ui_pum_set_height(height)
+  See +:h nvim_ui_pum_set_height()+
+  @param [Integer] height
+  @return [void]
+
+@method ui_pum_set_bounds(width, height, row, col)
+  See +:h nvim_ui_pum_set_bounds()+
+  @param [Float] width
+  @param [Float] height
+  @param [Float] row
+  @param [Float] col
+  @return [void]
+
+@method get_hl_id_by_name(name)
+  See +:h nvim_get_hl_id_by_name()+
   @param [String] name
-  @param [Boolean] rgb
-  @return [Hash]
+  @return [Integer]
 
-@method get_hl_by_id(hl_id, rgb)
-  See +:h nvim_get_hl_by_id()+
-  @param [Integer] hl_id
-  @param [Boolean] rgb
+@method get_hl(ns_id, opts)
+  See +:h nvim_get_hl()+
+  @param [Integer] ns_id
+  @param [Hash] opts
   @return [Hash]
 
-@method feedkeys(keys, mode, escape_csi)
+@method set_hl(ns_id, name, val)
+  See +:h nvim_set_hl()+
+  @param [Integer] ns_id
+  @param [String] name
+  @param [Hash] val
+  @return [void]
+
+@method set_hl_ns(ns_id)
+  See +:h nvim_set_hl_ns()+
+  @param [Integer] ns_id
+  @return [void]
+
+@method set_hl_ns_fast(ns_id)
+  See +:h nvim_set_hl_ns_fast()+
+  @param [Integer] ns_id
+  @return [void]
+
+@method feedkeys(keys, mode, escape_ks)
   See +:h nvim_feedkeys()+
   @param [String] keys
   @param [String] mode
-  @param [Boolean] escape_csi
+  @param [Boolean] escape_ks
   @return [void]
 
 @method input(keys)
@@ -169,6 +354,16 @@ module Neovim
   @param [String] keys
   @return [Integer]
 
+@method input_mouse(button, action, modifier, grid, row, col)
+  See +:h nvim_input_mouse()+
+  @param [String] button
+  @param [String] action
+  @param [String] modifier
+  @param [Integer] grid
+  @param [Integer] row
+  @param [Integer] col
+  @return [void]
+
 @method replace_termcodes(str, from_part, do_lt, special)
   See +:h nvim_replace_termcodes()+
   @param [String] str
@@ -177,33 +372,17 @@ module Neovim
   @param [Boolean] special
   @return [String]
 
-@method command_output(command)
-  See +:h nvim_command_output()+
-  @param [String] command
-  @return [String]
-
-@method eval(expr)
-  See +:h nvim_eval()+
-  @param [String] expr
-  @return [Object]
-
-@method execute_lua(code, args)
-  See +:h nvim_execute_lua()+
+@method exec_lua(code, args)
+  See +:h nvim_exec_lua()+
   @param [String] code
   @param [Array] args
   @return [Object]
 
-@method call_function(fn, args)
-  See +:h nvim_call_function()+
-  @param [String] fn
-  @param [Array] args
-  @return [Object]
-
-@method call_dict_function(dict, fn, args)
-  See +:h nvim_call_dict_function()+
-  @param [Object] dict
-  @param [String] fn
-  @param [Array] args
+@method notify(msg, log_level, opts)
+  See +:h nvim_notify()+
+  @param [String] msg
+  @param [Integer] log_level
+  @param [Hash] opts
   @return [Object]
 
 @method strwidth(text)
@@ -215,6 +394,12 @@ module Neovim
   See +:h nvim_list_runtime_paths()+
   @return [Array<String>]
 
+@method get_runtime_file(name, all)
+  See +:h nvim_get_runtime_file()+
+  @param [String] name
+  @param [Boolean] all
+  @return [Array<String>]
+
 @method set_current_dir(dir)
   See +:h nvim_set_current_dir()+
   @param [String] dir
@@ -254,10 +439,18 @@ module Neovim
   @param [String] name
   @return [Object]
 
-@method get_option(name)
-  See +:h nvim_get_option()+
+@method set_vvar(name, value)
+  See +:h nvim_set_vvar()+
   @param [String] name
-  @return [Object]
+  @param [Object] value
+  @return [void]
+
+@method echo(chunks, history, opts)
+  See +:h nvim_echo()+
+  @param [Array] chunks
+  @param [Boolean] history
+  @param [Hash] opts
+  @return [void]
 
 @method out_write(str)
   See +:h nvim_out_write()+
@@ -300,6 +493,24 @@ module Neovim
   @param [Window] window
   @return [void]
 
+@method create_buf(listed, scratch)
+  See +:h nvim_create_buf()+
+  @param [Boolean] listed
+  @param [Boolean] scratch
+  @return [Buffer]
+
+@method open_term(buffer, opts)
+  See +:h nvim_open_term()+
+  @param [Buffer] buffer
+  @param [Hash] opts
+  @return [Integer]
+
+@method chan_send(chan, data)
+  See +:h nvim_chan_send()+
+  @param [Integer] chan
+  @param [String] data
+  @return [void]
+
 @method list_tabpages
   See +:h nvim_list_tabpages()+
   @return [Array<Tabpage>]
@@ -313,14 +524,20 @@ module Neovim
   @param [Tabpage] tabpage
   @return [void]
 
-@method create_namespace(name)
-  See +:h nvim_create_namespace()+
-  @param [String] name
-  @return [Integer]
+@method paste(data, crlf, phase)
+  See +:h nvim_paste()+
+  @param [String] data
+  @param [Boolean] crlf
+  @param [Integer] phase
+  @return [Boolean]
 
-@method get_namespaces
-  See +:h nvim_get_namespaces()+
-  @return [Hash]
+@method put(lines, type, after, follow)
+  See +:h nvim_put()+
+  @param [Array<String>] lines
+  @param [String] type
+  @param [Boolean] after
+  @param [Boolean] follow
+  @return [void]
 
 @method subscribe(event)
   See +:h nvim_subscribe()+
@@ -341,6 +558,16 @@ module Neovim
   See +:h nvim_get_color_map()+
   @return [Hash]
 
+@method get_context(opts)
+  See +:h nvim_get_context()+
+  @param [Hash] opts
+  @return [Hash]
+
+@method load_context(dict)
+  See +:h nvim_load_context()+
+  @param [Hash] dict
+  @return [Object]
+
 @method get_mode
   See +:h nvim_get_mode()+
   @return [Hash]
@@ -350,10 +577,19 @@ module Neovim
   @param [String] mode
   @return [Array<Hash>]
 
-@method get_commands(opts)
-  See +:h nvim_get_commands()+
+@method set_keymap(mode, lhs, rhs, opts)
+  See +:h nvim_set_keymap()+
+  @param [String] mode
+  @param [String] lhs
+  @param [String] rhs
   @param [Hash] opts
-  @return [Hash]
+  @return [void]
+
+@method del_keymap(mode, lhs)
+  See +:h nvim_del_keymap()+
+  @param [String] mode
+  @param [String] lhs
+  @return [void]
 
 @method get_api_info
   See +:h nvim_get_api_info()+
@@ -382,13 +618,6 @@ module Neovim
   @param [Array] calls
   @return [Array]
 
-@method parse_expression(expr, flags, highlight)
-  See +:h nvim_parse_expression()+
-  @param [String] expr
-  @param [String] flags
-  @param [Boolean] highlight
-  @return [Hash]
-
 @method list_uis
   See +:h nvim_list_uis()+
   @return [Array]
@@ -403,6 +632,74 @@ module Neovim
   @param [Integer] pid
   @return [Object]
 
+@method select_popupmenu_item(item, insert, finish, opts)
+  See +:h nvim_select_popupmenu_item()+
+  @param [Integer] item
+  @param [Boolean] insert
+  @param [Boolean] finish
+  @param [Hash] opts
+  @return [void]
+
+@method del_mark(name)
+  See +:h nvim_del_mark()+
+  @param [String] name
+  @return [Boolean]
+
+@method get_mark(name, opts)
+  See +:h nvim_get_mark()+
+  @param [String] name
+  @param [Hash] opts
+  @return [Array]
+
+@method eval_statusline(str, opts)
+  See +:h nvim_eval_statusline()+
+  @param [String] str
+  @param [Hash] opts
+  @return [Hash]
+
+@method exec2(src, opts)
+  See +:h nvim_exec2()+
+  @param [String] src
+  @param [Hash] opts
+  @return [Hash]
+
+@method command(command)
+  See +:h nvim_command()+
+  @param [String] command
+  @return [void]
+
+@method eval(expr)
+  See +:h nvim_eval()+
+  @param [String] expr
+  @return [Object]
+
+@method call_function(fn, args)
+  See +:h nvim_call_function()+
+  @param [String] fn
+  @param [Array] args
+  @return [Object]
+
+@method call_dict_function(dict, fn, args)
+  See +:h nvim_call_dict_function()+
+  @param [Object] dict
+  @param [String] fn
+  @param [Array] args
+  @return [Object]
+
+@method parse_expression(expr, flags, highlight)
+  See +:h nvim_parse_expression()+
+  @param [String] expr
+  @param [String] flags
+  @param [Boolean] highlight
+  @return [Hash]
+
+@method open_win(buffer, enter, config)
+  See +:h nvim_open_win()+
+  @param [Buffer] buffer
+  @param [Boolean] enter
+  @param [Hash] config
+  @return [Window]
+
 =end
   end
 end
diff --git a/lib/neovim/connection.rb b/lib/neovim/connection.rb
index 4f4a824..bc22249 100644
--- a/lib/neovim/connection.rb
+++ b/lib/neovim/connection.rb
@@ -35,7 +35,6 @@ module Neovim
 
       @unpacker = MessagePack::Unpacker.new(@rd)
       @packer = MessagePack::Packer.new(@wr)
-      @running = false
     end
 
     def write(object)
diff --git a/lib/neovim/event_loop.rb b/lib/neovim/event_loop.rb
index c59e596..1e22b41 100644
--- a/lib/neovim/event_loop.rb
+++ b/lib/neovim/event_loop.rb
@@ -36,7 +36,7 @@ module Neovim
     def shutdown
       @running = false
       @shutdown = true
-      run
+      @connection.close
     end
 
     def request(request_id, method, *args)
@@ -70,20 +70,23 @@ module Neovim
 
     def run
       @running = true
+      last_value = nil
 
       loop do
         break unless @running
         break if @shutdown
 
         begin
-          yield read
-        rescue EOFError => e
+          last_value = yield(read)
+        rescue EOFError, Errno::EPIPE => e
           log_exception(:debug, e, __method__)
           shutdown
         rescue => e
           log_exception(:error, e, __method__)
         end
       end
+
+      last_value
     ensure
       @connection.close if @shutdown
     end
diff --git a/lib/neovim/host/cli.rb b/lib/neovim/host/cli.rb
new file mode 100644
index 0000000..8cf22d0
--- /dev/null
+++ b/lib/neovim/host/cli.rb
@@ -0,0 +1,41 @@
+require "neovim/connection"
+require "neovim/event_loop"
+require "neovim/host"
+require "neovim/version"
+require "optparse"
+
+module Neovim
+  class Host
+    # @api private
+    class CLI
+      def self.run(path, argv, inn, out, err)
+        cmd = File.basename(path)
+
+        OptionParser.new do |opts|
+          opts.on("-V", "--version") do
+            out.puts Neovim::VERSION
+            exit(0)
+          end
+
+          opts.on("-h", "--help") do
+            out.puts "Usage: #{cmd} [-hV] rplugin_path ..."
+            exit(0)
+          end
+        end.order!(argv)
+
+        if inn.tty?
+          err.puts("Can't run #{cmd} interactively.")
+          exit(1)
+        else
+          conn = Connection.new(inn, out)
+          event_loop = EventLoop.new(conn)
+
+          Host.run(argv, event_loop)
+        end
+      rescue OptionParser::InvalidOption => e
+        err.puts(e.message)
+        exit(1)
+      end
+    end
+  end
+end
diff --git a/lib/neovim/logging.rb b/lib/neovim/logging.rb
index 0e15544..9fdf874 100644
--- a/lib/neovim/logging.rb
+++ b/lib/neovim/logging.rb
@@ -18,7 +18,7 @@ module Neovim
 
       @logger = Logger.new(env_file || STDERR)
 
-      if env_level
+      if /\S+/.match?(env_level)
         begin
           @logger.level = Integer(env_level)
         rescue ArgumentError
diff --git a/lib/neovim/ruby_provider.rb b/lib/neovim/ruby_provider.rb
index 354b100..34b5039 100644
--- a/lib/neovim/ruby_provider.rb
+++ b/lib/neovim/ruby_provider.rb
@@ -1,4 +1,5 @@
 require "neovim/ruby_provider/vim"
+require "neovim/ruby_provider/object_ext"
 require "neovim/ruby_provider/buffer_ext"
 require "neovim/ruby_provider/window_ext"
 require "stringio"
@@ -18,6 +19,7 @@ module Neovim
 
         __define_setup(plug)
         __define_ruby_execute(plug)
+        __define_ruby_eval(plug)
         __define_ruby_execute_file(plug)
         __define_ruby_do_range(plug)
         __define_ruby_chdir(plug)
@@ -44,12 +46,26 @@ module Neovim
     def self.__define_ruby_execute(plug)
       plug.__send__(:rpc, :ruby_execute) do |nvim, ruby|
         __wrap_client(nvim) do
-          eval(ruby, TOPLEVEL_BINDING, "eval")
+          eval(ruby, TOPLEVEL_BINDING, "ruby_execute")
         end
+        nil
       end
     end
     private_class_method :__define_ruby_execute
 
+    # Evaluate the provided Ruby code, exposing the +Vim+ constant for
+    # interactions with the editor and returning the value.
+    #
+    # This is used by the +:rubyeval+ command.
+    def self.__define_ruby_eval(plug)
+      plug.__send__(:rpc, :ruby_eval) do |nvim, ruby|
+        __wrap_client(nvim) do
+          eval(ruby, TOPLEVEL_BINDING, "ruby_eval")
+        end
+      end
+    end
+    private_class_method :__define_ruby_eval
+
     # Evaluate the provided Ruby file, exposing the +Vim+ constant for
     # interactions with the editor.
     #
@@ -57,6 +73,7 @@ module Neovim
     def self.__define_ruby_execute_file(plug)
       plug.__send__(:rpc, :ruby_execute_file) do |nvim, path|
         __wrap_client(nvim) { load(path) }
+        nil
       end
     end
     private_class_method :__define_ruby_execute_file
@@ -75,14 +92,15 @@ module Neovim
           __start, __stop, __ruby = __args
           __buffer = __nvim.get_current_buf
 
-          __update_lines_in_chunks(__buffer, __start, __stop, 5000) do |__lines|
+          __update_lines_in_chunks(__buffer, __start, __stop, 1_000) do |__lines|
             __lines.map do |__line|
               $_ = __line
-              eval(__ruby, binding, "eval")
+              eval(__ruby, binding, "ruby_do_range")
               $_
             end
           end
         end
+        nil
       end
     end
     private_class_method :__define_ruby_do_range
@@ -103,7 +121,6 @@ module Neovim
           yield
         end
       end
-      nil
     end
     private_class_method :__wrap_client
 
@@ -122,10 +139,10 @@ module Neovim
       $stdout, $stderr = StringIO.new, StringIO.new
 
       begin
-        yield
-
-        client.out_write($stdout.string + $/) if $stdout.size > 0
-        client.err_writeln($stderr.string) if $stderr.size > 0
+        yield.tap do
+          client.out_write($stdout.string + $/) if $stdout.size > 0
+          client.err_writeln($stderr.string) if $stderr.size > 0
+        end
       ensure
         $stdout = old_stdout
         $stderr = old_stderr
diff --git a/lib/neovim/ruby_provider/object_ext.rb b/lib/neovim/ruby_provider/object_ext.rb
new file mode 100644
index 0000000..e6645d7
--- /dev/null
+++ b/lib/neovim/ruby_provider/object_ext.rb
@@ -0,0 +1,5 @@
+class Object
+  def to_msgpack(packer)
+    packer.pack(to_s)
+  end
+end
diff --git a/lib/neovim/session.rb b/lib/neovim/session.rb
index e01086b..95d136c 100644
--- a/lib/neovim/session.rb
+++ b/lib/neovim/session.rb
@@ -12,9 +12,9 @@ module Neovim
     attr_writer :request_id
 
     # @api private
-    class Exited < RuntimeError
+    class Disconnected < RuntimeError
       def initialize
-        super("nvim process exited")
+        super("Disconnected from nvim process")
       end
     end
 
@@ -28,19 +28,21 @@ module Neovim
     end
 
     def run(&block)
-      @running = true
-
-      while (pending = @pending_messages.shift)
-        Fiber.new { pending.received(@response_handlers, &block) }.resume
-      end
+      block ||= ->(msg) { @pending_messages << msg }
 
-      return unless @running
+      @running = true
 
       @event_loop.run do |message|
         Fiber.new { message.received(@response_handlers, &block) }.resume
       end
     end
 
+    def next
+      return @pending_messages.shift if @pending_messages.any?
+
+      run { |msg| stop; msg }
+    end
+
     # Make an RPC request and return its response.
     #
     # If this method is called inside a callback, we are already inside a
@@ -68,7 +70,7 @@ module Neovim
         @event_loop.request(@request_id, method, *args)
         response = blocking ? blocking_response : yielding_response
 
-        raise(Exited) if response.nil?
+        raise(Disconnected) if response.nil?
         raise(response.error) if response.error
         response.value
       end
@@ -95,15 +97,8 @@ module Neovim
     private
 
     def blocking_response
-      response = nil
-
-      @response_handlers[@request_id] = lambda do |res|
-        response = res
-        stop
-      end
-
-      run { |message| @pending_messages << message }
-      response
+      @response_handlers[@request_id] = ->(res) { stop; res }
+      run
     end
 
     def yielding_response
diff --git a/lib/neovim/tabpage.rb b/lib/neovim/tabpage.rb
index 58bf185..7452275 100644
--- a/lib/neovim/tabpage.rb
+++ b/lib/neovim/tabpage.rb
@@ -3,7 +3,7 @@ require "neovim/remote_object"
 module Neovim
   # Class representing an +nvim+ tabpage.
   #
-  # The methods documented here were generated using NVIM v0.3.7
+  # The methods documented here were generated using NVIM v0.9.1
   class Tabpage < RemoteObject
 # The following methods are dynamically generated.
 =begin
diff --git a/lib/neovim/version.rb b/lib/neovim/version.rb
index 0e33f6e..5b57fbd 100644
--- a/lib/neovim/version.rb
+++ b/lib/neovim/version.rb
@@ -1,3 +1,3 @@
 module Neovim
-  VERSION = Gem::Version.new("0.8.1")
+  VERSION = Gem::Version.new("0.9.1")
 end
diff --git a/lib/neovim/window.rb b/lib/neovim/window.rb
index 63b0638..56a1769 100644
--- a/lib/neovim/window.rb
+++ b/lib/neovim/window.rb
@@ -3,7 +3,7 @@ require "neovim/remote_object"
 module Neovim
   # Class representing an +nvim+ window.
   #
-  # The methods documented here were generated using NVIM v0.3.7
+  # The methods documented here were generated using NVIM v0.9.1
   class Window < RemoteObject
     # Get the buffer displayed in the window
     #
@@ -64,6 +64,26 @@ module Neovim
 
 # The following methods are dynamically generated.
 =begin
+@method get_option(name)
+  See +:h nvim_win_get_option()+
+  @param [String] name
+  @return [Object]
+
+@method set_option(name, value)
+  See +:h nvim_win_set_option()+
+  @param [String] name
+  @param [Object] value
+  @return [void]
+
+@method set_config(config)
+  See +:h nvim_win_set_config()+
+  @param [Hash] config
+  @return [void]
+
+@method get_config
+  See +:h nvim_win_get_config()+
+  @return [Hash]
+
 @method get_buf
   See +:h nvim_win_get_buf()+
   @return [Buffer]
@@ -116,17 +136,6 @@ module Neovim
   @param [String] name
   @return [void]
 
-@method get_option(name)
-  See +:h nvim_win_get_option()+
-  @param [String] name
-  @return [Object]
-
-@method set_option(name, value)
-  See +:h nvim_win_set_option()+
-  @param [String] name
-  @param [Object] value
-  @return [void]
-
 @method get_position
   See +:h nvim_win_get_position()+
   @return [Array<Integer>]
@@ -143,6 +152,25 @@ module Neovim
   See +:h nvim_win_is_valid()+
   @return [Boolean]
 
+@method hide
+  See +:h nvim_win_hide()+
+  @return [void]
+
+@method close(force)
+  See +:h nvim_win_close()+
+  @param [Boolean] force
+  @return [void]
+
+@method call(fun)
+  See +:h nvim_win_call()+
+  @param [LuaRef] fun
+  @return [Object]
+
+@method set_hl_ns(ns_id)
+  See +:h nvim_win_set_hl_ns()+
+  @param [Integer] ns_id
+  @return [void]
+
 =end
   end
 end
diff --git a/neovim.gemspec b/neovim.gemspec
index e86bb66..e0caa62 100644
--- a/neovim.gemspec
+++ b/neovim.gemspec
@@ -23,10 +23,9 @@ Gem::Specification.new do |spec|
   spec.add_dependency "multi_json", "~> 1.0"
 
   spec.add_development_dependency "bundler"
-  spec.add_development_dependency "pry"
+  spec.add_development_dependency "pry", "~> 0.14"
   spec.add_development_dependency "pry-byebug"
   spec.add_development_dependency "rake"
-  spec.add_development_dependency "rspec", "~> 3.0"
-  spec.add_development_dependency "rubocop", "0.56.0"
-  spec.add_development_dependency "vim-flavor", "2.2.2"
+  spec.add_development_dependency "rspec"
+  spec.add_development_dependency "vim-flavor"
 end
diff --git a/script/ci/download_nvim.sh b/script/ci/download_nvim.sh
new file mode 100755
index 0000000..7c75312
--- /dev/null
+++ b/script/ci/download_nvim.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+set -eu
+
+: ${RUNNER_OS:?}
+
+case "$(echo "$RUNNER_OS" | tr "[:upper:]" "[:lower:]")" in
+  macos)
+    wget -nv -P /tmp \
+      "https://github.com/neovim/neovim/releases/download/stable/nvim-macos.tar.gz"
+    tar -C /tmp -xzf /tmp/nvim-macos.tar.gz
+    mv /tmp/nvim-macos ./_nvim
+    ;;
+  linux)
+    mkdir -p _nvim/bin
+    wget -nv -O _nvim/bin/nvim \
+      "https://github.com/neovim/neovim/releases/download/stable/nvim.appimage"
+    ;;
+  *)
+    echo "Unrecognized \$RUNNER_OS" >&2
+    exit 1
+    ;;
+esac
+
+chmod u+x _nvim/bin/nvim
+
+_nvim/bin/nvim --version
diff --git a/script/run_acceptance.rb b/script/run_acceptance.rb
index 4c99339..48f7feb 100755
--- a/script/run_acceptance.rb
+++ b/script/run_acceptance.rb
@@ -5,11 +5,13 @@ require "fileutils"
 ENV.delete("VIM")
 ENV.delete("VIMRUNTIME")
 
-acceptance_root = File.expand_path("../spec/acceptance", __dir__)
+root = File.expand_path("..", __dir__)
+acceptance_root = File.join(root, "spec/acceptance")
 themis_rtp = File.join(acceptance_root, "runtime")
-themis_home = File.join(themis_rtp, "flavors/thinca_vim-themis")
+themis_home = File.join(themis_rtp, "pack/flavors/start/thinca_vim-themis")
 manifest = File.join(themis_rtp, "rplugin_manifest.vim")
 vimrc = File.join(themis_rtp, "init.vim")
+nvim = ENV.fetch("NVIM_EXECUTABLE", "nvim")
 
 themis_exe = Gem.win_platform? ?
   File.join(themis_home, "bin/themis.bat") :
@@ -17,19 +19,21 @@ themis_exe = Gem.win_platform? ?
 
 env = {
   "NVIM_RPLUGIN_MANIFEST" => manifest,
-  "THEMIS_VIM" => "nvim",
+  "THEMIS_VIM" => nvim,
   "THEMIS_HOME" => themis_home,
   "THEMIS_ARGS" => "-e -s --headless -u #{vimrc}"
 }
 
 FileUtils.rm_f(manifest)
 
-system(
-  env,
-  "nvim",
-  "-e", "-s", "--headless",
-  "-u", vimrc,
-  "+UpdateRemotePlugins", "+qa!"
-)
-
-exec(env, themis_exe, *ARGV)
+Dir.chdir(root) do
+  system(
+    env,
+    nvim,
+    "-e", "-s", "--headless",
+    "-u", vimrc,
+    "+UpdateRemotePlugins", "+qa!"
+  )
+
+  exec(env, themis_exe, *ARGV)
+end
diff --git a/script/validate_docs.rb b/script/validate_docs.rb
deleted file mode 100755
index 875ee6b..0000000
--- a/script/validate_docs.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/usr/bin/env ruby
-
-require "json"
-require "open-uri"
-
-url = "https://api.github.com/repos/neovim/neovim/releases/latest"
-
-begin
-  response = open(url) { |res| JSON.parse(res.read) }
-rescue SocketError, OpenURI::HTTPError => e
-  puts "Request failed: #{e}\n"
-  exit
-end
-
-release_version = response["name"][/NVIM v?(.+)$/, 1]
-
-client_file = File.read(
-  File.expand_path("../lib/neovim/client.rb", __dir__)
-)
-docs_version = client_file[
-  /The methods documented here were generated using NVIM v?(.+)$/,
-  1
-]
-
-if docs_version == release_version
-  puts "Documentation is up-to-date."
-else
-  abort "Documentation is out of date: expected #{release_version}, got #{docs_version}."
-end
diff --git a/spec/acceptance/rplugin_command_spec.vim b/spec/acceptance/rplugin_command_spec.vim
index ce2f780..0d6865e 100644
--- a/spec/acceptance/rplugin_command_spec.vim
+++ b/spec/acceptance/rplugin_command_spec.vim
@@ -77,8 +77,8 @@ function! s:suite.supports_register() abort
 endfunction
 
 function! s:suite.supports_completion() abort
-  RPluginCommandCompletion
-  call s:expect(g:rplugin_command_completion).to_equal("buffer")
+  RPluginCommandCompletion foo
+  call s:expect(g:rplugin_command_completion).to_equal(["buffer", "foo"])
 endfunction
 
 function! s:suite.supports_eval() abort
diff --git a/spec/acceptance/ruby_spec.vim b/spec/acceptance/ruby_spec.vim
index d26b6b8..b201718 100644
--- a/spec/acceptance/ruby_spec.vim
+++ b/spec/acceptance/ruby_spec.vim
@@ -2,11 +2,16 @@ let s:suite = themis#suite(":ruby")
 let s:expect = themis#helper("expect")
 
 function! s:suite.before_each() abort
+  let s:pwd = getcwd()
   1,$delete
   call append(0, ["one", "two"])
   unlet! s:var
 endfunction
 
+function! s:suite.after_each() abort
+  execute("cd " . s:pwd)
+endfunction
+
 function! s:suite.has_nvim() abort
   call s:expect(has("nvim")).to_equal(1)
 endfunction
@@ -27,7 +32,7 @@ endfunction
 
 function! s:suite.updates_working_directory() abort
   cd /
-  ruby Vim.command("let s:var = '#{Dir.pwd.sub(/^C:/, "")}'")
+  ruby Vim.command("let s:var = '#{Dir.pwd.sub(/^[A-Z]:/, "")}'")
   cd -
 
   call s:expect(s:var).to_equal("/")
diff --git a/spec/acceptance/rubyeval_spec.vim b/spec/acceptance/rubyeval_spec.vim
new file mode 100644
index 0000000..fe38e8c
--- /dev/null
+++ b/spec/acceptance/rubyeval_spec.vim
@@ -0,0 +1,22 @@
+let s:suite = themis#suite(":rubyeval")
+let s:expect = themis#helper("expect")
+
+function! s:suite.evaluates_ruby_objects() abort
+  call s:expect(rubyeval("123")).to_equal(123)
+  call s:expect(rubyeval("1.2")).to_equal(1.2)
+  call s:expect(rubyeval("'test'")).to_equal("test")
+  call s:expect(rubyeval("nil")).to_equal(v:null)
+  call s:expect(rubyeval("true")).to_equal(v:true)
+  call s:expect(rubyeval("false")).to_equal(v:false)
+  call s:expect(rubyeval("{x: 1}")).to_equal({"x": 1})
+  call s:expect(rubyeval(":test")).to_equal("test")
+  call s:expect(rubyeval(":test.class")).to_equal("Symbol")
+endfunction
+
+function! s:suite.propagates_exceptions() abort
+  try
+    rubyeval("raise 'BOOM'")
+    throw "Nothing raised"
+  catch /BOOM/
+  endtry
+endfunction
diff --git a/spec/acceptance/rubyfile/set_pwd_before.rb b/spec/acceptance/rubyfile/set_pwd_before.rb
index ab50062..838c78a 100644
--- a/spec/acceptance/rubyfile/set_pwd_before.rb
+++ b/spec/acceptance/rubyfile/set_pwd_before.rb
@@ -1 +1 @@
-Vim.command("let s:var = ['#{Dir.pwd.sub(/^C:/, '')}']")
+Vim.command("let s:var = ['#{Dir.pwd.sub(/^[A-Z]:/, '')}']")
diff --git a/spec/acceptance/rubyfile_spec.vim b/spec/acceptance/rubyfile_spec.vim
index a8c69eb..11dad7d 100644
--- a/spec/acceptance/rubyfile_spec.vim
+++ b/spec/acceptance/rubyfile_spec.vim
@@ -3,19 +3,17 @@ let s:expect = themis#helper("expect")
 
 function! s:suite.before() abort
   let s:pwd = getcwd()
-  cd spec/acceptance/rubyfile
+  cd ./spec/acceptance/rubyfile
   unlet! s:var
+
+  1,$delete
+  call append(0, ["one", "two"])
 endfunction
 
 function! s:suite.after() abort
   execute("cd " . s:pwd)
 endfunction
 
-function! s:suite.before_each() abort
-  1,$delete
-  call append(0, ["one", "two"])
-endfunction
-
 function! s:suite.has_nvim() abort
   call s:expect(has("nvim")).to_equal(1)
 endfunction
diff --git a/spec/acceptance/runtime/rplugin/ruby/commands.rb b/spec/acceptance/runtime/rplugin/ruby/commands.rb
index dce9c65..938e60c 100644
--- a/spec/acceptance/runtime/rplugin/ruby/commands.rb
+++ b/spec/acceptance/runtime/rplugin/ruby/commands.rb
@@ -43,10 +43,10 @@ Neovim.plugin do |plug|
     nvim.set_var("rplugin_command_register", reg)
   end
 
-  plug.command(:RPluginCommandCompletion, complete: "buffer", sync: true) do |nvim|
+  plug.command(:RPluginCommandCompletion, complete: "buffer", nargs: 1, sync: true) do |nvim, arg|
     attrs = nvim.command_output("silent command RPluginCommandCompletion")
     compl = attrs.split($/).last.split[2]
-    nvim.set_var("rplugin_command_completion", compl)
+    nvim.set_var("rplugin_command_completion", [compl, arg])
   end
 
   plug.command(:RPluginCommandEval, eval: "g:to_eval", sync: true) do |nvim, to_eval|
diff --git a/spec/helper.rb b/spec/helper.rb
index 80fd792..2ea7b1b 100644
--- a/spec/helper.rb
+++ b/spec/helper.rb
@@ -11,6 +11,8 @@ require "timeout"
 
 require File.expand_path("support.rb", __dir__)
 
+ENV["NVIM_RUBY_LOG_LEVEL"] ||= "FATAL"
+
 RSpec.configure do |config|
   config.expect_with :rspec do |exp|
     exp.syntax = :expect
@@ -40,12 +42,33 @@ RSpec.configure do |config|
     timeout = spec.metadata.fetch(:timeout, 3)
 
     begin
-      Timeout.timeout(timeout) { spec.run }
+      Timeout.timeout(timeout) do
+        Support.clean_persistent_client
+        spec.run
+      end
     ensure
       Support.teardown_workspace
     end
   end
 
+  config.before(:example, :nvim_version) do |spec|
+    comparator = spec.metadata.fetch(:nvim_version)
+    requirement = Gem::Requirement.create(comparator)
+
+    nvim_version = Support
+      .nvim_version
+      .split("+")
+      .first
+
+    if !requirement.satisfied_by?(Gem::Version.new(nvim_version))
+      skip "Skipping on nvim #{nvim_version} (requires #{comparator})"
+    end
+  end
+
+  config.after(:suite) do
+    Support.persistent_client.shutdown
+  end
+
   Kernel.srand config.seed
 end
 
diff --git a/spec/neovim/api_spec.rb b/spec/neovim/api_spec.rb
index 6c655ed..b758435 100644
--- a/spec/neovim/api_spec.rb
+++ b/spec/neovim/api_spec.rb
@@ -2,7 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe API do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
+    let(:client) { Support.persistent_client }
 
     let(:api) do
       API.new(
diff --git a/spec/neovim/buffer_spec.rb b/spec/neovim/buffer_spec.rb
index 64cead5..395cf78 100644
--- a/spec/neovim/buffer_spec.rb
+++ b/spec/neovim/buffer_spec.rb
@@ -2,7 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe Buffer do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
+    let(:client) { Support.persistent_client }
     let(:buffer) { client.current.buffer }
 
     before do
@@ -11,8 +11,6 @@ module Neovim
       client.command("normal gg")
     end
 
-    after { client.shutdown }
-
     describe "#lines" do
       it "returns a LineRange" do
         expect(buffer.lines).to be_a(LineRange)
@@ -37,9 +35,9 @@ module Neovim
 
     describe "#number" do
       it "returns the buffer number" do
-        expect(buffer.number).to eq(1)
-        client.command("new")
-        expect(client.get_current_buf.number).to eq(2)
+        expect do
+          client.command("new")
+        end.to change { client.get_current_buf.number }
       end
     end
 
diff --git a/spec/neovim/client_spec.rb b/spec/neovim/client_spec.rb
index 699d3ce..7b0a8f3 100644
--- a/spec/neovim/client_spec.rb
+++ b/spec/neovim/client_spec.rb
@@ -2,8 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe Client do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
-    after { client.shutdown }
+    let(:client) { Support.persistent_client }
 
     describe "#set_option" do
       it "sets an option from two arguments" do
@@ -27,8 +26,9 @@ module Neovim
 
     describe "#shutdown" do
       it "causes nvim to exit" do
+        client = Neovim.attach_child(Support.child_argv)
         client.shutdown
-        expect { client.strwidth("hi") }.to raise_error(Neovim::Session::Exited)
+        expect { client.strwidth("hi") }.to raise_error(Neovim::Session::Disconnected)
       end
     end
 
diff --git a/spec/neovim/current_spec.rb b/spec/neovim/current_spec.rb
index 6c81442..121b7c7 100644
--- a/spec/neovim/current_spec.rb
+++ b/spec/neovim/current_spec.rb
@@ -2,9 +2,8 @@ require "helper"
 
 module Neovim
   RSpec.describe Current do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
+    let(:client) { Support.persistent_client }
     let(:current) { client.current }
-    after { client.shutdown }
 
     describe "#line" do
       it "returns an empty string if the current line is empty" do
diff --git a/spec/neovim/event_loop_spec.rb b/spec/neovim/event_loop_spec.rb
index dfa59a7..b44f1ea 100644
--- a/spec/neovim/event_loop_spec.rb
+++ b/spec/neovim/event_loop_spec.rb
@@ -58,6 +58,16 @@ module Neovim
         expect(message.method_name).to eq("foo_method")
       end
 
+      it "returns the last message received" do
+        server_wr.write(MessagePack.pack([0, 1, :foo_method, []]))
+        server_wr.flush
+
+        message = event_loop.run { |req| event_loop.stop; req }
+
+        expect(message.sync?).to eq(true)
+        expect(message.method_name).to eq("foo_method")
+      end
+
       it "shuts down after receiving EOFError" do
         run_thread = Thread.new do
           event_loop.run
diff --git a/spec/neovim/host/cli_spec.rb b/spec/neovim/host/cli_spec.rb
new file mode 100644
index 0000000..c71e197
--- /dev/null
+++ b/spec/neovim/host/cli_spec.rb
@@ -0,0 +1,94 @@
+require "helper"
+require "neovim/host/cli"
+
+begin
+  require "pty"
+rescue LoadError
+  # Not available on Windows
+end
+
+module Neovim
+  class Host
+    RSpec.describe CLI do
+      let(:stdin) { StringIO.new }
+      let(:stdout) { StringIO.new }
+      let(:stderr) { StringIO.new }
+
+      specify "-V" do
+        expect do
+          CLI.run("/exe/nv-rb-host", ["-V"], stdin, stdout, stderr)
+        end.to raise_error(SystemExit) { |e| expect(e.status).to eq(0) }
+
+        expect(stderr.string).to be_empty
+        expect(stdout.string).to eq(Neovim::VERSION.to_s + "\n")
+      end
+
+      specify "-h" do
+        expect do
+          CLI.run("/exe/nv-rb-host", ["-h"], stdin, stdout, stderr)
+        end.to raise_error(SystemExit) { |e| expect(e.status).to eq(0) }
+
+        expect(stderr.string).to be_empty
+        expect(stdout.string).to eq("Usage: nv-rb-host [-hV] rplugin_path ...\n")
+      end
+
+      it "fails with invalid arguments" do
+        expect do
+          CLI.run("/exe/nv-rb-host", ["-x"], stdin, stdout, stderr)
+        end.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
+
+        expect(stdout.string).to be_empty
+        expect(stderr.string).to eq("invalid option: -x\n")
+      end
+
+      it "fails when run interactively" do
+        if !defined?(PTY)
+          skip "Skipping without `pty` library."
+        end
+
+        PTY.open do |tty,|
+          expect do
+            CLI.run("/exe/nv-rb-host", ["plugin.rb"], tty, stdout, stderr)
+          end.to raise_error(SystemExit) { |e| expect(e.status).to eq(1) }
+
+          expect(stdout.string).to be_empty
+          expect(stderr.string).to eq("Can't run nv-rb-host interactively.\n")
+        end
+      end
+
+      it "starts a stdio host" do
+        nvim_r, host_w = IO.pipe
+        host_r, nvim_w = IO.pipe
+
+        nvim_u = MessagePack::Unpacker.new(nvim_r)
+        nvim_p = MessagePack::Packer.new(nvim_w)
+
+        Support.file_path("plugin").tap do |path|
+          File.write(path, "Neovim.plugin { |p| p.function('Foo') }")
+
+          thr = Thread.new do
+            CLI.run("/exe/nv-rb-host", [path], host_r, host_w, stderr)
+          end
+
+          begin
+            nvim_p.write([0, 1, :poll, []]).flush
+
+            expect(nvim_u.read[0..1]).to eq([2, "nvim_set_client_info"])
+            expect(nvim_u.read[0..2]).to eq([0, 2, "nvim_get_api_info"])
+
+            nvim_p.write([1, 2, nil, [10, {"functions" => {}, "types" => {}}]]).flush
+            expect(nvim_u.read).to eq([1, 1, nil, "ok"])
+
+            nvim_p.write([0, 3, :specs, [path]]).flush
+            *prefix, (payload, *) = nvim_u.read
+
+            expect(prefix).to eq([1, 3, nil])
+            expect(payload["name"]).to eq("Foo")
+          ensure
+            thr.kill.join
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/neovim/host_spec.rb b/spec/neovim/host_spec.rb
index 43642a3..ac7994a 100644
--- a/spec/neovim/host_spec.rb
+++ b/spec/neovim/host_spec.rb
@@ -41,10 +41,7 @@ module Neovim
         end
       end
 
-      after do
-        host_thread.kill while host_thread.alive?
-        host_thread.join
-      end
+      after { host_thread.kill.join }
 
       context "poll" do
         it "initializes a client, sets client info, and responds 'ok'" do
@@ -72,9 +69,7 @@ module Neovim
 
           expect(method).to eq("nvim_get_api_info")
 
-          session = Session.new(EventLoop.child(Support.child_argv))
-          api_info = session.request(:nvim_get_api_info)
-          session.shutdown
+          api_info = Support.persistent_client.get_api_info
 
           nvim_wr.write([1, reqid, nil, api_info]).flush
 
diff --git a/spec/neovim/line_range_spec.rb b/spec/neovim/line_range_spec.rb
index 97351bd..28a541a 100644
--- a/spec/neovim/line_range_spec.rb
+++ b/spec/neovim/line_range_spec.rb
@@ -2,7 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe LineRange do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
+    let(:client) { Support.persistent_client }
     let(:buffer) { client.current.buffer }
     let(:line_range) { LineRange.new(buffer) }
 
@@ -10,8 +10,6 @@ module Neovim
       buffer.set_lines(0, -1, true, ["1", "2", "3", "4"])
     end
 
-    after { client.shutdown }
-
     describe "#each" do
       it "yields each line" do
         yielded = []
diff --git a/spec/neovim/remote_object_spec.rb b/spec/neovim/remote_object_spec.rb
index ccdaf50..aa42983 100644
--- a/spec/neovim/remote_object_spec.rb
+++ b/spec/neovim/remote_object_spec.rb
@@ -2,8 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe RemoteObject do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
-    after { client.shutdown }
+    let(:client) { Support.persistent_client }
 
     context Window do
       let(:window) { client.current.window }
diff --git a/spec/neovim/ruby_provider/buffer_ext_spec.rb b/spec/neovim/ruby_provider/buffer_ext_spec.rb
index d3e1611..4280d23 100644
--- a/spec/neovim/ruby_provider/buffer_ext_spec.rb
+++ b/spec/neovim/ruby_provider/buffer_ext_spec.rb
@@ -4,13 +4,11 @@ require "neovim/ruby_provider/buffer_ext"
 module Neovim
   RSpec.describe Buffer do
     let!(:nvim) do
-      Neovim.attach_child(Support.child_argv).tap do |nvim|
-        stub_const("::Vim", nvim)
+      Support.persistent_client.tap do |client|
+        stub_const("::Vim", client)
       end
     end
 
-    after { nvim.shutdown }
-
     describe ".current" do
       it "returns the current buffer from the global Vim client" do
         expect(Buffer.current).to eq(nvim.get_current_buf)
@@ -27,9 +25,10 @@ module Neovim
 
     describe ".[]" do
       it "returns the buffer from the global Vim client at the given index" do
-        expect(Buffer[0]).to eq(nvim.get_current_buf)
-        nvim.command("new")
-        expect(Buffer[1]).to eq(nvim.get_current_buf)
+        buffer = Buffer[0]
+
+        expect(buffer).to be_a(Buffer)
+        expect(buffer).to eq(nvim.list_bufs[0])
       end
     end
   end
diff --git a/spec/neovim/ruby_provider/object_ext_spec.rb b/spec/neovim/ruby_provider/object_ext_spec.rb
new file mode 100644
index 0000000..d3b0bcd
--- /dev/null
+++ b/spec/neovim/ruby_provider/object_ext_spec.rb
@@ -0,0 +1,10 @@
+require "helper"
+require "neovim/ruby_provider/object_ext"
+
+RSpec.describe Object do
+  describe "#to_msgpack" do
+    it "converts classes to strings" do
+      expect(MessagePack.pack(String)).to eq(MessagePack.pack("String"))
+    end
+  end
+end
diff --git a/spec/neovim/ruby_provider/vim_spec.rb b/spec/neovim/ruby_provider/vim_spec.rb
index 3e2ed1d..a62d98f 100644
--- a/spec/neovim/ruby_provider/vim_spec.rb
+++ b/spec/neovim/ruby_provider/vim_spec.rb
@@ -52,7 +52,7 @@ RSpec.describe Vim do
     end
 
     it "refreshes global variables" do
-      client = Neovim.attach_child(Support.child_argv)
+      client = Support.persistent_client
       client.command("vs foo")
 
       Vim.__client = client
diff --git a/spec/neovim/ruby_provider/window_ext_spec.rb b/spec/neovim/ruby_provider/window_ext_spec.rb
index 8d1ab9e..178695b 100644
--- a/spec/neovim/ruby_provider/window_ext_spec.rb
+++ b/spec/neovim/ruby_provider/window_ext_spec.rb
@@ -4,13 +4,11 @@ require "neovim/ruby_provider/window_ext"
 module Neovim
   RSpec.describe Window do
     let!(:nvim) do
-      Neovim.attach_child(Support.child_argv).tap do |nvim|
-        stub_const("::Vim", nvim)
+      Support.persistent_client.tap do |client|
+        stub_const("::Vim", client)
       end
     end
 
-    after { nvim.shutdown }
-
     describe ".current" do
       it "returns the current window from the global Vim client" do
         expect(Window.current).to eq(nvim.get_current_win)
@@ -33,17 +31,16 @@ module Neovim
 
     describe ".[]" do
       it "returns the window at the given index" do
-        expect do
-          nvim.command("new")
-        end.to change { Window[1] }.from(nil).to(kind_of(Window))
+        window = Window[0]
+
+        expect(window).to be_a(Window)
+        expect(window).to eq(nvim.list_wins[0])
       end
 
       it "only includes windows within a tabpage" do
-        nvim.command("new")
-
         expect do
           nvim.command("tabnew")
-        end.to change { Window[1] }.from(kind_of(Window)).to(nil)
+        end.to change { Window[0] }
       end
     end
   end
diff --git a/spec/neovim/session_spec.rb b/spec/neovim/session_spec.rb
index dad7f5c..101dccf 100644
--- a/spec/neovim/session_spec.rb
+++ b/spec/neovim/session_spec.rb
@@ -2,10 +2,8 @@ require "helper"
 
 module Neovim
   RSpec.describe Session do
-    let(:event_loop) { EventLoop.child(Support.child_argv) }
-    let!(:session) { Session.new(event_loop) }
-
-    after { session.shutdown }
+    let(:client) { Support.persistent_client }
+    let(:session) { client.session }
 
     describe "#request" do
       it "synchronously returns a result" do
@@ -18,18 +16,6 @@ module Neovim
         end.to raise_error(/wrong number of arguments/i)
       end
 
-      it "handles large data" do
-        large_str = Array.new(1024 * 17) { SecureRandom.hex(1) }.join
-        session.request(:nvim_set_current_line, large_str)
-        expect(session.request(:nvim_get_current_line)).to eq(large_str)
-      end
-
-      it "raises an exception when a command causes nvim to exit" do
-        expect do
-          session.request(:nvim_command, "qa!")
-        end.to raise_error(Neovim::Session::Exited, /exited/)
-      end
-
       it "fails outside of the main thread", :silence_thread_exceptions do
         expect do
           Thread.new { session.request(:nvim_strwidth, "foo") }.join
@@ -38,18 +24,6 @@ module Neovim
     end
 
     describe "#notify" do
-      it "doesn't raise exceptions" do
-        expect do
-          session.notify(:nvim_strwidth, "too", "many")
-        end.not_to raise_error
-      end
-
-      it "handles large data" do
-        large_str = Array.new(1024 * 17) { SecureRandom.hex(1) }.join
-        session.notify(:nvim_set_current_line, large_str)
-        expect(session.request(:nvim_get_current_line)).to eq(large_str)
-      end
-
       it "succeeds outside of the main thread" do
         expect do
           Thread.new { session.notify(:nvim_set_current_line, "foo") }.join
@@ -57,33 +31,26 @@ module Neovim
       end
     end
 
-    describe "#run" do
-      it "enqueues messages received during blocking requests" do
-        session.request(:nvim_subscribe, "my_event")
-        session.request(:nvim_command, "call rpcnotify(0, 'my_event', 'foo')")
+    describe "#next" do
+      it "returns the next message from the event loop" do
+        cid, = session.request(:nvim_get_api_info)
+        session.request(:nvim_command, "call rpcnotify(#{cid}, 'my_event', 'foo')")
 
-        message = nil
-        session.run do |msg|
-          message = msg
-          session.shutdown
-        end
+        message = session.next
 
         expect(message.sync?).to eq(false)
         expect(message.method_name).to eq("my_event")
         expect(message.arguments).to eq(["foo"])
       end
 
-      it "supports requests within callbacks" do
-        session.request(:nvim_subscribe, "my_event")
-        session.request(:nvim_command, "call rpcnotify(0, 'my_event', 'foo')")
+      it "returns asynchronous notification errors", nvim_version: ">= 0.4.pre.dev" do
+        session.notify(:nvim_set_current_line, "too", "many", "args")
 
-        result = nil
-        session.run do |msg|
-          result = session.request(:nvim_strwidth, msg.arguments.first)
-          session.shutdown
-        end
+        message = session.next
 
-        expect(result).to be(3)
+        expect(message.sync?).to eq(false)
+        expect(message.method_name).to eq("nvim_error_event")
+        expect(message.arguments).to eq([0, "Wrong number of arguments: expecting 1 but got 3"])
       end
     end
   end
diff --git a/spec/neovim/window_spec.rb b/spec/neovim/window_spec.rb
index 7f72843..b5ddbb4 100644
--- a/spec/neovim/window_spec.rb
+++ b/spec/neovim/window_spec.rb
@@ -2,7 +2,7 @@ require "helper"
 
 module Neovim
   RSpec.describe Window do
-    let(:client) { Neovim.attach_child(Support.child_argv) }
+    let(:client) { Support.persistent_client }
     let(:window) { client.current.window }
 
     before do
diff --git a/spec/neovim_spec.rb b/spec/neovim_spec.rb
index 688d16f..0a2afb2 100644
--- a/spec/neovim_spec.rb
+++ b/spec/neovim_spec.rb
@@ -1,11 +1,18 @@
 require "helper"
 
 RSpec.describe Neovim do
-  shared_context "attached client" do
-    it "establishes an RPC connection" do
-      expect(client.strwidth("hi")).to eq(2)
+  let(:client) { Support.persistent_client }
+
+  let(:stream) do
+    case Support.backend_strategy
+    when /^tcp/, /^unix/
+      "socket"
+    else
+      "stdio"
     end
+  end
 
+  describe ".attach" do
     it "sets appropriate client info" do
       chan_info = client.evaluate("nvim_get_chan_info(#{client.channel_id})")
 
@@ -28,66 +35,6 @@ RSpec.describe Neovim do
     end
   end
 
-  describe ".attach_tcp" do
-    include_context "attached client" do
-      let(:port) { Support.tcp_port }
-      let(:stream) { "socket" }
-
-      let!(:nvim_pid) do
-        env = {"NVIM_LISTEN_ADDRESS" => "127.0.0.1:#{port}"}
-        Process.spawn(env, *Support.child_argv, [:out, :err] => File::NULL)
-      end
-
-      let(:client) do
-        begin
-          Neovim.attach_tcp("127.0.0.1", port)
-        rescue Errno::ECONNREFUSED
-          retry
-        end
-      end
-
-      after { Support.kill(nvim_pid) }
-    end
-  end
-
-  describe ".attach_unix" do
-    before do
-      skip("Not supported on this platform") if Support.windows?
-    end
-
-    include_context "attached client" do
-      let(:socket_path) { Support.socket_path }
-      let(:stream) { "socket" }
-
-      let!(:nvim_pid) do
-        env = {"NVIM_LISTEN_ADDRESS" => socket_path}
-        Process.spawn(env, *Support.child_argv, [:out, :err] => File::NULL)
-      end
-
-      let(:client) do
-        begin
-          Neovim.attach_unix(socket_path)
-        rescue Errno::ENOENT, Errno::ECONNREFUSED
-          retry
-        end
-      end
-
-      after { Support.kill(nvim_pid) }
-    end
-  end
-
-  describe ".attach_child" do
-    include_context "attached client" do
-      let(:stream) { "stdio" }
-
-      let(:client) do
-        Neovim.attach_child(Support.child_argv)
-      end
-    end
-
-    after { client.shutdown }
-  end
-
   describe ".executable" do
     it "returns the current executable" do
       expect(Neovim.executable).to be_a(Neovim::Executable)
diff --git a/spec/support.rb b/spec/support.rb
index 538680f..edf57a2 100644
--- a/spec/support.rb
+++ b/spec/support.rb
@@ -1,8 +1,33 @@
+require "shellwords"
+
 module Support
   class << self
     attr_accessor :nvim_version
   end
 
+  def self.clean_persistent_client
+    persistent_client.command("%bdelete! | tabonly | only | set all&")
+  end
+
+  def self.backend_strategy
+    @backend_strategy ||= ENV.fetch("NVIM_RUBY_SPEC_BACKEND", "child")
+  end
+
+  def self.persistent_client
+    return @persistent_client if defined?(@persistent_client)
+
+    case backend_strategy
+    when /^child:?(.*)$/
+      @persistent_client = Neovim.attach_child(child_argv + Shellwords.split($1))
+    when /^tcp:(.+)$/
+      @persistent_client = Neovim.attach_tcp(*$1.split(":", 2))
+    when /^unix:(.+)$/
+      @persistent_client = Neovim.attach_unix($1)
+    else
+      raise "Unrecognized $NVIM_RUBY_SPEC_BACKEND #{backend_strategy.inspect}"
+    end
+  end
+
   def self.workspace
     File.expand_path("workspace", __dir__)
   end

Debdiff

[The following lists of changes regard files as different if they have different names, permissions or owners.]

Files in second set of .debs but not in first

-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/neovim/host/cli.rb
-rw-r--r--  root/root   /usr/lib/ruby/vendor_ruby/neovim/ruby_provider/object_ext.rb
-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/neovim-0.9.1.gemspec

Files in first set of .debs but not in second

-rw-r--r--  root/root   /usr/share/rubygems-integration/all/specifications/neovim-0.8.1.gemspec

No differences were encountered in the control files

More details

Full run details