center.lua

Branches > main > e956ca8 > files / nvim / lua / center.lua

--- Keeps the current line centered on the screen always,
---  including when at the top of a buffer.
--- It does this by adding a bunch of fake virtual lines above the first line of the buf,
---  and scrolling the screen to the correct place
local api = vim.api
local ac = vim.api.nvim_create_autocmd
local ag = vim.api.nvim_create_augroup
local fn = vim.fn

local ns = api.nvim_create_namespace("dev.grahp.nvim.padding_lines")

local function set_padding_lines(lines)
   api.nvim_buf_clear_namespace(0, ns, 0, -1)
   api.nvim_buf_set_extmark(0, ns, 0, 0, {
      virt_lines = lines,
      virt_lines_above = true
   })
end

--- Needed for first paint otherwise it'd be in utils :p
local function replicate(n, x)
   local tbl = {}
   if n > 0 then
      for i = 1, n do
         table.insert(tbl, x)
      end
   end
   return tbl
end

--- Centers the cursor on the screen
--- Normally, the screen cannot go above the first line of the buffer,
---  so this adds enough virtual lines above the top of the buffer to allow you to scroll past the top of the buffer
--- Centers the screen, and then feeds enough <C-y>s to scroll the cursor to the very center of the screen
--- If you're completely past the top of the buffer, just `zz` works and it won't scroll anything.
local function center()
   local win_height = api.nvim_win_get_height(0)
   local padding_lines = math.ceil(win_height / 2)

   local blank_line = {{""}}

   set_padding_lines(replicate(win_height, blank_line))

   local _, _, col, off, curswant = unpack(fn.getcurpos())
   vim.cmd("norm! zz")

   local line_number = fn.line(".")
   local offset = padding_lines - line_number
   if offset > 0 then
     vim.cmd("norm! " .. offset .. "")
   end

   -- Reset cursor back to the column it was at before
   -- Without this, zz and <C-y> muck up the cursor position when in insert mode
   fn.cursor({vim.fn.getcurpos()[2], col, off, curswant})
end

--- When moving the cursor, entering a buffer, etc., center the screen
ag("TopPad", {clear = true})
ac({ "BufWinEnter", "BufEnter", "WinResized", "WinScrolled", "CursorMoved", "CursorMovedI"}, {
  group = "TopPad",
  callback = center,
})