repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
neovim/lua/bugabinga/pivi/diff.lua
Rawlocal entry = require 'bugabinga.pivi.entry'
local navigate = require 'bugabinga.pivi.navigate'
local tool = require 'bugabinga.pivi.tool'
local state = { window = nil, buffer = nil, }
--- Reads a reported change back into the lines it added and removed.
--- The tool numbers every changed line, so the change can be undone on the
--- content it produced.
local parse = function( change )
local removed, added = {}, {}
for _, line in ipairs( vim.split( change or '', '\n', { plain = true, } ) ) do
local mark = line:sub( 1, 1 )
local number, text = line:sub( 2 ):match '^%s*(%d+) (.*)$'
if number then
local record = { line = tonumber( number ), text = text, }
if mark == '+' then
table.insert( added, record )
elseif mark == '-' then
table.insert( removed, record )
end
end
end
return removed, added
end
--- The content that existed before a change, rebuilt from the change itself.
--- @return string[] lines
--- @return boolean faithful whether the content still matches what was changed
local before = function( current, change )
local removed, added = parse( change )
local lines = vim.deepcopy( current )
local faithful = true
for _, record in ipairs( added ) do
if lines[record.line] ~= record.text then faithful = false end
end
local descending = vim.deepcopy( added )
table.sort( descending, function( left, right ) return left.line > right.line end )
for _, record in ipairs( descending ) do
if lines[record.line] ~= nil then table.remove( lines, record.line ) end
end
local ascending = vim.deepcopy( removed )
table.sort( ascending, function( left, right ) return left.line < right.line end )
for _, record in ipairs( ascending ) do
table.insert( lines, math.min( record.line, #lines + 1 ), record.text )
end
return lines, faithful
end
local close = function()
if state.window and vim.api.nvim_win_is_valid( state.window ) then
pcall( vim.api.nvim_win_call, state.window, function() vim.cmd 'diffoff' end )
pcall( vim.api.nvim_win_close, state.window, true )
end
if state.buffer and vim.api.nvim_buf_is_valid( state.buffer ) then
pcall( vim.api.nvim_buf_delete, state.buffer, { force = true, } )
end
state.window, state.buffer = nil, nil
end
--- Opens a completed change as an editor comparison.
--- The change is compared against the content the tool produced, so ordinary
--- diff motions and diff folds apply.
--- @return number? changed the window showing the file
--- @return number? original the window showing the content before the change
--- @return boolean? faithful whether the file still matches what was changed
local open = function( bufnr, line_number )
local item = entry.at( bufnr, line_number or vim.api.nvim_win_get_cursor( 0 )[1] )
if not item or item.kind ~= 'tool' then return nil end
local change = tool.diff( item.meta )
local location = navigate.location_of( item )
if not change or not location then return nil end
local working = vim.api.nvim_get_current_win()
local changed = navigate.locate( bufnr, item.first )
if not changed then return nil end
close()
local target = vim.api.nvim_win_get_buf( changed )
local current = vim.api.nvim_buf_get_lines( target, 0, -1, false )
local original, faithful = before( current, change )
state.buffer = vim.api.nvim_create_buf( false, true )
vim.bo[state.buffer].buftype = 'nofile'
vim.bo[state.buffer].bufhidden = 'wipe'
vim.bo[state.buffer].swapfile = false
vim.api.nvim_buf_set_lines( state.buffer, 0, -1, false, original )
vim.bo[state.buffer].modifiable = false
vim.bo[state.buffer].filetype = vim.bo[target].filetype
local label = 'pivi://' .. vim.fs.basename( location.path ) .. '/before'
if not faithful then label = label .. ' (the file has moved on)' end
pcall( vim.api.nvim_buf_set_name, state.buffer, label )
local opened = pcall( vim.api.nvim_win_call, changed, function()
vim.cmd 'diffthis'
vim.cmd 'leftabove vsplit'
vim.api.nvim_win_set_buf( 0, state.buffer )
vim.cmd 'diffthis'
state.window = vim.api.nvim_get_current_win()
end )
if not opened then
close()
return nil
end
if vim.api.nvim_win_is_valid( working ) then
vim.api.nvim_set_current_win( working )
end
return changed, state.window, faithful
end
local bind = function( bufnr )
vim.keymap.set( 'n', '<Plug>(pivi-compare-change)', function() open( bufnr ) end,
{ buffer = bufnr, desc = 'pivi compare the change of the entry', } )
end
require( 'bugabinga.pivi.transcript' ).on_buffer( bind )
return {
parse = parse,
before = before,
open = open,
close = close,
bind = bind,
}