vimrunner 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -2,8 +2,60 @@ Using Vim's client/server functionality, this library exposes a way to spawn a
2
2
  Vim instance and control it programatically. Apart from being a fun party
3
3
  trick, this could be used to do integration testing on vimscript.
4
4
 
5
+ This is still fairly experimental, so use with caution. Any issue reports or
6
+ contributions are very welcome on the
7
+ [github issue tracker](https://github.com/AndrewRadev/Vimrunner/issues)
8
+
9
+ ## Usage
10
+
11
+ There are two objects that can be used to control Vim, a "server" and a
12
+ "client". The server takes care of spawning a server vim instance that will be
13
+ controlled, and the client is its interface.
14
+
15
+ A server can be started in two ways:
16
+
17
+ - `Vimrunner::Server.start`: Starts a terminal instance. Since it's
18
+ "invisible", it's nice for use in automated tests. If it's impossible to
19
+ start a terminal instance due to missing requirements for the `vim` binary
20
+ (see "Requirements" below), a GUI instance will be started.
21
+ - `Vimrunner::Server.start(:gui => true)`: Starts a GUI instance of Vim. On
22
+ Linux, it'll be a `gvim`, on Mac it defaults to `mvim`.
23
+
24
+ Both methods return a `Server` instance. For a more comprehensive list of
25
+ options you can start the server with, check out
26
+ [the docs](http://rubydoc.info/gems/vimrunner/Vimrunner/Server).
27
+
28
+ To be able to send commands to the server, you need a client that knows some
29
+ details about it:
30
+
31
+ ``` ruby
32
+ server = Vimrunner::Server.start
33
+
34
+ client = Vimrunner::Client.new(server)
35
+ # or,
36
+ client = server.new_client
37
+ ```
38
+
39
+ There are also two convenience methods that start a server and return a
40
+ connected client -- `Vimrunner.start_vim` and `Vimrunner.start_gui_vim`.
41
+
42
+ For a full list of methods you can invoke on the remote vim instance, check out
43
+ the methods on the `Client` class in
44
+ [the docs](http://rubydoc.info/gems/vimrunner/Vimrunner/Client).
45
+
46
+ ## Requirements
47
+
48
+ Vim needs to be compiled with `+clientserver`. This should be available with
49
+ the `normal`, `big` and `huge` featuresets. The client/server functionality
50
+ (regrettably) needs a running X server to function, even for a terminal vim.
51
+ This means that if you're using it for automated tests on a remote server,
52
+ you'll probably need to start it with xvfb.
53
+
54
+ ## Experimenting
55
+
5
56
  The `vimrunner` executable opens up an irb session with `$vim` set to a running
6
- `gvim` instance. A few things you can try:
57
+ `gvim` (or `mvim`) client. You can use this for interactive experimentation. A
58
+ few things you can try:
7
59
 
8
60
  ``` ruby
9
61
  $vim.edit 'some_file_name' # edit a file
@@ -12,13 +64,3 @@ $vim.normal 'T,' # go back to the nearest comma
12
64
  $vim.type 'a<cr>' # append a newline after the comma
13
65
  $vim.write # write file to disk
14
66
  ```
15
-
16
- For more examples of what you can do, you could take a look at the specs, they
17
- should be fairly readable.
18
-
19
- Note that this should work on a Linux box, but probably won't on a Mac. I'm
20
- assuming you'd need to change the binary to `mvim` at the very least.
21
-
22
- This is still fairly experimental, so use with caution. Any issue reports or
23
- contributions are very welcome on the
24
- [github issue tracker](https://github.com/AndrewRadev/Vimrunner/issues)
data/bin/vimrunner CHANGED
@@ -5,6 +5,6 @@ $: << File.expand_path('../../lib', __FILE__)
5
5
  require 'irb'
6
6
  require 'vimrunner'
7
7
 
8
- $vim = Vimrunner::Runner.start_gvim
8
+ $vim = Vimrunner.start_gui_vim
9
9
 
10
10
  IRB.start
data/lib/vimrunner.rb CHANGED
@@ -1 +1,16 @@
1
- require 'vimrunner/runner'
1
+ require 'vimrunner/client'
2
+ require 'vimrunner/server'
3
+
4
+ module Vimrunner
5
+ # Starts a new Server with a terminal vim instance and returns a client,
6
+ # connected to it.
7
+ def self.start_vim
8
+ Client.new(Server.start)
9
+ end
10
+
11
+ # Starts a new Server with a GUI vim instance and returns a client, connected
12
+ # to it.
13
+ def self.start_gui_vim
14
+ Client.new(Server.start(:gui => true))
15
+ end
16
+ end
@@ -0,0 +1,109 @@
1
+ require 'vimrunner/shell'
2
+
3
+ module Vimrunner
4
+
5
+ # A Client is simply a proxy to a Vim server. It's initialized with a Server
6
+ # instance and sends commands, keys and signals to it.
7
+ class Client
8
+ def initialize(server)
9
+ @server = server
10
+ end
11
+
12
+ # Adds a plugin to Vim's runtime. Initially, Vim is started without
13
+ # sourcing any plugins to ensure a clean state. This method can be used to
14
+ # populate the instance's environment.
15
+ #
16
+ # dir - The base directory of the plugin, the one that contains
17
+ # its autoload, plugin, ftplugin, etc. directories.
18
+ # entry_script - The Vim script that's runtime'd to initialize the plugin.
19
+ # Optional.
20
+ #
21
+ # Example:
22
+ #
23
+ # vim.add_plugin 'rails', 'plugin/rails.vim'
24
+ #
25
+ def add_plugin(dir, entry_script = nil)
26
+ command("set runtimepath+=#{dir}")
27
+ command("runtime #{entry_script}") if entry_script
28
+ end
29
+
30
+ # Invokes one of the basic actions the Vim server supports, sending a key
31
+ # sequence. The keys are sent as-is, so it'd probably be better to use the
32
+ # wrapper methods, #normal, #insert and so on.
33
+ def type(keys)
34
+ invoke_vim '--remote-send', keys
35
+ end
36
+
37
+ # Executes the given command in the Vim instance and returns its output,
38
+ # stripping all surrounding whitespace.
39
+ def command(vim_command)
40
+ normal
41
+
42
+ expression = "VimrunnerEvaluateCommandOutput('#{vim_command.to_s}')"
43
+
44
+ invoke_vim('--remote-expr', expression).strip.tap do |output|
45
+ raise InvalidCommandError if output =~ /^Vim:E\d+:/
46
+ end
47
+ end
48
+
49
+ # Starts a search in Vim for the given text. The result is that the cursor
50
+ # is positioned on its first occurrence.
51
+ def search(text)
52
+ normal
53
+ type "/#{text}<cr>"
54
+ end
55
+
56
+ # Sets a setting in Vim. If +value+ is nil, the setting is considered to be
57
+ # a boolean.
58
+ #
59
+ # Examples:
60
+ #
61
+ # vim.set 'expandtab' # invokes ":set expandtab"
62
+ # vim.set 'tabstop', 3 # invokes ":set tabstop=3"
63
+ #
64
+ def set(setting, value = nil)
65
+ if value
66
+ command "set #{setting}=#{value}"
67
+ else
68
+ command "set #{setting}"
69
+ end
70
+ end
71
+
72
+ # Edits the file +filename+ with Vim.
73
+ #
74
+ # Note that this doesn't use the '--remote' Vim flag, it simply types in
75
+ # the command manually. This is necessary to avoid the Vim instance getting
76
+ # focus.
77
+ def edit(filename)
78
+ command "edit #{filename}"
79
+ end
80
+
81
+ # Writes the file being edited to disk. Note that you probably want to set
82
+ # the file's name first by using Runner#edit.
83
+ def write
84
+ command :write
85
+ end
86
+
87
+ # Switches Vim to insert mode and types in the given text.
88
+ def insert(text = '')
89
+ normal "i#{text}"
90
+ end
91
+
92
+ # Switches Vim to normal mode and types in the given keys.
93
+ def normal(keys = '')
94
+ type "<c-\\><c-n>#{keys}"
95
+ end
96
+
97
+ # Kills the server it's connected to.
98
+ def kill
99
+ @server.kill
100
+ end
101
+
102
+ private
103
+
104
+ def invoke_vim(*args)
105
+ args = [@server.vim_path, '--servername', @server.name, *args]
106
+ Shell.run *args
107
+ end
108
+ end
109
+ end
@@ -1,3 +1,9 @@
1
1
  module Vimrunner
2
2
  class InvalidCommandError < RuntimeError; end
3
+
4
+ class TimeoutError < RuntimeError
5
+ def message
6
+ "Timed out while waiting for serverlist. Is an X11 server running?"
7
+ end
8
+ end
3
9
  end
@@ -0,0 +1,151 @@
1
+ require 'timeout'
2
+ require 'rbconfig'
3
+ require 'pty'
4
+
5
+ require 'vimrunner/errors'
6
+ require 'vimrunner/shell'
7
+ require 'vimrunner/client'
8
+
9
+ module Vimrunner
10
+
11
+ # The Server is a wrapper around the Vim server process that is controlled by
12
+ # clients. It will attempt to start the most appropriate Vim binary available
13
+ # on the system, though there are some options that can control this
14
+ # behaviour. See #initialize for more details.
15
+ class Server
16
+ class << self
17
+
18
+ # A convenience method that initializes a new server and starts it.
19
+ def start(options = {})
20
+ server = new(options)
21
+ server.start
22
+ end
23
+
24
+ # A convenience method that returns a new Client instance, connected to
25
+ # the server.
26
+ def new_client
27
+ Client.new(self)
28
+ end
29
+
30
+ # Retrieve a list of names of currently running Vim servers.
31
+ def list
32
+ %x[#{vim_path} --serverlist].strip.split "\n"
33
+ end
34
+
35
+ # The default path to use when starting a server with a terminal Vim. If
36
+ # the "vim" executable is not compiled with clientserver capabilities,
37
+ # the GUI version is started instead.
38
+ def vim_path
39
+ if clientserver_enabled? 'vim'
40
+ 'vim'
41
+ else
42
+ gui_vim_path
43
+ end
44
+ end
45
+
46
+ # The default path to use when starting a server with the GUI version of
47
+ # Vim. Defaults to "mvim" on a mac and "gvim" on linux.
48
+ def gui_vim_path
49
+ if mac?
50
+ 'mvim'
51
+ else
52
+ 'gvim'
53
+ end
54
+ end
55
+
56
+ # The path to a vimrc file containing some required vimscript. The server
57
+ # is started with no settings or a vimrc, apart from this one.
58
+ def vimrc_path
59
+ File.join(File.expand_path('../../..', __FILE__), 'vim', 'vimrc')
60
+ end
61
+
62
+ # Returns true if the current operating system is Mac OS X.
63
+ def mac?
64
+ host_os =~ /darwin/
65
+ end
66
+
67
+ # Returns true if the given Vim binary is compiled with support for the
68
+ # client/server functionality.
69
+ def clientserver_enabled?(vim_path)
70
+ vim_version = %x[#{vim_path} --version]
71
+ vim_version.include? '+clientserver' and vim_version.include? '+xterm_clipboard'
72
+ end
73
+
74
+ private
75
+
76
+ def host_os
77
+ RbConfig::CONFIG['host_os']
78
+ end
79
+ end
80
+
81
+ attr_accessor :pid
82
+ attr_reader :name, :vim_path
83
+
84
+ # A Server is initialized with two options that control its behaviour:
85
+ #
86
+ # :gui - Whether or not to start Vim with a GUI, either 'gvim' or 'mvim'
87
+ # depending on the OS. The default is false, which means that
88
+ # the server will start itself as a terminal instance. Note
89
+ # that, if the terminal Vim doesn't have client/server
90
+ # support, a GUI version will be started anyway.
91
+ #
92
+ # :vim_path - A path to a custom Vim binary. If this option is not set,
93
+ # the server attempts to guess an appropriate one, given the
94
+ # :gui option and the current OS.
95
+ #
96
+ # Note that simply initializing a Server doesn't start the binary. You need
97
+ # to call the #start method to do that.
98
+ #
99
+ # Examples:
100
+ #
101
+ # server = Server.new # Will start a 'vim' if possible
102
+ # server = Server.new(:gui => true) # Will start a 'gvim' or 'mvim' depending on the OS
103
+ # server = Server.new(:vim_path => '/opt/bin/vim') # Will start a server with the given vim instance
104
+ #
105
+ def initialize(options = {})
106
+ @gui = options.fetch(:gui) { false }
107
+ @vim_path = options.fetch(:vim_path) { gui? ? Server.gui_vim_path : Server.vim_path }
108
+
109
+ @name = "VIMRUNNER#{rand.to_s}"
110
+ end
111
+
112
+ # Starts a Vim server.
113
+ def start
114
+ command = "#{vim_path} -f -u #{Server.vimrc_path} --noplugin --servername #{name}"
115
+
116
+ if gui?
117
+ @pid = Kernel.spawn(command, [:in, :out, :err] => :close)
118
+ else
119
+ _out, _in, @pid = PTY.spawn(command)
120
+ end
121
+
122
+ wait_until_started
123
+ self
124
+ end
125
+
126
+ # Returns true if the server was initialized with :gui => true. Note that
127
+ # this may not be consistent with the binary that was actually started. If
128
+ # a terminal Vim instance was attempted, but an appropriate one was not
129
+ # available, it may still have a GUI.
130
+ def gui?
131
+ @gui
132
+ end
133
+
134
+ # Kills the Vim instance in the background by sending it a TERM signal.
135
+ def kill
136
+ Shell.kill(@pid)
137
+ end
138
+
139
+ private
140
+
141
+ def wait_until_started
142
+ Timeout.timeout(5, TimeoutError) do
143
+ serverlist = Server.list
144
+ while serverlist.empty? or not serverlist.include? name
145
+ sleep 0.1
146
+ serverlist = Server.list
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -12,18 +12,10 @@ module Vimrunner
12
12
  IO.popen(command) { |io| io.read.strip }
13
13
  end
14
14
 
15
- # Sends a TERM signal to the given PID if it corresponds to a running
16
- # process.
15
+ # Sends a TERM signal to the given PID. Returns true if the process was
16
+ # found and killed, false otherwise.
17
17
  def kill(pid)
18
- Process.kill(Signal.list['TERM'], pid) if running?(pid)
19
- end
20
-
21
- private
22
-
23
- # Checks if the given PID corresponds to a running process
24
- def running?(pid)
25
- return false if pid.nil?
26
- Process.getpgid(pid)
18
+ Process.kill('TERM', pid)
27
19
  true
28
20
  rescue Errno::ESRCH
29
21
  false
@@ -1,3 +1,3 @@
1
1
  module Vimrunner
2
- VERSION = '0.0.2'
2
+ VERSION = '0.0.3'
3
3
  end
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.0.2
4
+ version: 0.0.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-10-29 00:00:00.000000000Z
12
+ date: 2012-04-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rake
16
- requirement: &22761920 !ruby/object:Gem::Requirement
16
+ requirement: &23987740 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *22761920
24
+ version_requirements: *23987740
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rdoc
27
- requirement: &22761500 !ruby/object:Gem::Requirement
27
+ requirement: &23987320 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *22761500
35
+ version_requirements: *23987320
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &22761000 !ruby/object:Gem::Requirement
38
+ requirement: &23986780 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: 2.0.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *22761000
46
+ version_requirements: *23986780
47
47
  description: ! " Using vim's client/server functionality, this library exposes
48
48
  a way to\n spawn a vim instance and control it programatically. Apart from being
49
49
  a fun\n party trick, this could be used to do integration testing on vimscript.\n"
@@ -54,11 +54,12 @@ executables:
54
54
  extensions: []
55
55
  extra_rdoc_files: []
56
56
  files:
57
+ - lib/vimrunner.rb
58
+ - lib/vimrunner/errors.rb
57
59
  - lib/vimrunner/shell.rb
60
+ - lib/vimrunner/server.rb
58
61
  - lib/vimrunner/version.rb
59
- - lib/vimrunner/runner.rb
60
- - lib/vimrunner/errors.rb
61
- - lib/vimrunner.rb
62
+ - lib/vimrunner/client.rb
62
63
  - vim/vimrc
63
64
  - bin/vimrunner
64
65
  - LICENSE
@@ -77,7 +78,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
78
  version: '0'
78
79
  segments:
79
80
  - 0
80
- hash: -4179387824099540343
81
+ hash: -3146226263794770349
81
82
  required_rubygems_version: !ruby/object:Gem::Requirement
82
83
  none: false
83
84
  requirements:
@@ -86,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
87
  version: 1.3.6
87
88
  requirements: []
88
89
  rubyforge_project: vimrunner
89
- rubygems_version: 1.8.11
90
+ rubygems_version: 1.8.15
90
91
  signing_key:
91
92
  specification_version: 3
92
93
  summary: Lets you control a vim instance through ruby
@@ -1,158 +0,0 @@
1
- require 'vimrunner/shell'
2
- require 'vimrunner/errors'
3
-
4
- module Vimrunner
5
-
6
- # The Runner class acts as the actual proxy to a vim instance. Upon
7
- # initialization, a vim process is started in the background. The Runner
8
- # instance's public methods correspond to actions the instance will perform.
9
- #
10
- # Use Runner#kill to manually destroy the background process.
11
- class Runner
12
- attr_reader :servername
13
-
14
- class << self
15
- def start_gvim
16
- servername = "VIMRUNNER#{rand.to_s.gsub '.', ''}"
17
-
18
- child_stdin, parent_stdin = IO::pipe
19
- parent_stdout, child_stdout = IO::pipe
20
- parent_stderr, child_stderr = IO::pipe
21
-
22
- pid = Kernel.fork do
23
- [parent_stdin, parent_stdout, parent_stderr].each { |io| io.close }
24
-
25
- STDIN.reopen(child_stdin)
26
- STDOUT.reopen(child_stdout)
27
- STDERR.reopen(child_stderr)
28
-
29
- [child_stdin, child_stdout, child_stderr].each { |io| io.close }
30
-
31
- exec 'gvim', '-f', '-u', vimrc_path, '--noplugin', '--servername', servername
32
- end
33
-
34
- [child_stdin, child_stdout, child_stderr].each { |io| io.close }
35
-
36
- new(pid, servername)
37
- end
38
-
39
- def vimrc_path
40
- File.join(File.expand_path('../../..', __FILE__), 'vim', 'vimrc')
41
- end
42
-
43
- def serverlist
44
- %x[vim --serverlist].strip.split "\n"
45
- end
46
- end
47
-
48
- def initialize(pid, servername)
49
- @pid = pid
50
- @servername = servername
51
- wait_until_started
52
- end
53
-
54
- # Adds a plugin to Vim's runtime. Initially, Vim is started without
55
- # sourcing any plugins to ensure a clean state. This method can be used to
56
- # populate the instance's environment.
57
- #
58
- # dir - The base directory of the plugin, the one that contains
59
- # its autoload, plugin, ftplugin, etc. directories.
60
- # entry_script - The vim script that's runtime'd to initialize the plugin.
61
- #
62
- # Example:
63
- #
64
- # vim.add_plugin 'rails', 'plugin/rails.vim'
65
- #
66
- def add_plugin(dir, entry_script)
67
- command("set runtimepath+=#{dir}")
68
- command("runtime #{entry_script}")
69
- end
70
-
71
- # Invokes one of the basic actions the vim server supports, sending a key
72
- # sequence. The keys are sent as-is, so it'd probably be better to use the
73
- # wrapper methods, #normal, #insert and so on.
74
- def type(keys)
75
- invoke_vim '--remote-send', keys
76
- end
77
-
78
- # Executes the given command in the vim instance and returns its output,
79
- # stripping all surrounding whitespace.
80
- def command(vim_command)
81
- normal
82
-
83
- expression = "VimrunnerEvaluateCommandOutput('#{vim_command.to_s}')"
84
-
85
- invoke_vim('--remote-expr', expression).strip.tap do |output|
86
- raise InvalidCommandError if output =~ /^Vim:E\d+:/
87
- end
88
- end
89
-
90
- # Starts a search in vim for the given text. The result is that the cursor
91
- # is positioned on its first occurrence.
92
- def search(text)
93
- normal
94
- type "/#{text}<cr>"
95
- end
96
-
97
- # Sets a setting in vim. If +value+ is nil, the setting is considered to be
98
- # a boolean.
99
- #
100
- # Examples:
101
- #
102
- # vim.set 'expandtab' # invokes ":set expandtab"
103
- # vim.set 'tabstop', 3 # invokes ":set tabstop=3"
104
- #
105
- def set(setting, value = nil)
106
- if value
107
- command "set #{setting}=#{value}"
108
- else
109
- command "set #{setting}"
110
- end
111
- end
112
-
113
- # Edits the file +filename+ with Vim.
114
- #
115
- # Note that this doesn't use the '--remote' vim flag, it simply types in
116
- # the command manually. This is necessary to avoid the vim instance getting
117
- # focus.
118
- def edit(filename)
119
- command "edit #{filename}"
120
- end
121
-
122
- # Writes the file being edited to disk. Note that you need to set the
123
- # file's name first by using Runner#edit.
124
- def write
125
- command :write
126
- end
127
-
128
- # Switches vim to insert mode and types in the given text.
129
- def insert(text = '')
130
- normal "i#{text}"
131
- end
132
-
133
- # Switches vim to insert mode and types in the given keys.
134
- def normal(keys = '')
135
- type "<c-\\><c-n>#{keys}"
136
- end
137
-
138
- # Kills the vim instance in the background by sending it a TERM signal.
139
- def kill
140
- Shell.kill(@pid)
141
- end
142
-
143
- private
144
-
145
- def invoke_vim(*args)
146
- args = ['vim', '--servername', @servername, *args]
147
- Shell.run *args
148
- end
149
-
150
- def wait_until_started
151
- serverlist = Runner.serverlist
152
- while serverlist.empty? or not serverlist.include? @servername
153
- sleep 0.1
154
- serverlist = Runner.serverlist
155
- end
156
- end
157
- end
158
- end