feat(fugitive): add section header and untracked file support

Section headers (Staged/Unstaged) now show all diffs in that section,
matching fugitive's behavior. Untracked files show as all-added diffs.
Deleted files show as all-removed diffs.

Also handles edge cases:
- Empty new/old content for deleted/new files
- Section header detection returns is_header flag
This commit is contained in:
Barrett Ruth 2026-02-04 22:39:07 -05:00
parent ce8fe3b89b
commit 6072dd0156
3 changed files with 150 additions and 23 deletions

View file

@ -91,6 +91,7 @@ end
---@class diffs.GdiffFileOpts
---@field vertical? boolean
---@field staged? boolean
---@field untracked? boolean
---@param filepath string
---@param opts? diffs.GdiffFileOpts
@ -106,16 +107,22 @@ function M.gdiff_file(filepath, opts)
local old_lines, new_lines, err
local diff_label
if opts.staged then
if opts.untracked then
old_lines = {}
new_lines, err = git.get_working_content(filepath)
if not new_lines then
vim.notify('[diffs.nvim]: ' .. (err or 'cannot read file'), vim.log.levels.ERROR)
return
end
diff_label = 'untracked'
elseif opts.staged then
old_lines, err = git.get_file_content('HEAD', filepath)
if not old_lines then
vim.notify('[diffs.nvim]: ' .. (err or 'file not in HEAD'), vim.log.levels.ERROR)
return
old_lines = {}
end
new_lines, err = git.get_index_content(filepath)
if not new_lines then
vim.notify('[diffs.nvim]: ' .. (err or 'file not in index'), vim.log.levels.ERROR)
return
new_lines = {}
end
diff_label = 'staged'
else
@ -133,8 +140,7 @@ function M.gdiff_file(filepath, opts)
end
new_lines, err = git.get_working_content(filepath)
if not new_lines then
vim.notify('[diffs.nvim]: ' .. (err or 'cannot read file'), vim.log.levels.ERROR)
return
new_lines = {}
end
end
@ -163,6 +169,50 @@ function M.gdiff_file(filepath, opts)
end)
end
---@class diffs.GdiffSectionOpts
---@field vertical? boolean
---@field staged? boolean
---@param repo_root string
---@param opts? diffs.GdiffSectionOpts
function M.gdiff_section(repo_root, opts)
opts = opts or {}
local cmd = { 'git', '-C', repo_root, 'diff', '--no-ext-diff', '--no-color' }
if opts.staged then
table.insert(cmd, '--cached')
end
local result = vim.fn.systemlist(cmd)
if vim.v.shell_error ~= 0 then
vim.notify('[diffs.nvim]: git diff failed', vim.log.levels.ERROR)
return
end
if #result == 0 then
vim.notify('[diffs.nvim]: no changes in section', vim.log.levels.INFO)
return
end
local diff_label = opts.staged and 'staged' or 'unstaged'
local diff_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(diff_buf, 0, -1, false, result)
vim.api.nvim_set_option_value('buftype', 'nofile', { buf = diff_buf })
vim.api.nvim_set_option_value('bufhidden', 'wipe', { buf = diff_buf })
vim.api.nvim_set_option_value('modifiable', false, { buf = diff_buf })
vim.api.nvim_set_option_value('filetype', 'diff', { buf = diff_buf })
vim.api.nvim_buf_set_name(diff_buf, 'diffs://' .. diff_label .. ':all')
vim.cmd(opts.vertical and 'vsplit' or 'split')
vim.api.nvim_win_set_buf(0, diff_buf)
dbg('opened section diff buffer %d (%s)', diff_buf, diff_label)
vim.schedule(function()
require('diffs').attach(diff_buf)
end)
end
function M.setup()
vim.api.nvim_create_user_command('Gdiff', function(opts)
M.gdiff(opts.args ~= '' and opts.args or nil, false)