aboutsummaryrefslogblamecommitdiffstats
path: root/ftplugin/diff_navigator.vim
blob: 7e28b36a4a3d8a3d8aef5562171b20a7d8c39374 (plain) (tree)
1
2
3
4
5
6
7


                                                                              



                                                      










                                                                               

                                                                         
                                                         

                                                                         




                                                                              
                                                      



                             
 


                                    
     
















                                                                        
                                                             




                                                                      
                                                    



                                                                             
                                                                   










                                                                                 
                                                






























                                                                        
                                                 
                                
                                         
                                
                                         
















                                                                  

                                  







                                                                    

                   


                                        

                       


                                         

                           


                              
 











                                                           
     











































                                                                       
                                                       
                 
                                  
 









                                                                   




















                                                                   
 




































                                                                         


                     







                                               



                                                                   




                                                          

                                                                    
" ============================================================================
" File:         diff_navigator.vim
" Description:  Filetype plugin to ease navigation in (unified) diffs
" Maintainer:   Petr Uzel <petr.uzel -at- centrum.cz>,
"               Matěj Cepl <mcepl -at- cepl dot eu>
" Version:      0.2
" Last Change:  10 Sep, 2013
" License:      This program is free software. It comes without any warranty,
"               to the extent permitted by applicable law. You can redistribute
"               it and/or modify it under the terms of the Do What The Fuck You
"               Want To Public License, Version 2, as published by Sam Hocevar.
"               See http://sam.zoy.org/wtfpl/COPYING for more details.
"
" Bugs:         Send bugreports/patches directly to me via mail
" Dependencies: filterdiff (part of patchutils project)
"
"
" TODO:         show current hunk in status line
" TODO:         delete whole file diff -
"               like http://www.vim.org/scripts/script.php?script_id=444)
" TODO:         incorporate more patchutils functionality
" TODO:         something like taglist for diff (shows all files/hunks in
"               the diff)
" TODO:         option for *Next|Prev* funtions to wrap around end of file
" ============================================================================


" Only do this when not done yet for this buffer
" Usually, not needed, just for the keeping normal API
" if exists("b:did_ftplugin")
"     finish
" endif
" let b:did_ftplugin = 1

" Load this plugin only once
if exists("g:loaded_diff_navigator")
    finish
endif
let g:loaded_diff_navigator = 1

function s:checkFilterDiff()
    if !executable('filterdiff')
        echohl WarningMsg
        echo "You need to install filterdiff first (part of patchutils)"
        echohl None
        return 0
    endif
    return 1
endfunction

" Given the linenumber of the hunk returned its parsed content
"
" From http://www.clearchain.com/blog/posts/splitting-a-patch
" -----------------------------------------------------------
"
" here's at least some info about the format of a patch file.
"
" @@ -143,6 +143,13 @@
"
"     the first number is the starting line for this hunk in oldfile
"     the second number is the number of original source lines in this
"         hunk (this includes lines marked with "-")
"     the third number is the starting line for this hunk in newfile
"     the last number is the number of lines after the hunk has been applied.
function s:parseHunkHeader(lineno)
    let inline = getline(a:lineno)
    " Thanks to somian from #vim IRC channel for this incredible RE
    let hunk_nos = substitute(inline,
         \ '\_^\S\S\s\+-\(\d\+\),\(\d\+\)\s\++\(\d\+\),\(\d\+\)\s\+\S\S\(\_.*\)',
         \ "\\1,\\2,\\3,\\4,\\5","")
    let result = split(hunk_nos, ",")

    return {
        \ 'line': a:lineno,
        \ 'oldFirst': result[0],
        \ 'oldCount': result[1],
        \ 'newFirst': result[2],
        \ 'newCount': result[3],
        \ 'remainderLine': join(result[4:], ",")
        \ }
endfunction

" Get hunk header from the hunk surrounding cursor
function s:getCurrentHunkHeader()
    if getline(".") =~ '^+++ \|^--- '
        let lineno = search('^@@[ +-\,\d]*@@.*$', 'ncW')
    else
        let lineno = search('^@@[ +-\,\d]*@@.*$', 'bncW')
    endif

    return s:parseHunkHeader(lineno)
endfunction

" Generate hunk header line
function s:createHunkHeader(oldStart, oldLen, newStart, newLen, remaind)
    return "@@ -" . a:oldStart . "," . a:oldLen .
        \ " +" . a:newStart . "," . a:newLen . " @@" . a:remaind
endfunction

" Return number of lines in the range between start and end line (both
" inclusive) which are from the state before patch and the one after
" patch is applied.
function s:countLines(start, end)
    let context_lines = 0
    let old_lines = 0
    let new_lines = 0

    for line in getline(a:start, a:end)
        let first_char = strpart(line, 0, 1)
        if first_char == ' '
            let context_lines = context_lines + 1
        elseif first_char == '-'
            let old_lines = old_lines + 1
        elseif first_char == '+'
            let new_lines = new_lines + 1
        else
        endif
    endfor

    return [context_lines + old_lines, context_lines + new_lines]
endfunction

" -------------------------------------------------------------

" Annotate each hunk with it's number and name of the changed file
function s:DiffAnnotate()
    if s:checkFilterDiff()
        let l:cursorpos = winsaveview()
        %!filterdiff --annotate
        call winrestview(cursorpos)
    endif
endfunction

" Print annotation of current hunk
function s:DiffShowHunk()
    " if the current line begins with '+++' or '---', then it makes
    " sense to search forwards
    let hunk_header = s:getCurrentHunkHeader()
    let l:hunk_annotation = substitute(getline(hunk_header['line']),
        \ '^@@[ +-\,\d]*@@\s*\(.*\)$', '\1', '')
    echo l:hunk_annotation
endfunction

" Skip to next hunk
function s:DiffNextHunk()
    call search('^@@[ +-\,\d]*@@', 'sW')
endfunction

" Skip to previous hunk
function s:DiffPrevHunk()
    call search('^@@[ +-\,\d]*@@', 'bsW')
endfunction

" Skip to next changed file
function s:DiffNextFile()
    call search('^--- ', 'sW')
endfunction

" Skip to previous changed file
function s:DiffPrevFile()
    call search('^--- ', 'bsW')
endfunction

function s:DiffSplitHunk()
    let old_cur_header = s:getCurrentHunkHeader()
    let cur_line_no = line(".")

    " With this hunk:
    "
    " @@ -20,8 +20,17 @@ Hunk #1, a/tests/test_ec_curves.py
    "
    "  import unittest
    "  #import sha
    " -from M2Crypto import EC, Rand
    " -from test_ecdsa import ECDSATestCase as ECDSATest
    " +try:
    " +    from M2Crypto import EC, Rand
"-- " +    from test_ecdsa import ECDSATestCase as ECDSATest
    " +# AttributeError: 'module' object has no attribute 'ec_init'
    " +#except AttributeError:
    " +except:
    " +    EC_Module_Available = False
    " +    print("No EC modules available")
    " +else:
    " +    EC_Module_Available = True
    " +    print("EC modules are available")
    "
    " creates above the line
    " @@ -25,3 +25,12 @@
    "
    " and the original hunk line is now
    " @@ -20,5 +20,5 @@ Hunk #1, a/tests/test_ec_curves.py
    "
    "
    " Start line below header and stop one line above the current line
    let diff_lines = s:countLines(old_cur_header['line'] + 1,
        \ cur_line_no - 1)
    let diff_old = diff_lines[0]
    let diff_new = diff_lines[1]

    " IN THE NEW START HUNK HEADER
    " 1. length is number of lines above the current position which
    " are either context or deleted lines (-)
    " 2. length is number of lines above the current position which
    " are either context or added lines (+)
    " Start positions are same as well the stuff after the second @@
    let new_start_del_start = old_cur_header['oldFirst']
    let new_start_del_len = diff_old
    let new_start_add_start = old_cur_header['newFirst']
    let new_start_add_len = diff_new
    let @x = s:createHunkHeader(new_start_del_start, new_start_del_len,
        \ new_start_add_start, new_start_add_len,
        \ old_cur_header['remainderLine'])
    let window_state = winsaveview()
    " write the new original header line
    call setpos(".", [0, old_cur_header['line'], 1, 0])
    normal ^d$"xp
    call winrestview(window_state)

    " IN THE NEW HUNK HEADER
    " new lengths = original len - new len
    " new starts = original start + (difference)
    let new_pos_del_start = old_cur_header['oldFirst'] + diff_old
    let new_pos_del_len = old_cur_header['oldCount'] - diff_old
    let new_pos_add_start = old_cur_header['newFirst'] + diff_new
    let new_pos_add_len = old_cur_header['newCount'] - diff_new
    let @x = s:createHunkHeader(new_pos_del_start, new_pos_del_len,
        \ new_pos_add_start, new_pos_add_len, "")
    execute "normal! O\<Esc>\"xP"
endfunction

" Delete the hunk cursor is in.
function s:DiffDeleteHunk()
    let last_hunk_in_file = 0

    let start_hunk_line = search('^@@[ +-\,\d]*@@.*$', 'bncW')
    " we are before the first hunk ... return
    if start_hunk_line == 0
        return
    endif

    " end of the hunk is start of next hunk or next file, whichever
    " comes first
    let next_hunk_line = search('^@@[ +-\,\d]*@@.*$', 'nW')
    let start_of_next_file_line = search('^--- .*$', 'ncW')
    if start_of_next_file_line > 0 &&
            \ start_of_next_file_line < next_hunk_line
        let next_hunk_line = start_of_next_file_line
        let last_hunk_in_file = 1
    endif

    " if this is the last hunk in the file ... just erase everything
    " from the start of the hunk (inclusive) to the end
    if next_hunk_line == 0
        execute start_hunk_line . ",$" . "d"
    " we are in the middle of the file ... it's a bit more complicated
    else
        let count_lines = s:countLines(start_hunk_line + 1,
            \ next_hunk_line - 1)
        let added_lines = count_lines[1] - count_lines[0]
        call setpos(".", [0, next_hunk_line, 1, 0])
        execute start_hunk_line . "," . (next_hunk_line - 1) . "d"

        " record the line number of the new hunk header after delete
        let new_header_line = line(".")

        " recalculate current hunk header
        while ! last_hunk_in_file
            let cur_header = s:parseHunkHeader(line("."))
            let old_line = cur_header['newFirst']
            let new_line = old_line - added_lines
            let @x = substitute(getline("."), "+" . old_line . ",",
                \ "+" . new_line . ",", "")
            normal ^d$"xp

            " check the next hunk
            let next_hunk_line = search('\_^@@[ +-\,\d]*@@\_.*\_$', 'nW')
            let start_of_next_file_line = search('^--- .*$', 'ncW')
            if start_of_next_file_line < next_hunk_line
                let next_hunk_line = start_of_next_file_line
                let last_hunk_in_file = 1
            endif
            call setpos(".", [0, next_hunk_line, 1, 0])
        endwhile

        " jump to the line after the deleted hunk
        call setpos(".", [0, new_header_line, 1, 0])
    endif
endfunction

" Define new commands
command DiffAnnotate    call s:DiffAnnotate()
command DiffShowHunk    call s:DiffShowHunk()
command DiffNextHunk    call s:DiffNextHunk()
command DiffPrevHunk    call s:DiffPrevHunk()
command DiffNextFile    call s:DiffNextFile()
command DiffPrevFile    call s:DiffPrevFile()
command DiffSplitHunk   call s:DiffSplitHunk()
command DiffDeleteHunk  call s:DiffDeleteHunk()


" Default },{,(,) do not make much sense in diffs, so remap them to
" make something useful
nnoremap <silent> <script> } :call <SID>DiffNextFile()<CR>
nnoremap <silent> <script> { :call <SID>DiffPrevFile()<CR>
nnoremap <silent> <script> ) :call <SID>DiffNextHunk()<CR>
nnoremap <silent> <script> ( :call <SID>DiffPrevHunk()<CR>
nnoremap <silent> <script> ! :call <SID>DiffShowHunk()<CR>
nnoremap <silent> <script> <leader>s :call <SID>DiffSplitHunk()<CR>
nnoremap <silent> <script> <leader>d :call <SID>DiffDeleteHunk()<CR>