nvim-lualine/lualine.nvim

Bug: (my code, looking for recommendation) cursor doesn't move as expected between rows if using a `cond` for showing the `buffers` component in tabline

Amar1729 opened this issue · 3 comments

Self Checks

  • I'm using the latest lualine. (commit 2248ef25)
  • I didn't find the issue in existing issues or PRs.
  • macOS, neovim installed by bob
$ nvim --version
NVIM v0.10.0-dev-594ff34
Build type: RelWithDebInfo
LuaJIT 2.1.1696795921
Run "nvim -V1 -v" for more info

How to reproduce the problem

Expected behaviour

When moving the cursor in vim between rows, the colum position is kept if no horizontal movements have been made. e.g. when pressing j twice in the following lines, the cursor position would be represented by |:

long line with cursor near |end
short|
a longer line where the cur|sor will be in the middle

Actual behaviour

Instead, the cursor falls back to the most-left column where there is text.

long line with cursor near |end
shor|t
a lo|nger line where the cursor will be in the middle

Minimal config to reproduce the issue

call plug#begin("/private/tmp/lualine-dbg/.local/share/nvim/plugged")
  Plug 'nvim-lualine/lualine.nvim'
call plug#end()

lua << END
local has_multiple_buffers = function ()
  local len = 0
  for buffer = 1, vim.fn.bufnr('$') do
    if vim.fn.buflisted(buffer) == 1 then
      len = len + 1

      -- early exit if we have more than 1 buffer
      if len > 1 then
        break
      end
    end
  end

  -- i'm sure it's inefficient to set this on every check
  -- is there a better way to re-hide the tabline if i close out buffers?
  if len < 2 then
    vim.opt.showtabline = 1
    return false
  else
    vim.opt.showtabline = 2
    return true
  end
end

-- Your lua part of config goes here
require("lualine").setup {
  tabline = {
    lualine_a = {
      {
        -- show buffers if we have > 1
        'buffers',
        -- default max length (2/3) too short
        max_length = function () return vim.o.columns * 95 / 100 end,
        cond = has_multiple_buffers,
      },
    },
  },
}
END

Additional information

Sorry for logging this issue - it's more that i'm looking for advice in something i might have missed. it seems that the broken code is my function has_multiple_buffers; you'll notice if you comment out cond = has_multiple_buffers the cursor issue isn't present. If i change it to a simple cond = function () return false end movement works as expected, so i'm somewhat convinced the error(?) is in my function. Am i doing something obviously wrong in that function? is there a way i could make it better? (the point is that i only want to show the tabline at all if i have more than one buffer open)

Thanks. feel free to close if you don't want to spend maintainer time on a this.

buffer numbers are not in ordered eg. u can have buffer 1 and buffer 50,only tabs are in order.
and u can hide the tab line outside of the component for performance.

      local autocmd = vim.api.nvim_create_autocmd
      autocmd({ "BufDelete","BufNew"}, {
         pattern = { "*" },
         callback = function(ev)
            local tab_count = vim.fn.tabpagenr('$')
            local buflist = {}
            for i = 1, tab_count, 1 do
               buflist = { unpack(buflist), unpack(vim.fn.tabpagebuflist(i)) }
            end
            local len = 0
            for _,buf in pairs(buflist) do
               if vim.fn.buflisted(buf) then
                  len = len + 1
               end
               if len > 1 then
                  vim.o.showtabline = 2
                  return
               end
            end
            vim.o.showtabline = 0
         end
      })

hmm, interesting. For some reason, your solution doesn't seem to work for me: even though :h tabpagebuflist indicates a similar snippet to "get a list of all buffers in all tabs", i end up with a buflist that does not have all listed buffers in certain situations.

I've gone with a slightly modified autocmd solution which (mostly) works. There's a bit of an edge case sometimes when i close all but the last buffer, since BufDelete happens before a buffer gets deleted. Not sure why showtabline doesn't correctly get set during the following proc of the BufEnter event, but ... 🤷

local has_multiple_buffers = function ()
  local found = false
  for buffer = 1, vim.fn.bufnr('$') do
    if vim.fn.buflisted(buffer) == 1 then
      -- early exit if we've already found a buffer (i.e. have more than 1)
      if found == true then return found end
      found = true
    end
  end
  return false
end

local set_tabline = function ()
  if has_multiple_buffers() then
    vim.opt.showtabline = 2
  else
    vim.opt.showtabline = 1
  end
end

-- TODO: for some reason, the bufenter event doesn't seem to happen until after a buffer
-- gets unloaded (???). so, the tabline won't go away until after some other buffer event
-- happens, eg a help page or a command
-- hacky solution for now by also proccing this cmd on CmdlineEnter, and i just type ':<Esc>'
-- after closing buffers down to only 1.
--
-- also, it seems like cmp-cmd is a buffer, so can i filter on stuff like that?
-- i guess it doesn't really matter cause that's not a visible buffer
-- but it causes this autocmd to run more often, which may be unwanted
vim.api.nvim_create_autocmd({
  -- events that should hide tabline
  "CmdlineEnter", "BufEnter", "BufDelete",
  -- events that might create multiple buffers (show tabline)
  "VimEnter", "BufNew",
}, {
  pattern = "*",
  callback = set_tabline,
})

require("lualine").setup {
  tabline = {
    lualine_a = {
      {
        -- show buffers if we have > 1
        'buffers',
        -- default max length (2/3) too short
        max_length = function () return vim.o.columns * 95 / 100 end,
        cond = has_multiple_buffers,
      },
    },
  },
}

the issues was tabpagebuflist can return the same buffer twice,
i updated the code to only use unique buffers and not count floating window's buffers.
i used fmt to ignore the autocmd headaches.

require("lualine").setup {
  tabline = {
    lualine_a = {
      {
        ...
        fmt = function(name)
          local tab_count = vim.fn.tabpagenr('$')
          local winlist
          for i = 1, tab_count, 1 do
            if winlist ~= nil then
              winlist = { unpack(winlist), unpack(vim.fn.nvim_tabpage_list_wins(i)) }
            else
              winlist = vim.api.nvim_tabpage_list_wins(i)
            end
          end
          local buflist = {}
          for _, win in pairs(winlist) do
            local is_float = vim.api.nvim_win_get_config(win).relative ~= ""
            if not is_float then
              local buf = vim.api.nvim_win_get_buf(win)
              if vim.fn.buflisted(buf) then
                buflist[#buflist + 1] = buf
              end
            end
          end
          buflist = vim.fn.uniq(buflist)
          if #buflist > 1 then
            vim.o.showtabline = 2
          else
            vim.o.showtabline = 0
          end
          return name
        end
      },
    },
  },
}