Big refactor.

- Hunk stage/undo/preview no longer saves the buffer.
- Hunk undo no longer makes locations go out of sync.
- Grep can be opted out of (grep output with ansi escapes is number one cause
  of issues).
- Replaced g:gitgutter_grep_command with g:gitgutter_grep.
- Always runs git-diff the same way instead of in two possible ways.
- Separated detection of git tracking from diffing.
- Simplified path handling.
- Removed support for xolox shell: Windows taskbar does not flash with async
  jobs.
- Removed g:gitgutter_{eager,realtime}.
- Simplified implementation generally.
This commit is contained in:
Andy Stewart
2018-02-08 10:12:38 +00:00
parent 932ffaca09
commit 5bfe5b9209
12 changed files with 750 additions and 875 deletions

View File

@@ -1,12 +1,14 @@
## vim-gitgutter ## vim-gitgutter
A Vim plugin which shows a git diff in the 'gutter' (sign column). It shows whether each line has been added, modified, and where lines have been removed. You can also stage and undo individual hunks. 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 signs are always up to date and the plugin never saves your buffer.
Features: Features:
* Shows signs for added, modified, and removed lines. * Shows signs for added, modified, and removed lines.
* Runs the diffs asynchronously in terminal Vim/MacVim (7.4.1826+), gVim (7.4.1850+), MacVim GUI (7.4.1832+), and NeoVim. * Runs the diffs asynchronously where possible.
* Ensures signs are always as up to date as possible (but without running more than necessary). * Ensures signs are always up to date.
* Quick jumping between blocks of changed lines ("hunks"). * Quick jumping between blocks of changed lines ("hunks").
* Stage/undo/preview individual hunks. * Stage/undo/preview individual hunks.
* Provides a hunk text object. * Provides a hunk text object.
@@ -39,7 +41,7 @@ In the screenshot above you can see:
### Installation ### 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. 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. You install vim-gitgutter like any other vim plugin.
@@ -100,12 +102,9 @@ cp -r vim-gitgutter/* ~/.vim/
See `:help add-global-plugin`. See `:help add-global-plugin`.
If you are on Windows you may find the command prompt pops up briefly every time vim-gitgutter runs. You can avoid this by installing both [vim-misc](https://github.com/xolox/vim-misc) and [vim-shell](https://github.com/xolox/vim-shell). If you have those two plugins but don't want vim-gitgutter to use them, you can opt out with `let g:gitgutter_avoid_cmd_prompt_on_windows = 0` in your `~/.vimrc`.
### Getting started ### 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 4 seconds but I suggest reducing it to around 250ms (add `set updatetime=250` to your vimrc). 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 can jump between hunks with `[c` and `]c`. You can preview, stage, and undo hunks with `<leader>hp`, `<leader>hs`, and `<leader>hu` respectively.
@@ -136,7 +135,7 @@ Note that if you have line highlighting on and signs off, you will have an empty
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). 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 itself 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: 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 ```viml
let g:gitgutter_max_signs = 500 " default value let g:gitgutter_max_signs = 500 " default value
@@ -205,33 +204,6 @@ Finally, you can force vim-gitgutter to update its signs across all visible buff
See the customisation section below for how to change the defaults. See the customisation section below for how to change the defaults.
### When are the signs updated?
By default the signs are updated as follows:
| Event | Reason for update | Configuration |
|---------------------------|--------------------------------------|------------------------|
| Stop typing | So the signs are real time | `g:gitgutter_realtime` |
| Switch buffer | To notice change to git index | `g:gitgutter_eager` |
| Switch tab | To notice change to git index | `g:gitgutter_eager` |
| Focus the GUI | To notice change to git index | `g:gitgutter_eager` (not gVim on Windows) |
| After shell command | To notice change to git index | `g:gitgutter_eager` |
| Read a file into a buffer | To display initial signs | [always] |
| Save a buffer | So non-realtime signs are up to date | [always] |
| Change a file outside Vim | To notice `git stash` | [always] |
The length of time Vim waits after you stop typing before it triggers the plugin is governed by the setting `updatetime`. This defaults to `4000` milliseconds which is rather too long. I recommend around `250` milliseconds but it depends on your system and your preferences. Note that in terminal Vim pre-7.4.427 an `updatetime` of less than approximately `1000` milliseconds can lead to random highlighting glitches; the lower the `updatetime`, the more glitches.
If you experience a lag, you can trade speed for accuracy:
```viml
let g:gitgutter_realtime = 0
let g:gitgutter_eager = 0
```
Note the realtime updating requires Vim 7.3.105 or higher.
### Customisation ### Customisation
You can customise: You can customise:
@@ -351,7 +323,7 @@ If you use an alternative to grep, you can tell vim-gitgutter to use it here.
```viml ```viml
" Default: " Default:
let g:gitgutter_grep_command = 'grep' let g:gitgutter_grep = 'grep'
``` ```
#### To turn off vim-gitgutter by default #### To turn off vim-gitgutter by default
@@ -498,13 +470,13 @@ Unstaging staged hunks is feasible but not quite as easy as it sounds. There ar
2. The version staged in the index. 2. The version staged in the index.
3. The version in the working tree, in your vim buffer. 3. The version in the working tree, in your vim buffer.
`git-diff` without arguments shows you how 3 and 2 differ; this is what vim-gitgutter shows too. `git-diff` without arguments shows you how 2 and 3 differ; this is what vim-gitgutter shows too.
`git-diff --staged` shows you how 2 and 1 differ. `git-diff --staged` shows you how 1 and 2 differ.
Let's say you are looking at a file in vim which has some unstaged changes. Now you stage a hunk, either via vim-gitgutter or another means. The hunk is no longer marked in vim-gitgutter because it is the same in 3 and 2. Let's say you are looking at a file in vim which has some unstaged changes. Now you stage a hunk, either via vim-gitgutter or another means. The hunk is no longer marked in vim-gitgutter because it is the same in 2 and 3.
Now you want to unstage that hunk. To see it, you need the difference between 2 and 1. For vim-gitgutter to show those differences, it would need to show you 2 instead of 3 in your vim buffer. But 2 is virtual so vim-gitgutter would need to handle it without touching 3. Now you want to unstage that hunk. To see it, you need the difference between 1 and 2. For vim-gitgutter to show those differences, it would need to show you 2 instead of 3 in your vim buffer. But 2 is virtual so vim-gitgutter would need to handle it without touching 3.
I intend to implement this but I can't commit to any deadline. I intend to implement this but I can't commit to any deadline.
@@ -512,32 +484,24 @@ I intend to implement this but I can't commit to any deadline.
Your colorscheme is configuring the `SignColumn` highlight group weirdly. Please see the section above on customising the sign column. Your colorscheme is configuring the `SignColumn` highlight group weirdly. Please see the section above on customising the sign column.
> There's a noticeable lag when vim-gitter runs; how can I avoid it?
By default vim-gitgutter runs often so the signs are as accurate as possible. The delay is governed by `updatetime`; see [above](#when-are-the-signs-updated) for more information.
If you don't want realtime updates and would like to trade a little accuracy for speed, add this to your `~/.vimrc`:
```viml
let g:gitgutter_realtime = 0
let g:gitgutter_eager = 0
```
> What happens if I also use another plugin which uses signs (e.g. Syntastic)? > 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. 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.
> Why aren't any signs showing at all?
### Troubleshooting
#### When no signs are showing at all
Here are some things you can check: Here are some things you can check:
* `:echo system("git --version")` succeeds. * 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.
* Your git config is compatible with the version of git returned by the command above. * Verify `:echo system("git --version")` succeeds.
* Your Vim supports signs (`:echo has('signs')` should give `1`). * Verify your git config is compatible with the version of git returned by the command above.
* Your file is being tracked by git and has unstaged changes. * Verify your Vim supports signs (`:echo has('signs')` should give `1`).
* If you have aliased or configured `grep` to use any flags, add `let g:gitgutter_grep_command = 'grep'` to your `~/.vimrc`. * Verify your file is being tracked by git and has unstaged changes.
> Why is the whole file marked as added when I edit it? #### 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. * If you use zsh, and you set `CDPATH`, make sure `CDPATH` doesn't include the current directory.
@@ -561,4 +525,3 @@ Copyright Andrew Stewart, AirBlade Software Ltd. Released under the MIT licence
[pathogen]: https://github.com/tpope/vim-pathogen [pathogen]: https://github.com/tpope/vim-pathogen
[siv]: http://pluralsight.com/training/Courses/TableOfContents/smash-into-vim [siv]: http://pluralsight.com/training/Courses/TableOfContents/smash-into-vim
[airblade]: http://airbladesoftware.com/peepcode-vim [airblade]: http://airbladesoftware.com/peepcode-vim
[macvim]: http://code.google.com/p/macvim/

View File

@@ -1,67 +1,50 @@
let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
" Primary functions {{{ " Primary functions {{{
function! gitgutter#all() abort function! gitgutter#all(force) abort
for buffer_id in gitgutter#utility#dedup(tabpagebuflist()) for bufnr in tabpagebuflist()
let file = expand('#' . buffer_id . ':p') let file = expand('#'.bufnr.':p')
if !empty(file) if !empty(file)
call gitgutter#process_buffer(buffer_id, 0) call gitgutter#init_buffer(bufnr)
call gitgutter#process_buffer(bufnr, a:force)
endif endif
endfor endfor
endfunction endfunction
" bufnr: (integer) the buffer to process.
" realtime: (boolean) when truthy, do a realtime diff; otherwise do a disk-based diff.
function! gitgutter#process_buffer(bufnr, realtime) abort
call gitgutter#utility#use_known_shell()
call gitgutter#utility#set_buffer(a:bufnr) " Finds the file's path relative to the repo root.
if gitgutter#utility#is_active() function! gitgutter#init_buffer(bufnr)
if g:gitgutter_sign_column_always let p = gitgutter#utility#repo_path(a:bufnr, 0)
call gitgutter#sign#add_dummy_sign() if type(p) != v:t_string || empty(p)
endif call gitgutter#utility#set_repo_path(a:bufnr)
try endif
if !a:realtime || gitgutter#utility#has_fresh_changes() endfunction
let diff = gitgutter#diff#run_diff(a:realtime || gitgutter#utility#has_unsaved_changes(), 0)
if diff != 'async'
call gitgutter#handle_diff(diff) function! gitgutter#process_buffer(bufnr, force) abort
endif " 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
catch /diff failed/
call gitgutter#debug#log('diff failed')
call gitgutter#hunk#reset()
endtry
execute "silent doautocmd" s:nomodeline "User GitGutter"
else
call gitgutter#hunk#reset()
endif
call gitgutter#utility#restore_shell() endif
endif
endfunction endfunction
function! gitgutter#handle_diff(diff) abort
call gitgutter#debug#log(a:diff)
call gitgutter#utility#setbufvar(gitgutter#utility#bufnr(), 'tracked', 1)
call gitgutter#hunk#set_hunks(gitgutter#diff#parse_diff(a:diff))
let modified_lines = gitgutter#diff#process_hunks(gitgutter#hunk#hunks())
if len(modified_lines) > g:gitgutter_max_signs
call gitgutter#utility#warn_once('exceeded maximum number of signs (configured by g:gitgutter_max_signs).', 'max_signs')
call gitgutter#sign#clear_signs()
return
endif
if g:gitgutter_signs || g:gitgutter_highlight_lines
call gitgutter#sign#update_signs(modified_lines)
endif
call gitgutter#utility#save_last_seen_change()
endfunction
function! gitgutter#disable() abort function! gitgutter#disable() abort
" get list of all buffers (across all tabs) " get list of all buffers (across all tabs)
let buflist = [] let buflist = []
@@ -69,13 +52,12 @@ function! gitgutter#disable() abort
call extend(buflist, tabpagebuflist(i + 1)) call extend(buflist, tabpagebuflist(i + 1))
endfor endfor
for buffer_id in gitgutter#utility#dedup(buflist) for bufnr in buflist
let file = expand('#' . buffer_id . ':p') let file = expand('#'.bufnr.':p')
if !empty(file) if !empty(file)
call gitgutter#utility#set_buffer(buffer_id) call gitgutter#sign#clear_signs(bufnr)
call gitgutter#sign#clear_signs() call gitgutter#sign#remove_dummy_sign(bufnr, 1)
call gitgutter#sign#remove_dummy_sign(1) call gitgutter#hunk#reset(bufnr)
call gitgutter#hunk#reset()
endif endif
endfor endfor
@@ -84,7 +66,7 @@ endfunction
function! gitgutter#enable() abort function! gitgutter#enable() abort
let g:gitgutter_enabled = 1 let g:gitgutter_enabled = 1
call gitgutter#all() call gitgutter#all(1)
endfunction endfunction
function! gitgutter#toggle() abort function! gitgutter#toggle() abort
@@ -97,163 +79,7 @@ endfunction
" }}} " }}}
" Line highlights {{{ function! s:has_fresh_changes(bufnr) abort
return getbufvar(a:bufnr, 'changedtick') != gitgutter#utility#getbufvar(a:bufnr, 'tick')
function! gitgutter#line_highlights_disable() abort
let g:gitgutter_highlight_lines = 0
call gitgutter#highlight#define_sign_line_highlights()
if !g:gitgutter_signs
call gitgutter#sign#clear_signs()
call gitgutter#sign#remove_dummy_sign(0)
endif
redraw!
endfunction endfunction
function! gitgutter#line_highlights_enable() abort
let old_highlight_lines = g:gitgutter_highlight_lines
let g:gitgutter_highlight_lines = 1
call gitgutter#highlight#define_sign_line_highlights()
if !old_highlight_lines && !g:gitgutter_signs
call gitgutter#all()
endif
redraw!
endfunction
function! gitgutter#line_highlights_toggle() abort
if g:gitgutter_highlight_lines
call gitgutter#line_highlights_disable()
else
call gitgutter#line_highlights_enable()
endif
endfunction
" }}}
" Signs {{{
function! gitgutter#signs_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()
endif
endfunction
function! gitgutter#signs_disable() abort
let g:gitgutter_signs = 0
call gitgutter#highlight#define_sign_text_highlights()
if !g:gitgutter_highlight_lines
call gitgutter#sign#clear_signs()
call gitgutter#sign#remove_dummy_sign(0)
endif
endfunction
function! gitgutter#signs_toggle() abort
if g:gitgutter_signs
call gitgutter#signs_disable()
else
call gitgutter#signs_enable()
endif
endfunction
" }}}
" Hunks {{{
function! gitgutter#stage_hunk() abort
call gitgutter#utility#use_known_shell()
if gitgutter#utility#is_active()
" Ensure the working copy of the file is up to date.
" It doesn't make sense to stage a hunk otherwise.
noautocmd silent write
let diff = gitgutter#diff#run_diff(0, 1)
call gitgutter#handle_diff(diff)
if empty(gitgutter#hunk#current_hunk())
call gitgutter#utility#warn('cursor is not in a hunk')
else
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'stage')
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file(g:gitgutter_git_executable.' apply --cached --unidiff-zero - '), diff_for_hunk)
" refresh gitgutter's view of buffer
silent execute "GitGutter"
endif
silent! call repeat#set("\<Plug>GitGutterStageHunk", -1)<CR>
endif
call gitgutter#utility#restore_shell()
endfunction
function! gitgutter#undo_hunk() abort
call gitgutter#utility#use_known_shell()
if gitgutter#utility#is_active()
" Ensure the working copy of the file is up to date.
" It doesn't make sense to stage a hunk otherwise.
noautocmd silent write
let diff = gitgutter#diff#run_diff(0, 1)
call gitgutter#handle_diff(diff)
if empty(gitgutter#hunk#current_hunk())
call gitgutter#utility#warn('cursor is not in a hunk')
else
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'undo')
call gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file(g:gitgutter_git_executable.' apply --reverse --unidiff-zero - '), diff_for_hunk)
" reload file preserving screen line position
" CTRL-Y and CTRL-E treat negative counts as positive counts.
let x = line('w0')
silent edit
let y = line('w0')
let z = x - y
if z > 0
execute "normal! ".z."\<C-E>"
else
execute "normal! ".z."\<C-Y>"
endif
endif
silent! call repeat#set("\<Plug>GitGutterUndoHunk", -1)<CR>
endif
call gitgutter#utility#restore_shell()
endfunction
function! gitgutter#preview_hunk() abort
call gitgutter#utility#use_known_shell()
if gitgutter#utility#is_active()
" Ensure the working copy of the file is up to date.
" It doesn't make sense to stage a hunk otherwise.
noautocmd silent write
let diff = gitgutter#diff#run_diff(0, 1)
call gitgutter#handle_diff(diff)
if empty(gitgutter#hunk#current_hunk())
call gitgutter#utility#warn('cursor is not in a hunk')
else
let diff_for_hunk = gitgutter#diff#generate_diff_for_hunk(diff, 'preview')
silent! wincmd P
if !&previewwindow
noautocmd execute 'bo' &previewheight 'new'
set previewwindow
endif
setlocal noro modifiable filetype=diff buftype=nofile bufhidden=delete noswapfile
execute "%delete_"
call append(0, split(diff_for_hunk, "\n"))
noautocmd wincmd p
endif
endif
call gitgutter#utility#restore_shell()
endfunction
" }}}

View File

@@ -11,10 +11,13 @@ function! gitgutter#async#available()
endfunction endfunction
function! gitgutter#async#execute(cmd) abort function! gitgutter#async#execute(cmd, bufnr, handler) abort
call gitgutter#debug#log('[async] '.a:cmd)
let options = { let options = {
\ 'stdoutbuffer': [], \ 'stdoutbuffer': [],
\ 'buffer': gitgutter#utility#bufnr() \ 'buffer': a:bufnr,
\ 'handler': a:handler
\ } \ }
let command = s:build_command(a:cmd) let command = s:build_command(a:cmd)
@@ -58,33 +61,13 @@ function! s:on_stdout_nvim(_job_id, data, _event) dict abort
endfunction endfunction
function! s:on_stderr_nvim(_job_id, _data, _event) dict abort function! s:on_stderr_nvim(_job_id, _data, _event) dict abort
" Backward compatibility for nvim < 0.2.0 call self.handler.err(self.buffer)
if !has('nvim-0.2.0')
let current_buffer = gitgutter#utility#bufnr()
call gitgutter#utility#set_buffer(self.buffer)
if gitgutter#utility#is_active()
call gitgutter#hunk#reset()
endif
call gitgutter#utility#set_buffer(current_buffer)
return
endif
call s:buffer_exec(self.buffer, function('gitgutter#hunk#reset'))
endfunction endfunction
function! s:on_exit_nvim(_job_id, _data, _event) dict abort function! s:on_exit_nvim(_job_id, exit_code, _event) dict abort
" Backward compatibility for nvim < 0.2.0 if !a:exit_code
if !has('nvim-0.2.0') call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
let current_buffer = gitgutter#utility#bufnr()
call gitgutter#utility#set_buffer(self.buffer)
if gitgutter#utility#is_active()
call gitgutter#handle_diff(gitgutter#utility#stringify(self.stdoutbuffer))
endif
call gitgutter#utility#set_buffer(current_buffer)
return
endif endif
call s:buffer_exec(self.buffer, function('gitgutter#handle_diff', [gitgutter#utility#stringify(self.stdoutbuffer)]))
endfunction endfunction
@@ -92,22 +75,15 @@ function! s:on_stdout_vim(_channel, data) dict abort
call add(self.stdoutbuffer, a:data) call add(self.stdoutbuffer, a:data)
endfunction endfunction
function! s:on_stderr_vim(_channel, _data) dict abort function! s:on_stderr_vim(channel, _data) dict abort
call s:buffer_exec(self.buffer, function('gitgutter#hunk#reset')) 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 endfunction
function! s:on_exit_vim(_channel) dict abort function! s:on_exit_vim(_channel) dict abort
call s:buffer_exec(self.buffer, function('gitgutter#handle_diff', [gitgutter#utility#stringify(self.stdoutbuffer)])) call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
endfunction
function! s:buffer_exec(buffer, fn)
let current_buffer = gitgutter#utility#bufnr()
call gitgutter#utility#set_buffer(a:buffer)
if gitgutter#utility#is_active()
call a:fn()
endif
call gitgutter#utility#set_buffer(current_buffer)
endfunction endfunction

View File

@@ -12,67 +12,67 @@ function! gitgutter#debug#debug()
setlocal bufhidden=delete setlocal bufhidden=delete
setlocal noswapfile setlocal noswapfile
call gitgutter#debug#vim_version() call s:vim_version()
call gitgutter#debug#separator() call s:separator()
call gitgutter#debug#git_version() call s:git_version()
call gitgutter#debug#separator() call s:separator()
call gitgutter#debug#grep_version() call s:grep_version()
call gitgutter#debug#separator() call s:separator()
call gitgutter#debug#option('updatetime') call s:option('updatetime')
call gitgutter#debug#option('shell') call s:option('shell')
call gitgutter#debug#option('shellcmdflag') call s:option('shellcmdflag')
call gitgutter#debug#option('shellpipe') call s:option('shellpipe')
call gitgutter#debug#option('shellquote') call s:option('shellquote')
call gitgutter#debug#option('shellredir') call s:option('shellredir')
call gitgutter#debug#option('shellslash') call s:option('shellslash')
call gitgutter#debug#option('shelltemp') call s:option('shelltemp')
call gitgutter#debug#option('shelltype') call s:option('shelltype')
call gitgutter#debug#option('shellxescape') call s:option('shellxescape')
call gitgutter#debug#option('shellxquote') call s:option('shellxquote')
endfunction endfunction
function! gitgutter#debug#separator() function! s:separator()
call gitgutter#debug#output('') call s:output('')
endfunction endfunction
function! gitgutter#debug#vim_version() function! s:vim_version()
redir => version_info redir => version_info
silent execute 'version' silent execute 'version'
redir END redir END
call gitgutter#debug#output(split(version_info, '\n')[0:2]) call s:output(split(version_info, '\n')[0:2])
endfunction endfunction
function! gitgutter#debug#git_version() function! s:git_version()
let v = system(g:gitgutter_git_executable.' --version') let v = system(g:gitgutter_git_executable.' --version')
call gitgutter#debug#output( substitute(v, '\n$', '', '') ) call s:output( substitute(v, '\n$', '', '') )
endfunction endfunction
function! gitgutter#debug#grep_version() function! s:grep_version()
let v = system('grep --version') let v = system('grep --version')
call gitgutter#debug#output( substitute(v, '\n$', '', '') ) call s:output( substitute(v, '\n$', '', '') )
let v = system('grep --help') let v = system('grep --help')
call gitgutter#debug#output( substitute(v, '\%x00', '', 'g') ) call s:output( substitute(v, '\%x00', '', 'g') )
endfunction endfunction
function! gitgutter#debug#option(name) function! s:option(name)
if exists('+' . a:name) if exists('+' . a:name)
let v = eval('&' . a:name) let v = eval('&' . a:name)
call gitgutter#debug#output(a:name . '=' . v) call s:output(a:name . '=' . v)
" redir => output " redir => output
" silent execute "verbose set " . a:name . "?" " silent execute "verbose set " . a:name . "?"
" redir END " redir END
" call gitgutter#debug#output(a:name . '=' . output) " call s:output(a:name . '=' . output)
else else
call gitgutter#debug#output(a:name . ' [n/a]') call s:output(a:name . ' [n/a]')
end end
endfunction endfunction
function! gitgutter#debug#output(text) function! s:output(text)
call append(line('$'), a:text) call append(line('$'), a:text)
endfunction endfunction

View File

@@ -1,152 +1,137 @@
if exists('g:gitgutter_grep_command') let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
let s:grep_available = 1
let s:grep_command = g:gitgutter_grep_command
else
let s:grep_available = executable('grep')
if s:grep_available
let s:grep_command = 'grep'
if $GREP_OPTIONS =~# '--color=always'
let s:grep_command .= ' --color=never'
endif
endif
endif
let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@' let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
let s:c_flag = gitgutter#utility#git_supports_command_line_config_override() " 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_index = tempname()
let s:temp_buffer = tempname() let s:temp_buffer = tempname()
" Returns a diff of the buffer. " Returns a diff of the buffer.
" "
" The way to get the diff depends on whether the buffer is saved or unsaved. " 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:
" "
" * Saved: the buffer contents is the same as the file on disk in the working " git diff myfileA myfileB
" tree so we simply do:
" "
" git diff myfile " where myfileA comes from
" "
" * Unsaved: the buffer contents is not the same as the file on disk so we " git show :myfile > myfileA
" need to pass two instances of the file to git-diff:
" "
" git diff myfileA myfileB " and myfileB is the buffer contents. Ideally we would pass this to
" git-diff on stdin via the second argument to vim's system() function.
" Unfortunately git-diff does not do CRLF conversion for input received on
" stdin, and git-show never performs CRLF conversion, so repos with CRLF
" conversion report that every line is modified due to mismatching EOLs.
" "
" The first instance is the file in the index which we obtain with: " Instead, we write the buffer contents to a temporary file - myfileB in this
" " example. Note the file extension must be preserved for the CRLF
" git show :myfile > myfileA " conversion to work.
"
" The second instance is the buffer contents. Ideally we would pass this to
" git-diff on stdin via the second argument to vim's system() function.
" Unfortunately git-diff does not do CRLF conversion for input received on
" stdin, and git-show never performs CRLF conversion, so repos with CRLF
" conversion report that every line is modified due to mismatching EOLs.
"
" Instead, we write the buffer contents to a temporary file - myfileB in this
" example. Note the file extension must be preserved for the CRLF
" conversion to work.
"
" Before diffing a buffer for the first time, we check whether git knows about
" the file:
"
" git ls-files --error-unmatch myfile
" "
" After running the diff we pass it through grep where available to reduce " 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 " subsequent processing by the plugin. If grep is not available the plugin
" does the filtering instead. " does the filtering instead.
function! gitgutter#diff#run_diff(realtime, preserve_full_diff) abort 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. " Wrap compound commands in parentheses to make Windows happy.
" bash doesn't mind the parentheses. " bash doesn't mind the parentheses.
let cmd = '(' let cmd = '('
let bufnr = gitgutter#utility#bufnr() let blob_file = s:temp_index
let tracked = gitgutter#utility#getbufvar(bufnr, 'tracked', 0) " i.e. tracked by git let buff_file = s:temp_buffer
if !tracked
" Don't bother trying to realtime-diff an untracked file. let extension = gitgutter#utility#extension(a:bufnr)
" NOTE: perhaps we should pull this guard up to the caller? if !empty(extension)
if a:realtime let blob_file .= '.'.extension
throw 'diff failed' let buff_file .= '.'.extension
else
let cmd .= g:gitgutter_git_executable.' ls-files --error-unmatch '.gitgutter#utility#shellescape(gitgutter#utility#filename()).' && ('
endif
endif endif
if a:realtime " Write file from index to temporary file.
let blob_name = g:gitgutter_diff_base.':'.gitgutter#utility#shellescape(gitgutter#utility#file_relative_to_repo_root()) let blob_name = g:gitgutter_diff_base.':'.gitgutter#utility#repo_path(a:bufnr, 1)
let blob_file = s:temp_index let cmd .= g:gitgutter_git_executable.' show '.blob_name.' > '.blob_file.' && '
let buff_file = s:temp_buffer
let extension = gitgutter#utility#extension()
if !empty(extension)
let blob_file .= '.'.extension
let buff_file .= '.'.extension
endif
let cmd .= g:gitgutter_git_executable.' show '.blob_name.' > '.blob_file.' && '
" Writing the whole buffer resets the '[ and '] marks and also the " Write buffer to temporary file.
" 'modified' flag (if &cpoptions includes '+'). These are unwanted call s:write_buffer(a:bufnr, buff_file)
" side-effects so we save and restore the values ourselves.
let modified = getbufvar(bufnr, "&mod")
let op_mark_start = getpos("'[")
let op_mark_end = getpos("']")
let current_buffer = bufnr('')
execute 'buffer '.bufnr
execute 'keepalt noautocmd silent write!' buff_file
execute 'buffer '.current_buffer
call setbufvar(bufnr, "&mod", modified)
call setpos("'[", op_mark_start)
call setpos("']", op_mark_end)
endif
" Call git-diff with the temporary files.
let cmd .= g:gitgutter_git_executable let cmd .= g:gitgutter_git_executable
if s:c_flag if s:c_flag
let cmd .= ' -c "diff.autorefreshindex=0"' let cmd .= ' -c "diff.autorefreshindex=0"'
let cmd .= ' -c "diff.noprefix=false"' let cmd .= ' -c "diff.noprefix=false"'
endif endif
let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' ' let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args.' -- '.blob_file.' '.buff_file
if a:realtime " Pipe git-diff output into grep.
let cmd .= ' -- '.blob_file.' '.buff_file if !a:preserve_full_diff && !empty(g:gitgutter_grep)
else let cmd .= ' | '.g:gitgutter_grep.' '.gitgutter#utility#shellescape('^@@ ')
let cmd .= g:gitgutter_diff_base.' -- '.gitgutter#utility#shellescape(gitgutter#utility#filename())
endif endif
if !a:preserve_full_diff && s:grep_available " grep exits with 1 when no matches are found; git-diff exits with 1 when
let cmd .= ' | '.s:grep_command.' '.gitgutter#utility#shellescape('^@@ ') " differences are found. However we want to treat non-matches and
endif " differences as non-erroneous behaviour; so we OR the command with one
" which always exits with success (0).
if (!a:preserve_full_diff && s:grep_available) || a:realtime let cmd .= ' || exit 0'
" grep exits with 1 when no matches are found; 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'
endif
let cmd .= ')' let cmd .= ')'
if !tracked let cmd = gitgutter#utility#cd_cmd(a:bufnr, cmd)
let cmd .= ')'
endif
let cmd = gitgutter#utility#command_in_directory_of_file(cmd) if g:gitgutter_async && gitgutter#async#available()
call gitgutter#async#execute(cmd, a:bufnr, {
if g:gitgutter_async && gitgutter#async#available() && !a:preserve_full_diff \ 'out': function('gitgutter#diff#handler'),
call gitgutter#async#execute(cmd) \ 'err': function('gitgutter#hunk#reset'),
\ })
return 'async' return 'async'
else else
let diff = gitgutter#utility#system(cmd) let diff = gitgutter#utility#system(cmd)
if gitgutter#utility#shell_error() if v:shell_error
" A shell error indicates the file is not tracked by git (unless something bizarre is going on). call gitgutter#debug#log(diff)
throw 'diff failed' throw 'gitgutter diff failed'
endif endif
return diff return diff
endif endif
endfunction endfunction
function! gitgutter#diff#handler(bufnr, diff) abort
call gitgutter#debug#log(a:diff)
call gitgutter#hunk#set_hunks(a:bufnr, gitgutter#diff#parse_diff(a:diff))
let modified_lines = s:process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr))
if len(modified_lines) > g:gitgutter_max_signs
call gitgutter#utility#warn_once('exceeded maximum number of signs (configured by 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)
execute "silent doautocmd" s:nomodeline "User GitGutter"
endfunction
function! gitgutter#diff#parse_diff(diff) abort function! gitgutter#diff#parse_diff(diff) abort
let hunks = [] let hunks = []
for line in split(a:diff, '\n') for line in split(a:diff, '\n')
@@ -171,69 +156,69 @@ function! gitgutter#diff#parse_hunk(line) abort
end end
endfunction endfunction
function! gitgutter#diff#process_hunks(hunks) abort function! s:process_hunks(bufnr, hunks) abort
let modified_lines = [] let modified_lines = []
for hunk in a:hunks for hunk in a:hunks
call extend(modified_lines, gitgutter#diff#process_hunk(hunk)) call extend(modified_lines, s:process_hunk(a:bufnr, hunk))
endfor endfor
return modified_lines return modified_lines
endfunction endfunction
" Returns [ [<line_number (number)>, <name (string)>], ...] " Returns [ [<line_number (number)>, <name (string)>], ...]
function! gitgutter#diff#process_hunk(hunk) abort function! s:process_hunk(bufnr, hunk) abort
let modifications = [] let modifications = []
let from_line = a:hunk[0] let from_line = a:hunk[0]
let from_count = a:hunk[1] let from_count = a:hunk[1]
let to_line = a:hunk[2] let to_line = a:hunk[2]
let to_count = a:hunk[3] let to_count = a:hunk[3]
if gitgutter#diff#is_added(from_count, to_count) if s:is_added(from_count, to_count)
call gitgutter#diff#process_added(modifications, from_count, to_count, to_line) call s:process_added(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_added(to_count) call gitgutter#hunk#increment_lines_added(a:bufnr, to_count)
elseif gitgutter#diff#is_removed(from_count, to_count) elseif s:is_removed(from_count, to_count)
call gitgutter#diff#process_removed(modifications, from_count, to_count, to_line) call s:process_removed(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_removed(from_count) call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count)
elseif gitgutter#diff#is_modified(from_count, to_count) elseif s:is_modified(from_count, to_count)
call gitgutter#diff#process_modified(modifications, from_count, to_count, to_line) call s:process_modified(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_modified(to_count) call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
elseif gitgutter#diff#is_modified_and_added(from_count, to_count) elseif s:is_modified_and_added(from_count, to_count)
call gitgutter#diff#process_modified_and_added(modifications, from_count, to_count, to_line) call s:process_modified_and_added(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_added(to_count - from_count) call gitgutter#hunk#increment_lines_added(a:bufnr, to_count - from_count)
call gitgutter#hunk#increment_lines_modified(from_count) call gitgutter#hunk#increment_lines_modified(a:bufnr, from_count)
elseif gitgutter#diff#is_modified_and_removed(from_count, to_count) elseif s:is_modified_and_removed(from_count, to_count)
call gitgutter#diff#process_modified_and_removed(modifications, from_count, to_count, to_line) call s:process_modified_and_removed(modifications, from_count, to_count, to_line)
call gitgutter#hunk#increment_lines_modified(to_count) call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
call gitgutter#hunk#increment_lines_removed(from_count - to_count) call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count - to_count)
endif endif
return modifications return modifications
endfunction endfunction
function! gitgutter#diff#is_added(from_count, to_count) abort function! s:is_added(from_count, to_count) abort
return a:from_count == 0 && a:to_count > 0 return a:from_count == 0 && a:to_count > 0
endfunction endfunction
function! gitgutter#diff#is_removed(from_count, to_count) abort function! s:is_removed(from_count, to_count) abort
return a:from_count > 0 && a:to_count == 0 return a:from_count > 0 && a:to_count == 0
endfunction endfunction
function! gitgutter#diff#is_modified(from_count, to_count) abort function! s:is_modified(from_count, to_count) abort
return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count
endfunction endfunction
function! gitgutter#diff#is_modified_and_added(from_count, to_count) abort 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 return a:from_count > 0 && a:to_count > 0 && a:from_count < a:to_count
endfunction endfunction
function! gitgutter#diff#is_modified_and_removed(from_count, to_count) abort 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 return a:from_count > 0 && a:to_count > 0 && a:from_count > a:to_count
endfunction endfunction
function! gitgutter#diff#process_added(modifications, from_count, to_count, to_line) abort function! s:process_added(modifications, from_count, to_count, to_line) abort
let offset = 0 let offset = 0
while offset < a:to_count while offset < a:to_count
let line_number = a:to_line + offset let line_number = a:to_line + offset
@@ -242,7 +227,7 @@ function! gitgutter#diff#process_added(modifications, from_count, to_count, to_l
endwhile endwhile
endfunction endfunction
function! gitgutter#diff#process_removed(modifications, from_count, to_count, to_line) abort function! s:process_removed(modifications, from_count, to_count, to_line) abort
if a:to_line == 0 if a:to_line == 0
call add(a:modifications, [1, 'removed_first_line']) call add(a:modifications, [1, 'removed_first_line'])
else else
@@ -250,7 +235,7 @@ function! gitgutter#diff#process_removed(modifications, from_count, to_count, to
endif endif
endfunction endfunction
function! gitgutter#diff#process_modified(modifications, from_count, to_count, to_line) abort function! s:process_modified(modifications, from_count, to_count, to_line) abort
let offset = 0 let offset = 0
while offset < a:to_count while offset < a:to_count
let line_number = a:to_line + offset let line_number = a:to_line + offset
@@ -259,7 +244,7 @@ function! gitgutter#diff#process_modified(modifications, from_count, to_count, t
endwhile endwhile
endfunction endfunction
function! gitgutter#diff#process_modified_and_added(modifications, from_count, to_count, to_line) abort function! s:process_modified_and_added(modifications, from_count, to_count, to_line) abort
let offset = 0 let offset = 0
while offset < a:from_count while offset < a:from_count
let line_number = a:to_line + offset let line_number = a:to_line + offset
@@ -273,7 +258,7 @@ function! gitgutter#diff#process_modified_and_added(modifications, from_count, t
endwhile endwhile
endfunction endfunction
function! gitgutter#diff#process_modified_and_removed(modifications, from_count, to_count, to_line) abort function! s:process_modified_and_removed(modifications, from_count, to_count, to_line) abort
let offset = 0 let offset = 0
while offset < a:to_count while offset < a:to_count
let line_number = a:to_line + offset let line_number = a:to_line + offset
@@ -283,28 +268,14 @@ function! gitgutter#diff#process_modified_and_removed(modifications, from_count,
let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed'] let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
endfunction endfunction
" Generates a zero-context diff for the current hunk.
"
" diff - the full diff for the buffer
" type - stage | undo | preview
function! gitgutter#diff#generate_diff_for_hunk(diff, type) abort
let diff_for_hunk = gitgutter#diff#discard_hunks(a:diff, a:type == 'stage' || a:type == 'undo')
if a:type == 'stage' || a:type == 'undo' " Returns a diff for the current hunk.
let diff_for_hunk = gitgutter#diff#adjust_hunk_summary(diff_for_hunk, a:type == 'stage') function! gitgutter#diff#hunk_diff(bufnr, full_diff)
endif
return diff_for_hunk
endfunction
" Returns the diff with all hunks discarded except the current.
"
" diff - the diff to process
" keep_header - truthy to keep the diff header and hunk summary, falsy to discard it
function! gitgutter#diff#discard_hunks(diff, keep_header) abort
let modified_diff = [] let modified_diff = []
let keep_line = a:keep_header let keep_line = 1
for line in split(a:diff, '\n') " 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) let hunk_info = gitgutter#diff#parse_hunk(line)
if len(hunk_info) == 4 " start of new hunk if len(hunk_info) == 4 " start of new hunk
let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info) let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info)
@@ -313,36 +284,36 @@ function! gitgutter#diff#discard_hunks(diff, keep_header) abort
call add(modified_diff, line) call add(modified_diff, line)
endif endif
endfor endfor
return join(modified_diff, "\n")."\n"
if a:keep_header
return gitgutter#utility#stringify(modified_diff)
else
" Discard hunk summary too.
return gitgutter#utility#stringify(modified_diff[1:])
endif
endfunction endfunction
" Adjust hunk summary (from's / to's line number) to ignore changes above/before this one.
" function! s:write_buffer(bufnr, file)
" diff_for_hunk - a diff containing only the hunk of interest " Write specified buffer (which may not be the current buffer) to buff_file.
" staging - truthy if the hunk is to be staged, falsy if it is to be undone " There doesn't seem to be a clean way to write a buffer that isn't the current
" " to a file; we have to switch to it, write it, then switch back.
" TODO: push this down to #discard_hunks? let current_buffer = bufnr('')
function! gitgutter#diff#adjust_hunk_summary(diff_for_hunk, staging) abort execute 'buffer '.a:bufnr
let line_adjustment = gitgutter#hunk#line_adjustment_for_current_hunk()
let adj_diff = [] " Writing the whole buffer resets the '[ and '] marks and also the
for line in split(a:diff_for_hunk, '\n') " 'modified' flag (if &cpoptions includes '+'). These are unwanted
if match(line, s:hunk_re) != -1 " side-effects so we save and restore the values ourselves.
if a:staging let modified = getbufvar(a:bufnr, "&mod")
" increment 'to' line number let op_mark_start = getpos("'[")
let line = substitute(line, '+\@<=\(\d\+\)', '\=submatch(1)+line_adjustment', '') let op_mark_end = getpos("']")
else
" decrement 'from' line number execute 'keepalt noautocmd silent write!' a:file
let line = substitute(line, '-\@<=\(\d\+\)', '\=submatch(1)-line_adjustment', '')
endif call setbufvar(a:bufnr, "&mod", modified)
endif call setpos("'[", op_mark_start)
call add(adj_diff, line) call setpos("']", op_mark_end)
endfor
return gitgutter#utility#stringify(adj_diff) execute 'buffer '.current_buffer
endfunction endfunction
function! s:save_last_seen_change(bufnr) abort
call gitgutter#utility#setbufvar(a:bufnr, 'tick', getbufvar(a:bufnr, 'changedtick'))
endfunction

View File

@@ -1,3 +1,37 @@
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 function! gitgutter#highlight#define_sign_column_highlight() abort
if g:gitgutter_override_sign_column_highlight if g:gitgutter_override_sign_column_highlight
highlight! link SignColumn LineNr highlight! link SignColumn LineNr
@@ -7,7 +41,7 @@ function! gitgutter#highlight#define_sign_column_highlight() abort
endfunction endfunction
function! gitgutter#highlight#define_highlights() abort function! gitgutter#highlight#define_highlights() abort
let [guibg, ctermbg] = gitgutter#highlight#get_background_colors('SignColumn') let [guibg, ctermbg] = s:get_background_colors('SignColumn')
" Highlights used by the signs. " Highlights used by the signs.
@@ -42,12 +76,12 @@ function! gitgutter#highlight#define_signs() abort
sign define GitGutterLineModifiedRemoved sign define GitGutterLineModifiedRemoved
sign define GitGutterDummy sign define GitGutterDummy
call gitgutter#highlight#define_sign_text() call s:define_sign_text()
call gitgutter#highlight#define_sign_text_highlights() call gitgutter#highlight#define_sign_text_highlights()
call gitgutter#highlight#define_sign_line_highlights() call s:define_sign_line_highlights()
endfunction endfunction
function! gitgutter#highlight#define_sign_text() abort function! s:define_sign_text() abort
execute "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added execute "sign define GitGutterLineAdded text=" . g:gitgutter_sign_added
execute "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified execute "sign define GitGutterLineModified text=" . g:gitgutter_sign_modified
execute "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed execute "sign define GitGutterLineRemoved text=" . g:gitgutter_sign_removed
@@ -75,7 +109,7 @@ function! gitgutter#highlight#define_sign_text_highlights() abort
endif endif
endfunction endfunction
function! gitgutter#highlight#define_sign_line_highlights() abort function! s:define_sign_line_highlights() abort
if g:gitgutter_highlight_lines if g:gitgutter_highlight_lines
sign define GitGutterLineAdded linehl=GitGutterAddLine sign define GitGutterLineAdded linehl=GitGutterAddLine
sign define GitGutterLineModified linehl=GitGutterChangeLine sign define GitGutterLineModified linehl=GitGutterChangeLine
@@ -91,22 +125,22 @@ function! gitgutter#highlight#define_sign_line_highlights() abort
endif endif
endfunction endfunction
function! gitgutter#highlight#get_background_colors(group) abort function! s:get_background_colors(group) abort
redir => highlight redir => highlight
silent execute 'silent highlight ' . a:group silent execute 'silent highlight ' . a:group
redir END redir END
let link_matches = matchlist(highlight, 'links to \(\S\+\)') let link_matches = matchlist(highlight, 'links to \(\S\+\)')
if len(link_matches) > 0 " follow the link if len(link_matches) > 0 " follow the link
return gitgutter#highlight#get_background_colors(link_matches[1]) return s:get_background_colors(link_matches[1])
endif endif
let ctermbg = gitgutter#highlight#match_highlight(highlight, 'ctermbg=\([0-9A-Za-z]\+\)') let ctermbg = s:match_highlight(highlight, 'ctermbg=\([0-9A-Za-z]\+\)')
let guibg = gitgutter#highlight#match_highlight(highlight, 'guibg=\([#0-9A-Za-z]\+\)') let guibg = s:match_highlight(highlight, 'guibg=\([#0-9A-Za-z]\+\)')
return [guibg, ctermbg] return [guibg, ctermbg]
endfunction endfunction
function! gitgutter#highlight#match_highlight(highlight, pattern) abort function! s:match_highlight(highlight, pattern) abort
let matches = matchlist(a:highlight, a:pattern) let matches = matchlist(a:highlight, a:pattern)
if len(matches) == 0 if len(matches) == 0
return 'NONE' return 'NONE'

View File

@@ -1,15 +1,15 @@
function! gitgutter#hunk#set_hunks(hunks) abort function! gitgutter#hunk#set_hunks(bufnr, hunks) abort
call gitgutter#utility#setbufvar(gitgutter#utility#bufnr(), 'hunks', a:hunks) call gitgutter#utility#setbufvar(a:bufnr, 'hunks', a:hunks)
call s:reset_summary() call s:reset_summary(a:bufnr)
endfunction endfunction
function! gitgutter#hunk#hunks() abort function! gitgutter#hunk#hunks(bufnr) abort
return gitgutter#utility#getbufvar(gitgutter#utility#bufnr(), 'hunks', []) return gitgutter#utility#getbufvar(a:bufnr, 'hunks', [])
endfunction endfunction
function! gitgutter#hunk#reset() abort function! gitgutter#hunk#reset(bufnr) abort
call gitgutter#utility#setbufvar(gitgutter#utility#bufnr(), 'hunks', []) call gitgutter#utility#setbufvar(a:bufnr, 'hunks', [])
call s:reset_summary() call s:reset_summary(a:bufnr)
endfunction endfunction
@@ -17,37 +17,35 @@ function! gitgutter#hunk#summary(bufnr) abort
return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0]) return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0])
endfunction endfunction
function! s:reset_summary() abort function! s:reset_summary(bufnr) abort
call gitgutter#utility#setbufvar(gitgutter#utility#bufnr(), 'summary', [0,0,0]) call gitgutter#utility#setbufvar(a:bufnr, 'summary', [0,0,0])
endfunction endfunction
function! gitgutter#hunk#increment_lines_added(count) abort function! gitgutter#hunk#increment_lines_added(bufnr, count) abort
let bufnr = gitgutter#utility#bufnr() let summary = gitgutter#hunk#summary(a:bufnr)
let summary = gitgutter#hunk#summary(bufnr)
let summary[0] += a:count let summary[0] += a:count
call gitgutter#utility#setbufvar(bufnr, 'summary', summary) call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction endfunction
function! gitgutter#hunk#increment_lines_modified(count) abort function! gitgutter#hunk#increment_lines_modified(bufnr, count) abort
let bufnr = gitgutter#utility#bufnr() let summary = gitgutter#hunk#summary(a:bufnr)
let summary = gitgutter#hunk#summary(bufnr)
let summary[1] += a:count let summary[1] += a:count
call gitgutter#utility#setbufvar(bufnr, 'summary', summary) call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction endfunction
function! gitgutter#hunk#increment_lines_removed(count) abort function! gitgutter#hunk#increment_lines_removed(bufnr, count) abort
let bufnr = gitgutter#utility#bufnr() let summary = gitgutter#hunk#summary(a:bufnr)
let summary = gitgutter#hunk#summary(bufnr)
let summary[2] += a:count let summary[2] += a:count
call gitgutter#utility#setbufvar(bufnr, 'summary', summary) call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
endfunction endfunction
function! gitgutter#hunk#next_hunk(count) abort function! gitgutter#hunk#next_hunk(count) abort
if gitgutter#utility#is_active() let bufnr = bufnr('')
if gitgutter#utility#is_active(bufnr)
let current_line = line('.') let current_line = line('.')
let hunk_count = 0 let hunk_count = 0
for hunk in gitgutter#hunk#hunks() for hunk in gitgutter#hunk#hunks(bufnr)
if hunk[2] > current_line if hunk[2] > current_line
let hunk_count += 1 let hunk_count += 1
if hunk_count == a:count if hunk_count == a:count
@@ -61,10 +59,11 @@ function! gitgutter#hunk#next_hunk(count) abort
endfunction endfunction
function! gitgutter#hunk#prev_hunk(count) abort function! gitgutter#hunk#prev_hunk(count) abort
if gitgutter#utility#is_active() let bufnr = bufnr('')
if gitgutter#utility#is_active(bufnr)
let current_line = line('.') let current_line = line('.')
let hunk_count = 0 let hunk_count = 0
for hunk in reverse(copy(gitgutter#hunk#hunks())) for hunk in reverse(copy(gitgutter#hunk#hunks(bufnr)))
if hunk[2] < current_line if hunk[2] < current_line
let hunk_count += 1 let hunk_count += 1
if hunk_count == a:count if hunk_count == a:count
@@ -80,10 +79,11 @@ endfunction
" Returns the hunk the cursor is currently in or an empty list if the cursor " Returns the hunk the cursor is currently in or an empty list if the cursor
" isn't in a hunk. " isn't in a hunk.
function! gitgutter#hunk#current_hunk() abort function! s:current_hunk() abort
let bufnr = bufnr('')
let current_hunk = [] let current_hunk = []
for hunk in gitgutter#hunk#hunks() for hunk in gitgutter#hunk#hunks(bufnr)
if gitgutter#hunk#cursor_in_hunk(hunk) if gitgutter#hunk#cursor_in_hunk(hunk)
let current_hunk = hunk let current_hunk = hunk
break break
@@ -107,22 +107,9 @@ function! gitgutter#hunk#cursor_in_hunk(hunk) abort
return 0 return 0
endfunction 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! gitgutter#hunk#line_adjustment_for_current_hunk() abort
let adj = 0
for hunk in gitgutter#hunk#hunks()
if gitgutter#hunk#cursor_in_hunk(hunk)
break
else
let adj += hunk[1] - hunk[3]
endif
endfor
return adj
endfunction
function! gitgutter#hunk#text_object(inner) abort function! gitgutter#hunk#text_object(inner) abort
let hunk = gitgutter#hunk#current_hunk() let bufnr = bufnr('')
let hunk = s:current_hunk(bufnr)
if empty(hunk) if empty(hunk)
return return
@@ -141,3 +128,129 @@ function! gitgutter#hunk#text_object(inner) abort
execute 'normal! 'first_line.'GV'.last_line.'G' execute 'normal! 'first_line.'GV'.last_line.'G'
endfunction endfunction
function! gitgutter#hunk#stage() abort
call s:hunk_op(function('s:stage'))
endfunction
function! gitgutter#hunk#undo() abort
call s:hunk_op(function('s:undo'))
endfunction
function! gitgutter#hunk#preview() abort
call s:hunk_op(function('s:preview'))
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)
silent! wincmd P
if !&previewwindow
noautocmd execute 'bo' &previewheight 'new'
set previewwindow
endif
setlocal noro modifiable filetype=diff buftype=nofile bufhidden=delete noswapfile
execute "%delete_"
call append(0, split(s:discard_header(a:hunk_diff), "\n"))
noautocmd wincmd p
endfunction
function! s:adjust_header(bufnr, hunk_diff)
return s:adjust_hunk_summary(s:fix_file_references(a:bufnr, a:hunk_diff))
endfunction
" Replaces references to temp files with the actual file.
function! s:fix_file_references(bufnr, hunk_diff)
let filepath = gitgutter#utility#repo_path(a:bufnr, 0)
let diff = a:hunk_diff
for tmp in matchlist(diff, '\vdiff --git a/(\S+) b/(\S+)\n')[1:2]
let diff = substitute(diff, tmp, filepath, 'g')
endfor
return diff
endfunction
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

@@ -10,14 +10,43 @@ let s:dummy_sign_id = s:first_sign_id - 1
let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596")) let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596"))
" Removes gitgutter's signs (excluding dummy sign) from the buffer being processed. function! gitgutter#sign#enable() abort
function! gitgutter#sign#clear_signs() abort let old_signs = g:gitgutter_signs
let bufnr = gitgutter#utility#bufnr()
call gitgutter#sign#find_current_signs()
let sign_ids = map(values(gitgutter#utility#getbufvar(bufnr, 'gitgutter_signs')), 'v:val.id') let g:gitgutter_signs = 1
call gitgutter#sign#remove_signs(sign_ids, 1) call gitgutter#highlight#define_sign_text_highlights()
call gitgutter#utility#setbufvar(bufnr, 'gitgutter_signs', {})
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 endfunction
@@ -25,39 +54,37 @@ endfunction
" "
" modified_lines: list of [<line_number (number)>, <name (string)>] " modified_lines: list of [<line_number (number)>, <name (string)>]
" where name = 'added|removed|modified|modified_removed' " where name = 'added|removed|modified|modified_removed'
function! gitgutter#sign#update_signs(modified_lines) abort function! gitgutter#sign#update_signs(bufnr, modified_lines) abort
call gitgutter#sign#find_current_signs() call s:find_current_signs(a:bufnr)
let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]') let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]')
let obsolete_signs = gitgutter#sign#obsolete_gitgutter_signs_to_remove(new_gitgutter_signs_line_numbers) 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) let flicker_possible = s:remove_all_old_signs && !empty(a:modified_lines)
if flicker_possible if flicker_possible
call gitgutter#sign#add_dummy_sign() call s:add_dummy_sign(a:bufnr)
endif endif
call gitgutter#sign#remove_signs(obsolete_signs, s:remove_all_old_signs) call s:remove_signs(a:bufnr, obsolete_signs, s:remove_all_old_signs)
call gitgutter#sign#upsert_new_gitgutter_signs(a:modified_lines) call s:upsert_new_gitgutter_signs(a:bufnr, a:modified_lines)
if flicker_possible if flicker_possible
call gitgutter#sign#remove_dummy_sign(0) call gitgutter#sign#remove_dummy_sign(a:bufnr, 0)
endif endif
endfunction endfunction
function! gitgutter#sign#add_dummy_sign() abort function! s:add_dummy_sign(bufnr) abort
let bufnr = gitgutter#utility#bufnr() if !gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign')
if !gitgutter#utility#getbufvar(bufnr, 'dummy_sign') execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . a:bufnr
execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . bufnr call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', 1)
call gitgutter#utility#setbufvar(bufnr, 'dummy_sign', 1)
endif endif
endfunction endfunction
function! gitgutter#sign#remove_dummy_sign(force) abort function! gitgutter#sign#remove_dummy_sign(bufnr, force) abort
let bufnr = gitgutter#utility#bufnr() if gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign') && (a:force || !g:gitgutter_sign_column_always)
if gitgutter#utility#getbufvar(bufnr, 'dummy_sign') && (a:force || !g:gitgutter_sign_column_always) execute "sign unplace" s:dummy_sign_id "buffer=" . a:bufnr
execute "sign unplace" s:dummy_sign_id "buffer=" . bufnr call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', 0)
call gitgutter#utility#setbufvar(bufnr, 'dummy_sign', 0)
endif endif
endfunction endfunction
@@ -67,14 +94,13 @@ endfunction
" "
function! gitgutter#sign#find_current_signs() abort function! s:find_current_signs(bufnr) abort
let bufnr = gitgutter#utility#bufnr()
let gitgutter_signs = {} " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>} let gitgutter_signs = {} " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>}
let other_signs = [] " [<line_number (number),...] let other_signs = [] " [<line_number (number),...]
let dummy_sign_placed = 0 let dummy_sign_placed = 0
redir => signs redir => signs
silent execute "sign place buffer=" . bufnr silent execute "sign place buffer=" . a:bufnr
redir END redir END
for sign_line in filter(split(signs, '\n')[2:], 'v:val =~# "="') for sign_line in filter(split(signs, '\n')[2:], 'v:val =~# "="')
@@ -101,19 +127,18 @@ function! gitgutter#sign#find_current_signs() abort
end end
endfor endfor
call gitgutter#utility#setbufvar(bufnr, 'dummy_sign', dummy_sign_placed) call gitgutter#utility#setbufvar(a:bufnr, 'dummy_sign', dummy_sign_placed)
call gitgutter#utility#setbufvar(bufnr, 'gitgutter_signs', gitgutter_signs) call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', gitgutter_signs)
call gitgutter#utility#setbufvar(bufnr, 'other_signs', other_signs) call gitgutter#utility#setbufvar(a:bufnr, 'other_signs', other_signs)
endfunction endfunction
" Returns a list of [<id (number)>, ...] " Returns a list of [<id (number)>, ...]
" Sets `s:remove_all_old_signs` as a side-effect. " Sets `s:remove_all_old_signs` as a side-effect.
function! gitgutter#sign#obsolete_gitgutter_signs_to_remove(new_gitgutter_signs_line_numbers) abort function! s:obsolete_gitgutter_signs_to_remove(bufnr, new_gitgutter_signs_line_numbers) abort
let bufnr = gitgutter#utility#bufnr()
let signs_to_remove = [] " list of [<id (number)>, ...] let signs_to_remove = [] " list of [<id (number)>, ...]
let remove_all_signs = 1 let remove_all_signs = 1
let old_gitgutter_signs = gitgutter#utility#getbufvar(bufnr, 'gitgutter_signs') let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
for line_number in keys(old_gitgutter_signs) for line_number in keys(old_gitgutter_signs)
if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1 if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1
call add(signs_to_remove, old_gitgutter_signs[line_number].id) call add(signs_to_remove, old_gitgutter_signs[line_number].id)
@@ -126,13 +151,12 @@ function! gitgutter#sign#obsolete_gitgutter_signs_to_remove(new_gitgutter_signs_
endfunction endfunction
function! gitgutter#sign#remove_signs(sign_ids, all_signs) abort function! s:remove_signs(bufnr, sign_ids, all_signs) abort
let bufnr = gitgutter#utility#bufnr() if a:all_signs && s:supports_star && empty(gitgutter#utility#getbufvar(a:bufnr, 'other_signs'))
if a:all_signs && s:supports_star && empty(gitgutter#utility#getbufvar(bufnr, 'other_signs')) let dummy_sign_present = gitgutter#utility#getbufvar(a:bufnr, 'dummy_sign')
let dummy_sign_present = gitgutter#utility#getbufvar(bufnr, 'dummy_sign') execute "sign unplace * buffer=" . a:bufnr
execute "sign unplace * buffer=" . bufnr
if dummy_sign_present if dummy_sign_present
execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . bufnr execute "sign place" s:dummy_sign_id "line=" . 9999 "name=GitGutterDummy buffer=" . a:bufnr
endif endif
else else
for id in a:sign_ids for id in a:sign_ids
@@ -142,22 +166,21 @@ function! gitgutter#sign#remove_signs(sign_ids, all_signs) abort
endfunction endfunction
function! gitgutter#sign#upsert_new_gitgutter_signs(modified_lines) abort function! s:upsert_new_gitgutter_signs(bufnr, modified_lines) abort
let bufnr = gitgutter#utility#bufnr() let other_signs = gitgutter#utility#getbufvar(a:bufnr, 'other_signs')
let other_signs = gitgutter#utility#getbufvar(bufnr, 'other_signs') let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
let old_gitgutter_signs = gitgutter#utility#getbufvar(bufnr, 'gitgutter_signs')
for line in a:modified_lines for line in a:modified_lines
let line_number = line[0] " <number> let line_number = line[0] " <number>
if index(other_signs, line_number) == -1 " don't clobber others' signs if index(other_signs, line_number) == -1 " don't clobber others' signs
let name = gitgutter#utility#highlight_name_for_change(line[1]) let name = s:highlight_name_for_change(line[1])
if !has_key(old_gitgutter_signs, line_number) " insert if !has_key(old_gitgutter_signs, line_number) " insert
let id = gitgutter#sign#next_sign_id() let id = s:next_sign_id()
execute "sign place" id "line=" . line_number "name=" . name "buffer=" . bufnr execute "sign place" id "line=" . line_number "name=" . name "buffer=" . a:bufnr
else " update if sign has changed else " update if sign has changed
let old_sign = old_gitgutter_signs[line_number] let old_sign = old_gitgutter_signs[line_number]
if old_sign.name !=# name if old_sign.name !=# name
execute "sign place" old_sign.id "name=" . name "buffer=" . bufnr execute "sign place" old_sign.id "name=" . name "buffer=" . a:bufnr
end end
endif endif
endif endif
@@ -166,7 +189,7 @@ function! gitgutter#sign#upsert_new_gitgutter_signs(modified_lines) abort
endfunction endfunction
function! gitgutter#sign#next_sign_id() abort function! s:next_sign_id() abort
let next_id = s:next_sign_id let next_id = s:next_sign_id
let s:next_sign_id += 1 let s:next_sign_id += 1
return next_id return next_id
@@ -177,3 +200,20 @@ endfunction
function! gitgutter#sign#reset() function! gitgutter#sign#reset()
let s:next_sign_id = s:first_sign_id let s:next_sign_id = s:first_sign_id
endfunction 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

@@ -1,7 +1,3 @@
let s:file = ''
let s:using_xolox_shell = -1
let s:exit_code = 0
function! gitgutter#utility#setbufvar(buffer, varname, val) function! gitgutter#utility#setbufvar(buffer, varname, val)
let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {}) let dict = get(getbufvar(a:buffer, ''), 'gitgutter', {})
let dict[a:varname] = a:val let dict[a:varname] = a:val
@@ -38,188 +34,128 @@ endfunction
" Returns truthy when the buffer's file should be processed; and falsey when it shouldn't. " 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. " This function does not and should not make any system calls.
function! gitgutter#utility#is_active() abort function! gitgutter#utility#is_active(bufnr) abort
return g:gitgutter_enabled && return g:gitgutter_enabled &&
\ !pumvisible() && \ !pumvisible() &&
\ gitgutter#utility#is_file_buffer() && \ s:is_file_buffer(a:bufnr) &&
\ gitgutter#utility#exists_file() && \ s:exists_file(a:bufnr) &&
\ gitgutter#utility#not_git_dir() \ s:not_git_dir(a:bufnr)
endfunction endfunction
function! gitgutter#utility#not_git_dir() abort function! s:not_git_dir(bufnr) abort
return gitgutter#utility#full_path_to_directory_of_file() !~ '[/\\]\.git\($\|[/\\]\)' return s:dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
endfunction endfunction
function! gitgutter#utility#is_file_buffer() abort function! s:is_file_buffer(bufnr) abort
return empty(getbufvar(s:bufnr, '&buftype')) return empty(getbufvar(a:bufnr, '&buftype'))
endfunction endfunction
" A replacement for the built-in `shellescape(arg)`. " From tpope/vim-fugitive
" function! s:winshell()
" Recent versions of Vim handle shell escaping pretty well. However older return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
" versions aren't as good. This attempts to do the right thing. endfunction
"
" See: " From tpope/vim-fugitive
" https://github.com/tpope/vim-fugitive/blob/8f0b8edfbd246c0026b7a2388e1d883d579ac7f6/plugin/fugitive.vim#L29-L37
function! gitgutter#utility#shellescape(arg) abort function! gitgutter#utility#shellescape(arg) abort
if a:arg =~ '^[A-Za-z0-9_/.-]\+$' if a:arg =~ '^[A-Za-z0-9_/.-]\+$'
return a:arg return a:arg
elseif &shell =~# 'cmd' || gitgutter#utility#using_xolox_shell() elseif s:winshell()
return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"' return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"'
else else
return shellescape(a:arg) return shellescape(a:arg)
endif endif
endfunction endfunction
function! gitgutter#utility#set_buffer(bufnr) abort function! gitgutter#utility#file(bufnr)
let s:bufnr = a:bufnr return s:abs_path(a:bufnr, 1)
let s:file = resolve(bufname(a:bufnr))
endfunction endfunction
function! gitgutter#utility#bufnr() " Not shellescaped
return s:bufnr function! gitgutter#utility#extension(bufnr) abort
endfunction return fnamemodify(s:abs_path(a:bufnr, 0), ':e')
function! gitgutter#utility#file()
return s:file
endfunction
function! gitgutter#utility#filename() abort
return fnamemodify(s:file, ':t')
endfunction
function! gitgutter#utility#extension() abort
return fnamemodify(s:file, ':e')
endfunction
function! gitgutter#utility#full_path_to_directory_of_file() abort
return fnamemodify(s:file, ':p:h')
endfunction
function! gitgutter#utility#directory_of_file() abort
return fnamemodify(s:file, ':h')
endfunction
function! gitgutter#utility#exists_file() abort
return filereadable(s:file)
endfunction
function! gitgutter#utility#has_unsaved_changes() abort
return getbufvar(s:bufnr, "&mod")
endfunction
function! gitgutter#utility#has_fresh_changes() abort
return getbufvar(s:bufnr, 'changedtick') != gitgutter#utility#getbufvar(s:bufnr, 'last_tick')
endfunction
function! gitgutter#utility#save_last_seen_change() abort
call gitgutter#utility#setbufvar(s:bufnr, 'last_tick', getbufvar(s:bufnr, 'changedtick'))
endfunction
function! gitgutter#utility#shell_error() abort
return gitgutter#utility#using_xolox_shell() ? s:exit_code : v:shell_error
endfunction
function! gitgutter#utility#using_xolox_shell() abort
if s:using_xolox_shell == -1
if !g:gitgutter_avoid_cmd_prompt_on_windows
let s:using_xolox_shell = 0
" Although xolox/vim-shell works on both windows and unix we only want to use
" it on windows.
elseif has('win32') || has('win64') || has('win32unix')
let s:using_xolox_shell = exists('g:xolox#misc#version') && exists('g:xolox#shell#version')
else
let s:using_xolox_shell = 0
endif
endif
return s:using_xolox_shell
endfunction endfunction
function! gitgutter#utility#system(cmd, ...) abort function! gitgutter#utility#system(cmd, ...) abort
call gitgutter#debug#log(a:cmd, a:000) call gitgutter#debug#log(a:cmd, a:000)
if gitgutter#utility#using_xolox_shell() call s:use_known_shell()
let options = {'command': a:cmd, 'check': 0} silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
if a:0 > 0 call s:restore_shell()
let options['stdin'] = a:1
endif
let ret = xolox#misc#os#exec(options)
let output = join(ret.stdout, "\n")
let s:exit_code = ret.exit_code
else
silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
endif
return output return output
endfunction endfunction
function! gitgutter#utility#file_relative_to_repo_root() abort " Path of file relative to repo root.
let file_path_relative_to_repo_root = gitgutter#utility#getbufvar(s:bufnr, 'repo_relative_path') "
if empty(file_path_relative_to_repo_root) " * empty string - not set
let dir_path_relative_to_repo_root = gitgutter#utility#system(gitgutter#utility#command_in_directory_of_file(g:gitgutter_git_executable.' rev-parse --show-prefix')) " * non-empty string - path
let dir_path_relative_to_repo_root = gitgutter#utility#strip_trailing_new_line(dir_path_relative_to_repo_root) " * -1 - pending
let file_path_relative_to_repo_root = dir_path_relative_to_repo_root . gitgutter#utility#filename() " * -2 - not tracked by git
call gitgutter#utility#setbufvar(s:bufnr, 'repo_relative_path', file_path_relative_to_repo_root) function! gitgutter#utility#repo_path(bufnr, shellesc) abort
endif let p = gitgutter#utility#getbufvar(a:bufnr, 'path')
return file_path_relative_to_repo_root return a:shellesc ? gitgutter#utility#shellescape(p) : p
endfunction endfunction
function! gitgutter#utility#command_in_directory_of_file(cmd) abort function! gitgutter#utility#set_repo_path(bufnr) abort
return 'cd '.gitgutter#utility#shellescape(gitgutter#utility#directory_of_file()).' && '.a:cmd " Values of path:
endfunction " * non-empty string - path
" * -1 - pending
" * -2 - not tracked by git
function! gitgutter#utility#highlight_name_for_change(text) abort call gitgutter#utility#setbufvar(a:bufnr, 'path', -1)
if a:text ==# 'added' 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)))
return 'GitGutterLineAdded'
elseif a:text ==# 'removed' if g:gitgutter_async && gitgutter#async#available()
return 'GitGutterLineRemoved' call gitgutter#async#execute(cmd, a:bufnr, {
elseif a:text ==# 'removed_first_line' \ 'out': {bufnr, path -> gitgutter#utility#setbufvar(bufnr, 'path', s:strip_trailing_new_line(path))},
return 'GitGutterLineRemovedFirstLine' \ 'err': {bufnr -> gitgutter#utility#setbufvar(bufnr, 'path', -2)},
elseif a:text ==# 'modified' \ })
return 'GitGutterLineModified' else
elseif a:text ==# 'modified_removed' let path = gitgutter#utility#system(cmd)
return 'GitGutterLineModifiedRemoved' 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 endif
endfunction endfunction
" Dedups list in-place. function! gitgutter#utility#cd_cmd(bufnr, cmd) abort
" Assumes list has no empty entries. return 'cd '.s:dir(a:bufnr).' && '.a:cmd
function! gitgutter#utility#dedup(list)
return filter(sort(a:list), 'index(a:list, v:val, v:key + 1) == -1')
endfunction endfunction
function! gitgutter#utility#strip_trailing_new_line(line) abort 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$', '', '') return substitute(a:line, '\n$', '', '')
endfunction endfunction
" True for git v1.7.2+.
function! gitgutter#utility#git_supports_command_line_config_override() abort
call system(g:gitgutter_git_executable.' -c foo.bar=baz --version')
return !v:shell_error
endfunction
function! gitgutter#utility#stringify(list) abort
return join(a:list, "\n")."\n"
endfunction
function! gitgutter#utility#use_known_shell() abort
if has('unix')
if &shell !=# 'sh'
let s:shell = &shell
let s:shellcmdflag = &shellcmdflag
let s:shellredir = &shellredir
let &shell = 'sh'
set shellcmdflag=-c
set shellredir=>%s\ 2>&1
endif
endif
endfunction
function! gitgutter#utility#restore_shell() abort
if has('unix')
if exists('s:shell')
let &shell = s:shell
let &shellcmdflag = s:shellcmdflag
let &shellredir = s:shellredir
endif
endif
endfunction

View File

@@ -22,11 +22,11 @@ CONTENTS *GitGutterContents*
1. INTRODUCTION *GitGutterIntroduction* 1. INTRODUCTION *GitGutterIntroduction*
*GitGutter* *GitGutter*
Vim Git Gutter is a Vim plugin which shows a git diff in the 'gutter' (sign GitGutter is a Vim plugin which shows a git diff in the 'gutter' (sign
column). It shows whether each line has been added, modified, and where lines column). It shows whether each line has been added, modified, and where lines
have been removed. have been removed.
This is a port of the Git Gutter plugin for Sublime Text 2. This is a port of the GitGutter plugin for Sublime Text 2.
=============================================================================== ===============================================================================
2. INSTALLATION *GitGutterInstallation* 2. INSTALLATION *GitGutterInstallation*
@@ -301,20 +301,6 @@ Add to your |vimrc|
let g:gitgutter_highlight_lines = 1 let g:gitgutter_highlight_lines = 1
< <
TO STOP VIM-GITGUTTER RUNNING IN REALTIME
Add to your |vimrc|
>
let g:gitgutter_realtime = 0
<
TO STOP VIM-GITGUTTER RUNNING EAGERLY
Add to your |vimrc|
>
let g:gitgutter_eager = 0
<
TO TURN OFF ASYNCHRONOUS UPDATES TO TURN OFF ASYNCHRONOUS UPDATES
By default diffs are run asynchronously. To run diffs synchronously By default diffs are run asynchronously. To run diffs synchronously

View File

@@ -33,13 +33,12 @@ call s:set('g:gitgutter_signs', 1)
call s:set('g:gitgutter_highlight_lines', 0) call s:set('g:gitgutter_highlight_lines', 0)
call s:set('g:gitgutter_sign_column_always', 0) call s:set('g:gitgutter_sign_column_always', 0)
if g:gitgutter_sign_column_always && exists('&signcolumn') if g:gitgutter_sign_column_always && exists('&signcolumn')
" Vim 7.4.2201.
set signcolumn=yes set signcolumn=yes
let g:gitgutter_sign_column_always = 0 let g:gitgutter_sign_column_always = 0
call gitgutter#utility#warn('please replace "let g:gitgutter_sign_column_always=1" with "set signcolumn=yes"') call gitgutter#utility#warn('please replace "let g:gitgutter_sign_column_always=1" with "set signcolumn=yes"')
endif endif
call s:set('g:gitgutter_override_sign_column_highlight', 1) call s:set('g:gitgutter_override_sign_column_highlight', 1)
call s:set('g:gitgutter_realtime', 1)
call s:set('g:gitgutter_eager', 1)
call s:set('g:gitgutter_sign_added', '+') call s:set('g:gitgutter_sign_added', '+')
call s:set('g:gitgutter_sign_modified', '~') call s:set('g:gitgutter_sign_modified', '~')
call s:set('g:gitgutter_sign_removed', '_') call s:set('g:gitgutter_sign_removed', '_')
@@ -53,15 +52,26 @@ call s:set('g:gitgutter_sign_modified_removed', '~_')
call s:set('g:gitgutter_diff_args', '') call s:set('g:gitgutter_diff_args', '')
call s:set('g:gitgutter_diff_base', '') call s:set('g:gitgutter_diff_base', '')
call s:set('g:gitgutter_map_keys', 1) call s:set('g:gitgutter_map_keys', 1)
call s:set('g:gitgutter_avoid_cmd_prompt_on_windows', 1)
call s:set('g:gitgutter_async', 1) call s:set('g:gitgutter_async', 1)
call s:set('g:gitgutter_log', 0) call s:set('g:gitgutter_log', 0)
call s:set('g:gitgutter_git_executable', 'git')
call s:set('g:gitgutter_git_executable', 'git')
if !executable(g:gitgutter_git_executable) if !executable(g:gitgutter_git_executable)
call gitgutter#utility#warn('cannot find git. Please set g:gitgutter_git_executable.') call gitgutter#utility#warn('cannot find git. Please set g:gitgutter_git_executable.')
endif endif
call s:set('g:gitgutter_grep', 'grep')
if !empty(g:gitgutter_grep)
if !executable(g:gitgutter_grep)
call gitgutter#utility#warn('cannot find '.g:gitgutter_grep.'. Please set g:gitgutter_grep.')
let g:gitgutter_grep = ''
else
if $GREP_OPTIONS =~# '--color=always'
let g:gitgutter_grep .= ' --color=never'
endif
endif
endif
call gitgutter#highlight#define_sign_column_highlight() call gitgutter#highlight#define_sign_column_highlight()
call gitgutter#highlight#define_highlights() call gitgutter#highlight#define_highlights()
call gitgutter#highlight#define_signs() call gitgutter#highlight#define_signs()
@@ -70,8 +80,8 @@ call gitgutter#highlight#define_signs()
" Primary functions {{{ " Primary functions {{{
command -bar GitGutterAll call gitgutter#all() command -bar GitGutterAll call gitgutter#all(1)
command -bar GitGutter call gitgutter#process_buffer(bufnr(''), 0) command -bar GitGutter call gitgutter#process_buffer(bufnr(''), 1)
command -bar GitGutterDisable call gitgutter#disable() command -bar GitGutterDisable call gitgutter#disable()
command -bar GitGutterEnable call gitgutter#enable() command -bar GitGutterEnable call gitgutter#enable()
@@ -81,17 +91,17 @@ command -bar GitGutterToggle call gitgutter#toggle()
" Line highlights {{{ " Line highlights {{{
command -bar GitGutterLineHighlightsDisable call gitgutter#line_highlights_disable() command -bar GitGutterLineHighlightsDisable call gitgutter#highlight#line_disable()
command -bar GitGutterLineHighlightsEnable call gitgutter#line_highlights_enable() command -bar GitGutterLineHighlightsEnable call gitgutter#highlight#line_enable()
command -bar GitGutterLineHighlightsToggle call gitgutter#line_highlights_toggle() command -bar GitGutterLineHighlightsToggle call gitgutter#highlight#line_toggle()
" }}} " }}}
" Signs {{{ " Signs {{{
command -bar GitGutterSignsEnable call gitgutter#signs_enable() command -bar GitGutterSignsEnable call gitgutter#sign#enable()
command -bar GitGutterSignsDisable call gitgutter#signs_disable() command -bar GitGutterSignsDisable call gitgutter#sign#disable()
command -bar GitGutterSignsToggle call gitgutter#signs_toggle() command -bar GitGutterSignsToggle call gitgutter#sign#toggle()
" }}} " }}}
@@ -100,10 +110,10 @@ command -bar GitGutterSignsToggle call gitgutter#signs_toggle()
command -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk(<count>) command -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk(<count>)
command -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk(<count>) command -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk(<count>)
command -bar GitGutterStageHunk call gitgutter#stage_hunk() command -bar GitGutterStageHunk call gitgutter#hunk#stage()
command -bar GitGutterUndoHunk call gitgutter#undo_hunk() command -bar GitGutterUndoHunk call gitgutter#hunk#undo()
command -bar GitGutterRevertHunk echomsg 'GitGutterRevertHunk is deprecated. Use GitGutterUndoHunk'<Bar>call gitgutter#undo_hunk() command -bar GitGutterRevertHunk echomsg 'GitGutterRevertHunk is deprecated. Use GitGutterUndoHunk'<Bar>call gitgutter#hunk#undo()
command -bar GitGutterPreviewHunk call gitgutter#preview_hunk() command -bar GitGutterPreviewHunk call gitgutter#hunk#preview()
" Hunk text object " Hunk text object
onoremap <silent> <Plug>GitGutterTextObjectInnerPending :<C-U>call gitgutter#hunk#text_object(1)<CR> onoremap <silent> <Plug>GitGutterTextObjectInnerPending :<C-U>call gitgutter#hunk#text_object(1)<CR>
@@ -130,7 +140,8 @@ xnoremap <silent> <Plug>GitGutterTextObjectOuterVisual :<C-U>call gitgutter#hun
" `line` - refers to the line number where the change starts " `line` - refers to the line number where the change starts
" `count` - refers to the number of lines the change covers " `count` - refers to the number of lines the change covers
function! GitGutterGetHunks() function! GitGutterGetHunks()
return gitgutter#utility#is_active() ? gitgutter#hunk#hunks() : [] let bufnr = bufnr('')
return gitgutter#utility#is_active(bufnr) ? gitgutter#hunk#hunks(bufnr) : []
endfunction endfunction
" Returns an array that contains a summary of the hunk status for the current " Returns an array that contains a summary of the hunk status for the current
@@ -196,34 +207,27 @@ endif
augroup gitgutter augroup gitgutter
autocmd! autocmd!
if g:gitgutter_realtime autocmd TabEnter * call settabvar(tabpagenr(), 'gitgutter_didtabenter', 1)
autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 1)
endif
if g:gitgutter_eager autocmd BufEnter *
autocmd BufWritePost,FileChangedShellPost,ShellCmdPost * call gitgutter#process_buffer(bufnr(''), 0) \ if gettabvar(tabpagenr(), 'gitgutter_didtabenter') |
\ call settabvar(tabpagenr(), 'gitgutter_didtabenter', 0) |
\ call gitgutter#all(0) |
\ else |
\ call gitgutter#init_buffer(bufnr('')) |
\ call gitgutter#process_buffer(bufnr(''), 0) |
\ endif
autocmd BufEnter * autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 0)
\ if gettabvar(tabpagenr(), 'gitgutter_didtabenter') | autocmd FileChangedShellPost,ShellCmdPost * call gitgutter#process_buffer(bufnr(''), 1)
\ call settabvar(tabpagenr(), 'gitgutter_didtabenter', 0) |
\ call gitgutter#all() |
\ else |
\ call gitgutter#process_buffer(bufnr(''), 0) |
\ endif
autocmd TabEnter * call settabvar(tabpagenr(), 'gitgutter_didtabenter', 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
" Ensure that all buffers are processed when opening vim with multiple files, e.g.: if !has('gui_win32')
" autocmd FocusGained * call gitgutter#all(1)
" vim -o file1 file2
autocmd VimEnter * if winnr() != winnr('$') | :GitGutterAll | endif
if !has('gui_win32')
autocmd FocusGained * call gitgutter#all()
endif
else
autocmd BufRead,BufWritePost,FileChangedShellPost * call gitgutter#process_buffer(bufnr(''), 0)
endif endif
autocmd ColorScheme * call gitgutter#highlight#define_sign_column_highlight() | call gitgutter#highlight#define_highlights() autocmd ColorScheme * call gitgutter#highlight#define_sign_column_highlight() | call gitgutter#highlight#define_highlights()

View File

@@ -64,7 +64,7 @@ endfunction
function Test_add_lines() function Test_add_lines()
normal ggo* normal ggo*
write doautocmd CursorHold
let expected = ["line=2 id=3000 name=GitGutterLineAdded"] let expected = ["line=2 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -76,7 +76,7 @@ function Test_add_lines_fish()
set shell=/usr/local/bin/fish set shell=/usr/local/bin/fish
normal ggo* normal ggo*
write doautocmd CursorHold
let expected = ["line=2 id=3000 name=GitGutterLineAdded"] let expected = ["line=2 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -87,7 +87,7 @@ endfunction
function Test_modify_lines() function Test_modify_lines()
normal ggi* normal ggi*
write doautocmd CursorHold
let expected = ["line=1 id=3000 name=GitGutterLineModified"] let expected = ["line=1 id=3000 name=GitGutterLineModified"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -96,7 +96,7 @@ endfunction
function Test_remove_lines() function Test_remove_lines()
execute '5d' execute '5d'
write doautocmd CursorHold
let expected = ["line=4 id=3000 name=GitGutterLineRemoved"] let expected = ["line=4 id=3000 name=GitGutterLineRemoved"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -105,7 +105,7 @@ endfunction
function Test_remove_first_lines() function Test_remove_first_lines()
execute '1d' execute '1d'
write doautocmd CursorHold
let expected = ["line=1 id=3000 name=GitGutterLineRemovedFirstLine"] let expected = ["line=1 id=3000 name=GitGutterLineRemovedFirstLine"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -115,7 +115,7 @@ endfunction
function Test_edit_file_with_same_name_as_a_branch() function Test_edit_file_with_same_name_as_a_branch()
normal 5Gi* normal 5Gi*
call system('git checkout -b fixture.txt') call system('git checkout -b fixture.txt')
write doautocmd CursorHold
let expected = ["line=5 id=3000 name=GitGutterLineModified"] let expected = ["line=5 id=3000 name=GitGutterLineModified"]
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
@@ -127,7 +127,7 @@ function Test_file_added_to_git()
call system('touch '.tmpfile.' && git add '.tmpfile) call system('touch '.tmpfile.' && git add '.tmpfile)
execute 'edit '.tmpfile execute 'edit '.tmpfile
normal ihello normal ihello
write doautocmd CursorHold
let expected = ["line=1 id=3000 name=GitGutterLineAdded"] let expected = ["line=1 id=3000 name=GitGutterLineAdded"]
call assert_equal(expected, s:signs('fileAddedToGit.tmp')) call assert_equal(expected, s:signs('fileAddedToGit.tmp'))
@@ -138,7 +138,7 @@ function Test_filename_with_equals()
call system('touch =fixture=.txt && git add =fixture=.txt') call system('touch =fixture=.txt && git add =fixture=.txt')
edit =fixture=.txt edit =fixture=.txt
normal ggo* normal ggo*
write doautocmd CursorHold
let expected = [ let expected = [
\ 'line=1 id=3000 name=GitGutterLineAdded', \ 'line=1 id=3000 name=GitGutterLineAdded',
@@ -152,7 +152,7 @@ function Test_filename_with_square_brackets()
call system('touch fix[tu]re.txt && git add fix[tu]re.txt') call system('touch fix[tu]re.txt && git add fix[tu]re.txt')
edit fix[tu]re.txt edit fix[tu]re.txt
normal ggo* normal ggo*
write doautocmd CursorHold
let expected = [ let expected = [
\ 'line=1 id=3000 name=GitGutterLineAdded', \ 'line=1 id=3000 name=GitGutterLineAdded',
@@ -168,7 +168,7 @@ function Test_follow_symlink()
call system('ln -nfs fixture.txt '.tmp) call system('ln -nfs fixture.txt '.tmp)
execute 'edit '.tmp execute 'edit '.tmp
6d 6d
write doautocmd CursorHold
let expected = ['line=5 id=3000 name=GitGutterLineRemoved'] let expected = ['line=5 id=3000 name=GitGutterLineRemoved']
call assert_equal(expected, s:signs('symlink')) call assert_equal(expected, s:signs('symlink'))
@@ -218,26 +218,15 @@ endfunction
function Test_orphaned_signs() function Test_orphaned_signs()
execute "normal 5GoX\<CR>Y" execute "normal 5GoX\<CR>Y"
write doautocmd CursorHold
6d 6d
write doautocmd CursorHold
let expected = ['line=6 id=3001 name=GitGutterLineAdded'] let expected = ['line=6 id=3001 name=GitGutterLineAdded']
call assert_equal(expected, s:signs('fixture.txt')) call assert_equal(expected, s:signs('fixture.txt'))
endfunction endfunction
function Test_sign_column_always()
let g:gitgutter_sign_column_always=1
write
let expected = ['line=9999 id=2999 name=GitGutterDummy']
call assert_equal(expected, s:signs('fixture.txt'))
let g:gitgutter_sign_column_always=0
endfunction
function Test_untracked_file_outside_repo() function Test_untracked_file_outside_repo()
let tmp = tempname() let tmp = tempname()
call system('touch '.tmp) call system('touch '.tmp)
@@ -301,8 +290,19 @@ function Test_hunk_stage()
call assert_equal([], s:signs('fixture.txt')) call assert_equal([], s:signs('fixture.txt'))
call assert_equal([], s:git_diff()) " 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 = [ let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt', \ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..ae8e546 100644', \ 'index f5c6aff..ae8e546 100644',
@@ -313,6 +313,11 @@ function Test_hunk_stage()
\ '+*e' \ '+*e'
\ ] \ ]
call assert_equal(expected, s:git_diff_staged()) call assert_equal(expected, s:git_diff_staged())
" Save the buffer
write
call assert_equal([], s:git_diff())
endfunction endfunction
@@ -329,6 +334,31 @@ function Test_hunk_stage_nearby_hunk()
\ ] \ ]
call assert_equal(expected, s:signs('fixture.txt')) 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 = [ let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt', \ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index 53b13df..8fdfda7 100644', \ 'index 53b13df..8fdfda7 100644',
@@ -340,16 +370,6 @@ function Test_hunk_stage_nearby_hunk()
\ '+z', \ '+z',
\ ] \ ]
call assert_equal(expected, s:git_diff()) call assert_equal(expected, s:git_diff())
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())
endfunction endfunction
@@ -359,7 +379,6 @@ function Test_hunk_undo()
normal 5Gi* normal 5Gi*
GitGutterUndoHunk GitGutterUndoHunk
write " write file so we can verify git diff (--staged)
call assert_equal('foo', &shell) call assert_equal('foo', &shell)
let &shell = _shell let &shell = _shell
@@ -374,8 +393,9 @@ function Test_undo_nearby_hunk()
execute "normal! 2Gox\<CR>y\<CR>z" execute "normal! 2Gox\<CR>y\<CR>z"
normal 2jdd normal 2jdd
normal k normal k
doautocmd CursorHold
GitGutterUndoHunk GitGutterUndoHunk
write " write file so we can verify git diff (--staged) doautocmd CursorHold
let expected = [ let expected = [
\ 'line=3 id=3000 name=GitGutterLineAdded', \ 'line=3 id=3000 name=GitGutterLineAdded',
@@ -384,6 +404,13 @@ function Test_undo_nearby_hunk()
\ ] \ ]
call assert_equal(expected, s:signs('fixture.txt')) 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 = [ let expected = [
\ 'diff --git a/fixture.txt b/fixture.txt', \ 'diff --git a/fixture.txt b/fixture.txt',
\ 'index f5c6aff..3fbde56 100644', \ 'index f5c6aff..3fbde56 100644',
@@ -396,5 +423,4 @@ function Test_undo_nearby_hunk()
\ ] \ ]
call assert_equal(expected, s:git_diff()) call assert_equal(expected, s:git_diff())
call assert_equal([], s:git_diff_staged())
endfunction endfunction