Skip to content

Instantly share code, notes, and snippets.

@galaxia4Eva
Last active September 1, 2025 04:59
Show Gist options
  • Select an option

  • Save galaxia4Eva/9e91c4f275554b4bd844b6feece16b3d to your computer and use it in GitHub Desktop.

Select an option

Save galaxia4Eva/9e91c4f275554b4bd844b6feece16b3d to your computer and use it in GitHub Desktop.
nvim pager for kitty history
return function(INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN)
print('kitty sent:', INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN)
vim.opt.encoding='utf-8'
vim.opt.clipboard = 'unnamed'
vim.opt.compatible = false
vim.opt.number = false
vim.opt.relativenumber = false
vim.opt.termguicolors = true
vim.opt.showmode = false
vim.opt.ruler = false
vim.opt.laststatus = 0
vim.o.cmdheight = 0
vim.opt.showcmd = false
vim.opt.scrollback = INPUT_LINE_NUMBER + CURSOR_LINE
local term_buf = vim.api.nvim_create_buf(true, false);
local term_io = vim.api.nvim_open_term(term_buf, {})
vim.api.nvim_buf_set_keymap(term_buf, 'n', 'q', '<Cmd>q<CR>', { })
vim.api.nvim_buf_set_keymap(term_buf, 'n', '<ESC>', '<Cmd>q<CR>', { })
local group = vim.api.nvim_create_augroup('kitty+page', {})
local setCursor = function()
vim.api.nvim_feedkeys(tostring(INPUT_LINE_NUMBER) .. [[ggzt]], 'n', true)
local line = vim.api.nvim_buf_line_count(term_buf)
if (CURSOR_LINE <= line) then
line = CURSOR_LINE
end
vim.api.nvim_feedkeys(tostring(line - 1) .. [[j]], 'n', true)
vim.api.nvim_feedkeys([[0]], 'n', true)
vim.api.nvim_feedkeys(tostring(CURSOR_COLUMN - 1) .. [[l]], 'n', true)
end
vim.api.nvim_create_autocmd('ModeChanged', {
group = group,
buffer = term_buf,
callback = function()
local mode = vim.fn.mode()
if mode == 't' then
vim.cmd.stopinsert()
vim.schedule(setCursor)
end
end,
})
vim.api.nvim_create_autocmd('VimEnter', {
group = group,
pattern = '*',
once = true,
callback = function(ev)
local current_win = vim.fn.win_getid()
for _, line in ipairs(vim.api.nvim_buf_get_lines(ev.buf, 0, -2, false)) do
vim.api.nvim_chan_send(term_io, line)
vim.api.nvim_chan_send(term_io, '\r\n')
end
for _, line in ipairs(vim.api.nvim_buf_get_lines(ev.buf, -2, -1, false)) do
vim.api.nvim_chan_send(term_io, line)
end
vim.api.nvim_win_set_buf(current_win, term_buf)
vim.api.nvim_buf_delete(ev.buf, { force = true } )
vim.schedule(setCursor)
end
})
end
# ...
scrollback_pager nvim -u NONE -R -M -c 'lua require("kitty+page")(INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN)' -
# ...
@insilications
Copy link

This is my modified attempt, using @yusufshalaby solution with vim.defer_fn(setCursor, 10) to make sure the terminal has time to process the content and the buffer is ready.

/home/YOUR_USER/.config/nvim/lua/kitty+page.lua:

return function(INPUT_LINE_NUMBER)
    vim.opt.encoding='utf-8'
    -- Prevent auto-centering on click
    vim.opt.scrolloff = 0
    vim.opt.compatible = false
    vim.opt.number = false
    vim.opt.relativenumber = false
    vim.opt.termguicolors = true
    vim.opt.showmode = false
    vim.opt.ruler = false
    vim.opt.signcolumn=no
    vim.opt.showtabline=0
    vim.opt.laststatus = 0
    vim.o.cmdheight = 0
    vim.opt.showcmd = false
    vim.opt.scrollback = 100000
    vim.opt.clipboard:append('unnamedplus')
    local term_buf = vim.api.nvim_create_buf(true, false)
    local term_io = vim.api.nvim_open_term(term_buf, {})
    -- Map 'q' to first yank the visual selection (if any), which makes the copy selection work, and then quit.
    vim.api.nvim_buf_set_keymap(term_buf, 'v', 'q', 'y<Cmd>qa!<CR>', { })
    -- Regular quit mapping for normal mode
    vim.api.nvim_buf_set_keymap(term_buf, 'n', 'q', '<Cmd>qa!<CR>', { })
    local group = vim.api.nvim_create_augroup('kitty+page', {clear = true})

    local setCursor = function()
        local max_line_nr = vim.api.nvim_buf_line_count(term_buf)
        local input_line_nr = math.max(1, math.min(tonumber(INPUT_LINE_NUMBER), max_line_nr))

        -- It seems that both the view (view.topline) and the cursor (nvim_win_set_cursor) must be set
        -- for scrolling and cursor positioning to work reliably with terminal buffers.
        vim.fn.winrestview({topline = input_line_nr})
        vim.api.nvim_win_set_cursor(0, {input_line_nr, 0})
    end

  vim.api.nvim_create_autocmd('ModeChanged', {
    group = group,
    buffer = term_buf,
    callback = function()
      local mode = vim.fn.mode()
      if mode == 't' then
        vim.cmd.stopinsert()
        vim.schedule(setCursor)
      end
    end,
  })

  vim.api.nvim_create_autocmd('VimEnter', {
    group = group,
    pattern = '*',
    once = true,
    callback = function(ev)
        local current_win = vim.fn.win_getid()
        -- Instead of sending lines individually, concatenate them.
        local main_lines = vim.api.nvim_buf_get_lines(ev.buf, 0, -2, false)
        local content = table.concat(main_lines, '\r\n')
        vim.api.nvim_chan_send(term_io, content .. '\r\n')

        -- Process the last line separately (without trailing \r\n)
        local last_line = vim.api.nvim_buf_get_lines(ev.buf, -2, -1, false)[1]
        if last_line then
            vim.api.nvim_chan_send(term_io, last_line)
        end
        vim.api.nvim_win_set_buf(current_win, term_buf)
        vim.api.nvim_buf_delete(ev.buf, { force = true } )
        -- Use vim.defer_fn to make sure the terminal has time to process the content and the buffer is ready.
        vim.defer_fn(setCursor, 10)
    end
  })
end

On my kitty.conf:

scrollback_pager nvim -u NONE -R -M -c 'lua require("kitty+page")(INPUT_LINE_NUMBER)' -

@amosbird
Copy link

This solution breaks long lines into multiple actual lines in the buffer, rather than using proper soft wrapping in Neovim. As a result, copying long lines becomes problematic.

@peterjumper
Copy link

Does anyone know how to kitty_mod+g (less the previous command) work when using tmux ? It seems impossible to do that due to kitty limitation. are there any alternative method to quick look back the top of previous command? Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment