Skip to content

Instantly share code, notes, and snippets.

@galaxia4Eva
Last active November 12, 2024 10:53
Show Gist options
  • Save galaxia4Eva/9e91c4f275554b4bd844b6feece16b3d to your computer and use it in GitHub Desktop.
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)' -
# ...
@galaxia4Eva
Copy link
Author

@mikesmithgh it's, probably, an artifact from the previous version where term_io was a global variable. Good find! Thank you!

@jackielii
Copy link

@mikesmithgh thanks for the snippet! I tried to follow the original thread and get the set cursor position. I just couldn't get it to work: if the lines are within the number of lines of the window, it works. Otherwise it just reports cursor position outside of buffer...

@galaxia4Eva
Copy link
Author

Dear @jackielii,

From my observations, kitty sometimes replaces INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN with not incorrect values when there was output to stderr in one of the scripts (kitty and fish shell on MacBook)

Maybe there's a more precise way that would also account for stderr lines, but I'm not aware of it.

@galaxia4Eva
Copy link
Author

@mikesmithgh @jackielii @SmartFinn @Ssnnee

I have updated kitty+page.lua

now, while still problematic with codepages that take more bytes that display length, it should place cursor properly. Pay attention to detail:

  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,
  })

maybe you would like to remove vim.schedule(setCursor) on ModeChanged for *:t

@sohanglal
Copy link

Error detected while processing command line:
E5108: Error executing lua vim/_options.lua:0: E474: Invalid argument
stack traceback:
        [C]: in function 'nvim_set_option_value'
        vim/_options.lua: in function '_set'
        vim/_options.lua: in function '__newindex'
        /home/sl/.config/nvim/lua/kitty+page.lua:17: in function </home/sl/.config/nvim/lua/kitty+page.lua
:4>
        [string ":lua"]:1: in main chunk
Press ENTER or type command to continue

This error sometimes occurs when the output of the scollback buffer becomes rather large?! I don't know what is causing this. It seems to work great most of the time
Any pointers would be helpful thanks!

@sohanglal
Copy link

Solved it by handling the error when it occurs. I am using fish shell. This is the code:

return function(INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN)
  print('kitty sent:', INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN)

local function setOptions()
  vim.opt.encoding='utf-8'
  vim.opt.clipboard = 'unnamedplus'
  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
end
 -- Use pcall to execute setOptions and catch any errors
 local success, errorMessage = pcall(setOptions)

 -- If an error occurred, substitute INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN with 0,0,0
 if not success then
    -- print("Error setting options:", errorMessage)
    INPUT_LINE_NUMBER, CURSOR_LINE, CURSOR_COLUMN = 0, 0, 0
 end
  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', 'i', '<Cmd>q<CR>', { })
  vim.api.nvim_buf_set_keymap(term_buf, 'n', 'q', '<Nop>', { })
  -- 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()
      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

@mikesmithgh
Copy link

FYI If anyone is interested. I developed the plugin kitty-scrollback.nvim to navigate your Kitty scrollback buffer to quickly search, copy, and execute commands in Neovim. Feel free to check it out 😺!

@yusufshalaby
Copy link

yusufshalaby commented May 20, 2024

I'm using this gist but I'm finding that if the setCursor function is called in the VimEnter autocommand callback it doesnt work properly because the buffer isn't fully written when we try to set the cursor. INPUT_LINE_NUMBER is usually greater than the number of lines in the buffer at that point resulting in the cursor being placed at the very bottom of the buffer.

My hacky solution was to take out the vim.schedule(setCursor) line from the VimEnter callback and put vim.defer_fn(setCursor, 10) at the bottom of the returned function. I basically want to synchronously invoke setCursor AFTER the VimEnter autocommand is complete, and 10ms seems to be enough time for the VimEnter autocommand to finish writing the contents of the buffer. Has anyone else has encountered this? Looking for some guidance on a less hacky approach.

@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)' -

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