Skip to content

Commit

Permalink
Vim/Git: replace git-jump with vcs-jump
Browse files Browse the repository at this point in the history
Port git-jump to Ruby and teach it to handle both Mercurial and Git. The
code is a bit of a hack job (blend of Ruby-style and the original
Shell-plus-Perl-style) but it seems to handle the basic cases ok.

For now, "diff" is supported for Mercurial and Git, but "merge" and
"grep" are only supported for Git.

I would have just adapted the existing script, but Mercurial's `hg diff`
has no equivalent of `git diff --relative` that I am aware of, and
manipulating ("relativizing") the paths in Bash would have been a little
more painful. Switching to Ruby, where we have the Pathname class, makes
path manipulation easier.

As a nicety, if the editor is Vim, we open the quickfix window instead
of only jumping to the first error.

On errors, such as when we don't seem to be inside a repo, we raise an
exception; this means that you see some diagnostic info on the console
when running `vcs-jump` on the command line. When running it inside Vim
using the mapping, we redirect error output to /dev/null, which means
you just get an empty error listing (ie. nothing happens).

Signed-off-by: Wincent Colaiuta <win@wincent.com>
  • Loading branch information
wincent committed Feb 22, 2014
1 parent 1fabf0e commit 54f8815
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 80 deletions.
73 changes: 0 additions & 73 deletions .shells/bin/git-jump

This file was deleted.

161 changes: 161 additions & 0 deletions .shells/bin/vcs-jump
@@ -0,0 +1,161 @@
#!/usr/bin/env ruby

# based on `git-jump`, that comes with Git, but works with Mercurial as well
# (ported to Ruby to make some of the relative path manipulation easier)

require 'pathname'
require 'shellwords'
require 'tempfile'

def usage()
puts <<-EOF.gsub(/^ {4}/, '')
usage: vcs-jump <mode> [<args>]
Jump to interesting elements in an editor.
The <mode> parameter is one of:
diff: elements are diff hunks. Arguments are given to diff.
[supports: git, hg]
merge: elements are merge conflicts. Arguments are ignored.
[supports: git]
grep: elements are grep hits. Arguments are given to grep.
[supports: git]
EOF
end

def open_editor(tmp)
editor = `git var GIT_EDITOR 2> /dev/null`.chomp
editor = ENV['EDITOR'] if !$?.success?
editor = `which vim`.chomp unless editor
raise 'error: cannot locate editor' if editor == ''

if Pathname.new(editor).basename.to_s == 'vim'
additional_args = %w[-c :cw] # open the quickfix window
end

exec(editor, '-q', tmp, *additional_args)
end

def pwd
@pwd ||= Pathname.pwd.realpath
end

def absolutize(file)
Pathname.new(file).realpath
end

def relativize(file)
return file if git?
relative = pwd.relative_path_from(root).to_s
file.to_s.sub("#{relative}/", '')
end

def git_root
root = `git rev-parse --show-toplevel 2> /dev/null`.chomp
[absolutize(root), 'git'] if $?.success?
end

def hg_root
root = `hg root`.chomp
[absolutize(root), 'hg'] if $?.success?
end

def vcs_info
@info ||= begin
info = git_root
info = hg_root unless info

raise 'Unable to detect VCS info' unless info

info
end
end

def root
vcs_info[0]
end

def vcs
vcs_info[1]
end

def git?
vcs == 'git'
end

def hg?
vcs == 'hg'
end

def require_git
raise 'error: not a Git repo!' unless git?
end

def shellescape(args)
ARGV.map { |arg| Shellwords.shellescape(arg) }.join(' ')
end

def redir(new_fd, &block)
old_stdout, old_stderr = $stdout, $stderr
$stdout, $stderr = new_fd, new_fd
yield
ensure
$stdout, $stderr = old_stdout, old_stderr
end

def mode_diff(args)
args = shellescape(args)
diff = git? ? `git diff --relative #{args}` : `hg diff --git #{args}`
idx = nil
file = nil

diff.lines.each do |line|
# setting the inner Perl hacker free since 2007
(line =~ %r{^\+\+\+ b/(.*)}) ? (file = relativize($~[1])) : (next unless file)
(line =~ %r{^@@ .*\+(\d+)}) ? (idx = $~[1].to_i) : (next unless idx)
(line =~ %r{^ }) && (idx += 1; next)
(line =~ %r{^[-+]\s*(.*)}) && ( puts "#{file}:#{idx}: #{$~[1]}"; idx = nil)
end
end

def mode_merge(__args__ignored)
require_git()
puts %x{
git ls-files -u |
perl -pe 's/^.*?\t//' |
sort -u |
while IFS= read fn; do
grep -Hn '^<<<<<<<' "$fn"
done
}
end

# Grep -n generates nice quickfix-looking lines by itself,
# but let's clean up extra whitespace, so they look better if the
# editor shows them to us in the status bar.
def mode_grep(args)
require_git
puts %x{git grep -n #{shellescape args} | perl -pe 's/[ \t]+/ /g; s/^ *//;'}
end

if ARGV.count < 1
usage()
exit 1
end

mode = ARGV.shift

if STDOUT.tty?
begin
tmp = Tempfile.new('vcs-jump')
redir(tmp) { send("mode_#{mode}", ARGV) }
tmp.flush
open_editor(tmp.path)
ensure
tmp.close
end
else
send("mode_#{mode}", ARGV)
end
7 changes: 0 additions & 7 deletions .vim/plugin/git.vim

This file was deleted.

7 changes: 7 additions & 0 deletions .vim/plugin/vcs.vim
@@ -0,0 +1,7 @@
function! VcsJump(command)
cexpr system("vcs-jump " . a:command . " 2> /dev/null")
cw
endfunction

command! -nargs=+ -complete=file VcsJump call VcsJump(<q-args>)
nnoremap <leader>d :VcsJump diff<space>

0 comments on commit 54f8815

Please sign in to comment.