Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions doc/inline-diff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ COMMANDS *inline-diff-commands*
Toggle inline diff for the current buffer. Without `ref`, disables if
currently enabled. With `ref`, always enables (or switches ref).

:InlineDiffNext *:InlineDiffNext*
Jump to the next change hunk. Wraps to the first hunk when past the last.

:InlineDiffPrev *:InlineDiffPrev*
Jump to the previous change hunk. Wraps to the last hunk when before the
first.

==============================================================================
LUA API *inline-diff-api*

Expand All @@ -78,12 +85,24 @@ require("inline-diff").toggle([bufnr], [ref]) *inline-diff.toggle()*
Toggle inline diff. Without `ref`, disables if currently enabled.
With `ref`, always enables (or switches ref).

require("inline-diff").next_hunk([bufnr]) *inline-diff.next_hunk()*
Jump the cursor to the next change hunk (wrapping to the first). Returns
the target 1-based line, or `nil` if there are no hunks.

require("inline-diff").prev_hunk([bufnr]) *inline-diff.prev_hunk()*
Jump the cursor to the previous change hunk (wrapping to the last).
Returns the target 1-based line, or `nil` if there are no hunks.

Example keymaps: >lua

vim.keymap.set("n", "<leader>gd", "<cmd>InlineDiff<cr>",
{ desc = "Toggle inline diff" })
vim.keymap.set("n", "<leader>gD", "<cmd>InlineDiff HEAD~1<cr>",
{ desc = "Diff against HEAD~1" })
vim.keymap.set("n", "]c", "<cmd>InlineDiffNext<cr>",
{ desc = "Next change" })
vim.keymap.set("n", "[c", "<cmd>InlineDiffPrev<cr>",
{ desc = "Prev change" })
<

==============================================================================
Expand Down
44 changes: 44 additions & 0 deletions lua/inline-diff/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -301,4 +301,48 @@ function M.toggle(bufnr, ref)
end
end

-- Jump to the next (step > 0) or previous (step < 0) change hunk, wrapping
-- around at either end. Returns the target 1-based line, or nil when there
-- are no hunks (or the buffer is not enabled).
function M._goto_hunk(bufnr, step)
local s = state._bufs[bufnr]
local hunks = s and s.prev_hunks
if not s or not s.enabled or not hunks or #hunks == 0 then
return nil
end
local row = vim.api.nvim_win_get_cursor(0)[1]
local target
if step > 0 then
for _, h in ipairs(hunks) do
local t = math.max(h.new_start, 1)
if t > row then
target = t
break
end
end
target = target or math.max(hunks[1].new_start, 1)
else
for i = #hunks, 1, -1 do
local t = math.max(hunks[i].new_start, 1)
if t < row then
target = t
break
end
end
target = target or math.max(hunks[#hunks].new_start, 1)
end
vim.api.nvim_win_set_cursor(0, { target, 0 })
return target
end

function M.next_hunk(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
return M._goto_hunk(bufnr, 1)
end

function M.prev_hunk(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
return M._goto_hunk(bufnr, -1)
end

return M
8 changes: 8 additions & 0 deletions plugin/inline-diff.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ vim.api.nvim_create_user_command("InlineDiff", function(args)
local ref = args.args ~= "" and args.args or nil
require("inline-diff").toggle(nil, ref)
end, { nargs = "?" })

vim.api.nvim_create_user_command("InlineDiffNext", function()
require("inline-diff").next_hunk()
end, { desc = "Jump to next inline-diff change" })

vim.api.nvim_create_user_command("InlineDiffPrev", function()
require("inline-diff").prev_hunk()
end, { desc = "Jump to previous inline-diff change" })
61 changes: 61 additions & 0 deletions tests/init_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,67 @@ describe("enable / disable / toggle", function()
end)
end)

describe("_goto_hunk", function()
local bufnr

local function set_hunks(...)
local s = state._bufs[bufnr]
s.enabled = true
s.prev_hunks = { ... }
end

before_each(function()
bufnr = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { "l1", "l2", "l3", "l4", "l5" })
vim.api.nvim_set_current_buf(bufnr)
M.enable(bufnr)
set_hunks()
end)

after_each(function()
pcall(M.disable, bufnr)
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
state._bufs[bufnr] = nil
end)

it("returns nil when there are no hunks", function()
assert.is_nil(M.next_hunk(bufnr))
assert.is_nil(M.prev_hunk(bufnr))
end)

it("moves to the next hunk after the cursor", function()
set_hunks(make_hunk(1, { "a" }, 1, { "x" }), make_hunk(3, { "c" }, 3, { "z" }))
vim.api.nvim_win_set_cursor(0, { 2, 0 })
assert.are.equal(3, M.next_hunk(bufnr))
assert.are.equal(3, vim.api.nvim_win_get_cursor(0)[1])
end)

it("moves to the previous hunk before the cursor", function()
set_hunks(make_hunk(1, { "a" }, 1, { "x" }), make_hunk(3, { "c" }, 3, { "z" }))
vim.api.nvim_win_set_cursor(0, { 4, 0 })
assert.are.equal(3, M.prev_hunk(bufnr))
assert.are.equal(3, vim.api.nvim_win_get_cursor(0)[1])
end)

it("wraps from the last hunk to the first on next", function()
set_hunks(make_hunk(1, { "a" }, 1, { "x" }), make_hunk(3, { "c" }, 3, { "z" }))
vim.api.nvim_win_set_cursor(0, { 5, 0 })
assert.are.equal(1, M.next_hunk(bufnr))
end)

it("wraps from the first hunk to the last on prev", function()
set_hunks(make_hunk(1, { "a" }, 1, { "x" }), make_hunk(3, { "c" }, 3, { "z" }))
vim.api.nvim_win_set_cursor(0, { 1, 0 })
assert.are.equal(3, M.prev_hunk(bufnr))
end)

it("clamps a deletion at line 0 to line 1", function()
set_hunks(make_hunk(1, { "a" }, 0, {}))
vim.api.nvim_win_set_cursor(0, { 2, 0 })
assert.are.equal(1, M.next_hunk(bufnr))
end)
end)

describe("_hunks_equal", function()
it("returns true for both nil", function()
assert.is_true(M._hunks_equal(nil, nil))
Expand Down