aboutsummaryrefslogblamecommitdiffstats
path: root/ftplugin/diff_navigator.lua
blob: 543bed3305188379475e70bf37290bed396c8966 (plain) (tree)

























                                                                                

                                                                                                       















                                                       













                                                                                                    

                           













                                                                              






                                                                           
                                                                           




                                                              
                                   

   
















                                                                    
                                    





















































                                                                       
                                                                 
                        

                                  










                                                                     
                                                                                   














                                                                   
                                                                               





























                                                                         
                                                               
                               
                                                           







                                                                                   
                                                          

                                                   
                                                                                          



                                            

                                                                                 
                                                              

                                                        







                                                         









                                                   
   










                                                                    
-- ============================================================================
-- 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

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<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 = 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', '<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)