linrongbin16/fzfx.nvim

Automatically resize buffer selection window

Closed this issue · 10 comments

Buffer names are usually comparatively short and don't require a preview.

I have implemented it via fzf.vim ages ago:

function! FZFBufFloat() abort
    let l:blengths = []
    for l:buffer in getbufinfo({'buflisted': 1})
        if empty(l:buffer.name)
            continue
        endif
        let l:buffer = substitute(shellescape(l:buffer.name),
              \ getcwd()."/", "", "")
        let l:buffer = substitute(l:buffer, $HOME, "~", "")
        call add(l:blengths, strchars(l:buffer))
    endfor
    return { 'width': max(l:blengths)+17, 'height':
          \ len(l:blengths)+6, 'yoffset': 0.5, 'border': 'rounded' }
endfunction

command! -bang -nargs=? -complete=dir Buffers call
      \ fzf#vim#buffers(<q-args>, {'options': '--padding=1',
      \ 'window': FZFBufFloat()}, <bang>0)

Would it be possible to have something like this in fzfx.nvim in Lua?

hi @savchenko ,

The code looks like doing below things:

  1. add --padding=1 option to fzf binary.
  2. adjust width, height, border, rounded options for --preview-window option.

If all the code is going to setting the options directly pass to fzf binary, then it could be done via the fzf_opts option (the only difference is written by lua), please see:

  1. FzfxBuffers command default fzf_opts option:
    M.fzf_opts = {
    consts.FZF_OPTS.MULTI,
    { "--prompt", "Buffers > " },
    function()
    local current_bufnr = vim.api.nvim_get_current_buf()
    return nvims.buf_is_valid(current_bufnr) and "--header-lines=1" or nil
    end,
    }

To configure the fzf_opts for FzfxBuffers, please use:

require('fzfx').setup({
    buffers = {
        fzf_opts = {
            -- please note, this configured `fzf_opts` will override the default configs.
            -- so you may want to also add the builtin options.
            "--multi",
            { "--prompt", "Buffers > " },
            function()
                local current_bufnr = vim.api.nvim_get_current_buf()
                return nvims.buf_is_valid(current_bufnr) and "--header-lines=1" or nil
            end,

            -- add more options here: height, width, rounded, etc.
        },
    },
})

After further investigation, this works:

buffers = {
  fzf_opts = {
    "--info=hidden --header= --preview-window=hidden --padding=1"
  },
  win_opts = {
        relative = 'win',
        height = 10,
        width = 40,
        border = "rounded",
        zindex = 51,
    }
}

However, this does not:

buffers = {
  fzf_opts = {
    "--info=hidden --header= --preview-window=hidden --padding=1"
  },
  win_opts = function()
    local blengths = {}
    local max_length = 0
    for _, buffer_number in ipairs(vim.api.nvim_list_bufs()) do
      local buffer_object = vim.api.nvim_buf_get_name(buffer_number)
      local buffer_name = vim.fn.expand(buffer_object)
      buffer_name = vim.fn.substitute(buffer_name, vim.fn.getcwd(), "", "g")
      buffer_name = vim.fn.substitute(buffer_name, vim.fn.expand("$HOME"), "~", "g")
      blengths[#blengths + 1] = vim.fn.strwidth(buffer_name)
    end
    for _, length in ipairs(blengths) do
      max_length = math.max(max_length, length)
    end
    return {
      border = "rounded",
      height = #blengths + 4,
      width = max_length + 4,
      relative = 'win',
      zindex = 51,
    }
  end
}

Any idea why? The function returns correctly.

@savchenko, because 'win_opts' doesn't support function.

but it looks like function is a good idea.

@linrongbin16, rather vital I'd say. Would allow changing window size / position based on context. Buffer switching is one of the examples.

After further investigation, this works:

buffers = {
  fzf_opts = {
    "--info=hidden --header= --preview-window=hidden --padding=1"
  },
  win_opts = {
        relative = 'win',
        height = 10,
        width = 40,
        border = "rounded",
        zindex = 51,
    }
}

However, this does not:

buffers = {
  fzf_opts = {
    "--info=hidden --header= --preview-window=hidden --padding=1"
  },
  win_opts = function()
    local blengths = {}
    local max_length = 0
    for _, buffer_number in ipairs(vim.api.nvim_list_bufs()) do
      local buffer_object = vim.api.nvim_buf_get_name(buffer_number)
      local buffer_name = vim.fn.expand(buffer_object)
      buffer_name = vim.fn.substitute(buffer_name, vim.fn.getcwd(), "", "g")
      buffer_name = vim.fn.substitute(buffer_name, vim.fn.expand("$HOME"), "~", "g")
      blengths[#blengths + 1] = vim.fn.strwidth(buffer_name)
    end
    for _, length in ipairs(blengths) do
      max_length = math.max(max_length, length)
    end
    return {
      border = "rounded",
      height = #blengths + 4,
      width = max_length + 4,
      relative = 'win',
      zindex = 51,
    }
  end
}

Any idea why? The function returns correctly.

hi @savchenko,

After carefully read your win_opts function, I think it's trying to calculate current buffer list size (for example 8), and max buffer name length (for example 36). Then returns the 8 as the fzfx popup window's height, 36 as the fzfx popup window's width.

Is that correct?

@linrongbin16 , it returns a table with width and height set to maximum length of the buffer name and number of buffers respectively.

+4 is just for testing/safety to make sure we're not kissing the window border, likely will need to be re-adjusted based on the actual behaviour. As you can see in the original fzf.vim function, it was empirically set to +17/+6 and it rendered perfectly sized every time.

hi @savchenko, current engine doesn't support real-time refresh the 'win_opts'.

for example, when 1st run FzfxBuffers, the height is 8, max buffer name length is 36. (we have 8 buffers, and max buffer name length is 36).

in the 1st fzfx popup, if you press some 'ctrl-d' keys and delete some buffers, the 'win_opts' will not be calculate again and refresh the 1st fzfx popup.

but after you press esc key and quit 1st popup, then run the 2nd FzfxBuffers, now the height and width will be calculate by the current buffers status.

I will submit PR to support the 'win_opts' to be a function, but it still cannot support the real-time scenario when you press 'ctrl-d' key.

I will submit PR to support the 'win_opts' to be a function

This should be enough. I am not certain even if the original fzf.vim supports real-time updates.

hi @savchenko , this feature is added in #517 and #520 , please pull latest main branch and have a try.

@linrongbin16 , tested. Works well.

Here is the resulting function, please feel free to use in the base distribution if you'd like:

win_opts = function()
    local bufs_fn_len = {}
    local max_len = 0
    for _, buf_nr in ipairs(vim.api.nvim_list_bufs()) do
        if vim.api.nvim_buf_is_valid(buf_nr) and vim.api.nvim_buf_is_loaded(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")
                table.insert(bufs_fn_len, string.len(buf_nm))
            end
        end
    end
    for _, len in ipairs(bufs_fn_len) do
        max_len = math.max(max_len, len)
    end
    return {
        height = #bufs_fn_len + 4,
        width = max_len + 7,
        relative = 'win',
        zindex = 51,
    }
end

The only caveat is that I explicitly calculate loaded buffers, which might result in buffers returning more than we have accounted for. #522 would be fix this.