vimrunner 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -12,7 +12,8 @@ contributions are very welcome on the
12
12
 
13
13
  ## Usage
14
14
 
15
- Vimrunner can be used in one of two main ways:
15
+ If you don't already have a running Vim server, Vimrunner can be used in one
16
+ of two main ways:
16
17
 
17
18
  ```ruby
18
19
  # Vim will automatically be started and killed.
@@ -66,33 +67,75 @@ you can control Vim. For a full list of methods you can invoke on the remote
66
67
  Vim instance, check out the [`Client`
67
68
  documentation](http://rubydoc.info/gems/vimrunner/Vimrunner/Client).
68
69
 
70
+ If you already have a remote-capable Vim server running, you can connect
71
+ Vimrunner to it directly by using `Vimrunner.connect` or `Vimrunner.connect!`
72
+ like so:
73
+
74
+ ```ruby
75
+ # Assuming a running Vim server called FOO...
76
+ vim = Vimrunner.connect("FOO")
77
+ if vim
78
+ vim.insert("Hello world!")
79
+ end
80
+
81
+ # Or, if you're confident there's a running server...
82
+ vim = Vimrunner.connect!("FOO")
83
+ vim.insert("Hello world!")
84
+ ```
85
+
86
+ In case of failure to find the server `FOO`, the first form will return `nil`,
87
+ while the second form will raise an exception.
88
+
69
89
  ## Testing
70
90
 
71
- If you're using Vimrunner for testing vim plugins, take a look at the
72
- documentation for the
73
- [Vimrunner::Testing](http://rubydoc.info/gems/vimrunner/Vimrunner/Testing)
74
- module. It contains a few simple helpers that may make it a bit easier to write
75
- regression tests in rspec. With them, it could work something like this:
91
+ If you're using Vimrunner for testing vim plugins, a simple way to get up and
92
+ running is by requiring the `vimrunner/rspec` file. With that, your
93
+ `spec_helper.rb` would look like this:
76
94
 
77
95
  ``` ruby
78
- require 'spec_helper'
79
- require 'vimrunner/testing'
96
+ require 'vimrunner'
97
+ require 'vimrunner/rspec'
80
98
 
81
- describe "My Vim plugin" do
82
- let(:vim) { some_instance_of_vim }
99
+ Vimrunner::RSpec.configure do |config|
100
+ # Use a single Vim instance for the test suite. Set to false to use an
101
+ # instance per test (slower, but can be easier to manage).
102
+ config.reuse_server = true
103
+
104
+ # Decide how to start a Vim instance. In this block, an instance should be
105
+ # spawned and set up with anything project-specific.
106
+ config.start_vim do
107
+ vim = Vimrunner.start
83
108
 
84
- around :each do |example|
85
- # needed only once for any Vim instance:
86
- vim.add_plugin(File.expand_path('../my_plugin_path'), 'plugin/my_plugin.vim')
109
+ # Or, start a GUI instance:
110
+ # vim = Vimrunner.start_gvim
87
111
 
88
- # ensure a clean temporary directory for each test:
89
- Vimrunner::Testing.tmpdir(vim) do
90
- example.call
91
- end
112
+ # Setup your plugin in the Vim instance
113
+ plugin_path = File.expand_path('.')
114
+ vim.add_plugin(plugin_path, 'plugin/my_plugin.vim')
115
+
116
+ # The returned value is the Client available in the tests.
117
+ vim
92
118
  end
119
+ end
120
+ ```
121
+
122
+ This will result in:
123
+
124
+ - A `vim` helper in every rspec example, returning the configured
125
+ `Vimrunner::Client` instance.
126
+ - Every example is executed in a separate temporary directory to make it easier
127
+ to manipulate files.
128
+ - A few helper methods from the `Vimrunner::Testing` module
129
+ ([documentation](http://rubydoc.info/gems/vimrunner/Vimrunner/Testing)).
93
130
 
131
+ The specs would then look something like this:
132
+
133
+ ``` ruby
134
+ require 'spec_helper'
135
+
136
+ describe "My Vim plugin" do
94
137
  specify "some behaviour" do
95
- Vimrunner::Testing.write_file('test.rb', <<-EOF)
138
+ write_file('test.rb', <<-EOF)
96
139
  def foo
97
140
  bar
98
141
  end
@@ -102,7 +145,7 @@ describe "My Vim plugin" do
102
145
  do_plugin_related_stuff_with(vim)
103
146
  vim.write
104
147
 
105
- IO.read('test.rb').should eq Vimrunner::Testing.normalize_string_indent(<<-EOF)
148
+ IO.read('test.rb').should eq normalize_string_indent(<<-EOF)
106
149
  def bar
107
150
  foo
108
151
  end
@@ -111,9 +154,8 @@ describe "My Vim plugin" do
111
154
  end
112
155
  ```
113
156
 
114
- It's possible to make this a lot more concise by including
115
- `Vimrunner::Testing`, by making your own helper methods that wrap common
116
- behaviour, by extracting some code to `spec_helper.rb`, and so on.
157
+ If you need a different setup, please look through the file
158
+ `lib/vimrunner/rspec.rb` for ideas on how to build your own testing scaffold.
117
159
 
118
160
  ## Requirements
119
161
 
@@ -23,7 +23,7 @@ module Vimrunner
23
23
  #
24
24
  # Returns a Client for the started Server.
25
25
  def self.start(vim = Platform.vim, &blk)
26
- Server.new(vim).start(&blk)
26
+ Server.new(:executable => vim).start(&blk)
27
27
  end
28
28
 
29
29
  # Public: Start a Vim process with a GUI and return a Client through which
@@ -46,6 +46,36 @@ module Vimrunner
46
46
  #
47
47
  # Returns a Client for the started Server.
48
48
  def self.start_gvim(&blk)
49
- Server.new(Platform.gvim).start(&blk)
49
+ Server.new(:executable => Platform.gvim).start(&blk)
50
+ end
51
+
52
+ # Public: Connect to an existing Vim process by name. Returns nil in case of
53
+ # failure.
54
+ #
55
+ # name - The String name of the Vim server to connect to.
56
+ #
57
+ # Examples
58
+ #
59
+ # client = Vimrunner.connect("FOO")
60
+ # # => #<Vimrunner::Client>
61
+ #
62
+ # Returns a Client for the named server.
63
+ def self.connect(name)
64
+ Server.new(:name => name).connect
65
+ end
66
+
67
+ # Public: Connect to an existing Vim process by name. Raises an exception in
68
+ # case of failure.
69
+ #
70
+ # name - The String name of the Vim server to connect to.
71
+ #
72
+ # Examples
73
+ #
74
+ # client = Vimrunner.connect("FOO")
75
+ # # => #<Vimrunner::Client>
76
+ #
77
+ # Returns a Client for the named server.
78
+ def self.connect!(name)
79
+ Server.new(:name => name).connect!
50
80
  end
51
81
  end
@@ -25,6 +25,19 @@ module Vimrunner
25
25
  command("runtime #{entry_script}") if entry_script
26
26
  end
27
27
 
28
+ # Public: source a script in Vim server
29
+ #
30
+ # script - The Vim script to be sourced
31
+ #
32
+ # Examples
33
+ #
34
+ # vim.source '/path/to/plugin/rails.vim'
35
+ #
36
+ # Returns nothing.
37
+ def source(script)
38
+ feedkeys(":\\<C-u>source #{escape_filename(script)}\\<CR>")
39
+ end
40
+
28
41
  # Public: Appends a directory to Vim's runtimepath
29
42
  #
30
43
  # dir - The directory added to the path
@@ -96,6 +109,28 @@ module Vimrunner
96
109
  command "echo #{expressions.join(' ')}"
97
110
  end
98
111
 
112
+ # Public: Send keys as if they come from a mapping or typed by a user.
113
+ #
114
+ # Vim's usual remote-send functionality to send keys to a server does not
115
+ # respect mappings. As a workaround, the feedkeys() function can be used
116
+ # to more closely simulate user input.
117
+ #
118
+ # Any keys are sent in a double-quoted string so that special keys such as
119
+ # <CR> and <C-L> can be used. Note that, as per Vim documentation, such
120
+ # keys should be preceded by a backslash, e.g. '\<CR>' for a carriage
121
+ # return, '<CR>' will send those four characters separately.
122
+ #
123
+ # Examples
124
+ #
125
+ # vim.command 'map <C-R> ihello'
126
+ # vim.feedkeys '\<C-R>'
127
+ #
128
+ # Returns nothing.
129
+ def feedkeys(string)
130
+ string = string.gsub('"', '\"')
131
+ server.remote_expr(%Q{feedkeys("#{string}")})
132
+ end
133
+
99
134
  # Public: Sets a setting in Vim. If +value+ is nil, the setting is
100
135
  # considered to be a boolean.
101
136
  #
@@ -122,7 +157,7 @@ module Vimrunner
122
157
  #
123
158
  # Returns the Client instance.
124
159
  def edit(filename)
125
- command "edit #{filename}"
160
+ command "edit #{escape_filename(filename)}"
126
161
  self
127
162
  end
128
163
 
@@ -132,7 +167,7 @@ module Vimrunner
132
167
  #
133
168
  # Returns the Client instance.
134
169
  def edit!(filename)
135
- command "edit! #{filename}"
170
+ command "edit! #{escape_filename(filename)}"
136
171
  self
137
172
  end
138
173
 
@@ -142,9 +177,7 @@ module Vimrunner
142
177
  # Returns the String output.
143
178
  # Raises InvalidCommandError if the command is not recognised by vim.
144
179
  def command(commands)
145
- expression = "VimrunnerEvaluateCommandOutput('#{escape(commands)}')"
146
-
147
- server.remote_expr(expression).tap do |output|
180
+ server.remote_expr("VimrunnerEvaluateCommandOutput('#{escape_single_quote(commands)}')").tap do |output|
148
181
  raise InvalidCommandError.new(output) if output =~ /^Vim:E\d+:/
149
182
  end
150
183
  end
@@ -156,7 +189,11 @@ module Vimrunner
156
189
 
157
190
  private
158
191
 
159
- def escape(string)
192
+ def escape_filename(name)
193
+ name.gsub(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")
194
+ end
195
+
196
+ def escape_single_quote(string)
160
197
  string.to_s.gsub("'", "''")
161
198
  end
162
199
  end
@@ -0,0 +1,69 @@
1
+ require 'vimrunner'
2
+ require 'vimrunner/testing'
3
+
4
+ module Vimrunner
5
+ module RSpec
6
+ class Configuration
7
+ attr_accessor :reuse_server
8
+
9
+ def start_vim(&block)
10
+ @start_vim_method = block
11
+ end
12
+
13
+ def start_vim_method
14
+ @start_vim_method || lambda { Vimrunner.start }
15
+ end
16
+ end
17
+
18
+ def self.configuration
19
+ @configuration ||= Configuration.new
20
+ end
21
+
22
+ def self.configure
23
+ yield configuration
24
+ end
25
+ end
26
+
27
+ module Testing
28
+ class << self
29
+ attr_accessor :instance
30
+ end
31
+
32
+ def vim
33
+ Testing.instance ||= Vimrunner::RSpec.configuration.start_vim_method.call
34
+ end
35
+ end
36
+ end
37
+
38
+ # Default configuration
39
+ Vimrunner::RSpec.configure do |config|
40
+ config.reuse_server = false
41
+ end
42
+
43
+ RSpec.configure do |config|
44
+
45
+ # Include the Testing DSL into all examples.
46
+ config.include(Vimrunner::Testing)
47
+
48
+ # Each example is executed in a separate directory
49
+ config.around(:each) do |example|
50
+ Dir.mktmpdir do |dir|
51
+ Dir.chdir(dir) do
52
+ vim.command("cd #{dir}")
53
+ example.run
54
+ end
55
+ end
56
+ end
57
+
58
+ config.before(:each) do
59
+ unless Vimrunner::RSpec.configuration.reuse_server
60
+ Vimrunner::Testing.instance.kill if Vimrunner::Testing.instance
61
+ Vimrunner::Testing.instance = nil
62
+ end
63
+ end
64
+
65
+ # Kill the Vim server after all tests are over.
66
+ config.after(:suite) do
67
+ Vimrunner::Testing.instance.kill if Vimrunner::Testing.instance
68
+ end
69
+ end
@@ -3,25 +3,41 @@ require "pty"
3
3
 
4
4
  require "vimrunner/errors"
5
5
  require "vimrunner/client"
6
+ require "vimrunner/platform"
6
7
 
7
8
  module Vimrunner
8
9
 
9
10
  # Public: A Server has the responsibility of starting a Vim process and
10
11
  # communicating with it through the clientserver interface. The process can
11
- # be started with "start" and stopped with "kill". A Client would be
12
- # necessary as the actual interface, though it is possible to use a Server
13
- # directly to invoke --remote commands on its Vim instance.
12
+ # be started with "start" and stopped with "kill". If given the servername of
13
+ # an existing Vim instance, it can control that instance without the need to
14
+ # start a new process.
15
+ #
16
+ # A Client would be necessary as there's a ctual interface, though it is possible
17
+ # to use a Server directly to invoke --remote commands on its Vim instance.
14
18
  class Server
15
- VIMRC = File.expand_path("../../../vim/vimrc", __FILE__)
19
+ VIMRC = File.expand_path("../../../vim/vimrc", __FILE__)
20
+ VIMRUNNER_RC = File.expand_path("../../../vim/vimrunner_rc", __FILE__)
16
21
 
17
- attr_reader :name, :executable
22
+ attr_reader :name, :executable, :vimrc
18
23
 
19
24
  # Public: Initialize a Server
20
25
  #
21
- # executable - a String representing a Vim executable.
22
- def initialize(executable)
23
- @executable = executable
24
- @name = "VIMRUNNER#{rand}"
26
+ # options - The Hash options used to define a server (default: {}):
27
+ # :executable - The String Vim executable to use (optional)
28
+ # (default: Platform.vim).
29
+ # :name - The String name of the Vim server (optional)
30
+ # (default: "VIMRUNNER#{rand}").
31
+ # :vimrc - The String vimrc file to source in the client (optional)
32
+ # (default: Server::VIMRC).
33
+ # :foreground - Boolean, whether to start Vim with the -f option (optional)
34
+ # (default: true).
35
+ #
36
+ def initialize(options = {})
37
+ @executable = options.fetch(:executable) { Platform.vim }
38
+ @name = options.fetch(:name) { "VIMRUNNER#{rand}" }
39
+ @vimrc = options.fetch(:vimrc) { VIMRC }
40
+ @foreground = options.fetch(:foreground, true)
25
41
  end
26
42
 
27
43
  # Public: Start a Server. This spawns a background process.
@@ -38,27 +54,64 @@ module Vimrunner
38
54
  # Returns a new Client instance initialized with this Server.
39
55
  # Yields a new Client instance initialized with this Server.
40
56
  def start
57
+ @r, @w, @pid = spawn
58
+
41
59
  if block_given?
42
- spawn do |r, w, pid|
43
- begin
44
- wait_until_started
45
- @result = yield(new_client)
46
- ensure
47
- r.close
48
- w.close
49
- Process.kill(9, pid) rescue Errno::ESRCH
50
- end
60
+ begin
61
+ @result = yield(connect!)
62
+ ensure
63
+ @r.close
64
+ @w.close
65
+ Process.kill(9, @pid) rescue Errno::ESRCH
51
66
  end
52
-
53
67
  @result
54
68
  else
55
- @r, @w, @pid = spawn
56
- wait_until_started
57
-
58
- new_client
69
+ connect!
59
70
  end
60
71
  end
61
72
 
73
+ # Public: Connects to the running server by name, blocking if need be.
74
+ # Returns nil if no server was found in the given time.
75
+ #
76
+ # options - An optional Hash. For now, only used for specifying a timeout
77
+ # (default: {}):
78
+ #
79
+ # :timeout - The Integer timeout to wait for a running server (optional)
80
+ # (default: 5).
81
+ #
82
+ # Returns a new Client instance initialized with this Server.
83
+ def connect(options = {})
84
+ connect!(options)
85
+ rescue TimeoutError
86
+ nil
87
+ end
88
+
89
+ # Public: Connects to the running server by name, blocking if need be.
90
+ # Raises a TimeoutError if no server was found in the given time in
91
+ # seconds.
92
+ #
93
+ # options - An optional Hash. For now, only used for specifying a timeout
94
+ # (default: {}):
95
+ #
96
+ # :timeout - The Integer timeout to wait for a running server
97
+ # (default: 5).
98
+ #
99
+ # Returns a new Client instance initialized with this Server.
100
+ def connect!(options = {})
101
+ wait_until_running(options[:timeout] || 5)
102
+
103
+ client = new_client
104
+ client.source(VIMRUNNER_RC)
105
+ client
106
+ end
107
+
108
+ # Public: Checks if there's a running Vim instance with the server's name.
109
+ #
110
+ # Returns a Boolean
111
+ def running?
112
+ serverlist.include?(name)
113
+ end
114
+
62
115
  # Public: Kills the Vim instance in the background.
63
116
  #
64
117
  # Returns self.
@@ -107,17 +160,21 @@ module Vimrunner
107
160
 
108
161
  private
109
162
 
163
+ def foreground_option
164
+ @foreground ? '-f' : ''
165
+ end
166
+
110
167
  def execute(command)
111
168
  IO.popen(command) { |io| io.read.strip }
112
169
  end
113
170
 
114
- def spawn(&blk)
115
- PTY.spawn(executable, "-f", "--servername", name, "-u", VIMRC, &blk)
171
+ def spawn
172
+ PTY.spawn("#{executable} #{foreground_option} --servername #{name} -u #{vimrc}")
116
173
  end
117
174
 
118
- def wait_until_started
119
- Timeout.timeout(5, TimeoutError) do
120
- sleep 0.1 while !serverlist.include?(name)
175
+ def wait_until_running(seconds)
176
+ Timeout.timeout(seconds, TimeoutError) do
177
+ sleep 0.1 while !running?
121
178
  end
122
179
  end
123
180
  end
@@ -6,28 +6,6 @@ module Vimrunner
6
6
  # testing purposes.
7
7
  module Testing
8
8
 
9
- # Public: Within the given block, switches to a temporary directory for
10
- # isolation purposes.
11
- #
12
- # Example:
13
- #
14
- # tmpdir(vim) do
15
- # puts vim.command('pwd')
16
- # end
17
- #
18
- # vim - a Vimrunner::Client instance
19
- #
20
- # Returns nothing.
21
- # Yields nothing.
22
- def tmpdir(vim)
23
- Dir.mktmpdir do |dir|
24
- Dir.chdir(dir) do
25
- vim.command("cd #{dir}")
26
- yield
27
- end
28
- end
29
- end
30
-
31
9
  # Public: Writes the given string to the file identified by "filename".
32
10
  #
33
11
  # Uses #normalize_string_indent to ensure consistent indentation when given
@@ -1,3 +1,3 @@
1
1
  module Vimrunner
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
data/vim/vimrc CHANGED
@@ -10,17 +10,4 @@ set noswapfile nobackup
10
10
  set runtimepath-=~/.vim
11
11
  set runtimepath-=~/.vim/after
12
12
 
13
- function! VimrunnerEvaluateCommandOutput(command)
14
- let base_command = split(a:command, '\s\+')[0]
15
- let base_command = substitute(base_command, '!$', '', '')
16
-
17
- if !exists(':'.base_command)
18
- let output = 'Vim:E492: Not an editor command: '.base_command
19
- else
20
- redir => output
21
- silent exe a:command
22
- redir END
23
- endif
24
-
25
- return output
26
- endfunction
13
+ execute "source " . expand('<sfile>:p:h') . "/vimrunner_rc"
@@ -0,0 +1,56 @@
1
+ let has_error_handling_bugfix =
2
+ \ v:version > 703 ||
3
+ \ v:version == 703 && has('patch860')
4
+
5
+ if has_error_handling_bugfix
6
+ function! VimrunnerEvaluateCommandOutput(command)
7
+ let output = ''
8
+
9
+ try
10
+ redir => output
11
+ silent exe a:command
12
+ redir END
13
+
14
+ let output = s:StripSilencedErrors(output)
15
+ catch
16
+ let output = v:exception
17
+ endtry
18
+
19
+ return output
20
+ endfunction
21
+ else
22
+ " Use some fake error handling to provide at least rudimentary errors for
23
+ " missing commands.
24
+ function! VimrunnerEvaluateCommandOutput(command)
25
+ let base_command = split(a:command, '\s\+')[0]
26
+ let base_command = substitute(base_command, '!$', '', '')
27
+
28
+ if !exists(':'.base_command)
29
+ let output = 'Vim:E492: Not an editor command: '.base_command
30
+ else
31
+ redir => output
32
+ silent exe a:command
33
+ redir END
34
+ endif
35
+
36
+ return output
37
+ endfunction
38
+ endif
39
+
40
+ " Remove errors from the output that have been silenced by :silent!. These are
41
+ " visible in the captured output since all messages are captured by :redir.
42
+ function! s:StripSilencedErrors(output)
43
+ let processed_output = []
44
+
45
+ for line in reverse(split(a:output, "\n"))
46
+ if line =~ '^E\d\+:'
47
+ break
48
+ endif
49
+
50
+ call add(processed_output, line)
51
+ endfor
52
+
53
+ return join(reverse(processed_output), "\n")
54
+ endfunction
55
+
56
+ " vim: set ft=vim
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vimrunner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-17 00:00:00.000000000 Z
12
+ date: 2013-04-21 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
@@ -66,7 +66,7 @@ dependencies:
66
66
  requirements:
67
67
  - - ! '>='
68
68
  - !ruby/object:Gem::Version
69
- version: 2.0.0
69
+ version: 2.13.0
70
70
  type: :development
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
@@ -74,7 +74,7 @@ dependencies:
74
74
  requirements:
75
75
  - - ! '>='
76
76
  - !ruby/object:Gem::Version
77
- version: 2.0.0
77
+ version: 2.13.0
78
78
  description: ! " Using Vim's client/server functionality, this library exposes
79
79
  a way to\n spawn a Vim instance and control it programatically. Apart from being
80
80
  a fun\n party trick, this can be used to integration test Vim script.\n"
@@ -88,10 +88,12 @@ files:
88
88
  - lib/vimrunner.rb
89
89
  - lib/vimrunner/client.rb
90
90
  - lib/vimrunner/testing.rb
91
+ - lib/vimrunner/rspec.rb
91
92
  - lib/vimrunner/errors.rb
92
93
  - lib/vimrunner/version.rb
93
94
  - lib/vimrunner/server.rb
94
95
  - lib/vimrunner/platform.rb
96
+ - vim/vimrunner_rc
95
97
  - vim/vimrc
96
98
  - bin/vimrunner
97
99
  - LICENSE
@@ -110,7 +112,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
110
112
  version: '0'
111
113
  segments:
112
114
  - 0
113
- hash: 3996768695942180221
115
+ hash: -1164770989909868074
114
116
  required_rubygems_version: !ruby/object:Gem::Requirement
115
117
  none: false
116
118
  requirements: