FIX: gitgutter

This commit is contained in:
Tobias Manske 2018-07-16 16:56:25 +02:00
parent b68cc36157
commit fe8c5889ca
No known key found for this signature in database
GPG Key ID: 978D99F12D4E041F
23 changed files with 3704 additions and 0 deletions

3
.gitmodules vendored
View File

@ -148,3 +148,6 @@
[submodule "vim/plugins/fzf"]
path = vim/plugins/fzf
url = git://github.com/junegunn/fzf.vim
[submodule "vim/plugins/vim-markdown"]
path = vim/plugins/vim-markdown
url = https://github.com/plasticboy/vim-markdown.git

View File

@ -0,0 +1,8 @@
> What is the latest commit SHA in your installed vim-gitgutter?
> What vim/nvim version are you on?
> If no signs are showing at all, what does `:echo b:gitgutter.path` give?
> If no signs are showing at all, and the `path` value is a path and not `-2`, does it work with `let g:gitgutter_grep=''`?

5
vim/plugins/vim-gitgutter/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/doc/tags
/misc
/test/*.actual
*.log

View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) Andrew Stewart
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,538 @@
## 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](https://github.com/bling/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](https://github.com/mhinz/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](https://raw.github.com/airblade/vim-gitgutter/master/screenshot.png)
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:
```viml
Plug 'airblade/vim-gitgutter'
```
Then run the following in Vim:
```
:source %
:PlugInstall
```
##### NeoBundle
Place this in your .vimrc:
```viml
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:
```viml
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`:
```viml
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](https://github.com/tpope/vim-repeat).
To set your own mappings for these, for example if you prefer the mnemonics hunk-add and hunk-revert:
```viml
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:
```viml
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`:
```viml
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:
```viml
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:
```viml
let g:gitgutter_override_sign_column_highlight = 0
```
And then either update your colorscheme's `SignColumn` highlight group or set it in your vimrc:
```viml
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:
```viml
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`:
```viml
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`:
```viml
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`:
```viml
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:
```viml
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:
```viml
let g:gitgutter_diff_args = '-w'
```
#### Key mappings
To disable all key mappings:
```viml
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.
```viml
" 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:
```viml
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.
```viml
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.
```viml
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:
```viml
: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.
```viml
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):
```viml
" .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:
```viml
autocmd BufWritePost * GitGutter
```
> 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:
* [Smash Into Vim][siv]
This was one of PeepCode's all-time top three bestsellers and is now available at Pluralsight.
You can read reviews on my [website][airblade].
### Intellectual Property
Copyright Andrew Stewart, AirBlade Software Ltd. Released under the MIT licence.
[pathogen]: https://github.com/tpope/vim-pathogen
[siv]: http://pluralsight.com/training/Courses/TableOfContents/smash-into-vim
[airblade]: http://airbladesoftware.com/peepcode-vim
[terminus]: https://github.com/wincent/terminus

View File

@ -0,0 +1,139 @@
let s:t_string = type('')
" Primary functions {{{
function! gitgutter#all(force) abort
let visible = tabpagebuflist()
for bufnr in range(1, bufnr('$') + 1)
if buflisted(bufnr)
let file = expand('#'.bufnr.':p')
if !empty(file)
if index(visible, bufnr) != -1
call gitgutter#init_buffer(bufnr)
call gitgutter#process_buffer(bufnr, a:force)
elseif a:force
call s:reset_tick(bufnr)
endif
endif
endif
endfor
endfunction
" Finds the file's path relative to the repo root.
function! gitgutter#init_buffer(bufnr)
if gitgutter#utility#is_active(a:bufnr)
let p = gitgutter#utility#repo_path(a:bufnr, 0)
if type(p) != s:t_string || empty(p)
call gitgutter#utility#set_repo_path(a:bufnr)
call s:setup_maps()
endif
endif
endfunction
function! gitgutter#process_buffer(bufnr, force) abort
" NOTE a:bufnr is not necessarily the current buffer.
if gitgutter#utility#is_active(a:bufnr)
if a:force || s:has_fresh_changes(a:bufnr)
let diff = ''
try
let diff = gitgutter#diff#run_diff(a:bufnr, 0)
catch /gitgutter not tracked/
call gitgutter#debug#log('Not tracked: '.gitgutter#utility#file(a:bufnr))
catch /gitgutter diff failed/
call gitgutter#debug#log('Diff failed: '.gitgutter#utility#file(a:bufnr))
call gitgutter#hunk#reset(a:bufnr)
endtry
if diff != 'async'
call gitgutter#diff#handler(a:bufnr, diff)
endif
endif
endif
endfunction
function! gitgutter#disable() abort
" get list of all buffers (across all tabs)
for bufnr in range(1, bufnr('$') + 1)
if buflisted(bufnr)
let file = expand('#'.bufnr.':p')
if !empty(file)
call s:clear(bufnr)
endif
endif
endfor
let g:gitgutter_enabled = 0
endfunction
function! gitgutter#enable() abort
let g:gitgutter_enabled = 1
call gitgutter#all(1)
endfunction
function! gitgutter#toggle() abort
if g:gitgutter_enabled
call gitgutter#disable()
else
call gitgutter#enable()
endif
endfunction
" }}}
function! s:setup_maps()
if !g:gitgutter_map_keys
return
endif
if !hasmapto('<Plug>GitGutterPrevHunk') && maparg('[c', 'n') ==# ''
nmap <buffer> [c <Plug>GitGutterPrevHunk
endif
if !hasmapto('<Plug>GitGutterNextHunk') && maparg(']c', 'n') ==# ''
nmap <buffer> ]c <Plug>GitGutterNextHunk
endif
if !hasmapto('<Plug>GitGutterStageHunk') && maparg('<Leader>hs', 'n') ==# ''
nmap <buffer> <Leader>hs <Plug>GitGutterStageHunk
endif
if !hasmapto('<Plug>GitGutterUndoHunk') && maparg('<Leader>hu', 'n') ==# ''
nmap <buffer> <Leader>hu <Plug>GitGutterUndoHunk
endif
if !hasmapto('<Plug>GitGutterPreviewHunk') && maparg('<Leader>hp', 'n') ==# ''
nmap <buffer> <Leader>hp <Plug>GitGutterPreviewHunk
endif
if !hasmapto('<Plug>GitGutterTextObjectInnerPending') && maparg('ic', 'o') ==# ''
omap <buffer> ic <Plug>GitGutterTextObjectInnerPending
endif
if !hasmapto('<Plug>GitGutterTextObjectOuterPending') && maparg('ac', 'o') ==# ''
omap <buffer> ac <Plug>GitGutterTextObjectOuterPending
endif
if !hasmapto('<Plug>GitGutterTextObjectInnerVisual') && maparg('ic', 'x') ==# ''
xmap <buffer> ic <Plug>GitGutterTextObjectInnerVisual
endif
if !hasmapto('<Plug>GitGutterTextObjectOuterVisual') && maparg('ac', 'x') ==# ''
xmap <buffer> ac <Plug>GitGutterTextObjectOuterVisual
endif
endfunction
function! s:has_fresh_changes(bufnr) abort
return getbufvar(a:bufnr, 'changedtick') != gitgutter#utility#getbufvar(a:bufnr, 'tick')
endfunction
function! s:reset_tick(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'tick', 0)
endfunction
function! s:clear(bufnr)
call gitgutter#sign#clear_signs(a:bufnr)
call gitgutter#sign#remove_dummy_sign(a:bufnr, 1)
call gitgutter#hunk#reset(a:bufnr)
call s:reset_tick(a:bufnr)
endfunction

View File

@ -0,0 +1,89 @@
let s:available = has('nvim') || (
\ has('job') && (
\ (has('patch-7-4-1826') && !has('gui_running')) ||
\ (has('patch-7-4-1850') && has('gui_running')) ||
\ (has('patch-7-4-1832') && has('gui_macvim'))
\ )
\ )
function! gitgutter#async#available()
return s:available
endfunction
function! gitgutter#async#execute(cmd, bufnr, handler) abort
call gitgutter#debug#log('[async] '.a:cmd)
let options = {
\ 'stdoutbuffer': [],
\ 'buffer': a:bufnr,
\ 'handler': a:handler
\ }
let command = s:build_command(a:cmd)
if has('nvim')
call jobstart(command, extend(options, {
\ 'on_stdout': function('s:on_stdout_nvim'),
\ 'on_stderr': function('s:on_stderr_nvim'),
\ 'on_exit': function('s:on_exit_nvim')
\ }))
else
call job_start(command, {
\ 'out_cb': function('s:on_stdout_vim', options),
\ 'err_cb': function('s:on_stderr_vim', options),
\ 'close_cb': function('s:on_exit_vim', options)
\ })
endif
endfunction
function! s:build_command(cmd)
if has('unix')
return ['sh', '-c', a:cmd]
endif
if has('win32')
return has('nvim') ? ['cmd.exe', '/c', a:cmd] : 'cmd.exe /c '.a:cmd
endif
throw 'unknown os'
endfunction
function! s:on_stdout_nvim(_job_id, data, _event) dict abort
if empty(self.stdoutbuffer)
let self.stdoutbuffer = a:data
else
let self.stdoutbuffer = self.stdoutbuffer[:-2] +
\ [self.stdoutbuffer[-1] . a:data[0]] +
\ a:data[1:]
endif
endfunction
function! s:on_stderr_nvim(_job_id, _data, _event) dict abort
call self.handler.err(self.buffer)
endfunction
function! s:on_exit_nvim(_job_id, exit_code, _event) dict abort
if !a:exit_code
call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
endif
endfunction
function! s:on_stdout_vim(_channel, data) dict abort
call add(self.stdoutbuffer, a:data)
endfunction
function! s:on_stderr_vim(channel, _data) dict abort
call self.handler.err(self.buffer)
try
call ch_close(a:channel) " so close_cb and its 'out' handler are not triggered
catch /E906/
" noop
endtry
endfunction
function! s:on_exit_vim(_channel) dict abort
call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
endfunction

View File

@ -0,0 +1,119 @@
let s:plugin_dir = expand('<sfile>:p:h:h:h').'/'
let s:log_file = s:plugin_dir.'gitgutter.log'
let s:channel_log = s:plugin_dir.'channel.log'
let s:new_log_session = 1
function! gitgutter#debug#debug()
" Open a scratch buffer
vsplit __GitGutter_Debug__
normal! ggdG
setlocal buftype=nofile
setlocal bufhidden=delete
setlocal noswapfile
call s:vim_version()
call s:separator()
call s:git_version()
call s:separator()
call s:grep_version()
call s:separator()
call s:option('updatetime')
call s:option('shell')
call s:option('shellcmdflag')
call s:option('shellpipe')
call s:option('shellquote')
call s:option('shellredir')
call s:option('shellslash')
call s:option('shelltemp')
call s:option('shelltype')
call s:option('shellxescape')
call s:option('shellxquote')
endfunction
function! s:separator()
call s:output('')
endfunction
function! s:vim_version()
redir => version_info
silent execute 'version'
redir END
call s:output(split(version_info, '\n')[0:2])
endfunction
function! s:git_version()
let v = system(g:gitgutter_git_executable.' --version')
call s:output( substitute(v, '\n$', '', '') )
endfunction
function! s:grep_version()
let v = system('grep --version')
call s:output( substitute(v, '\n$', '', '') )
let v = system('grep --help')
call s:output( substitute(v, '\%x00', '', 'g') )
endfunction
function! s:option(name)
if exists('+' . a:name)
let v = eval('&' . a:name)
call s:output(a:name . '=' . v)
" redir => output
" silent execute "verbose set " . a:name . "?"
" redir END
" call s:output(a:name . '=' . output)
else
call s:output(a:name . ' [n/a]')
end
endfunction
function! s:output(text)
call append(line('$'), a:text)
endfunction
" assumes optional args are calling function's optional args
function! gitgutter#debug#log(message, ...) abort
if g:gitgutter_log
if s:new_log_session && gitgutter#async#available()
if exists('*ch_logfile')
call ch_logfile(s:channel_log, 'w')
endif
endif
execute 'redir >> '.s:log_file
if s:new_log_session
let s:start = reltime()
silent echo "\n==== start log session ===="
endif
let elapsed = reltimestr(reltime(s:start)).' '
silent echo ''
" callers excluding this function
silent echo elapsed.expand('<sfile>')[:-22].':'
silent echo elapsed.s:format_for_log(a:message)
if a:0 && !empty(a:1)
for msg in a:000
silent echo elapsed.s:format_for_log(msg)
endfor
endif
redir END
let s:new_log_session = 0
endif
endfunction
function! s:format_for_log(data) abort
if type(a:data) == 1
return join(split(a:data,'\n'),"\n")
elseif type(a:data) == 3
return '['.join(a:data,"\n").']'
else
return a:data
endif
endfunction

View File

@ -0,0 +1,360 @@
let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
" True for git v1.7.2+.
function! s:git_supports_command_line_config_override() abort
call system(g:gitgutter_git_executable.' -c foo.bar=baz --version')
return !v:shell_error
endfunction
let s:c_flag = s:git_supports_command_line_config_override()
let s:temp_index = tempname()
let s:temp_buffer = tempname()
" Returns a diff of the buffer.
"
" The buffer contents is not the same as the file on disk so we need to pass
" two instances of the file to git-diff:
"
" git diff myfileA myfileB
"
" where myfileA comes from
"
" git show :myfile > myfileA
"
" and myfileB is the buffer contents.
"
" After running the diff we pass it through grep where available to reduce
" subsequent processing by the plugin. If grep is not available the plugin
" does the filtering instead.
"
"
" Regarding line endings:
"
" git-show does not convert line endings.
" git-diff FILE FILE does convert line endings for the given files.
"
" If a file has CRLF line endings and git's core.autocrlf is true,
" the file in git's object store will have LF line endings. Writing
" it out via git-show will produce a file with LF line endings.
"
" If this last file is one of the files passed to git-diff, git-diff will
" convert its line endings to CRLF before diffing -- which is what we want --
" but also by default output a warning on stderr.
"
" warning: LF will be replace by CRLF in <temp file>.
" The file will have its original line endings in your working directory.
"
" When running the diff asynchronously, the warning message triggers the stderr
" callbacks which assume the overall command has failed and reset all the
" signs. As this is not what we want, and we can safely ignore the warning,
" we turn it off by passing the '-c "core.safecrlf=false"' argument to
" git-diff.
"
" When writing the temporary files we preserve the original file's extension
" so that repos using .gitattributes to control EOL conversion continue to
" convert correctly.
function! gitgutter#diff#run_diff(bufnr, preserve_full_diff) abort
while gitgutter#utility#repo_path(a:bufnr, 0) == -1
sleep 5m
endwhile
if gitgutter#utility#repo_path(a:bufnr, 0) == -2
throw 'gitgutter not tracked'
endif
" Wrap compound commands in parentheses to make Windows happy.
" bash doesn't mind the parentheses.
let cmd = '('
" Append buffer number to avoid race conditions between writing and reading
" the files when asynchronously processing multiple buffers.
"
" Without the buffer number, index_file would have a race in the shell
" between the second process writing it (with git-show) and the first
" reading it (with git-diff).
let index_file = s:temp_index.'.'.a:bufnr
" Without the buffer number, buff_file would have a race between the
" second gitgutter#process_buffer() writing the file (synchronously, below)
" and the first gitgutter#process_buffer()'s async job reading it (with
" git-diff).
let buff_file = s:temp_buffer.'.'.a:bufnr
let extension = gitgutter#utility#extension(a:bufnr)
if !empty(extension)
let index_file .= '.'.extension
let buff_file .= '.'.extension
endif
" Write file from index to temporary file.
let index_name = g:gitgutter_diff_base.':'.gitgutter#utility#repo_path(a:bufnr, 1)
let cmd .= g:gitgutter_git_executable.' --no-pager show '.index_name.' > '.index_file.' && '
" Write buffer to temporary file.
" Note: this is synchronous.
call s:write_buffer(a:bufnr, buff_file)
" Call git-diff with the temporary files.
let cmd .= g:gitgutter_git_executable.' --no-pager'
if s:c_flag
let cmd .= ' -c "diff.autorefreshindex=0"'
let cmd .= ' -c "diff.noprefix=false"'
let cmd .= ' -c "core.safecrlf=false"'
endif
let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.index_file.' '.buff_file
" Pipe git-diff output into grep.
if !a:preserve_full_diff && !empty(g:gitgutter_grep)
let cmd .= ' | '.g:gitgutter_grep.' '.gitgutter#utility#shellescape('^@@ ')
endif
" grep exits with 1 when no matches are found; git-diff exits with 1 when
" differences are found. However we want to treat non-matches and
" differences as non-erroneous behaviour; so we OR the command with one
" which always exits with success (0).
let cmd .= ' || exit 0'
let cmd .= ')'
let cmd = gitgutter#utility#cd_cmd(a:bufnr, cmd)
if g:gitgutter_async && gitgutter#async#available()
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': function('gitgutter#diff#handler'),
\ 'err': function('gitgutter#hunk#reset'),
\ })
return 'async'
else
let diff = gitgutter#utility#system(cmd)
if v:shell_error
call gitgutter#debug#log(diff)
throw 'gitgutter diff failed'
endif
return diff
endif
endfunction
function! gitgutter#diff#handler(bufnr, diff) abort
call gitgutter#debug#log(a:diff)
if !bufexists(a:bufnr)
return
endif
call gitgutter#hunk#set_hunks(a:bufnr, gitgutter#diff#parse_diff(a:diff))
let modified_lines = gitgutter#diff#process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr))
let signs_count = len(modified_lines)
if signs_count > g:gitgutter_max_signs
call gitgutter#utility#warn_once(a:bufnr, printf(
\ 'exceeded maximum number of signs (%d > %d, configured by g:gitgutter_max_signs).',
\ signs_count, g:gitgutter_max_signs), 'max_signs')
call gitgutter#sign#clear_signs(a:bufnr)
else
if g:gitgutter_signs || g:gitgutter_highlight_lines
call gitgutter#sign#update_signs(a:bufnr, modified_lines)
endif
endif
call s:save_last_seen_change(a:bufnr)
if exists('#User#GitGutter')
let g:gitgutter_hook_context = {'bufnr': a:bufnr}
execute 'doautocmd' s:nomodeline 'User GitGutter'
unlet g:gitgutter_hook_context
endif
endfunction
function! gitgutter#diff#parse_diff(diff) abort
let hunks = []
for line in split(a:diff, '\n')
let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4
call add(hunks, hunk_info)
endif
endfor
return hunks
endfunction
function! gitgutter#diff#parse_hunk(line) abort
let matches = matchlist(a:line, s:hunk_re)
if len(matches) > 0
let from_line = str2nr(matches[1])
let from_count = (matches[2] == '') ? 1 : str2nr(matches[2])
let to_line = str2nr(matches[3])
let to_count = (matches[4] == '') ? 1 : str2nr(matches[4])
return [from_line, from_count, to_line, to_count]
else
return []
end
endfunction
" This function is public so it may be used by other plugins
" e.g. vim-signature.
function! gitgutter#diff#process_hunks(bufnr, hunks) abort
let modified_lines = []
for hunk in a:hunks
call extend(modified_lines, s:process_hunk(a:bufnr, hunk))
endfor
return modified_lines
endfunction
" Returns [ [<line_number (number)>, <name (string)>], ...]
function! s:process_hunk(bufnr, hunk) abort
let modifications = []
let from_line = a:hunk[0]
let from_count = a:hunk[1]
let to_line = a:hunk[2]
let to_count = a:hunk[3]
if s:is_added(from_count, to_count)
call s:process_added(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_added(a:bufnr, to_count)
elseif s:is_removed(from_count, to_count)
call s:process_removed(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count)
elseif s:is_modified(from_count, to_count)
call s:process_modified(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
elseif s:is_modified_and_added(from_count, to_count)
call s:process_modified_and_added(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_added(a:bufnr, to_count - from_count)
call gitgutter#hunk#increment_lines_modified(a:bufnr, from_count)
elseif s:is_modified_and_removed(from_count, to_count)
call s:process_modified_and_removed(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count - to_count)
endif
return modifications
endfunction
function! s:is_added(from_count, to_count) abort
return a:from_count == 0 && a:to_count > 0
endfunction
function! s:is_removed(from_count, to_count) abort
return a:from_count > 0 && a:to_count == 0
endfunction
function! s:is_modified(from_count, to_count) abort
return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count
endfunction
function! s:is_modified_and_added(from_count, to_count) abort
return a:from_count > 0 && a:to_count > 0 && a:from_count < a:to_count
endfunction
function! s:is_modified_and_removed(from_count, to_count) abort
return a:from_count > 0 && a:to_count > 0 && a:from_count > a:to_count
endfunction
function! s:process_added(modifications, from_count, to_count, to_line) abort
let offset = 0
while offset < a:to_count
let line_number = a:to_line + offset
call add(a:modifications, [line_number, 'added'])
let offset += 1
endwhile
endfunction
function! s:process_removed(modifications, from_count, to_count, to_line) abort
if a:to_line == 0
call add(a:modifications, [1, 'removed_first_line'])
else
call add(a:modifications, [a:to_line, 'removed'])
endif
endfunction
function! s:process_modified(modifications, from_count, to_count, to_line) abort
let offset = 0
while offset < a:to_count
let line_number = a:to_line + offset
call add(a:modifications, [line_number, 'modified'])
let offset += 1
endwhile
endfunction
function! s:process_modified_and_added(modifications, from_count, to_count, to_line) abort
let offset = 0
while offset < a:from_count
let line_number = a:to_line + offset
call add(a:modifications, [line_number, 'modified'])
let offset += 1
endwhile
while offset < a:to_count
let line_number = a:to_line + offset
call add(a:modifications, [line_number, 'added'])
let offset += 1
endwhile
endfunction
function! s:process_modified_and_removed(modifications, from_count, to_count, to_line) abort
let offset = 0
while offset < a:to_count
let line_number = a:to_line + offset
call add(a:modifications, [line_number, 'modified'])
let offset += 1
endwhile
let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
endfunction
" Returns a diff for the current hunk.
function! gitgutter#diff#hunk_diff(bufnr, full_diff)
let modified_diff = []
let keep_line = 1
" Don't keepempty when splitting because the diff we want may not be the
" final one. Instead add trailing NL at end of function.
for line in split(a:full_diff, '\n')
let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4 " start of new hunk
let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info)
endif
if keep_line
call add(modified_diff, line)
endif
endfor
return join(modified_diff, "\n")."\n"
endfunction
function! s:write_buffer(bufnr, file)
let bufcontents = getbufline(a:bufnr, 1, '$')
if getbufvar(a:bufnr, '&fileformat') ==# 'dos'
call map(bufcontents, 'v:val."\r"')
endif
let fenc = getbufvar(a:bufnr, '&fileencoding')
if fenc !=# &encoding
call map(bufcontents, 'iconv(v:val, &encoding, "'.fenc.'")')
endif
if getbufvar(a:bufnr, '&bomb')
let bufcontents[0]=''.bufcontents[0]
endif
call writefile(bufcontents, a:file)
endfunction
function! s:save_last_seen_change(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'tick', getbufvar(a:bufnr, 'changedtick'))
endfunction

View File

@ -0,0 +1,149 @@
function! gitgutter#highlight#line_disable() abort
let g:gitgutter_highlight_lines = 0
call s:define_sign_line_highlights()
if !g:gitgutter_signs
call gitgutter#sign#clear_signs(bufnr(''))
call gitgutter#sign#remove_dummy_sign(bufnr(''), 0)
endif
redraw!
endfunction
function! gitgutter#highlight#line_enable() abort
let old_highlight_lines = g:gitgutter_highlight_lines
let g:gitgutter_highlight_lines = 1
call s:define_sign_line_highlights()
if !old_highlight_lines && !g:gitgutter_signs
call gitgutter#all(1)
endif
redraw!
endfunction
function! gitgutter#highlight#line_toggle() abort
if g:gitgutter_highlight_lines
call gitgutter#highlight#line_disable()
else
call gitgutter#highlight#line_enable()
endif
endfunction
function! gitgutter#highlight#define_sign_column_highlight() abort
if g:gitgutter_override_sign_column_highlight
highlight! link SignColumn LineNr
else
highlight default link SignColumn LineNr
endif
endfunction
function! gitgutter#highlight#define_highlights() abort
let [guibg, ctermbg] = s:get_background_colors('SignColumn')
" Highlights used by the signs.
execute "highlight GitGutterAddDefault guifg=#009900 guibg=" . guibg . " ctermfg=2 ctermbg=" . ctermbg
execute "highlight GitGutterChangeDefault guifg=#bbbb00 guibg=" . guibg . " ctermfg=3 ctermbg=" . ctermbg
execute "highlight GitGutterDeleteDefault guifg=#ff2222 guibg=" . guibg . " ctermfg=1 ctermbg=" . ctermbg
highlight default link GitGutterChangeDeleteDefault GitGutterChangeDefault
execute "highlight GitGutterAddInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
execute "highlight GitGutterChangeInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
execute "highlight GitGutterDeleteInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
highlight default link GitGutterChangeDeleteInvisible GitGutterChangeInvisible
highlight default link GitGutterAdd GitGutterAddDefault
highlight default link GitGutterChange GitGutterChangeDefault
highlight default link GitGutterDelete GitGutterDeleteDefault
highlight default link GitGutterChangeDelete GitGutterChangeDeleteDefault
" Highlights used for the whole line.
highlight default link GitGutterAddLine DiffAdd
highlight default link GitGutterChangeLine DiffChange
highlight default link GitGutterDeleteLine DiffDelete
highlight default link GitGutterChangeDeleteLine GitGutterChangeLine
endfunction
function! gitgutter#highlight#define_signs() abort
sign define GitGutterLineAdded
sign define GitGutterLineModified
sign define GitGutterLineRemoved
sign define GitGutterLineRemovedFirstLine
sign define GitGutterLineModifiedRemoved
sign define GitGutterDummy
call s:define_sign_text()
call gitgutter#highlight#define_sign_text_highlights()
call s:define_sign_line_highlights()
endfunction
function! s:define_sign_text() abort
execute "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added
execute "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified
execute "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed
execute "sign define GitGutterLineRemovedFirstLine text=" . g:gitgutter_sign_removed_first_line
execute "sign define GitGutterLineModifiedRemoved text=" . g:gitgutter_sign_modified_removed
endfunction
function! gitgutter#highlight#define_sign_text_highlights() abort
" Once a sign's text attribute has been defined, it cannot be undefined or
" set to an empty value. So to make signs' text disappear (when toggling
" off or disabling) we make them invisible by setting their foreground colours
" to the background's.
if g:gitgutter_signs
sign define GitGutterLineAdded texthl=GitGutterAdd
sign define GitGutterLineModified texthl=GitGutterChange
sign define GitGutterLineRemoved texthl=GitGutterDelete
sign define GitGutterLineRemovedFirstLine texthl=GitGutterDelete
sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDelete
else
sign define GitGutterLineAdded texthl=GitGutterAddInvisible
sign define GitGutterLineModified texthl=GitGutterChangeInvisible
sign define GitGutterLineRemoved texthl=GitGutterDeleteInvisible
sign define GitGutterLineRemovedFirstLine texthl=GitGutterDeleteInvisible
sign define GitGutterLineModifiedRemoved texthl=GitGutterChangeDeleteInvisible
endif
endfunction
function! s:define_sign_line_highlights() abort
if g:gitgutter_highlight_lines
sign define GitGutterLineAdded linehl=GitGutterAddLine
sign define GitGutterLineModified linehl=GitGutterChangeLine
sign define GitGutterLineRemoved linehl=GitGutterDeleteLine
sign define GitGutterLineRemovedFirstLine linehl=GitGutterDeleteLine
sign define GitGutterLineModifiedRemoved linehl=GitGutterChangeDeleteLine
else
sign define GitGutterLineAdded linehl=
sign define GitGutterLineModified linehl=
sign define GitGutterLineRemoved linehl=
sign define GitGutterLineRemovedFirstLine linehl=
sign define GitGutterLineModifiedRemoved linehl=
endif
endfunction
function! s:get_background_colors(group) abort
redir => highlight
silent execute 'silent highlight ' . a:group
redir END
let link_matches = matchlist(highlight, 'links to \(\S\+\)')
if len(link_matches) > 0 " follow the link
return s:get_background_colors(link_matches[1])
endif
let ctermbg = s:match_highlight(highlight, 'ctermbg=\([0-9A-Za-z]\+\)')
let guibg = s:match_highlight(highlight, 'guibg=\([#0-9A-Za-z]\+\)')
return [guibg, ctermbg]
endfunction
function! s:match_highlight(highlight, pattern) abort
let matches = matchlist(a:highlight, a:pattern)
if len(matches) == 0
return 'NONE'
endif
return matches[1]
endfunction

View File

@ -0,0 +1,281 @@
function! gitgutter#hunk#set_hunks(bufnr, hunks) abort
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', a:hunks)
call s:reset_summary(a:bufnr)
endfunction
function! gitgutter#hunk#hunks(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'hunks', [])
endfunction
function! gitgutter#hunk#reset(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'hunks', [])
call s:reset_summary(a:bufnr)
endfunction
function! gitgutter#hunk#summary(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0])
endfunction
function! s:reset_summary(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'summary', [0,0,0])
endfunction
function! gitgutter#hunk#increment_lines_added(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[0] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#increment_lines_modified(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[1] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#increment_lines_removed(bufnr, count) abort
let summary = gitgutter#hunk#summary(a:bufnr)
let summary[2] += a:count
call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction
function! gitgutter#hunk#next_hunk(count) abort
let bufnr = bufnr('')
if gitgutter#utility#is_active(bufnr)
let current_line = line('.')
let hunk_count = 0
for hunk in gitgutter#hunk#hunks(bufnr)
if hunk[2] > current_line
let hunk_count += 1
if hunk_count == a:count
execute 'normal!' hunk[2] . 'Gzv'
return
endif
endif
endfor
call gitgutter#utility#warn('No more hunks')
endif
endfunction
function! gitgutter#hunk#prev_hunk(count) abort
let bufnr = bufnr('')
if gitgutter#utility#is_active(bufnr)
let current_line = line('.')
let hunk_count = 0
for hunk in reverse(copy(gitgutter#hunk#hunks(bufnr)))
if hunk[2] < current_line
let hunk_count += 1
if hunk_count == a:count
let target = hunk[2] == 0 ? 1 : hunk[2]
execute 'normal!' target . 'Gzv'
return
endif
endif
endfor
call gitgutter#utility#warn('No previous hunks')
endif
endfunction
" Returns the hunk the cursor is currently in or an empty list if the cursor
" isn't in a hunk.
function! s:current_hunk() abort
let bufnr = bufnr('')
let current_hunk = []
for hunk in gitgutter#hunk#hunks(bufnr)
if gitgutter#hunk#cursor_in_hunk(hunk)
let current_hunk = hunk
break
endif
endfor
return current_hunk
endfunction
function! gitgutter#hunk#cursor_in_hunk(hunk) abort
let current_line = line('.')
if current_line == 1 && a:hunk[2] == 0
return 1
endif
if current_line >= a:hunk[2] && current_line < a:hunk[2] + (a:hunk[3] == 0 ? 1 : a:hunk[3])
return 1
endif
return 0
endfunction
function! gitgutter#hunk#text_object(inner) abort
let hunk = s:current_hunk()
if empty(hunk)
return
endif
let [first_line, last_line] = [hunk[2], hunk[2] + hunk[3] - 1]
if ! a:inner
let lnum = last_line
let eof = line('$')
while lnum < eof && empty(getline(lnum + 1))
let lnum +=1
endwhile
let last_line = lnum
endif
execute 'normal! 'first_line.'GV'.last_line.'G'
endfunction
function! gitgutter#hunk#stage() abort
call s:hunk_op(function('s:stage'))
silent! call repeat#set("\<Plug>GitGutterStageHunk", -1)<CR>
endfunction
function! gitgutter#hunk#undo() abort
call s:hunk_op(function('s:undo'))
silent! call repeat#set("\<Plug>GitGutterUndoHunk", -1)<CR>
endfunction
function! gitgutter#hunk#preview() abort
call s:hunk_op(function('s:preview'))
silent! call repeat#set("\<Plug>GitGutterPreviewHunk", -1)<CR>
endfunction
function! s:hunk_op(op)
let bufnr = bufnr('')
if gitgutter#utility#is_active(bufnr)
" Get a (synchronous) diff.
let [async, g:gitgutter_async] = [g:gitgutter_async, 0]
let diff = gitgutter#diff#run_diff(bufnr, 1)
let g:gitgutter_async = async
call gitgutter#hunk#set_hunks(bufnr, gitgutter#diff#parse_diff(diff))
if empty(s:current_hunk())
call gitgutter#utility#warn('cursor is not in a hunk')
else
call a:op(gitgutter#diff#hunk_diff(bufnr, diff))
endif
endif
endfunction
function! s:stage(hunk_diff)
let bufnr = bufnr('')
let diff = s:adjust_header(bufnr, a:hunk_diff)
" Apply patch to index.
call gitgutter#utility#system(
\ gitgutter#utility#cd_cmd(bufnr, g:gitgutter_git_executable.' apply --cached --unidiff-zero - '),
\ diff)
" Refresh gitgutter's view of buffer.
call gitgutter#process_buffer(bufnr, 1)
endfunction
function! s:undo(hunk_diff)
" Apply reverse patch to buffer.
let hunk = gitgutter#diff#parse_hunk(split(a:hunk_diff, '\n')[4])
let lines = map(split(a:hunk_diff, '\n')[5:], 'v:val[1:]')
let lnum = hunk[2]
let added_only = hunk[1] == 0 && hunk[3] > 0
let removed_only = hunk[1] > 0 && hunk[3] == 0
if removed_only
call append(lnum, lines)
elseif added_only
execute lnum .','. (lnum+len(lines)-1) .'d'
else
call append(lnum-1, lines[0:hunk[1]])
execute (lnum+hunk[1]) .','. (lnum+hunk[1]+hunk[3]) .'d'
endif
endfunction
function! s:preview(hunk_diff)
let hunk_lines = split(s:discard_header(a:hunk_diff), "\n")
let hunk_lines_length = len(hunk_lines)
let previewheight = min([hunk_lines_length, &previewheight])
silent! wincmd P
if !&previewwindow
noautocmd execute 'bo' previewheight 'new'
set previewwindow
else
execute 'resize' previewheight
endif
setlocal noreadonly modifiable filetype=diff buftype=nofile bufhidden=delete noswapfile
execute "%delete_"
call append(0, hunk_lines)
normal! gg
setlocal readonly nomodifiable
noautocmd wincmd p
endfunction
function! s:adjust_header(bufnr, hunk_diff)
let filepath = gitgutter#utility#repo_path(a:bufnr, 0)
return s:adjust_hunk_summary(s:fix_file_references(filepath, a:hunk_diff))
endfunction
" Replaces references to temp files with the actual file.
function! s:fix_file_references(filepath, hunk_diff)
let lines = split(a:hunk_diff, '\n')
let left_prefix = matchstr(lines[2], '[abciow12]').'/'
let right_prefix = matchstr(lines[3], '[abciow12]').'/'
let quote = lines[0][11] == '"' ? '"' : ''
let left_file = quote.left_prefix.a:filepath.quote
let right_file = quote.right_prefix.a:filepath.quote
let lines[0] = 'diff --git '.left_file.' '.right_file
let lines[2] = '--- '.left_file
let lines[3] = '+++ '.right_file
return join(lines, "\n")."\n"
endfunction
if $VIM_GITGUTTER_TEST
function! gitgutter#hunk#fix_file_references(filepath, hunk_diff)
return s:fix_file_references(a:filepath, a:hunk_diff)
endfunction
endif
function! s:adjust_hunk_summary(hunk_diff) abort
let line_adjustment = s:line_adjustment_for_current_hunk()
let diff = split(a:hunk_diff, '\n', 1)
let diff[4] = substitute(diff[4], '+\@<=\(\d\+\)', '\=submatch(1)+line_adjustment', '')
return join(diff, "\n")
endfunction
function! s:discard_header(hunk_diff)
return join(split(a:hunk_diff, '\n', 1)[5:], "\n")
endfunction
" Returns the number of lines the current hunk is offset from where it would
" be if any changes above it in the file didn't exist.
function! s:line_adjustment_for_current_hunk() abort
let bufnr = bufnr('')
let adj = 0
for hunk in gitgutter#hunk#hunks(bufnr)
if gitgutter#hunk#cursor_in_hunk(hunk)
break
else
let adj += hunk[1] - hunk[3]
endif
endfor
return adj
endfunction

View File

@ -0,0 +1,219 @@
" Vim doesn't namespace sign ids so every plugin shares the same
" namespace. Sign ids are simply integers so to avoid clashes with other
" signs we guess at a clear run.
"
" Note also we currently never reset s:next_sign_id.
let s:first_sign_id = 3000
let s:next_sign_id = s:first_sign_id
let s:dummy_sign_id = s:first_sign_id - 1
" Remove-all-signs optimisation requires Vim 7.3.596+.
let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596"))
function! gitgutter#sign#enable() abort
let old_signs = g:gitgutter_signs
let g:gitgutter_signs = 1
call gitgutter#highlight#define_sign_text_highlights()
if !old_signs && !g:gitgutter_highlight_lines
call gitgutter#all(1)
endif
endfunction
function! gitgutter#sign#disable() abort
let g:gitgutter_signs = 0
call gitgutter#highlight#define_sign_text_highlights()
if !g:gitgutter_highlight_lines
call gitgutter#sign#clear_signs(bufnr(''))
call gitgutter#sign#remove_dummy_sign(bufnr(''), 0)
endif
endfunction
function! gitgutter#sign#toggle() abort
if g:gitgutter_signs
call gitgutter#sign#disable()
else
call gitgutter#sign#enable()
endif
endfunction
" Removes gitgutter's signs (excluding dummy sign) from the buffer being processed.
function! gitgutter#sign#clear_signs(bufnr) abort
call s:find_current_signs(a:bufnr)
let sign_ids = map(values(gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')), 'v:val.id')
call s:remove_signs(a:bufnr, sign_ids, 1)
call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', {})
endfunction
" Updates gitgutter's signs in the buffer being processed.
"
" modified_lines: list of [<line_number (number)>, <name (string)>]
" where name = 'added|removed|modified|modified_removed'
function! gitgutter#sign#update_signs(bufnr, modified_lines) abort
call s:find_current_signs(a:bufnr)
let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]')
let obsolete_signs = s:obsolete_gitgutter_signs_to_remove(a:bufnr, new_gitgutter_signs_line_numbers)
let flicker_possible = s:remove_all_old_signs && !empty(a:modified_lines)
if flicker_possible
call s:add_dummy_sign(a:bufnr)
endif
call s:remove_signs(a:bufnr, obsolete_signs, s:remove_all_old_signs)
call s:upsert_new_gitgutter_signs(a:bufnr, a:modified_lines)
if flicker_possible
call gitgutter#sign#remove_dummy_sign(a:bufnr, 0)
endif
endfunction
function! s:add_dummy_sign(bufnr) abort
if !gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign')
execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . a:bufnr
call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', 1)
endif
endfunction
function! gitgutter#sign#remove_dummy_sign(bufnr, force) abort
if gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign') && (a:force || !g:gitgutter_sign_column_always)
execute "sign unplace" s:dummy_sign_id "buffer=" . a:bufnr
call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', 0)
endif
endfunction
"
" Internal functions
"
function! s:find_current_signs(bufnr) abort
let gitgutter_signs = {} " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>}
let other_signs = [] " [<line_number (number),...]
let dummy_sign_placed = 0
redir => signs
silent execute "sign place buffer=" . a:bufnr
redir END
for sign_line in filter(split(signs, '\n')[2:], 'v:val =~# "="')
" Typical sign line: line=88 id=1234 name=GitGutterLineAdded
" We assume splitting is faster than a regexp.
let components = split(sign_line)
let name = split(components[2], '=')[1]
if name =~# 'GitGutterDummy'
let dummy_sign_placed = 1
else
let line_number = str2nr(split(components[0], '=')[1])
if name =~# 'GitGutter'
let id = str2nr(split(components[1], '=')[1])
" Remove orphaned signs (signs placed on lines which have been deleted).
" (When a line is deleted its sign lingers. Subsequent lines' signs'
" line numbers are decremented appropriately.)
if has_key(gitgutter_signs, line_number)
execute "sign unplace" gitgutter_signs[line_number].id
endif
let gitgutter_signs[line_number] = {'id': id, 'name': name}
else
call add(other_signs, line_number)
endif
end
endfor
call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', dummy_sign_placed)
call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', gitgutter_signs)
call gitgutter#utility#setbufvar(a:bufnr, 'other_signs', other_signs)
endfunction
" Returns a list of [<id (number)>, ...]
" Sets `s:remove_all_old_signs` as a side-effect.
function! s:obsolete_gitgutter_signs_to_remove(bufnr, new_gitgutter_signs_line_numbers) abort
let signs_to_remove = [] " list of [<id (number)>, ...]
let remove_all_signs = 1
let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
for line_number in keys(old_gitgutter_signs)
if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1
call add(signs_to_remove, old_gitgutter_signs[line_number].id)
else
let remove_all_signs = 0
endif
endfor
let s:remove_all_old_signs = remove_all_signs
return signs_to_remove
endfunction
function! s:remove_signs(bufnr, sign_ids, all_signs) abort
if a:all_signs && s:supports_star && empty(gitgutter#utility#getbufvar(a:bufnr, 'other_signs'))
let dummy_sign_present = gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign')
execute "sign unplace * buffer=" . a:bufnr
if dummy_sign_present
execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . a:bufnr
endif
else
for id in a:sign_ids
execute "sign unplace" id
endfor
endif
endfunction
function! s:upsert_new_gitgutter_signs(bufnr, modified_lines) abort
let other_signs = gitgutter#utility#getbufvar(a:bufnr, 'other_signs')
let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
for line in a:modified_lines
let line_number = line[0] " <number>
if index(other_signs, line_number) == -1 " don't clobber others' signs
let name = s:highlight_name_for_change(line[1])
if !has_key(old_gitgutter_signs, line_number) " insert
let id = s:next_sign_id()
execute "sign place" id "line=" . line_number "name=" . name "buffer=" . a:bufnr
else " update if sign has changed
let old_sign = old_gitgutter_signs[line_number]
if old_sign.name !=# name
execute "sign place" old_sign.id "name=" . name "buffer=" . a:bufnr
end
endif
endif
endfor
" At this point b:gitgutter_gitgutter_signs is out of date.
endfunction
function! s:next_sign_id() abort
let next_id = s:next_sign_id
let s:next_sign_id += 1
return next_id
endfunction
" Only for testing.
function! gitgutter#sign#reset()
let s:next_sign_id = s:first_sign_id
endfunction
function! s:highlight_name_for_change(text) abort
if a:text ==# 'added'
return 'GitGutterLineAdded'
elseif a:text ==# 'removed'
return 'GitGutterLineRemoved'
elseif a:text ==# 'removed_first_line'
return 'GitGutterLineRemovedFirstLine'
elseif a:text ==# 'modified'
return 'GitGutterLineModified'
elseif a:text ==# 'modified_removed'
return 'GitGutterLineModifiedRemoved'
endif
endfunction

View File

@ -0,0 +1,210 @@
function! gitgutter#utility#supports_overscore_sign()
if gitgutter#utility#windows()
return &encoding ==? 'utf-8'
else
return &termencoding ==? &encoding || &termencoding == ''
endif
endfunction
function! gitgutter#utility#setbufvar(buffer, varname, val)
let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {})
let needs_setting = empty(dict)
let dict[a:varname] = a:val
if needs_setting
call setbufvar(+a:buffer, 'gitgutter', dict)
endif
endfunction
function! gitgutter#utility#getbufvar(buffer, varname, ...)
let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {})
if has_key(dict, a:varname)
return dict[a:varname]
else
if a:0
return a:1
endif
endif
endfunction
function! gitgutter#utility#warn(message) abort
echohl WarningMsg
echo 'vim-gitgutter: ' . a:message
echohl None
let v:warningmsg = a:message
endfunction
function! gitgutter#utility#warn_once(bufnr, message, key) abort
if empty(gitgutter#utility#getbufvar(a:bufnr, a:key))
call gitgutter#utility#setbufvar(a:bufnr, a:key, '1')
echohl WarningMsg
redraw | echom 'vim-gitgutter: ' . a:message
echohl None
let v:warningmsg = a:message
endif
endfunction
" Returns truthy when the buffer's file should be processed; and falsey when it shouldn't.
" This function does not and should not make any system calls.
function! gitgutter#utility#is_active(bufnr) abort
return g:gitgutter_enabled &&
\ !pumvisible() &&
\ s:is_file_buffer(a:bufnr) &&
\ s:exists_file(a:bufnr) &&
\ s:not_git_dir(a:bufnr)
endfunction
function! s:not_git_dir(bufnr) abort
return s:dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
endfunction
function! s:is_file_buffer(bufnr) abort
return empty(getbufvar(a:bufnr, '&buftype'))
endfunction
" From tpope/vim-fugitive
function! s:winshell()
return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
endfunction
" From tpope/vim-fugitive
function! gitgutter#utility#shellescape(arg) abort
if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
return a:arg
elseif s:winshell()
return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"'
else
return shellescape(a:arg)
endif
endfunction
function! gitgutter#utility#file(bufnr)
return s:abs_path(a:bufnr, 1)
endfunction
" Not shellescaped
function! gitgutter#utility#extension(bufnr) abort
return fnamemodify(s:abs_path(a:bufnr, 0), ':e')
endfunction
function! gitgutter#utility#system(cmd, ...) abort
call gitgutter#debug#log(a:cmd, a:000)
call s:use_known_shell()
silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
call s:restore_shell()
return output
endfunction
" Path of file relative to repo root.
"
" * empty string - not set
" * non-empty string - path
" * -1 - pending
" * -2 - not tracked by git
function! gitgutter#utility#repo_path(bufnr, shellesc) abort
let p = gitgutter#utility#getbufvar(a:bufnr, 'path')
return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction
function! gitgutter#utility#set_repo_path(bufnr) abort
" Values of path:
" * non-empty string - path
" * -1 - pending
" * -2 - not tracked by git
call gitgutter#utility#setbufvar(a:bufnr, 'path', -1)
let cmd = gitgutter#utility#cd_cmd(a:bufnr, g:gitgutter_git_executable.' ls-files --error-unmatch --full-name -- '.gitgutter#utility#shellescape(s:filename(a:bufnr)))
if g:gitgutter_async && gitgutter#async#available()
if has('lambda')
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': {bufnr, path -> gitgutter#utility#setbufvar(bufnr, 'path', s:strip_trailing_new_line(path))},
\ 'err': {bufnr -> gitgutter#utility#setbufvar(bufnr, 'path', -2)},
\ })
else
if has('nvim') && !has('nvim-0.2.0')
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': function('s:set_path'),
\ 'err': function('s:not_tracked_by_git')
\ })
else
call gitgutter#async#execute(cmd, a:bufnr, {
\ 'out': function('s:set_path'),
\ 'err': function('s:set_path', [-2])
\ })
endif
endif
else
let path = gitgutter#utility#system(cmd)
if v:shell_error
call gitgutter#utility#setbufvar(a:bufnr, 'path', -2)
else
call gitgutter#utility#setbufvar(a:bufnr, 'path', s:strip_trailing_new_line(path))
endif
endif
endfunction
if has('nvim') && !has('nvim-0.2.0')
function! s:not_tracked_by_git(bufnr)
call s:set_path(a:bufnr, -2)
endfunction
endif
function! s:set_path(bufnr, path)
if a:bufnr == -2
let [bufnr, path] = [a:path, a:bufnr]
call gitgutter#utility#setbufvar(bufnr, 'path', path)
else
call gitgutter#utility#setbufvar(a:bufnr, 'path', s:strip_trailing_new_line(a:path))
endif
endfunction
function! gitgutter#utility#cd_cmd(bufnr, cmd) abort
let cd = s:unc_path(a:bufnr) ? 'pushd' : (gitgutter#utility#windows() ? 'cd /d' : 'cd')
return cd.' '.s:dir(a:bufnr).' && '.a:cmd
endfunction
function! s:unc_path(bufnr)
return s:abs_path(a:bufnr, 0) =~ '^\\\\'
endfunction
function! s:use_known_shell() abort
if has('unix') && &shell !=# 'sh'
let [s:shell, s:shellcmdflag, s:shellredir] = [&shell, &shellcmdflag, &shellredir]
let &shell = 'sh'
set shellcmdflag=-c shellredir=>%s\ 2>&1
endif
endfunction
function! s:restore_shell() abort
if has('unix') && exists('s:shell')
let [&shell, &shellcmdflag, &shellredir] = [s:shell, s:shellcmdflag, s:shellredir]
endif
endfunction
function! s:abs_path(bufnr, shellesc)
let p = resolve(expand('#'.a:bufnr.':p'))
return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction
function! s:dir(bufnr) abort
return gitgutter#utility#shellescape(fnamemodify(s:abs_path(a:bufnr, 0), ':h'))
endfunction
" Not shellescaped.
function! s:filename(bufnr) abort
return fnamemodify(s:abs_path(a:bufnr, 0), ':t')
endfunction
function! s:exists_file(bufnr) abort
return filereadable(s:abs_path(a:bufnr, 0))
endfunction
function! s:strip_trailing_new_line(line) abort
return substitute(a:line, '\n$', '', '')
endfunction
function! gitgutter#utility#windows()
return has('win64') || has('win32') || has('win16')
endfunction

View File

@ -0,0 +1,529 @@
*gitgutter.txt* A Vim plugin which shows a git diff in the gutter.
Vim Git Gutter
Author: Andy Stewart <https://airbladesoftware.com/>
Plugin Homepage: <https://github.com/airblade/vim-gitgutter>
===============================================================================
CONTENTS *gitgutter*
Introduction ................. |gitgutter-introduction|
Installation ................. |gitgutter-installation|
Commands ..................... |gitgutter-commands|
Mappings ..................... |gitgutter-mappings|
Autocommand .................. |gitgutter-autocommand|
Options ...................... |gitgutter-options|
Highlights ................... |gitgutter-highlights|
FAQ .......................... |gitgutter-faq|
TROUBLESHOOTING .............. |gitgutter-troubleshooting|
===============================================================================
INTRODUCTION *gitgutter-introduction*
GitGutter is 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.
===============================================================================
INSTALLATION *gitgutter-installation*
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 vim-gitgutter/* ~/.vim/
<
See |add-global-plugin|.
===============================================================================
COMMANDS *gitgutter-commands*
Commands for turning vim-gitgutter on and off:~
*gitgutter-:GitGutterDisable*
:GitGutterDisable Turn vim-gitgutter off for all buffers.
*gitgutter-:GitGutterEnable*
:GitGutterEnable Turn vim-gitgutter on for all buffers.
*gitgutter-:GitGutterToggle*
:GitGutterToggle Toggle vim-gitgutter on or off for all buffers.
*gitgutter-:GitGutter*
:GitGutter Update signs for the current buffer. You shouldn't
need to run this.
*gitgutter-:GitGutterAll*
:GitGutterAll Update signs for all buffers. You shouldn't need to
run this.
Commands for turning signs on and off (defaults to on):~
*gitgutter-:GitGutterSignsEnable*
:GitGutterSignsEnable Show signs for the diff.
*gitgutter-:GitGutterSignsDisable*
:GitGutterSignsDisable Do not show signs for the diff.
*gitgutter-:GitGutterSignsToggle*
:GitGutterSignsToggle Toggle signs on or off.
Commands for turning line highlighting on and off (defaults to off):~
*gitgutter-:GitGutterLineHighlightsEnable*
:GitGutterLineHighlightsEnable Turn on line highlighting.
*gitgutter-:GitGutterLineHighlightsDisable*
:GitGutterLineHighlightsDisable Turn off line highlighting.
*gitgutter-:GitGutterLineHighlightsToggle*
:GitGutterLineHighlightsToggle Turn line highlighting on or off.
Commands for jumping between hunks:~
*gitgutter-:GitGutterNextHunk*
:GitGutterNextHunk Jump to the next [count] hunk.
*gitgutter-:GitGutterPrevHunk*
:GitGutterPrevHunk Jump to the previous [count] hunk.
Commands for operating on a hunk:~
*gitgutter-:GitGutterStageHunk*
:GitGutterStageHunk Stage the hunk the cursor is in.
*gitgutter-:GitGutterUndoHunk*
:GitGutterUndoHunk Undo the hunk the cursor is in.
*gitgutter-:GitGutterPreviewHunk*
:GitGutterPreviewHunk Preview the hunk the cursor is in.
Use |:pclose| or |CTRL-W_CTRL-Z| to close the preview
window.
===============================================================================
AUTOCOMMAND *gitgutter-autocommand*
User GitGutter~
After updating a buffer's signs vim-gitgutter fires a |User| |autocmd| with the
event GitGutter. You can listen for this event, for example:
>
autocmd User GitGutter call updateMyStatusLine()
<
A dictionary `g:gitgutter_hook_context` is made available during its execution,
which contains an entry `bufnr` that contains the buffer number being updated.
===============================================================================
MAPPINGS *gitgutter-mappings*
You can disable all these mappings with:
>
let g:gitgutter_map_keys = 0
<
Hunk operations:~
These can be repeated with `.` if you have vim-repeat installed.
*gitgutter-<Leader>hp*
<Leader>hp Preview the hunk under the cursor.
*gitgutter-<Leader>hs*
<Leader>hs Stage the hunk under the cursor.
*gitgutter-<Leader>hu*
<Leader>hu Undo the hunk under the cursor.
You can change these mappings like this:
>
nmap ghp <Plug>GitGutterPreviewHunk
nmap ghs <Plug>GitGutterStageHunk
nmap ghu <Plug>GitGutterUndoHunk
<
Hunk jumping:~
*gitgutter-]c*
]c Jump to the next [count] hunk.
*gitgutter-[c*
[c Jump to the previous [count] hunk.
You can change these mappings like this:
>
nmap [c <Plug>GitGutterPrevHunk
nmap ]c <Plug>GitGutterNextHunk
<
Hunk text object:~
*gitgutter-ic* *gitgutter-ac* *gitgutter-text-object*
"ic" operates on the current hunk's lines. "ac" does the same but also includes
trailing empty lines.
>
omap ic <Plug>GitGutterTextObjectInnerPending
omap ac <Plug>GitGutterTextObjectOuterPending
xmap ic <Plug>GitGutterTextObjectInnerVisual
xmap ac <Plug>GitGutterTextObjectOuterVisual
<
===============================================================================
OPTIONS *gitgutter-options*
The most important option is 'updatetime' which determines how long (in
milliseconds) the plugin will wait after you stop typing before it updates the
signs. Vim's default is 4000. I recommend 100.
Most important option:~
'updatetime'
Git:~
|g:gitgutter_git_executable|
|g:gitgutter_diff_args|
|g:gitgutter_diff_base|
Grep:~
|g:gitgutter_grep|
Signs:~
|g:gitgutter_signs|
|g:gitgutter_highlight_lines|
|g:gitgutter_max_signs|
|g:gitgutter_sign_added|
|g:gitgutter_sign_modified|
|g:gitgutter_sign_removed|
|g:gitgutter_sign_removed_first_line|
|g:gitgutter_sign_modified_removed|
|g:gitgutter_sign_column_always|
|g:gitgutter_override_sign_column_highlight|
Terminal:~
|g:gitgutter_terminal_reports_focus|
General:~
|g:gitgutter_enabled|
|g:gitgutter_map_keys|
|g:gitgutter_async|
|g:gitgutter_log|
*g:gitgutter_git_executable*
Default: 'git'
This option determines what git binary to use. Set this if git is not on your
path.
*g:gitgutter_diff_args*
Default: empty
Use this option to pass any extra arguments to git-diff. For example:
>
let g:gitgutter_diff_args = '-w'
<
*g:gitgutter_diff_base*
Default: empty
By default buffers are diffed against the index. Use this option to diff against
a revision instead. For example:
>
let g:gitgutter_diff_base = '<some commit SHA>'
<
*g:gitgutter_grep*
Default: 'grep'
The plugin pipes the output of git-diff into grep to minimise the amount of data
vim has to process. Set this option if grep is not on your path.
grep must produce plain-text output without any ANSI escape codes or colours.
Use this option to turn off colours if necessary.
>
let g:gitgutter_grep = 'grep --color=never'
<
If you do not want to use grep at all (perhaps to debug why signs are not
showing), set this option to an empty string:
>
let g:gitgutter_grep = ''
<
*g:gitgutter_signs*
Default: 1
Determines whether or not to show signs.
*g:gitgutter_highlight_lines*
Default: 0
Determines whether or not to show line highlights.
*g:gitgutter_max_signs*
Default: 500
Sets the maximum number of signs to show in a buffer. Vim is slow at updating
signs, so to avoid slowing down the GUI the number of signs is capped. When
the number of changed lines exceeds this value, the plugin removes all signs
and displays a warning message.
*g:gitgutter_sign_added*
*g:gitgutter_sign_modified*
*g:gitgutter_sign_removed*
*g:gitgutter_sign_removed_first_line*
*g:gitgutter_sign_modified_removed*
Defaults:
>
let g:gitgutter_sign_added = '+'
let g:gitgutter_sign_modified = '~'
let g:gitgutter_sign_removed = '_'
let g:gitgutter_sign_removed_first_line = '‾'
let g:gitgutter_sign_modified_removed = '~_'
<
You can use unicode characters but not images. Signs must not take up more than
2 columns.
*g:gitgutter_sign_column_always*
Default: 0
This legacy option controls whether the sign column should always be shown, even
if there are no signs to display.
From Vim 7.4.2201, use 'signcolumn' instead:
>
set signcolumn=yes
<
*g:gitgutter_override_sign_column_highlight*
Default: 1
Controls whether to make the sign column look like the line-number column (i.e.
the |hl-LineNr| highlight group).
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 |hlSignColumn| highlight group or set
it in your |vimrc|:
Desired appearance Command ~
Same as line-number column highlight clear SignColumn
User-defined (terminal Vim) highlight SignColumn ctermbg={whatever}
User-defined (graphical Vim) highlight SignColumn guibg={whatever}
*g:gitgutter_terminal_reports_focus*
Default: 1
Normally the plugin uses |FocusGained| to force-update all buffers when Vim
receives focus. However some terminals do not report focus events and so the
|FocusGained| autocommand never fires.
If this applies to you, either install something like Terminus
(https://github.com/wincent/terminus) to make |FocusGained| work or set this
option to 0.
When this option is 0, the plugin force-updates the buffer on |BufEnter|
(instead of only updating if the buffer's contents has changed since the last
update).
*g:gitgutter_enabled*
Default: 1
Controls whether or not the plugin is on at startup.
*g:gitgutter_map_keys*
Default: 1
Controls whether or not the plugin provides mappings. See |gitgutter-mapppings|.
*g:gitgutter_async*
Default: 1
Controls whether or not diffs are run in the background. This has no effect if
your Vim does not support background jobs.
*g:gitgutter_log*
Default: 0
When switched on, the plugin logs to gitgutter.log in the directory where it is
installed. Additionally it logs channel activity to channel.log.
===============================================================================
HIGHLIGHTS *gitgutter-highlights*
To change the signs' 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 change the line highlights, 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
<
===============================================================================
FAQ *gitgutter-faq*
a. How do I turn off realtime updates?
Add this to your vim configuration in an |after-directory|:
>
autocmd! gitgutter CursorHold,CursorHoldI
<
b. 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
<
c. 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.
d. Why are the colours in the sign column weird?
Your colorscheme is configuring the |hl-SignColumn| highlight group weirdly.
Please see |g:gitgutter_override_sign_column_highlight| on customising the
sign column.
e. What happens if I also use another plugin which uses signs (e.g. Syntastic)?
Vim only allows one sign per line. Vim-gitgutter will not interfere with
signs it did not add.
===============================================================================
TROUBLESHOOTING *gitgutter-troubleshooting*
When no signs are showing at all:~
1. Try bypassing grep with:
>
let g:gitgutter_grep = ''
<
If it works, the problem is grep outputting ANSI escape codes. Use this
option to pass arguments to grep to turn off the escape codes.
2. Verify git is on your path:
>
:echo system('git --version')
<
3. Verify your git config is compatible with the version of git return by the
command above.
4. Verify your Vim supports signs. The following should give 1:
>
:echo has('signs')
<
5. Check whether the plugin thinks git knows about your file:
>
:echo getbufvar('','gitgutter').path
<
If the result is -2, the plugin thinks your file is not tracked by git.
When the whole file is marked as added:~
If you use zsh, and you set "CDPATH", make sure "CDPATH" does not include the
current directory.
When signs take a few seconds to appear:~
Try reducing 'updatetime':
>
set updatetime=100
<
When signs don't update after focusing Vim:~
Your terminal probably isn't reporting focus events. Either try installing
Terminus (https://github.com/wincent/terminus) or set:
>
let g:gitgutter_terminal_reports_focus = 0
<

View File

@ -0,0 +1,214 @@
scriptencoding utf-8
if exists('g:loaded_gitgutter') || !has('signs') || &cp
finish
endif
let g:loaded_gitgutter = 1
" Initialisation {{{
if v:version < 703 || (v:version == 703 && !has("patch105"))
call gitgutter#utility#warn('requires Vim 7.3.105')
finish
endif
function! s:set(var, default) abort
if !exists(a:var)
if type(a:default)
execute 'let' a:var '=' string(a:default)
else
execute 'let' a:var '=' a:default
endif
endif
endfunction
call s:set('g:gitgutter_enabled', 1)
call s:set('g:gitgutter_max_signs', 500)
call s:set('g:gitgutter_signs', 1)
call s:set('g:gitgutter_highlight_lines', 0)
call s:set('g:gitgutter_sign_column_always', 0)
if g:gitgutter_sign_column_always && exists('&signcolumn')
" Vim 7.4.2201.
set signcolumn=yes
let g:gitgutter_sign_column_always = 0
call gitgutter#utility#warn('please replace "let g:gitgutter_sign_column_always=1" with "set signcolumn=yes"')
endif
call s:set('g:gitgutter_override_sign_column_highlight', 1)
call s:set('g:gitgutter_sign_added', '+')
call s:set('g:gitgutter_sign_modified', '~')
call s:set('g:gitgutter_sign_removed', '_')
if gitgutter#utility#supports_overscore_sign()
call s:set('g:gitgutter_sign_removed_first_line', '‾')
else
call s:set('g:gitgutter_sign_removed_first_line', '_^')
endif
call s:set('g:gitgutter_sign_modified_removed', '~_')
call s:set('g:gitgutter_diff_args', '')
call s:set('g:gitgutter_diff_base', '')
call s:set('g:gitgutter_map_keys', 1)
call s:set('g:gitgutter_terminal_reports_focus', 1)
call s:set('g:gitgutter_async', 1)
call s:set('g:gitgutter_log', 0)
call s:set('g:gitgutter_git_executable', 'git')
if !executable(g:gitgutter_git_executable)
call gitgutter#utility#warn('cannot find git. Please set g:gitgutter_git_executable.')
endif
let default_grep = 'grep'
call s:set('g:gitgutter_grep', default_grep)
if !empty(g:gitgutter_grep)
if executable(split(g:gitgutter_grep)[0])
if $GREP_OPTIONS =~# '--color=always'
let g:gitgutter_grep .= ' --color=never'
endif
else
if g:gitgutter_grep !=# default_grep
call gitgutter#utility#warn('cannot find '.g:gitgutter_grep.'. Please check g:gitgutter_grep.')
endif
let g:gitgutter_grep = ''
endif
endif
call gitgutter#highlight#define_sign_column_highlight()
call gitgutter#highlight#define_highlights()
call gitgutter#highlight#define_signs()
" Prevent infinite loop where:
" - executing a job in the foreground launches a new window which takes the focus;
" - when the job finishes, focus returns to gvim;
" - the FocusGained event triggers a new job (see below).
if gitgutter#utility#windows() && !(g:gitgutter_async && gitgutter#async#available())
set noshelltemp
endif
" }}}
" Primary functions {{{
command! -bar GitGutterAll call gitgutter#all(1)
command! -bar GitGutter call gitgutter#process_buffer(bufnr(''), 1)
command! -bar GitGutterDisable call gitgutter#disable()
command! -bar GitGutterEnable call gitgutter#enable()
command! -bar GitGutterToggle call gitgutter#toggle()
" }}}
" Line highlights {{{
command! -bar GitGutterLineHighlightsDisable call gitgutter#highlight#line_disable()
command! -bar GitGutterLineHighlightsEnable call gitgutter#highlight#line_enable()
command! -bar GitGutterLineHighlightsToggle call gitgutter#highlight#line_toggle()
" }}}
" Signs {{{
command! -bar GitGutterSignsEnable call gitgutter#sign#enable()
command! -bar GitGutterSignsDisable call gitgutter#sign#disable()
command! -bar GitGutterSignsToggle call gitgutter#sign#toggle()
" }}}
" Hunks {{{
command! -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk(<count>)
command! -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk(<count>)
command! -bar GitGutterStageHunk call gitgutter#hunk#stage()
command! -bar GitGutterUndoHunk call gitgutter#hunk#undo()
command! -bar GitGutterPreviewHunk call gitgutter#hunk#preview()
" Hunk text object
onoremap <silent> <Plug>GitGutterTextObjectInnerPending :<C-U>call gitgutter#hunk#text_object(1)<CR>
onoremap <silent> <Plug>GitGutterTextObjectOuterPending :<C-U>call gitgutter#hunk#text_object(0)<CR>
xnoremap <silent> <Plug>GitGutterTextObjectInnerVisual :<C-U>call gitgutter#hunk#text_object(1)<CR>
xnoremap <silent> <Plug>GitGutterTextObjectOuterVisual :<C-U>call gitgutter#hunk#text_object(0)<CR>
" Returns the git-diff hunks for the file or an empty list if there
" aren't any hunks.
"
" The return value is a list of lists. There is one inner list per hunk.
"
" [
" [from_line, from_count, to_line, to_count],
" [from_line, from_count, to_line, to_count],
" ...
" ]
"
" where:
"
" `from` - refers to the staged file
" `to` - refers to the working tree's file
" `line` - refers to the line number where the change starts
" `count` - refers to the number of lines the change covers
function! GitGutterGetHunks()
let bufnr = bufnr('')
return gitgutter#utility#is_active(bufnr) ? gitgutter#hunk#hunks(bufnr) : []
endfunction
" Returns an array that contains a summary of the hunk status for the current
" window. The format is [ added, modified, removed ], where each value
" represents the number of lines added/modified/removed respectively.
function! GitGutterGetHunkSummary()
return gitgutter#hunk#summary(winbufnr(0))
endfunction
" }}}
command! -bar GitGutterDebug call gitgutter#debug#debug()
" Maps {{{
nnoremap <silent> <expr> <Plug>GitGutterNextHunk &diff ? ']c' : ":\<C-U>execute v:count1 . 'GitGutterNextHunk'\<CR>"
nnoremap <silent> <expr> <Plug>GitGutterPrevHunk &diff ? '[c' : ":\<C-U>execute v:count1 . 'GitGutterPrevHunk'\<CR>"
nnoremap <silent> <Plug>GitGutterStageHunk :GitGutterStageHunk<CR>
nnoremap <silent> <Plug>GitGutterUndoHunk :GitGutterUndoHunk<CR>
nnoremap <silent> <Plug>GitGutterPreviewHunk :GitGutterPreviewHunk<CR>
" }}}
function! s:on_bufenter()
if exists('t:gitgutter_didtabenter') && t:gitgutter_didtabenter
let t:gitgutter_didtabenter = 0
call gitgutter#all(!g:gitgutter_terminal_reports_focus)
else
call gitgutter#init_buffer(bufnr(''))
call gitgutter#process_buffer(bufnr(''), !g:gitgutter_terminal_reports_focus)
endif
endfunction
" Autocommands {{{
augroup gitgutter
autocmd!
autocmd TabEnter * let t:gitgutter_didtabenter = 1
autocmd BufEnter * call s:on_bufenter()
autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 0)
autocmd FileChangedShellPost * call gitgutter#process_buffer(bufnr(''), 1)
" Ensure that all buffers are processed when opening vim with multiple files, e.g.:
"
" vim -o file1 file2
autocmd VimEnter * if winnr() != winnr('$') | call gitgutter#all(0) | endif
autocmd FocusGained,ShellCmdPost * call gitgutter#all(1)
autocmd ColorScheme * call gitgutter#highlight#define_sign_column_highlight() | call gitgutter#highlight#define_highlights()
" Disable during :vimgrep
autocmd QuickFixCmdPre *vimgrep* let g:gitgutter_enabled = 0
autocmd QuickFixCmdPost *vimgrep* let g:gitgutter_enabled = 1
augroup END
" }}}
" vim:set et sw=2 fdm=marker:

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,8 @@
The quick brown fox jumps
over the lazy dog
いろはにほへとちりぬるを
わかよたれそつねならむ
うゐのおくやまけふこえて
あさきゆめみしゑひもせす

View File

@ -0,0 +1,11 @@
a
b
c
d
e
f
g
h
i
j

View File

@ -0,0 +1,162 @@
"
" Adapted from https://github.com/vim/vim/blob/master/src/testdir/runtest.vim
"
" When debugging tests it can help to write debug output:
" call Log('oh noes')
"
function RunTest(test)
if exists("*SetUp")
call SetUp()
endif
try
execute 'call '.a:test
catch
call Exception()
let s:errored = 1
endtry
if exists("*TearDown")
call TearDown()
endif
endfunction
function Log(msg)
if type(a:msg) == type('')
call add(s:messages, a:msg)
elseif type(a:msg) == type([])
call extend(s:messages, a:msg)
else
call add(v:errors, 'Exception: unsupported type: '.type(a:msg))
endif
endfunction
function Exception()
call add(v:errors, v:throwpoint.'..'.'Exception: '.v:exception)
endfunction
" Shuffles list in place.
function Shuffle(list)
" Fisher-Yates-Durstenfeld-Knuth
let n = len(a:list)
if n < 2
return a:list
endif
for i in range(0, n-2)
let j = Random(0, n-i-1)
let e = a:list[i]
let a:list[i] = a:list[i+j]
let a:list[i+j] = e
endfor
return a:list
endfunction
" Returns a pseudorandom integer i such that 0 <= i <= max
function Random(min, max)
if has('unix')
let i = system('echo $RANDOM') " 0 <= i <= 32767
else
let i = system('echo %RANDOM%') " 0 <= i <= 32767
endif
return i * (a:max - a:min + 1) / 32768 + a:min
endfunction
function FriendlyName(test_name)
return substitute(a:test_name[5:-3], '_', ' ', 'g')
endfunction
function Align(left, right)
if type(a:right) == type([])
let result = []
for s in a:right
if empty(result)
call add(result, printf('%-'.s:indent.'S', a:left).s)
else
call add(result, printf('%-'.s:indent.'S', '').s)
endif
endfor
return result
endif
return printf('%-'.s:indent.'S', a:left).a:right
endfunction
let g:testname = expand('%')
let s:errored = 0
let s:done = 0
let s:fail = 0
let s:errors = 0
let s:messages = []
let s:indent = ''
call Log(g:testname.':')
" Source the test script.
try
source %
catch
let s:errors += 1
call Exception()
endtry
" Locate the test functions.
set nomore
redir @q
silent function /^Test_
redir END
let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
" If there is another argument, filter test-functions' names against it.
if argc() > 1
let s:tests = filter(s:tests, 'v:val =~ argv(1)')
endif
let s:indent = max(map(copy(s:tests), {_, val -> len(FriendlyName(val))}))
" Run the tests in random order.
for test in Shuffle(s:tests)
call RunTest(test)
let s:done += 1
let friendly_name = FriendlyName(test)
if len(v:errors) == 0
call Log(Align(friendly_name, ' - ok'))
else
if s:errored
let s:errors += 1
let s:errored = 0
else
let s:fail += 1
endif
call Log(Align(friendly_name, ' - not ok'))
let i = 0
for error in v:errors
if i != 0
call Log(Align('',' ! ----'))
endif
for trace in reverse(split(error, '\.\.'))
call Log(Align('', ' ! '.trace))
endfor
let i += 1
endfor
let v:errors = []
endif
endfor
let summary = [
\ s:done.( s:done == 1 ? ' test' : ' tests'),
\ s:errors.(s:errors == 1 ? ' error' : ' errors'),
\ s:fail.( s:fail == 1 ? ' failure' : ' failures'),
\ ]
call Log('')
call Log(join(summary, ', '))
split messages.log
call append(line('$'), s:messages)
write
qall!

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
VIM="/Applications/MacVim.app/Contents/MacOS/Vim -v"
export VIM_GITGUTTER_TEST=1
$VIM -u NONE -U NONE -N \
--cmd 'set rtp+=../' \
--cmd 'let g:gitgutter_async=0' \
--cmd 'source ../plugin/gitgutter.vim' \
-S runner.vim \
test_*.vim \
$*
cat messages.log
grep -q "0 errors, 0 failures" messages.log
status=$?
rm messages.log
exit $status

View File

@ -0,0 +1,589 @@
let s:current_dir = expand('%:p:h')
let s:test_repo = s:current_dir.'/test-repo'
let s:bufnr = bufnr('')
"
" Helpers
"
function s:signs(filename)
redir => signs
silent execute 'sign place'
redir END
let signs = split(signs, '\n')
" filter out signs for this test file
" assumes a:filename's signs are last set listed
let i = index(signs, 'Signs for '.a:filename.':')
let signs = (i > -1 ? signs[i+1:] : [])
call map(signs, {_, v -> substitute(v, ' ', '', '')})
return signs
endfunction
function s:git_diff()
return split(system('git diff -U0 fixture.txt'), '\n')
endfunction
function s:git_diff_staged()
return split(system('git diff -U0 --staged fixture.txt'), '\n')
endfunction
function s:trigger_gitgutter()
doautocmd CursorHold
endfunction
"
" SetUp / TearDown
"
function SetUp()
call system("git init ".s:test_repo.
\ " && cd ".s:test_repo.
\ " && cp ../fixture.txt .".
\ " && git add . && git commit -m 'initial'".
\ " && git config diff.mnemonicPrefix false")
execute ':cd' s:test_repo
edit! fixture.txt
call gitgutter#sign#reset()
endfunction
function TearDown()
" delete all buffers except this one
" TODO: move to runner.vim, accounting for multiple test files
if s:bufnr > 1
silent! execute '1,'.s:bufnr-1.'bdelete!'
endif
silent! execute s:bufnr+1.',$bdelete!'
execute ':cd' s:current_dir
call system("rm -rf ".s:test_repo)
endfunction
"
" The tests
"
function Test_add_lines()
normal ggo*
call s:trigger_gitgutter()
let expected = ["line=2 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_add_lines_fish()
let _shell = &shell
set shell=/usr/local/bin/fish
normal ggo*
call s:trigger_gitgutter()
let expected = ["line=2 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fixture.txt'))
let &shell = _shell
endfunction
function Test_modify_lines()
normal ggi*
call s:trigger_gitgutter()
let expected = ["line=1 id=3000 name=GitGutterLineModified"]
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_remove_lines()
execute '5d'
call s:trigger_gitgutter()
let expected = ["line=4 id=3000 name=GitGutterLineRemoved"]
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_remove_first_lines()
execute '1d'
call s:trigger_gitgutter()
let expected = ["line=1 id=3000 name=GitGutterLineRemovedFirstLine"]
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_edit_file_with_same_name_as_a_branch()
normal 5Gi*
call system('git checkout -b fixture.txt')
call s:trigger_gitgutter()
let expected = ["line=5 id=3000 name=GitGutterLineModified"]
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_file_added_to_git()
let tmpfile = 'fileAddedToGit.tmp'
call system('touch '.tmpfile.' && git add '.tmpfile)
execute 'edit '.tmpfile
normal ihello
call s:trigger_gitgutter()
let expected = ["line=1 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fileAddedToGit.tmp'))
endfunction
function Test_filename_with_equals()
call system('touch =fixture=.txt && git add =fixture=.txt')
edit =fixture=.txt
normal ggo*
call s:trigger_gitgutter()
let expected = [
\ 'line=1 id=3000 name=GitGutterLineAdded',
\ 'line=2 id=3001 name=GitGutterLineAdded'
\ ]
call assert_equal(expected, s:signs('=fixture=.txt'))
endfunction
function Test_filename_with_square_brackets()
call system('touch fix[tu]re.txt && git add fix[tu]re.txt')
edit fix[tu]re.txt
normal ggo*
call s:trigger_gitgutter()
let expected = [
\ 'line=1 id=3000 name=GitGutterLineAdded',
\ 'line=2 id=3001 name=GitGutterLineAdded'
\ ]
call assert_equal(expected, s:signs('fix[tu]re.txt'))
endfunction
function Test_filename_leading_dash()
call system('touch -- -fixture.txt && git add -- -fixture.txt')
edit -fixture.txt
normal ggo*
call s:trigger_gitgutter()
let expected = [
\ 'line=1 id=3000 name=GitGutterLineAdded',
\ 'line=2 id=3001 name=GitGutterLineAdded'
\ ]
call assert_equal(expected, s:signs('-fixture.txt'))
endfunction
" FIXME: this test fails when it is the first (or only) test to be run
function Test_follow_symlink()
let tmp = 'symlink'
call system('ln -nfs fixture.txt '.tmp)
execute 'edit '.tmp
6d
call s:trigger_gitgutter()
let expected = ['line=5 id=3000 name=GitGutterLineRemoved']
call assert_equal(expected, s:signs('symlink'))
endfunction
function Test_keep_alt()
enew
execute "normal! \<C-^>"
call assert_equal('fixture.txt', bufname(''))
call assert_equal('', bufname('#'))
normal ggx
call s:trigger_gitgutter()
call assert_equal('', bufname('#'))
endfunction
function Test_keep_modified()
normal 5Go*
call assert_equal(1, getbufvar('', '&modified'))
call s:trigger_gitgutter()
call assert_equal(1, getbufvar('', '&modified'))
endfunction
function Test_keep_op_marks()
normal 5Go*
call assert_equal([0,6,1,0], getpos("'["))
call assert_equal([0,6,2,0], getpos("']"))
call s:trigger_gitgutter()
call assert_equal([0,6,1,0], getpos("'["))
call assert_equal([0,6,2,0], getpos("']"))
endfunction
function Test_no_modifications()
call assert_equal([], s:signs('fixture.txt'))
endfunction
function Test_orphaned_signs()
execute "normal 5GoX\<CR>Y"
call s:trigger_gitgutter()
6d
call s:trigger_gitgutter()
let expected = ['line=6 id=3001 name=GitGutterLineAdded']
call assert_equal(expected, s:signs('fixture.txt'))
endfunction
function Test_untracked_file_outside_repo()
let tmp = tempname()
call system('touch '.tmp)
execute 'edit '.tmp
call assert_equal([], s:signs(tmp))
endfunction
function Test_untracked_file_within_repo()
let tmp = 'untrackedFileWithinRepo.tmp'
call system('touch '.tmp)
execute 'edit '.tmp
normal ggo*
call s:trigger_gitgutter()
call assert_equal([], s:signs(tmp))
call system('rm '.tmp)
endfunction
function Test_untracked_file_square_brackets_within_repo()
let tmp = '[un]trackedFileWithinRepo.tmp'
call system('touch '.tmp)
execute 'edit '.tmp
normal ggo*
call s:trigger_gitgutter()
call assert_equal([], s:signs(tmp))
call system('rm '.tmp)
endfunction
function Test_hunk_outside_noop()
normal 5G
GitGutterStageHunk
call assert_equal([], s:signs('fixture.txt'))
call assert_equal([], s:git_diff())
call assert_equal([], s:git_diff_staged())
GitGutterUndoHunk
call assert_equal([], s:signs('fixture.txt'))
call assert_equal([], s:git_diff())
call assert_equal([], s:git_diff_staged())
endfunction
function Test_hunk_stage()
let _shell = &shell
set shell=foo
normal 5Gi*
GitGutterStageHunk
call assert_equal('foo', &shell)
let &shell = _shell
call assert_equal([], s:signs('fixture.txt'))
" Buffer is unsaved
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index ae8e546..f5c6aff 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -5 +5 @@ d',
\ '-*e',
\ '+e'
\ ]
call assert_equal(expected, s:git_diff())
" Index has been updated
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..ae8e546 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -5 +5 @@ d',
\ '-e',
\ '+*e'
\ ]
call assert_equal(expected, s:git_diff_staged())
" Save the buffer
write
call assert_equal([], s:git_diff())
endfunction
function Test_hunk_stage_nearby_hunk()
execute "normal! 2Gox\<CR>y\<CR>z"
normal 2jdd
normal k
GitGutterStageHunk
let expected = [
\ 'line=3 id=3000 name=GitGutterLineAdded',
\ 'line=4 id=3001 name=GitGutterLineAdded',
\ 'line=5 id=3002 name=GitGutterLineAdded'
\ ]
call assert_equal(expected, s:signs('fixture.txt'))
" Buffer is unsaved
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index 53b13df..f5c6aff 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -3,0 +4 @@ c',
\ '+d',
\ ]
call assert_equal(expected, s:git_diff())
" Index has been updated
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..53b13df 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -4 +3,0 @@ c',
\ '-d',
\ ]
call assert_equal(expected, s:git_diff_staged())
" Save the buffer
write
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index 53b13df..8fdfda7 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -2,0 +3,3 @@ b',
\ '+x',
\ '+y',
\ '+z',
\ ]
call assert_equal(expected, s:git_diff())
endfunction
function Test_hunk_undo()
let _shell = &shell
set shell=foo
normal 5Gi*
GitGutterUndoHunk
call assert_equal('foo', &shell)
let &shell = _shell
call assert_equal([], s:signs('fixture.txt'))
call assert_equal([], s:git_diff())
call assert_equal([], s:git_diff_staged())
endfunction
function Test_undo_nearby_hunk()
execute "normal! 2Gox\<CR>y\<CR>z"
normal 2jdd
normal k
call s:trigger_gitgutter()
GitGutterUndoHunk
call s:trigger_gitgutter()
let expected = [
\ 'line=3 id=3000 name=GitGutterLineAdded',
\ 'line=4 id=3001 name=GitGutterLineAdded',
\ 'line=5 id=3002 name=GitGutterLineAdded'
\ ]
call assert_equal(expected, s:signs('fixture.txt'))
call assert_equal([], s:git_diff())
call assert_equal([], s:git_diff_staged())
" Save the buffer
write
let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..3fbde56 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -2,0 +3,3 @@ b',
\ '+x',
\ '+y',
\ '+z',
\ ]
call assert_equal(expected, s:git_diff())
endfunction
function Test_write_option()
set nowrite
normal ggo*
call s:trigger_gitgutter()
let expected = ["line=2 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fixture.txt'))
set write
endfunction
function Test_inner_text_object()
execute "normal! 2Gox\<CR>y\<CR>z\<CR>\<CR>"
call s:trigger_gitgutter()
normal dic
call s:trigger_gitgutter()
call assert_equal([], s:signs('fixture.txt'))
call assert_equal(readfile('fixture.txt'), getline(1,'$'))
" Excludes trailing lines
normal 9Gi*
normal 10Gi*
call s:trigger_gitgutter()
execute "normal vic\<Esc>"
call assert_equal([9, 10], [line("'<"), line("'>")])
endfunction
function Test_around_text_object()
execute "normal! 2Gox\<CR>y\<CR>z\<CR>\<CR>"
call s:trigger_gitgutter()
normal dac
call s:trigger_gitgutter()
call assert_equal([], s:signs('fixture.txt'))
call assert_equal(readfile('fixture.txt'), getline(1,'$'))
" Includes trailing lines
normal 9Gi*
normal 10Gi*
call s:trigger_gitgutter()
execute "normal vac\<Esc>"
call assert_equal([9, 11], [line("'<"), line("'>")])
endfunction
function Test_user_autocmd()
autocmd User GitGutter let s:autocmd_user = g:gitgutter_hook_context.bufnr
" Verify not fired when nothing changed.
let s:autocmd_user = 0
call s:trigger_gitgutter()
call assert_equal(0, s:autocmd_user)
" Verify fired when there was a change.
normal ggo*
let bufnr = bufnr('')
call s:trigger_gitgutter()
call assert_equal(bufnr, s:autocmd_user)
endfunction
function Test_fix_file_references()
" No special characters
let hunk_diff = join([
\ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..3fbde56 100644',
\ '--- a/fixture.txt',
\ '+++ b/fixture.txt',
\ '@@ -2,0 +3,1 @@ b',
\ '+x'
\ ], "\n")."\n"
let filepath = 'blah.txt'
let expected = join([
\ 'diff --git a/blah.txt b/blah.txt',
\ 'index f5c6aff..3fbde56 100644',
\ '--- a/blah.txt',
\ '+++ b/blah.txt',
\ '@@ -2,0 +3,1 @@ b',
\ '+x'
\ ], "\n")."\n"
call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff))
" diff.mnemonicPrefix; spaces in filename
let hunk_diff = join([
\ 'diff --git i/x/cat dog w/x/cat dog',
\ 'index f5c6aff..3fbde56 100644',
\ '--- i/x/cat dog',
\ '+++ w/x/cat dog',
\ '@@ -2,0 +3,1 @@ b',
\ '+x'
\ ], "\n")."\n"
let filepath = 'blah.txt'
let expected = join([
\ 'diff --git i/blah.txt w/blah.txt',
\ 'index f5c6aff..3fbde56 100644',
\ '--- i/blah.txt',
\ '+++ w/blah.txt',
\ '@@ -2,0 +3,1 @@ b',
\ '+x'
\ ], "\n")."\n"
call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff))
" Backslashes in filename; quotation marks
let hunk_diff = join([
\ 'diff --git "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim" "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"',
\ 'index f42aeb0..4930403 100644',
\ '--- "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim"',
\ '+++ "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"',
\ '@@ -172,0 +173 @@ stuff',
\ '+x'
\ ], "\n")."\n"
let filepath = 'init.vim'
let expected = join([
\ 'diff --git "a/init.vim" "b/init.vim"',
\ 'index f42aeb0..4930403 100644',
\ '--- "a/init.vim"',
\ '+++ "b/init.vim"',
\ '@@ -172,0 +173 @@ stuff',
\ '+x'
\ ], "\n")."\n"
call assert_equal(expected, gitgutter#hunk#fix_file_references(filepath, hunk_diff))
endfunction
function Test_encoding()
call system('cp ../cp932.txt . && git add cp932.txt')
edit ++enc=cp932 cp932.txt
call s:trigger_gitgutter()
call assert_equal([], s:signs('cp932.txt'))
endfunction

View File

@ -0,0 +1,27 @@
" Measure how long it takes to unplace signs.
"
" Source this file with `:source %` or `vim -S unplace.vim`
let num = 500
sign define Foo text=*
new
call append(0, range(1, num))
for i in range(1, num)
execute "sign place ".i." line=".i." name=Foo buffer=".bufnr('')
endfor
let start = reltime()
for i in range(1, num)
execute "sign unplace ".i
endfor
let elapsed = reltime(start)
bdelete!
echom split(reltimestr(elapsed))[0]."s to remove ".num." signs"
echom string(reltimefloat(elapsed) * 1000 / num).' ms/sign'
echom string(float2nr(num / reltimefloat(elapsed))).' sign/s'

@ -0,0 +1 @@
Subproject commit 6d2cb3c06cd546fd4bee4136679db3a3d5de97fa