Skip to content

Commit bf4e4bd

Browse files
txemaoteroCKolkey
authored andcommitted
Feat: Add jumpable commit view hunk navigation
- Allow jumping from commit view hunks to worktree or committed file content - Share jump logic via new lib/jump helpers and extra tests - Make commit-view "open file" mapping configurable - Document commit view mappings and behavior
1 parent d73e41e commit bf4e4bd

File tree

6 files changed

+411
-23
lines changed

6 files changed

+411
-23
lines changed

doc/neogit.txt

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,10 @@ The following mappings can all be customized via the setup function.
391391
["<c-c><c-k>"] = "Abort",
392392
}
393393

394+
commit_view = {
395+
["a"] = "OpenFileInWorktree",
396+
}
397+
394398
rebase_editor = {
395399
["p"] = "Pick",
396400
["r"] = "Reword",
@@ -2171,7 +2175,32 @@ Reflog Buffer *neogit_reflog_buffer*
21712175

21722176
==============================================================================
21732177
Commit Buffer *neogit_commit_buffer*
2174-
(TODO)
2178+
The commit buffer shows a single commit, including metadata, summary, and an
2179+
inline diff. It opens via |:NeogitCommit|, from log/graph views, or when
2180+
pressing `<cr>` on a commit hash elsewhere in Neogit.
2181+
2182+
Mappings (normal mode):
2183+
`a` (|neogit_setup_mappings|: "OpenFileInWorktree" in commit_view)
2184+
On a diff hunk, try to jump to that location in the worktree
2185+
(i.e. current version of the file).
2186+
Note: it can fail if the file no longer exists or it can take
2187+
you to a wrong location in the file if the lines have been
2188+
moved around.
2189+
`<cr>` On a diff hunk, open the file contents from the commit in a
2190+
temporary read-only tab, or the parent revision for deleted
2191+
lines; on a filepath line, jump to that file's diff section
2192+
within the buffer.
2193+
`o` Open the commit in the configured git service (requires
2194+
Neovim >= 0.10 for |vim.ui.open|).
2195+
`{` / `}` Jump to previous/next hunk or diff header.
2196+
`q` / `<esc>` Close the commit buffer (keys are configurable via
2197+
|neogit_setup_mappings| for the commit view).
2198+
`Y` Yank the current commit hash to the clipboard.
2199+
`za` / `<tab>` Toggle folding for the current section.
2200+
• Popup shortcuts honour |neogit_setup_mappings|, default keys:
2201+
- `A` Cherry-pick, `b` Branch, `B` Bisect, `c` Commit, `d` Diff,
2202+
`f` Fetch, `i` Ignore, `l` Log, `m` Merge, `p` Pull, `P` Push,
2203+
`r` Rebase, `t` Tag, `v` Revert, `w` Worktree, `X` Reset, `Z` Stash.
21752204

21762205
==============================================================================
21772206
Refs Buffer *neogit_refs_buffer*

lua/neogit/buffers/commit_view/init.lua

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ local ui = require("neogit.buffers.commit_view.ui")
44
local git = require("neogit.lib.git")
55
local config = require("neogit.config")
66
local popups = require("neogit.popups")
7+
local commit_view_maps = require("neogit.config").get_reversed_commit_view_maps()
78
local status_maps = require("neogit.config").get_reversed_status_maps()
89
local notification = require("neogit.lib.notification")
10+
local jump = require("neogit.lib.jump")
911

1012
local api = vim.api
1113

@@ -151,6 +153,71 @@ function M:update(commit_id, filter)
151153
self.buffer:win_call(vim.cmd, "normal! gg")
152154
end
153155

156+
---Generate a callback to re-open CommitViewBuffer in the current commit
157+
---@param self CommitViewBuffer
158+
---@return fun()
159+
local function get_reopen_cb(self)
160+
local original_cursor = api.nvim_win_get_cursor(0)
161+
local back_commit = self.commit_info.oid
162+
return function()
163+
M.new(back_commit):open()
164+
api.nvim_win_set_cursor(0, original_cursor)
165+
end
166+
end
167+
168+
---@param self CommitViewBuffer
169+
---@param location LocationInHunk
170+
---@return string|nil, integer[]
171+
local function location_to_commit_cursor(self, location)
172+
if string.sub(location.line, 1, 1) == "-" then
173+
return git.log.parent(self.commit_info.oid), { location.old, 0 }
174+
else
175+
return self.commit_info.oid, { location.new, 0 }
176+
end
177+
end
178+
179+
---Visit the file at the location specified by the provided hunk component
180+
---@param self CommitViewBuffer
181+
---@param component Component A component that evaluates is_jumpable_hunk_line_component() to true
182+
---@param worktree boolean if true, try to jump to the file in the current worktree. Otherwise jump to the file in the referenced commit
183+
local function diff_visit_file(self, component, worktree)
184+
local hunk_component = component.parent.parent
185+
local hunk = hunk_component.options.hunk
186+
local path = vim.trim(hunk.file)
187+
if path == "" then
188+
notification.warn("Unable to determine file path for diff line")
189+
return
190+
end
191+
192+
local line = self.buffer:cursor_line()
193+
local offset = line - hunk_component.position.row_start
194+
local location = jump.translate_hunk_location(hunk, offset)
195+
if not location then
196+
-- Cursor outside the hunk, shouldn't happen. Don't warn in that case
197+
return
198+
end
199+
200+
if worktree then
201+
local cursor = { location.new, 0 }
202+
jump.goto_file_at(path, cursor)
203+
else
204+
local target_commit, cursor = location_to_commit_cursor(self, location)
205+
if not target_commit then
206+
notification.warn("Unable to retrieve parent commit")
207+
return nil, cursor
208+
end
209+
jump.goto_file_in_commit_at(target_commit, path, cursor, get_reopen_cb(self))
210+
end
211+
end
212+
213+
---@param c Component
214+
---@return boolean
215+
local function is_jumpable_hunk_line_component(c)
216+
return c.options.line_hl == "NeogitDiffContext"
217+
or c.options.line_hl == "NeogitDiffAdd"
218+
or c.options.line_hl == "NeogitDiffDelete"
219+
end
220+
154221
---Opens the CommitViewBuffer
155222
---If already open will close the buffer
156223
---@param kind? string
@@ -189,15 +256,29 @@ function M:open(kind)
189256
notification.warn("Couldn't determine commit URL to open")
190257
end
191258
end,
259+
[commit_view_maps["OpenFileInWorktree"]] = function()
260+
-- Abort if rebase_editor
261+
local c = self.buffer.ui:get_component_under_cursor(function(c)
262+
return is_jumpable_hunk_line_component(c)
263+
end)
264+
if c then
265+
diff_visit_file(self, c, true)
266+
end
267+
end,
192268
["<cr>"] = function()
193269
local c = self.buffer.ui:get_component_under_cursor(function(c)
194-
return c.options.highlight == "NeogitFilePath"
270+
return c.options.highlight == "NeogitFilePath" or is_jumpable_hunk_line_component(c)
195271
end)
196272

197273
if not c then
198274
return
199275
end
200276

277+
if is_jumpable_hunk_line_component(c) then
278+
diff_visit_file(self, c, false)
279+
return
280+
end
281+
201282
-- Some paths are padded for formatting purposes. We need to trim them
202283
-- in order to use them as match patterns.
203284
local selected_path = vim.fn.trim(c.value)

lua/neogit/buffers/status/actions.lua

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ local input = require("neogit.lib.input")
88
local notification = require("neogit.lib.notification")
99
local util = require("neogit.lib.util")
1010
local config = require("neogit.config")
11+
local jump = require("neogit.lib.jump")
1112

1213
local FuzzyFinderBuffer = require("neogit.buffers.fuzzy_finder")
1314

@@ -53,41 +54,25 @@ end
5354

5455
---@param self StatusBuffer
5556
---@param item StatusItem
56-
---@return table|nil
57+
---@return integer[]|nil
5758
local function translate_cursor_location(self, item)
5859
if rawget(item, "diff") then
5960
local line = self.buffer:cursor_line()
6061

6162
for _, hunk in ipairs(item.diff.hunks) do
6263
if line >= hunk.first and line <= hunk.last then
6364
local offset = line - hunk.first
64-
local row = hunk.disk_from + offset - 1
65-
66-
for i = 1, offset do
67-
-- If the line is a deletion, we need to adjust the row
68-
if string.sub(hunk.lines[i], 1, 1) == "-" then
69-
row = row - 1
70-
end
65+
local location = jump.translate_hunk_location(hunk, offset)
66+
if location then
67+
return { location.new, 0 }
7168
end
72-
73-
return { row, 0 }
7469
end
7570
end
7671
end
7772
end
7873

7974
local function open(type, path, cursor)
80-
local command = ("silent! %s %s | %s"):format(type, fn.fnameescape(path), cursor and cursor[1] or "1")
81-
82-
logger.debug("[Status - Open] '" .. command .. "'")
83-
84-
vim.cmd(command)
85-
86-
command = "redraw! | norm! zz"
87-
88-
logger.debug("[Status - Open] '" .. command .. "'")
89-
90-
vim.cmd(command)
75+
jump.open(type, path, cursor, "[Status - Open]")
9176
end
9277

9378
local M = {}

lua/neogit/config.lua

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ function M.get_reversed_commit_editor_maps_I()
6262
return get_reversed_maps("commit_editor_I")
6363
end
6464

65+
---@return table<string, string[]>
66+
function M.get_reversed_commit_view_maps()
67+
return get_reversed_maps("commit_view")
68+
end
69+
6570
---@return table<string, string[]>
6671
function M.get_reversed_refs_view_maps()
6772
return get_reversed_maps("refs_view")
@@ -590,6 +595,9 @@ function M.get_default_values()
590595
},
591596
ignored_settings = {},
592597
mappings = {
598+
commit_view = {
599+
["a"] = "OpenFileInWorktree",
600+
},
593601
commit_editor = {
594602
["q"] = "Close",
595603
["<c-c><c-c>"] = "Submit",

0 commit comments

Comments
 (0)