Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ https://github.com/user-attachments/assets/64c41f01-dffe-4318-bce4-16eec8de356e
toggle_explorer = "<leader>b", -- Toggle explorer visibility (explorer mode only)
next_hunk = "]c", -- Jump to next change
prev_hunk = "[c", -- Jump to previous change
next_file = "]f", -- Next file in explorer mode
prev_file = "[f", -- Previous file in explorer mode
next_file = "]f", -- Next file in explorer/history mode
prev_file = "[f", -- Previous file in explorer/history mode
diff_get = "do", -- Get change from other buffer (like vimdiff)
diff_put = "dp", -- Put change to other buffer (like vimdiff)
open_in_prev_tab = "gf", -- Open current buffer in previous tab (or create one before)
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.15.0
2.16.0
4 changes: 4 additions & 0 deletions doc/codediff.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ Setup entry point:
unstage_all = "U",
restore = "X",
},
history = {
select = "<CR>",
toggle_view_mode = "i",
},
conflict = {
accept_incoming = "<leader>ct",
accept_current = "<leader>co",
Expand Down
6 changes: 5 additions & 1 deletion lua/codediff/ui/history/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,14 @@ local render = require("codediff.ui.history.render")
-- opts: { range, path, ... } original options
M.create = render.create

-- Navigation
-- Navigation (files within expanded commits)
M.navigate_next = render.navigate_next
M.navigate_prev = render.navigate_prev

-- Navigation (commits in single-file mode)
M.navigate_next_commit = render.navigate_next_commit
M.navigate_prev_commit = render.navigate_prev_commit

-- Toggle visibility
M.toggle_visibility = render.toggle_visibility

Expand Down
125 changes: 125 additions & 0 deletions lua/codediff/ui/history/render.lua
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ function M.create(commits, git_root, tabpage, width, opts)
current_commit = nil,
current_file = nil,
is_hidden = false,
is_single_file_mode = is_single_file_mode,
}

-- Load files for a commit and update its children
Expand Down Expand Up @@ -484,6 +485,130 @@ function M.navigate_prev(history)
history.on_file_select(prev_file.data)
end

-- Get all commit nodes from tree (for navigation in single-file mode)
function M.get_all_commits(tree)
local commits = {}
local nodes = tree:get_nodes()
for _, node in ipairs(nodes) do
if node.data and node.data.type == "commit" then
table.insert(commits, {
node = node,
data = node.data,
})
end
end
return commits
end

-- Navigate to next commit (single-file history mode)
function M.navigate_next_commit(history)
local all_commits = M.get_all_commits(history.tree)
if #all_commits == 0 then
vim.notify("No commits in history", vim.log.levels.WARN)
return
end

local current_commit = history.current_commit

if not current_commit then
-- Select first commit
local first_commit = all_commits[1]
local file_path = first_commit.data.file_path or history.opts.file_path
local file_data = {
path = file_path,
commit_hash = first_commit.data.hash,
git_root = history.git_root,
}
history.on_file_select(file_data)
return
end

-- Find current index
local current_index = 0
for i, commit in ipairs(all_commits) do
if commit.data.hash == current_commit then
current_index = i
break
end
end

local next_index = current_index % #all_commits + 1
local next_commit = all_commits[next_index]

-- Update cursor position in history panel
local current_win = vim.api.nvim_get_current_win()
if vim.api.nvim_win_is_valid(history.winid) then
vim.api.nvim_set_current_win(history.winid)
vim.api.nvim_win_set_cursor(history.winid, { next_commit.node._line or 1, 0 })
vim.api.nvim_set_current_win(current_win)
end

-- Select file at this commit
local file_path = next_commit.data.file_path or history.opts.file_path
local file_data = {
path = file_path,
commit_hash = next_commit.data.hash,
git_root = history.git_root,
}
history.on_file_select(file_data)
end

-- Navigate to previous commit (single-file history mode)
function M.navigate_prev_commit(history)
local all_commits = M.get_all_commits(history.tree)
if #all_commits == 0 then
vim.notify("No commits in history", vim.log.levels.WARN)
return
end

local current_commit = history.current_commit

if not current_commit then
-- Select last commit
local last_commit = all_commits[#all_commits]
local file_path = last_commit.data.file_path or history.opts.file_path
local file_data = {
path = file_path,
commit_hash = last_commit.data.hash,
git_root = history.git_root,
}
history.on_file_select(file_data)
return
end

local current_index = 0
for i, commit in ipairs(all_commits) do
if commit.data.hash == current_commit then
current_index = i
break
end
end

local prev_index = current_index - 2
if prev_index < 0 then
prev_index = #all_commits + prev_index
end
prev_index = prev_index % #all_commits + 1
local prev_commit = all_commits[prev_index]

-- Update cursor position in history panel
local current_win = vim.api.nvim_get_current_win()
if vim.api.nvim_win_is_valid(history.winid) then
vim.api.nvim_set_current_win(history.winid)
vim.api.nvim_win_set_cursor(history.winid, { prev_commit.node._line or 1, 0 })
vim.api.nvim_set_current_win(current_win)
end

-- Select file at this commit
local file_path = prev_commit.data.file_path or history.opts.file_path
local file_data = {
path = file_path,
commit_hash = prev_commit.data.hash,
git_root = history.git_root,
}
history.on_file_select(file_data)
end

-- Toggle visibility
function M.toggle_visibility(history)
if not history or not history.split then
Expand Down
3 changes: 3 additions & 0 deletions lua/codediff/ui/view/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,9 @@ function M.create(session_config, filetype, on_ready)
vim.api.nvim_win_set_width(original_win, diff_width)
vim.api.nvim_win_set_width(modified_win, diff_width)
end

-- Setup keymaps for history mode (needs to be after session is created with mode="history")
setup_all_keymaps(tabpage, original_info.bufnr, modified_info.bufnr, false)
end

return {
Expand Down
66 changes: 44 additions & 22 deletions lua/codediff/ui/view/keymaps.lua
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ local config = require("codediff.config")
function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explorer_mode)
local keymaps = config.options.keymaps.view

-- Check if this is history mode
local session = lifecycle.get_session(tabpage)
local is_history_mode = session and session.mode == "history"

-- Helper: Navigate to next hunk
local function navigate_next_hunk()
local session = lifecycle.get_session(tabpage)
Expand Down Expand Up @@ -113,26 +117,44 @@ function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explore
vim.api.nvim_echo({ { string.format("Hunk %d of %d", #diff_result.changes, #diff_result.changes), "None" } }, false, {})
end

-- Helper: Navigate to next file (explorer mode only)
-- Helper: Navigate to next file (works in both explorer and history mode)
-- In single-file history mode, navigates commits instead
local function navigate_next_file()
local explorer_obj = lifecycle.get_explorer(tabpage)
if not explorer_obj then
vim.notify("No explorer found for this tab", vim.log.levels.WARN)
local panel_obj = lifecycle.get_explorer(tabpage)
if not panel_obj then
return
end
local explorer = require("codediff.ui.explorer")
explorer.navigate_next(explorer_obj)
if is_history_mode then
local history = require("codediff.ui.history")
if panel_obj.is_single_file_mode then
history.navigate_next_commit(panel_obj)
else
history.navigate_next(panel_obj)
end
else
local explorer = require("codediff.ui.explorer")
explorer.navigate_next(panel_obj)
end
end

-- Helper: Navigate to previous file (explorer mode only)
-- Helper: Navigate to previous file (works in both explorer and history mode)
-- In single-file history mode, navigates commits instead
local function navigate_prev_file()
local explorer_obj = lifecycle.get_explorer(tabpage)
if not explorer_obj then
vim.notify("No explorer found for this tab", vim.log.levels.WARN)
local panel_obj = lifecycle.get_explorer(tabpage)
if not panel_obj then
return
end
local explorer = require("codediff.ui.explorer")
explorer.navigate_prev(explorer_obj)
if is_history_mode then
local history = require("codediff.ui.history")
if panel_obj.is_single_file_mode then
history.navigate_prev_commit(panel_obj)
else
history.navigate_prev(panel_obj)
end
else
local explorer = require("codediff.ui.explorer")
explorer.navigate_prev(panel_obj)
end
end

-- Helper: Quit diff view
Expand Down Expand Up @@ -417,16 +439,6 @@ function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explore
lifecycle.set_tab_keymap(tabpage, "n", keymaps.toggle_explorer, toggle_explorer, { desc = "Toggle explorer visibility" })
end

-- File navigation (]f, [f) - only in explorer mode
if is_explorer_mode then
if keymaps.next_file then
lifecycle.set_tab_keymap(tabpage, "n", keymaps.next_file, navigate_next_file, { desc = "Next file in explorer" })
end
if keymaps.prev_file then
lifecycle.set_tab_keymap(tabpage, "n", keymaps.prev_file, navigate_prev_file, { desc = "Previous file in explorer" })
end
end

-- Diff get/put (do, dp) - like vimdiff
if keymaps.diff_get then
lifecycle.set_tab_keymap(tabpage, "n", keymaps.diff_get, diff_get, { desc = "Get change from other buffer" })
Expand Down Expand Up @@ -456,6 +468,16 @@ function M.setup_all_keymaps(tabpage, original_bufnr, modified_bufnr, is_explore
lifecycle.set_tab_keymap(tabpage, "n", toggle_stage_key, toggle_stage, { desc = "Toggle stage/unstage" })
end
end

-- File navigation (]f, [f) - works in both explorer and history mode
if is_explorer_mode or is_history_mode then
if keymaps.next_file then
lifecycle.set_tab_keymap(tabpage, "n", keymaps.next_file, navigate_next_file, { desc = "Next file" })
end
if keymaps.prev_file then
lifecycle.set_tab_keymap(tabpage, "n", keymaps.prev_file, navigate_prev_file, { desc = "Previous file" })
end
end
end

return M