fzf-lua icon indicating copy to clipboard operation
fzf-lua copied to clipboard

Feature: Re-use focused window for displaying results live instead of a preview window

Open enoryw opened this issue 11 months ago • 16 comments

Have you RTFM'd?

  • [x] I have done proper research

Feature Request

Feature: Re-use focused window for displaying results live instead of a preview window The idea is to feel like vim's / by interacting with the existing buffer. This brief demo of Emacs's Swiper package best conveys this and provides the best UI (IMO, since it ditches floating windows which are self-limiting): it is simply opens a prompt at the bottom of the screen. For file/buffer-related pickers where the preview is a file, re-use window to display the "preview" and for other pickers like a help picker where it makes sense, create a split window and display its previews there. When cancelling the picker, the window layout and buffer position is restored if it was changed, else selecting the item would simply close the picker because the contents are already inserted into the window.


Motivation: it feels natural and part of vim's UI to interact with the existing buffer you're searching on. It avoids short-lived floating windows for preview which is not space-efficient. Re-using windows respects window options for consistent viewing experience. The following fzf-lua options resemble Swiper's (Ivy) UI (snacks picker looks exactly like Swiper's UI and even preserves options like number, relativenumber, and gitsigns by default so it's close):

  winopts = {
    fullscreen = true,
    border = "none",
    preview = {
      border = "none",
      vertical = "up:70%",
      layout = "vertical",
    },
  },

This gives the feeling of re-using the buffer; however, a preview window in place of the position of the existing buffer still has problems, the second being the main issue:

  • Contents of the preview window does not match existing buffer's position for blines and pickers that begin searching from the current buffer (it always starts from top of the preview of a file). This is only relevant when the search result is empty or if results are already visible on the screen (imagine using vim's /, to search for a string already visible on the screen--it just gets highlighted and the position of the buffer stays the same) since it gets replaced as you search. When I work with Swiper in Emacs, this is a nice touch that makes it feel like the plugin is fully integrated into the editor.

  • There's a flicker that happens and is unavoidable when a preview window is created--this is evident when it replaces the existing buffer is on the screen with the intention of appearing as if it's the existing buffer (the preview would take up the majority of the space so nearly the screen flashes). I see @folke saying there should be no flickering (I'm not the OP of that thread but this feature request is raised having shared similar thoughts with OP)--I guess what's misunderstood is that in Swiper, there is no preview generated since it's acting on the existing buffer so there is no flicker at all. There's no flicker at all when using vim's / or Swiper because they work with the existing window.

I actually found that with both of these issues combined, it's disorientating to use existing pickers in Neovim when they are configured in fullscreen to replicate Swiper's UI for maximum space efficiency because the illusion of re-using the window for the buffer is visibly distracting with the nearly full screen flicker. I would rather settle with floating windows if the FR is not possible because of these issues. I assume this might be why Neovim pickers tend to default to heavy use of floating windows even for pickers that would benefit from fullscreen previews (files, buffers, help pickers, etc.).


EDIT: Made some adjustments in language to try to be more technically correct regarding windows and buffers.

enoryw avatar Jan 24 '25 03:01 enoryw

Ty for openin this @enoryw, this seems to only make sense for blines ano grep_curbuf, otherwise we will eventually have to swirch away from the current buffer anyways.

How does swiper deal with results outside the current buffer?

ibhagwan avatar Jan 24 '25 04:01 ibhagwan

The window for the buffer that was focused when Swiper is launched will be re-used to show all the results for all pickers where it makes sense (files, buffers, etc.). It feels natural because you can cancel the picker and restore the previous state or accept and it will just close the picker because the buffer is already in the window. For pickers like a help picker, it opens a split and shows its hovered result in that window [https://www.youtube.com/watch?v=QcPenVmmQyM&t=147s](timestamped demo because I nuked my Emacs config). For all these pickers, you use C-p/C-n to select prev/next without previewing, and hold the Alt key if you want the preview for that item (or just default to previewing and hold for non-previewing)--this is a nice QoL behavior to quickly selectively disable previewing if you know the file is big or don't care for previewing a subset of results and is more convenient than toggling preview on/off (which can also be done, of course).

There's no real benefit functionality-wise to the feature request but describing it in words don't really do it justice how integrated the Swiper is integrated with Emacs simply by using a normal window for its results live, i.e. no flickering of the whole screen because it doesn't involve giant floating windows (happens when fzf-lua/telescope/snacks picker's layouts try to resemble Swiper's space-efficient Ivy-style UI). Re-uses the window where it makes sense or do a split and re-use that otherwise for all the results as they are hovered. Cancel the picker to restore previous window layout if it was changed as well as buffer position or select the item and the prompt simply exits because the insertion of its contents to the window was already done live (with existing window options respected. i.e. "WYSIWYG". A window actually acting like a window for different buffers as opposed to short-lived floating windows).


P.S. Unrelated to this FR: for grep and friends, Swiper also highlights the matching parts of the string in the live buffer like vim's / (rg is is used for searching and applies to all files in the results, not just those from open buffers), not just in fzf's item results (this is a whole separate FR which I'm also not sure if it's worth the effort to implement. I saw the FR was brought up to telescope but nothing came of it along with a FR essentially the same as the raised here).

enoryw avatar Jan 24 '25 08:01 enoryw

I prefer vscode Go to Symbol in Editor feature instead of current preview solution. Do not need to shift eyes after selected and auto jump back to last location if press esc

y1rn avatar Mar 05 '25 03:03 y1rn

I too am interested in this feature. Coming from Emacs to Neovim I don't understand the fascination with floating and preview windows when the using the existing buffer to display the results feels more integrated with the editor with no overlapping elements responsible for the flickering issue.

rieje avatar Mar 20 '25 07:03 rieje

I think this mimic swiper behavior? TODO: need to correctly set cursor highlight

local ns = api.nvim_create_namespace('blines')
require('fzf-lua').blines {
  winopts = function()
    return {
      split = ('botright %snew +set\\ nobl'):format(math.ceil(vim.o.lines / 4)),
      preview = { hidden = true },
    }
  end,
  actions = {
    focus = {
      fn = function(sel, opts)
        local query = sel[1]
        if not query or not sel[2] then return end
        local entry = require('fzf-lua.path').entry_to_file(sel[2], opts)
        local ctx = require('fzf-lua.utils').CTX()
        api.nvim_win_set_buf(ctx.winid, ctx.bufnr)
        api.nvim_win_set_cursor(ctx.winid, { entry.line, entry.col })
        vim.api.nvim_buf_clear_namespace(ctx.bufnr, ns, 0, -1)
        vim.hl.range(
          ctx.bufnr,
          ns,
          'Search',
          { entry.line, entry.col },
          { entry.line, entry.col },
          {}
        )
      end,
      field_index = '{q} {}',
      exec_silent = true,
    },
  },
}

phanen avatar Mar 20 '25 10:03 phanen

Ty for openin this @enoryw, this seems to only make sense for blines ano grep_curbuf, otherwise we will eventually have to swirch away from the current buffer anyways.

How does swiper deal with results outside the current buffer?

As of now, currently the Snacks.picker.lines() does the described behavior, which I think is useful, as you said mainly to grep the current buffer:

Image

My best effort right now is just having it in the floating window with vertical layout:

Image

Although having the file path next to each line in the bottom is kind of useless and a bit annoying since I already have the file path in the title of the preview window. I don't know if I can get rid of that 🤔 I will keep exploring.

carloscalla avatar Apr 25 '25 16:04 carloscalla

As of now, currently the Snacks.picker.lines() does the described behavior, which I think is useful, as you said mainly to grep the current buffer: My best effort right now is just having it in the floating window with vertical layout:

You can have a layout similar to snacks with the “ivy” profile, OP request is not just the layout, it’s also about reusing the buffer to avoid the cosmetic “flash” due to opening a new float on top of the current buffer.

Try :FzfLua blines profile=ivy

Image

If you want to customize this further see how the settings used by the ivy profile: https://github.com/ibhagwan/fzf-lua/blob/18ac8df2e878d4975ad9b316aceeb673f7975a2b/lua/fzf-lua/profiles/ivy.lua#L43-L53

ibhagwan avatar Apr 25 '25 16:04 ibhagwan

You're right. I also support OP's request. Not only regarding the flick but having it in the current buffer also helps when working with splits.

carloscalla avatar Apr 25 '25 16:04 carloscalla

You're right. I also support OP's request. Not only regarding the flick but having it in the current buffer also helps when having working with splits.

I respect the request that’s why I left it open, but TBH it’s totally in the “nice to have” category :)

ibhagwan avatar Apr 25 '25 17:04 ibhagwan

TODO: need to correctly set cursor highlight

A better implement: https://github.com/phanen/fzf-lua-overlay/blob/265329c066cfd8003d2acec11f3a1ebe8b4d8cb0/lua/fzf-lua-extra/providers/swiper_blines.lua.

phanen avatar Jun 05 '25 12:06 phanen

hi @phanen how can I test this out? I added your repo with the extras but I can't seem to find a way to load the extra pickers. I'd appreciate your help 🙏

carloscalla avatar Jun 05 '25 13:06 carloscalla

require('fzf-lua-extra').swiper_blines()

Or you can just copy the function out to a tmpfile then source it.

phanen avatar Jun 05 '25 13:06 phanen

@phanen, if your plugin is considered “stable” and you’re happy with the implementation we can close this issue and refer users to https://github.com/phanen/fzf-lua-extra, what do you think?

If you strongly believe the code should be part of this repo we can also PR it, your call.

ibhagwan avatar Jun 06 '25 15:06 ibhagwan

That's ok. But i think this issue is more about another preview layout (to split a new fzf win but still reuse the origin win if possible). Corrently previewer is always a float win (and implictly placed left/bottom of fzf win even if fzf win is splited. So it seems also impossible to get a bqf's up+down layout)

swiper_blines just set cursor hl more correctly (can also be used in live_grep if possible). But for float window we need find its grid id first from winid (seems no way to achieve it unless ffi).

phanen avatar Jun 07 '25 02:06 phanen

I think the Snacks picker layout ivy_split achieves this: https://github.com/folke/snacks.nvim/blob/main/docs/picker.md#ivy_split

I recently tried this out and you can use it for any picker. It's set by default to the lines picker but not limited to it.

You can try it for files for example by doing:

:lua Snacks.picker.files({layout={preset="ivy_split"}})

Or with any other picker.

I believe that would be the goal of this issue, but I understand it's not a priority as mentioned by the author before.

carloscalla avatar Jun 07 '25 08:06 carloscalla

So it seems also impossible to get a bqf's up+down layout)

Actually it's supported. tabnew to get rid of window separator...

require('fzf-lua').files({
  winopts = {
    split = 'tabnew',
    preview = {
      layout = 'vertical',
      vertical = 'up:80%',
      border = 'none',
    },
  },
})

phanen avatar Jun 07 '25 11:06 phanen

I've been using split = 'tabnew +set\\ nobl' for a long time. The only issue I've encountered is that every time I jump to a file, FzfWin:close gets called, and the line vim.api.nvim_set_current_win(self.src_winid) causes the focus to switch to the wrong window. As a result, I have to modify the code to skip that line in order to make it work properly.

Aiyane avatar Jul 30 '25 18:07 Aiyane

I've been using split = 'tabnew +set\\ nobl' for a long time. The only issue I've encountered is that every time I jump to a file, FzfWin:close gets called, and the line vim.api.nvim_set_current_win(self.src_winid) causes the focus to switch to the wrong window. As a result, I have to modify the code to skip that line in order to make it work properly.

Just tried and I don’t have this issue, maybe I’m misunderstanding “jump to a file”? If you don’t want fzf-lua to return to the origin window where would you like it to jump to?

ibhagwan avatar Jul 30 '25 19:07 ibhagwan

@ibhagwan Sorry, I didn’t explain it clearly. This issue actually only happens in some edge cases. When you open a file in a new tabpage and then trigger a jump1 to jump to a definition, the target file ends up opening in the previous tabpage. But if you don’t jump directly and instead preview the result first, the issue doesn’t occur.

Aiyane avatar Jul 31 '25 02:07 Aiyane

@ibhagwan Sorry, I didn’t explain it clearly. This issue actually only happens in some edge cases. When you open a file in a new tabpage and then trigger a jump1 to jump to a definition, the target file ends up opening in the previous tabpage. But if you don’t jump directly and instead preview the result first, the issue doesn’t occur.

@Aiyane, when using jump1 the interface never opens if you're since the defaults use async=false, instead vim.lsp.util.show_document is called directly, try :FzfLua lsp_definitions async=true maybe it will help as winopts.split will be called in this case.

ibhagwan avatar Jul 31 '25 07:07 ibhagwan

So it seems also impossible to get a bqf's up+down layout)

Actually it's supported. tabnew to get rid of window separator...

require('fzf-lua').files({ winopts = { split = 'tabnew', preview = { layout = 'vertical', vertical = 'up:80%', border = 'none', }, }, })

After last week’s commits, this is now better supported with enew (gets rid of the tab header):

require('fzf-lua').files({
  winopts = {
    split = 'enew',
    preview = {
      layout = 'vertical',
      vertical = 'up:80%',
      border = 'none',
    },
  },
})

Image

Note that it’s still not perfect, due to enew replacing the current win buffer some pickers need small adjustments (which I’m slowly fixing), issues I’ve noticed:

  1. Tabs missing current win buf
  2. Opening blines from a man page
  3. Lsp document symbols with async=true

ibhagwan avatar Aug 09 '25 17:08 ibhagwan

Did some more thinking about this, reusing the focused window only makes a difference when the picker is a "current buffer" picker which is currently only blines and git_blame (I may be forgetting another picker, but that's an easy fix).

For the rest of the pickers which aren't "current buffer" we aren't really avoiding any cosmetic flashes as we have to reload the preview buffer anyways so I find the current approach sufficient to close this issue.

Big thanks to @phanen, I've copied a modified version of the code into the "ivy" profile and enabled it by default for the blines and git_blame pickers and started to use it in my own config to track any potential issues.

Note it's not required to enable the ivy profile globally to use this as it's possible to set the profile per picker, equally as important, combining the profiles so you can get a superior resume with the "hide" profile (which is what I use):

:FzfLua blines profile=ivy
-- combined with "hide" profile
:FzfLua blines profile={"ivy","hide"}
-- lua version:
:lua FzfLua.git_blame({ profile={"ivy","hide"} })

ibhagwan avatar Aug 10 '25 03:08 ibhagwan