diff --git a/README.md b/README.md index cc1c9aa4..14aa1965 100644 --- a/README.md +++ b/README.md @@ -126,8 +126,8 @@ https://github.com/user-attachments/assets/64c41f01-dffe-4318-bce4-16eec8de356e toggle_explorer = "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) diff --git a/VERSION b/VERSION index 68e69e40..75249069 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.15.0 +2.16.0 diff --git a/doc/codediff.txt b/doc/codediff.txt index 56a0a889..7fbb2661 100644 --- a/doc/codediff.txt +++ b/doc/codediff.txt @@ -135,6 +135,10 @@ Setup entry point: unstage_all = "U", restore = "X", }, + history = { + select = "", + toggle_view_mode = "i", + }, conflict = { accept_incoming = "ct", accept_current = "co", diff --git a/lua/codediff/ui/history/init.lua b/lua/codediff/ui/history/init.lua index 56fe34d3..75cbb346 100644 --- a/lua/codediff/ui/history/init.lua +++ b/lua/codediff/ui/history/init.lua @@ -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 diff --git a/lua/codediff/ui/history/render.lua b/lua/codediff/ui/history/render.lua index 6a864e6e..b9d96069 100644 --- a/lua/codediff/ui/history/render.lua +++ b/lua/codediff/ui/history/render.lua @@ -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 @@ -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 diff --git a/lua/codediff/ui/view/init.lua b/lua/codediff/ui/view/init.lua index f904f7fb..9b6d3341 100644 --- a/lua/codediff/ui/view/init.lua +++ b/lua/codediff/ui/view/init.lua @@ -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 { diff --git a/lua/codediff/ui/view/keymaps.lua b/lua/codediff/ui/view/keymaps.lua index e355039b..34795bb2 100644 --- a/lua/codediff/ui/view/keymaps.lua +++ b/lua/codediff/ui/view/keymaps.lua @@ -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) @@ -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 @@ -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" }) @@ -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