My goal is to remap zt and zb to set scrolloff=0, run zt or zb, then set scrolloff back to its original value. I like the context when scrolling, but when I hit one of these keys I really want the top at the top or the bottom at the bottom. I want to do something like this:

vim.keymap.set("n", "zb", scroll_very_bottom, { desc = "better bottom" })
vim.keymap.set("n", "zt", scroll_very_top, { desc = "better top" })

To expand, what I'd really like is to have zt jump to the top of the screen but as my cursor gets further from the top scrolloff is updated to this distance with the maximum set to the previously set offset. So, say scrolloff=3.

  1. zt puts current like at top of window.
  2. j puts cursor on line 2 but leaves same line at top.
  3. j puts cursor on line 3 but leaves same line at top.
  4. j leaves cursor on line 3 but scroll window by one position.

Then scrolling is back to normal offset.

  • Put your cursor on the window's topmost line, then :set scrolloff=3. You'll notice that the window moves up three lines (or the line with your cursor moves down three lines). Even if your mapping would work, your function wouldn't.
    – Friedrich
    Feb 14 at 13:32
  • 2
    use :norm! to prevent the keymapping recursively Feb 14 at 13:36
  • 1
    Yeah, simply mapping a toggle for scrolloff seems like the way to go. I'm not sure I follow your question. I intended to reset scrolloff directly after redrawing, but the really ideal solution would be to have zt redraw with cursor at top and with each line scrolled down increment scrolloff by 1, or something like that. So it goes to absolute top but gradually transitions back to the original scrolloff setting.
    – theherk
    Feb 14 at 14:48
  • 1
    Indeed Rich, that does look like it could do the trick. I'll give it a whirl.
    – theherk
    Feb 14 at 16:31
  • 1
    @Rich: Thanks a ton. That worked like a champ. Answer updated with solution.
    – theherk
    Feb 15 at 11:38

1 Answer 1


Based on great feedback and some really good inspiration from Rich, I have put together a working solution for this and I love it.

local function scrolloff_should_reenable()
  if vim.w.orig_scrolloff == nil then return false end -- Nothing to do.
  if vim.fn.winheight(0) <= vim.w.orig_scrolloff then return true end -- Too small for override.
  return vim.w.orig_scrolloff < vim.fn.winline() and vim.fn.winline() < vim.fn.winheight(0) - vim.w.orig_scrolloff

local function scrolloff_add_autocmd()
  vim.api.nvim_create_augroup("h4s_scrolloff_enhanced", { clear = true })
  vim.api.nvim_create_autocmd("CursorMoved", {
    group = "h4s_scrolloff_enhanced",
    callback = function()
      if scrolloff_should_reenable() then
        vim.opt.scrolloff = vim.w.orig_scrolloff
        vim.w.orig_scrolloff = nil
        return true -- Remove the autocmd.

local function scrolloff_disable()
  vim.w.orig_scrolloff = vim.opt.scrolloff:get()
  vim.opt.scrolloff = 0

local function scroll_very_bottom()
  vim.cmd(":norm! zb")

local function scroll_very_top()
  vim.cmd(":norm! zt")

vim.keymap.set("n", "zb", scroll_very_bottom, { desc = "better bottom" })
vim.keymap.set("n", "zt", scroll_very_top, { desc = "better top" })

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.