airblade / vim-gitgutter

A Vim plugin which shows git diff markers in the sign column and stages/previews/undoes hunks and partial hunks.
MIT License
8.37k stars 297 forks source link
diff git gitgutter neovim vim

vim-gitgutter

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

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

The name "gitgutter" comes from the Sublime Text 3 plugin which inspired this in 2013.

Features:

Constraints:

Compatibility:

Compatible back to Vim 7.4, and probably 7.3.

Screenshot

screenshot

In the screenshot above you can see:

Installation

First, install using your favourite package manager, or use Vim's built-in package support.

Vim:

mkdir -p ~/.vim/pack/airblade/start
cd ~/.vim/pack/airblade/start
git clone https://github.com/airblade/vim-gitgutter.git
vim -u NONE -c "helptags vim-gitgutter/doc" -c q

Neovim:

mkdir -p ~/.config/nvim/pack/airblade/start
cd ~/.config/nvim/pack/airblade/start
git clone https://github.com/airblade/vim-gitgutter.git
nvim -u NONE -c "helptags vim-gitgutter/doc" -c q

Second, ensure your updatetime and signcolumn options are set appropriately.

When you make a change to a file tracked by git, the diff markers should appear automatically after a short delay. 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). Note updatetime also controls the delay before vim writes its swap file (see :help updatetime).

The signcolumn option can have any value except 'off'.

Windows

There is a potential risk on Windows due to cmd.exe prioritising the current folder over folders in PATH. If you have a file named git.* (i.e. with any extension in PATHEXT) in your current folder, it will be executed instead of git whenever the plugin calls git.

You can avoid this risk by configuring the full path to your git executable. For example:

" This path probably won't work
let g:gitgutter_git_executable = 'C:\Program Files\Git\bin\git.exe'

Unfortunately I don't know the correct escaping for the path - if you do, please let me know!

Getting started

When you make a change to a file tracked by git, the diff markers should appear automatically after a short delay.

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.

After updating the signs, the plugin fires the GitGutter User autocommand.

After staging a hunk or part of a hunk, the plugin fires the GitGutterStage User autocommand.

Activation

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

To toggle vim-gitgutter per buffer:

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

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

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/NeoVim always shows the sign column when there are signs even if the signs are invisible.

With Neovim 0.3.2 or higher, you can turn line number highlighting on and off (defaults to off):

The same caveat applies to line number highlighting as to line highlighting just above.

If you switch off both line highlighting and signs, you won't see the sign column.

In older Vims (pre 8.1.0614 / Neovim 0.4.0) vim-gitgutter will suppress the signs when a file has more than 500 changes, to avoid slowing down the UI. 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 (Vim < 8.1.0614, Neovim < 0.4.0)
let g:gitgutter_max_signs = -1   " default value (otherwise)

You can also remove the limit by setting g:gitgutter_max_signs = -1.

Hunks

You can jump between hunks:

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)

When you jump between hunks, a message like Hunk 4 of 11 is shown on the command line. If you want to turn the message off, you can use:

let g:gitgutter_show_msg_on_hunk_jumping = 0

You can load all your hunks into the quickfix list with :GitGutterQuickFix. Note this ignores any unsaved changes in your buffers. If the option g:gitgutter_use_location_list is set, this command will load hunks into the current window's location list instead. Use :copen (or :lopen) to open the quickfix / location list or add a custom command like this:

command! Gqf GitGutterQuickFix | copen

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

To stage part of an additions-only hunk by:

To stage part of any hunk:

Note the above workflow is not possible if you have opted in to preview hunks with Vim's popup windows.

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 g-based maps:

nmap ghs <Plug>(GitGutterStageHunk)
nmap ghu <Plug>(GitGutterUndoHunk)

And you can preview a hunk's changes with <Leader>hp. The location of the preview window is configured with g:gitgutter_preview_win_location (default 'bo'). You can of course change this mapping, e.g:

nmap ghp <Plug>(GitGutterPreviewHunk)

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

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.

Vimdiff

Use the GitGutterDiffOrig command to open a vimdiff view of the current buffer, respecting g:gitgutter_diff_relative_to and :gitgutter_diff_base.

Folding

Use the GitGutterFold command to fold all unchanged lines, leaving just the hunks visible. Use zr to unfold 3 lines of context above and below a hunk.

Execute GitGutterFold a second time to restore the previous view.

Use gitgutter#fold#foldtext() to augment the default foldtext() with an indicator of whether the folded lines have been changed.

set foldtext=gitgutter#fold#foldtext()

For a closed fold with changed lines:

Default foldtext():         +-- 45 lines: abcdef
gitgutter#fold#foldtext():  +-- 45 lines (*): abcdef

You can use gitgutter#fold#is_changed() in your own foldtext expression to find out whether the folded lines have been changed.

Status line

Call the GitGutterGetHunkSummary() function from your status line to get a list of counts of added, modified, and removed lines in the current buffer. For example:

" Your vimrc
function! GitStatus()
  let [a,m,r] = GitGutterGetHunkSummary()
  return printf('+%d ~%d -%d', a, m, r)
endfunction
set statusline+=%{GitStatus()}

Customisation

You can customise:

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

Sign column

Set the SignColumn highlight group to change the sign column's colour. For example:

" vim-gitgutter used to do this by default:
highlight! link SignColumn LineNr

" or you could do this:
highlight SignColumn guibg=whatever ctermbg=whatever

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:

" Vim 7.4.2201
set signcolumn=yes

GitGutter can preserve or ignore non-gitgutter signs. For Vim v8.1.0614 and later you can set gitgutter's signs' priorities with g:gitgutter_sign_priority, so gitgutter defaults to clobbering other signs. For Neovim v0.4.0 and later you can set an expanding sign column so gitgutter again defaults to clobbering other signs. Otherwise, gitgutter defaults to preserving other signs. You can configure this with:

let g:gitgutter_sign_allow_clobber = 1

Signs' colours and symbols

If you or your colourscheme has defined GitGutter* highlight groups, the plugin will use them for the signs' colours.

If you want the background colours to match the sign column, but don't want to update the GitGutter* groups yourself, you can get the plugin to do it:

let g:gitgutter_set_sign_backgrounds = 1

If no GitGutter* highlight groups exist, the plugin will check the Diff* highlight groups. If their foreground colours differ the plugin will use them; if not, these colours will be used:

highlight GitGutterAdd    guifg=#009900 ctermfg=2
highlight GitGutterChange guifg=#bbbb00 ctermfg=3
highlight GitGutterDelete guifg=#ff2222 ctermfg=1

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_removed_above_and_below = '{'
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 GitGutterChangeLine, i.e. DiffChange

For example, in some colorschemes the DiffText highlight group is easier to read than DiffChange. You could use it like this:

highlight link GitGutterChangeLine DiffText

Line number highlights

NOTE: This feature requires Neovim 0.3.2 or higher.

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

GitGutterAddLineNr          " default: links to CursorLineNr
GitGutterChangeLineNr       " default: links to CursorLineNr
GitGutterDeleteLineNr       " default: links to CursorLineNr
GitGutterChangeDeleteLineNr " default: links to GitGutterChangeLineNr

Maybe you think CursorLineNr is a bit annoying. For example, you could use Underlined for this:

highlight link GitGutterChangeLineNr Underlined

The diff syntax colours used in the preview window

To change the diff syntax colours used in the preview window, set up the diff* highlight groups in your colorscheme or ~/.vimrc:

diffAdded   " if not set: use GitGutterAdd's foreground colour
diffChanged " if not set: use GitGutterChange's foreground colour
diffRemoved " if not set: use GitGutterDelete's foreground colour

Note the diff* highlight groups are used in any buffer whose 'syntax' is diff.

The intra-line diff highlights used in the preview window

To change the intra-line diff highlights used in the preview window, set up the following highlight groups in your colorscheme or ~/.vimrc:

GitGutterAddIntraLine    " default: gui=reverse cterm=reverse
GitGutterDeleteIntraLine " default: gui=reverse cterm=reverse

For example, to use DiffAdd for intra-line added regions:

highlight link GitGutterAddIntraLine DiffAdd

Whether the diff is relative to the index or working tree

By default diffs are relative to the index. How you can make them relative to the working tree:

let g:gitgutter_diff_relative_to = 'working_tree'

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>'

If you are looking at a previous version of a file with Fugitive (e.g. via :0Gclog), gitgutter sets the diff base to the parent of the current revision.

This setting is ignored when the diffs are relative to the working tree.

Extra arguments for git when running git diff

If you want to pass extra arguments to git when running git diff, do so like this:

let g:gitgutter_git_args = '--git-dir-""'

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 on line number highlighting by default

Add let g:gitgutter_highlight_linenrs = 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

To use floating/popup windows for hunk previews

Add let g:gitgutter_preview_win_floating = 1 to your ~/.vimrc. Note that on Vim this prevents you staging (partial) hunks via the preview window.

The appearance of a floating/popup window for hunk previews

Either set g:gitgutter_floating_window_options to a dictionary of the options you want. This dictionary is passed directly to popup_create() (Vim) / nvim_open_win() (Neovim).

Or if you just want to override one or two of the defaults, you can do that with a file in an after/ directory. For example:

" ~/.vim/after/vim-gitgutter/overrides.vim
let g:gitgutter_floating_window_options['border'] = 'single'

To load all hunks into the current window's location list instead of the quickfix list

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

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 current buffer

This is like :GitGutterNextHunk but when it gets to the last hunk in the buffer it cycles around to the first.

function! GitGutterNextHunkCycle()
  let line = line('.')
  silent! GitGutterNextHunk
  if line('.') == line
    1
    GitGutterNextHunk
  endif
endfunction

Cycle through hunks in all buffers

You can use :GitGutterQuickFix to load all hunks into the quickfix list or the current window's location list.

Alternatively, given that]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())
      1
      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

I turned off realtime updates, how can I have signs updated when I save a file?

If you really want to update the signs when you save a file, add this to your vimrc:

autocmd BufWritePost * GitGutter

Why can't I unstage staged changes?

This plugin is for showing changes between the buffer 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)?

You can configure whether GitGutter preserves or clobbers other signs using g:gitgutter_sign_allow_clobber. Set to 1 to clobber other signs (default on Vim >= 8.1.0614 and NeoVim >= 0.4.0) or 0 to preserve them.

Troubleshooting

When no signs are showing at all

Here are some things you can check:

When the whole file is marked as added

When signs take a few seconds to appear

When signs don't update after focusing Vim

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.

Intellectual Property

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