-
Notifications
You must be signed in to change notification settings - Fork 13
Initial attempt to extract Vim selection logic #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lib/vimrunner/client.rb
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the use of Vim.client
here to choose the most appropriate client; this means that if a user has to start a server with gvim
or mvim
they can still communicate with vim
as long as it has +clientserver
.
However, this does ignore any user-specified Vim path in the Server
; perhaps we could try that for suitability before falling back to Vim.client
.
Awesome. I've been pretty busy this week, though, so I'll probably be able to look through it in more detail in the weekend. |
No problem. Two things to think about:
def vims_to_try
vims = %w( vim gvim )
vims.insert(1, "mvim") if mac?
vims
end |
One thing to check: there is a lot of logic to determine the best client and the best server separately: couldn't we always use the server as the client as neither --remote-send nor --remote-expr start an instance? |
This is a bit rough and I haven't tested it thoroughly but here's another approach: having a single require "pty"
module Vimrunner
class Command
attr_reader :path, :pid
def initialize(path)
@path = path
end
def suitable?
features = run(path, "--version")
if gui?
features.include?("+clientserver")
else
features.include?("+clientserver") && features.include?("+xterm_clipboard")
end
rescue Errno::ENOENT
false
end
def gui?
File.basename(path)[0, 1] =~ /g|m/
end
def name
@name ||= "VIMRUNNER#{rand}"
end
def server_path
[path, "--servername", name]
end
def start_server(vimrc_path)
command = server_path + ["--noplugin", "-f", "-u", vimrc_path]
_r, _w, @pid = PTY.spawn(*command)
@pid
end
def remote_expr(expr)
command = server_path + ["--remote-expr", expr]
output = run(*command).strip
raise InvalidCommandError if output =~ /^Vim:E\d+:/
output
end
def remote_send(keys)
command = server_path + ["--remote-send", keys]
run(*command)
end
# Sends a TERM signal to the given PID. Returns true if the process was
# found and killed, false otherwise.
def kill
Process.kill("TERM", pid)
true
rescue Errno::ESRCH
false
end
alias quit kill
private
# Executes a shell command, waits until it's finished, and returns the
# output
def run(*command)
IO.popen(command) { |io| io.read.strip }
end
end
end |
Yup, that's something I've been thinking of myself, though with a different name. I want to flesh it out a bit and I'll push later tonight. |
Move out the driver selection logic to a private method.
Alright, so I made a few changes here and there, mostly to naming, but to organization as well. Let me know what you think of them. Basically, the current structure looks like this:
Some thoughts:
Feel free to change things around and let me know what you think. I'll work on the points above some more tomorrow. |
I think we could get rid of the separate I'll try to put together a proof of concept today. |
True, but I'm not sure if the |
Can you please test the following for me on Linux? require 'pty'
r, w, pid = PTY.spawn('vim', '--servername', 'FOO')
r.close_read
w.close_write #=> the Process quits at this point
Process.kill('QUIT', pid) #=> returns 1 |
I don't know if it works as intended for Personally, I don't mind the subclassing much. It may make sense in the future as well, maybe allowing for some more differences between a gvim and a headless vim. Might be a good idea to have a separate class for mvim as well, though it doesn't seem necessary for now. The problem is simply the dispatching logic. I'll try to clean it right now, I'll probably be able to push something soonish. |
I've attempted to simplify the choice logic. This fails the specs badly, because I haven't adapted This should also allow us to do: if not driver.suitable?
raise "Sorry, couldn't find a suitable driver for this system"
end ...in the server. The idea is that if it's reached the server, there's nothing more we can do (could be a missing |
Nah, this won't work at all. On a mac, this needs to always start |
Hmm, well, it seems a bit fragile to me. If you can get it to work, then I guess there's no harm in it, but at this point, I'm thinking this might be the simplest working approach: if File.basename(executable) =~ /^(g|m)/
pid = Kernel.spawn(...)
else
_in, _out, pid = PTY.spawn(...)
end
def gui?
File.basename(executable) =~ /^(g|m)/
end |
Just to keep you updated, I've been experimenting with an (almost) one file, miniature Vimrunner at https://gist.github.com/f8a4a88bbda60fbf5cba (the almost is because it requires your It allows you to use Vimrunner in one of two main ways: require 'vimrunner'
Vimrunner.start do |client|
client.type "ihello"
client.normal ":w test.txt"
end
client = Vimrunner.start
client.type "ihello"
client.normal ":w test2.txt"
client.close I'm planning to now try and TDD this into the existing Vimrunner and match the existing interface, etc. Have a look and see what you think regarding the way the different vim executables are dealt with and how responsibility is split between |
Looks good. I like I'm a bit worried about the spawning logic, mainly because I don't understand it (what's with the detaches and closing, and why the difference between Linux and Mac), but if it works, it works. I'll just have to sit down these days and try to figure it out. |
Regarding the spawning logic, here's how I currently understand it: Using In my experiments, a As for
On a Mac, calling static VALUE
pty_detach_process(struct pty_info *info)
{
rb_detach_process(info->child_pid);
return Qnil;
} |
Sounds reasonable. It's a bit weird to me that you don't have to explicitly kill the process, but with a pseudo-terminal, I guess not having input or output would leave it with nothing to do except kill itself :). Thanks for the explanation. |
So I've just pushed a big commit that tries to (mostly) match the old interface, restore the I've tested it on both Mac OS X and Ubuntu 12.04 and it seems to work pretty well. We still need to cover the Server and Vimrunner module itself with some tests and documentation regarding the new block interface. I added in Vimrunner::Server.new("my custom vim path").start I also started to conform to TomDoc but wanted to push up what I had so far so we could review it together. |
Without this, rspec generates a semi-related deprecation warning when run as `rspec spec`.
Looks great. I added simplecov to the project to catch some areas we may not have specced. Apart from the server spawning, there's just a few small spots left. I'll try to finish the specs and work on writing documentation this evening. |
One thing before we cut a new release: it might be a good idea to use something like Semantic Versioning regarding API changes so that people can use: gem 'vimrunner', '~> 0.1.0' Or similar in their |
SemVer specifies that versions before 1.0 are not subject to the rules, so that you can experiment wth the API a lot before coming to the decision to maintain backwards compatibility. Otherwise, you'd never be able to remove any public API without releasing 1.0. As for the dependency problem, I'd say for any gem under 1.0 it's reasonable to do a hard requirement instead: gem 'vimrunner', '0.0.4' |
Ah, that's fair enough; I did wonder how it applied to sub 1.0.0. |
I've written some docs by following the TomDoc spec, but it's my first time with it, so do correct me if I've messed up somewhere. I'm not sure if we should also document the private methods, it seems a bit of a waste at this point in time. I've yet to write the missing specs and maybe a few tweaks that I had in mind. I should also update the README. I'll probably have to leave those for the weekend. |
I've added specs to cover the missing parts of the code and a little more documentation where needed but we still need to revise the README, version and gemspec. |
Thanks. Sorry I wasn't able to rewrite the README myself, turned out to be a busy weekend. I'm going to merge this into master, bump the version to |
Initial attempt to extract Vim selection logic
This isn't particularly ready but might be a good point for us to start collaborating on extracting the Vim selection logic into separate classes.
To summarise, I created a
Vimrunner::Vim
module which hasserver
,client
andgui
methods to return the appropriate binary for each selection. The different permutations should be covered inspec/vimrunner/vim_spec.rb
. The result of this selection is not just a string (e.g."mvim"
) but aVimrunner::VimInstance
(for want of a better name) which is either aVimrunner::HeadlessVim
or aVimrunner::GuiVim
: the difference being how they spawn. TheseVimInstance
s are then passed around and interrogated by bothClient
andServer
classes.The reason I created an abstract parent class of both
HeadlessVim
andGuiVim
was to avoid violating the Liskov substitution principle where possible by changing the behaviour ofspawn
between parent and child.I'd be keen to get your feedback to see how we can simplify this (particularly the initialisation logic in
Server
).