The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .github
    └── issue_template.md
├── .gitignore
├── LICENCE
├── README.mkd
├── autoload
    ├── gitgutter.vim
    └── gitgutter
    │   ├── async.vim
    │   ├── debug.vim
    │   ├── diff.vim
    │   ├── diff_highlight.vim
    │   ├── fold.vim
    │   ├── highlight.vim
    │   ├── hunk.vim
    │   ├── sign.vim
    │   └── utility.vim
├── doc
    └── gitgutter.txt
├── plugin
    └── gitgutter.vim
├── screenshot.png
└── test
    ├── .gitattributes
    ├── .gitconfig
    ├── cp932.txt
    ├── fixture.foo
    ├── fixture.txt
    ├── fixture_dos.txt
    ├── fixture_dos_noeol.txt
    ├── runner.vim
    ├── test
    └── test_gitgutter.vim


/.github/issue_template.md:
--------------------------------------------------------------------------------
1 | > What is the latest commit SHA in your installed vim-gitgutter?
2 | 
3 | > What vim/nvim version are you on?
4 | 
5 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /doc/tags
2 | /misc
3 | /test/*.actual
4 | *.log
5 | 
6 | 


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


--------------------------------------------------------------------------------
/README.mkd:
--------------------------------------------------------------------------------
  1 | ## vim-gitgutter
  2 | 
  3 | A Vim plugin which shows a git diff in the sign column.  It shows which lines have been added, modified, or removed.  You can also preview, stage, and undo individual hunks; and stage partial hunks.  The plugin also provides a hunk text object.
  4 | 
  5 | The signs are always up to date and the plugin never saves your buffer.
  6 | 
  7 | The name "gitgutter" comes from the Sublime Text 3 plugin which inspired this in 2013.
  8 | 
  9 | Features:
 10 | 
 11 | * Shows signs for added, modified, and removed lines.
 12 | * Runs the diffs asynchronously where possible.
 13 | * Ensures signs are always up to date.
 14 | * Never saves the buffer.
 15 | * Quick jumping between blocks of changed lines ("hunks").
 16 | * Stage/undo/preview individual hunks.
 17 | * Previews highlight intra-line changes.
 18 | * Stage partial hunks.
 19 | * Provides a hunk text object.
 20 | * Diffs against index (default) or any commit.
 21 | * Handles file moves / renames.
 22 | * Heeds git's "assume unchanged" bit.
 23 | * Allows folding all unchanged text.
 24 | * Provides fold text showing whether folded lines have been changed.
 25 | * Can load all hunk locations into quickfix list or the current window's location list.
 26 | * Handles line endings correctly, even with repos that do CRLF conversion.
 27 | * Handles clean/smudge filters.
 28 | * Optional line highlighting.
 29 | * Optional line number highlighting. (Only available in Neovim 0.3.2 or higher)
 30 | * Fully customisable (signs, sign column, line (number) highlights, mappings, extra git-diff arguments, etc).
 31 | * Can be toggled on/off, globally or per buffer.
 32 | * Preserves signs from other plugins.
 33 | * Does the right thing when viewing revisions with [fugitive](https://github.com/tpope/vim-fugitive)'s `:0Gclog`.
 34 | * Easy to integrate diff stats into status line; built-in integration with [vim-airline](https://github.com/bling/vim-airline/).
 35 | * Works with fish shell (in addition to the usual shells).
 36 | 
 37 | Constraints:
 38 | 
 39 | * Supports git only.  If you work with other version control systems, I recommend [vim-signify](https://github.com/mhinz/vim-signify).
 40 | * Relies on the `FocusGained` event.  If your terminal doesn't report focus events, either use something like [Terminus][] or set `let g:gitgutter_terminal_reports_focus=0`.  For tmux, `set -g focus-events on` in your tmux.conf.
 41 | 
 42 | Compatibility:
 43 | 
 44 | Compatible back to Vim 7.4, and probably 7.3.
 45 | 
 46 | 
 47 | ### Screenshot
 48 | 
 49 | ![screenshot](./screenshot.png?raw=true)
 50 | 
 51 | In the screenshot above you can see:
 52 | 
 53 | * Lines 183-184 are new.
 54 | * Lines 186-187 have been modified.
 55 | * The preview for the modified lines highlights changed regions within the line.
 56 | 
 57 | 
 58 | ### Installation
 59 | 
 60 | First, install using your favourite package manager, or use Vim's built-in package support.
 61 | 
 62 | Vim:
 63 | 
 64 | ```
 65 | mkdir -p ~/.vim/pack/airblade/start
 66 | cd ~/.vim/pack/airblade/start
 67 | git clone https://github.com/airblade/vim-gitgutter.git
 68 | vim -u NONE -c "helptags vim-gitgutter/doc" -c q
 69 | ```
 70 | 
 71 | Neovim:
 72 | 
 73 | ```
 74 | mkdir -p ~/.config/nvim/pack/airblade/start
 75 | cd ~/.config/nvim/pack/airblade/start
 76 | git clone https://github.com/airblade/vim-gitgutter.git
 77 | nvim -u NONE -c "helptags vim-gitgutter/doc" -c q
 78 | ```
 79 | 
 80 | Second, ensure your `updatetime` and `signcolumn` options are set appropriately.
 81 | 
 82 | When you make a change to a file tracked by git, the diff markers should appear automatically after a short delay.  The delay is governed by vim's `updatetime` option; the default value is `4000`, i.e. 4 seconds, but I suggest reducing it to around 100ms (add `set updatetime=100` to your vimrc).  Note `updatetime` also controls the delay before vim writes its swap file (see `:help updatetime`).
 83 | 
 84 | The `signcolumn` option can have any value except `'off'`.
 85 | 
 86 | 
 87 | ### Windows
 88 | 
 89 | There is a potential risk on Windows due to `cmd.exe` prioritising the current folder over folders in `PATH`.  If you have a file named `git.*` (i.e. with any extension in `PATHEXT`) in your current folder, it will be executed instead of git whenever the plugin calls git.
 90 | 
 91 | You can avoid this risk by configuring the full path to your git executable.  For example:
 92 | 
 93 | ```viml
 94 | " This path probably won't work
 95 | let g:gitgutter_git_executable = 'C:\Program Files\Git\bin\git.exe'
 96 | ```
 97 | 
 98 | Unfortunately I don't know the correct escaping for the path - if you do, please let me know!
 99 | 
100 | 
101 | ### Getting started
102 | 
103 | When you make a change to a file tracked by git, the diff markers should appear automatically after a short delay.
104 | 
105 | 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.
106 | 
107 | You cannot unstage a staged hunk.
108 | 
109 | After updating the signs, the plugin fires the `GitGutter` User autocommand.
110 | 
111 | After staging a hunk or part of a hunk, the plugin fires the `GitGutterStage` User autocommand.
112 | 
113 | 
114 | #### Activation
115 | 
116 | You can explicitly turn vim-gitgutter off and on (defaults to on):
117 | 
118 | * turn off with `:GitGutterDisable`
119 | * turn on with `:GitGutterEnable`
120 | * toggle with `:GitGutterToggle`.
121 | 
122 | To toggle vim-gitgutter per buffer:
123 | 
124 | * turn off with `:GitGutterBufferDisable`
125 | * turn on with `:GitGutterBufferEnable`
126 | * toggle with `:GitGutterBufferToggle`
127 | 
128 | You can turn the signs on and off (defaults to on):
129 | 
130 | * turn on with `:GitGutterSignsEnable`
131 | * turn off with `:GitGutterSignsDisable`
132 | * toggle with `:GitGutterSignsToggle`.
133 | 
134 | And you can turn line highlighting on and off (defaults to off):
135 | 
136 | * turn on with `:GitGutterLineHighlightsEnable`
137 | * turn off with `:GitGutterLineHighlightsDisable`
138 | * toggle with `:GitGutterLineHighlightsToggle`.
139 | 
140 | Note that if you have line highlighting on and signs off, you will have an empty sign column – more accurately, a sign column with invisible signs.  This is because line highlighting requires signs and Vim/NeoVim always shows the sign column when there are signs even if the signs are invisible.
141 | 
142 | With Neovim 0.3.2 or higher, you can turn line number highlighting on and off (defaults to off):
143 | 
144 | * turn on with `:GitGutterLineNrHighlightsEnable`
145 | * turn off with `:GitGutterLineNrHighlightsDisable`
146 | * toggle with `:GitGutterLineNrHighlightsToggle`.
147 | 
148 | The same caveat applies to line number highlighting as to line highlighting just above.
149 | 
150 | If you switch off both line highlighting and signs, you won't see the sign column.
151 | 
152 | In older Vims (pre 8.1.0614 / Neovim 0.4.0) vim-gitgutter will suppress the signs when a file has more than 500 changes, to avoid slowing down the UI.  As soon as the number of changes falls below the limit vim-gitgutter will show the signs again.  You can configure the threshold with:
153 | 
154 | ```viml
155 | let g:gitgutter_max_signs = 500  " default value (Vim < 8.1.0614, Neovim < 0.4.0)
156 | let g:gitgutter_max_signs = -1   " default value (otherwise)
157 | ```
158 | 
159 | You can also remove the limit by setting `g:gitgutter_max_signs = -1`.
160 | 
161 | #### Hunks
162 | 
163 | You can jump between hunks:
164 | 
165 | * jump to next hunk (change): `]c`
166 | * jump to previous hunk (change): `[c`.
167 | 
168 | Both of those take a preceding count.
169 | 
170 | To set your own mappings for these, for example `]h` and `[h`:
171 | 
172 | ```viml
173 | nmap ]h <Plug>(GitGutterNextHunk)
174 | nmap [h <Plug>(GitGutterPrevHunk)
175 | ```
176 | 
177 | When you jump between hunks, a message like `Hunk 4 of 11` is shown on the command line. If you want to turn the message off, you can use:
178 | 
179 | ```viml
180 | let g:gitgutter_show_msg_on_hunk_jumping = 0
181 | ```
182 | 
183 | You can load all your hunks into the quickfix list with `:GitGutterQuickFix`.  Note this ignores any unsaved changes in your buffers. If the option `g:gitgutter_use_location_list` is set, this command will load hunks into the current window's location list instead.  Use `:copen` (or `:lopen`) to open the quickfix / location list or add a custom command like this:
184 | 
185 | ```viml
186 | command! Gqf GitGutterQuickFix | copen
187 | ```
188 | 
189 | You can stage or undo an individual hunk when your cursor is in it:
190 | 
191 | * stage the hunk with `<Leader>hs` or
192 | * undo it with `<Leader>hu`.
193 | 
194 | To stage part of an additions-only hunk by:
195 | 
196 | * either visually selecting the part you want and staging with your mapping, e.g. `<Leader>hs`;
197 | * or using a range with the `GitGutterStageHunk` command, e.g. `:42,45GitGutterStageHunk`.
198 | 
199 | To stage part of any hunk:
200 | 
201 | * preview the hunk, e.g. `<Leader>hp`;
202 | * move to the preview window, e.g. `:wincmd P`;
203 | * delete the lines you do not want to stage;
204 | * stage the remaining lines: either write (`:w`) the window or stage via `<Leader>hs` or `:GitGutterStageHunk`.
205 | 
206 | Note the above workflow is not possible if you have opted in to preview hunks with Vim's popup windows.
207 | 
208 | See the FAQ if you want to unstage staged changes.
209 | 
210 | The `.` command will work with both these if you install [repeat.vim](https://github.com/tpope/vim-repeat).
211 | 
212 | To set your own mappings for these, for example if you prefer `g`-based maps:
213 | 
214 | ```viml
215 | nmap ghs <Plug>(GitGutterStageHunk)
216 | nmap ghu <Plug>(GitGutterUndoHunk)
217 | ```
218 | 
219 | And you can preview a hunk's changes with `<Leader>hp`.  The location of the preview window is configured with `g:gitgutter_preview_win_location` (default `'bo'`).  You can of course change this mapping, e.g:
220 | 
221 | ```viml
222 | nmap ghp <Plug>(GitGutterPreviewHunk)
223 | ```
224 | 
225 | A hunk text object is provided which works in visual and operator-pending modes.
226 | 
227 | - `ic` operates on all lines in the current hunk.
228 | - `ac` operates on all lines in the current hunk and any trailing empty lines.
229 | 
230 | To re-map these, for example to `ih` and `ah`:
231 | 
232 | ```viml
233 | omap ih <Plug>(GitGutterTextObjectInnerPending)
234 | omap ah <Plug>(GitGutterTextObjectOuterPending)
235 | xmap ih <Plug>(GitGutterTextObjectInnerVisual)
236 | xmap ah <Plug>(GitGutterTextObjectOuterVisual)
237 | ```
238 | 
239 | If you don't want vim-gitgutter to set up any mappings at all, use this:
240 | 
241 | ```viml
242 | let g:gitgutter_map_keys = 0
243 | ```
244 | 
245 | Finally, you can force vim-gitgutter to update its signs across all visible buffers with `:GitGutterAll`.
246 | 
247 | See the customisation section below for how to change the defaults.
248 | 
249 | 
250 | ### Vimdiff
251 | 
252 | Use the `GitGutterDiffOrig` command to open a vimdiff view of the current buffer, respecting `g:gitgutter_diff_relative_to` and `:gitgutter_diff_base`.
253 | 
254 | 
255 | ### Folding
256 | 
257 | Use the `GitGutterFold` command to fold all unchanged lines, leaving just the hunks visible.  Use `zr` to unfold 3 lines of context above and below a hunk.
258 | 
259 | Execute `GitGutterFold` a second time to restore the previous view.
260 | 
261 | Use `gitgutter#fold#foldtext()` to augment the default `foldtext()` with an indicator of whether the folded lines have been changed.
262 | 
263 | ```viml
264 | set foldtext=gitgutter#fold#foldtext()
265 | ```
266 | 
267 | For a closed fold with changed lines:
268 | 
269 | ```
270 | Default foldtext():         +-- 45 lines: abcdef
271 | gitgutter#fold#foldtext():  +-- 45 lines (*): abcdef
272 | ```
273 | 
274 | You can use `gitgutter#fold#is_changed()` in your own `foldtext` expression to find out whether the folded lines have been changed.
275 | 
276 | 
277 | ### Status line
278 | 
279 | Call the `GitGutterGetHunkSummary()` function from your status line to get a list of counts of added, modified, and removed lines in the current buffer.  For example:
280 | 
281 | ```viml
282 | " Your vimrc
283 | function! GitStatus()
284 |   let [a,m,r] = GitGutterGetHunkSummary()
285 |   return printf('+%d ~%d -%d', a, m, r)
286 | endfunction
287 | set statusline+=%{GitStatus()}
288 | ```
289 | 
290 | 
291 | ### Customisation
292 | 
293 | You can customise:
294 | 
295 | * The sign column's colours
296 | * Whether or not the sign column is shown when there aren't any signs (defaults to no)
297 | * How to handle non-gitgutter signs
298 | * The signs' colours and symbols
299 | * Line highlights
300 | * Line number highlights (only in Neovim 0.3.2 or higher)
301 | * The diff syntax colours used in the preview window
302 | * The intra-line diff highlights used in the preview window
303 | * Whether the diff is relative to the index (default) or working tree.
304 | * The base of the diff
305 | * Extra arguments for `git` when running `git diff`
306 | * Extra arguments for `git diff`
307 | * Key mappings
308 | * Whether vim-gitgutter is on initially (defaults to on)
309 | * Whether signs are shown (defaults to yes)
310 | * Whether line highlighting is on initially (defaults to off)
311 | * Whether line number highlighting is on initially (defaults to off)
312 | * Whether vim-gitgutter runs asynchronously (defaults to yes)
313 | * Whether to clobber or preserve non-gitgutter signs
314 | * The priority of gitgutter's signs.
315 | * Whether to use a floating/popup window for hunk previews
316 | * The appearance of a floating/popup window for hunk previews
317 | * Whether to populate the quickfix list or a location list with all hunks
318 | 
319 | Please note that vim-gitgutter won't override any colours or highlights you've set in your colorscheme.
320 | 
321 | 
322 | #### Sign column
323 | 
324 | Set the `SignColumn` highlight group to change the sign column's colour.  For example:
325 | 
326 | ```viml
327 | " vim-gitgutter used to do this by default:
328 | highlight! link SignColumn LineNr
329 | 
330 | " or you could do this:
331 | highlight SignColumn guibg=whatever ctermbg=whatever
332 | ```
333 | 
334 | By default the sign column will appear when there are signs to show and disappear when there aren't.  To always have the sign column, add to your vimrc:
335 | 
336 | ```viml
337 | " Vim 7.4.2201
338 | set signcolumn=yes
339 | ```
340 | 
341 | GitGutter can preserve or ignore non-gitgutter signs.  For Vim v8.1.0614 and later you can set gitgutter's signs' priorities with `g:gitgutter_sign_priority`, so gitgutter defaults to clobbering other signs.  For Neovim v0.4.0 and later you can set an expanding sign column so gitgutter again defaults to clobbering other signs.  Otherwise, gitgutter defaults to preserving other signs.  You can configure this with:
342 | 
343 | ```viml
344 | let g:gitgutter_sign_allow_clobber = 1
345 | ```
346 | 
347 | 
348 | #### Signs' colours and symbols
349 | 
350 | If you or your colourscheme has defined `GitGutter*` highlight groups, the plugin will use them for the signs' colours.
351 | 
352 | If you want the background colours to match the sign column, but don't want to update the `GitGutter*` groups yourself, you can get the plugin to do it:
353 | 
354 | ```viml
355 | let g:gitgutter_set_sign_backgrounds = 1
356 | ```
357 | 
358 | If no `GitGutter*` highlight groups exist, the plugin will check the `Diff*` highlight groups.  If their foreground colours differ the plugin will use them; if not, these colours will be used:
359 | 
360 | ```viml
361 | highlight GitGutterAdd    guifg=#009900 ctermfg=2
362 | highlight GitGutterChange guifg=#bbbb00 ctermfg=3
363 | highlight GitGutterDelete guifg=#ff2222 ctermfg=1
364 | ```
365 | 
366 | To customise the symbols, add the following to your `~/.vimrc`:
367 | 
368 | ```viml
369 | let g:gitgutter_sign_added = 'xx'
370 | let g:gitgutter_sign_modified = 'yy'
371 | let g:gitgutter_sign_removed = 'zz'
372 | let g:gitgutter_sign_removed_first_line = '^^'
373 | let g:gitgutter_sign_removed_above_and_below = '{'
374 | let g:gitgutter_sign_modified_removed = 'ww'
375 | ```
376 | 
377 | 
378 | #### Line highlights
379 | 
380 | Similarly to the signs' colours, set up the following highlight groups in your colorscheme or `~/.vimrc`:
381 | 
382 | ```viml
383 | GitGutterAddLine          " default: links to DiffAdd
384 | GitGutterChangeLine       " default: links to DiffChange
385 | GitGutterDeleteLine       " default: links to DiffDelete
386 | GitGutterChangeDeleteLine " default: links to GitGutterChangeLine, i.e. DiffChange
387 | ```
388 | 
389 | For example, in some colorschemes the `DiffText` highlight group is easier to read than `DiffChange`.  You could use it like this:
390 | 
391 | ```viml
392 | highlight link GitGutterChangeLine DiffText
393 | ```
394 | 
395 | 
396 | #### Line number highlights
397 | 
398 | NOTE: This feature requires Neovim 0.3.2 or higher.
399 | 
400 | Similarly to the signs' colours, set up the following highlight groups in your colorscheme or `~/.vimrc`:
401 | 
402 | ```viml
403 | GitGutterAddLineNr          " default: links to CursorLineNr
404 | GitGutterChangeLineNr       " default: links to CursorLineNr
405 | GitGutterDeleteLineNr       " default: links to CursorLineNr
406 | GitGutterChangeDeleteLineNr " default: links to GitGutterChangeLineNr
407 | ```
408 | 
409 | Maybe you think `CursorLineNr` is a bit annoying.  For example, you could use `Underlined` for this:
410 | 
411 | ```viml
412 | highlight link GitGutterChangeLineNr Underlined
413 | ```
414 | 
415 | 
416 | #### The diff syntax colours used in the preview window
417 | 
418 | To change the diff syntax colours used in the preview window, set up the `diff*` highlight groups in your colorscheme or `~/.vimrc`:
419 | 
420 | ```viml
421 | diffAdded   " if not set: use GitGutterAdd's foreground colour
422 | diffChanged " if not set: use GitGutterChange's foreground colour
423 | diffRemoved " if not set: use GitGutterDelete's foreground colour
424 | ```
425 | 
426 | Note the `diff*` highlight groups are used in any buffer whose `'syntax'` is `diff`.
427 | 
428 | 
429 | #### The intra-line diff highlights used in the preview window
430 | 
431 | To change the intra-line diff highlights used in the preview window, set up the following highlight groups in your colorscheme or `~/.vimrc`:
432 | 
433 | ```viml
434 | GitGutterAddIntraLine    " default: gui=reverse cterm=reverse
435 | GitGutterDeleteIntraLine " default: gui=reverse cterm=reverse
436 | ```
437 | 
438 | For example, to use `DiffAdd` for intra-line added regions:
439 | 
440 | ```viml
441 | highlight link GitGutterAddIntraLine DiffAdd
442 | ```
443 | 
444 | 
445 | #### Whether the diff is relative to the index or working tree
446 | 
447 | By default diffs are relative to the index.  How you can make them relative to the working tree:
448 | 
449 | ```viml
450 | let g:gitgutter_diff_relative_to = 'working_tree'
451 | ```
452 | 
453 | 
454 | #### The base of the diff
455 | 
456 | By default buffers are diffed against the index.  However you can diff against any commit by setting:
457 | 
458 | ```viml
459 | let g:gitgutter_diff_base = '<commit SHA>'
460 | ```
461 | 
462 | If you are looking at a previous version of a file with Fugitive (e.g. via `:0Gclog`), gitgutter sets the diff base to the parent of the current revision.
463 | 
464 | This setting is ignored when the diffs are relative to the working tree.
465 | 
466 | 
467 | #### Extra arguments for `git` when running `git diff`
468 | 
469 | If you want to pass extra arguments to `git` when running `git diff`, do so like this:
470 | 
471 | ```viml
472 | let g:gitgutter_git_args = '--git-dir-""'
473 | ```
474 | 
475 | #### Extra arguments for `git diff`
476 | 
477 | If you want to pass extra arguments to `git diff`, for example to ignore whitespace, do so like this:
478 | 
479 | ```viml
480 | let g:gitgutter_diff_args = '-w'
481 | ```
482 | 
483 | #### Key mappings
484 | 
485 | To disable all key mappings:
486 | 
487 | ```viml
488 | let g:gitgutter_map_keys = 0
489 | ```
490 | 
491 | See above for configuring maps for hunk-jumping and staging/undoing.
492 | 
493 | 
494 | #### Use a custom `grep` command
495 | 
496 | If you use an alternative to grep, you can tell vim-gitgutter to use it here.
497 | 
498 | ```viml
499 | " Default:
500 | let g:gitgutter_grep = 'grep'
501 | ```
502 | 
503 | #### To turn off vim-gitgutter by default
504 | 
505 | Add `let g:gitgutter_enabled = 0` to your `~/.vimrc`.
506 | 
507 | 
508 | #### To turn off signs by default
509 | 
510 | Add `let g:gitgutter_signs = 0` to your `~/.vimrc`.
511 | 
512 | 
513 | #### To turn on line highlighting by default
514 | 
515 | Add `let g:gitgutter_highlight_lines = 1` to your `~/.vimrc`.
516 | 
517 | 
518 | #### To turn on line number highlighting by default
519 | 
520 | Add `let g:gitgutter_highlight_linenrs = 1` to your `~/.vimrc`.
521 | 
522 | 
523 | #### To turn off asynchronous updates
524 | 
525 | By default diffs are run asynchronously.  To run diffs synchronously instead:
526 | 
527 | ```viml
528 | let g:gitgutter_async = 0
529 | ```
530 | 
531 | 
532 | #### To use floating/popup windows for hunk previews
533 | 
534 | Add `let g:gitgutter_preview_win_floating = 1` to your `~/.vimrc`.  Note that on Vim this prevents you staging (partial) hunks via the preview window.
535 | 
536 | On Neovim, the preview hunk command will move the cursor into the floating window if it is already open.
537 | 
538 | 
539 | #### The appearance of a floating/popup window for hunk previews
540 | 
541 | Either set `g:gitgutter_floating_window_options` to a dictionary of the options you want.  This dictionary is passed directly to `popup_create()` (Vim) / `nvim_open_win()` (Neovim).
542 | 
543 | Or if you just want to override one or two of the defaults, you can do that with a file in an `after/` directory.  For example:
544 | 
545 | ```viml
546 | " ~/.vim/after/vim-gitgutter/overrides.vim
547 | let g:gitgutter_floating_window_options['border'] = 'single'
548 | ```
549 | 
550 | 
551 | #### To load all hunks into the current window's location list instead of the quickfix list
552 | 
553 | Add `let g:gitgutter_use_location_list = 1` to your `~/.vimrc`.
554 | 
555 | 
556 | ### Extensions
557 | 
558 | #### Operate on every line in a hunk
559 | 
560 | You can map an operator to do whatever you want to every line in a hunk.
561 | 
562 | Let's say, for example, you want to remove trailing whitespace.
563 | 
564 | ```viml
565 | function! CleanUp(...)
566 |   if a:0  " opfunc
567 |     let [first, last] = [line("'["), line("']")]
568 |   else
569 |     let [first, last] = [line("'<"), line("'>")]
570 |   endif
571 |   for lnum in range(first, last)
572 |     let line = getline(lnum)
573 | 
574 |     " clean up the text, e.g.:
575 |     let line = substitute(line, '\s\+
#39;, '', '')
576 | 
577 |     call setline(lnum, line)
578 |   endfor
579 | endfunction
580 | 
581 | nmap <silent> <Leader>x :set opfunc=CleanUp<CR>g@
582 | ```
583 | 
584 | Then place your cursor in a hunk and type `\xic` (assuming a leader of `\`).
585 | 
586 | Alternatively you could place your cursor in a hunk, type `vic` to select it, then `:call CleanUp()`.
587 | 
588 | 
589 | #### Operate on every changed line in a file
590 | 
591 | You can write a command to do whatever you want to every changed line in a file.
592 | 
593 | ```viml
594 | function! GlobalChangedLines(ex_cmd)
595 |   for hunk in GitGutterGetHunks()
596 |     for lnum in range(hunk[2], hunk[2]+hunk[3]-1)
597 |       let cursor = getcurpos()
598 |       silent! execute lnum.a:ex_cmd
599 |       call setpos('.', cursor)
600 |     endfor
601 |   endfor
602 | endfunction
603 | 
604 | command -nargs=1 Glines call GlobalChangedLines(<q-args>)
605 | ```
606 | 
607 | Let's say, for example, you want to remove trailing whitespace from all changed lines:
608 | 
609 | ```viml
610 | :Glines s/\s\+$//
611 | ```
612 | 
613 | 
614 | #### Cycle through hunks in current buffer
615 | 
616 | This is like `:GitGutterNextHunk` but when it gets to the last hunk in the buffer it cycles around to the first.
617 | 
618 | ```viml
619 | function! GitGutterNextHunkCycle()
620 |   let line = line('.')
621 |   silent! GitGutterNextHunk
622 |   if line('.') == line
623 |     1
624 |     GitGutterNextHunk
625 |   endif
626 | endfunction
627 | ```
628 | 
629 | 
630 | #### Cycle through hunks in all buffers
631 | 
632 | You can use `:GitGutterQuickFix` to load all hunks into the quickfix list or the current window's location list.
633 | 
634 | Alternatively, given that`]c` and `[c` jump from one hunk to the next in the current buffer, you can use this code to jump to the next hunk no matter which buffer it's in.
635 | 
636 | ```viml
637 | function! NextHunkAllBuffers()
638 |   let line = line('.')
639 |   GitGutterNextHunk
640 |   if line('.') != line
641 |     return
642 |   endif
643 | 
644 |   let bufnr = bufnr('')
645 |   while 1
646 |     bnext
647 |     if bufnr('') == bufnr
648 |       return
649 |     endif
650 |     if !empty(GitGutterGetHunks())
651 |       1
652 |       GitGutterNextHunk
653 |       return
654 |     endif
655 |   endwhile
656 | endfunction
657 | 
658 | function! PrevHunkAllBuffers()
659 |   let line = line('.')
660 |   GitGutterPrevHunk
661 |   if line('.') != line
662 |     return
663 |   endif
664 | 
665 |   let bufnr = bufnr('')
666 |   while 1
667 |     bprevious
668 |     if bufnr('') == bufnr
669 |       return
670 |     endif
671 |     if !empty(GitGutterGetHunks())
672 |       normal! G
673 |       GitGutterPrevHunk
674 |       return
675 |     endif
676 |   endwhile
677 | endfunction
678 | 
679 | nmap <silent> ]c :call NextHunkAllBuffers()<CR>
680 | nmap <silent> [c :call PrevHunkAllBuffers()<CR>
681 | ```
682 | 
683 | 
684 | ### FAQ
685 | 
686 | > How can I turn off realtime updates?
687 | 
688 | Add this to your vim configuration (in an `/after/plugin` directory):
689 | 
690 | ```viml
691 | " .vim/after/plugin/gitgutter.vim
692 | autocmd! gitgutter CursorHold,CursorHoldI
693 | ```
694 | 
695 | > I turned off realtime updates, how can I have signs updated when I save a file?
696 | 
697 | If you really want to update the signs when you save a file, add this to your vimrc:
698 | 
699 | ```viml
700 | autocmd BufWritePost * GitGutter
701 | ```
702 | 
703 | > Why can't I unstage staged changes?
704 | 
705 | This plugin is for showing changes between the buffer and the index (and staging/undoing those changes).  Unstaging a staged hunk would require showing changes between the index and HEAD, which is out of scope.
706 | 
707 | > Why are the colours in the sign column weird?
708 | 
709 | Your colorscheme is configuring the `SignColumn` highlight group weirdly.  Please see the section above on customising the sign column.
710 | 
711 | > What happens if I also use another plugin which uses signs (e.g. Syntastic)?
712 | 
713 | You can configure whether GitGutter preserves or clobbers other signs using `g:gitgutter_sign_allow_clobber`.  Set to `1` to clobber other signs (default on Vim >= 8.1.0614 and NeoVim >= 0.4.0) or `0` to preserve them.
714 | 
715 | 
716 | ### Troubleshooting
717 | 
718 | #### When no signs are showing at all
719 | 
720 | Here are some things you can check:
721 | 
722 | * 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.
723 | * Verify `:echo system("git --version")` succeeds.
724 | * Verify your git config is compatible with the version of git returned by the command above.
725 | * Verify your Vim supports signs (`:echo has('signs')` should give `1`).
726 | * Verify your file is being tracked by git and has unstaged changes.  Check whether the plugin thinks git knows about your file: `:echo b:gitgutter.path` should show the path to the file in the repo.
727 | * Execute `:sign place group=gitgutter`; you should see a list of signs.
728 |   - If the signs are listed: this is a colorscheme / highlight problem.  Compare `:highlight GitGutterAdd` with `:highlight SignColumn`.
729 |   - If no signs are listed: the call to git-diff is probably failing.  Add `let g:gitgutter_log=1` to your vimrc, restart, reproduce the problem, and look at the `gitgutter.log` file in the plugin's directory.
730 | 
731 | #### When the whole file is marked as added
732 | 
733 | * If you use zsh, and you set `CDPATH`, make sure `CDPATH` doesn't include the current directory.
734 | 
735 | #### When signs take a few seconds to appear
736 | 
737 | * Try reducing `updatetime`, e.g. `set updatetime=100`.  Note this also controls the delay before vim writes its swap file.
738 | 
739 | #### When signs don't update after focusing Vim
740 | 
741 | * Your terminal probably isn't reporting focus events.  Either try installing [Terminus][] or set `let g:gitgutter_terminal_reports_focus=0`.  For tmux, try `set -g focus-events on` in your tmux.conf.
742 | 
743 | 
744 | ### Shameless Plug
745 | 
746 | If this plugin has helped you, or you'd like to learn more about Vim, why not check out this screencast I wrote for PeepCode:
747 | 
748 | * [Smash Into Vim][siv]
749 | 
750 | This was one of PeepCode's all-time top three bestsellers and is now available at Pluralsight.
751 | 
752 | 
753 | ### Intellectual Property
754 | 
755 | Copyright Andrew Stewart, AirBlade Software Ltd.  Released under the MIT licence.
756 | 
757 | 
758 |   [pathogen]: https://github.com/tpope/vim-pathogen
759 |   [siv]: http://pluralsight.com/training/Courses/TableOfContents/smash-into-vim
760 |   [terminus]: https://github.com/wincent/terminus
761 | 


--------------------------------------------------------------------------------
/autoload/gitgutter.vim:
--------------------------------------------------------------------------------
  1 | " Primary functions {{{
  2 | 
  3 | function! gitgutter#all(force) abort
  4 |   let visible = tabpagebuflist()
  5 | 
  6 |   for bufnr in range(1, bufnr('
#39;) + 1)
  7 |     if buflisted(bufnr)
  8 |       let file = expand('#'.bufnr.':p')
  9 |       if !empty(file)
 10 |         if index(visible, bufnr) != -1
 11 |           call gitgutter#process_buffer(bufnr, a:force)
 12 |         elseif a:force
 13 |           call s:reset_tick(bufnr)
 14 |         endif
 15 |       endif
 16 |     endif
 17 |   endfor
 18 | endfunction
 19 | 
 20 | 
 21 | function! gitgutter#process_buffer(bufnr, force) abort
 22 |   " NOTE a:bufnr is not necessarily the current buffer.
 23 | 
 24 |   if gitgutter#utility#getbufvar(a:bufnr, 'enabled', -1) == -1
 25 |     call gitgutter#utility#setbufvar(a:bufnr, 'enabled', g:gitgutter_enabled)
 26 |   endif
 27 | 
 28 |   if gitgutter#utility#is_active(a:bufnr)
 29 | 
 30 |     if has('patch-7.4.1559')
 31 |       let l:Callback = function('gitgutter#process_buffer', [a:bufnr, a:force])
 32 |     else
 33 |       let l:Callback = {'function': 'gitgutter#process_buffer', 'arguments': [a:bufnr, a:force]}
 34 |     endif
 35 |     let how = s:setup_path(a:bufnr, l:Callback)
 36 |     if [how] == ['async']  " avoid string-to-number conversion if how is a number
 37 |       return
 38 |     endif
 39 | 
 40 |     if a:force || s:has_fresh_changes(a:bufnr)
 41 | 
 42 |       let diff = 'NOT SET'
 43 |       try
 44 |         let diff = gitgutter#diff#run_diff(a:bufnr, g:gitgutter_diff_relative_to, 0)
 45 |       catch /gitgutter not tracked/
 46 |         call gitgutter#debug#log('Not tracked: '.gitgutter#utility#file(a:bufnr))
 47 |       catch /gitgutter assume unchanged/
 48 |         call gitgutter#debug#log('Assume unchanged: '.gitgutter#utility#file(a:bufnr))
 49 |       catch /gitgutter diff failed/
 50 |         call gitgutter#debug#log('Diff failed: '.gitgutter#utility#file(a:bufnr))
 51 |         call gitgutter#hunk#reset(a:bufnr)
 52 |       endtry
 53 | 
 54 |       if diff != 'async' && diff != 'NOT SET'
 55 |         call gitgutter#diff#handler(a:bufnr, diff)
 56 |       endif
 57 | 
 58 |     endif
 59 |   endif
 60 | endfunction
 61 | 
 62 | 
 63 | function! gitgutter#disable() abort
 64 |   call s:toggle_each_buffer(0)
 65 |   let g:gitgutter_enabled = 0
 66 | endfunction
 67 | 
 68 | function! gitgutter#enable() abort
 69 |   call s:toggle_each_buffer(1)
 70 |   let g:gitgutter_enabled = 1
 71 | endfunction
 72 | 
 73 | function s:toggle_each_buffer(enable)
 74 |   for bufnr in range(1, bufnr('
#39;) + 1)
 75 |     if buflisted(bufnr)
 76 |       let file = expand('#'.bufnr.':p')
 77 |       if !empty(file)
 78 |         if a:enable
 79 |           call gitgutter#buffer_enable(bufnr)
 80 |         else
 81 |           call gitgutter#buffer_disable(bufnr)
 82 |         end
 83 |       endif
 84 |     endif
 85 |   endfor
 86 | endfunction
 87 | 
 88 | function! gitgutter#toggle() abort
 89 |   if g:gitgutter_enabled
 90 |     call gitgutter#disable()
 91 |   else
 92 |     call gitgutter#enable()
 93 |   endif
 94 | endfunction
 95 | 
 96 | 
 97 | function! gitgutter#buffer_disable(...) abort
 98 |   let bufnr = a:0 ? a:1 : bufnr('')
 99 |   call gitgutter#utility#setbufvar(bufnr, 'enabled', 0)
100 |   call s:clear(bufnr)
101 | endfunction
102 | 
103 | function! gitgutter#buffer_enable(...) abort
104 |   let bufnr = a:0 ? a:1 : bufnr('')
105 |   call gitgutter#utility#setbufvar(bufnr, 'enabled', 1)
106 |   call gitgutter#process_buffer(bufnr, 1)
107 | endfunction
108 | 
109 | function! gitgutter#buffer_toggle(...) abort
110 |   let bufnr = a:0 ? a:1 : bufnr('')
111 |   if gitgutter#utility#getbufvar(bufnr, 'enabled', 1)
112 |     call gitgutter#buffer_disable(bufnr)
113 |   else
114 |     call gitgutter#buffer_enable(bufnr)
115 |   endif
116 | endfunction
117 | 
118 | " }}}
119 | 
120 | 
121 | " Optional argument is buffer number
122 | function! gitgutter#git(...)
123 |   let git = g:gitgutter_git_executable
124 |   if a:0
125 |     let git .= ' -C '.gitgutter#utility#dir(a:1)
126 |   endif
127 |   if empty(g:gitgutter_git_args)
128 |     return git
129 |   else
130 |     return git.' '.g:gitgutter_git_args
131 |   endif
132 | endfunction
133 | 
134 | 
135 | function! gitgutter#setup_maps()
136 |   if !g:gitgutter_map_keys
137 |     return
138 |   endif
139 | 
140 |   " Note hasmapto() and maparg() operate on the current buffer.
141 | 
142 |   let bufnr = bufnr('')
143 | 
144 |   if gitgutter#utility#getbufvar(bufnr, 'mapped', 0)
145 |     return
146 |   endif
147 | 
148 |   if !hasmapto('<Plug>(GitGutterPrevHunk)') && maparg('[c', 'n') ==# ''
149 |     nmap <buffer> [c <Plug>(GitGutterPrevHunk)
150 |   endif
151 |   if !hasmapto('<Plug>(GitGutterNextHunk)') && maparg(']c', 'n') ==# ''
152 |     nmap <buffer> ]c <Plug>(GitGutterNextHunk)
153 |   endif
154 | 
155 |   if !hasmapto('<Plug>(GitGutterStageHunk)', 'v') && maparg('<Leader>hs', 'x') ==# ''
156 |     xmap <buffer> <Leader>hs <Plug>(GitGutterStageHunk)
157 |   endif
158 |   if !hasmapto('<Plug>(GitGutterStageHunk)', 'n') && maparg('<Leader>hs', 'n') ==# ''
159 |     nmap <buffer> <Leader>hs <Plug>(GitGutterStageHunk)
160 |   endif
161 |   if !hasmapto('<Plug>(GitGutterUndoHunk)') && maparg('<Leader>hu', 'n') ==# ''
162 |     nmap <buffer> <Leader>hu <Plug>(GitGutterUndoHunk)
163 |   endif
164 |   if !hasmapto('<Plug>(GitGutterPreviewHunk)') && maparg('<Leader>hp', 'n') ==# ''
165 |     nmap <buffer> <Leader>hp <Plug>(GitGutterPreviewHunk)
166 |   endif
167 | 
168 |   if !hasmapto('<Plug>(GitGutterTextObjectInnerPending)') && maparg('ic', 'o') ==# ''
169 |     omap <buffer> ic <Plug>(GitGutterTextObjectInnerPending)
170 |   endif
171 |   if !hasmapto('<Plug>(GitGutterTextObjectOuterPending)') && maparg('ac', 'o') ==# ''
172 |     omap <buffer> ac <Plug>(GitGutterTextObjectOuterPending)
173 |   endif
174 |   if !hasmapto('<Plug>(GitGutterTextObjectInnerVisual)') && maparg('ic', 'x') ==# ''
175 |     xmap <buffer> ic <Plug>(GitGutterTextObjectInnerVisual)
176 |   endif
177 |   if !hasmapto('<Plug>(GitGutterTextObjectOuterVisual)') && maparg('ac', 'x') ==# ''
178 |     xmap <buffer> ac <Plug>(GitGutterTextObjectOuterVisual)
179 |   endif
180 | 
181 |   call gitgutter#utility#setbufvar(bufnr, 'mapped', 1)
182 | endfunction
183 | 
184 | function! s:setup_path(bufnr, continuation)
185 |   if gitgutter#utility#has_repo_path(a:bufnr) | return | endif
186 | 
187 |   return gitgutter#utility#set_repo_path(a:bufnr, a:continuation)
188 | endfunction
189 | 
190 | function! s:has_fresh_changes(bufnr) abort
191 |   return getbufvar(a:bufnr, 'changedtick') != gitgutter#utility#getbufvar(a:bufnr, 'tick')
192 | endfunction
193 | 
194 | function! s:reset_tick(bufnr) abort
195 |   call gitgutter#utility#setbufvar(a:bufnr, 'tick', 0)
196 | endfunction
197 | 
198 | function! s:clear(bufnr)
199 |   call gitgutter#sign#clear_signs(a:bufnr)
200 |   call gitgutter#hunk#reset(a:bufnr)
201 |   call s:reset_tick(a:bufnr)
202 |   call gitgutter#utility#setbufvar(a:bufnr, 'path', '')
203 |   call gitgutter#utility#setbufvar(a:bufnr, 'basepath', '')
204 | endfunction
205 | 
206 | 
207 | " Note:
208 | " - this runs synchronously
209 | " - it ignores unsaved changes in buffers
210 | " - it does not change to the repo root
211 | function! gitgutter#quickfix(current_file)
212 |   let cmd = gitgutter#git().' rev-parse --show-cdup'
213 |   let path_to_repo = get(systemlist(cmd), 0, '')
214 |   if !empty(path_to_repo) && path_to_repo[-1:] != '/'
215 |     let path_to_repo .= '/'
216 |   endif
217 | 
218 |   let locations = []
219 |   let cmd = gitgutter#git().' --no-pager'.
220 |         \ ' diff --no-ext-diff --no-color -U0'.
221 |         \ ' --src-prefix=a/'.path_to_repo.' --dst-prefix=b/'.path_to_repo.' '.
222 |         \ g:gitgutter_diff_args. ' '. g:gitgutter_diff_base
223 |   if a:current_file
224 |     let cmd = cmd.' -- '.expand('%:p')
225 |   endif
226 |   let diff = systemlist(cmd)
227 |   let lnum = 0
228 |   for line in diff
229 |     if line =~ '^diff --git [^"]'
230 |       " No quotation mark therefore no spaces in filenames
231 |       let [fnamel, fnamer] = split(line)[2:3]
232 |       let fname = fnamel ==# fnamer ? fnamer : fnamer[2:]
233 |     elseif line =~ '^diff --git "'
234 |       " Quotation mark therefore do not split on space
235 |       let [_, fnamel, _, fnamer] = split(line, '"')
236 |       let fname = fnamel ==# fnamer ? fnamer : fnamer[2:]
237 |     elseif line =~ '^diff --cc [^"]'
238 |       let fname = line[10:]
239 |     elseif line =~ '^diff --cc "'
240 |       let [_, fname] = split(line, '"')
241 |     elseif line =~ '^@@'
242 |       let lnum = matchlist(line, '+\(\d\+\)')[1]
243 |     elseif lnum > 0
244 |       call add(locations, {'filename': fname, 'lnum': lnum, 'text': line})
245 |       let lnum = 0
246 |     endif
247 |   endfor
248 |   if !g:gitgutter_use_location_list
249 |     call setqflist(locations)
250 |   else
251 |     call setloclist(0, locations)
252 |   endif
253 | endfunction
254 | 
255 | 
256 | function! gitgutter#difforig()
257 |   let bufnr = bufnr('')
258 |   let filetype = &filetype
259 | 
260 |   vertical new
261 |   set buftype=nofile
262 |   let &filetype = filetype
263 | 
264 |   if g:gitgutter_diff_relative_to ==# 'index'
265 |     let index_name = gitgutter#utility#get_diff_base(bufnr).':'.gitgutter#utility#base_path(bufnr)
266 |     let cmd = gitgutter#git(bufnr).' --no-pager show '.index_name
267 |     " NOTE: this uses &shell to execute cmd.  Perhaps we should use instead
268 |     " gitgutter#utility's use_known_shell() / restore_shell() functions.
269 |     silent! execute "read ++edit !" cmd
270 |   else
271 |     silent! execute "read ++edit" gitgutter#utility#repo_path(bufnr, 1)
272 |   endif
273 | 
274 |   0d_
275 |   diffthis
276 |   wincmd p
277 |   diffthis
278 | endfunction
279 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/async.vim:
--------------------------------------------------------------------------------
  1 | let s:available = has('nvim') || (
  2 |       \   has('job') && (
  3 |       \     (has('patch-7.4.1826') && !has('gui_running')) ||
  4 |       \     (has('patch-7.4.1850') &&  has('gui_running')) ||
  5 |       \     (has('patch-7.4.1832') &&  has('gui_macvim'))
  6 |       \   )
  7 |       \ )
  8 | 
  9 | let s:jobs = {}
 10 | 
 11 | function! gitgutter#async#available()
 12 |   return s:available
 13 | endfunction
 14 | 
 15 | 
 16 | function! gitgutter#async#execute(cmd, bufnr, handler) abort
 17 |   call gitgutter#debug#log('[async] '.a:cmd)
 18 | 
 19 |   let options = {
 20 |         \   'stdoutbuffer': [],
 21 |         \   'buffer': a:bufnr,
 22 |         \   'handler': a:handler
 23 |         \ }
 24 |   let command = s:build_command(a:cmd)
 25 | 
 26 |   if has('nvim')
 27 |     call jobstart(command, extend(options, {
 28 |           \   'on_stdout': function('s:on_stdout_nvim'),
 29 |           \   'on_stderr': function('s:on_stderr_nvim'),
 30 |           \   'on_exit':   function('s:on_exit_nvim')
 31 |           \ }))
 32 |   else
 33 |     let job = job_start(command, {
 34 |           \   'out_cb':   function('s:on_stdout_vim', options),
 35 |           \   'err_cb':   function('s:on_stderr_vim', options),
 36 |           \   'close_cb': function('s:on_exit_vim', options)
 37 |           \ })
 38 |     let s:jobs[s:job_id(job)] = 1
 39 |   endif
 40 | endfunction
 41 | 
 42 | 
 43 | function! s:build_command(cmd)
 44 |   if has('unix')
 45 |     return ['sh', '-c', a:cmd]
 46 |   endif
 47 | 
 48 |   if has('win32')
 49 |     return has('nvim') ? a:cmd : 'cmd.exe /c '.a:cmd
 50 |   endif
 51 | 
 52 |   throw 'unknown os'
 53 | endfunction
 54 | 
 55 | 
 56 | function! s:on_stdout_nvim(_job_id, data, _event) dict abort
 57 |   if empty(self.stdoutbuffer)
 58 |     let self.stdoutbuffer = a:data
 59 |   else
 60 |     let self.stdoutbuffer = self.stdoutbuffer[:-2] +
 61 |           \ [self.stdoutbuffer[-1] . a:data[0]] +
 62 |           \ a:data[1:]
 63 |   endif
 64 | endfunction
 65 | 
 66 | function! s:on_stderr_nvim(_job_id, data, _event) dict abort
 67 |   if a:data != ['']  " With Neovim there is always [''] reported on stderr.
 68 |     call self.handler.err(self.buffer)
 69 |   endif
 70 | endfunction
 71 | 
 72 | function! s:on_exit_nvim(_job_id, exit_code, _event) dict abort
 73 |   if !a:exit_code
 74 |     call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
 75 |   endif
 76 | endfunction
 77 | 
 78 | 
 79 | function! s:on_stdout_vim(_channel, data) dict abort
 80 |   call add(self.stdoutbuffer, a:data)
 81 | endfunction
 82 | 
 83 | function! s:on_stderr_vim(channel, _data) dict abort
 84 |   call self.handler.err(self.buffer)
 85 | endfunction
 86 | 
 87 | function! s:on_exit_vim(channel) dict abort
 88 |   let job = ch_getjob(a:channel)
 89 |   let jobid = s:job_id(job)
 90 |   if has_key(s:jobs, jobid) | unlet s:jobs[jobid] | endif
 91 |   while 1
 92 |     if job_status(job) == 'dead'
 93 |       let exit_code = job_info(job).exitval
 94 |       break
 95 |     endif
 96 |     sleep 5m
 97 |   endwhile
 98 | 
 99 |   if !exit_code
100 |     call self.handler.out(self.buffer, join(self.stdoutbuffer, "\n"))
101 |   endif
102 | endfunction
103 | 
104 | function! s:job_id(job)
105 |   " Vim
106 |   return job_info(a:job).process
107 | endfunction
108 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/debug.vim:
--------------------------------------------------------------------------------
  1 | let s:plugin_dir  = expand('<sfile>:p:h:h:h').'/'
  2 | let s:log_file    = s:plugin_dir.'gitgutter.log'
  3 | let s:channel_log = s:plugin_dir.'channel.log'
  4 | let s:new_log_session = 1
  5 | 
  6 | 
  7 | function! gitgutter#debug#debug()
  8 |   " Open a scratch buffer
  9 |   vsplit __GitGutter_Debug__
 10 |   normal! ggdG
 11 |   setlocal buftype=nofile
 12 |   setlocal bufhidden=delete
 13 |   setlocal noswapfile
 14 | 
 15 |   call s:vim_version()
 16 |   call s:separator()
 17 | 
 18 |   call s:git_version()
 19 |   call s:separator()
 20 | 
 21 |   call s:grep_version()
 22 |   call s:separator()
 23 | 
 24 |   call s:option('updatetime')
 25 | endfunction
 26 | 
 27 | 
 28 | function! s:separator()
 29 |   call s:output('')
 30 | endfunction
 31 | 
 32 | function! s:vim_version()
 33 |   redir => version_info
 34 |     silent execute 'version'
 35 |   redir END
 36 |   call s:output(split(version_info, '\n')[0:2])
 37 | endfunction
 38 | 
 39 | function! s:git_version()
 40 |   let v = system(g:gitgutter_git_executable.' --version')
 41 |   call s:output( substitute(v, '\n
#39;, '', '') )
 42 | endfunction
 43 | 
 44 | function! s:grep_version()
 45 |   let v = system(g:gitgutter_grep.' --version')
 46 |   call s:output( substitute(v, '\n
#39;, '', '') )
 47 | 
 48 |   let v = system(g:gitgutter_grep.' --help')
 49 |   call s:output( substitute(v, '\%x00', '', 'g') )
 50 | endfunction
 51 | 
 52 | function! s:option(name)
 53 |   if exists('+' . a:name)
 54 |     let v = eval('&' . a:name)
 55 |     call s:output(a:name . '=' . v)
 56 |     " redir => output
 57 |     "   silent execute "verbose set " . a:name . "?"
 58 |     " redir END
 59 |     " call s:output(a:name . '=' . output)
 60 |   else
 61 |     call s:output(a:name . ' [n/a]')
 62 |   end
 63 | endfunction
 64 | 
 65 | function! s:output(text)
 66 |   call append(line('
#39;), a:text)
 67 | endfunction
 68 | 
 69 | " assumes optional args are calling function's optional args
 70 | function! gitgutter#debug#log(message, ...) abort
 71 |   if g:gitgutter_log
 72 |     if s:new_log_session && gitgutter#async#available()
 73 |       if exists('*ch_logfile')
 74 |         call ch_logfile(s:channel_log, 'w')
 75 |       endif
 76 |     endif
 77 | 
 78 |     if s:new_log_session
 79 |       let s:start = reltime()
 80 |       call writefile(['', '========== start log session '.strftime('%d.%m.%Y %H:%M:%S').' =========='], s:log_file, 'a')
 81 |     endif
 82 | 
 83 |     let elapsed = reltimestr(reltime(s:start)).' '
 84 |     call writefile([''], s:log_file, 'a')
 85 |     " callers excluding this function
 86 |     call writefile([elapsed.expand('<sfile>')[:-22].':'], s:log_file, 'a')
 87 |     call writefile([elapsed.s:format_for_log(a:message)], s:log_file, 'a')
 88 |     if a:0 && !empty(a:1)
 89 |       for msg in a:000
 90 |         call writefile([elapsed.s:format_for_log(msg)], s:log_file, 'a')
 91 |       endfor
 92 |     endif
 93 | 
 94 |     let s:new_log_session = 0
 95 |   endif
 96 | endfunction
 97 | 
 98 | function! s:format_for_log(data) abort
 99 |   if type(a:data) == 1
100 |     return join(split(a:data,'\n'),"\n")
101 |   elseif type(a:data) == 3
102 |     return '['.join(a:data,"\n").']'
103 |   else
104 |     return a:data
105 |   endif
106 | endfunction
107 | 
108 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/diff.vim:
--------------------------------------------------------------------------------
  1 | scriptencoding utf8
  2 | 
  3 | let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
  4 | 
  5 | let s:hunk_re = '^@@ -\(\d\+\),\?\(\d*\) +\(\d\+\),\?\(\d*\) @@'
  6 | 
  7 | let s:temp_from = tempname()
  8 | let s:temp_buffer = tempname()
  9 | let s:counter = 0
 10 | 
 11 | " Returns a diff of the buffer against the index or the working tree.
 12 | "
 13 | " After running the diff we pass it through grep where available to reduce
 14 | " subsequent processing by the plugin.  If grep is not available the plugin
 15 | " does the filtering instead.
 16 | "
 17 | " When diffing against the index:
 18 | "
 19 | " The buffer contents is not the same as the file on disk so we need to pass
 20 | " two instances of the file to git-diff:
 21 | "
 22 | "     git diff myfileA myfileB
 23 | "
 24 | " where myfileA comes from
 25 | "
 26 | "     git show :myfile > myfileA
 27 | "
 28 | " and myfileB is the buffer contents.
 29 | "
 30 | " Regarding line endings:
 31 | "
 32 | " git-show does not convert line endings.
 33 | " git-diff FILE FILE does convert line endings for the given files.
 34 | "
 35 | " If a file has CRLF line endings and git's core.autocrlf is true,
 36 | " the file in git's object store will have LF line endings.  Writing
 37 | " it out via git-show will produce a file with LF line endings.
 38 | "
 39 | " If this last file is one of the files passed to git-diff, git-diff will
 40 | " convert its line endings to CRLF before diffing -- which is what we want --
 41 | " but also by default output a warning on stderr.
 42 | "
 43 | "   warning: LF will be replace by CRLF in <temp file>.
 44 | "   The file will have its original line endings in your working directory.
 45 | "
 46 | " When running the diff asynchronously, the warning message triggers the stderr
 47 | " callbacks which assume the overall command has failed and reset all the
 48 | " signs.  As this is not what we want, and we can safely ignore the warning,
 49 | " we turn it off by passing the '-c "core.safecrlf=false"' argument to
 50 | " git-diff.
 51 | "
 52 | " When writing the temporary files we preserve the original file's extension
 53 | " so that repos using .gitattributes to control EOL conversion continue to
 54 | " convert correctly.
 55 | "
 56 | " Arguments:
 57 | "
 58 | " bufnr              - the number of the buffer to be diffed
 59 | " from               - 'index' or 'working_tree'; what the buffer is diffed against
 60 | " preserve_full_diff - truthy to return the full diff or falsey to return only
 61 | "                      the hunk headers (@@ -x,y +m,n @@); only possible if
 62 | "                      grep is available.
 63 | function! gitgutter#diff#run_diff(bufnr, from, preserve_full_diff) abort
 64 |   if gitgutter#utility#repo_path(a:bufnr, 0) == -1
 65 |     throw 'gitgutter path not set'
 66 |   endif
 67 | 
 68 |   if gitgutter#utility#repo_path(a:bufnr, 0) == -2
 69 |     throw 'gitgutter not tracked'
 70 |   endif
 71 | 
 72 |   if gitgutter#utility#repo_path(a:bufnr, 0) == -3
 73 |     throw 'gitgutter assume unchanged'
 74 |   endif
 75 | 
 76 |   " Wrap compound commands in parentheses to make Windows happy.
 77 |   " bash doesn't mind the parentheses.
 78 |   let cmd = '('
 79 | 
 80 |   " Append buffer number to temp filenames to avoid race conditions between
 81 |   " writing and reading the files when asynchronously processing multiple
 82 |   " buffers.
 83 | 
 84 |   " Without the buffer number, buff_file would have a race between the
 85 |   " second gitgutter#process_buffer() writing the file (synchronously, below)
 86 |   " and the first gitgutter#process_buffer()'s async job reading it (with
 87 |   " git-diff).
 88 |   let buff_file = s:temp_buffer.'.'.a:bufnr
 89 | 
 90 |   " Add a counter to avoid a similar race with two quick writes of the same buffer.
 91 |   " Use a modulus greater than a maximum reasonable number of visible buffers.
 92 |   let s:counter = (s:counter + 1) % 20
 93 |   let buff_file .= '.'.s:counter
 94 | 
 95 |   let extension = gitgutter#utility#extension(a:bufnr)
 96 |   if !empty(extension)
 97 |     let buff_file .= '.'.extension
 98 |   endif
 99 | 
100 |   " Write buffer to temporary file.
101 |   " Note: this is synchronous.
102 |   call s:write_buffer(a:bufnr, buff_file)
103 | 
104 |   if a:from ==# 'index'
105 |     " Without the buffer number, from_file would have a race in the shell
106 |     " between the second process writing it (with git-show) and the first
107 |     " reading it (with git-diff).
108 |     let from_file = s:temp_from.'.'.a:bufnr
109 | 
110 |     " Add a counter to avoid a similar race with two quick writes of the same buffer.
111 |     let from_file .= '.'.s:counter
112 | 
113 |     if !empty(extension)
114 |       let from_file .= '.'.extension
115 |     endif
116 | 
117 |     " Write file from index to temporary file.
118 |     let index_name = gitgutter#utility#get_diff_base(a:bufnr).':'.gitgutter#utility#base_path(a:bufnr)
119 |     let cmd .= gitgutter#git(a:bufnr).' --no-pager show --textconv '.index_name
120 |     let cmd .= ' > '.gitgutter#utility#shellescape(from_file).' || exit 0) && ('
121 | 
122 |   elseif a:from ==# 'working_tree'
123 |     let from_file = gitgutter#utility#repo_path(a:bufnr, 1)
124 |   endif
125 | 
126 |   " Call git-diff.
127 |   let cmd .= gitgutter#git(a:bufnr).' --no-pager'
128 |   if gitgutter#utility#git_supports_command_line_config_override()
129 |     let cmd .= ' -c "diff.autorefreshindex=0"'
130 |     let cmd .= ' -c "diff.noprefix=false"'
131 |     let cmd .= ' -c "core.safecrlf=false"'
132 |   endif
133 |   let cmd .= ' diff --no-ext-diff --no-color -U0 '.g:gitgutter_diff_args
134 |   let cmd .= ' -- '.gitgutter#utility#shellescape(from_file).' '.gitgutter#utility#shellescape(buff_file)
135 | 
136 |   " Pipe git-diff output into grep.
137 |   if !a:preserve_full_diff && !empty(g:gitgutter_grep)
138 |     let cmd .= ' | '.g:gitgutter_grep.' '.gitgutter#utility#shellescape('^@@ ')
139 |   endif
140 | 
141 |   " grep exits with 1 when no matches are found; git-diff exits with 1 when
142 |   " differences are found.  However we want to treat non-matches and
143 |   " differences as non-erroneous behaviour; so we OR the command with one
144 |   " which always exits with success (0).
145 |   let cmd .= ' || exit 0'
146 | 
147 |   let cmd .= ')'
148 | 
149 |   if g:gitgutter_async && gitgutter#async#available()
150 |     call gitgutter#async#execute(cmd, a:bufnr, {
151 |           \   'out': function('gitgutter#diff#handler'),
152 |           \   'err': function('gitgutter#hunk#reset'),
153 |           \ })
154 |     return 'async'
155 | 
156 |   else
157 |     let [diff, error_code] = gitgutter#utility#system(cmd)
158 | 
159 |     if error_code
160 |       call gitgutter#debug#log(diff)
161 |       throw 'gitgutter diff failed'
162 |     endif
163 | 
164 |     return diff
165 |   endif
166 | endfunction
167 | 
168 | 
169 | function! gitgutter#diff#handler(bufnr, diff) abort
170 |   call gitgutter#debug#log(a:diff)
171 | 
172 |   if !bufexists(a:bufnr)
173 |     return
174 |   endif
175 | 
176 |   call gitgutter#hunk#set_hunks(a:bufnr, gitgutter#diff#parse_diff(a:diff))
177 |   let modified_lines = gitgutter#diff#process_hunks(a:bufnr, gitgutter#hunk#hunks(a:bufnr))
178 | 
179 |   let signs_count = len(modified_lines)
180 |   if g:gitgutter_max_signs != -1 && signs_count > g:gitgutter_max_signs
181 |     call gitgutter#utility#warn_once(a:bufnr, printf(
182 |           \ 'exceeded maximum number of signs (%d > %d, configured by g:gitgutter_max_signs).',
183 |           \ signs_count, g:gitgutter_max_signs), 'max_signs')
184 |     call gitgutter#sign#clear_signs(a:bufnr)
185 | 
186 |   else
187 |     if g:gitgutter_signs || g:gitgutter_highlight_lines || g:gitgutter_highlight_linenrs
188 |       call gitgutter#sign#update_signs(a:bufnr, modified_lines)
189 |     endif
190 |   endif
191 | 
192 |   call s:save_last_seen_change(a:bufnr)
193 |   if exists('#User#GitGutter')
194 |     let g:gitgutter_hook_context = {'bufnr': a:bufnr}
195 |     execute 'doautocmd' s:nomodeline 'User GitGutter'
196 |     unlet g:gitgutter_hook_context
197 |   endif
198 | endfunction
199 | 
200 | 
201 | function! gitgutter#diff#parse_diff(diff) abort
202 |   let hunks = []
203 |   for line in split(a:diff, '\n')
204 |     let hunk_info = gitgutter#diff#parse_hunk(line)
205 |     if len(hunk_info) == 4
206 |       call add(hunks, hunk_info)
207 |     endif
208 |   endfor
209 |   return hunks
210 | endfunction
211 | 
212 | function! gitgutter#diff#parse_hunk(line) abort
213 |   let matches = matchlist(a:line, s:hunk_re)
214 |   if len(matches) > 0
215 |     let from_line  = str2nr(matches[1])
216 |     let from_count = (matches[2] == '') ? 1 : str2nr(matches[2])
217 |     let to_line    = str2nr(matches[3])
218 |     let to_count   = (matches[4] == '') ? 1 : str2nr(matches[4])
219 |     return [from_line, from_count, to_line, to_count]
220 |   else
221 |     return []
222 |   end
223 | endfunction
224 | 
225 | " This function is public so it may be used by other plugins
226 | " e.g. vim-signature.
227 | function! gitgutter#diff#process_hunks(bufnr, hunks) abort
228 |   let modified_lines = []
229 |   for hunk in a:hunks
230 |     call extend(modified_lines, s:process_hunk(a:bufnr, hunk))
231 |   endfor
232 |   return modified_lines
233 | endfunction
234 | 
235 | " Returns [ [<line_number (number)>, <name (string)>], ...]
236 | function! s:process_hunk(bufnr, hunk) abort
237 |   let modifications = []
238 |   let from_line  = a:hunk[0]
239 |   let from_count = a:hunk[1]
240 |   let to_line    = a:hunk[2]
241 |   let to_count   = a:hunk[3]
242 | 
243 |   if s:is_added(from_count, to_count)
244 |     call s:process_added(modifications, from_count, to_count, to_line)
245 |     call gitgutter#hunk#increment_lines_added(a:bufnr, to_count)
246 | 
247 |   elseif s:is_removed(from_count, to_count)
248 |     call s:process_removed(modifications, from_count, to_count, to_line)
249 |     call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count)
250 | 
251 |   elseif s:is_modified(from_count, to_count)
252 |     call s:process_modified(modifications, from_count, to_count, to_line)
253 |     call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
254 | 
255 |   elseif s:is_modified_and_added(from_count, to_count)
256 |     call s:process_modified_and_added(modifications, from_count, to_count, to_line)
257 |     call gitgutter#hunk#increment_lines_added(a:bufnr, to_count - from_count)
258 |     call gitgutter#hunk#increment_lines_modified(a:bufnr, from_count)
259 | 
260 |   elseif s:is_modified_and_removed(from_count, to_count)
261 |     call s:process_modified_and_removed(modifications, from_count, to_count, to_line)
262 |     call gitgutter#hunk#increment_lines_modified(a:bufnr, to_count)
263 |     call gitgutter#hunk#increment_lines_removed(a:bufnr, from_count - to_count)
264 | 
265 |   endif
266 |   return modifications
267 | endfunction
268 | 
269 | function! s:is_added(from_count, to_count) abort
270 |   return a:from_count == 0 && a:to_count > 0
271 | endfunction
272 | 
273 | function! s:is_removed(from_count, to_count) abort
274 |   return a:from_count > 0 && a:to_count == 0
275 | endfunction
276 | 
277 | function! s:is_modified(from_count, to_count) abort
278 |   return a:from_count > 0 && a:to_count > 0 && a:from_count == a:to_count
279 | endfunction
280 | 
281 | function! s:is_modified_and_added(from_count, to_count) abort
282 |   return a:from_count > 0 && a:to_count > 0 && a:from_count < a:to_count
283 | endfunction
284 | 
285 | function! s:is_modified_and_removed(from_count, to_count) abort
286 |   return a:from_count > 0 && a:to_count > 0 && a:from_count > a:to_count
287 | endfunction
288 | 
289 | function! s:process_added(modifications, from_count, to_count, to_line) abort
290 |   let offset = 0
291 |   while offset < a:to_count
292 |     let line_number = a:to_line + offset
293 |     call add(a:modifications, [line_number, 'added'])
294 |     let offset += 1
295 |   endwhile
296 | endfunction
297 | 
298 | function! s:process_removed(modifications, from_count, to_count, to_line) abort
299 |   if a:to_line == 0
300 |     call add(a:modifications, [1, 'removed_first_line'])
301 |   else
302 |     call add(a:modifications, [a:to_line, 'removed'])
303 |   endif
304 | endfunction
305 | 
306 | function! s:process_modified(modifications, from_count, to_count, to_line) abort
307 |   let offset = 0
308 |   while offset < a:to_count
309 |     let line_number = a:to_line + offset
310 |     call add(a:modifications, [line_number, 'modified'])
311 |     let offset += 1
312 |   endwhile
313 | endfunction
314 | 
315 | function! s:process_modified_and_added(modifications, from_count, to_count, to_line) abort
316 |   let offset = 0
317 |   while offset < a:from_count
318 |     let line_number = a:to_line + offset
319 |     call add(a:modifications, [line_number, 'modified'])
320 |     let offset += 1
321 |   endwhile
322 |   while offset < a:to_count
323 |     let line_number = a:to_line + offset
324 |     call add(a:modifications, [line_number, 'added'])
325 |     let offset += 1
326 |   endwhile
327 | endfunction
328 | 
329 | function! s:process_modified_and_removed(modifications, from_count, to_count, to_line) abort
330 |   let offset = 0
331 |   while offset < a:to_count
332 |     let line_number = a:to_line + offset
333 |     call add(a:modifications, [line_number, 'modified'])
334 |     let offset += 1
335 |   endwhile
336 |   let a:modifications[-1] = [a:to_line + offset - 1, 'modified_removed']
337 | endfunction
338 | 
339 | 
340 | " Returns a diff for the current hunk.
341 | " Assumes there is only 1 current hunk unless the optional argument is given,
342 | " in which case the cursor is in two hunks and the argument specifies the one
343 | " to choose.
344 | "
345 | " Optional argument: 0 (to use the first hunk) or 1 (to use the second).
346 | function! gitgutter#diff#hunk_diff(bufnr, full_diff, ...)
347 |   let modified_diff = []
348 |   let hunk_index = 0
349 |   let keep_line = 1
350 |   " Don't keepempty when splitting because the diff we want may not be the
351 |   " final one.  Instead add trailing NL at end of function.
352 |   for line in split(a:full_diff, '\n')
353 |     let hunk_info = gitgutter#diff#parse_hunk(line)
354 |     if len(hunk_info) == 4  " start of new hunk
355 |       let keep_line = gitgutter#hunk#cursor_in_hunk(hunk_info)
356 | 
357 |       if a:0 && hunk_index != a:1
358 |         let keep_line = 0
359 |       endif
360 | 
361 |       let hunk_index += 1
362 |     endif
363 |     if keep_line
364 |       call add(modified_diff, line)
365 |     endif
366 |   endfor
367 |   return join(modified_diff, "\n")."\n"
368 | endfunction
369 | 
370 | 
371 | function! s:write_buffer(bufnr, file)
372 |   let bufcontents = getbufline(a:bufnr, 1, '
#39;)
373 | 
374 |   if bufcontents == [''] && line2byte(1) == -1
375 |     " Special case: completely empty buffer.
376 |     " A nearly empty buffer of only a newline has line2byte(1) == 1.
377 |     call writefile([], a:file)
378 |     return
379 |   endif
380 | 
381 |   if getbufvar(a:bufnr, '&fileformat') ==# 'dos'
382 |     if getbufvar(a:bufnr, '&endofline')
383 |       call map(bufcontents, 'v:val."\r"')
384 |     else
385 |       for i in range(len(bufcontents) - 1)
386 |         let bufcontents[i] = bufcontents[i] . "\r"
387 |       endfor
388 |     endif
389 |   endif
390 | 
391 |   if getbufvar(a:bufnr, '&endofline')
392 |     call add(bufcontents, '')
393 |   endif
394 | 
395 |   let fenc = getbufvar(a:bufnr, '&fileencoding')
396 |   if fenc !=# &encoding
397 |     call map(bufcontents, 'iconv(v:val, &encoding, "'.fenc.'")')
398 |   endif
399 | 
400 |   if getbufvar(a:bufnr, '&bomb')
401 |     let bufcontents[0]=''.bufcontents[0]
402 |   endif
403 | 
404 |   " The file we are writing to is a temporary file.  Sometimes the parent
405 |   " directory is deleted outside Vim but, because Vim caches the directory
406 |   " name at startup and does not check for its existence subsequently, Vim
407 |   " does not realise.  This causes E482 errors.
408 |   try
409 |     call writefile(bufcontents, a:file, 'b')
410 |   catch /E482/
411 |     call mkdir(fnamemodify(a:file, ':h'), '', '0700')
412 |     call writefile(bufcontents, a:file, 'b')
413 |   endtry
414 | endfunction
415 | 
416 | 
417 | function! s:save_last_seen_change(bufnr) abort
418 |   call gitgutter#utility#setbufvar(a:bufnr, 'tick', getbufvar(a:bufnr, 'changedtick'))
419 | endfunction
420 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/diff_highlight.vim:
--------------------------------------------------------------------------------
  1 | " This is the minimum number of characters required between regions of change
  2 | " in a line.  It's somewhat arbitrary: higher values mean less visual busyness;
  3 | " lower values mean more detail.
  4 | let s:gap_between_regions = 5
  5 | 
  6 | 
  7 | " Calculates the changed portions of lines.
  8 | "
  9 | " Based on:
 10 | "
 11 | " - diff-highlight (included with git)
 12 | "   https://github.com/git/git/blob/master/contrib/diff-highlight/DiffHighlight.pm
 13 | "
 14 | " - Diff Strategies, Neil Fraser
 15 | "   https://neil.fraser.name/writing/diff/
 16 | 
 17 | 
 18 | " Returns a list of intra-line changed regions.
 19 | " Each element is a list:
 20 | "
 21 | "   [
 22 | "     line number (1-based),
 23 | "     type ('+' or '-'),
 24 | "     start column (1-based, inclusive),
 25 | "     stop column (1-based, inclusive),
 26 | "   ]
 27 | "
 28 | " Args:
 29 | "   hunk_body - list of lines
 30 | function! gitgutter#diff_highlight#process(hunk_body)
 31 |   " Check whether we have the same number of lines added as removed.
 32 |   let [removed, added] = [0, 0]
 33 |   for line in a:hunk_body
 34 |     if line[0] == '-'
 35 |       let removed += 1
 36 |     elseif line[0] == '+'
 37 |       let added += 1
 38 |     endif
 39 |   endfor
 40 |   if removed != added
 41 |     return []
 42 |   endif
 43 | 
 44 |   let regions = []
 45 | 
 46 |   for i in range(removed)
 47 |     " pair lines by position
 48 |     let rline = a:hunk_body[i]
 49 |     let aline = a:hunk_body[i + removed]
 50 | 
 51 |     call s:diff(rline, aline, i, i+removed, 0, 0, regions, 1)
 52 |   endfor
 53 | 
 54 |   return regions
 55 | endfunction
 56 | 
 57 | 
 58 | function! s:diff(rline, aline, rlinenr, alinenr, rprefix, aprefix, regions, whole_line)
 59 |   " diff marker does not count as a difference in prefix
 60 |   let start = a:whole_line ? 1 : 0
 61 |   let prefix = s:common_prefix(a:rline[start:], a:aline[start:])
 62 |   if a:whole_line
 63 |     let prefix += 1
 64 |   endif
 65 |   let [rsuffix, asuffix] = s:common_suffix(a:rline, a:aline, prefix+1)
 66 | 
 67 |   " region of change (common prefix and suffix removed)
 68 |   let rtext = a:rline[prefix+1:rsuffix-1]
 69 |   let atext = a:aline[prefix+1:asuffix-1]
 70 | 
 71 |   " singular insertion
 72 |   if empty(rtext)
 73 |     if !a:whole_line || len(atext) != len(a:aline)  " not whole line
 74 |       call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+asuffix+1-1])
 75 |     endif
 76 |     return
 77 |   endif
 78 | 
 79 |   " singular deletion
 80 |   if empty(atext)
 81 |     if !a:whole_line || len(rtext) != len(a:rline)  " not whole line
 82 |       call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+rsuffix+1-1])
 83 |     endif
 84 |     return
 85 |   endif
 86 | 
 87 |   " two insertions
 88 |   let j = stridx(atext, rtext)
 89 |   if j != -1
 90 |     call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+prefix+j+1])
 91 |     call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1+j+len(rtext), a:aprefix+asuffix+1-1])
 92 |     return
 93 |   endif
 94 | 
 95 |   " two deletions
 96 |   let j = stridx(rtext, atext)
 97 |   if j != -1
 98 |     call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+prefix+j+1])
 99 |     call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1+j+len(atext), a:rprefix+rsuffix+1-1])
100 |     return
101 |   endif
102 | 
103 |   " two edits
104 |   let lcs = s:lcs(rtext, atext)
105 |   " TODO do we need to ensure we don't get more than 2 elements when splitting?
106 |   if len(lcs) > s:gap_between_regions
107 |     let redits = s:split(rtext, lcs)
108 |     let aedits = s:split(atext, lcs)
109 |     call s:diff(redits[0], aedits[0], a:rlinenr, a:alinenr, a:rprefix+prefix+1,                         a:aprefix+prefix+1,                         a:regions, 0)
110 |     call s:diff(redits[1], aedits[1], a:rlinenr, a:alinenr, a:rprefix+prefix+1+len(redits[0])+len(lcs), a:aprefix+prefix+1+len(aedits[0])+len(lcs), a:regions, 0)
111 |     return
112 |   endif
113 | 
114 |   " fall back to highlighting entire changed area
115 | 
116 |   " if a change (but not the whole line)
117 |   if !a:whole_line || ((prefix != 0 || rsuffix != len(a:rline)) && prefix+1 < rsuffix)
118 |     call add(a:regions, [a:rlinenr+1, '-', a:rprefix+prefix+1+1, a:rprefix+rsuffix+1-1])
119 |   endif
120 | 
121 |   " if a change (but not the whole line)
122 |   if !a:whole_line || ((prefix != 0 || asuffix != len(a:aline)) && prefix+1 < asuffix)
123 |     call add(a:regions, [a:alinenr+1, '+', a:aprefix+prefix+1+1, a:aprefix+asuffix+1-1])
124 |   endif
125 | endfunction
126 | 
127 | 
128 | function! s:lcs(s1, s2)
129 |   if empty(a:s1) || empty(a:s2)
130 |     return ''
131 |   endif
132 | 
133 |   let matrix = map(repeat([repeat([0], len(a:s2)+1)], len(a:s1)+1), 'copy(v:val)')
134 | 
135 |   let maxlength = 0
136 |   let endindex = len(a:s1)
137 | 
138 |   for i in range(1, len(a:s1))
139 |     for j in range(1, len(a:s2))
140 |       if a:s1[i-1] ==# a:s2[j-1]
141 |         let matrix[i][j] = 1 + matrix[i-1][j-1]
142 |         if matrix[i][j] > maxlength
143 |           let maxlength = matrix[i][j]
144 |           let endindex = i - 1
145 |         endif
146 |       endif
147 |     endfor
148 |   endfor
149 | 
150 |   return a:s1[endindex - maxlength + 1 : endindex]
151 | endfunction
152 | 
153 | 
154 | " Returns 0-based index of last character of common prefix
155 | " If there is no common prefix, returns -1.
156 | "
157 | " a, b - strings
158 | "
159 | function! s:common_prefix(a, b)
160 |   let len = min([len(a:a), len(a:b)])
161 |   if len == 0
162 |     return -1
163 |   endif
164 |   for i in range(len)
165 |     if a:a[i:i] !=# a:b[i:i]
166 |       return i - 1
167 |     endif
168 |   endfor
169 |   return i
170 | endfunction
171 | 
172 | 
173 | " Returns 0-based indices of start of common suffix
174 | "
175 | " a, b - strings
176 | " start - 0-based index to start from
177 | function! s:common_suffix(a, b, start)
178 |   let [sa, sb] = [len(a:a), len(a:b)]
179 |   while sa >= a:start && sb >= a:start
180 |     if a:a[sa] ==# a:b[sb]
181 |       let sa -= 1
182 |       let sb -= 1
183 |     else
184 |       break
185 |     endif
186 |   endwhile
187 |   return [sa+1, sb+1]
188 | endfunction
189 | 
190 | 
191 | " Split a string on another string.
192 | " Assumes 1 occurrence of the delimiter.
193 | function! s:split(str, delimiter)
194 |   let i = stridx(a:str, a:delimiter)
195 | 
196 |   if i == 0
197 |     return ['', a:str[len(a:delimiter):]]
198 |   endif
199 | 
200 |   return [a:str[:i-1], a:str[i+len(a:delimiter):]]
201 | endfunction
202 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/fold.vim:
--------------------------------------------------------------------------------
  1 | function! gitgutter#fold#enable()
  2 |   call s:save_fold_state()
  3 | 
  4 |   call s:set_fold_levels()
  5 |   setlocal foldexpr=gitgutter#fold#level(v:lnum)
  6 |   setlocal foldmethod=expr
  7 |   setlocal foldlevel=0
  8 |   setlocal foldenable
  9 | 
 10 |   call gitgutter#utility#setbufvar(bufnr(''), 'folded', 1)
 11 | endfunction
 12 | 
 13 | 
 14 | function! gitgutter#fold#disable()
 15 |   call s:restore_fold_state()
 16 |   call gitgutter#utility#setbufvar(bufnr(''), 'folded', 0)
 17 | endfunction
 18 | 
 19 | 
 20 | function! gitgutter#fold#toggle()
 21 |   if s:folded()
 22 |     call gitgutter#fold#disable()
 23 |   else
 24 |     call gitgutter#fold#enable()
 25 |   endif
 26 | endfunction
 27 | 
 28 | 
 29 | function! gitgutter#fold#level(lnum)
 30 |   return gitgutter#utility#getbufvar(bufnr(''), 'fold_levels')[a:lnum]
 31 | endfunction
 32 | 
 33 | 
 34 | function! gitgutter#fold#foldtext()
 35 |   if !gitgutter#fold#is_changed()
 36 |     return foldtext()
 37 |   endif
 38 | 
 39 |   return substitute(foldtext(), ':', ' (*):', '')
 40 | endfunction
 41 | 
 42 | 
 43 | " Returns 1 if any of the folded lines have been changed
 44 | " (added, removed, or modified), 0 otherwise.
 45 | function! gitgutter#fold#is_changed()
 46 |   for hunk in gitgutter#hunk#hunks(bufnr(''))
 47 |     let hunk_begin = hunk[2]
 48 |     let hunk_end   = hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
 49 | 
 50 |     if hunk_end < v:foldstart
 51 |       continue
 52 |     endif
 53 | 
 54 |     if hunk_begin > v:foldend
 55 |       break
 56 |     endif
 57 | 
 58 |     return 1
 59 |   endfor
 60 | 
 61 |   return 0
 62 | endfunction
 63 | 
 64 | 
 65 | " A line in a hunk has a fold level of 0.
 66 | " A line within 3 lines of a hunk has a fold level of 1.
 67 | " All other lines have a fold level of 2.
 68 | function! s:set_fold_levels()
 69 |   let fold_levels = ['']
 70 | 
 71 |   for lnum in range(1, line('
#39;))
 72 |     let in_hunk = gitgutter#hunk#in_hunk(lnum)
 73 |     call add(fold_levels, (in_hunk ? 0 : 2))
 74 |   endfor
 75 | 
 76 |   let lines_of_context = 3
 77 | 
 78 |   for lnum in range(1, line('
#39;))
 79 |     if fold_levels[lnum] == 2
 80 |       let pre = lnum >= 3 ? lnum - lines_of_context : 0
 81 |       let post = lnum + lines_of_context
 82 |       if index(fold_levels[pre:post], 0) != -1
 83 |         let fold_levels[lnum] = 1
 84 |       endif
 85 |     endif
 86 |   endfor
 87 | 
 88 |   call gitgutter#utility#setbufvar(bufnr(''), 'fold_levels', fold_levels)
 89 | endfunction
 90 | 
 91 | 
 92 | function! s:save_fold_state()
 93 |   let bufnr = bufnr('')
 94 |   call gitgutter#utility#setbufvar(bufnr, 'foldlevel', &foldlevel)
 95 |   call gitgutter#utility#setbufvar(bufnr, 'foldmethod', &foldmethod)
 96 |   if &foldmethod ==# 'manual'
 97 |     mkview
 98 |   endif
 99 | endfunction
100 | 
101 | function! s:restore_fold_state()
102 |   let bufnr = bufnr('')
103 |   let &foldlevel = gitgutter#utility#getbufvar(bufnr, 'foldlevel')
104 |   let &foldmethod = gitgutter#utility#getbufvar(bufnr, 'foldmethod')
105 |   if &foldmethod ==# 'manual'
106 |     loadview
107 |   else
108 |     normal! zx
109 |   endif
110 | endfunction
111 | 
112 | function! s:folded()
113 |   return gitgutter#utility#getbufvar(bufnr(''), 'folded')
114 | endfunction
115 | 
116 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/highlight.vim:
--------------------------------------------------------------------------------
  1 | function! gitgutter#highlight#line_disable() abort
  2 |   let g:gitgutter_highlight_lines = 0
  3 |   call s:define_sign_line_highlights()
  4 | 
  5 |   if !g:gitgutter_signs
  6 |     call gitgutter#sign#clear_signs(bufnr(''))
  7 |   endif
  8 | 
  9 |   redraw!
 10 | endfunction
 11 | 
 12 | function! gitgutter#highlight#line_enable() abort
 13 |   let old_highlight_lines = g:gitgutter_highlight_lines
 14 | 
 15 |   let g:gitgutter_highlight_lines = 1
 16 |   call s:define_sign_line_highlights()
 17 | 
 18 |   if !old_highlight_lines && !g:gitgutter_signs
 19 |     call gitgutter#all(1)
 20 |   endif
 21 | 
 22 |   redraw!
 23 | endfunction
 24 | 
 25 | function! gitgutter#highlight#line_toggle() abort
 26 |   if g:gitgutter_highlight_lines
 27 |     call gitgutter#highlight#line_disable()
 28 |   else
 29 |     call gitgutter#highlight#line_enable()
 30 |   endif
 31 | endfunction
 32 | 
 33 | 
 34 | function! gitgutter#highlight#linenr_disable() abort
 35 |   let g:gitgutter_highlight_linenrs = 0
 36 |   call s:define_sign_linenr_highlights()
 37 | 
 38 |   if !g:gitgutter_signs
 39 |     call gitgutter#sign#clear_signs(bufnr(''))
 40 |   endif
 41 | 
 42 |   redraw!
 43 | endfunction
 44 | 
 45 | function! gitgutter#highlight#linenr_enable() abort
 46 |   let old_highlight_linenrs = g:gitgutter_highlight_linenrs
 47 | 
 48 |   let g:gitgutter_highlight_linenrs = 1
 49 |   call s:define_sign_linenr_highlights()
 50 | 
 51 |   if !old_highlight_linenrs && !g:gitgutter_signs
 52 |     call gitgutter#all(1)
 53 |   endif
 54 | 
 55 |   redraw!
 56 | endfunction
 57 | 
 58 | function! gitgutter#highlight#linenr_toggle() abort
 59 |   if g:gitgutter_highlight_linenrs
 60 |     call gitgutter#highlight#linenr_disable()
 61 |   else
 62 |     call gitgutter#highlight#linenr_enable()
 63 |   endif
 64 | endfunction
 65 | 
 66 | 
 67 | function! gitgutter#highlight#define_highlights() abort
 68 |   let [guibg, ctermbg] = s:get_background_colors('SignColumn')
 69 | 
 70 |   " Highlights used by the signs.
 71 | 
 72 |   " When they are invisible.
 73 |   execute "highlight GitGutterAddInvisible    guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
 74 |   execute "highlight GitGutterChangeInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
 75 |   execute "highlight GitGutterDeleteInvisible guifg=bg guibg=" . guibg . " ctermfg=" . ctermbg . " ctermbg=" . ctermbg
 76 |   highlight default link GitGutterChangeDeleteInvisible GitGutterChangeInvisible
 77 | 
 78 |   " When they are visible.
 79 |   for type in ["Add", "Change", "Delete"]
 80 |     if hlexists("GitGutter".type) && s:get_foreground_colors("GitGutter".type) != ['NONE', 'NONE']
 81 |       if g:gitgutter_set_sign_backgrounds
 82 |         execute "highlight GitGutter".type." guibg=".guibg." ctermbg=".ctermbg
 83 |       endif
 84 |       continue
 85 |     elseif s:useful_diff_colours()
 86 |       let [guifg, ctermfg] = s:get_foreground_colors('Diff'.type)
 87 |     else
 88 |       let [guifg, ctermfg] = s:get_foreground_fallback_colors(type)
 89 |     endif
 90 |     execute "highlight GitGutter".type." guifg=".guifg." guibg=".guibg." ctermfg=".ctermfg." ctermbg=".ctermbg
 91 |   endfor
 92 | 
 93 |   if hlexists("GitGutterChangeDelete") && g:gitgutter_set_sign_backgrounds
 94 |     execute "highlight GitGutterChangeDelete guibg=".guibg." ctermbg=".ctermbg
 95 |   endif
 96 | 
 97 |   highlight default link GitGutterChangeDelete GitGutterChange
 98 | 
 99 |   " Highlights used for the whole line.
100 | 
101 |   highlight default link GitGutterAddLine          DiffAdd
102 |   highlight default link GitGutterChangeLine       DiffChange
103 |   highlight default link GitGutterDeleteLine       DiffDelete
104 |   highlight default link GitGutterChangeDeleteLine GitGutterChangeLine
105 | 
106 |   highlight default link GitGutterAddLineNr          CursorLineNr
107 |   highlight default link GitGutterChangeLineNr       CursorLineNr
108 |   highlight default link GitGutterDeleteLineNr       CursorLineNr
109 |   highlight default link GitGutterChangeDeleteLineNr GitGutterChangeLineNr
110 | 
111 |   " Highlights used intra line.
112 |   highlight default GitGutterAddIntraLine    gui=reverse cterm=reverse
113 |   highlight default GitGutterDeleteIntraLine gui=reverse cterm=reverse
114 |   " Set diff syntax colours (used in the preview window) - diffAdded,diffChanged,diffRemoved -
115 |   " to match the signs, if not set aleady.
116 |   for [dtype,type] in [['Added','Add'], ['Changed','Change'], ['Removed','Delete']]
117 |     if !hlexists('diff'.dtype)
118 |       let [guifg, ctermfg] = s:get_foreground_colors('GitGutter'.type)
119 |       execute "highlight diff".dtype." guifg=".guifg." ctermfg=".ctermfg." guibg=NONE ctermbg=NONE"
120 |     endif
121 |   endfor
122 | endfunction
123 | 
124 | function! gitgutter#highlight#define_signs() abort
125 |   sign define GitGutterLineAdded
126 |   sign define GitGutterLineModified
127 |   sign define GitGutterLineRemoved
128 |   sign define GitGutterLineRemovedFirstLine
129 |   sign define GitGutterLineRemovedAboveAndBelow
130 |   sign define GitGutterLineModifiedRemoved
131 | 
132 |   call s:define_sign_text()
133 |   call gitgutter#highlight#define_sign_text_highlights()
134 |   call s:define_sign_line_highlights()
135 |   call s:define_sign_linenr_highlights()
136 | endfunction
137 | 
138 | function! s:define_sign_text() abort
139 |   execute "sign define GitGutterLineAdded                 text=" . g:gitgutter_sign_added
140 |   execute "sign define GitGutterLineModified              text=" . g:gitgutter_sign_modified
141 |   execute "sign define GitGutterLineRemoved               text=" . g:gitgutter_sign_removed
142 |   execute "sign define GitGutterLineRemovedFirstLine      text=" . g:gitgutter_sign_removed_first_line
143 |   execute "sign define GitGutterLineRemovedAboveAndBelow  text=" . g:gitgutter_sign_removed_above_and_below
144 |   execute "sign define GitGutterLineModifiedRemoved       text=" . g:gitgutter_sign_modified_removed
145 | endfunction
146 | 
147 | function! gitgutter#highlight#define_sign_text_highlights() abort
148 |   " Once a sign's text attribute has been defined, it cannot be undefined or
149 |   " set to an empty value.  So to make signs' text disappear (when toggling
150 |   " off or disabling) we make them invisible by setting their foreground colours
151 |   " to the background's.
152 |   if g:gitgutter_signs
153 |     sign define GitGutterLineAdded                 texthl=GitGutterAdd
154 |     sign define GitGutterLineModified              texthl=GitGutterChange
155 |     sign define GitGutterLineRemoved               texthl=GitGutterDelete
156 |     sign define GitGutterLineRemovedFirstLine      texthl=GitGutterDelete
157 |     sign define GitGutterLineRemovedAboveAndBelow  texthl=GitGutterDelete
158 |     sign define GitGutterLineModifiedRemoved       texthl=GitGutterChangeDelete
159 |   else
160 |     sign define GitGutterLineAdded                 texthl=GitGutterAddInvisible
161 |     sign define GitGutterLineModified              texthl=GitGutterChangeInvisible
162 |     sign define GitGutterLineRemoved               texthl=GitGutterDeleteInvisible
163 |     sign define GitGutterLineRemovedFirstLine      texthl=GitGutterDeleteInvisible
164 |     sign define GitGutterLineRemovedAboveAndBelow  texthl=GitGutterDeleteInvisible
165 |     sign define GitGutterLineModifiedRemoved       texthl=GitGutterChangeDeleteInvisible
166 |   endif
167 | endfunction
168 | 
169 | function! s:define_sign_line_highlights() abort
170 |   if g:gitgutter_highlight_lines
171 |     sign define GitGutterLineAdded                 linehl=GitGutterAddLine
172 |     sign define GitGutterLineModified              linehl=GitGutterChangeLine
173 |     sign define GitGutterLineRemoved               linehl=GitGutterDeleteLine
174 |     sign define GitGutterLineRemovedFirstLine      linehl=GitGutterDeleteLine
175 |     sign define GitGutterLineRemovedAboveAndBelow  linehl=GitGutterDeleteLine
176 |     sign define GitGutterLineModifiedRemoved       linehl=GitGutterChangeDeleteLine
177 |   else
178 |     sign define GitGutterLineAdded                 linehl=NONE
179 |     sign define GitGutterLineModified              linehl=NONE
180 |     sign define GitGutterLineRemoved               linehl=NONE
181 |     sign define GitGutterLineRemovedFirstLine      linehl=NONE
182 |     sign define GitGutterLineRemovedAboveAndBelow  linehl=NONE
183 |     sign define GitGutterLineModifiedRemoved       linehl=NONE
184 |   endif
185 | endfunction
186 | 
187 | function! s:define_sign_linenr_highlights() abort
188 |   if has('nvim-0.3.2')
189 |     try
190 |       if g:gitgutter_highlight_linenrs
191 |         sign define GitGutterLineAdded                 numhl=GitGutterAddLineNr
192 |         sign define GitGutterLineModified              numhl=GitGutterChangeLineNr
193 |         sign define GitGutterLineRemoved               numhl=GitGutterDeleteLineNr
194 |         sign define GitGutterLineRemovedFirstLine      numhl=GitGutterDeleteLineNr
195 |         sign define GitGutterLineRemovedAboveAndBelow  numhl=GitGutterDeleteLineNr
196 |         sign define GitGutterLineModifiedRemoved       numhl=GitGutterChangeDeleteLineNr
197 |       else
198 |         sign define GitGutterLineAdded                 numhl=NONE
199 |         sign define GitGutterLineModified              numhl=NONE
200 |         sign define GitGutterLineRemoved               numhl=NONE
201 |         sign define GitGutterLineRemovedFirstLine      numhl=NONE
202 |         sign define GitGutterLineRemovedAboveAndBelow  numhl=NONE
203 |         sign define GitGutterLineModifiedRemoved       numhl=NONE
204 |       endif
205 |     catch /E475/
206 |     endtry
207 |   endif
208 | endfunction
209 | 
210 | function! s:get_hl(group, what, mode) abort
211 |   let r = synIDattr(synIDtrans(hlID(a:group)), a:what, a:mode)
212 |   if empty(r) || r == -1
213 |     return 'NONE'
214 |   endif
215 |   return r
216 | endfunction
217 | 
218 | function! s:get_foreground_colors(group) abort
219 |   let ctermfg = s:get_hl(a:group, 'fg', 'cterm')
220 |   let guifg = s:get_hl(a:group, 'fg', 'gui')
221 |   return [guifg, ctermfg]
222 | endfunction
223 | 
224 | function! s:get_background_colors(group) abort
225 |   let ctermbg = s:get_hl(a:group, 'bg', 'cterm')
226 |   let guibg = s:get_hl(a:group, 'bg', 'gui')
227 |   return [guibg, ctermbg]
228 | endfunction
229 | 
230 | function! s:useful_diff_colours()
231 |   let [guifg_add, ctermfg_add] = s:get_foreground_colors('DiffAdd')
232 |   let [guifg_del, ctermfg_del] = s:get_foreground_colors('DiffDelete')
233 | 
234 |   return guifg_add != guifg_del && ctermfg_add != ctermfg_del
235 | endfunction
236 | 
237 | function! s:get_foreground_fallback_colors(type)
238 |   if a:type == 'Add'
239 |     return ['#009900', '2']
240 |   elseif a:type == 'Change'
241 |     return ['#bbbb00', '3']
242 |   elseif a:type == 'Delete'
243 |     return ['#ff2222', '1']
244 |   endif
245 | endfunction
246 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/hunk.vim:
--------------------------------------------------------------------------------
  1 | let s:winid = 0
  2 | let s:preview_bufnr = 0
  3 | let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
  4 | 
  5 | function! gitgutter#hunk#set_hunks(bufnr, hunks) abort
  6 |   call gitgutter#utility#setbufvar(a:bufnr, 'hunks', a:hunks)
  7 |   call s:reset_summary(a:bufnr)
  8 | endfunction
  9 | 
 10 | function! gitgutter#hunk#hunks(bufnr) abort
 11 |   return gitgutter#utility#getbufvar(a:bufnr, 'hunks', [])
 12 | endfunction
 13 | 
 14 | function! gitgutter#hunk#reset(bufnr) abort
 15 |   call gitgutter#utility#setbufvar(a:bufnr, 'hunks', [])
 16 |   call s:reset_summary(a:bufnr)
 17 | endfunction
 18 | 
 19 | 
 20 | function! gitgutter#hunk#summary(bufnr) abort
 21 |   return gitgutter#utility#getbufvar(a:bufnr, 'summary', [0,0,0])
 22 | endfunction
 23 | 
 24 | function! s:reset_summary(bufnr) abort
 25 |   call gitgutter#utility#setbufvar(a:bufnr, 'summary', [0,0,0])
 26 | endfunction
 27 | 
 28 | function! gitgutter#hunk#increment_lines_added(bufnr, count) abort
 29 |   let summary = gitgutter#hunk#summary(a:bufnr)
 30 |   let summary[0] += a:count
 31 |   call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
 32 | endfunction
 33 | 
 34 | function! gitgutter#hunk#increment_lines_modified(bufnr, count) abort
 35 |   let summary = gitgutter#hunk#summary(a:bufnr)
 36 |   let summary[1] += a:count
 37 |   call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
 38 | endfunction
 39 | 
 40 | function! gitgutter#hunk#increment_lines_removed(bufnr, count) abort
 41 |   let summary = gitgutter#hunk#summary(a:bufnr)
 42 |   let summary[2] += a:count
 43 |   call gitgutter#utility#setbufvar(a:bufnr, 'summary', summary)
 44 | endfunction
 45 | 
 46 | 
 47 | function! gitgutter#hunk#next_hunk(count) abort
 48 |   let bufnr = bufnr('')
 49 |   if !gitgutter#utility#is_active(bufnr) | return | endif
 50 | 
 51 |   let hunks = gitgutter#hunk#hunks(bufnr)
 52 |   if empty(hunks)
 53 |     call gitgutter#utility#warn('No hunks in file')
 54 |     return
 55 |   endif
 56 | 
 57 |   let current_line = line('.')
 58 |   let hunk_count = 0
 59 |   for hunk in hunks
 60 |     if hunk[2] > current_line
 61 |       let hunk_count += 1
 62 |       if hunk_count == a:count
 63 |         let keys = &foldopen =~# '\<block\>' ? 'zv' : ''
 64 |         execute 'normal!' hunk[2] . 'G' . keys
 65 |         if g:gitgutter_show_msg_on_hunk_jumping
 66 |           redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
 67 |         endif
 68 |         if gitgutter#hunk#is_preview_window_open()
 69 |           call gitgutter#hunk#preview()
 70 |         endif
 71 |         return
 72 |       endif
 73 |     endif
 74 |   endfor
 75 |   call gitgutter#utility#warn('No more hunks')
 76 | endfunction
 77 | 
 78 | function! gitgutter#hunk#prev_hunk(count) abort
 79 |   let bufnr = bufnr('')
 80 |   if !gitgutter#utility#is_active(bufnr) | return | endif
 81 | 
 82 |   let hunks = gitgutter#hunk#hunks(bufnr)
 83 |   if empty(hunks)
 84 |     call gitgutter#utility#warn('No hunks in file')
 85 |     return
 86 |   endif
 87 | 
 88 |   let current_line = line('.')
 89 |   let hunk_count = 0
 90 |   for hunk in reverse(copy(hunks))
 91 |     if hunk[2] < current_line
 92 |       let hunk_count += 1
 93 |       if hunk_count == a:count
 94 |         let keys = &foldopen =~# '\<block\>' ? 'zv' : ''
 95 |         let target = hunk[2] == 0 ? 1 : hunk[2]
 96 |         execute 'normal!' target . 'G' . keys
 97 |         if g:gitgutter_show_msg_on_hunk_jumping
 98 |           redraw | echo printf('Hunk %d of %d', index(hunks, hunk) + 1, len(hunks))
 99 |         endif
100 |         if gitgutter#hunk#is_preview_window_open()
101 |           call gitgutter#hunk#preview()
102 |         endif
103 |         return
104 |       endif
105 |     endif
106 |   endfor
107 |   call gitgutter#utility#warn('No previous hunks')
108 | endfunction
109 | 
110 | " Returns the hunk the cursor is currently in or an empty list if the cursor
111 | " isn't in a hunk.
112 | function! s:current_hunk() abort
113 |   let bufnr = bufnr('')
114 |   let current_hunk = []
115 | 
116 |   for hunk in gitgutter#hunk#hunks(bufnr)
117 |     if gitgutter#hunk#cursor_in_hunk(hunk)
118 |       let current_hunk = hunk
119 |       break
120 |     endif
121 |   endfor
122 | 
123 |   return current_hunk
124 | endfunction
125 | 
126 | " Returns truthy if the cursor is in two hunks (which can only happen if the
127 | " cursor is on the first line and lines above have been deleted and lines
128 | " immediately below have been deleted) or falsey otherwise.
129 | function! s:cursor_in_two_hunks()
130 |   let hunks = gitgutter#hunk#hunks(bufnr(''))
131 | 
132 |   if line('.') == 1 && len(hunks) > 1 && hunks[0][2:3] == [0, 0] && hunks[1][2:3] == [1, 0]
133 |     return 1
134 |   endif
135 | 
136 |   return 0
137 | endfunction
138 | 
139 | " A line can be in 0 or 1 hunks, with the following exception: when the first
140 | " line(s) of a file has been deleted, and the new second line (and
141 | " optionally below) has been deleted, the new first line is in two hunks.
142 | function! gitgutter#hunk#cursor_in_hunk(hunk) abort
143 |   let current_line = line('.')
144 | 
145 |   if current_line == 1 && a:hunk[2] == 0
146 |     return 1
147 |   endif
148 | 
149 |   if current_line >= a:hunk[2] && current_line < a:hunk[2] + (a:hunk[3] == 0 ? 1 : a:hunk[3])
150 |     return 1
151 |   endif
152 | 
153 |   return 0
154 | endfunction
155 | 
156 | 
157 | function! gitgutter#hunk#in_hunk(lnum)
158 |   " Hunks are sorted in the order they appear in the buffer.
159 |   for hunk in gitgutter#hunk#hunks(bufnr(''))
160 |     " if in a hunk on first line of buffer
161 |     if a:lnum == 1 && hunk[2] == 0
162 |       return 1
163 |     endif
164 | 
165 |     " if in a hunk generally
166 |     if a:lnum >= hunk[2] && a:lnum < hunk[2] + (hunk[3] == 0 ? 1 : hunk[3])
167 |       return 1
168 |     endif
169 | 
170 |     " if hunk starts after the given line
171 |     if a:lnum < hunk[2]
172 |       return 0
173 |     endif
174 |   endfor
175 | 
176 |   return 0
177 | endfunction
178 | 
179 | 
180 | function! gitgutter#hunk#text_object(inner) abort
181 |   let hunk = s:current_hunk()
182 | 
183 |   if empty(hunk)
184 |     return
185 |   endif
186 | 
187 |   let [first_line, last_line] = [hunk[2], hunk[2] + hunk[3] - 1]
188 | 
189 |   if ! a:inner
190 |     let lnum = last_line
191 |     let eof = line('
#39;)
192 |     while lnum < eof && empty(getline(lnum + 1))
193 |       let lnum +=1
194 |     endwhile
195 |     let last_line = lnum
196 |   endif
197 | 
198 |   execute 'normal! 'first_line.'GV'.last_line.'G'
199 | endfunction
200 | 
201 | 
202 | function! gitgutter#hunk#stage(...) abort
203 |   if !s:in_hunk_preview_window() && !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
204 | 
205 |   if a:0 && (a:1 != 1 || a:2 != line('
#39;))
206 |     call s:hunk_op(function('s:stage'), a:1, a:2)
207 |   else
208 |     call s:hunk_op(function('s:stage'))
209 |   endif
210 |   silent! call repeat#set("\<Plug>(GitGutterStageHunk)", -1)
211 | endfunction
212 | 
213 | function! gitgutter#hunk#undo() abort
214 |   if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
215 | 
216 |   call s:hunk_op(function('s:undo'))
217 |   silent! call repeat#set("\<Plug>(GitGutterUndoHunk)", -1)
218 | endfunction
219 | 
220 | function! gitgutter#hunk#preview() abort
221 |   if !gitgutter#utility#has_repo_path(bufnr('')) | return | endif
222 | 
223 |   call s:hunk_op(function('s:preview'))
224 |   silent! call repeat#set("\<Plug>(GitGutterPreviewHunk)", -1)
225 | endfunction
226 | 
227 | 
228 | function! s:hunk_op(op, ...)
229 |   let bufnr = bufnr('')
230 | 
231 |   if s:in_hunk_preview_window()
232 |     if string(a:op) =~ '_stage'
233 |       " combine hunk-body in preview window with updated hunk-header
234 |       let hunk_body = getline(1, '
#39;)
235 | 
236 |       let [removed, added] = [0, 0]
237 |       for line in hunk_body
238 |         if line[0] == '-'
239 |           let removed += 1
240 |         elseif line[0] == '+'
241 |           let added += 1
242 |         endif
243 |       endfor
244 | 
245 |       let hunk_header = b:hunk_header
246 |       " from count
247 |       let hunk_header[4] = substitute(hunk_header[4], '\(-\d\+\)\(,\d\+\)\?', '\=submatch(1).",".removed', '')
248 |       " to count
249 |       let hunk_header[4] = substitute(hunk_header[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".added', '')
250 | 
251 |       let hunk_diff = join(hunk_header + hunk_body, "\n")."\n"
252 | 
253 |       if &previewwindow
254 |         call s:goto_original_window()
255 |       endif
256 |       call gitgutter#hunk#close_hunk_preview_window()
257 |       call s:stage(hunk_diff)
258 |     endif
259 | 
260 |     return
261 |   endif
262 | 
263 |   if gitgutter#utility#is_active(bufnr)
264 |     " Get a (synchronous) diff.
265 |     let [async, g:gitgutter_async] = [g:gitgutter_async, 0]
266 |     let diff = gitgutter#diff#run_diff(bufnr, g:gitgutter_diff_relative_to, 1)
267 |     let g:gitgutter_async = async
268 | 
269 |     call gitgutter#hunk#set_hunks(bufnr, gitgutter#diff#parse_diff(diff))
270 |     call gitgutter#diff#process_hunks(bufnr, gitgutter#hunk#hunks(bufnr))  " so the hunk summary is updated
271 | 
272 |     if empty(s:current_hunk())
273 |       call gitgutter#utility#warn('Cursor is not in a hunk')
274 |     elseif s:cursor_in_two_hunks()
275 |       let choice = input('Choose hunk: upper or lower (u/l)? ')
276 |       " Clear input
277 |       normal! :<ESC>
278 |       if choice =~ 'u'
279 |         call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 0))
280 |       elseif choice =~ 'l'
281 |         call a:op(gitgutter#diff#hunk_diff(bufnr, diff, 1))
282 |       else
283 |         call gitgutter#utility#warn('Did not recognise your choice')
284 |       endif
285 |     else
286 |       let hunk_diff = gitgutter#diff#hunk_diff(bufnr, diff)
287 | 
288 |       if a:0
289 |         let hunk_first_line = s:current_hunk()[2]
290 |         let hunk_diff = s:part_of_diff(hunk_diff, a:1-hunk_first_line, a:2-hunk_first_line)
291 |       endif
292 | 
293 |       call a:op(hunk_diff)
294 |     endif
295 |   endif
296 | endfunction
297 | 
298 | 
299 | function! s:stage(hunk_diff)
300 |   let bufnr = bufnr('')
301 | 
302 |   if gitgutter#utility#clean_smudge_filter_applies(bufnr)
303 |     let choice = input('File uses clean/smudge filter. Stage entire file (y/n)? ')
304 |     normal! :<ESC>
305 |     if choice =~ 'y'
306 |       " We are about to add the file to the index so write the buffer to
307 |       " ensure the file on disk matches it (the buffer).
308 |       write
309 |       let path = gitgutter#utility#repo_path(bufnr, 1)
310 |       " Add file to index.
311 |       let cmd = gitgutter#git(bufnr).' add '.
312 |             \ gitgutter#utility#shellescape(gitgutter#utility#filename(bufnr))
313 |       let [_, error_code] = gitgutter#utility#system(cmd)
314 |     else
315 |       return
316 |     endif
317 | 
318 |   else
319 |     let diff = s:adjust_header(bufnr, a:hunk_diff)
320 |     " Apply patch to index.
321 |     let [_, error_code] = gitgutter#utility#system(
322 |           \ gitgutter#git(bufnr).' apply --cached --unidiff-zero - ',
323 |           \ diff)
324 |   endif
325 | 
326 |   if error_code
327 |     call gitgutter#utility#warn('Patch does not apply')
328 |   else
329 |     if exists('#User#GitGutterStage')
330 |       execute 'doautocmd' s:nomodeline 'User GitGutterStage'
331 |     endif
332 |   endif
333 | 
334 |   " Refresh gitgutter's view of buffer.
335 |   call gitgutter#process_buffer(bufnr, 1)
336 | endfunction
337 | 
338 | 
339 | function! s:undo(hunk_diff)
340 |   " Apply reverse patch to buffer.
341 |   let hunk  = gitgutter#diff#parse_hunk(split(a:hunk_diff, '\n')[4])
342 |   let lines = map(split(a:hunk_diff, '\r\?\n')[5:], 'v:val[1:]')
343 |   let lnum  = hunk[2]
344 |   let added_only   = hunk[1] == 0 && hunk[3]  > 0
345 |   let removed_only = hunk[1]  > 0 && hunk[3] == 0
346 | 
347 |   if removed_only
348 |     call append(lnum, lines)
349 |   elseif added_only
350 |     execute lnum .','. (lnum+len(lines)-1) .'d _'
351 |   else
352 |     call append(lnum-1, lines[0:hunk[1]])
353 |     execute (lnum+hunk[1]) .','. (lnum+hunk[1]+hunk[3]) .'d _'
354 |   endif
355 | 
356 |   " Refresh gitgutter's view of buffer.
357 |   call gitgutter#process_buffer(bufnr(''), 1)
358 | endfunction
359 | 
360 | 
361 | function! s:preview(hunk_diff)
362 |   if g:gitgutter_preview_win_floating && exists('*nvim_set_current_win') && s:winid != 0
363 |     call nvim_set_current_win(s:winid)
364 |     return
365 |   endif
366 | 
367 |   let lines = split(a:hunk_diff, '\r\?\n')
368 |   let header = lines[0:4]
369 |   let body = lines[5:]
370 | 
371 |   call s:open_hunk_preview_window()
372 |   call s:populate_hunk_preview_window(header, body)
373 |   call s:enable_staging_from_hunk_preview_window()
374 |   if &previewwindow
375 |     call s:goto_original_window()
376 |   endif
377 | endfunction
378 | 
379 | 
380 | " Returns a new hunk diff using the specified lines from the given one.
381 | " Assumes all lines are additions.
382 | " a:first, a:last - 0-based indexes into the body of the hunk.
383 | function! s:part_of_diff(hunk_diff, first, last)
384 |   let diff_lines = split(a:hunk_diff, '\n', 1)
385 | 
386 |   " adjust 'to' line count in header
387 |   let diff_lines[4] = substitute(diff_lines[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".(a:last-a:first+1)', '')
388 | 
389 |   return join(diff_lines[0:4] + diff_lines[5+a:first:5+a:last], "\n")."\n"
390 | endfunction
391 | 
392 | 
393 | function! s:adjust_header(bufnr, hunk_diff)
394 |   let filepath = gitgutter#utility#repo_path(a:bufnr, 0)
395 |   return s:adjust_hunk_summary(s:fix_file_references(filepath, a:hunk_diff))
396 | endfunction
397 | 
398 | 
399 | " Replaces references to temp files with the actual file.
400 | function! s:fix_file_references(filepath, hunk_diff)
401 |   let lines = split(a:hunk_diff, '\n')
402 | 
403 |   let left_prefix  = matchstr(lines[2], '[abciow12]').'/'
404 |   let right_prefix = matchstr(lines[3], '[abciow12]').'/'
405 |   let quote        = lines[0][11] == '"' ? '"' : ''
406 | 
407 |   let left_file  = quote.left_prefix.a:filepath.quote
408 |   let right_file = quote.right_prefix.a:filepath.quote
409 | 
410 |   let lines[0] = 'diff --git '.left_file.' '.right_file
411 |   let lines[2] = '--- '.left_file
412 |   let lines[3] = '+++ '.right_file
413 | 
414 |   return join(lines, "\n")."\n"
415 | endfunction
416 | 
417 | 
418 | function! s:adjust_hunk_summary(hunk_diff) abort
419 |   let line_adjustment = s:line_adjustment_for_current_hunk()
420 |   let diff = split(a:hunk_diff, '\n', 1)
421 |   let diff[4] = substitute(diff[4], '+\zs\(\d\+\)', '\=submatch(1)+line_adjustment', '')
422 |   return join(diff, "\n")
423 | endfunction
424 | 
425 | 
426 | " Returns the number of lines the current hunk is offset from where it would
427 | " be if any changes above it in the file didn't exist.
428 | function! s:line_adjustment_for_current_hunk() abort
429 |   let bufnr = bufnr('')
430 |   let adj = 0
431 |   for hunk in gitgutter#hunk#hunks(bufnr)
432 |     if gitgutter#hunk#cursor_in_hunk(hunk)
433 |       break
434 |     else
435 |       let adj += hunk[1] - hunk[3]
436 |     endif
437 |   endfor
438 |   return adj
439 | endfunction
440 | 
441 | 
442 | function! s:in_hunk_preview_window()
443 |   if g:gitgutter_preview_win_floating
444 |     return win_id2win(s:winid) == winnr()
445 |   else
446 |     return &previewwindow
447 |   endif
448 | endfunction
449 | 
450 | 
451 | " Floating window: does not move cursor to floating window.
452 | " Preview window: moves cursor to preview window.
453 | function! s:open_hunk_preview_window()
454 |   let source_wrap = &wrap
455 |   let source_window = winnr()
456 | 
457 |   if g:gitgutter_preview_win_floating
458 |     if exists('*nvim_open_win')
459 |       call gitgutter#hunk#close_hunk_preview_window()
460 | 
461 |       let buf = nvim_create_buf(v:false, v:false)
462 |       " Set default width and height for now.
463 |       let s:winid = nvim_open_win(buf, v:false, g:gitgutter_floating_window_options)
464 |       call nvim_win_set_option(s:winid, 'wrap', source_wrap ? v:true : v:false)
465 |       call nvim_buf_set_option(buf, 'filetype',  'diff')
466 |       call nvim_buf_set_option(buf, 'buftype',   'acwrite')
467 |       call nvim_buf_set_option(buf, 'bufhidden', 'delete')
468 |       call nvim_buf_set_option(buf, 'swapfile',  v:false)
469 |       call nvim_buf_set_name(buf, 'gitgutter://hunk-preview')
470 | 
471 |       if g:gitgutter_close_preview_on_escape
472 |         let winnr = nvim_win_get_number(s:winid)
473 |         execute winnr.'wincmd w'
474 |         nnoremap <buffer> <silent> <Esc> :<C-U>call gitgutter#hunk#close_hunk_preview_window()<CR>
475 |         wincmd w
476 |       endif
477 | 
478 |       " Assumes cursor is in original window.
479 |       autocmd CursorMoved,TabLeave <buffer> ++once call gitgutter#hunk#close_hunk_preview_window()
480 | 
481 |       return
482 |     endif
483 | 
484 |     if exists('*popup_create')
485 |       if g:gitgutter_close_preview_on_escape
486 |         let g:gitgutter_floating_window_options.filter = function('s:close_popup_on_escape')
487 |       endif
488 | 
489 |       let s:winid = popup_create('', g:gitgutter_floating_window_options)
490 | 
491 |       call setbufvar(winbufnr(s:winid), '&filetype', 'diff')
492 |       call setwinvar(s:winid, '&wrap', source_wrap)
493 | 
494 |       return
495 |     endif
496 |   endif
497 | 
498 |   if exists('&previewpopup')
499 |     let [previewpopup, &previewpopup] = [&previewpopup, '']
500 |   endif
501 | 
502 |   " Specifying where to open the preview window can lead to the cursor going
503 |   " to an unexpected window when the preview window is closed (#769).
504 |   silent! noautocmd execute g:gitgutter_preview_win_location 'pedit gitgutter://hunk-preview'
505 |   silent! wincmd P
506 |   setlocal statusline=%{''}
507 |   doautocmd WinEnter
508 |   if exists('*win_getid')
509 |     let s:winid = win_getid()
510 |   else
511 |     let s:preview_bufnr = bufnr('')
512 |   endif
513 |   setlocal filetype=diff buftype=acwrite bufhidden=delete
514 |   let &l:wrap = source_wrap
515 |   let b:source_window = source_window
516 |   " Reset some defaults in case someone else has changed them.
517 |   setlocal noreadonly modifiable noswapfile
518 |   if g:gitgutter_close_preview_on_escape
519 |     " Ensure cursor goes to the expected window.
520 |     nnoremap <buffer> <silent> <Esc> :<C-U>execute b:source_window . "wincmd w"<Bar>pclose<CR>
521 |   endif
522 | 
523 |   if exists('&previewpopup')
524 |     let &previewpopup=previewpopup
525 |   endif
526 | endfunction
527 | 
528 | 
529 | function! s:close_popup_on_escape(winid, key)
530 |   if a:key == "\<Esc>"
531 |     call popup_close(a:winid)
532 |     return 1
533 |   endif
534 |   return 0
535 | endfunction
536 | 
537 | 
538 | " Floating window: does not care where cursor is.
539 | " Preview window: assumes cursor is in preview window.
540 | function! s:populate_hunk_preview_window(header, body)
541 |   if g:gitgutter_preview_win_floating
542 |     if exists('*nvim_open_win')
543 |       " Assumes cursor is not in previewing window.
544 |       call nvim_buf_set_var(winbufnr(s:winid), 'hunk_header', a:header)
545 | 
546 |       let [_scrolloff, &scrolloff] = [&scrolloff, 0]
547 | 
548 |       let [width, height] = s:screen_lines(a:body)
549 |       let height = min([height, g:gitgutter_floating_window_options.height])
550 |       call nvim_win_set_width(s:winid, width)
551 |       call nvim_win_set_height(s:winid, height)
552 | 
553 |       let &scrolloff=_scrolloff
554 | 
555 |       call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, [])
556 |       call nvim_buf_set_lines(winbufnr(s:winid), 0, -1, v:false, a:body)
557 |       call nvim_buf_set_option(winbufnr(s:winid), 'modified', v:false)
558 | 
559 |       let ns_id = nvim_create_namespace('GitGutter')
560 |       call nvim_buf_clear_namespace(winbufnr(s:winid), ns_id, 0, -1)
561 |       for region in gitgutter#diff_highlight#process(a:body)
562 |         let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
563 |         call nvim_buf_add_highlight(winbufnr(s:winid), ns_id, group, region[0]-1, region[2]-1, region[3])
564 |       endfor
565 | 
566 |       call nvim_win_set_cursor(s:winid, [1,0])
567 |     endif
568 | 
569 |     if exists('*popup_create')
570 |       call popup_settext(s:winid, a:body)
571 | 
572 |       for region in gitgutter#diff_highlight#process(a:body)
573 |         let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
574 |         call win_execute(s:winid, "call matchaddpos('".group."', [[".region[0].", ".region[2].", ".(region[3]-region[2]+1)."]])")
575 |       endfor
576 |     endif
577 | 
578 |   else
579 |     let b:hunk_header = a:header
580 | 
581 |     %delete _
582 |     call setline(1, a:body)
583 |     setlocal nomodified
584 | 
585 |     let [_, height] = s:screen_lines(a:body)
586 |     execute 'resize' height
587 |     1
588 | 
589 |     call clearmatches()
590 |     for region in gitgutter#diff_highlight#process(a:body)
591 |       let group = region[1] == '+' ? 'GitGutterAddIntraLine' : 'GitGutterDeleteIntraLine'
592 |       call matchaddpos(group, [[region[0], region[2], region[3]-region[2]+1]])
593 |     endfor
594 | 
595 |     1
596 |   endif
597 | endfunction
598 | 
599 | 
600 | " Calculates the number of columns and the number of screen lines the given
601 | " array of lines will take up, taking account of wrapping.
602 | function! s:screen_lines(lines)
603 |   let [_virtualedit, &virtualedit]=[&virtualedit, 'all']
604 |   let cursor = getcurpos()
605 |   normal! 0g$
606 |   let available_width = virtcol('.')
607 |   call setpos('.', cursor)
608 |   let &virtualedit=_virtualedit
609 |   let width = min([max(map(copy(a:lines), 'strdisplaywidth(v:val)')), available_width])
610 | 
611 |   if exists('*reduce')
612 |     let height = reduce(a:lines, { acc, val -> acc + strdisplaywidth(val) / width + (strdisplaywidth(val) % width == 0 ? 0 : 1) }, 0)
613 |   else
614 |     let height = eval(join(map(copy(a:lines), 'strdisplaywidth(v:val) / width + (strdisplaywidth(v:val) % width == 0 ? 0 : 1)'), '+'))
615 |   endif
616 | 
617 |   return [width, height]
618 | endfunction
619 | 
620 | 
621 | function! s:enable_staging_from_hunk_preview_window()
622 |   augroup gitgutter_hunk_preview
623 |     autocmd!
624 |     let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
625 |     execute 'autocmd BufWriteCmd <buffer='.bufnr.'> GitGutterStageHunk'
626 |   augroup END
627 | endfunction
628 | 
629 | 
630 | function! s:goto_original_window()
631 |   noautocmd execute b:source_window . "wincmd w"
632 |   doautocmd WinEnter
633 | endfunction
634 | 
635 | 
636 | function! gitgutter#hunk#close_hunk_preview_window()
637 |   let bufnr = s:winid != 0 ? winbufnr(s:winid) : s:preview_bufnr
638 |   call setbufvar(bufnr, '&modified', 0)
639 | 
640 |   if g:gitgutter_preview_win_floating
641 |     if win_id2win(s:winid) > 0
642 |       execute win_id2win(s:winid).'wincmd c'
643 |     endif
644 |   else
645 |     pclose
646 |   endif
647 | 
648 |   let s:winid = 0
649 |   let s:preview_bufnr = 0
650 | endfunction
651 | 
652 | 
653 | function gitgutter#hunk#is_preview_window_open()
654 |   if g:gitgutter_preview_win_floating
655 |     if win_id2win(s:winid) > 0
656 |       execute win_id2win(s:winid).'wincmd c'
657 |     endif
658 |   else
659 |     for i in range(1, winnr('
#39;))
660 |       if getwinvar(i, '&previewwindow')
661 |         return 1
662 |       endif
663 |     endfor
664 |   endif
665 |   return 0
666 | endfunction
667 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/sign.vim:
--------------------------------------------------------------------------------
  1 | " For older Vims without sign_place() the plugin has to manaage the sign ids.
  2 | let s:first_sign_id = 3000
  3 | let s:next_sign_id  = s:first_sign_id
  4 | " Remove-all-signs optimisation requires Vim 7.3.596+.
  5 | let s:supports_star = v:version > 703 || (v:version == 703 && has("patch596"))
  6 | 
  7 | 
  8 | function! gitgutter#sign#enable() abort
  9 |   let old_signs = g:gitgutter_signs
 10 | 
 11 |   let g:gitgutter_signs = 1
 12 |   call gitgutter#highlight#define_sign_text_highlights()
 13 | 
 14 |   if !old_signs && !g:gitgutter_highlight_lines && !g:gitgutter_highlight_linenrs
 15 |     call gitgutter#all(1)
 16 |   endif
 17 | endfunction
 18 | 
 19 | function! gitgutter#sign#disable() abort
 20 |   let g:gitgutter_signs = 0
 21 |   call gitgutter#highlight#define_sign_text_highlights()
 22 | 
 23 |   if !g:gitgutter_highlight_lines && !g:gitgutter_highlight_linenrs
 24 |     call gitgutter#sign#clear_signs(bufnr(''))
 25 |   endif
 26 | endfunction
 27 | 
 28 | function! gitgutter#sign#toggle() abort
 29 |   if g:gitgutter_signs
 30 |     call gitgutter#sign#disable()
 31 |   else
 32 |     call gitgutter#sign#enable()
 33 |   endif
 34 | endfunction
 35 | 
 36 | 
 37 | " Removes gitgutter's signs from the buffer being processed.
 38 | function! gitgutter#sign#clear_signs(bufnr) abort
 39 |   if exists('*sign_unplace')
 40 |     call sign_unplace('gitgutter', {'buffer': a:bufnr})
 41 |     return
 42 |   endif
 43 | 
 44 | 
 45 |   call s:find_current_signs(a:bufnr)
 46 | 
 47 |   let sign_ids = map(values(gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')), 'v:val.id')
 48 |   call s:remove_signs(a:bufnr, sign_ids, 1)
 49 |   call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', {})
 50 | endfunction
 51 | 
 52 | 
 53 | " Updates gitgutter's signs in the buffer being processed.
 54 | "
 55 | " modified_lines: list of [<line_number (number)>, <name (string)>]
 56 | " where name = 'added|removed|modified|modified_removed'
 57 | function! gitgutter#sign#update_signs(bufnr, modified_lines) abort
 58 |   if exists('*sign_unplace')
 59 |     " Vim is (hopefully) now quick enough to remove all signs then place new ones.
 60 |     call sign_unplace('gitgutter', {'buffer': a:bufnr})
 61 | 
 62 |     let modified_lines = s:handle_double_hunk(a:modified_lines)
 63 |     let signs = map(copy(modified_lines), '{'.
 64 |           \ '"buffer":   a:bufnr,'.
 65 |           \ '"group":    "gitgutter",'.
 66 |           \ '"name":     s:highlight_name_for_change(v:val[1]),'.
 67 |           \ '"lnum":     v:val[0],'.
 68 |           \ '"priority": g:gitgutter_sign_priority'.
 69 |           \ '}')
 70 | 
 71 |     if exists('*sign_placelist')
 72 |       call sign_placelist(signs)
 73 |       return
 74 |     endif
 75 | 
 76 |     for sign in signs
 77 |       call sign_place(0, sign.group, sign.name, sign.buffer, {'lnum': sign.lnum, 'priority': sign.priority})
 78 |     endfor
 79 |     return
 80 |   endif
 81 | 
 82 | 
 83 |   " Derive a delta between the current signs and the ones we want.
 84 |   " Remove signs from lines that no longer need a sign.
 85 |   " Upsert the remaining signs.
 86 | 
 87 |   call s:find_current_signs(a:bufnr)
 88 | 
 89 |   let new_gitgutter_signs_line_numbers = map(copy(a:modified_lines), 'v:val[0]')
 90 |   let obsolete_signs = s:obsolete_gitgutter_signs_to_remove(a:bufnr, new_gitgutter_signs_line_numbers)
 91 | 
 92 |   call s:remove_signs(a:bufnr, obsolete_signs, s:remove_all_old_signs)
 93 |   call s:upsert_new_gitgutter_signs(a:bufnr, a:modified_lines)
 94 | endfunction
 95 | 
 96 | 
 97 | "
 98 | " Internal functions
 99 | "
100 | 
101 | 
102 | function! s:find_current_signs(bufnr) abort
103 |   let gitgutter_signs = {}  " <line_number (string)>: {'id': <id (number)>, 'name': <name (string)>}
104 |   if !g:gitgutter_sign_allow_clobber
105 |     let other_signs = []      " [<line_number (number),...]
106 |   endif
107 | 
108 |   if exists('*getbufinfo')
109 |     let bufinfo = getbufinfo(a:bufnr)[0]
110 |     let signs = has_key(bufinfo, 'signs') ? bufinfo.signs : []
111 |   else
112 |     let signs = []
113 | 
114 |     redir => signlines
115 |       silent execute "sign place buffer=" . a:bufnr
116 |     redir END
117 | 
118 |     for signline in filter(split(signlines, '\n')[2:], 'v:val =~# "="')
119 |       " Typical sign line before v8.1.0614:  line=88 id=1234 name=GitGutterLineAdded
120 |       " We assume splitting is faster than a regexp.
121 |       let components = split(signline)
122 |       call add(signs, {
123 |             \ 'lnum': str2nr(split(components[0], '=')[1]),
124 |             \ 'id':   str2nr(split(components[1], '=')[1]),
125 |             \ 'name':        split(components[2], '=')[1]
126 |             \ })
127 |     endfor
128 |   endif
129 | 
130 |   for sign in signs
131 |     if sign.name =~# 'GitGutter'
132 |       " Remove orphaned signs (signs placed on lines which have been deleted).
133 |       " (When a line is deleted its sign lingers.  Subsequent lines' signs'
134 |       " line numbers are decremented appropriately.)
135 |       if has_key(gitgutter_signs, sign.lnum)
136 |         execute "sign unplace" gitgutter_signs[sign.lnum].id
137 |       endif
138 |       let gitgutter_signs[sign.lnum] = {'id': sign.id, 'name': sign.name}
139 |     else
140 |       if !g:gitgutter_sign_allow_clobber
141 |         call add(other_signs, sign.lnum)
142 |       endif
143 |     endif
144 |   endfor
145 | 
146 |   call gitgutter#utility#setbufvar(a:bufnr, 'gitgutter_signs', gitgutter_signs)
147 |   if !g:gitgutter_sign_allow_clobber
148 |     call gitgutter#utility#setbufvar(a:bufnr, 'other_signs', other_signs)
149 |   endif
150 | endfunction
151 | 
152 | 
153 | " Returns a list of [<id (number)>, ...]
154 | " Sets `s:remove_all_old_signs` as a side-effect.
155 | function! s:obsolete_gitgutter_signs_to_remove(bufnr, new_gitgutter_signs_line_numbers) abort
156 |   let signs_to_remove = []  " list of [<id (number)>, ...]
157 |   let remove_all_signs = 1
158 |   let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
159 |   for line_number in keys(old_gitgutter_signs)
160 |     if index(a:new_gitgutter_signs_line_numbers, str2nr(line_number)) == -1
161 |       call add(signs_to_remove, old_gitgutter_signs[line_number].id)
162 |     else
163 |       let remove_all_signs = 0
164 |     endif
165 |   endfor
166 |   let s:remove_all_old_signs = remove_all_signs
167 |   return signs_to_remove
168 | endfunction
169 | 
170 | 
171 | function! s:remove_signs(bufnr, sign_ids, all_signs) abort
172 |   if a:all_signs && s:supports_star && (g:gitgutter_sign_allow_clobber || empty(gitgutter#utility#getbufvar(a:bufnr, 'other_signs')))
173 |     execute "sign unplace * buffer=" . a:bufnr
174 |   else
175 |     for id in a:sign_ids
176 |       execute "sign unplace" id
177 |     endfor
178 |   endif
179 | endfunction
180 | 
181 | 
182 | function! s:upsert_new_gitgutter_signs(bufnr, modified_lines) abort
183 |   if !g:gitgutter_sign_allow_clobber
184 |     let other_signs = gitgutter#utility#getbufvar(a:bufnr, 'other_signs')
185 |   endif
186 |   let old_gitgutter_signs = gitgutter#utility#getbufvar(a:bufnr, 'gitgutter_signs')
187 | 
188 |   let modified_lines = s:handle_double_hunk(a:modified_lines)
189 | 
190 |   for line in modified_lines
191 |     let line_number = line[0]  " <number>
192 |     if g:gitgutter_sign_allow_clobber || index(other_signs, line_number) == -1  " don't clobber others' signs
193 |       let name = s:highlight_name_for_change(line[1])
194 |       if !has_key(old_gitgutter_signs, line_number)  " insert
195 |         let id = s:next_sign_id()
196 |         execute "sign place" id "line=" . line_number "name=" . name "buffer=" . a:bufnr
197 |       else  " update if sign has changed
198 |         let old_sign = old_gitgutter_signs[line_number]
199 |         if old_sign.name !=# name
200 |           execute "sign place" old_sign.id "name=" . name "buffer=" . a:bufnr
201 |         end
202 |       endif
203 |     endif
204 |   endfor
205 |   " At this point b:gitgutter_gitgutter_signs is out of date.
206 | endfunction
207 | 
208 | 
209 | " Handle special case where the first line is the site of two hunks:
210 | " lines deleted above at the start of the file, and lines deleted
211 | " immediately below.
212 | function! s:handle_double_hunk(modified_lines)
213 |   if a:modified_lines[0:1] == [[1, 'removed_first_line'], [1, 'removed']]
214 |     return [[1, 'removed_above_and_below']] + a:modified_lines[2:]
215 |   endif
216 | 
217 |   return a:modified_lines
218 | endfunction
219 | 
220 | 
221 | function! s:next_sign_id() abort
222 |   let next_id = s:next_sign_id
223 |   let s:next_sign_id += 1
224 |   return next_id
225 | endfunction
226 | 
227 | 
228 | " Only for testing.
229 | function! gitgutter#sign#reset()
230 |   let s:next_sign_id  = s:first_sign_id
231 | endfunction
232 | 
233 | 
234 | function! s:highlight_name_for_change(text) abort
235 |   if a:text ==# 'added'
236 |     return 'GitGutterLineAdded'
237 |   elseif a:text ==# 'removed'
238 |     return 'GitGutterLineRemoved'
239 |   elseif a:text ==# 'removed_first_line'
240 |     return 'GitGutterLineRemovedFirstLine'
241 |   elseif a:text ==# 'modified'
242 |     return 'GitGutterLineModified'
243 |   elseif a:text ==# 'modified_removed'
244 |     return 'GitGutterLineModifiedRemoved'
245 |   elseif a:text ==# 'removed_above_and_below'
246 |     return 'GitGutterLineRemovedAboveAndBelow'
247 |   endif
248 | endfunction
249 | 
250 | 
251 | 


--------------------------------------------------------------------------------
/autoload/gitgutter/utility.vim:
--------------------------------------------------------------------------------
  1 | function! gitgutter#utility#supports_overscore_sign()
  2 |   if gitgutter#utility#windows()
  3 |     return &encoding ==? 'utf-8'
  4 |   else
  5 |     return &termencoding ==? &encoding || &termencoding == ''
  6 |   endif
  7 | endfunction
  8 | 
  9 | " True for git v1.7.2+.
 10 | function! gitgutter#utility#git_supports_command_line_config_override() abort
 11 |   if !exists('s:c_flag')
 12 |     let [_, error_code] = gitgutter#utility#system(gitgutter#git().' -c foo.bar=baz --version')
 13 |     let s:c_flag = !error_code
 14 |   endif
 15 |   return s:c_flag
 16 | endfunction
 17 | 
 18 | function! gitgutter#utility#setbufvar(buffer, varname, val)
 19 |   let buffer = +a:buffer
 20 |   " Default value for getbufvar() was introduced in Vim 7.3.831.
 21 |   let ggvars = getbufvar(buffer, 'gitgutter')
 22 |   if type(ggvars) == type('')
 23 |     unlet ggvars
 24 |     let ggvars = {}
 25 |     call setbufvar(buffer, 'gitgutter', ggvars)
 26 |   endif
 27 |   let ggvars[a:varname] = a:val
 28 | endfunction
 29 | 
 30 | function! gitgutter#utility#getbufvar(buffer, varname, ...)
 31 |   let buffer = +a:buffer
 32 |   let ggvars = getbufvar(buffer, 'gitgutter')
 33 |   if type(ggvars) == type({}) && has_key(ggvars, a:varname)
 34 |     return ggvars[a:varname]
 35 |   endif
 36 |   if a:0
 37 |     return a:1
 38 |   endif
 39 | endfunction
 40 | 
 41 | function! gitgutter#utility#warn(message) abort
 42 |   echohl WarningMsg
 43 |   echo a:message
 44 |   echohl None
 45 |   let v:warningmsg = a:message
 46 | endfunction
 47 | 
 48 | function! gitgutter#utility#warn_once(bufnr, message, key) abort
 49 |   if empty(gitgutter#utility#getbufvar(a:bufnr, a:key))
 50 |     call gitgutter#utility#setbufvar(a:bufnr, a:key, '1')
 51 |     echohl WarningMsg
 52 |     redraw | echom a:message
 53 |     echohl None
 54 |     let v:warningmsg = a:message
 55 |   endif
 56 | endfunction
 57 | 
 58 | " Returns truthy when the buffer's file should be processed; and falsey when it shouldn't.
 59 | " This function does not and should not make any system calls.
 60 | function! gitgutter#utility#is_active(bufnr) abort
 61 |   return gitgutter#utility#getbufvar(a:bufnr, 'enabled') &&
 62 |         \ !pumvisible() &&
 63 |         \ s:is_file_buffer(a:bufnr) &&
 64 |         \ s:exists_file(a:bufnr) &&
 65 |         \ s:not_git_dir(a:bufnr)
 66 | endfunction
 67 | 
 68 | function! s:not_git_dir(bufnr) abort
 69 |   return gitgutter#utility#dir(a:bufnr) !~ '[/\\]\.git\($\|[/\\]\)'
 70 | endfunction
 71 | 
 72 | function! s:is_file_buffer(bufnr) abort
 73 |   return empty(getbufvar(a:bufnr, '&buftype'))
 74 | endfunction
 75 | 
 76 | " From tpope/vim-fugitive
 77 | function! s:winshell()
 78 |   return &shell =~? 'cmd' || exists('+shellslash') && !&shellslash
 79 | endfunction
 80 | 
 81 | " From tpope/vim-fugitive
 82 | function! gitgutter#utility#shellescape(arg) abort
 83 |   if a:arg =~ '^[A-Za-z0-9_/.-]\+
#39;
 84 |     return a:arg
 85 |   elseif s:winshell()
 86 |     return '"' . substitute(substitute(a:arg, '"', '""', 'g'), '%', '"%"', 'g') . '"'
 87 |   else
 88 |     return shellescape(a:arg)
 89 |   endif
 90 | endfunction
 91 | 
 92 | function! gitgutter#utility#file(bufnr)
 93 |   return s:abs_path(a:bufnr, 1)
 94 | endfunction
 95 | 
 96 | " Not shellescaped
 97 | function! gitgutter#utility#extension(bufnr) abort
 98 |   return fnamemodify(s:abs_path(a:bufnr, 0), ':e')
 99 | endfunction
100 | 
101 | function! gitgutter#utility#system(cmd, ...) abort
102 |   call gitgutter#debug#log(a:cmd, a:000)
103 | 
104 |   call s:use_known_shell()
105 |   let prev_error_code = v:shell_error
106 |   silent let output = (a:0 == 0) ? system(a:cmd) : system(a:cmd, a:1)
107 |   let error_code = v:shell_error
108 |   silent call system('exit ' . prev_error_code)
109 |   call s:restore_shell()
110 | 
111 |   return [output, error_code]
112 | endfunction
113 | 
114 | function! gitgutter#utility#has_repo_path(bufnr)
115 |   return index(['', -1, -2], gitgutter#utility#repo_path(a:bufnr, 0)) == -1
116 | endfunction
117 | 
118 | " Path of file relative to repo root.
119 | "
120 | " *     empty string - not set
121 | " * non-empty string - path
122 | " *               -1 - pending
123 | " *               -2 - not tracked by git
124 | " *               -3 - assume unchanged
125 | function! gitgutter#utility#repo_path(bufnr, shellesc) abort
126 |   let p = gitgutter#utility#getbufvar(a:bufnr, 'path', '')
127 |   return a:shellesc ? gitgutter#utility#shellescape(p) : p
128 | endfunction
129 | 
130 | 
131 | let s:set_path_handler = {}
132 | 
133 | function! s:set_path_handler.out(buffer, listing) abort
134 |   let listing = s:strip_trailing_new_line(a:listing)
135 |   let [status, path] = [listing[0], listing[2:]]
136 |   if status =~# '[a-z]'
137 |     call gitgutter#utility#setbufvar(a:buffer, 'path', -3)
138 |   else
139 |     call gitgutter#utility#setbufvar(a:buffer, 'path', path)
140 |   endif
141 | 
142 |   if type(self.continuation) == type(function('tr'))
143 |     call self.continuation()
144 |   else
145 |     call call(self.continuation.function, self.continuation.arguments)
146 |   endif
147 | endfunction
148 | 
149 | function! s:set_path_handler.err(buffer) abort
150 |   call gitgutter#utility#setbufvar(a:buffer, 'path', -2)
151 | endfunction
152 | 
153 | 
154 | " continuation - a funcref or hash to call after setting the repo path asynchronously.
155 | "
156 | " Returns 'async' if the the path is set asynchronously, 0 otherwise.
157 | function! gitgutter#utility#set_repo_path(bufnr, continuation) abort
158 |   " Values of path:
159 |   " * non-empty string - path
160 |   " *               -1 - pending
161 |   " *               -2 - not tracked by git
162 |   " *               -3 - assume unchanged
163 | 
164 |   call gitgutter#utility#setbufvar(a:bufnr, 'path', -1)
165 |   let cmd = gitgutter#git(a:bufnr).' ls-files -v --error-unmatch --full-name -z -- '.
166 |         \ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr))
167 | 
168 |   if g:gitgutter_async && gitgutter#async#available() && !has('vim_starting')
169 |     let handler = copy(s:set_path_handler)
170 |     let handler.continuation = a:continuation
171 |     call gitgutter#async#execute(cmd, a:bufnr, handler)
172 |     return 'async'
173 |   endif
174 | 
175 |   let [listing, error_code] = gitgutter#utility#system(cmd)
176 | 
177 |   if error_code
178 |     call gitgutter#utility#setbufvar(a:bufnr, 'path', -2)
179 |     return
180 |   endif
181 | 
182 |   let listing = s:strip_trailing_new_line(listing)
183 |   let [status, path] = [listing[0], listing[2:]]
184 |   if status =~# '[a-z]'
185 |     call gitgutter#utility#setbufvar(a:bufnr, 'path', -3)
186 |   else
187 |     call gitgutter#utility#setbufvar(a:bufnr, 'path', path)
188 |   endif
189 | endfunction
190 | 
191 | 
192 | function! gitgutter#utility#clean_smudge_filter_applies(bufnr)
193 |   let filtered = gitgutter#utility#getbufvar(a:bufnr, 'filter', -1)
194 |   if filtered == -1
195 |     let cmd = gitgutter#git(a:bufnr).' check-attr filter -- '.
196 |           \ gitgutter#utility#shellescape(gitgutter#utility#filename(a:bufnr))
197 |     let [out, _] = gitgutter#utility#system(cmd)
198 |     let filtered = out !~ 'unspecified'
199 |     call gitgutter#utility#setbufvar(a:bufnr, 'filter', filtered)
200 |   endif
201 |   return filtered
202 | endfunction
203 | 
204 | 
205 | function! s:use_known_shell() abort
206 |   if has('unix') && &shell !=# 'sh'
207 |     let [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote] = [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote]
208 |     let &shell = 'sh'
209 |     set shellcmdflag=-c shellredir=>%s\ 2>&1
210 |   endif
211 |   if has('win32') && (&shell =~# 'pwsh' || &shell =~# 'powershell')
212 |     let [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote] = [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote]
213 |     let &shell = 'cmd.exe'
214 |     set shellcmdflag=/s\ /c shellredir=>%s\ 2>&1 shellpipe=>%s\ 2>&1 shellquote= shellxquote="
215 |   endif
216 | endfunction
217 | 
218 | function! s:restore_shell() abort
219 |   if (has('unix') || has('win32')) && exists('s:shell')
220 |     let [&shell, &shellcmdflag, &shellredir, &shellpipe, &shellquote, &shellxquote] = [s:shell, s:shellcmdflag, s:shellredir, s:shellpipe, s:shellquote, s:shellxquote]
221 |   endif
222 | endfunction
223 | 
224 | function! gitgutter#utility#get_diff_base(bufnr)
225 |   let p = resolve(expand('#'.a:bufnr.':p'))
226 |   let ml = matchlist(p, '\v^fugitive:/.*/(\x{40,})/')
227 |   if !empty(ml) && !empty(ml[1])
228 |     return ml[1].'^'
229 |   endif
230 |   return g:gitgutter_diff_base
231 | endfunction
232 | 
233 | " Returns the original path (shellescaped) at the buffer's diff base.
234 | function! gitgutter#utility#base_path(bufnr)
235 |   let diffbase = gitgutter#utility#get_diff_base(a:bufnr)
236 | 
237 |   " If we already know the original path at this diff base, return it.
238 |   let basepath = gitgutter#utility#getbufvar(a:bufnr, 'basepath', '')
239 |   if !empty(basepath)
240 |     " basepath is diffbase:path
241 |     " Note that path can also contain colons.
242 |     " List destructuring / unpacking where the remaining items are assigned
243 |     " to a single variable (:help let-unpack) is only available in v8.2.0540.
244 |     let parts = split(basepath, ':', 1)
245 |     let base = parts[0]
246 |     let bpath = join(parts[1:], ':')
247 | 
248 |     if base == diffbase
249 |       return gitgutter#utility#shellescape(bpath)
250 |     endif
251 |   endif
252 | 
253 |   " Obtain buffers' paths.
254 |   let current_paths = {}
255 |   for bufnr in range(1, bufnr('
#39;) + 1)
256 |     if gitgutter#utility#has_repo_path(bufnr)
257 |       let current_paths[gitgutter#utility#repo_path(bufnr, 0)] = bufnr
258 |     endif
259 |   endfor
260 | 
261 |   " Get a list of file renames at the buffer's diff base.
262 |   " Store the original paths on any corresponding buffers.
263 |   " If the buffer's file was one of them, return its original path.
264 |   let op = ''
265 |   let renames = s:obtain_file_renames(a:bufnr, diffbase)
266 |   for [current, original] in items(renames)
267 |     if has_key(current_paths, current)
268 |       let bufnr = current_paths[current]
269 |       let basepath = diffbase.':'.original
270 |       call gitgutter#utility#setbufvar(bufnr, 'basepath', basepath)
271 | 
272 |       if bufnr == a:bufnr
273 |         let op = original
274 |       endif
275 |     endif
276 |   endfor
277 |   if !empty(op)
278 |     return gitgutter#utility#shellescape(op)
279 |   endif
280 | 
281 |   " Buffer's file was not renamed, so store current path and return it.
282 |   let current_path = gitgutter#utility#repo_path(a:bufnr, 0)
283 |   let basepath = diffbase.':'.current_path
284 |   call gitgutter#utility#setbufvar(a:bufnr, 'basepath', basepath)
285 |   return gitgutter#utility#shellescape(current_path)
286 | endfunction
287 | 
288 | " Returns a dict of current path to original path at the given base.
289 | function! s:obtain_file_renames(bufnr, base)
290 |   let renames = {}
291 |   let cmd = gitgutter#git(a:bufnr)
292 |   if gitgutter#utility#git_supports_command_line_config_override()
293 |     let cmd .= ' -c "core.safecrlf=false"'
294 |   endif
295 |   let cmd .= ' diff --diff-filter=R --name-status '.a:base
296 |   let [out, error_code] = gitgutter#utility#system(cmd)
297 |   if error_code
298 |     " Assume the problem is the diff base.
299 |     call gitgutter#utility#warn('g:gitgutter_diff_base ('.a:base.') is invalid')
300 |     return {}
301 |   endif
302 |   for line in split(out, '\n')
303 |     let fields = split(line)
304 |     if len(fields) != 3
305 |       call gitgutter#utility#warn('gitgutter: unable to list renamed files: '.line)
306 |       return {}
307 |     endif
308 |     let [original, current] = fields[1:]
309 |     let renames[current] = original
310 |   endfor
311 |   return renames
312 | endfunction
313 | 
314 | function! s:abs_path(bufnr, shellesc)
315 |   let p = resolve(expand('#'.a:bufnr.':p'))
316 | 
317 |   " Remove extra parts from fugitive's filepaths
318 |   let p = substitute(substitute(p, '^fugitive:', '', ''), '\v\.git/\x{40,}/', '', '')
319 | 
320 |   return a:shellesc ? gitgutter#utility#shellescape(p) : p
321 | endfunction
322 | 
323 | " Shellescaped
324 | function! gitgutter#utility#dir(bufnr) abort
325 |   return gitgutter#utility#shellescape(fnamemodify(s:abs_path(a:bufnr, 0), ':h'))
326 | endfunction
327 | 
328 | " Not shellescaped.
329 | function! gitgutter#utility#filename(bufnr) abort
330 |   return fnamemodify(s:abs_path(a:bufnr, 0), ':t')
331 | endfunction
332 | 
333 | function! s:exists_file(bufnr) abort
334 |   return filereadable(s:abs_path(a:bufnr, 0))
335 | endfunction
336 | 
337 | " Get rid of any trailing new line or SOH character.
338 | "
339 | " git ls-files -z produces output with null line termination.
340 | " Vim's system() replaces any null characters in the output
341 | " with SOH (start of header), i.e. ^A.
342 | function! s:strip_trailing_new_line(line) abort
343 |   return substitute(a:line, '[[:cntrl:]]
#39;, '', '')
344 | endfunction
345 | 
346 | function! gitgutter#utility#windows()
347 |   return has('win64') || has('win32') || has('win16')
348 | endfunction
349 | 


--------------------------------------------------------------------------------
/doc/gitgutter.txt:
--------------------------------------------------------------------------------
  1 | *gitgutter.txt*              A Vim plugin which shows a git diff in the gutter.
  2 | 
  3 | 
  4 |                            Vim GitGutter
  5 | 
  6 | 
  7 | Author:            Andy Stewart <https://airbladesoftware.com/>
  8 | Plugin Homepage:   <https://github.com/airblade/vim-gitgutter>
  9 | 
 10 | 
 11 | ===============================================================================
 12 | CONTENTS                                                            *gitgutter*
 13 | 
 14 |   Introduction ................. |gitgutter-introduction|
 15 |   Installation ................. |gitgutter-installation|
 16 |   Windows      ................. |gitgutter-windows|
 17 |   Commands ..................... |gitgutter-commands|
 18 |   Mappings ..................... |gitgutter-mappings|
 19 |   Autocommand .................. |gitgutter-autocommand|
 20 |   Status line .................. |gitgutter-statusline|
 21 |   Options ...................... |gitgutter-options|
 22 |   Highlights ................... |gitgutter-highlights|
 23 |   FAQ .......................... |gitgutter-faq|
 24 |   TROUBLESHOOTING .............. |gitgutter-troubleshooting|
 25 | 
 26 | 
 27 | ===============================================================================
 28 | INTRODUCTION                                           *gitgutter-introduction*
 29 | 
 30 | GitGutter is a Vim plugin which shows a git diff in the sign column.
 31 | It shows which lines have been added, modified, or removed.  You can also
 32 | preview, stage, and undo individual hunks.  The plugin also provides a hunk
 33 | text object.
 34 | 
 35 | The signs are always up to date and the plugin never saves your buffer.
 36 | 
 37 | The name "gitgutter" comes from the Sublime Text 3 plugin which inspired this
 38 | one in 2013.
 39 | 
 40 | 
 41 | ===============================================================================
 42 | INSTALLATION                                           *gitgutter-installation*
 43 | 
 44 | First, use your favourite package manager, or use Vim's built-in package
 45 | support.
 46 | 
 47 | Vim:~
 48 | >
 49 |   mkdir -p ~/.vim/pack/airblade/start
 50 |   cd ~/.vim/pack/airblade/start
 51 |   git clone https://github.com/airblade/vim-gitgutter.git
 52 |   vim -u NONE -c "helptags vim-gitgutter/doc" -c q
 53 | <
 54 | 
 55 | Neovim:~
 56 | >
 57 |   mkdir -p ~/.config/nvim/pack/airblade/start
 58 |   cd ~/.config/nvim/pack/airblade/start
 59 |   git clone https://github.com/airblade/vim-gitgutter.git
 60 |   nvim -u NONE -c "helptags vim-gitgutter/doc" -c q
 61 | <
 62 | 
 63 | Second, ensure your 'updatetime' and 'signcolumn' options are set appropriately.
 64 | 
 65 | When you make a change to a file tracked by git, the diff markers should
 66 | appear automatically after a short delay.  The delay is governed by vim's
 67 | 'updatetime' option; the default value is `4000`, i.e. 4 seconds, but I
 68 | suggest reducing it to around 100ms (add `set updatetime=100` to your vimrc).
 69 | Note 'updatetime' also controls the delay before vim writes its swap file.
 70 | 
 71 | The 'signcolumn' option can have any value except "off".
 72 | 
 73 | 
 74 | ===============================================================================
 75 | WINDOWS                                                     *gitgutter-windows*
 76 | 
 77 | There is a potential risk on Windows due to `cmd.exe` prioritising the current
 78 | folder over folders in `PATH`.  If you have a file named `git.*` (i.e. with
 79 | any extension in `PATHEXT`) in your current folder, it will be executed
 80 | instead of git whenever the plugin calls git.
 81 | 
 82 | You can avoid this risk by configuring the full path to your git executable.
 83 | For example:
 84 | >
 85 |     " This path probably won't work
 86 |     let g:gitgutter_git_executable = 'C:\Program Files\Git\bin\git.exe'
 87 | <
 88 | 
 89 | Unfortunately I don't know the correct escaping for the path - if you do,
 90 | please let me know!
 91 | 
 92 | 
 93 | ===============================================================================
 94 | COMMANDS                                                   *gitgutter-commands*
 95 | 
 96 | Commands for turning vim-gitgutter on and off:~
 97 | 
 98 |                                                   *gitgutter-:GitGutterDisable*
 99 | :GitGutterDisable       Turn vim-gitgutter off for all buffers.
100 | 
101 |                                                    *gitgutter-:GitGutterEnable*
102 | :GitGutterEnable        Turn vim-gitgutter on for all buffers.
103 | 
104 |                                                    *gitgutter-:GitGutterToggle*
105 | :GitGutterToggle        Toggle vim-gitgutter on or off for all buffers.
106 | 
107 |                                             *gitgutter-:GitGutterBufferDisable*
108 | :GitGutterBufferDisable Turn vim-gitgutter off for current buffer.
109 | 
110 |                                              *gitgutter-:GitGutterBufferEnable*
111 | :GitGutterBufferEnable  Turn vim-gitgutter on for current buffer.
112 | 
113 |                                              *gitgutter-:GitGutterBufferToggle*
114 | :GitGutterBufferToggle  Toggle vim-gitgutter on or off for current buffer.
115 | 
116 |                                                          *gitgutter-:GitGutter*
117 | :GitGutter              Update signs for the current buffer.  You shouldn't
118 |                         need to run this.
119 | 
120 |                                                       *gitgutter-:GitGutterAll*
121 | :GitGutterAll           Update signs for all buffers.  You shouldn't need to
122 |                         run this.
123 | 
124 | 
125 | Commands for turning signs on and off (defaults to on):~
126 | 
127 |                                               *gitgutter-:GitGutterSignsEnable*
128 | :GitGutterSignsEnable   Show signs for the diff.
129 | 
130 |                                              *gitgutter-:GitGutterSignsDisable*
131 | :GitGutterSignsDisable  Do not show signs for the diff.
132 | 
133 |                                               *gitgutter-:GitGutterSignsToggle*
134 | :GitGutterSignsToggle   Toggle signs on or off.
135 | 
136 | 
137 | Commands for turning line highlighting on and off (defaults to off):~
138 | 
139 |                                      *gitgutter-:GitGutterLineHighlightsEnable*
140 | :GitGutterLineHighlightsEnable  Turn on line highlighting.
141 | 
142 |                                     *gitgutter-:GitGutterLineHighlightsDisable*
143 | :GitGutterLineHighlightsDisable Turn off line highlighting.
144 | 
145 |                                      *gitgutter-:GitGutterLineHighlightsToggle*
146 | :GitGutterLineHighlightsToggle  Turn line highlighting on or off.
147 | 
148 | 
149 | Commands for turning line number highlighting on and off (defaults to off):~
150 | NOTE: This feature requires Neovim 0.3.2 or higher.
151 | 
152 |                                    *gitgutter-:GitGutterLineNrHighlightsEnable*
153 | :GitGutterLineNrHighlightsEnable  Turn on line highlighting.
154 | 
155 |                                   *gitgutter-:GitGutterLineNrHighlightsDisable*
156 | :GitGutterLineNrHighlightsDisable Turn off line highlighting.
157 | 
158 |                                    *gitgutter-:GitGutterLineNrHighlightsToggle*
159 | :GitGutterLineNrHighlightsToggle  Turn line highlighting on or off.
160 | 
161 | 
162 | Commands for jumping between hunks:~
163 | 
164 |                                                  *gitgutter-:GitGutterNextHunk*
165 | :GitGutterNextHunk      Jump to the next [count] hunk.
166 | 
167 |                                                  *gitgutter-:GitGutterPrevHunk*
168 | :GitGutterPrevHunk      Jump to the previous [count] hunk.
169 | 
170 |                                                  *gitgutter-:GitGutterQuickFix*
171 | :GitGutterQuickFix      Load all hunks into the |quickfix| list.  Note this
172 |                         ignores any unsaved changes in your buffers. The
173 |                         |g:gitgutter_use_location_list| option can be set to
174 |                         populate the location list of the current window
175 |                         instead.  Use |:copen| (or |:lopen|) to open a buffer
176 |                         containing the search results in linked form; or add a
177 |                         custom command like this:
178 | >
179 |                           command! Gqf GitGutterQuickFix | copen
180 | <
181 |                                                  *gitgutter-:GitGutterQuickFixCurrentFile*
182 | :GitGutterQuickFixCurrentFile     Same as :GitGutterQuickFix, but only load hunks for
183 |                                   the file in the focused buffer. This has the same
184 |                                   functionality as :GitGutterQuickFix when the focused
185 |                                   buffer is empty.
186 | 
187 | 
188 | Commands for operating on a hunk:~
189 | 
190 |                                                 *gitgutter-:GitGutterStageHunk*
191 | :GitGutterStageHunk     Stage the hunk the cursor is in.  Use a visual selection
192 |                         to stage part of an (additions-only) hunk; or use a
193 |                         range.
194 | 
195 |                         To stage part of any hunk, first |GitGutterPreviewHunk|
196 |                         it, then move to the preview window, delete the lines
197 |                         you do not want to stage, and |write| or
198 |                         |GitGutterStageHunk|.
199 | 
200 |                                                  *gitgutter-:GitGutterUndoHunk*
201 | :GitGutterUndoHunk      Undo the hunk the cursor is in.
202 | 
203 |                                               *gitgutter-:GitGutterPreviewHunk*
204 | :GitGutterPreviewHunk   Preview the hunk the cursor is in or, if you are using
205 |                         floating preview windows in Neovim and the window is
206 |                         already open, move the cursor into the window.
207 | 
208 |                         To stage part of the hunk, move to the preview window,
209 |                         delete any lines you do not want to stage, and |write|
210 |                         or |GitGutterStageHunk|.
211 | 
212 |                         To close a non-floating preview window use |:pclose|
213 |                         or |CTRL-W_z| or |CTRL-W_CTRL-Z|; or normal window-
214 |                         closing (|:quit| or |:close| or |CTRL-W_c|) if your cursor
215 |                         is in the preview window.
216 | 
217 |                         To close a floating window when the cursor is in the
218 |                         original buffer, move the cursor.
219 | 
220 |                         To close a floating window when the cursor is in the
221 |                         floating window use normal window-closing, or move to
222 |                         the original window with |CTRL-W_p|.  Alternatively set
223 |                         |g:gitgutter_close_preview_on_escape| and use <Esc>.
224 | 
225 |                         Two functions are available for your own logic:
226 | >
227 |                           gitgutter#hunk#is_preview_window_open()
228 |                           gitgutter#hunk#close_hunk_preview_window()
229 | <
230 | 
231 | Commands for folds:~
232 | 
233 |                                                      *gitgutter-:GitGutterFold*
234 | :GitGutterFold          Fold all unchanged lines.  Execute again to undo.
235 | 
236 | 
237 | Other commands:~
238 | 
239 |                                                  *gitgutter-:GitGutterDiffOrig*
240 | :GitGutterDiffOrig      Similar to |:DiffOrig| but shows gitgutter's diff.
241 | 
242 | 
243 | ===============================================================================
244 | AUTOCOMMANDS                                           *gitgutter-autocommands*
245 | 
246 | User GitGutter~
247 | 
248 | After updating a buffer's signs vim-gitgutter fires a |User| |autocmd| with the
249 | event GitGutter.  You can listen for this event, for example:
250 | >
251 |   autocmd User GitGutter call updateMyStatusLine()
252 | <
253 | A dictionary `g:gitgutter_hook_context` is made available during its execution,
254 | which contains an entry `bufnr` that contains the buffer number being updated.
255 | 
256 | User GitGutterStage~
257 | 
258 | After staging a hunk or part of a hunk vim-gitgutter fires a |User| |autocmd|
259 | with the event GitGutterStage.  Staging always happens in the current buffer.
260 | 
261 | ===============================================================================
262 | MAPPINGS                                                   *gitgutter-mappings*
263 | 
264 | You can disable all these mappings with:
265 | >
266 |     let g:gitgutter_map_keys = 0
267 | <
268 | 
269 | Hunk operations:~
270 | 
271 | These can be repeated with `.` if you have vim-repeat installed.
272 | 
273 |                                                          *gitgutter-<Leader>hp*
274 | <Leader>hp              Preview the hunk under the cursor.
275 | 
276 |                                                          *gitgutter-<Leader>hs*
277 | <Leader>hs              Stage the hunk under the cursor.
278 | 
279 |                                                          *gitgutter-<Leader>hu*
280 | <Leader>hu              Undo the hunk under the cursor.
281 | 
282 | You can change these mappings like this:
283 | >
284 |     nmap ghp <Plug>(GitGutterPreviewHunk)
285 |     nmap ghs <Plug>(GitGutterStageHunk)
286 |     nmap ghu <Plug>(GitGutterUndoHunk)
287 | <
288 | 
289 | Hunk jumping:~
290 | 
291 |                                                                  *gitgutter-]c*
292 | ]c                      Jump to the next [count] hunk.
293 | 
294 |                                                                  *gitgutter-[c*
295 | [c                      Jump to the previous [count] hunk.
296 | 
297 | You can change these mappings like this:
298 | >
299 |     nmap [c <Plug>(GitGutterPrevHunk)
300 |     nmap ]c <Plug>(GitGutterNextHunk)
301 | <
302 | 
303 | Hunk text object:~
304 | 
305 |                           *gitgutter-ic* *gitgutter-ac* *gitgutter-text-object*
306 | "ic" operates on the current hunk's lines.  "ac" does the same but also includes
307 | trailing empty lines.
308 | >
309 |     omap ic <Plug>(GitGutterTextObjectInnerPending)
310 |     omap ac <Plug>(GitGutterTextObjectOuterPending)
311 |     xmap ic <Plug>(GitGutterTextObjectInnerVisual)
312 |     xmap ac <Plug>(GitGutterTextObjectOuterVisual)
313 | <
314 | 
315 | 
316 | ===============================================================================
317 | STATUS LINE                                              *gitgutter-statusline*
318 | 
319 | 
320 | Call the `GitGutterGetHunkSummary()` function from your status line to get a
321 | list of counts of added, modified, and removed lines in the current buffer.
322 | For example:
323 | >
324 |     " Your vimrc
325 |     function! GitStatus()
326 |       let [a,m,r] = GitGutterGetHunkSummary()
327 |       return printf('+%d ~%d -%d', a, m, r)
328 |     endfunction
329 |     set statusline+=%{GitStatus()}
330 | <
331 | 
332 | 
333 | ===============================================================================
334 | OPTIONS                                                     *gitgutter-options*
335 | 
336 | The most important option is 'updatetime' which determines how long (in
337 | milliseconds) the plugin will wait after you stop typing before it updates the
338 | signs.  Vim's default is 4000.  I recommend 100.  Note this also controls how
339 | long vim waits before writing its swap file.
340 | 
341 | Most important option:~
342 | 
343 |     'updatetime'
344 | 
345 | Git:~
346 | 
347 |     |g:gitgutter_git_executable|
348 |     |g:gitgutter_git_args|
349 |     |g:gitgutter_diff_args|
350 |     |g:gitgutter_diff_relative_to|
351 |     |g:gitgutter_diff_base|
352 | 
353 | Grep:~
354 | 
355 |     |g:gitgutter_grep|
356 | 
357 | Signs:~
358 | 
359 |     |g:gitgutter_signs|
360 |     |g:gitgutter_highlight_lines|
361 |     |g:gitgutter_highlight_linenrs|
362 |     |g:gitgutter_max_signs|
363 |     |g:gitgutter_sign_priority|
364 |     |g:gitgutter_sign_allow_clobber|
365 |     |g:gitgutter_sign_added|
366 |     |g:gitgutter_sign_modified|
367 |     |g:gitgutter_sign_removed|
368 |     |g:gitgutter_sign_removed_first_line|
369 |     |g:gitgutter_sign_modified_removed|
370 |     |g:gitgutter_set_sign_backgrounds|
371 | 
372 | Hunk jumping:~
373 | 
374 |     |g:gitgutter_show_msg_on_hunk_jumping|
375 | 
376 | Hunk previews:~
377 | 
378 |     |g:gitgutter_preview_win_floating|
379 |     |g:gitgutter_floating_window_options|
380 |     |g:gitgutter_close_preview_on_escape|
381 | 
382 | Terminal:~
383 | 
384 |     |g:gitgutter_terminal_reports_focus|
385 | 
386 | General:~
387 | 
388 |     |g:gitgutter_enabled|
389 |     |g:gitgutter_map_keys|
390 |     |g:gitgutter_async|
391 |     |g:gitgutter_log|
392 |     |g:gitgutter_use_location_list|
393 | 
394 | 
395 |                                              *g:gitgutter_preview_win_location*
396 | Default: 'bo'
397 | 
398 | This option determines where the preview window pops up as a result of the
399 | :GitGutterPreviewHunk command. Other plausible values are 'to', 'bel', 'abo'.
400 | See the end of the |opening-window| docs.
401 | 
402 |                                                    *g:gitgutter_git_executable*
403 | Default: 'git'
404 | 
405 | This option determines what git binary to use.  Set this if git is not on your
406 | path.
407 | 
408 |                                                          *g:gitgutter_git_args*
409 | Default: empty
410 | 
411 | Use this option to pass any extra arguments to git when running git-diff.
412 | For example:
413 | >
414 |     let g:gitgutter_git_args = '--git-dir=""'
415 | <
416 | 
417 |                                                         *g:gitgutter_diff_args*
418 | Default: empty
419 | 
420 | Use this option to pass any extra arguments to git-diff.  For example:
421 | >
422 |     let g:gitgutter_diff_args = '-w'
423 | <
424 | 
425 |                                                  *g:gitgutter_diff_relative_to*
426 | Default: empty
427 | 
428 | By default buffers are diffed against the index.  Use this option to diff against
429 | the working tree.  For example:
430 | >
431 |     let g:gitgutter_diff_relative_to = 'working_tree'
432 | <
433 | 
434 |                                                         *g:gitgutter_diff_base*
435 | Default: empty
436 | 
437 | By default buffers are diffed against the index.  Use this option to diff against
438 | a revision instead.  For example:
439 | >
440 |     let g:gitgutter_diff_base = '<some commit SHA>'
441 | <
442 | 
443 | If you are looking at a previous version of a file with Fugitive (e.g.
444 | via :0Gclog), gitgutter sets the diff base to the parent of the current revision.
445 | 
446 | This setting is ignore when the diff is relative to the working tree
447 | (|g:gitgutter_diff_relative_to|).
448 | 
449 |                                                              *g:gitgutter_grep*
450 | Default: 'grep'
451 | 
452 | The plugin pipes the output of git-diff into grep to minimise the amount of data
453 | vim has to process.  Set this option if grep is not on your path.
454 | 
455 | grep must produce plain-text output without any ANSI escape codes or colours.
456 | Use this option to turn off colours if necessary.
457 | >
458 |     let g:gitgutter_grep = 'grep --color=never'
459 | <
460 | If you do not want to use grep at all (perhaps to debug why signs are not
461 | showing), set this option to an empty string:
462 | >
463 |     let g:gitgutter_grep = ''
464 | <
465 | 
466 |                                                             *g:gitgutter_signs*
467 | Default: 1
468 | 
469 | Determines whether or not to show signs.
470 | 
471 |                                                   *g:gitgutter_highlight_lines*
472 | Default: 0
473 | 
474 | Determines whether or not to show line highlights.
475 | 
476 |                                                 *g:gitgutter_highlight_linenrs*
477 | Default: 0
478 | 
479 | Determines whether or not to show line number highlights.
480 | 
481 |                                                         *g:gitgutter_max_signs*
482 | Default: 500 (Vim < 8.1.0614, Neovim < 0.4.0)
483 |           -1 (otherwise)
484 | 
485 | Sets the maximum number of signs to show in a buffer.  Vim is slow at updating
486 | signs, so to avoid slowing down the GUI the number of signs is capped.  When
487 | the number of changed lines exceeds this value, the plugin removes all signs
488 | and displays a warning message.
489 | 
490 | When set to -1 the limit is not applied.
491 | 
492 |                                                    *g:gitgutter_sign_priority*
493 | Default: 10
494 | 
495 | Sets the |sign-priority| gitgutter assigns to its signs.
496 | 
497 |                                                *g:gitgutter_sign_allow_clobber*
498 | Default: 0 (Vim < 8.1.0614, Neovim < 0.4.0)
499 |          1 (otherwise)
500 | 
501 | Determines whether gitgutter preserves non-gitgutter signs. When 1, gitgutter
502 | will not preserve non-gitgutter signs.
503 | 
504 |                                           *g:gitgutter_sign_added*
505 |                                           *g:gitgutter_sign_modified*
506 |                                           *g:gitgutter_sign_removed*
507 |                                           *g:gitgutter_sign_removed_first_line*
508 |                                           *g:gitgutter_sign_removed_above_and_below*
509 |                                           *g:gitgutter_sign_modified_removed*
510 | Defaults:
511 | >
512 |     let g:gitgutter_sign_added              = '+'
513 |     let g:gitgutter_sign_modified           = '~'
514 |     let g:gitgutter_sign_removed            = '_'
515 |     let g:gitgutter_sign_removed_first_line = '‾'
516 |     let g:gitgutter_sign_removed_above_and_below = '_¯'
517 |     let g:gitgutter_sign_modified_removed   = '~_'
518 | <
519 | You can use unicode characters but not images.  Signs must not take up more than
520 | 2 columns.
521 | 
522 |                                               *g:gitgutter_set_sign_backgrounds*
523 | Default: 0
524 | 
525 | Only applies to existing GitGutter* highlight groups.  See
526 | |gitgutter-highlights|.
527 | 
528 | Controls whether to override the signs' background colours to match the
529 | |hl-SignColumn|.
530 | 
531 |                                              *g:gitgutter_preview_win_floating*
532 | Default: 0 (Vim)
533 |          0 (NeoVim which does not support floating windows)
534 |          1 (NeoVim which does support floating windows)
535 | 
536 | Whether to use floating/popup windows for hunk previews.  Note that if you use
537 | popup windows on Vim you will not be able to stage partial hunks via the
538 | preview window.
539 | 
540 |                                           *g:gitgutter_floating_window_options*
541 | Default:
542 | >
543 |     " Vim
544 |     {
545 |         \ 'line': 'cursor+1',
546 |         \ 'col': 'cursor',
547 |         \ 'moved': 'any'
548 |     }
549 | 
550 |     " Neovim
551 |     {
552 |         \ 'relative': 'cursor',
553 |         \ 'row': 1,
554 |         \ 'col': 0,
555 |         \ 'width': 42,
556 |         \ 'height': &previewheight,
557 |         \ 'style': 'minimal'
558 |     }
559 | <
560 | This dictionary is passed directly to |popup_create()| (Vim) or
561 | |nvim_open_win()| (Neovim).
562 | 
563 | If you simply want to override one or two of the default values, create a file
564 | in an after/ directory.  For example:
565 | >
566 |     " ~/.vim/after/vim-gitgutter/overrides.vim
567 |     let g:gitgutter_floating_window_options['border'] = 'single'
568 | <
569 | 
570 |                                           *g:gitgutter_close_preview_on_escape*
571 | Default: 0
572 | 
573 | Whether pressing <Esc> in a preview window closes it.
574 | 
575 |                                            *g:gitgutter_terminal_reports_focus*
576 | Default: 1
577 | 
578 | Normally the plugin uses |FocusGained| to force-update all buffers when Vim
579 | receives focus.  However some terminals do not report focus events and so the
580 | |FocusGained| autocommand never fires.
581 | 
582 | If this applies to you, either install something like Terminus
583 | (https://github.com/wincent/terminus) to make |FocusGained| work or set this
584 | option to 0.
585 | 
586 | If you use tmux, try this in your tmux.conf:
587 | >
588 |     set -g focus-events on
589 | <
590 | 
591 | When this option is 0, the plugin force-updates the buffer on |BufEnter|
592 | (instead of only updating if the buffer's contents has changed since the last
593 | update).
594 | 
595 |                                                           *g:gitgutter_enabled*
596 | Default: 1
597 | 
598 | Controls whether or not the plugin is on at startup.
599 | 
600 |                                                          *g:gitgutter_map_keys*
601 | Default: 1
602 | 
603 | Controls whether or not the plugin provides mappings.  See |gitgutter-mappings|.
604 | 
605 |                                                             *g:gitgutter_async*
606 | Default: 1
607 | 
608 | Controls whether or not diffs are run in the background.  This has no effect if
609 | your Vim does not support background jobs.
610 | 
611 |                                                               *g:gitgutter_log*
612 | Default: 0
613 | 
614 | When switched on, the plugin logs to gitgutter.log in the directory where it is
615 | installed.  Additionally it logs channel activity to channel.log.
616 | 
617 |                                                 *g:gitgutter_use_location_list*
618 | Default: 0
619 | 
620 | When switched on, the :GitGutterQuickFix command populates the location list
621 | of the current window instead of the global quickfix list.
622 | 
623 |                                          *g:gitgutter_show_msg_on_hunk_jumping*
624 | Default: 1
625 | 
626 | When switched on, a message like "Hunk 4 of 11" is shown on hunk jumping.
627 | 
628 | 
629 | ===============================================================================
630 | HIGHLIGHTS                                               *gitgutter-highlights*
631 | 
632 | To change the signs' colours, specify these highlight groups in your |vimrc|:
633 | >
634 |     highlight GitGutterAdd    guifg=#009900 ctermfg=2
635 |     highlight GitGutterChange guifg=#bbbb00 ctermfg=3
636 |     highlight GitGutterDelete guifg=#ff2222 ctermfg=1
637 | <
638 | 
639 | See |highlight-guifg| and |highlight-ctermfg| for the values you can use.
640 | 
641 | If you do not like the signs' background colours and you do not want to update
642 | the GitGutter* highlight groups yourself, you can get the plugin to do it
643 | |g:gitgutter_set_sign_backgrounds|.
644 | 
645 | To change the line highlights, set up the following highlight groups in your
646 | colorscheme or |vimrc|:
647 | >
648 |     GitGutterAddLine          " default: links to DiffAdd
649 |     GitGutterChangeLine       " default: links to DiffChange
650 |     GitGutterDeleteLine       " default: links to DiffDelete
651 |     GitGutterChangeDeleteLine " default: links to GitGutterChangeLine
652 | <
653 | 
654 | For example, to use |hl-DiffText| instead of |hl-DiffChange|:
655 | >
656 |     highlight link GitGutterChangeLine DiffText
657 | <
658 | To change the line number highlights, set up the following highlight groups in
659 | your colorscheme or |vimrc|:
660 | >
661 |     GitGutterAddLineNr          " default: links to CursorLineNr
662 |     GitGutterChangeLineNr       " default: links to CursorLineNr
663 |     GitGutterDeleteLineNr       " default: links to CursorLineNr
664 |     GitGutterChangeDeleteLineNr " default: links to GitGutterChangeLineNr
665 | <
666 | For example, to use |hl-Underlined| instead of |hl-CursorLineNr|:
667 | >
668 |     highlight link GitGutterChangeLineNr Underlined
669 | <
670 | To change the diff syntax colours used in the preview window, set up the diff*
671 | highlight groups in your colorscheme or |vimrc|:
672 | >
673 |     diffAdded   " if not set: use GitGutterAdd's foreground colour
674 |     diffChanged " if not set: use GitGutterChange's foreground colour
675 |     diffRemoved " if not set: use GitGutterDelete's foreground colour
676 | <
677 | Note the diff* highlight groups are used in any buffer whose 'syntax' is
678 | "diff".
679 | 
680 | To change the intra-line diff highlights used in the preview window, set up
681 | the following highlight groups in your colorscheme or |vimrc|:
682 | >
683 |     GitGutterAddIntraLine    " default: gui=reverse cterm=reverse
684 |     GitGutterDeleteIntraLine " default: gui=reverse cterm=reverse
685 | <
686 | For example, to use |hl-DiffAdd| for intra-line added regions:
687 | >
688 |     highlight link GitGutterAddIntraLine DiffAdd
689 | <
690 | 
691 | 
692 | ===============================================================================
693 | FAQ                                                             *gitgutter-faq*
694 | 
695 | a. How do I turn off realtime updates?
696 | 
697 |   Add this to your vim configuration in an |after-directory|:
698 | >
699 |     autocmd! gitgutter CursorHold,CursorHoldI
700 | <
701 | 
702 | b. I turned off realtime updates, how can I have signs updated when I save a
703 |    file?
704 | 
705 |   If you really want to update the signs when you save a file, add this to your
706 |   |vimrc|:
707 | >
708 |     autocmd BufWritePost * GitGutter
709 | <
710 | 
711 | c. Why can't I unstage staged changes?
712 | 
713 |   This plugin is for showing changes between the working tree and the index
714 |   (and staging/undoing those changes). Unstaging a staged hunk would require
715 |   showing changes between the index and HEAD, which is out of scope.
716 | 
717 | d. Why are the colours in the sign column weird?
718 | 
719 |   Your colorscheme is configuring the |hl-SignColumn| highlight group weirdly.
720 |   Here are two ways you could change the colours:
721 | >
722 |     highlight! link SignColumn LineNr
723 |     highlight SignColumn guibg=whatever ctermbg=whatever
724 | <
725 | 
726 | e. What happens if I also use another plugin which uses signs (e.g. Syntastic)?
727 | 
728 |   Vim only allows one sign per line.  Vim-gitgutter will not interfere with
729 |   signs it did not add.
730 | 
731 | 
732 | ===============================================================================
733 | TROUBLESHOOTING                                     *gitgutter-troubleshooting*
734 | 
735 | When no signs are showing at all:~
736 | 
737 | 1. Try bypassing grep with:
738 | >
739 |     let g:gitgutter_grep = ''
740 | <
741 |   If it works, the problem is grep outputting ANSI escape codes.  Use this
742 |   option to pass arguments to grep to turn off the escape codes.
743 | 
744 | 2. Verify git is on your path:
745 | >
746 |     :echo system('git --version')
747 | <
748 | 
749 | 3. Verify your git config is compatible with the version of git return by the
750 |    command above.
751 | 
752 | 4. Verify your Vim supports signs.  The following should give 1:
753 | >
754 |     :echo has('signs')
755 | <
756 | 
757 | 5. Check whether the plugin thinks git knows about your file:
758 | >
759 |     :echo getbufvar('','gitgutter').path
760 | <
761 |   If the result is -2, the plugin thinks your file is not tracked by git.
762 | 
763 | 6. Check whether the signs have been placed:
764 | >
765 |     :sign place group=gitgutter
766 | <
767 |   If you see a list of signs, this is a colorscheme / highlight problem.
768 |   Compare these two highlight values:
769 | >
770 |     :highlight GitGutterAdd
771 |     :highlight SignColumn
772 | <
773 |   If no signs are listed, the call to git-diff is probably failing.  Turn on
774 |   logging by adding the following to your vimrc, restart, reproduce the problem,
775 |   and examing the gitgutter.log file in the plugin's directory.
776 | >
777 |     let g:gitgutter_log = 1
778 | <
779 | 
780 | When the whole file is marked as added:~
781 | 
782 | If you use zsh, and you set "CDPATH", make sure "CDPATH" does not include the
783 | current directory.
784 | 
785 | 
786 | When signs take a few seconds to appear:~
787 | 
788 | Try reducing 'updatetime':
789 | >
790 |     set updatetime=100
791 | <
792 | 
793 | Note this also controls how long vim waits before writing its swap file.
794 | 
795 | 
796 | When signs don't update after focusing Vim:~
797 | 
798 | Your terminal probably isn't reporting focus events.  Either try installing
799 | Terminus (https://github.com/wincent/terminus) or set:
800 | >
801 |     let g:gitgutter_terminal_reports_focus = 0
802 | <
803 | 
804 |   vim:tw=78:et:ft=help:norl:
805 | 


--------------------------------------------------------------------------------
/plugin/gitgutter.vim:
--------------------------------------------------------------------------------
  1 | scriptencoding utf-8
  2 | 
  3 | if exists('g:loaded_gitgutter') || !has('signs') || &cp
  4 |   finish
  5 | endif
  6 | let g:loaded_gitgutter = 1
  7 | 
  8 | " Initialisation {{{
  9 | 
 10 | if v:version < 703 || (v:version == 703 && !has("patch105"))
 11 |   call gitgutter#utility#warn('Requires Vim 7.3.105')
 12 |   finish
 13 | endif
 14 | 
 15 | let s:nomodeline = (v:version > 703 || (v:version == 703 && has('patch442'))) ? '<nomodeline>' : ''
 16 | 
 17 | function! s:obsolete(var)
 18 |   if exists(a:var)
 19 |     call gitgutter#utility#warn(a:var.' is obsolete and has no effect.')
 20 |   endif
 21 | endfunction
 22 | 
 23 | 
 24 | let g:gitgutter_preview_win_location = get(g:, 'gitgutter_preview_win_location', 'bo')
 25 | if exists('*nvim_open_win')
 26 |   let g:gitgutter_preview_win_floating = get(g:, 'gitgutter_preview_win_floating', 1)
 27 |   let g:gitgutter_floating_window_options = get(g:, 'gitgutter_floating_window_options', {
 28 |         \ 'relative': 'cursor',
 29 |         \ 'row': 1,
 30 |         \ 'col': 0,
 31 |         \ 'width': 42,
 32 |         \ 'height': &previewheight,
 33 |         \ 'style': 'minimal'
 34 |         \ })
 35 | else
 36 |   let default = exists('&previewpopup') ? !empty(&previewpopup) : 0
 37 |   let g:gitgutter_preview_win_floating = get(g:, 'gitgutter_preview_win_floating', default)
 38 |   let g:gitgutter_floating_window_options = get(g:, 'gitgutter_floating_window_options', {
 39 |         \ 'line': 'cursor+1',
 40 |         \ 'col': 'cursor',
 41 |         \ 'moved': 'any'
 42 |         \ })
 43 | endif
 44 | let g:gitgutter_enabled = get(g:, 'gitgutter_enabled', 1)
 45 | if exists('*sign_unplace')
 46 |   let g:gitgutter_max_signs = get(g:, 'gitgutter_max_signs', -1)
 47 | else
 48 |   let g:gitgutter_max_signs = get(g:, 'gitgutter_max_signs', 500)
 49 | endif
 50 | let g:gitgutter_signs             = get(g:, 'gitgutter_signs', 1)
 51 | let g:gitgutter_highlight_lines   = get(g:, 'gitgutter_highlight_lines', 0)
 52 | let g:gitgutter_highlight_linenrs = get(g:, 'gitgutter_highlight_linenrs', 0)
 53 | let g:gitgutter_sign_priority     = get(g:, 'gitgutter_sign_priority', 10)
 54 | " Nvim 0.4.0 has an expanding sign column
 55 | " The sign_place() function supports sign priority.
 56 | if (has('nvim-0.4.0') || exists('*sign_place')) && !exists('g:gitgutter_sign_allow_clobber')
 57 |   let g:gitgutter_sign_allow_clobber = 1
 58 | endif
 59 | let g:gitgutter_sign_allow_clobber   = get(g:, 'gitgutter_sign_allow_clobber', 0)
 60 | let g:gitgutter_set_sign_backgrounds = get(g:, 'gitgutter_set_sign_backgrounds', 0)
 61 | let g:gitgutter_sign_added           = get(g:, 'gitgutter_sign_added', '+')
 62 | let g:gitgutter_sign_modified        = get(g:, 'gitgutter_sign_modified', '~')
 63 | let g:gitgutter_sign_removed         = get(g:, 'gitgutter_sign_removed', '_')
 64 | 
 65 | if gitgutter#utility#supports_overscore_sign()
 66 |   let g:gitgutter_sign_removed_first_line = get(g:, 'gitgutter_sign_removed_first_line', '‾')
 67 | else
 68 |   let g:gitgutter_sign_removed_first_line = get(g:, 'gitgutter_sign_removed_first_line', '_^')
 69 | endif
 70 | 
 71 | let g:gitgutter_sign_removed_above_and_below = get(g:, 'gitgutter_sign_removed_above_and_below', '_¯')
 72 | let g:gitgutter_sign_modified_removed        = get(g:, 'gitgutter_sign_modified_removed', '~_')
 73 | let g:gitgutter_git_args                     = get(g:, 'gitgutter_git_args', '')
 74 | let g:gitgutter_diff_relative_to             = get(g:, 'gitgutter_diff_relative_to', 'index')
 75 | let g:gitgutter_diff_args                    = get(g:, 'gitgutter_diff_args', '')
 76 | let g:gitgutter_diff_base                    = get(g:, 'gitgutter_diff_base', '')
 77 | let g:gitgutter_map_keys                     = get(g:, 'gitgutter_map_keys', 1)
 78 | let g:gitgutter_terminal_reports_focus       = get(g:, 'gitgutter_terminal_reports_focus', 1)
 79 | let g:gitgutter_async                        = get(g:, 'gitgutter_async', 1)
 80 | let g:gitgutter_log                          = get(g:, 'gitgutter_log', 0)
 81 | let g:gitgutter_use_location_list            = get(g:, 'gitgutter_use_location_list', 0)
 82 | let g:gitgutter_close_preview_on_escape      = get(g:, 'gitgutter_close_preview_on_escape', 0)
 83 | let g:gitgutter_show_msg_on_hunk_jumping     = get(g:, 'gitgutter_show_msg_on_hunk_jumping', 1)
 84 | 
 85 | let g:gitgutter_git_executable = get(g:, 'gitgutter_git_executable', 'git')
 86 | if !executable(g:gitgutter_git_executable)
 87 |   if g:gitgutter_enabled
 88 |     call gitgutter#utility#warn('Cannot find git. Please set g:gitgutter_git_executable.')
 89 |   endif
 90 |   finish
 91 | endif
 92 | 
 93 | let default_grep = 'grep'
 94 | let g:gitgutter_grep = get(g:, 'gitgutter_grep', default_grep)
 95 | if !empty(g:gitgutter_grep)
 96 |   if executable(split(g:gitgutter_grep)[0])
 97 |     if $GREP_OPTIONS =~# '--color=always'
 98 |       let g:gitgutter_grep .= ' --color=never'
 99 |     endif
100 |   else
101 |     if g:gitgutter_grep !=# default_grep
102 |       call gitgutter#utility#warn('Cannot find '.g:gitgutter_grep.'. Please check g:gitgutter_grep.')
103 |     endif
104 |     let g:gitgutter_grep = ''
105 |   endif
106 | endif
107 | 
108 | call gitgutter#highlight#define_highlights()
109 | call gitgutter#highlight#define_signs()
110 | 
111 | " Prevent infinite loop where:
112 | " - executing a job in the foreground launches a new window which takes the focus;
113 | " - when the job finishes, focus returns to gvim;
114 | " - the FocusGained event triggers a new job (see below).
115 | if gitgutter#utility#windows() && !(g:gitgutter_async && gitgutter#async#available())
116 |   set noshelltemp
117 | endif
118 | 
119 | " }}}
120 | 
121 | " Primary functions {{{
122 | 
123 | command! -bar GitGutterAll call gitgutter#all(1)
124 | command! -bar GitGutter    call gitgutter#process_buffer(bufnr(''), 1)
125 | 
126 | command! -bar GitGutterDisable call gitgutter#disable()
127 | command! -bar GitGutterEnable  call gitgutter#enable()
128 | command! -bar GitGutterToggle  call gitgutter#toggle()
129 | 
130 | command! -bar GitGutterBufferDisable call gitgutter#buffer_disable()
131 | command! -bar GitGutterBufferEnable  call gitgutter#buffer_enable()
132 | command! -bar GitGutterBufferToggle  call gitgutter#buffer_toggle()
133 | 
134 | command! -bar GitGutterQuickFix call gitgutter#quickfix(0)
135 | command! -bar GitGutterQuickFixCurrentFile call gitgutter#quickfix(1)
136 | 
137 | command! -bar GitGutterDiffOrig call gitgutter#difforig()
138 | 
139 | " }}}
140 | 
141 | " Line highlights {{{
142 | 
143 | command! -bar GitGutterLineHighlightsDisable call gitgutter#highlight#line_disable()
144 | command! -bar GitGutterLineHighlightsEnable  call gitgutter#highlight#line_enable()
145 | command! -bar GitGutterLineHighlightsToggle  call gitgutter#highlight#line_toggle()
146 | 
147 | " }}}
148 | 
149 | " 'number' column highlights {{{
150 | command! -bar GitGutterLineNrHighlightsDisable call gitgutter#highlight#linenr_disable()
151 | command! -bar GitGutterLineNrHighlightsEnable  call gitgutter#highlight#linenr_enable()
152 | command! -bar GitGutterLineNrHighlightsToggle  call gitgutter#highlight#linenr_toggle()
153 | " }}}
154 | 
155 | " Signs {{{
156 | 
157 | command! -bar GitGutterSignsEnable  call gitgutter#sign#enable()
158 | command! -bar GitGutterSignsDisable call gitgutter#sign#disable()
159 | command! -bar GitGutterSignsToggle  call gitgutter#sign#toggle()
160 | 
161 | " }}}
162 | 
163 | " Hunks {{{
164 | 
165 | command! -bar -count=1 GitGutterNextHunk call gitgutter#hunk#next_hunk(<count>)
166 | command! -bar -count=1 GitGutterPrevHunk call gitgutter#hunk#prev_hunk(<count>)
167 | 
168 | command! -bar -range=% GitGutterStageHunk call gitgutter#hunk#stage(<line1>,<line2>)
169 | command! -bar GitGutterUndoHunk    call gitgutter#hunk#undo()
170 | command! -bar GitGutterPreviewHunk call gitgutter#hunk#preview()
171 | 
172 | " Hunk text object
173 | onoremap <silent> <Plug>(GitGutterTextObjectInnerPending) :<C-U>call gitgutter#hunk#text_object(1)<CR>
174 | onoremap <silent> <Plug>(GitGutterTextObjectOuterPending) :<C-U>call gitgutter#hunk#text_object(0)<CR>
175 | xnoremap <silent> <Plug>(GitGutterTextObjectInnerVisual)  :<C-U>call gitgutter#hunk#text_object(1)<CR>
176 | xnoremap <silent> <Plug>(GitGutterTextObjectOuterVisual)  :<C-U>call gitgutter#hunk#text_object(0)<CR>
177 | 
178 | 
179 | " Returns the git-diff hunks for the file or an empty list if there
180 | " aren't any hunks.
181 | "
182 | " The return value is a list of lists.  There is one inner list per hunk.
183 | "
184 | "   [
185 | "     [from_line, from_count, to_line, to_count],
186 | "     [from_line, from_count, to_line, to_count],
187 | "     ...
188 | "   ]
189 | "
190 | " where:
191 | "
192 | " `from`  - refers to the staged file
193 | " `to`    - refers to the working tree's file
194 | " `line`  - refers to the line number where the change starts
195 | " `count` - refers to the number of lines the change covers
196 | function! GitGutterGetHunks()
197 |   let bufnr = bufnr('')
198 |   return gitgutter#utility#is_active(bufnr) ? gitgutter#hunk#hunks(bufnr) : []
199 | endfunction
200 | 
201 | " Returns an array that contains a summary of the hunk status for the current
202 | " window.  The format is [ added, modified, removed ], where each value
203 | " represents the number of lines added/modified/removed respectively.
204 | function! GitGutterGetHunkSummary()
205 |   return gitgutter#hunk#summary(winbufnr(0))
206 | endfunction
207 | 
208 | " }}}
209 | 
210 | " Folds {{{
211 | 
212 | command! -bar GitGutterFold call gitgutter#fold#toggle()
213 | 
214 | " }}}
215 | 
216 | command! -bar GitGutterDebug call gitgutter#debug#debug()
217 | 
218 | " Maps {{{
219 | 
220 | nnoremap <silent> <expr> <Plug>(GitGutterNextHunk) &diff ? ']c' : ":\<C-U>execute v:count1 . 'GitGutterNextHunk'\<CR>"
221 | nnoremap <silent> <expr> <Plug>GitGutterNextHunk   &diff ? ']c' : ":\<C-U>call gitgutter#utility#warn('Please change your map \<lt>Plug>GitGutterNextHunk to \<lt>Plug>(GitGutterNextHunk)')\<CR>"
222 | nnoremap <silent> <expr> <Plug>(GitGutterPrevHunk) &diff ? '[c' : ":\<C-U>execute v:count1 . 'GitGutterPrevHunk'\<CR>"
223 | nnoremap <silent> <expr> <Plug>GitGutterPrevHunk   &diff ? '[c' : ":\<C-U>call gitgutter#utility#warn('Please change your map \<lt>Plug>GitGutterPrevHunk to \<lt>Plug>(GitGutterPrevHunk)')\<CR>"
224 | 
225 | xnoremap <silent> <Plug>(GitGutterStageHunk)   :GitGutterStageHunk<CR>
226 | xnoremap <silent> <Plug>GitGutterStageHunk     :call gitgutter#utility#warn('Please change your map <lt>Plug>GitGutterStageHunk to <lt>Plug>(GitGutterStageHunk)')<CR>
227 | nnoremap <silent> <Plug>(GitGutterStageHunk)   :GitGutterStageHunk<CR>
228 | nnoremap <silent> <Plug>GitGutterStageHunk     :call gitgutter#utility#warn('Please change your map <lt>Plug>GitGutterStageHunk to <lt>Plug>(GitGutterStageHunk)')<CR>
229 | nnoremap <silent> <Plug>(GitGutterUndoHunk)    :GitGutterUndoHunk<CR>
230 | nnoremap <silent> <Plug>GitGutterUndoHunk      :call gitgutter#utility#warn('Please change your map <lt>Plug>GitGutterUndoHunk to <lt>Plug>(GitGutterUndoHunk)')<CR>
231 | nnoremap <silent> <Plug>(GitGutterPreviewHunk) :GitGutterPreviewHunk<CR>
232 | nnoremap <silent> <Plug>GitGutterPreviewHunk   :call gitgutter#utility#warn('Please change your map <lt>Plug>GitGutterPreviewHunk to <lt>Plug>(GitGutterPreviewHunk)')<CR>
233 | 
234 | " }}}
235 | 
236 | function! s:on_bufenter()
237 |   call gitgutter#setup_maps()
238 | 
239 |   " To keep vim's start-up fast, do not process the buffer when vim is starting.
240 |   " Instead process it a short time later.  Normally we would rely on our
241 |   " CursorHold autocommand to handle this but it turns out CursorHold is not
242 |   " guaranteed to fire if the user has not typed anything yet; so set up a
243 |   " timer instead.  The disadvantage is that if CursorHold does fire, the
244 |   " plugin will do a round of unnecessary work; but since there will not have
245 |   " been any changes to the buffer since the first round, the second round
246 |   " will be cheap.
247 |   if has('vim_starting') && !$VIM_GITGUTTER_TEST
248 |     if exists('*timer_start') && has('lambda')
249 |       call s:next_tick("call gitgutter#process_buffer(+".bufnr('').", 0)")
250 |     else
251 |       call gitgutter#process_buffer(bufnr(''), 0)
252 |     endif
253 |     return
254 |   endif
255 | 
256 |   if exists('t:gitgutter_didtabenter') && t:gitgutter_didtabenter
257 |     let t:gitgutter_didtabenter = 0
258 |     call gitgutter#all(!g:gitgutter_terminal_reports_focus)
259 |   else
260 |     call gitgutter#process_buffer(bufnr(''), !g:gitgutter_terminal_reports_focus)
261 |   endif
262 | endfunction
263 | 
264 | function! s:next_tick(cmd)
265 |   call timer_start(1, {-> execute(a:cmd)})
266 | endfunction
267 | 
268 | function! s:on_buffilepre(bufnr)
269 |   if !exists('s:renaming')
270 |     let s:renaming = []
271 |     let s:gitgutter_was_enabled = gitgutter#utility#getbufvar(a:bufnr, 'enabled')
272 |   endif
273 | 
274 |   let s:renaming += [a:bufnr]
275 | endfunction
276 | 
277 | function! s:on_buffilepost(bufnr)
278 |   if len(s:renaming) > 1
279 |     if s:renaming[0] != a:bufnr
280 |       throw 'gitgutter rename error' s:renaming[0] a:bufnr
281 |     endif
282 |     unlet s:renaming[0]
283 |     return
284 |   endif
285 | 
286 |   " reset cached values
287 |   GitGutterBufferDisable
288 | 
289 |   if s:gitgutter_was_enabled
290 |     GitGutterBufferEnable
291 |   endif
292 | 
293 |   unlet s:renaming
294 |   unlet s:gitgutter_was_enabled
295 | endfunction
296 | 
297 | " Autocommands {{{
298 | 
299 | augroup gitgutter
300 |   autocmd!
301 | 
302 |   autocmd TabEnter * let t:gitgutter_didtabenter = 1
303 | 
304 |   autocmd BufEnter * call s:on_bufenter()
305 | 
306 |   " Ensure Vim is always checking for CursorMoved to avoid CursorMoved
307 |   " being fired at the wrong time in floating preview window on Neovim.
308 |   " See vim/vim#2053.
309 |   autocmd CursorMoved * execute ''
310 | 
311 |   autocmd CursorHold,CursorHoldI * call gitgutter#process_buffer(bufnr(''), 0)
312 |   if exists('*timer_start') && has('lambda')
313 |     autocmd FileChangedShellPost * call s:next_tick("call gitgutter#process_buffer(+".expand('<abuf>').", 1)")
314 |   else
315 |     autocmd FileChangedShellPost * call gitgutter#process_buffer(+expand('<abuf>'), 1)
316 |   endif
317 | 
318 |   " Ensure that all buffers are processed when opening vim with multiple files, e.g.:
319 |   "
320 |   "   vim -o file1 file2
321 |   autocmd VimEnter * if winnr() != winnr('
#39;) | call gitgutter#all(0) | endif
322 | 
323 |   autocmd ShellCmdPost * call gitgutter#all(1)
324 |   autocmd BufLeave term://* call gitgutter#all(1)
325 | 
326 |   autocmd User FugitiveChanged call gitgutter#all(1)
327 | 
328 |   " Handle all buffers when focus is gained, but only after it was lost.
329 |   " FocusGained gets triggered on startup with Neovim at least already.
330 |   " Therefore this tracks also if it was lost before.
331 |   let s:focus_was_lost = 0
332 |   autocmd FocusGained * if s:focus_was_lost | let s:focus_was_lost = 0 | call gitgutter#all(1) | endif
333 |   autocmd FocusLost * let s:focus_was_lost = 1
334 | 
335 |   if exists('##VimResume')
336 |     autocmd VimResume * call gitgutter#all(1)
337 |   endif
338 | 
339 |   autocmd ColorScheme * call gitgutter#highlight#define_highlights()
340 | 
341 |   autocmd BufFilePre  * call s:on_buffilepre(expand('<abuf>'))
342 |   autocmd BufFilePost * call s:on_buffilepost(expand('<abuf>'))
343 | 
344 |   autocmd QuickFixCmdPre *vimgrep*
345 |         \ if gitgutter#utility#getbufvar(expand('<abuf>'), 'enabled') |
346 |         \   let s:gitgutter_was_enabled = expand('<abuf>') |
347 |         \ else |
348 |         \   let s:gitgutter_was_enabled = 0 |
349 |         \ endif |
350 |         \ GitGutterBufferDisable
351 |   autocmd QuickFixCmdPost *vimgrep*
352 |         \ if s:gitgutter_was_enabled |
353 |         \   call gitgutter#buffer_enable(s:gitgutter_was_enabled) |
354 |         \ endif |
355 |         \ unlet s:gitgutter_was_enabled
356 | augroup END
357 | 
358 | " }}}
359 | 
360 | " vim:set et sw=2 fdm=marker:
361 | 


--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/airblade/vim-gitgutter/85ca3a087204e3a32cb2faa5d9d0451524e08720/screenshot.png


--------------------------------------------------------------------------------
/test/.gitattributes:
--------------------------------------------------------------------------------
1 | *.foo filter=reverse diff=reverse
2 | 


--------------------------------------------------------------------------------
/test/.gitconfig:
--------------------------------------------------------------------------------
1 | [filter "reverse"]
2 |   clean = "rev"
3 |   smudge = "rev"
4 | 
5 | [diff "reverse"]
6 |   textconv = "cat"
7 | 


--------------------------------------------------------------------------------
/test/cp932.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/airblade/vim-gitgutter/85ca3a087204e3a32cb2faa5d9d0451524e08720/test/cp932.txt


--------------------------------------------------------------------------------
/test/fixture.foo:
--------------------------------------------------------------------------------
1 | one
2 | two
3 | three
4 | four
5 | 


--------------------------------------------------------------------------------
/test/fixture.txt:
--------------------------------------------------------------------------------
 1 | a
 2 | b
 3 | c
 4 | d
 5 | e
 6 | f
 7 | g
 8 | h
 9 | i
10 | j
11 | 
12 | 


--------------------------------------------------------------------------------
/test/fixture_dos.txt:
--------------------------------------------------------------------------------
 1 | a
 2 | b
 3 | c
 4 | d
 5 | e
 6 | f
 7 | g
 8 | h
 9 | i
10 | j
11 | 
12 | 


--------------------------------------------------------------------------------
/test/fixture_dos_noeol.txt:
--------------------------------------------------------------------------------
1 | a
2 | b
3 | c
4 | d
5 | e
6 | f
7 | g


--------------------------------------------------------------------------------
/test/runner.vim:
--------------------------------------------------------------------------------
  1 | "
  2 | " Adapted from https://github.com/vim/vim/blob/master/src/testdir/runtest.vim
  3 | "
  4 | " When debugging tests it can help to write debug output:
  5 | "    call Log('oh noes')
  6 | "
  7 | 
  8 | function RunTest(test)
  9 |   if exists("*SetUp")
 10 |     call SetUp()
 11 |   endif
 12 | 
 13 |   try
 14 |     execute 'call '.a:test
 15 |   catch
 16 |     call Exception()
 17 |     let s:errored = 1
 18 |   endtry
 19 | 
 20 |   if exists("*TearDown")
 21 |     call TearDown()
 22 |   endif
 23 | endfunction
 24 | 
 25 | function Log(msg)
 26 |   if type(a:msg) == type('')
 27 |     call add(s:messages, a:msg)
 28 |   elseif type(a:msg) == type([])
 29 |     call extend(s:messages, a:msg)
 30 |   else
 31 |     call add(v:errors, 'Exception: unsupported type: '.type(a:msg))
 32 |   endif
 33 | endfunction
 34 | 
 35 | function Exception()
 36 |   call add(v:errors, v:throwpoint.'..'.'Exception: '.v:exception)
 37 | endfunction
 38 | 
 39 | " Shuffles list in place.
 40 | function Shuffle(list)
 41 |   " Fisher-Yates-Durstenfeld-Knuth
 42 |   let n = len(a:list)
 43 |   if n < 2
 44 |     return a:list
 45 |   endif
 46 |   for i in range(0, n-2)
 47 |     let j = Random(0, n-i-1)
 48 |     let e = a:list[i]
 49 |     let a:list[i] = a:list[i+j]
 50 |     let a:list[i+j] = e
 51 |   endfor
 52 |   return a:list
 53 | endfunction
 54 | 
 55 | " Returns a pseudorandom integer i such that 0 <= i <= max
 56 | function Random(min, max)
 57 |   if has('unix')
 58 |     let i = system('echo $RANDOM')  " 0 <= i <= 32767
 59 |   else
 60 |     let i = system('echo %RANDOM%')  " 0 <= i <= 32767
 61 |   endif
 62 |   return i * (a:max - a:min + 1) / 32768 + a:min
 63 | endfunction
 64 | 
 65 | function FriendlyName(test_name)
 66 |   return substitute(a:test_name[5:-3], '_', ' ', 'g')
 67 | endfunction
 68 | 
 69 | function Align(left, right)
 70 |   if type(a:right) == type([])
 71 |     let result = []
 72 |     for s in a:right
 73 |       if empty(result)
 74 |         call add(result, printf('%-'.s:indent.'S', a:left).s)
 75 |       else
 76 |         call add(result, printf('%-'.s:indent.'S',     '').s)
 77 |       endif
 78 |     endfor
 79 |     return result
 80 |   endif
 81 | 
 82 |   return printf('%-'.s:indent.'S', a:left).a:right
 83 | endfunction
 84 | 
 85 | let g:testname = expand('%')
 86 | let s:errored = 0
 87 | let s:done = 0
 88 | let s:fail = 0
 89 | let s:errors = 0
 90 | let s:messages = []
 91 | let s:indent = ''
 92 | 
 93 | call Log(g:testname.':')
 94 | 
 95 | " Source the test script.
 96 | try
 97 |   source %
 98 | catch
 99 |   let s:errors += 1
100 |   call Exception()
101 | endtry
102 | 
103 | " Locate the test functions.
104 | set nomore
105 | redir @q
106 | silent function /^Test_
107 | redir END
108 | let s:tests = split(substitute(@q, 'function \(\k*()\)', '\1', 'g'))
109 | 
110 | " If there is another argument, filter test-functions' names against it.
111 | if argc() > 1
112 |   let s:tests = filter(s:tests, 'v:val =~ argv(1)')
113 | endif
114 | 
115 | let s:indent = max(map(copy(s:tests), {_, val -> len(FriendlyName(val))}))
116 | 
117 | " Run the tests in random order.
118 | for test in Shuffle(s:tests)
119 |   call RunTest(test)
120 |   let s:done += 1
121 | 
122 |   let friendly_name = FriendlyName(test)
123 |   if len(v:errors) == 0
124 |     call Log(Align(friendly_name, ' - ok'))
125 |   else
126 |     if s:errored
127 |       let s:errors += 1
128 |       let s:errored = 0
129 |     else
130 |       let s:fail += 1
131 |     endif
132 |     call Log(Align(friendly_name, ' - not ok'))
133 | 
134 |     let i = 0
135 |     for error in v:errors
136 |       if i != 0
137 |         call Log(Align('','   ! ----'))
138 |       endif
139 |       for trace in reverse(split(error, '\.\.'))
140 |         call Log(Align('', '   ! '.trace))
141 |       endfor
142 |       let i += 1
143 |     endfor
144 | 
145 |     let v:errors = []
146 |   endif
147 | endfor
148 | 
149 | let summary = [
150 |       \ s:done.(  s:done   == 1 ? ' test'    : ' tests'),
151 |       \ s:errors.(s:errors == 1 ? ' error'   : ' errors'),
152 |       \ s:fail.(  s:fail   == 1 ? ' failure' : ' failures'),
153 |       \ ]
154 | call Log('')
155 | call Log(join(summary, ', '))
156 | 
157 | split messages.log
158 | call append(line('
#39;), s:messages)
159 | write
160 | 
161 | qall!
162 | 
163 | 


--------------------------------------------------------------------------------
/test/test:
--------------------------------------------------------------------------------
 1 | #!/usr/bin/env bash
 2 | 
 3 | VIM="/Applications/MacVim.app/Contents/MacOS/Vim -v"
 4 | 
 5 | export VIM_GITGUTTER_TEST=1
 6 | 
 7 | $VIM -u NONE -U NONE -N                      \
 8 |   --cmd 'set rtp+=../'                       \
 9 |   --cmd 'let g:gitgutter_async=0'            \
10 |   --cmd 'source ../plugin/gitgutter.vim'     \
11 |   -S runner.vim                              \
12 |   test_*.vim                                 \
13 |   "$@"
14 | 
15 | cat messages.log
16 | 
17 | grep -q "0 errors, 0 failures" messages.log
18 | status=$?
19 | rm messages.log
20 | exit $status
21 | 
22 | 


--------------------------------------------------------------------------------
/test/test_gitgutter.vim:
--------------------------------------------------------------------------------
   1 | let s:current_dir = expand('%:p:h')
   2 | let s:test_repo   = s:current_dir.'/test-repo'
   3 | let s:bufnr       = bufnr('')
   4 | 
   5 | "
   6 | " Helpers
   7 | "
   8 | 
   9 | " Ignores unexpected keys in actual.
  10 | function s:assert_list_of_dicts(expected, actual)
  11 |   if empty(a:expected)
  12 |     call assert_equal([], a:actual)
  13 |     return
  14 |   endif
  15 | 
  16 |   let expected_keys = keys(a:expected[0])
  17 | 
  18 |   for dict in a:actual
  19 |     for k in keys(dict)
  20 |       if index(expected_keys, k) == -1
  21 |         call remove(dict, k)
  22 |       endif
  23 |     endfor
  24 |   endfor
  25 | 
  26 |   call assert_equal(a:expected, a:actual)
  27 | endfunction
  28 | 
  29 | " Ignores unexpected keys.
  30 | "
  31 | " expected - list of signs
  32 | function s:assert_signs(expected, filename)
  33 |   let actual = sign_getplaced(a:filename, {'group': 'gitgutter'})[0].signs
  34 |   call s:assert_list_of_dicts(a:expected, actual)
  35 | endfunction
  36 | 
  37 | function s:git_diff(...)
  38 |   return split(system('git diff -U0 '.(a:0 ? a:1 : 'fixture.txt')), '\n')
  39 | endfunction
  40 | 
  41 | function s:git_diff_staged(...)
  42 |   return split(system('git diff -U0 --staged '.(a:0 ? a:1 : 'fixture.txt')), '\n')
  43 | endfunction
  44 | 
  45 | function s:trigger_gitgutter()
  46 |   doautocmd CursorHold
  47 | endfunction
  48 | 
  49 | 
  50 | "
  51 | " SetUp / TearDown
  52 | "
  53 | 
  54 | function SetUp()
  55 |   let g:gitgutter_diff_base = ''
  56 |   call system("git init ".s:test_repo.
  57 |         \ " && cd ".s:test_repo.
  58 |         \ " && cp ../.gitconfig .".
  59 |         \ " && cp ../.gitattributes .".
  60 |         \ " && cp ../fixture.foo .".
  61 |         \ " && cp ../fixture.txt .".
  62 |         \ " && cp ../fixture_dos.txt .".
  63 |         \ " && cp ../fixture_dos_noeol.txt .".
  64 |         \ " && git add . && git commit -m 'initial'".
  65 |         \ " && git config diff.mnemonicPrefix false")
  66 |   execute ':cd' s:test_repo
  67 |   edit! fixture.txt
  68 |   call gitgutter#sign#reset()
  69 | 
  70 |   " FIXME why won't vim autoload the file?
  71 |   execute 'source' '../../autoload/gitgutter/diff_highlight.vim'
  72 |   execute 'source' '../../autoload/gitgutter/fold.vim'
  73 | endfunction
  74 | 
  75 | function TearDown()
  76 |   " delete all buffers except this one
  77 |   " TODO: move to runner.vim, accounting for multiple test files
  78 |   if s:bufnr > 1
  79 |     silent! execute '1,'.s:bufnr-1.'bdelete!'
  80 |   endif
  81 |   silent! execute s:bufnr+1.',$bdelete!'
  82 | 
  83 |   execute ':cd' s:current_dir
  84 |   call system("rm -rf ".s:test_repo)
  85 | endfunction
  86 | 
  87 | "
  88 | " The tests
  89 | "
  90 | 
  91 | function Test_add_lines()
  92 |   normal ggo*
  93 |   call s:trigger_gitgutter()
  94 | 
  95 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded', 'group': 'gitgutter', 'priority': 10}]
  96 |   call s:assert_signs(expected, 'fixture.txt')
  97 | endfunction
  98 | 
  99 | 
 100 | function Test_add_lines_fish()
 101 |   let _shell = &shell
 102 |   set shell=/usr/local/bin/fish
 103 | 
 104 |   normal ggo*
 105 |   call s:trigger_gitgutter()
 106 | 
 107 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
 108 |   call s:assert_signs(expected, 'fixture.txt')
 109 | 
 110 |   let &shell = _shell
 111 | endfunction
 112 | 
 113 | 
 114 | function Test_modify_lines()
 115 |   normal ggi*
 116 |   call s:trigger_gitgutter()
 117 | 
 118 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineModified'}]
 119 |   call s:assert_signs(expected, 'fixture.txt')
 120 | endfunction
 121 | 
 122 | 
 123 | function Test_remove_lines()
 124 |   execute '5d'
 125 |   call s:trigger_gitgutter()
 126 | 
 127 |   let expected = [{'lnum': 4, 'name': 'GitGutterLineRemoved'}]
 128 |   call s:assert_signs(expected, 'fixture.txt')
 129 | endfunction
 130 | 
 131 | 
 132 | function Test_remove_first_lines()
 133 |   execute '1d'
 134 |   call s:trigger_gitgutter()
 135 | 
 136 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineRemovedFirstLine'}]
 137 |   call s:assert_signs(expected, 'fixture.txt')
 138 | endfunction
 139 | 
 140 | 
 141 | function Test_priority()
 142 |   let g:gitgutter_sign_priority = 5
 143 | 
 144 |   execute '1d'
 145 |   call s:trigger_gitgutter()
 146 | 
 147 |   call s:assert_signs([{'priority': 5}], 'fixture.txt')
 148 | 
 149 |   let g:gitgutter_sign_priority = 10
 150 | endfunction
 151 | 
 152 | 
 153 | function Test_overlapping_hunks()
 154 |   execute '3d'
 155 |   execute '1d'
 156 |   call s:trigger_gitgutter()
 157 | 
 158 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineRemovedAboveAndBelow'}]
 159 |   call s:assert_signs(expected, 'fixture.txt')
 160 | endfunction
 161 | 
 162 | 
 163 | function Test_edit_file_with_same_name_as_a_branch()
 164 |   normal 5Gi*
 165 |   call system('git checkout -b fixture.txt')
 166 |   call s:trigger_gitgutter()
 167 | 
 168 |   let expected = [{'lnum': 5, 'name': 'GitGutterLineModified'}]
 169 |   call s:assert_signs(expected, 'fixture.txt')
 170 | endfunction
 171 | 
 172 | 
 173 | function Test_file_added_to_git()
 174 |   let tmpfile = 'fileAddedToGit.tmp'
 175 |   call system('touch '.tmpfile.' && git add '.tmpfile)
 176 |   execute 'edit '.tmpfile
 177 |   normal ihello
 178 |   call s:trigger_gitgutter()
 179 | 
 180 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineAdded'}]
 181 |   call s:assert_signs(expected, 'fileAddedToGit.tmp')
 182 | endfunction
 183 | 
 184 | 
 185 | function Test_filename_with_equals()
 186 |   call system('touch =fixture=.txt && git add =fixture=.txt')
 187 |   edit =fixture=.txt
 188 |   normal ggo*
 189 |   call s:trigger_gitgutter()
 190 | 
 191 |   let expected = [
 192 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 193 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 194 |         \ ]
 195 |   call s:assert_signs(expected, '=fixture=.txt')
 196 | endfunction
 197 | 
 198 | 
 199 | function Test_filename_with_colon()
 200 |   call system('touch fix:ture.txt && git add fix:ture.txt')
 201 |   edit fix:ture.txt
 202 |   normal ggo*
 203 |   call s:trigger_gitgutter()
 204 | 
 205 |   let expected = [
 206 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 207 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 208 |         \ ]
 209 |   call s:assert_signs(expected, 'fix:ture.txt')
 210 | endfunction
 211 | 
 212 | 
 213 | function Test_filename_with_square_brackets()
 214 |   call system('touch fix[tu]re.txt && git add fix[tu]re.txt')
 215 |   edit fix[tu]re.txt
 216 |   normal ggo*
 217 |   call s:trigger_gitgutter()
 218 | 
 219 |   let expected = [
 220 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 221 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 222 |         \ ]
 223 |   call s:assert_signs(expected, 'fix[tu]re.txt')
 224 | endfunction
 225 | 
 226 | 
 227 | function Test_filename_with_space()
 228 |   call system('touch fix\ ture.txt && git add fix\ ture.txt')
 229 |   edit fix\ ture.txt
 230 |   normal ggo*
 231 |   call s:trigger_gitgutter()
 232 | 
 233 |   let expected = [
 234 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 235 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 236 |         \ ]
 237 |   call s:assert_signs(expected, 'fix\ ture.txt')
 238 | endfunction
 239 | 
 240 | 
 241 | function Test_filename_leading_dash()
 242 |   call system('touch -- -fixture.txt && git add -- -fixture.txt')
 243 |   edit -fixture.txt
 244 |   normal ggo*
 245 |   call s:trigger_gitgutter()
 246 | 
 247 |   let expected = [
 248 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 249 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 250 |         \ ]
 251 |   call s:assert_signs(expected, '-fixture.txt')
 252 | endfunction
 253 | 
 254 | 
 255 | function Test_filename_umlaut()
 256 |   call system('touch -- fixtüre.txt && git add -- fixtüre.txt')
 257 |   edit fixtüre.txt
 258 |   normal ggo*
 259 |   call s:trigger_gitgutter()
 260 | 
 261 |   let expected = [
 262 |         \ {'lnum': 1, 'name': 'GitGutterLineAdded'},
 263 |         \ {'lnum': 2, 'name': 'GitGutterLineAdded'}
 264 |         \ ]
 265 |   call s:assert_signs(expected, 'fixtüre.txt')
 266 | endfunction
 267 | 
 268 | 
 269 | function Test_file_cmd()
 270 |   normal ggo*
 271 | 
 272 |   file other.txt
 273 | 
 274 |   call s:trigger_gitgutter()
 275 |   call assert_equal(1, b:gitgutter.enabled)
 276 |   call assert_equal('', b:gitgutter.path)
 277 |   call s:assert_signs([], 'other.txt')
 278 | 
 279 |   write
 280 | 
 281 |   call s:trigger_gitgutter()
 282 |   call assert_equal(-2, b:gitgutter.path)
 283 | endfunction
 284 | 
 285 | 
 286 | function Test_saveas()
 287 |   normal ggo*
 288 | 
 289 |   saveas other.txt
 290 | 
 291 |   call s:trigger_gitgutter()
 292 |   call assert_equal(1, b:gitgutter.enabled)
 293 |   call assert_equal(-2, b:gitgutter.path)
 294 |   call s:assert_signs([], 'other.txt')
 295 | endfunction
 296 | 
 297 | 
 298 | function Test_file_mv()
 299 |   call system('git mv fixture.txt fixture_moved.txt')
 300 |   edit fixture_moved.txt
 301 |   normal ggo*
 302 |   call s:trigger_gitgutter()
 303 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
 304 |   call s:assert_signs(expected, 'fixture_moved.txt')
 305 | 
 306 |   write
 307 |   call system('git add fixture_moved.txt && git commit -m "moved and edited"')
 308 |   GitGutterDisable
 309 |   GitGutterEnable
 310 |   let expected = []
 311 |   call s:assert_signs(expected, 'fixture_moved.txt')
 312 | 
 313 |   GitGutterDisable
 314 |   let g:gitgutter_diff_base = 'HEAD^'
 315 |   GitGutterEnable
 316 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
 317 |   call s:assert_signs(expected, 'fixture_moved.txt')
 318 | endfunction
 319 | 
 320 | 
 321 | " FIXME: this test fails when it is the first (or only) test to be run
 322 | function Test_follow_symlink()
 323 |   let tmp = 'symlink'
 324 |   call system('ln -nfs fixture.txt '.tmp)
 325 |   execute 'edit '.tmp
 326 |   6d
 327 |   call s:trigger_gitgutter()
 328 | 
 329 |   let expected = [{'lnum': 5, 'name': 'GitGutterLineRemoved'}]
 330 |   call s:assert_signs(expected, 'symlink')
 331 | endfunction
 332 | 
 333 | 
 334 | function Test_keep_alt()
 335 |   enew
 336 |   execute "normal! \<C-^>"
 337 | 
 338 |   call assert_equal('fixture.txt', bufname(''))
 339 |   call assert_equal('',            bufname('#'))
 340 | 
 341 |   normal ggx
 342 |   call s:trigger_gitgutter()
 343 | 
 344 |   call assert_equal('', bufname('#'))
 345 | endfunction
 346 | 
 347 | 
 348 | function Test_keep_modified()
 349 |   normal 5Go*
 350 |   call assert_equal(1, getbufvar('', '&modified'))
 351 | 
 352 |   call s:trigger_gitgutter()
 353 | 
 354 |   call assert_equal(1, getbufvar('', '&modified'))
 355 | endfunction
 356 | 
 357 | 
 358 | function Test_keep_op_marks()
 359 |   normal 5Go*
 360 |   call assert_equal([0,6,1,0], getpos("'["))
 361 |   call assert_equal([0,6,2,0], getpos("']"))
 362 | 
 363 |   call s:trigger_gitgutter()
 364 | 
 365 |   call assert_equal([0,6,1,0], getpos("'["))
 366 |   call assert_equal([0,6,2,0], getpos("']"))
 367 | endfunction
 368 | 
 369 | 
 370 | function Test_no_modifications()
 371 |   call s:assert_signs([], 'fixture.txt')
 372 | endfunction
 373 | 
 374 | 
 375 | function Test_orphaned_signs()
 376 |   execute "normal 5GoX\<CR>Y"
 377 |   call s:trigger_gitgutter()
 378 |   6d
 379 |   call s:trigger_gitgutter()
 380 | 
 381 |   let expected = [{'lnum': 6, 'name': 'GitGutterLineAdded'}]
 382 |   call s:assert_signs(expected, 'fixture.txt')
 383 | endfunction
 384 | 
 385 | 
 386 | function Test_untracked_file_outside_repo()
 387 |   let tmp = tempname()
 388 |   call system('touch '.tmp)
 389 |   execute 'edit '.tmp
 390 | 
 391 |   call s:assert_signs([], tmp)
 392 | endfunction
 393 | 
 394 | 
 395 | function Test_untracked_file_within_repo()
 396 |   let tmp = 'untrackedFileWithinRepo.tmp'
 397 |   call system('touch '.tmp)
 398 |   execute 'edit '.tmp
 399 |   normal ggo*
 400 |   call s:trigger_gitgutter()
 401 | 
 402 |   call s:assert_signs([], tmp)
 403 |   call assert_equal(-2, b:gitgutter.path)
 404 | 
 405 |   call system('rm '.tmp)
 406 | endfunction
 407 | 
 408 | 
 409 | function Test_untracked_file_square_brackets_within_repo()
 410 |   let tmp = '[un]trackedFileWithinRepo.tmp'
 411 |   call system('touch '.tmp)
 412 |   execute 'edit '.tmp
 413 |   normal ggo*
 414 |   call s:trigger_gitgutter()
 415 | 
 416 |   call s:assert_signs([], tmp)
 417 | 
 418 |   call system('rm '.tmp)
 419 | endfunction
 420 | 
 421 | 
 422 | function Test_file_unknown_in_base()
 423 |   let starting_branch = split(system('git branch --show-current'))[0]
 424 |   call system('git checkout -b some-feature')
 425 |   let tmp = 'file-on-this-branch-only.tmp'
 426 |   call system('echo "hi" > '.tmp.' && git add '.tmp)
 427 |   execute 'edit '.tmp
 428 |   let g:gitgutter_diff_base = starting_branch
 429 |   GitGutter
 430 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineAdded', 'group': 'gitgutter', 'priority': 10}]
 431 |   call s:assert_signs(expected, tmp)
 432 |   let g:gitgutter_diff_base = ''
 433 | endfunction
 434 | 
 435 | 
 436 | function Test_v_shell_error_not_clobbered()
 437 |   " set gitgutter up to generate a shell error
 438 |   let starting_branch = split(system('git branch --show-current'))[0]
 439 |   call system('git checkout -b some-feature')
 440 |   let tmp = 'file-on-this-branch-only.tmp'
 441 |   call system('echo "hi" > '.tmp.' && git add '.tmp)
 442 |   execute 'edit '.tmp
 443 |   let g:gitgutter_diff_base = starting_branch
 444 | 
 445 |   " run a successful shell command
 446 |   silent !echo foobar >/dev/null
 447 | 
 448 |   " run gitgutter
 449 |   GitGutter
 450 | 
 451 |   call assert_equal(0, v:shell_error)
 452 | 
 453 |   let g:gitgutter_diff_base = ''
 454 | endfunction
 455 | 
 456 | 
 457 | function Test_hunk_outside_noop()
 458 |   5
 459 |   GitGutterStageHunk
 460 | 
 461 |   call s:assert_signs([], 'fixture.txt')
 462 |   call assert_equal([], s:git_diff())
 463 |   call assert_equal([], s:git_diff_staged())
 464 | 
 465 |   GitGutterUndoHunk
 466 | 
 467 |   call s:assert_signs([], 'fixture.txt')
 468 |   call assert_equal([], s:git_diff())
 469 |   call assert_equal([], s:git_diff_staged())
 470 | endfunction
 471 | 
 472 | 
 473 | function Test_preview()
 474 |   normal 5Gi*
 475 |   GitGutterPreviewHunk
 476 | 
 477 |   wincmd P
 478 |   call assert_equal(2, line('
#39;))
 479 |   call assert_equal('-e', getline(1))
 480 |   call assert_equal('+*e', getline(2))
 481 |   wincmd p
 482 | endfunction
 483 | 
 484 | 
 485 | function Test_preview_dos()
 486 |   edit! fixture_dos.txt
 487 | 
 488 |   normal 5Gi*
 489 |   GitGutterPreviewHunk
 490 | 
 491 |   wincmd P
 492 |   call assert_equal(2, line('
#39;))
 493 |   call assert_equal('-e', getline(1))
 494 |   call assert_equal('+*e', getline(2))
 495 |   wincmd p
 496 | endfunction
 497 | 
 498 | 
 499 | function Test_dos_noeol()
 500 |   edit! fixture_dos_noeol.txt
 501 |   GitGutter
 502 | 
 503 |   call s:assert_signs([], 'fixture_dos_noeol.txt')
 504 | endfunction
 505 | 
 506 | 
 507 | function Test_hunk_stage()
 508 |   let _shell = &shell
 509 |   set shell=foo
 510 | 
 511 |   normal 5Gi*
 512 |   GitGutterStageHunk
 513 | 
 514 |   call assert_equal('foo', &shell)
 515 |   let &shell = _shell
 516 | 
 517 |   call s:assert_signs([], 'fixture.txt')
 518 | 
 519 |   " Buffer is unsaved
 520 |   let expected = [
 521 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 522 |         \ 'index ae8e546..f5c6aff 100644',
 523 |         \ '--- a/fixture.txt',
 524 |         \ '+++ b/fixture.txt',
 525 |         \ '@@ -5 +5 @@ d',
 526 |         \ '-*e',
 527 |         \ '+e'
 528 |         \ ]
 529 |   call assert_equal(expected, s:git_diff())
 530 | 
 531 |   " Index has been updated
 532 |   let expected = [
 533 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 534 |         \ 'index f5c6aff..ae8e546 100644',
 535 |         \ '--- a/fixture.txt',
 536 |         \ '+++ b/fixture.txt',
 537 |         \ '@@ -5 +5 @@ d',
 538 |         \ '-e',
 539 |         \ '+*e'
 540 |         \ ]
 541 |   call assert_equal(expected, s:git_diff_staged())
 542 | 
 543 |   " Save the buffer
 544 |   write
 545 | 
 546 |   call assert_equal([], s:git_diff())
 547 | endfunction
 548 | 
 549 | 
 550 | function Test_hunk_stage_nearby_hunk()
 551 |   execute "normal! 2Gox\<CR>y\<CR>z"
 552 |   normal 2jdd
 553 |   normal k
 554 |   GitGutterStageHunk
 555 | 
 556 |   let expected = [
 557 |         \ {'lnum': 3, 'name': 'GitGutterLineAdded'},
 558 |         \ {'lnum': 4, 'name': 'GitGutterLineAdded'},
 559 |         \ {'lnum': 5, 'name': 'GitGutterLineAdded'}
 560 |         \ ]
 561 |   call s:assert_signs(expected, 'fixture.txt')
 562 | 
 563 |   " Buffer is unsaved
 564 |   let expected = [
 565 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 566 |         \ 'index 53b13df..f5c6aff 100644',
 567 |         \ '--- a/fixture.txt',
 568 |         \ '+++ b/fixture.txt',
 569 |         \ '@@ -3,0 +4 @@ c',
 570 |         \ '+d',
 571 |         \ ]
 572 |   call assert_equal(expected, s:git_diff())
 573 | 
 574 |   " Index has been updated
 575 |   let expected = [
 576 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 577 |         \ 'index f5c6aff..53b13df 100644',
 578 |         \ '--- a/fixture.txt',
 579 |         \ '+++ b/fixture.txt',
 580 |         \ '@@ -4 +3,0 @@ c',
 581 |         \ '-d',
 582 |         \ ]
 583 |   call assert_equal(expected, s:git_diff_staged())
 584 | 
 585 |   " Save the buffer
 586 |   write
 587 | 
 588 |   let expected = [
 589 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 590 |         \ 'index 53b13df..8fdfda7 100644',
 591 |         \ '--- a/fixture.txt',
 592 |         \ '+++ b/fixture.txt',
 593 |         \ '@@ -2,0 +3,3 @@ b',
 594 |         \ '+x',
 595 |         \ '+y',
 596 |         \ '+z',
 597 |         \ ]
 598 |   call assert_equal(expected, s:git_diff())
 599 | endfunction
 600 | 
 601 | 
 602 | function Test_hunk_stage_partial_visual_added()
 603 |   call append(5, ['A','B','C','D'])
 604 |   execute "normal 7GVj:GitGutterStageHunk\<CR>"
 605 | 
 606 |   let expected = [
 607 |         \ {'lnum': 6, 'name': 'GitGutterLineAdded'},
 608 |         \ {'lnum': 9, 'name': 'GitGutterLineAdded'},
 609 |         \ ]
 610 |   call s:assert_signs(expected, 'fixture.txt')
 611 | 
 612 |   let expected = [
 613 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 614 |         \ 'index 8a7026e..f5c6aff 100644',
 615 |         \ '--- a/fixture.txt',
 616 |         \ '+++ b/fixture.txt',
 617 |         \ '@@ -6,2 +5,0 @@ e',
 618 |         \ '-B',
 619 |         \ '-C',
 620 |         \ ]
 621 |   call assert_equal(expected, s:git_diff())
 622 | 
 623 |   let expected = [
 624 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 625 |         \ 'index f5c6aff..8a7026e 100644',
 626 |         \ '--- a/fixture.txt',
 627 |         \ '+++ b/fixture.txt',
 628 |         \ '@@ -5,0 +6,2 @@ e',
 629 |         \ '+B',
 630 |         \ '+C',
 631 |         \ ]
 632 |   call assert_equal(expected, s:git_diff_staged())
 633 | endfunction
 634 | 
 635 | 
 636 | function Test_hunk_stage_partial_cmd_added()
 637 |   call append(5, ['A','B','C','D'])
 638 |   6
 639 |   7,8GitGutterStageHunk
 640 | 
 641 |   let expected = [
 642 |         \ {'lnum': 6, 'name': 'GitGutterLineAdded'},
 643 |         \ {'lnum': 9, 'name': 'GitGutterLineAdded'},
 644 |         \ ]
 645 |   call s:assert_signs(expected, 'fixture.txt')
 646 | 
 647 |   let expected = [
 648 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 649 |         \ 'index 8a7026e..f5c6aff 100644',
 650 |         \ '--- a/fixture.txt',
 651 |         \ '+++ b/fixture.txt',
 652 |         \ '@@ -6,2 +5,0 @@ e',
 653 |         \ '-B',
 654 |         \ '-C',
 655 |         \ ]
 656 |   call assert_equal(expected, s:git_diff())
 657 | 
 658 |   let expected = [
 659 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 660 |         \ 'index f5c6aff..8a7026e 100644',
 661 |         \ '--- a/fixture.txt',
 662 |         \ '+++ b/fixture.txt',
 663 |         \ '@@ -5,0 +6,2 @@ e',
 664 |         \ '+B',
 665 |         \ '+C',
 666 |         \ ]
 667 |   call assert_equal(expected, s:git_diff_staged())
 668 | endfunction
 669 | 
 670 | 
 671 | function Test_hunk_stage_partial_preview_added()
 672 |   call append(5, ['A','B','C','D'])
 673 |   6
 674 |   GitGutterPreviewHunk
 675 |   wincmd P
 676 | 
 677 |   " remove C and A so we stage B and D
 678 |   3delete
 679 |   1delete
 680 | 
 681 |   GitGutterStageHunk
 682 |   write
 683 | 
 684 |   let expected = [
 685 |         \ {'lnum': 6, 'name': 'GitGutterLineAdded'},
 686 |         \ {'lnum': 8, 'name': 'GitGutterLineAdded'},
 687 |         \ ]
 688 |   call s:assert_signs(expected, 'fixture.txt')
 689 | 
 690 |   let expected = [
 691 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 692 |         \ 'index 975852f..3dd23a3 100644',
 693 |         \ '--- a/fixture.txt',
 694 |         \ '+++ b/fixture.txt',
 695 |         \ '@@ -5,0 +6 @@ e',
 696 |         \ '+A',
 697 |         \ '@@ -6,0 +8 @@ B',
 698 |         \ '+C',
 699 |         \ ]
 700 |   call assert_equal(expected, s:git_diff())
 701 | 
 702 |   let expected = [
 703 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 704 |         \ 'index f5c6aff..975852f 100644',
 705 |         \ '--- a/fixture.txt',
 706 |         \ '+++ b/fixture.txt',
 707 |         \ '@@ -5,0 +6,2 @@ e',
 708 |         \ '+B',
 709 |         \ '+D',
 710 |         \ ]
 711 |   call assert_equal(expected, s:git_diff_staged())
 712 | endfunction
 713 | 
 714 | 
 715 | function Test_hunk_stage_preview_write()
 716 |   call append(5, ['A','B','C','D'])
 717 |   6
 718 |   GitGutterPreviewHunk
 719 |   wincmd P
 720 | 
 721 |   " preview window
 722 |   call feedkeys(":w\<CR>", 'tx')
 723 |   " original window
 724 |   write
 725 | 
 726 |   call s:assert_signs([], 'fixture.txt')
 727 | 
 728 |   call assert_equal([], s:git_diff())
 729 | 
 730 |   let expected = [
 731 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 732 |         \ 'index f5c6aff..3dd23a3 100644',
 733 |         \ '--- a/fixture.txt',
 734 |         \ '+++ b/fixture.txt',
 735 |         \ '@@ -5,0 +6,4 @@ e',
 736 |         \ '+A',
 737 |         \ '+B',
 738 |         \ '+C',
 739 |         \ '+D',
 740 |         \ ]
 741 |   call assert_equal(expected, s:git_diff_staged())
 742 | endfunction
 743 | 
 744 | 
 745 | function Test_hunk_stage_partial_preview_added_removed()
 746 |   4,5delete
 747 |   call append(3, ['A','B','C','D'])
 748 |   4
 749 |   GitGutterPreviewHunk
 750 |   wincmd P
 751 | 
 752 |   " -d
 753 |   " -e
 754 |   " +A
 755 |   " +B
 756 |   " +C
 757 |   " +D
 758 | 
 759 |   " remove D and d so they do not get staged
 760 |   6delete
 761 |   1delete
 762 | 
 763 |   GitGutterStageHunk
 764 |   write
 765 | 
 766 |   let expected = [
 767 |         \ {'lnum': 3, 'name': 'GitGutterLineRemoved'},
 768 |         \ {'lnum': 7, 'name': 'GitGutterLineAdded'},
 769 |         \ ]
 770 |   call s:assert_signs(expected, 'fixture.txt')
 771 | 
 772 |   let expected = [
 773 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 774 |         \ 'index 9a19589..e63fb0a 100644',
 775 |         \ '--- a/fixture.txt',
 776 |         \ '+++ b/fixture.txt',
 777 |         \ '@@ -4 +3,0 @@ c',
 778 |         \ '-d',
 779 |         \ '@@ -7,0 +7 @@ C',
 780 |         \ '+D',
 781 |         \ ]
 782 |   call assert_equal(expected, s:git_diff())
 783 | 
 784 |   let expected = [
 785 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 786 |         \ 'index f5c6aff..9a19589 100644',
 787 |         \ '--- a/fixture.txt',
 788 |         \ '+++ b/fixture.txt',
 789 |         \ '@@ -5 +5,3 @@ d',
 790 |         \ '-e',
 791 |         \ '+A',
 792 |         \ '+B',
 793 |         \ '+C',
 794 |         \ ]
 795 |   call assert_equal(expected, s:git_diff_staged())
 796 | endfunction
 797 | 
 798 | 
 799 | function Test_hunk_undo()
 800 |   let _shell = &shell
 801 |   set shell=foo
 802 | 
 803 |   normal 5Gi*
 804 |   GitGutterUndoHunk
 805 | 
 806 |   call assert_equal('foo', &shell)
 807 |   let &shell = _shell
 808 | 
 809 |   call s:assert_signs([], 'fixture.txt')
 810 |   call assert_equal([], s:git_diff())
 811 |   call assert_equal([], s:git_diff_staged())
 812 |   call assert_equal('e', getline(5))
 813 | endfunction
 814 | 
 815 | 
 816 | function Test_hunk_undo_dos()
 817 |   edit! fixture_dos.txt
 818 | 
 819 |   normal 5Gi*
 820 |   GitGutterUndoHunk
 821 | 
 822 |   call s:assert_signs([], 'fixture_dos.txt')
 823 |   call assert_equal([], s:git_diff('fixture_dos.txt'))
 824 |   call assert_equal([], s:git_diff_staged('fixture_dos.txt'))
 825 |   call assert_equal('e', getline(5))
 826 | endfunction
 827 | 
 828 | 
 829 | function Test_undo_nearby_hunk()
 830 |   execute "normal! 2Gox\<CR>y\<CR>z"
 831 |   normal 2jdd
 832 |   normal k
 833 |   call s:trigger_gitgutter()
 834 |   GitGutterUndoHunk
 835 |   call s:trigger_gitgutter()
 836 | 
 837 |   let expected = [
 838 |         \ {'lnum': 3, 'name': 'GitGutterLineAdded'},
 839 |         \ {'lnum': 4, 'name': 'GitGutterLineAdded'},
 840 |         \ {'lnum': 5, 'name': 'GitGutterLineAdded'}
 841 |         \ ]
 842 |   call s:assert_signs(expected, 'fixture.txt')
 843 | 
 844 |   call assert_equal([], s:git_diff())
 845 | 
 846 |   call assert_equal([], s:git_diff_staged())
 847 | 
 848 |   " Save the buffer
 849 |   write
 850 | 
 851 |   let expected = [
 852 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 853 |         \ 'index f5c6aff..3fbde56 100644',
 854 |         \ '--- a/fixture.txt',
 855 |         \ '+++ b/fixture.txt',
 856 |         \ '@@ -2,0 +3,3 @@ b',
 857 |         \ '+x',
 858 |         \ '+y',
 859 |         \ '+z',
 860 |         \ ]
 861 |   call assert_equal(expected, s:git_diff())
 862 | 
 863 | endfunction
 864 | 
 865 | 
 866 | function Test_overlapping_hunk_op()
 867 |   func! Answer(char)
 868 |     call feedkeys(a:char."\<CR>")
 869 |   endfunc
 870 | 
 871 |   " Undo upper
 872 | 
 873 |   execute '3d'
 874 |   execute '1d'
 875 |   call s:trigger_gitgutter()
 876 |   normal gg
 877 |   call timer_start(100, {-> Answer('u')} )
 878 |   GitGutterUndoHunk
 879 |   call s:trigger_gitgutter()
 880 | 
 881 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineRemoved'}]
 882 |   call s:assert_signs(expected, 'fixture.txt')
 883 | 
 884 |   " Undo lower
 885 | 
 886 |   execute '1d'
 887 |   call s:trigger_gitgutter()
 888 |   normal gg
 889 |   call timer_start(100, {-> Answer('l')} )
 890 |   GitGutterUndoHunk
 891 |   call s:trigger_gitgutter()
 892 | 
 893 |   let expected = [{'lnum': 1, 'name': 'GitGutterLineRemovedFirstLine'}]
 894 |   call s:assert_signs(expected, 'fixture.txt')
 895 | endfunction
 896 | 
 897 | 
 898 | function Test_write_option()
 899 |   set nowrite
 900 | 
 901 |   normal ggo*
 902 |   call s:trigger_gitgutter()
 903 | 
 904 |   let expected = [{'lnum': 2, 'name': 'GitGutterLineAdded'}]
 905 |   call s:assert_signs(expected, 'fixture.txt')
 906 | 
 907 |   set write
 908 | endfunction
 909 | 
 910 | 
 911 | function Test_inner_text_object()
 912 |   execute "normal! 2Gox\<CR>y\<CR>z\<CR>\<CR>"
 913 |   call s:trigger_gitgutter()
 914 |   normal dic
 915 |   call s:trigger_gitgutter()
 916 | 
 917 |   call s:assert_signs([], 'fixture.txt')
 918 |   call assert_equal(readfile('fixture.txt'), getline(1,'
#39;))
 919 | 
 920 |   " Excludes trailing lines
 921 |   normal 9Gi*
 922 |   normal 10Gi*
 923 |   call s:trigger_gitgutter()
 924 |   execute "normal vic\<Esc>"
 925 |   call assert_equal([9, 10], [line("'<"), line("'>")])
 926 | endfunction
 927 | 
 928 | 
 929 | function Test_around_text_object()
 930 |   execute "normal! 2Gox\<CR>y\<CR>z\<CR>\<CR>"
 931 |   call s:trigger_gitgutter()
 932 |   normal dac
 933 |   call s:trigger_gitgutter()
 934 | 
 935 |   call s:assert_signs([], 'fixture.txt')
 936 |   call assert_equal(readfile('fixture.txt'), getline(1,'
#39;))
 937 | 
 938 |   " Includes trailing lines
 939 |   normal 9Gi*
 940 |   normal 10Gi*
 941 |   call s:trigger_gitgutter()
 942 |   execute "normal vac\<Esc>"
 943 |   call assert_equal([9, 11], [line("'<"), line("'>")])
 944 | endfunction
 945 | 
 946 | 
 947 | function Test_user_autocmd()
 948 |   autocmd User GitGutter let s:autocmd_user = g:gitgutter_hook_context.bufnr
 949 | 
 950 |   " Verify not fired when nothing changed.
 951 |   let s:autocmd_user = 0
 952 |   call s:trigger_gitgutter()
 953 |   call assert_equal(0, s:autocmd_user)
 954 | 
 955 |   " Verify fired when there was a change.
 956 |   normal ggo*
 957 |   let bufnr = bufnr('')
 958 |   call s:trigger_gitgutter()
 959 |   call assert_equal(bufnr, s:autocmd_user)
 960 | endfunction
 961 | 
 962 | 
 963 | function Test_fix_file_references()
 964 |   let sid = matchstr(execute('filter autoload/gitgutter/hunk.vim scriptnames'), '\d\+')
 965 |   let FixFileReferences = function("<SNR>".sid."_fix_file_references")
 966 | 
 967 |   " No special characters
 968 |   let hunk_diff = join([
 969 |         \ 'diff --git a/fixture.txt b/fixture.txt',
 970 |         \ 'index f5c6aff..3fbde56 100644',
 971 |         \ '--- a/fixture.txt',
 972 |         \ '+++ b/fixture.txt',
 973 |         \ '@@ -2,0 +3,1 @@ b',
 974 |         \ '+x'
 975 |         \ ], "\n")."\n"
 976 |   let filepath = 'blah.txt'
 977 | 
 978 |   let expected = join([
 979 |         \ 'diff --git a/blah.txt b/blah.txt',
 980 |         \ 'index f5c6aff..3fbde56 100644',
 981 |         \ '--- a/blah.txt',
 982 |         \ '+++ b/blah.txt',
 983 |         \ '@@ -2,0 +3,1 @@ b',
 984 |         \ '+x'
 985 |         \ ], "\n")."\n"
 986 | 
 987 |   call assert_equal(expected, FixFileReferences(filepath, hunk_diff))
 988 | 
 989 |   " diff.mnemonicPrefix; spaces in filename
 990 |   let hunk_diff = join([
 991 |         \ 'diff --git i/x/cat dog w/x/cat dog',
 992 |         \ 'index f5c6aff..3fbde56 100644',
 993 |         \ '--- i/x/cat dog',
 994 |         \ '+++ w/x/cat dog',
 995 |         \ '@@ -2,0 +3,1 @@ b',
 996 |         \ '+x'
 997 |         \ ], "\n")."\n"
 998 |   let filepath = 'blah.txt'
 999 | 
1000 |   let expected = join([
1001 |         \ 'diff --git i/blah.txt w/blah.txt',
1002 |         \ 'index f5c6aff..3fbde56 100644',
1003 |         \ '--- i/blah.txt',
1004 |         \ '+++ w/blah.txt',
1005 |         \ '@@ -2,0 +3,1 @@ b',
1006 |         \ '+x'
1007 |         \ ], "\n")."\n"
1008 | 
1009 |   call assert_equal(expected, FixFileReferences(filepath, hunk_diff))
1010 | 
1011 |   " Backslashes in filename; quotation marks
1012 |   let hunk_diff = join([
1013 |         \ 'diff --git "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim" "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"',
1014 |         \ 'index f42aeb0..4930403 100644',
1015 |         \ '--- "a/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\11.1.vim"',
1016 |         \ '+++ "b/C:\\Users\\FOO~1.PAR\\AppData\\Local\\Temp\\nvimJcmSv9\\12.1.vim"',
1017 |         \ '@@ -172,0 +173 @@ stuff',
1018 |         \ '+x'
1019 |         \ ], "\n")."\n"
1020 |   let filepath = 'init.vim'
1021 | 
1022 |   let expected = join([
1023 |         \ 'diff --git "a/init.vim" "b/init.vim"',
1024 |         \ 'index f42aeb0..4930403 100644',
1025 |         \ '--- "a/init.vim"',
1026 |         \ '+++ "b/init.vim"',
1027 |         \ '@@ -172,0 +173 @@ stuff',
1028 |         \ '+x'
1029 |         \ ], "\n")."\n"
1030 | 
1031 |   call assert_equal(expected, FixFileReferences(filepath, hunk_diff))
1032 | endfunction
1033 | 
1034 | 
1035 | function Test_encoding()
1036 |   call system('cp ../cp932.txt . && git add cp932.txt')
1037 |   edit ++enc=cp932 cp932.txt
1038 | 
1039 |   call s:trigger_gitgutter()
1040 | 
1041 |   call s:assert_signs([], 'cp932.txt')
1042 | endfunction
1043 | 
1044 | 
1045 | function Test_empty_file()
1046 |   " 0-byte file
1047 |   call system('touch empty.txt && git add empty.txt')
1048 |   edit empty.txt
1049 | 
1050 |   call s:trigger_gitgutter()
1051 |   call s:assert_signs([], 'empty.txt')
1052 | 
1053 | 
1054 |   " File consisting only of a newline
1055 |   call system('echo "" > newline.txt && git add newline.txt')
1056 |   edit newline.txt
1057 | 
1058 |   call s:trigger_gitgutter()
1059 |   call s:assert_signs([], 'newline.txt')
1060 | 
1061 | 
1062 |   " 1 line file without newline
1063 |   " Vim will force a newline unless we tell it not to.
1064 |   call system('echo -n a > oneline.txt && git add oneline.txt')
1065 |   set noeol nofixeol
1066 |   edit! oneline.txt
1067 | 
1068 |   call s:trigger_gitgutter()
1069 |   call s:assert_signs([], 'oneline.txt')
1070 | 
1071 |   set eol fixeol
1072 | endfunction
1073 | 
1074 | 
1075 | function Test_quickfix()
1076 |   call setline(5, ['A', 'B'])
1077 |   call setline(9, ['C', 'D'])
1078 |   write
1079 |   let bufnr1 = bufnr('')
1080 | 
1081 |   edit fixture_dos.txt
1082 |   call setline(2, ['A', 'B'])
1083 |   write
1084 |   let bufnr2 = bufnr('')
1085 | 
1086 |   GitGutterQuickFix
1087 | 
1088 |   let expected = [
1089 |         \ {'lnum': 5, 'bufnr': bufnr1, 'text': '-e'},
1090 |         \ {'lnum': 9, 'bufnr': bufnr1, 'text': '-i'},
1091 |         \ {'lnum': 2, 'bufnr': bufnr2, 'text': "-b\r"}
1092 |         \ ]
1093 | 
1094 |   call s:assert_list_of_dicts(expected, getqflist())
1095 | 
1096 |   GitGutterQuickFixCurrentFile
1097 | 
1098 |   let expected = [
1099 |         \ {'lnum': 2, 'bufnr': bufnr(''), 'text': "-b\r"},
1100 |         \ ]
1101 | 
1102 |   call s:assert_list_of_dicts(expected, getqflist())
1103 | endfunction
1104 | 
1105 | 
1106 | function Test_common_prefix()
1107 |   let sid = matchstr(execute('filter autoload/gitgutter/diff_highlight.vim scriptnames'), '\d\+')
1108 |   let CommonPrefix = function("<SNR>".sid."_common_prefix")
1109 | 
1110 |   " zero length
1111 |   call assert_equal(-1, CommonPrefix('', 'foo'))
1112 |   call assert_equal(-1, CommonPrefix('foo', ''))
1113 |   " nothing in common
1114 |   call assert_equal(-1, CommonPrefix('-abcde', '+pqrst'))
1115 |   call assert_equal(-1, CommonPrefix('abcde', 'pqrst'))
1116 |   " something in common
1117 |   call assert_equal(-1, CommonPrefix('-abcde', '+abcpq'))
1118 |   call assert_equal(2, CommonPrefix('abcde', 'abcpq'))
1119 |   call assert_equal(0, CommonPrefix('abc', 'apq'))
1120 |   " everything in common
1121 |   call assert_equal(-1, CommonPrefix('-abcde', '+abcde'))
1122 |   call assert_equal(4, CommonPrefix('abcde', 'abcde'))
1123 |   " different lengths
1124 |   call assert_equal(-1, CommonPrefix('-abcde', '+abx'))
1125 |   call assert_equal(1, CommonPrefix('abcde', 'abx'))
1126 |   call assert_equal(-1, CommonPrefix('-abx',   '+abcde'))
1127 |   call assert_equal(1, CommonPrefix('abx',   'abcde'))
1128 |   call assert_equal(-1, CommonPrefix('-abcde', '+abc'))
1129 |   call assert_equal(2, CommonPrefix('abcde', 'abc'))
1130 | endfunction
1131 | 
1132 | 
1133 | function Test_common_suffix()
1134 |   let sid = matchstr(execute('filter autoload/gitgutter/diff_highlight.vim scriptnames'), '\d\+')
1135 |   let CommonSuffix = function("<SNR>".sid."_common_suffix")
1136 | 
1137 |   " nothing in common
1138 |   call assert_equal([6,6], CommonSuffix('-abcde', '+pqrst', 0))
1139 |   " something in common
1140 |   call assert_equal([3,3], CommonSuffix('-abcde', '+pqcde', 0))
1141 |   " everything in common
1142 |   call assert_equal([5,5], CommonSuffix('-abcde', '+abcde', 5))
1143 |   " different lengths
1144 |   call assert_equal([4,2], CommonSuffix('-abcde', '+xde', 0))
1145 |   call assert_equal([2,4], CommonSuffix('-xde',   '+abcde', 0))
1146 | endfunction
1147 | 
1148 | 
1149 | " Note the order of lists within the overall returned list does not matter.
1150 | function Test_diff_highlight()
1151 |   " Ignores mismatched number of added and removed lines.
1152 |   call assert_equal([], gitgutter#diff_highlight#process(['-foo']))
1153 |   call assert_equal([], gitgutter#diff_highlight#process(['+foo']))
1154 |   call assert_equal([], gitgutter#diff_highlight#process(['-foo','-bar','+baz']))
1155 | 
1156 |   " everything changed
1157 |   let hunk = ['-foo', '+cat']
1158 |   let expected = []
1159 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1160 | 
1161 |   " change in middle
1162 |   let hunk = ['-foo bar baz', '+foo zip baz']
1163 |   let expected = [[1, '-', 6, 8], [2, '+', 6, 8]]
1164 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1165 | 
1166 |   " change at start
1167 |   let hunk = ['-foo bar baz', '+zip bar baz']
1168 |   let expected = [[1, '-', 2, 4], [2, '+', 2, 4]]
1169 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1170 | 
1171 |   " change at end
1172 |   let hunk = ['-foo bar baz', '+foo bar zip']
1173 |   let expected = [[1, '-', 10, 12], [2, '+', 10, 12]]
1174 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1175 | 
1176 |   " removed in middle
1177 |   let hunk = ['-foo bar baz', '+foo baz']
1178 |   let expected = [[1, '-', 8, 11]]
1179 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1180 | 
1181 |   " added in middle
1182 |   let hunk = ['-foo baz', '+foo bar baz']
1183 |   let expected = [[2, '+', 8, 11]]
1184 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1185 | 
1186 |   " two insertions at start
1187 |   let hunk = ['-foo bar baz', '+(foo) bar baz']
1188 |   let expected = [[2, '+', 2, 2], [2, '+', 6, 6]]
1189 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1190 | 
1191 |   " two insertions in middle
1192 |   let hunk = ['-foo bar baz', '+foo (bar) baz']
1193 |   let expected = [[2, '+', 6, 6], [2, '+', 10, 10]]
1194 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1195 | 
1196 |   " two insertions at end
1197 |   let hunk = ['-foo bar baz', '+foo bar (baz)']
1198 |   let expected = [[2, '+', 10, 10], [2, '+', 14, 14]]
1199 |   call assert_equal(expected, gitgutter#diff_highlight#process(hunk))
1200 | 
1201 |   " singular insertion
1202 |   let hunk = ['-The cat in the hat.', '+The furry cat in the hat.']
1203 |   call assert_equal([[2, '+', 6, 11]], gitgutter#diff_highlight#process(hunk))
1204 | 
1205 |   " singular deletion
1206 |   let hunk = ['-The cat in the hat.', '+The cat.']
1207 |   call assert_equal([[1, '-', 9, 19]], gitgutter#diff_highlight#process(hunk))
1208 | 
1209 |   " two insertions
1210 |   let hunk = ['-The cat in the hat.', '+The furry cat in the teal hat.']
1211 |   call assert_equal([[2, '+', 6, 11], [2, '+', 22, 26]], gitgutter#diff_highlight#process(hunk))
1212 | 
1213 |   " two deletions
1214 |   let hunk = ['-The furry cat in the teal hat.', '+The cat in the hat.']
1215 |   call assert_equal([[1, '-', 6, 11], [1, '-', 22, 26]], gitgutter#diff_highlight#process(hunk))
1216 | 
1217 |   " two edits
1218 |   let hunk = ['-The cat in the hat.', '+The ox in the box.']
1219 |   call assert_equal([[1, '-', 6, 8], [2, '+', 6, 7], [1, '-', 17, 19], [2, '+', 16, 18]], gitgutter#diff_highlight#process(hunk))
1220 | 
1221 |   " Requires s:gap_between_regions = 2 to pass.
1222 |   " let hunk = ['-foo: bar.zap', '+foo: quux(bar)']
1223 |   " call assert_equal([[2, '+', 7, 11], [1, '-', 10, 13], [2, '+', 15, 15]], gitgutter#diff_highlight#process(hunk))
1224 | 
1225 |   let hunk = ['-gross_value: transaction.unexplained_amount', '+gross_value: amount(transaction)']
1226 |   call assert_equal([[2, '+', 15, 21], [1, '-', 26, 44], [2, '+', 33, 33]], gitgutter#diff_highlight#process(hunk))
1227 | 
1228 |   let hunk = ['-gem "contact_sport", "~> 1.0.2"', '+gem ("contact_sport"), "~> 1.2"']
1229 |   call assert_equal([[2, '+', 6, 6], [2, '+', 22, 22], [1, '-', 28, 29]], gitgutter#diff_highlight#process(hunk))
1230 | endfunction
1231 | 
1232 | 
1233 | function Test_lcs()
1234 |   let sid = matchstr(execute('filter autoload/gitgutter/diff_highlight.vim scriptnames'), '\d\+')
1235 |   let Lcs = function("<SNR>".sid."_lcs")
1236 | 
1237 |   call assert_equal('', Lcs('', 'foo'))
1238 |   call assert_equal('', Lcs('foo', ''))
1239 |   call assert_equal('bar', Lcs('foobarbaz', 'bbart'))
1240 |   call assert_equal('transaction', Lcs('transaction.unexplained_amount', 'amount(transaction)'))
1241 | endfunction
1242 | 
1243 | 
1244 | function Test_split()
1245 |   let sid = matchstr(execute('filter autoload/gitgutter/diff_highlight.vim scriptnames'), '\d\+')
1246 |   let Split = function("<SNR>".sid."_split")
1247 | 
1248 |   call assert_equal(['foo', 'baz'], Split('foobarbaz', 'bar'))
1249 |   call assert_equal(['', 'barbaz'], Split('foobarbaz', 'foo'))
1250 |   call assert_equal(['foobar', ''], Split('foobarbaz', 'baz'))
1251 |   call assert_equal(['1', '2'], Split('1~2', '~'))
1252 | endfunction
1253 | 
1254 | 
1255 | function Test_foldtext()
1256 |   8d
1257 |   call s:trigger_gitgutter()
1258 |   call assert_equal(0, gitgutter#fold#is_changed())
1259 | 
1260 |   let v:foldstart = 5
1261 |   let v:foldend = 9
1262 |   call assert_equal(1, gitgutter#fold#is_changed())
1263 |   call assert_equal('+-  5 lines (*): e', gitgutter#fold#foldtext())
1264 | 
1265 |   let v:foldstart = 1
1266 |   let v:foldend = 3
1267 |   call assert_equal(0, gitgutter#fold#is_changed())
1268 |   call assert_equal('+-  3 lines: a', gitgutter#fold#foldtext())
1269 | endfunction
1270 | 
1271 | 
1272 | function Test_assume_unchanged()
1273 |   call system("git update-index --assume-unchanged fixture.txt")
1274 |   unlet b:gitgutter.path  " it was already set when fixture.txt was loaded in SetUp()
1275 |   normal ggo*
1276 |   call s:trigger_gitgutter()
1277 |   call s:assert_signs([], 'fixture.txt')
1278 | endfunction
1279 | 
1280 | 
1281 | function Test_clean_smudge_filter()
1282 |   call system("git config --local include.path ../.gitconfig")
1283 |   call system("rm fixture.foo && git checkout fixture.foo")
1284 | 
1285 |   func! Answer(char)
1286 |     call feedkeys(a:char."\<CR>")
1287 |   endfunc
1288 | 
1289 |   edit fixture.foo
1290 |   call setline(2, ['A'])
1291 |   call setline(4, ['B'])
1292 |   call s:trigger_gitgutter()
1293 |   normal! 2G
1294 |   call timer_start(100, {-> Answer('y')} )
1295 |   GitGutterStageHunk
1296 |   call s:trigger_gitgutter()
1297 | 
1298 |   let expected = [
1299 |         \ {'lnum': 2, 'id': 23, 'name': 'GitGutterLineModified', 'priority': 10, 'group': 'gitgutter'},
1300 |         \ {'lnum': 4, 'id': 24, 'name': 'GitGutterLineModified', 'priority': 10, 'group': 'gitgutter'}
1301 |         \ ]
1302 |   " call s:assert_signs(expected, 'fixture.foo')
1303 |   call s:assert_signs([], 'fixture.foo')
1304 | endfunction
1305 | 


--------------------------------------------------------------------------------