Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 8fe38d6

Browse files
feat(diff_view): add select_(next|previous)_commit
Other than some minor stylistic changes this commit was entirely produced by an LLM through the use of [Amp](https://ampcode.com/).
1 parent 4516612 commit 8fe38d6

File tree

3 files changed

+197
-2
lines changed

3 files changed

+197
-2
lines changed

‎doc/diffview.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,12 +1186,12 @@ select_last_entry *diffview-actions-select_last_en
11861186
Select the last entry.
11871187

11881188
[count] select_next_commit *diffview-actions-select_next_commit*
1189-
Contexts: `file_history_view`, `file_history_panel`
1189+
Contexts: `diff_view`, `file_history_view`, `file_history_panel`
11901190

11911191
Select the commit following the subject.
11921192

11931193
[count] select_prev_commit *diffview-actions-select_prev_commit*
1194-
Contexts: `file_history_view`, `file_history_panel`
1194+
Contexts: `diff_view`, `file_history_view`, `file_history_panel`
11951195

11961196
Select the commit preceding the subject.
11971197

‎lua/diffview/scene/views/diff/diff_view.lua

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,193 @@ function DiffView:is_valid()
551551
return self.valid
552552
end
553553

554+
---Helper function to navigate commit history
555+
---@param direction "next"|"prev" # Direction to navigate in commit history
556+
---@return string|nil # Commit hash or nil if none available
557+
DiffView._get_commit_in_direction = async.wrap(function(self, direction, callback)
558+
if not self._commit_history then
559+
-- Prevent race conditions by checking if we're already building
560+
if self._building_commit_history then
561+
callback(nil)
562+
return
563+
end
564+
565+
self._building_commit_history = true
566+
local err = await(self:_build_commit_history())
567+
self._building_commit_history = false
568+
569+
if err then
570+
callback(nil)
571+
return
572+
end
573+
end
574+
575+
local current_commit = self:_get_current_commit_hash()
576+
if not current_commit then
577+
callback(nil)
578+
return
579+
end
580+
581+
local current_idx = nil
582+
for i, commit_hash in ipairs(self._commit_history) do
583+
if commit_hash == current_commit then
584+
current_idx = i
585+
break
586+
end
587+
end
588+
589+
if not current_idx then
590+
callback(nil)
591+
return
592+
end
593+
594+
if direction == "next" then
595+
if current_idx >= #self._commit_history then
596+
callback(nil)
597+
else
598+
callback(self._commit_history[current_idx + 1])
599+
end
600+
elseif direction == "prev" then
601+
if current_idx <= 1 then
602+
callback(nil)
603+
else
604+
callback(self._commit_history[current_idx - 1])
605+
end
606+
else
607+
callback(nil)
608+
end
609+
end)
610+
611+
---Get the next commit in the commit history
612+
---@return string|nil # Next commit hash or nil if none available
613+
DiffView.get_older_commit = async.wrap(function(self, callback)
614+
local result = await(self:_get_commit_in_direction("next"))
615+
callback(result)
616+
end)
617+
618+
---Get the previous commit in the commit history
619+
---@return string|nil # Previous commit hash or nil if none available
620+
DiffView.get_newer_commit = async.wrap(function(self, callback)
621+
local result = await(self:_get_commit_in_direction("prev"))
622+
callback(result)
623+
end)
624+
625+
---Build commit history for navigation
626+
---@private
627+
---@return string|nil # Error message if failed
628+
DiffView._build_commit_history = async.wrap(function(self, callback)
629+
local Job = require("diffview.job").Job
630+
631+
-- Build git log arguments based on the diff view context
632+
local args = { "log", "--pretty=format:%H", "--no-merges", "--first-parent" }
633+
634+
-- Always use HEAD to get the full commit history for navigation
635+
-- We need the complete history to navigate forward/backward through commits
636+
table.insert(args, "HEAD")
637+
638+
-- Add path arguments if any
639+
if self.path_args and #self.path_args > 0 then
640+
table.insert(args, "--")
641+
for _, path in ipairs(self.path_args) do
642+
table.insert(args, path)
643+
end
644+
end
645+
646+
local job = Job({
647+
command = "git",
648+
args = args,
649+
cwd = self.adapter.ctx.toplevel,
650+
})
651+
652+
local ok = await(job)
653+
if not ok then
654+
callback("Failed to get commit history: " .. table.concat(job.stderr or {}, "\n"))
655+
return
656+
end
657+
658+
local raw_output = table.concat(job.stdout or {}, "\n")
659+
self._commit_history = vim.split(raw_output, "\n", { trimempty = true })
660+
callback(nil)
661+
end)
662+
663+
---Get current commit hash being viewed
664+
---@private
665+
---@return string|nil
666+
function DiffView:_get_current_commit_hash()
667+
if self.right.commit then
668+
-- Handle both cases: commit object with .hash property, or commit being the hash itself
669+
return type(self.right.commit) == "table" and self.right.commit.hash or self.right.commit
670+
elseif self.left.commit then
671+
-- Handle both cases: commit object with .hash property, or commit being the hash itself
672+
return type(self.left.commit) == "table" and self.left.commit.hash or self.left.commit
673+
end
674+
return nil
675+
end
676+
677+
---Set the current commit being viewed
678+
---@param commit_hash string
679+
DiffView.set_commit = async.void(function(self, commit_hash)
680+
local RevType = require("diffview.vcs.rev").RevType
681+
local Job = require("diffview.job").Job
682+
683+
-- Resolve the parent commit hash using git rev-parse
684+
local parent_job = Job({
685+
command = "git",
686+
args = { "rev-parse", commit_hash .. "^" },
687+
cwd = self.adapter.ctx.toplevel,
688+
})
689+
690+
local ok = await(parent_job)
691+
local new_left, new_right
692+
693+
if not ok or not parent_job.stdout or #parent_job.stdout == 0 then
694+
-- Fallback: use the string reference if we can't resolve it
695+
new_left = self.adapter.Rev(RevType.COMMIT, commit_hash .. "~1")
696+
new_right = self.adapter.Rev(RevType.COMMIT, commit_hash)
697+
else
698+
-- Use the resolved parent commit hash
699+
local parent_hash = vim.trim(parent_job.stdout[1])
700+
new_left = self.adapter.Rev(RevType.COMMIT, parent_hash)
701+
new_right = self.adapter.Rev(RevType.COMMIT, commit_hash)
702+
end
703+
704+
-- Update the view's revisions
705+
self.left = new_left
706+
self.right = new_right
707+
708+
-- Update the panel's pretty name to reflect the new commit
709+
-- For single commits, show the conventional git format: commit_hash^..commit_hash
710+
local right_abbrev = new_right:abbrev()
711+
self.panel.rev_pretty_name = right_abbrev .. "^.." .. right_abbrev
712+
713+
-- Update files and refresh the view
714+
self:update_files()
715+
716+
-- Update panel to show current commit info and refresh diff content
717+
vim.schedule(function()
718+
self.panel:render()
719+
self.panel:redraw()
720+
721+
-- If there's a currently selected file, update its revisions and refresh
722+
-- This needs to be scheduled to avoid fast event context issues
723+
if self.cur_entry then
724+
-- Update the current entry's file revisions to match the new commit
725+
if self.cur_entry.layout and self.cur_entry.layout.a and self.cur_entry.layout.b then
726+
-- Dispose old buffers to prevent cached stale content
727+
self.cur_entry.layout.a.file:dispose_buffer()
728+
self.cur_entry.layout.b.file:dispose_buffer()
729+
730+
-- Update the file objects with new revisions
731+
self.cur_entry.layout.a.file.rev = new_left
732+
self.cur_entry.layout.b.file.rev = new_right
733+
734+
-- Force refresh by calling use_entry again
735+
self:use_entry(self.cur_entry)
736+
end
737+
end
738+
end)
739+
end)
740+
554741
M.DiffView = DiffView
555742

556743
return M

‎lua/diffview/scene/views/diff/listeners.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,5 +329,13 @@ return function(view)
329329
local dir = view.panel:get_dir_at_cursor()
330330
if dir then view.panel:toggle_item_fold(dir) end
331331
end,
332+
select_next_commit = async.void(function()
333+
local commit = await(view:get_newer_commit())
334+
if commit then await(view:set_commit(commit)) end
335+
end),
336+
select_prev_commit = async.void(function()
337+
local commit = await(view:get_older_commit())
338+
if commit then await(view:set_commit(commit)) end
339+
end),
332340
}
333341
end

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /