diff options
Diffstat (limited to 'ftplugin/diff_navigator.lua')
-rw-r--r-- | ftplugin/diff_navigator.lua | 307 |
1 files changed, 307 insertions, 0 deletions
diff --git a/ftplugin/diff_navigator.lua b/ftplugin/diff_navigator.lua new file mode 100644 index 0000000..f2b5e0d --- /dev/null +++ b/ftplugin/diff_navigator.lua @@ -0,0 +1,307 @@ +-- ============================================================================ +-- 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 +-- ============================================================================ + +-- 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 + +-- from http://lua-users.org/wiki/SplitJoin +local split = function(self, sep) + local sep, fields = sep or ":", {} + local pattern = string.format("([^%s]+)", sep) + self:gsub(pattern, function(c) fields[#fields+1] = c end) + return fields +end + +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(vim.fn.getline(lineno), lineno) +end + +-- 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 countLines(start, endline) + local context_lines = 0 + local old_lines = 0 + local new_lines = 0 + + for line in pairs(vim.fn.getline(start, endline)) do + local first_char = vim.fn.strpart(line, 0, 1) + if first_char == ' ' then + local context_lines = context_lines + 1 + elseif first_char == '-' then + local old_lines = old_lines + 1 + elseif first_char == '+' then + local new_lines = new_lines + 1 + end + end + + return {context_lines + old_lines, context_lines + new_lines} +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 = 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<Esc>\"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 = 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(vim.fn.getline("."), "+" .. old_line .. ",", + "+" .. new_line .. ",", "")) + vim.cmd('normal ^d$"xp') + + -- check the next hunk + 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 < next_hunk_line) then + local next_hunk_line = start_of_next_file_line + local 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', '<Leader>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', '<leader>s', DiffSplitHunk) +-- vim.keymap.set('n', '<leader>s', DiffDeleteHunk) |