Skip to content

Commit

Permalink
feat(nav): add nav_hunk()
Browse files Browse the repository at this point in the history
- Deprecated `next_hunk()` and `prev_hunk()`.
- Can now navigate to the first/last hunk using `nav_hunk('first'|'last')`.
- Added `count` to navigation options. Defaults to `v:count1`.
- Updated recommended keymaps for navigation.
- Navigation actions now navigate to the first non-blank column.
  • Loading branch information
lewis6991 committed Apr 5, 2024
1 parent c093623 commit 59bdc18
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 112 deletions.
48 changes: 26 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ Here is a suggested example:
require('gitsigns').setup{
...
on_attach = function(bufnr)
local gs = package.loaded.gitsigns
local gitsigns = require('gitsigns')

local function map(mode, l, r, opts)
opts = opts or {}
Expand All @@ -123,31 +123,35 @@ require('gitsigns').setup{

-- Navigation
map('n', ']c', function()
if vim.wo.diff then return ']c' end
vim.schedule(function() gs.next_hunk() end)
return '<Ignore>'
end, {expr=true})
if vim.wo.diff then
vim.cmd.normal({']c', bang = true})
else
gitsigns.nav_hunk('next')
end
end)

map('n', '[c', function()
if vim.wo.diff then return '[c' end
vim.schedule(function() gs.prev_hunk() end)
return '<Ignore>'
end, {expr=true})
if vim.wo.diff then
vim.cmd.normal({'[c', bang = true})
else
gitsigns.nav_hunk('prev')
end
end)

-- Actions
map('n', '<leader>hs', gs.stage_hunk)
map('n', '<leader>hr', gs.reset_hunk)
map('v', '<leader>hs', function() gs.stage_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
map('v', '<leader>hr', function() gs.reset_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
map('n', '<leader>hS', gs.stage_buffer)
map('n', '<leader>hu', gs.undo_stage_hunk)
map('n', '<leader>hR', gs.reset_buffer)
map('n', '<leader>hp', gs.preview_hunk)
map('n', '<leader>hb', function() gs.blame_line{full=true} end)
map('n', '<leader>tb', gs.toggle_current_line_blame)
map('n', '<leader>hd', gs.diffthis)
map('n', '<leader>hD', function() gs.diffthis('~') end)
map('n', '<leader>td', gs.toggle_deleted)
map('n', '<leader>hs', gitsigns.stage_hunk)
map('n', '<leader>hr', gitsigns.reset_hunk)
map('v', '<leader>hs', function() gitsigns.stage_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
map('v', '<leader>hr', function() gitsigns.reset_hunk {vim.fn.line('.'), vim.fn.line('v')} end)
map('n', '<leader>hS', gitsigns.stage_buffer)
map('n', '<leader>hu', gitsigns.undo_stage_hunk)
map('n', '<leader>hR', gitsigns.reset_buffer)
map('n', '<leader>hp', gitsigns.preview_hunk)
map('n', '<leader>hb', function() gitsigns.blame_line{full=true} end)
map('n', '<leader>tb', gitsigns.toggle_current_line_blame)
map('n', '<leader>hd', gitsigns.diffthis)
map('n', '<leader>hD', function() gitsigns.diffthis('~') end)
map('n', '<leader>td', gitsigns.toggle_deleted)

-- Text object
map({'o', 'x'}, 'ih', ':<C-U>Gitsigns select_hunk<CR>')
Expand Down
54 changes: 36 additions & 18 deletions doc/gitsigns.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ preview_hunk() *gitsigns.preview_hunk()*
will cause the window to get focus.

prev_hunk({opts}, {callback?}) *gitsigns.prev_hunk()*
DEPRECATED: use |gitsigns.nav_hunk()|

Jump to the previous hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the previous hunk.
Expand All @@ -351,9 +353,11 @@ prev_hunk({opts}, {callback?}) *gitsigns.prev_hunk()*
{async}

Parameters: ~
See |gitsigns.next_hunk()|.
See |gitsigns.nav_hunk()|.

next_hunk({opts}, {callback?}) *gitsigns.next_hunk()*
DEPRECATED: use |gitsigns.nav_hunk()|

Jump to the next hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the next hunk.
Expand All @@ -362,23 +366,37 @@ next_hunk({opts}, {callback?}) *gitsigns.next_hunk()*
{async}

Parameters: ~
{opts} (table|nil): Configuration table. Keys:
{wrap}: (boolean)
Whether to loop around file or not. Defaults
to the value 'wrapscan'
• {navigation_message}: (boolean)
Whether to show navigation messages or not.
Looks at 'shortmess' for default behaviour.
{foldopen}: (boolean)
Expand folds when navigating to a hunk which is
inside a fold. Defaults to `true` if 'foldopen'
contains `search`.
{preview}: (boolean)
Automatically open preview_hunk() upon navigating
to a hunk.
{greedy}: (boolean)
Only navigate between non-contiguous hunks. Only useful if
'diff_opts' contains `linematch`. Defaults to `true`.
See |gitsigns.nav_hunk()|.

nav_hunk({direction}, {opts}, {callback?}) *gitsigns.nav_hunk()*
Jump to hunk in the current buffer. If a hunk preview
(popup or inline) was previously opened, it will be re-opened
at the next hunk.

Attributes: ~
{async}

Parameters: ~
{direction} ('first'|'last'|'next'|'prev'):
{opts} (table|nil): Configuration table. Keys:
{wrap}: (boolean)
Whether to loop around file or not. Defaults
to the value 'wrapscan'
• {navigation_message}: (boolean)
Whether to show navigation messages or not.
Looks at 'shortmess' for default behaviour.
{foldopen}: (boolean)
Expand folds when navigating to a hunk which is
inside a fold. Defaults to `true` if 'foldopen'
contains `search`.
{preview}: (boolean)
Automatically open preview_hunk() upon navigating
to a hunk.
{greedy}: (boolean)
Only navigate between non-contiguous hunks. Only useful if
'diff_opts' contains `linematch`. Defaults to `true`.
{count}: (integer)
Number of times to advance. Defaults to |v:count1|.

reset_buffer_index() *gitsigns.reset_buffer_index()*
Unstage all hunks for current buffer in the index. Note:
Expand Down
22 changes: 19 additions & 3 deletions gen_help.lua
Original file line number Diff line number Diff line change
Expand Up @@ -295,13 +295,22 @@ end
--- @param block string[]
--- @param params {[1]: string, [2]: string, [3]: string[]}[]
--- @param returns {[1]: string, [2]: string, [3]: string[]}[]
--- @param deprecated string?
--- @return string[]?
local function render_block(header, block, params, returns)
local function render_block(header, block, params, returns, deprecated)
if vim.startswith(header, '_') then
return
end

local res = { header }

if deprecated then
list_extend(res, {
' DEPRECATED: '..deprecated,
''
})
end

list_extend(res, block)

-- filter arguments beginning with '_'
Expand Down Expand Up @@ -356,21 +365,28 @@ local function gen_functions_doc_from_file(path)
local desc = {} --- @type string[]
local params = {} --- @type {[1]: string, [2]: string, [3]: string[]}[]
local returns = {} --- @type {[1]: string, [2]: string, [3]: string[]}[]
local deprecated --- @type string?

for l in i do
local doc_comment = l:match('^%-%-%- ?(.*)') --- @type string?
if doc_comment then
state = process_doc_comment(state, doc_comment, desc, params, returns)
local depre = doc_comment:match('@deprecated ?(.*)')
if depre then
deprecated = depre
else
state = process_doc_comment(state, doc_comment, desc, params, returns)
end
elseif state ~= 'none' then
-- First line after block
local ok, header = pcall(parse_func_header, l)
if ok then
blocks[#blocks + 1] = render_block(header, desc, params, returns)
blocks[#blocks + 1] = render_block(header, desc, params, returns, deprecated)
end
state = 'none'
desc = {}
params = {}
returns = {}
deprecated = nil
end
end

Expand Down
120 changes: 78 additions & 42 deletions lua/gitsigns/actions.lua
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ end)
--- @field navigation_message boolean
--- @field greedy boolean
--- @field preview boolean
--- @field count integer

--- @param x string
--- @param word string
Expand Down Expand Up @@ -494,6 +495,10 @@ local function process_nav_opts(opts)
opts.greedy = true
end

if opts.count == nil then
opts.count = vim.v.count1
end

return opts
end

Expand All @@ -514,75 +519,85 @@ local function has_preview_inline(bufnr)
end

--- @async
--- @param direction 'first'|'last'|'next'|'prev'
--- @param opts? Gitsigns.NavOpts
--- @param forwards boolean
local function nav_hunk(opts, forwards)
local function nav_hunk(direction, opts)
opts = process_nav_opts(opts)
local bufnr = current_buf()
local bcache = cache[bufnr]
if not bcache then
return
end

local hunks = {}
vim.list_extend(hunks, get_hunks(bufnr, bcache, opts.greedy, false) or {})
local hunks = get_hunks(bufnr, bcache, opts.greedy, false) or {}
local hunks_head = get_hunks(bufnr, bcache, opts.greedy, true) or {}
vim.list_extend(hunks, Hunks.filter_common(hunks_head, bcache.hunks) or {})
vim.list_extend(hunks, Hunks.filter_common(hunks_head, hunks) or {})

if not hunks or vim.tbl_isempty(hunks) then
if opts.navigation_message then
api.nvim_echo({ { 'No hunks', 'WarningMsg' } }, false, {})
end
return
end

local line = api.nvim_win_get_cursor(0)[1]
local index --- @type integer?

local hunk, index = Hunks.find_nearest_hunk(line, hunks, forwards, opts.wrap)
local forwards = direction == 'next' or direction == 'last'

if hunk == nil then
if opts.navigation_message then
api.nvim_echo({ { 'No more hunks', 'WarningMsg' } }, false, {})
for _ = 1, opts.count do
index = Hunks.find_nearest_hunk(line, hunks, direction, opts.wrap)

if not index then
if opts.navigation_message then
api.nvim_echo({ { 'No more hunks', 'WarningMsg' } }, false, {})
end
local _, col = vim.fn.getline(line):find('^%s*')
api.nvim_win_set_cursor(0, { line, col })
return
end
return

line = forwards and hunks[index].added.start or hunks[index].vend
end

local row = forwards and hunk.added.start or hunk.vend
if row then
-- Handle topdelete
if row == 0 then
row = 1
end
vim.cmd([[ normal! m' ]]) -- add current cursor position to the jump list
api.nvim_win_set_cursor(0, { row, 0 })
if opts.foldopen then
vim.cmd('silent! foldopen!')
end
if opts.preview or popup.is_open('hunk') ~= nil then
-- Use defer so the cursor change can settle, otherwise the popup might
-- appear in the old position
defer(function()
-- Close the popup in case one is open which will cause it to focus the
-- popup
popup.close('hunk')
M.preview_hunk()
end)
elseif has_preview_inline(bufnr) then
defer(M.preview_hunk_inline)
end
-- Handle topdelete
line = math.max(line, 1)

if index ~= nil and opts.navigation_message then
api.nvim_echo({ { string.format('Hunk %d of %d', index, #hunks), 'None' } }, false, {})
end
vim.cmd([[ normal! m' ]]) -- add current cursor position to the jump list

local _, col = vim.fn.getline(line):find('^%s*')
api.nvim_win_set_cursor(0, { line, col })

if opts.foldopen then
vim.cmd('silent! foldopen!')
end

if opts.preview or popup.is_open('hunk') ~= nil then
-- Use defer so the cursor change can settle, otherwise the popup might
-- appear in the old position
defer(function()
-- Close the popup in case one is open which will cause it to focus the
-- popup
popup.close('hunk')
M.preview_hunk()
end)
elseif has_preview_inline(bufnr) then
defer(M.preview_hunk_inline)
end

if index and opts.navigation_message then
api.nvim_echo({ { string.format('Hunk %d of %d', index, #hunks), 'None' } }, false, {})
end
end

--- Jump to the next hunk in the current buffer. If a hunk preview
--- Jump to hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the next hunk.
---
--- Attributes: ~
--- {async}
---
--- @param direction 'first'|'last'|'next'|'prev'
--- @param opts table|nil Configuration table. Keys:
--- • {wrap}: (boolean)
--- Whether to loop around file or not. Defaults
Expand All @@ -600,14 +615,35 @@ end
--- • {greedy}: (boolean)
--- Only navigate between non-contiguous hunks. Only useful if
--- 'diff_opts' contains `linematch`. Defaults to `true`.
--- • {count}: (integer)
--- Number of times to advance. Defaults to |v:count1|.
M.nav_hunk = async.create(2, function(direction, opts)
nav_hunk(direction, opts)
end)

C.nav_hunk = function(args, _)
M.nav_hunk(args[1], args)
end

--- @deprecated use |gitsigns.nav_hunk()|
--- Jump to the next hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the next hunk.
---
--- Attributes: ~
--- {async}
---
--- Parameters: ~
--- See |gitsigns.nav_hunk()|.
M.next_hunk = async.create(1, function(opts)
nav_hunk(opts, true)
nav_hunk('next', opts)
end)

C.next_hunk = function(args, _)
M.next_hunk(args)
M.nav_hunk('next', args)
end

--- @deprecated use |gitsigns.nav_hunk()|
--- Jump to the previous hunk in the current buffer. If a hunk preview
--- (popup or inline) was previously opened, it will be re-opened
--- at the previous hunk.
Expand All @@ -616,13 +652,13 @@ end
--- {async}
---
--- Parameters: ~
--- See |gitsigns.next_hunk()|.
--- See |gitsigns.nav_hunk()|.
M.prev_hunk = async.create(1, function(opts)
nav_hunk(opts, false)
nav_hunk('prev', opts)
end)

C.prev_hunk = function(args, _)
M.prev_hunk(args)
M.nav_hunk('prev', args)
end

--- @param fmt Gitsigns.LineSpec
Expand Down
Loading

0 comments on commit 59bdc18

Please sign in to comment.