-- ============================================================================ -- 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 -- ============================================================================ -- FIXME https://www.reddit.com/r/neovim/comments/unwvkw/github_hkuptyrunesnvim_lua_test_framework_for/ -- FIXME https://github.com/hkupty/runes.nvim -- 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 -- end -- local b:did_ftplugin = 1 -- Load this plugin only once if vim.g.loaded_diff_navigator ~= nil then return end vim.g.loaded_diff_navigator = 1 -- Utility functions local function executable(cmd) -- calling /bin/sh -c even for users with default shells -- which are not POSIX compatible (squinting at fish) return (os.execute("/bin/sh -c 'command -v " .. cmd .. " >/dev/null'") == 0) end local function checkFilterDiff() if not executable('filterdiff') then vim.notify('You need to install filterdiff first (part of patchutils)', vim.log.levels.WARN) return false end return true end local hnk = require('hunk') -- 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. -- vim.api.nvim_eval('"README.md" =~ glob2regpat("/home/nvim/*.md")') ~= 0 -- is equivalent of -- "README.md" =~ glob2regpat("/home/nvim/*.md") -- Get hunk header from the hunk surrounding cursor function getCurrentHunkHeader() local lineno = 0 if (vim.api.nvim_eval(getline(".") .. ' =~ "^+++ \\|^--- "') ~= 0) then lineno = vim.fn.search('^@@[ +-\\,\\d]*@@.*$', 'ncW') else lineno = vim.fn.search('^@@[ +-\\,\\d]*@@.*$', 'bncW') end return hnk.Header.parse(lineno) end -- ------------------------------------------------------------- -- Annotate each hunk with it's number and name of the changed file function DiffAnnotate() if checkFilterDiff() then local cursorpos = vim.cmd.winsaveview() vim.cmd('normal %!filterdiff --annotate') vim.cmd.winrestview(cursorpos) end end -- Print annotation of current hunk function DiffShowHunk() -- if the current line begins with '+++' or '---', then it makes -- sense to search forwards local hunk_header = getCurrentHunkHeader() print(hunk_header.remainderLine) end -- Skip to next hunk function DiffNextHunk() vim.fn.search('^@@[ +-\\,\\d]*@@', 'sW') end -- Skip to previous hunk function DiffPrevHunk() vim.fn.search('^@@[ +-\\,\\d]*@@', 'bsW') end -- Skip to next changed file function DiffNextFile() vim.fn.search('^--- ', 'sW') end -- Skip to previous changed file function DiffPrevFile() vim.fn.search('^--- ', 'bsW') end function DiffSplitHunk() local old_cur_header = getCurrentHunkHeader() local cur_line_no = vim.fn.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 local diff_lines = hnk.countLines(old_cur_header['line'] + 1, cur_line_no - 1) local diff_old = diff_lines[1] local diff_new = diff_lines[2] -- 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 @@ local new_start_del_start = old_cur_header['oldFirst'] local new_start_del_len = diff_old local new_start_add_start = old_cur_header['newFirst'] local new_start_add_len = diff_new vim.fn.setreg('x', hnk.createHunkHeader(new_start_del_start, new_start_del_len, new_start_add_start, new_start_add_len, old_cur_header['remainderLine'])) local window_state = vim.cmd.winsaveview() -- write the new original header line vim.fn.setpos(".", {0, old_cur_header['line'], 1, 0}) vim.cmd('normal ^d$"xp') vim.cmd.winrestview(window_state) -- IN THE NEW HUNK HEADER -- new lengths = original len - new len -- new starts = original start + (difference) local new_pos_del_start = old_cur_header['oldFirst'] + diff_old local new_pos_del_len = old_cur_header['oldCount'] - diff_old local new_pos_add_start = old_cur_header['newFirst'] + diff_new local new_pos_add_len = old_cur_header['newCount'] - diff_new vim.fn.setreg('x', hnk.createHunkHeader(new_pos_del_start, new_pos_del_len, new_pos_add_start, new_pos_add_len, "")) vim.cmd('normal! O\"xP') end -- Delete the hunk cursor is in. function DiffDeleteHunk() local last_hunk_in_file = 0 local start_hunk_line = vim.fn.search('^@@[ +-\\,\\d]*@@.*$', 'bncW') -- we are before the first hunk ... return if start_hunk_line == 0 then return end -- end of the hunk is start of next hunk or next file, whichever -- comes first local next_hunk_line = vim.fn.search('^@@[ +-\\,\\d]*@@.*$', 'nW') local start_of_next_file_line = vim.fn.search('^--- .*$', 'ncW') if start_of_next_file_line > 0 and start_of_next_file_line < next_hunk_line then local next_hunk_line = start_of_next_file_line local last_hunk_in_file = 1 end -- 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 then vim.cmd("normal " .. start_hunk_line .. ",$" .. "d") -- we are in the middle of the file ... it's a bit more complicated else local count_lines = hnk.countLines(start_hunk_line + 1, next_hunk_line - 1) local added_lines = count_lines[2] - count_lines[1] vim.fn.setpos(".", {0, next_hunk_line, 1, 0}) vim.cmd("normal " .. start_hunk_line .. "," .. (next_hunk_line - 1) .. "d") -- record the line number of the new hunk header after delete local new_header_line = vim.fn.line(".") -- recalculate current hunk header while (not last_hunk_in_file) do local cur_header = hnk.parse(vim.fn.line(".")) local old_line = cur_header['newFirst'] local new_line = old_line - added_lines vim.fn.setreg('x', vim.fn.substitute(hnk.getline("."), "+" .. old_line .. ",", "+" .. new_line .. ",", "")) vim.cmd('normal ^d$"xp') -- check the next hunk next_hunk_line = vim.fn.search('\\_^@@[ +-\\,\\d]*@@\\_.*\\_$', 'nW') start_of_next_file_line = vim.fn.search('^--- .*$', 'ncW') if (start_of_next_file_line < next_hunk_line) then next_hunk_line = start_of_next_file_line last_hunk_in_file = 1 end vim.fn.setpos(".", {0, next_hunk_line, 1, 0}) end -- jump to the line after the deleted hunk vim.fn.setpos(".", {0, new_header_line, 1, 0}) end end -- -- Define new commands vim.cmd('command DiffAnnotate DiffAnnotate()') vim.cmd('command DiffShowHunk DiffShowHunk()') vim.cmd('command DiffNextHunk DiffNextHunk()') vim.cmd('command DiffPrevHunk DiffPrevHunk()') vim.cmd('command DiffNextFile DiffNextFile()') vim.cmd('command DiffPrevFile DiffPrevFile()') vim.cmd('command DiffSplitHunk DiffSplitHunk()') vim.cmd('command DiffDeleteHunk DiffDeleteHunk()') -- -- Default },{,(,) do not make much sense in diffs, so remap them to -- make something useful vim.keymap.set('n', 'ex3', vim.treesitter.start) vim.keymap.set('n', '}', DiffNextFile) vim.keymap.set('n', '{', DiffPrevFile) vim.keymap.set('n', ')', DiffNextHunk) vim.keymap.set('n', '(', DiffPrevHunk) vim.keymap.set('n', '!', DiffShowHunk) vim.keymap.set('n', 's', DiffSplitHunk) vim.keymap.set('n', 's', DiffDeleteHunk)