Get unique list of buffers, odd behaviour after update

Let me preface this by saying that everything worked just fine until the most recent lspconfig update.

After that, I have noticed that vim.api.nvim_list_bufs() in the snippet below ends up adding duplicated values to the bufs_fn_len.

Well, until the bug (?) is fixed, let’s add a check and insert values until if those aren’t already present in the table. Turns out, this is not as straightforward as it sounds. Check the example below:

local bufs_fn_len = {}
for _, buf_nr in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_valid(buf_nr) then
    local buf = vim.api.nvim_buf_get_name(buf_nr)
    local buf_nm = vim.fn.expand(buf)
    if buf_nm ~= '' then
        buf_nm = vim.fn.substitute(buf_nm, vim.fn.getcwd(), '', 'g')
        buf_nm = vim.fn.substitute(buf_nm, vim.fn.expand('$HOME'), '~', 'g')
        buf_nm = vim.fs.basename(buf_nm)
        buf_nm = string.len(buf_nm)
        if (not bufs_fn_len[buf_nm]) then
            table.insert(bufs_fn_len, buf_nm)
            vim.print('Adding buf ' .. buf_nm)
            vim.print('Content: '.. table.concat(bufs_fn_len, '; '))
        end
    end
end

On my end this results in:

Adding buf 11
Content: 11
Adding buf 13
Content: 11; 13
Adding buf 12
Content: 11; 13; 12
Adding buf 9
Content: 11; 13; 12; 9
Adding buf 8
Content: 11; 13; 12; 9; 8
Adding buf 13
Content: 11; 13; 12; 9; 8; 13
Adding buf 8
Content: 11; 13; 12; 9; 8; 13; 8
Adding buf 10
Content: 11; 13; 12; 9; 8; 13; 8; 10
Adding buf 11
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11
Adding buf 11
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11; 11
Adding buf 14
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11; 11; 14
Adding buf 17
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11; 11; 14; 17
Adding buf 16
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11; 11; 14; 17; 16
Adding buf 15
Content: 11; 13; 12; 9; 8; 13; 8; 10; 11; 11; 14; 17; 16; 15

Double-checking that the issue is coming directly from nvim_list_bufs()

:lua vim.print(vim.api.nvim_list_bufs())

returns:

{ 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 }

Two questions

  1. Why vim.api.nvim_list_bufs() has duplicated values in the first place?
  2. Why the if (not bufs_fn_len[buf_nm]) then conditional has no effect?

I can’t reproduce the duplication behaviour, at least quickly right now with markdown buffers. I use a wrapper around vim.api.nvim_list_bufs() in this plugin but only to list terminals it looks like, I chose list_bufs() instead to implement the fuzzy buffer switcher. Maybe something in there is useful.

For the other part, that’s just how lua works:

Lua 5.3.6  Copyright (C) 1994-2020 Lua.org, PUC-Rio
> not nil
true
> a = { 42 }
> a[42]
nil

You should use vim.tbl_contains().

Could you please try the following:

  1. cd to ~/.config/nvim
  2. Create test_config.lua, make sure that the path in the first line points to the right location.
vim.opt.rtp:append("/home/user/.local/share/nvim/lazy/nvim-lspconfig")

local lspconfig = require("lspconfig")
library = vim.api.nvim_get_runtime_file("", true)
lspconfig.lua_ls.setup({})

ListValidBuffers = function()
    local bufs_fn_len = {}
    for _, buf_nr in ipairs(vim.api.nvim_list_bufs()) do
        if vim.api.nvim_buf_is_valid(buf_nr) then
            local buf = vim.api.nvim_buf_get_name(buf_nr)
            local buf_nm = vim.fn.expand(buf)
            if buf_nm ~= '' then
                buf_nm = vim.fn.substitute(buf_nm, vim.fn.getcwd(), '', 'g')
                buf_nm = vim.fn.substitute(buf_nm, vim.fn.expand('$HOME'), '~', 'g')
                buf_nm = vim.fs.basename(buf_nm)
                local buf_len = string.len(buf_nm)
                if (not bufs_fn_len[buf_len]) then
                    table.insert(bufs_fn_len, buf_len)
                    vim.print('Adding buffer ' .. buf_nm .. ', length ' .. buf_len)
                    vim.print('Content: '.. table.concat(bufs_fn_len, '; '))
                end
            end
        end
    end
end
  1. Execute nvim --clean -u test_config.lua -- test_config.lua
  2. Call :lua ListValidBuffers() and check if it picks-up other *.lua files.

Ref. `vim.api.nvim_list_bufs()` seems to be affected after the update · Issue #3161 · neovim/nvim-lspconfig · GitHub

I tried as you suggested but ListValidBuffers() only shows:

Adding buffer test_config.lua, length 15
Content: 15

There is also my own init.lua in this directory, and it is not being shown. I have neovim version 0.10 and lua language server version 3.9.1. It seems like a tricky one, the only thing I modified in test_config.lua from the above was the first line, to include my own username and also point to the correct lsp-config path since I use pckr not lazy.nvim. It might be worth trying on a different computer with your lsp-config setup or deleting lsp-config and re-installing.

In the meantime, I still recommend switching to vim.tbl_contains for your conditional check. Alternatively, This is my implementation for buffer listings, copied out of that plugin mentioned above, might be helpful. Although, if your nvim_list_bufs keeps returning duplicates this will not solve that.

-- Get list of open ("listed", or "loaded" if all is true) buffer IDs.
function Quark.list_bufs(all)
    local bufs = {}
    for i, buf in ipairs(api.nvim_list_bufs()) do
        if api.nvim_buf_is_loaded(buf) then
            if all then
                bufs[i] = buf
            else
                if vim.bo[buf].buflisted then
                    table.insert(bufs, buf)
                end
            end
        end
    end
    return bufs
end

-- Get list of open ("listed", or "loaded" if all is true) buffer names.
function Quark.list_buf_names(all)
    local buffer_names = {}
    for _, buf in pairs(Quark.list_bufs(all)) do
        table.insert(buffer_names, api.nvim_buf_get_name(buf))
    end
    return buffer_names
end

@adigitoleo , thank you for testing it.

if api.nvim_buf_is_loaded(buf) then

Would filter-out buffers that haven’t been seen since re-opening the session, wouldn’t it?

Either way, I am at a loss here. Using pre-built nVim from the official repository:

NVIM v0.10.0
Build type: Release
LuaJIT 2.1.1713484068

and the same language server as you do:

$ lua-language-server --version
3.9.1

Time to dig deeper…

  1. I have moved the relevant bits to /tmp: lua_ls, nvim_lspconfig and test.lua.

  2. Tried using a brand new user profile from TTY. It works fine!
    By this stage it’s clear there is something in my own profile that triggers this bug.

  3. Clean Bash session:

    $ env -i bash --norc --noprofile
    $ env                                                                                                                                                                                                   
    PWD=/tmp
    SHLVL=1
    PATH=/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:.:/tmp/lua_ls/bin/
    OLDPWD=/tmp
    _=/usr/bin/env
    

    No luck, ListValidBuffers() returns all .lua files under $PWD which is now /tmp.

  4. Removed .bashrc, .profile and .inputrc; same.

  5. Switched to X11 from Wayland, Sway → XFCE. No difference.

  6. Temporarily moved ~/.config and ~/.local outside the $HOME. Nothing :frowning:

Any new, revolutionary ideas apart from the “your username is cursed” ?..

Would filter-out buffers that haven’t been seen since re-opening the session, wouldn’t it?

Yes, you’re right. So this would mask your issue, but not solve it.

It seems like you’ve tried most of the logical things, and I’m not a lspconfig dev so it will be tricky for me to give a lot more help. On my system, I also have a socket at /var/run/user/$UID/nvim.XXXXX.X (where the X are random numbers). But I think that is related to nvim RPC, so probably not relevant. Because the issue seems to come from the lua LSP config, you could also check :checkhealth lsp and the LSP debug log with :=vim.lsp.set_log_level("debug") (the log file can be opened with :LspLog).

I’ve just realised as well that the recommended lua_ls snippet in lspconfig has changed in neovim v0.10, so this whole LSP system still is a bit of a moving target. Maybe if you are able to get some debug logs then they will take you more seriously in that github issue.

Nothing of particular interest in the logs or :checkhealth.

That being said, I have solved this by checking the buflisted option. Here is the working function:

local bufs_fn_len = {}

for _, buf_nr in ipairs(vim.api.nvim_list_bufs()) do
    if vim.api.nvim_buf_is_valid(buf_nr) then
        local buf_listed = vim.api.nvim_get_option_value('buflisted', { buf = buf_nr })
        local buf_nm = vim.api.nvim_buf_get_name(buf_nr)
        buf_nm = vim.fn.expand(buf_nm)
        if buf_listed then
            if buf_nm ~= '' then
                buf_nm = vim.fn.substitute(buf_nm, vim.fn.getcwd(), '', 'g')
                buf_nm = vim.fn.substitute(buf_nm, vim.fn.expand('$HOME'), '~', 'g')
                buf_nm = vim.fs.basename(buf_nm)
                buf_len = string.len(buf_nm)
                table.insert(bufs_fn_len, buf_len)
            end
        end
    end
end

\o/

1 Like