diff --git a/README.mkd b/README.mkd index 1668c49..b9416f9 100644 --- a/README.mkd +++ b/README.mkd @@ -12,6 +12,7 @@ Features: * Never saves the buffer. * Quick jumping between blocks of changed lines ("hunks"). * Stage/undo/preview individual hunks. +* Stage partial hunks. * Provides a hunk text object. * Diffs against index (default) or any commit. * Allows folding all unchanged text. @@ -187,11 +188,18 @@ You can stage or undo an individual hunk when your cursor is in it: * stage the hunk with `hs` or * undo it with `hu`. -You can stage part of an additions-only hunk by: +To stage part of an additions-only hunk by: * either visually selecting the part you want and staging with your mapping, e.g. `hs`; * or using a range with the `GitGutterStageHunk` command, e.g. `:42,45GitGutterStageHunk`. +To stage part of any hunk: + +* preview the hunk, e.g. `hp`; +* move to the preview window, e.g. `:wincmd P`; +* delete the lines you do not want to stage; +* stage the remaining lines, e.g. `hs` or `:GitGutterStageHunk`. + See the FAQ if you want to unstage staged changes. The `.` command will work with both these if you install [repeat.vim](https://github.com/tpope/vim-repeat). diff --git a/autoload/gitgutter/hunk.vim b/autoload/gitgutter/hunk.vim index 0ec20cb..4cd7c2a 100644 --- a/autoload/gitgutter/hunk.vim +++ b/autoload/gitgutter/hunk.vim @@ -192,6 +192,36 @@ endfunction function! s:hunk_op(op, ...) let bufnr = bufnr('') + if &previewwindow + if string(a:op) =~ '_stage' + " combine hunk-body in preview window with updated hunk-header + let hunk_body = getline(1, '$') + + let [removed, added] = [0, 0] + for line in hunk_body + if line[0] == '-' + let removed += 1 + elseif line[0] == '+' + let added += 1 + endif + endfor + + let hunk_header = b:hunk_header + " from count + let hunk_header[4] = substitute(hunk_header[4], '\(-\d\+\)\(,\d\+\)\?', '\=submatch(1).",".removed', '') + " to count + let hunk_header[4] = substitute(hunk_header[4], '\(+\d\+\)\(,\d\+\)\?', '\=submatch(1).",".added', '') + + let hunk_diff = join(hunk_header + hunk_body, "\n")."\n" + + wincmd p + pclose + call s:stage(hunk_diff) + endif + + return + endif + if gitgutter#utility#is_active(bufnr) " Get a (synchronous) diff. let [async, g:gitgutter_async] = [g:gitgutter_async, 0] @@ -278,11 +308,12 @@ function! s:preview(hunk_diff) execute 'resize' previewheight endif + let b:hunk_header = header + setlocal noreadonly modifiable filetype=diff buftype=nofile bufhidden=delete noswapfile execute "%delete_" call setline(1, body) normal! gg - setlocal readonly nomodifiable noautocmd wincmd p endfunction @@ -340,11 +371,6 @@ function! s:adjust_hunk_summary(hunk_diff) abort endfunction -function! s:discard_header(hunk_diff) - return join(split(a:hunk_diff, '\n', 1)[5:], "\n") -endfunction - - " Returns the number of lines the current hunk is offset from where it would " be if any changes above it in the file didn't exist. function! s:line_adjustment_for_current_hunk() abort diff --git a/doc/gitgutter.txt b/doc/gitgutter.txt index 034104e..b01df60 100644 --- a/doc/gitgutter.txt +++ b/doc/gitgutter.txt @@ -168,6 +168,10 @@ Commands for operating on a hunk:~ to stage part of an (additions-only) hunk; or use a range. + To stage part of any hunk, first |GitGutterPreviewHunk| + it, then move to the preview window, delete the lines + you do not want to stage, and |GitGutterStageHunk|. + *gitgutter-:GitGutterUndoHunk* :GitGutterUndoHunk Undo the hunk the cursor is in. @@ -176,6 +180,10 @@ Commands for operating on a hunk:~ Use |:pclose| or |CTRL-W_CTRL-Z| to close the preview window. + To stage part of the hunk, move to the preview window, + delete any lines you do not want to stage, and + |GitGutterStageHunk|. + Commands for folds:~ *gitgutter-:GitGutterFold* diff --git a/test/test_gitgutter.vim b/test/test_gitgutter.vim index ca1ddcc..c5741a6 100644 --- a/test/test_gitgutter.vim +++ b/test/test_gitgutter.vim @@ -486,6 +486,104 @@ function Test_hunk_stage_partial_cmd_added() endfunction +function Test_hunk_stage_partial_preview_added() + call append(5, ['A','B','C','D']) + normal 6G + GitGutterPreviewHunk + wincmd P + + " remove C and A so we stage B and D + 3delete + 1delete + + GitGutterStageHunk + write + + let expected = [ + \ 'line=6 id=3000 name=GitGutterLineAdded priority=10', + \ 'line=8 id=3002 name=GitGutterLineAdded priority=10', + \ ] + call assert_equal(expected, s:signs('fixture.txt')) + + let expected = [ + \ 'diff --git a/fixture.txt b/fixture.txt', + \ 'index 975852f..3dd23a3 100644', + \ '--- a/fixture.txt', + \ '+++ b/fixture.txt', + \ '@@ -5,0 +6 @@ e', + \ '+A', + \ '@@ -6,0 +8 @@ B', + \ '+C', + \ ] + call assert_equal(expected, s:git_diff()) + + let expected = [ + \ 'diff --git a/fixture.txt b/fixture.txt', + \ 'index f5c6aff..975852f 100644', + \ '--- a/fixture.txt', + \ '+++ b/fixture.txt', + \ '@@ -5,0 +6,2 @@ e', + \ '+B', + \ '+D', + \ ] + call assert_equal(expected, s:git_diff_staged()) +endfunction + + +function Test_hunk_stage_partial_preview_added_removed() + 4,5delete + call append(3, ['A','B','C','D']) + 4 + GitGutterPreviewHunk + wincmd P + + " -d + " -e + " +A + " +B + " +C + " +D + + " remove D and d so they do not get staged + 6delete + 1delete + + GitGutterStageHunk + write + + let expected = [ + \ 'line=3 id=3004 name=GitGutterLineRemoved priority=10', + \ 'line=7 id=3003 name=GitGutterLineAdded priority=10', + \ ] + call assert_equal(expected, s:signs('fixture.txt')) + + let expected = [ + \ 'diff --git a/fixture.txt b/fixture.txt', + \ 'index 9a19589..e63fb0a 100644', + \ '--- a/fixture.txt', + \ '+++ b/fixture.txt', + \ '@@ -4 +3,0 @@ c', + \ '-d', + \ '@@ -7,0 +7 @@ C', + \ '+D', + \ ] + call assert_equal(expected, s:git_diff()) + + let expected = [ + \ 'diff --git a/fixture.txt b/fixture.txt', + \ 'index f5c6aff..9a19589 100644', + \ '--- a/fixture.txt', + \ '+++ b/fixture.txt', + \ '@@ -5 +5,3 @@ d', + \ '-e', + \ '+A', + \ '+B', + \ '+C', + \ ] + call assert_equal(expected, s:git_diff_staged()) +endfunction + + function Test_hunk_undo() let _shell = &shell set shell=foo