/vim-gitgutter

A Vim plugin which shows a git diff in the gutter (sign column) and stages/undoes hunks.

Primary LanguageVim scriptMIT LicenseMIT

vim-gitgutter

A Vim plugin which shows a git diff in the 'gutter' (sign column). It shows which lines have been added, modified, or removed. You can also preview, stage, and undo individual hunks. The plugin also provides a hunk text object.

The signs are always up to date and the plugin never saves your buffer.

Features:

  • Shows signs for added, modified, and removed lines.
  • Runs the diffs asynchronously where possible.
  • Ensures signs are always up to date.
  • Quick jumping between blocks of changed lines ("hunks").
  • Stage/undo/preview individual hunks.
  • Provides a hunk text object.
  • Diffs against index (default) or any commit.
  • Handles line endings correctly, even with repos that do CRLF conversion.
  • Optional line highlighting.
  • Fully customisable (signs, sign column, line highlights, mappings, extra git-diff arguments, etc).
  • Can be toggled on/off.
  • Preserves signs from other plugins.
  • Easy to integrate diff stats into status line; built-in integration with vim-airline.
  • Works with fish shell (in addition to the usual shells).

Constraints:

  • Supports git only. If you work with other version control systems, I recommend vim-signify.
  • Relies on the FocusGained event. If your terminal doesn't report focus events, either use something like Terminus or set let g:gitgutter_terminal_reports_focus=0.

Screenshot

screenshot

In the screenshot above you can see:

  • Line 15 has been modified.
  • Lines 21-24 are new.
  • A line or lines were removed between lines 25 and 26.

Installation

Before installation, please check your Vim supports signs by running :echo has('signs'). 1 means you're all set; 0 means you need to install a Vim with signs support. If you're compiling Vim yourself you need the 'big' or 'huge' feature set. MacVim supports signs.

You install vim-gitgutter like any other vim plugin.

Pathogen
cd ~/.vim/bundle
git clone git://github.com/airblade/vim-gitgutter.git
Voom

Edit your plugin manifest (voom edit) and add:

airblade/vim-gitgutter
VimPlug

Place this in your .vimrc:

Plug 'airblade/vim-gitgutter'

Then run the following in Vim:

:source %
:PlugInstall
NeoBundle

Place this in your .vimrc:

NeoBundle 'airblade/vim-gitgutter'

Then run the following in Vim:

:source %
:NeoBundleInstall
No plugin manager

Copy vim-gitgutter's subdirectories into your vim configuration directory:

cd /tmp && git clone git://github.com/airblade/vim-gitgutter.git
cp -r vim-gitgutter/* ~/.vim/

See :help add-global-plugin.

Getting started

When you make a change to a file tracked by git, the diff markers should appear automatically. The delay is governed by vim's updatetime option; the default value is 4000, i.e. 4 seconds, but I suggest reducing it to around 100ms (add set updatetime=100 to your vimrc).

You can jump between hunks with [c and ]c. You can preview, stage, and undo hunks with <leader>hp, <leader>hs, and <leader>hu respectively.

You cannot unstage a staged hunk.

Activation

You can explicitly turn vim-gitgutter off and on (defaults to on):

  • turn off with :GitGutterDisable
  • turn on with :GitGutterEnable
  • toggle with :GitGutterToggle.

You can turn the signs on and off (defaults to on):

  • turn on with :GitGutterSignsEnable
  • turn off with :GitGutterSignsDisable
  • toggle with :GitGutterSignsToggle.

And you can turn line highlighting on and off (defaults to off):

  • turn on with :GitGutterLineHighlightsEnable
  • turn off with :GitGutterLineHighlightsDisable
  • toggle with :GitGutterLineHighlightsToggle.

Note that if you have line highlighting on and signs off, you will have an empty sign column – more accurately, a sign column with invisible signs. This is because line highlighting requires signs and Vim always shows the sign column even if the signs are invisible.

If you switch off both line highlighting and signs, you won't see the sign column. That is unless you configure the sign column always to be there (see Sign Column section).

To keep your Vim snappy, vim-gitgutter will suppress the signs when a file has more than 500 changes. As soon as the number of changes falls below the limit vim-gitgutter will show the signs again. You can configure the threshold with:

let g:gitgutter_max_signs = 500  " default value

Hunks

You can jump between hunks:

  • jump to next hunk (change): ]c
  • jump to previous hunk (change): [c.

Both of those take a preceding count.

To set your own mappings for these, for example ]h and [h:

nmap ]h <Plug>GitGutterNextHunk
nmap [h <Plug>GitGutterPrevHunk

You can stage or undo an individual hunk when your cursor is in it:

  • stage the hunk with <Leader>hs or
  • undo it with <Leader>hu.

See the FAQ if you want to unstage staged changes.

The . command will work with both these if you install repeat.vim.

To set your own mappings for these, for example if you prefer the mnemonics hunk-add and hunk-revert:

nmap <Leader>ha <Plug>GitGutterStageHunk
nmap <Leader>hr <Plug>GitGutterUndoHunk

And you can preview a hunk's changes with <Leader>hp. You can of course change this mapping, e.g:

nmap <Leader>hv <Plug>GitGutterPreviewHunk

A hunk text object is provided which works in visual and operator-pending modes.

  • ic operates on all lines in the current hunk.
  • ac operates on all lines in the current hunk and any trailing empty lines.

To re-map these, for example to ih and ah:

omap ih <Plug>GitGutterTextObjectInnerPending
omap ah <Plug>GitGutterTextObjectOuterPending
xmap ih <Plug>GitGutterTextObjectInnerVisual
xmap ah <Plug>GitGutterTextObjectOuterVisual

If you don't want vim-gitgutter to set up any mappings at all, use this:

let g:gitgutter_map_keys = 0

Finally, you can force vim-gitgutter to update its signs across all visible buffers with :GitGutterAll.

See the customisation section below for how to change the defaults.

Customisation

You can customise:

  • The sign column's colours
  • Whether or not the sign column is shown when there aren't any signs (defaults to no)
  • The signs' colours and symbols
  • Line highlights
  • The base of the diff
  • Extra arguments for git diff
  • Key mappings
  • Whether or not vim-gitgutter is on initially (defaults to on)
  • Whether or not signs are shown (defaults to yes)
  • Whether or not line highlighting is on initially (defaults to off)
  • Whether or not vim-gitgutter runs in "realtime" (defaults to yes)
  • Whether or not vim-gitgutter runs eagerly (defaults to yes)
  • Whether or not vim-gitgutter runs asynchronously (defaults to yes)

Please note that vim-gitgutter won't override any colours or highlights you've set in your colorscheme.

Sign column

By default vim-gitgutter will make the sign column look like the line number column.

To customise your sign column's background color, first tell vim-gitgutter to leave it alone:

let g:gitgutter_override_sign_column_highlight = 0

And then either update your colorscheme's SignColumn highlight group or set it in your vimrc:

highlight SignColumn ctermbg=whatever    " terminal Vim
highlight SignColumn guibg=whatever      " gVim/MacVim

By default the sign column will appear when there are signs to show and disappear when there aren't. To always have the sign column, add to your vimrc:

if exists('&signcolumn')  " Vim 7.4.2201
  set signcolumn=yes
else
  let g:gitgutter_sign_column_always = 1
endif

Signs' colours and symbols

To customise the colours, set up the following highlight groups in your colorscheme or ~/.vimrc:

GitGutterAdd          " an added line
GitGutterChange       " a changed line
GitGutterDelete       " at least one removed line
GitGutterChangeDelete " a changed line followed by at least one removed line

You can either set these with highlight GitGutterAdd {key}={arg}... or link them to existing highlight groups with, say, highlight link GitGutterAdd DiffAdd.

To customise the symbols, add the following to your ~/.vimrc:

let g:gitgutter_sign_added = 'xx'
let g:gitgutter_sign_modified = 'yy'
let g:gitgutter_sign_removed = 'zz'
let g:gitgutter_sign_removed_first_line = '^^'
let g:gitgutter_sign_modified_removed = 'ww'

Line highlights

Similarly to the signs' colours, set up the following highlight groups in your colorscheme or ~/.vimrc:

GitGutterAddLine          " default: links to DiffAdd
GitGutterChangeLine       " default: links to DiffChange
GitGutterDeleteLine       " default: links to DiffDelete
GitGutterChangeDeleteLine " default: links to GitGutterChangeLineDefault, i.e. DiffChange

The base of the diff

By default buffers are diffed against the index. However you can diff against any commit by setting:

let g:gitgutter_diff_base = '<commit SHA>'

Extra arguments for git diff

If you want to pass extra arguments to git diff, for example to ignore whitespace, do so like this:

let g:gitgutter_diff_args = '-w'

Key mappings

To disable all key mappings:

let g:gitgutter_map_keys = 0

See above for configuring maps for hunk-jumping and staging/undoing.

Use a custom grep command

If you use an alternative to grep, you can tell vim-gitgutter to use it here.

" Default:
let g:gitgutter_grep = 'grep'

To turn off vim-gitgutter by default

Add let g:gitgutter_enabled = 0 to your ~/.vimrc.

To turn off signs by default

Add let g:gitgutter_signs = 0 to your ~/.vimrc.

To turn on line highlighting by default

Add let g:gitgutter_highlight_lines = 1 to your ~/.vimrc.

To turn off asynchronous updates

By default diffs are run asynchronously. To run diffs synchronously instead:

let g:gitgutter_async = 0

Extensions

Operate on every line in a hunk

You can map an operator to do whatever you want to every line in a hunk.

Let's say, for example, you want to remove trailing whitespace.

function! CleanUp(...)
  if a:0  " opfunc
    let [first, last] = [line("'["), line("']")]
  else
    let [first, last] = [line("'<"), line("'>")]
  endif
  for lnum in range(first, last)
    let line = getline(lnum)

    " clean up the text, e.g.:
    let line = substitute(line, '\s\+$', '', '')

    call setline(lnum, line)
  endfor
endfunction

nmap <silent> <Leader>x :set opfunc=CleanUp<CR>g@

Then place your cursor in a hunk and type \xic (assuming a leader of \).

Alternatively you could place your cursor in a hunk, type vic to select it, then :call CleanUp().

Operate on every changed line in a file

You can write a command to do whatever you want to every changed line in a file.

function! GlobalChangedLines(ex_cmd)
  for hunk in GitGutterGetHunks()
    for lnum in range(hunk[2], hunk[2]+hunk[3]-1)
      let cursor = getcurpos()
      silent! execute lnum.a:ex_cmd
      call setpos('.', cursor)
    endfor
  endfor
endfunction

command -nargs=1 Glines call GlobalChangedLines(<q-args>)

Let's say, for example, you want to remove trailing whitespace from all changed lines:

:Glines s/\s\+$//

Cycle through hunks in all buffers

]c and [c jump from one hunk to the next in the current buffer. You can use this code to jump to the next hunk no matter which buffer it's in.

function! NextHunkAllBuffers()
  let line = line('.')
  GitGutterNextHunk
  if line('.') != line
    return
  endif

  let bufnr = bufnr('')
  while 1
    bnext
    if bufnr('') == bufnr
      return
    endif
    if !empty(GitGutterGetHunks())
      normal! 1G
      GitGutterNextHunk
      return
    endif
  endwhile
endfunction

function! PrevHunkAllBuffers()
  let line = line('.')
  GitGutterPrevHunk
  if line('.') != line
    return
  endif

  let bufnr = bufnr('')
  while 1
    bprevious
    if bufnr('') == bufnr
      return
    endif
    if !empty(GitGutterGetHunks())
      normal! G
      GitGutterPrevHunk
      return
    endif
  endwhile
endfunction

nmap <silent> ]c :call NextHunkAllBuffers()<CR>
nmap <silent> [c :call PrevHunkAllBuffers()<CR>

FAQ

How can I turn off realtime updates?

Add this to your vim configuration (in an /after/plugin directory):

" .vim/after/plugin/gitgutter.vim
autocmd! gitgutter CursorHold,CursorHoldI

Why can't I unstage staged changes?

This plugin is for showing changes between the working tree and the index (and staging/undoing those changes). Unstaging a staged hunk would require showing changes between the index and HEAD, which is out of scope.

Why are the colours in the sign column weird?

Your colorscheme is configuring the SignColumn highlight group weirdly. Please see the section above on customising the sign column.

What happens if I also use another plugin which uses signs (e.g. Syntastic)?

Vim only allows one sign per line. Before adding a sign to a line, vim-gitgutter checks whether a sign has already been added by somebody else. If so it doesn't do anything. In other words vim-gitgutter won't overwrite another plugin's signs. It also won't remove another plugin's signs.

Troubleshooting

When no signs are showing at all

Here are some things you can check:

  • Try adding let g:gitgutter_grep='' to your vimrc. If it works, the problem is grep producing non-plain output; e.g. ANSI escape codes or colours.
  • Verify :echo system("git --version") succeeds.
  • Verify your git config is compatible with the version of git returned by the command above.
  • Verify your Vim supports signs (:echo has('signs') should give 1).
  • Verify your file is being tracked by git and has unstaged changes.

When the whole file is marked as added

  • If you use zsh, and you set CDPATH, make sure CDPATH doesn't include the current directory.

When signs take a few seconds to appear

  • Try reducing updatetime, e.g. set updatetime=100.

When signs don't update after focusing Vim

  • Your terminal probably isn't reporting focus events. Either try installing Terminus or set let g:gitgutter_terminal_reports_focus=0.

Shameless Plug

If this plugin has helped you, or you'd like to learn more about Vim, why not check out this screencast I wrote for PeepCode:

This was one of PeepCode's all-time top three bestsellers and is now available at Pluralsight.

You can read reviews on my website.

Intellectual Property

Copyright Andrew Stewart, AirBlade Software Ltd. Released under the MIT licence.