" ============================================================================ " File: diff_navigator.vim " Description: Filetype plugin to ease navigation in (unified) diffs " Maintainer: Petr Uzel , " Matěj Cepl " 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\\"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