ibhagwan / fzf-lua

Improved fzf.vim written in lua
GNU Affero General Public License v3.0
2.16k stars 143 forks source link

Formatting the path of the results when searching for files or using grep #1051

Closed talovski closed 6 months ago

talovski commented 6 months ago

Info

fzf-lua configuration ```lua local actions = require("fzf-lua.actions") require("fzf-lua").setup({ actions = { files = { ["default"] = actions.file_edit, ["ctrl-_"] = actions.file_split, ["ctrl-v"] = actions.file_vsplit, ["ctrl-t"] = actions.file_tabedit, ["alt-q"] = actions.file_sel_to_qf, ["alt-l"] = actions.file_sel_to_ll, }, grep = { -- this action toggles between 'grep' and 'live_grep' ["ctrl-g"] = { actions.grep_lgrep }, ["default"] = actions.file_edit, ["ctrl-_"] = actions.file_split, ["ctrl-v"] = actions.file_vsplit, ["ctrl-t"] = actions.file_tabedit, ["alt-q"] = actions.file_sel_to_qf, ["alt-l"] = actions.file_sel_to_ll, }, }, fzf_opts = { ["--ansi"] = "", ["--info"] = "inline", ["--layout"] = "reverse-list", ["--no-separator"] = "", ["--height"] = "100%", ["--margin"] = "0", ["--padding"] = "1", ["--preview-window"] = ":rounded", ["--border"] = "none", }, previewers = { cat = { cmd = "cat", args = "--number", }, bat = { cmd = "bat", args = "--style=numbers,changes --color always", theme = "gruvbox-dark", -- bat preview theme (bat --list-themes) config = nil, -- nil uses $BAT_CONFIG_PATH }, }, files = { -- previewer = 'bat', prompt = "Files) ", multiprocess = true, git_icons = true, file_icons = true, color_icons = true, fd_opts = "--color=never --type f --hidden --follow --exclude .git --exclude node_modules", }, grep = { prompt = " Search) ", input_prompt = " Search) ", multiprocess = true, git_icons = true, file_icons = true, color_icons = true, rg_opts = "--column --line-number --no-heading --color=always --smart-case --hidden --follow --glob '!.git/*' --glob '!.node_modules/*' --glob '!pnpm-lock.yaml' --glob '!package.lock' --glob '!yarn.lock'", rg_glob = true, glob_flag = "--iglob", glob_separator = "%s%-%-", -- query separator pattern (lua): ' --' no_header = false, -- hide grep|cwd header? no_header_i = false, -- hide interactive header? }, }) ```

Description

I am primarily working in a large monorepo. Large monorepo means long paths — app/packages/package/src/components/component/ComponentName.tsx

Long paths means unreadably (worst case) or just ugly path when using :FzfLua files or :FzfLua live_grep:

Screenshot 2024-02-21 at 21 40 35

Apart from changing the layout or increasing the size of the FzfLua floating window, is there a possibility to format the path from this: pkgs/package/components/component/ComponentName.tsx to ComponentName.tsx — pkgs/package/components/component?

In Telescope, I managed to change the path display like this:

local function filenameFirst(_, path)
  local tail = vim.fs.basename(path)
  local parent = vim.fs.dirname(path)
  if parent == "." then return tail end
  return string.format("%s\t\t%s", tail, parent)
end

require('telescope').setup({
  defaults = {
    path_display = filenameFirst,
  }
})

But I can't figure out how to replicate it in FzfLua? I am not fluent in fzf, so there might be an obvious way to implement this that I am missing.

ibhagwan commented 6 months ago

But I can't figure out how to replicate it in FzfLua? I am not fluent in fzf, so there might be an obvious way to implement this that I am missing.

There are a few ways to address what you’re after:

  1. Use :FzfLua live_grep path_shorten=true
  2. Use fzf’s --keep-right option (within fzf_opts)
  3. This would be the closest to what you’re after, you can reformat the entry with the combination of --delimiter and --with-nth (man fzf and search the issues and you’ll understand how to use these)
hieulw commented 4 months ago

@talovski May I ask your config to solve this ? I try to achieve the same behavior since I migrate from Telescope to FzfLua.

        files = {
          cwd_prompt = false,
          previewer = false,
          -- fd_opts = [[--color=never --type f --hidden --follow --exclude .git \
          -- | awk -F'/' '{ print $NF, substr($0, 1, length($0)-length($NF)-1) }']],
          fzf_opts = {
            -- ["--delimiter"] = ("[%s]"):format(util.nbsp),
            -- ["--with-nth"] = "-1,..-2",
          },
          fn_transform = function(entry)
            local file_icon, path = unpack(vim.split(entry, util.nbsp))
            local tail = vim.fs.basename(path)
            local parent = vim.fs.dirname(path)
            if parent == "." then
              return ("%s%s%s"):format(file_icon, util.nbsp, tail)
            end
            return ("%s%s%s%s%s"):format(file_icon, util.nbsp, tail, util.nbsp, util.ansi_codes.grey(parent))
          end,
        },

image

Which is exact the display I want but when select an entry it doesn't open a file but a directory instead. The createdb.sql db/migration entry above will open db/migration instead of db/migration/createdb.sql.

image

As you can see in my config I try fzf_opts as well (uncomment and file_icons=false) and the display is not what I expected but It opens the file correctly

ibhagwan commented 4 months ago

@hieulw, instead of creating your own transformer, it’s better to use --delimiter to split the string into virtual fields and then use --with-nth to place the filename first, this way you’re just playing with formatting so everything continues to work as expected (opening files, previews, etc).

I’ve seen a post on Reddit about this recently you might find interesting (using the above technique): https://redlib.freedit.eu/r/neovim/comments/1c1id24/vscode_like_path_display_in_fzfluas_files_picker/

hieulw commented 4 months ago

Wow, you so fast @ibhagwan. It works! I never think of -x printf "{}: {/} %s\n" before. Thank you so much!

ibhagwan commented 4 months ago

Wow, you so fast @ibhagwan. It works! I never think of -x printf "{}: {/} %s\n" before. Thank you so much!

This seems so popular I’m thinking of adding this as a native functionality in fzf-lua (without the overhead of printing the path twice).

hieulw commented 4 months ago

Wow, you so fast @ibhagwan. It works! I never think of -x printf "{}: {/} %s\n" before. Thank you so much!

This seems so popular I’m thinking of adding this as a native functionality in fzf-lua (without the overhead of printing the path twice).

That would be great !

ibhagwan commented 4 months ago

https://github.com/ibhagwan/fzf-lua/commit/0d09b75aa7a6ea39752c6cb21cbe28ce5e4de689

@hieulw, @towry, try out the latest commi, to enable set `formatter="path.filename_first" (hardcoded in globals):

require("fzf-lua").setup({
  files = {
    formatter = "path.filename_first"
  }
})

Can also be sent directly in the call options, e.g:

:lua require("fzf-lua").live_grep({ formatter = "path.filename_first" })
:FzfLua lsp_finder formatter=path.filename_first

Currently the folder is hard color coded with terminal grey, when I get a chance I'll add a custom highlight for that (it's a bit more complex as it needs to be passed to the child process).

ibhagwan commented 4 months ago

A nice "side effect", the new formatter can be combined with path_shorten, for example:

FzfLua live_grep path_shorten=true formatter=path.filename_first

Note the folders are shortened to a single letter image

PangPangPangPangPang commented 4 months ago

How to change the highlight group of the path?

ibhagwan commented 4 months ago

How to change the highlight group of the path?

Use fzf_colors.fg, see man fzf for all other color elements you can change.

nikbrunner commented 4 months ago

Hi there 👋 😊 ,

i love this new "filename_first" functionality, but I also have problems with the color of the path. I am also working on a big colorscheme collection and I love FzfLua so this is important to me. :)

I tried to work with fzf_colors.fg but fg seem to affect the filename only and not the path. And I can't find in the Manual a fzf which affects only the path.

This is what I currently got:

fzf_colors = {
    -- I intentionally picked something colorful here 
    -- to illustrate my problem, that this color only affects the filename.
    -- Normally its `Normal`
    ["fg"] = { "fg", "@conditional" }, 
    -- ["fg"] = { "fg", "Normal" }, 
    ["fg+"] = { "fg", "CursorLineNr" },
    ["bg"] = { "bg", "NormalFloat" },
    ["hl"] = { "fg", "Comment" },
    ["bg+"] = { "bg", "Normal" },
    ["border"] = { "fg", "CursorLineNr" },
    ["hl+"] = { "fg", "Statement" },
    ["query"] = { "fg", "Statement" },
    ["info"] = { "fg", "PreProc" },
    ["label"] = { "fg", "CursorLineNr" },
    ["prompt"] = { "fg", "Conditional" },
    ["pointer"] = { "fg", "Exception" },
    ["marker"] = { "fg", "Keyword" },
    ["spinner"] = { "fg", "Label" },
    ["header"] = { "fg", "Comment" },
    ["gutter"] = { "bg", "NormalFloat" },
},

This results in this:

image

I don't know if this is a good idea or even possible from your end, but I would expected a dedicated highlight group in the same maner of FzfLuaPathColNr & FzfLuaPathLineNr. Something like FzfLuaPath or something.

ibhagwan commented 4 months ago

Currently the folder is hard color coded with terminal grey, when I get a chance I'll add a custom highlight for that (it's a bit more complex as it needs to be passed to the child process).

@nikbrunner, @PangPangPangPangPang - this is a new feature, I am planning on adding an hl group but haven’t gotten to it yet.

nikbrunner commented 4 months ago

Alright thank you very much! 😊

ibhagwan commented 4 months ago

@nikbrunner @PangPangPangPangPang

https://github.com/ibhagwan/fzf-lua/commit/e32f3dfb543c69fe3cc6a85b763f66b7ff98294e

The above commit defines a new highlight group FzfLuaDirPart:

Can be linked to anything

:hi! link FzfLuaDirPart IncSearch

defined in setup

require("fzf-lua").setup({
hls = { dir_part = "IncSearch" }
})

Or sent directly in a call

:FzfLua live_grep formatter=path.filename_first hls.dir_part=ErrorMsg

image

nikbrunner commented 4 months ago

Thank you so very much!!! Its working great. 😊

ibhagwan commented 4 months ago

Thank you so very much!!! Its working great. 😊

Ty @nikbrunner!

I just set it up for myself as well :-)

Btw, if you want to do this for all pickers that contain path entries use global picker defaults in setup.defaults:

require("fzf-lua").setup({
  defaults = { formatter = "path.filename_first" },
  -- rest of your setup
})
nikbrunner commented 4 months ago

Thank you so very much!!! Its working great. 😊

Ty @nikbrunner!

I just set it up for myself as well :-)

Btw, if you want to do this for all pickers that contain path entries use global picker defaults in setup.defaults:

require("fzf-lua").setup({
  defaults = { formatter = "path.filename_first" },
  -- If you're using `tags`, disable the formatter for `btags|tags`
  tags = { formatter = false },
  btags = { formatter = false },
  -- rest of your setup
})

You are reading my mind. ❤️

ibhagwan commented 4 months ago

You are reading my mind. ❤️

As the saying goes, I eat my own dogfood :-)

nikbrunner commented 4 months ago

I just noticed something while trying this out. I can open an issue for that, if it even is an issue or just a miss-setting from my side. Ill just mention it here, because we just talked about it.

When I set the defaults = { formatter = "path.filename_first" } and also have the fzf_colors.prompt set, I will get an error:

15:21:05 │ [Fzf-lua] fzf error 2: invalid color specification: formatter:path.filename_first

I found this in the repo and I guessed it has something todo with the fzf colors.

I commented out every line in the fzf_colors array and stopped at prompt. If I comment it out I no longer get the error.

This is what my fzf_colors array looks like, and behind that link is also the rest of my fzf-lua config file: https://github.com/nikbrunner/vin/blob/86389b98f870661d0390ae38e8fa8076873d1573/lua/vin/specs/fzf.lua#L235-L252

I also noticed that you have the default set, but the prompt commented out in your nvim config: https://github.com/ibhagwan/nvim-lua/blob/b049e0571328ca28cdf66030fd900473aab9f9e2/lua/plugins/fzf-lua/setup.lua#L83

Thank you again. :) If you want me to open an Issue for that, please let me know.

ibhagwan commented 4 months ago

Thank you again. :) If you want me to open an Issue for that, please let me know.

Sure, would be great, so we can track it as this is a different issue altogether unrelated to this issue.

Would also be helpful if you can post the output of the bugged out group, if the below returns a link to another group try to follow it until the definition and that will tell us what’s bugged.

lua vim.print(vim.api.nvim_get_hl(0,{name="Conditional"}))

I also noticed that you have the default set, but the prompt commented out in your nvim config:

Just a personal preference after testing, I suspect your group has a value the code doesn’t expect, if you change it to one of the other groups in your fzf_colors it will probably work, this has nothing specific to do with prompt.

ibhagwan commented 4 months ago

Actually I just noticed this:

invalid color specification: formatter:path.filename_first

Might be related to the new formatted after, I’ll try to reproduce with prompt.

In the meantime can you run :FzfLua files debug=true _base64=false formatter=path.filename_first and post the output from :messages?

Also, if you run without the new formatter, do you still get an error (albeit different)?

nikbrunner commented 4 months ago

I am just writing out the issue in the other window, but here you go. :)

This is the output of FzfLua files debug=true _base64=false formatter=path.filename_first

[Fzf-lua]: FZF_DEFAULT_COMMAND: VIMRUNTIME='/opt/homebrew/Cellar/neovim/0.9.5/share/nvim/runtime' '/opt/homebrew/Cellar/neovim/0.9.5/bin/nvim' -n --headless --clean --cmd 'lua loadfile([[/Users/nbr/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/libuv.lua]])().spawn_stdio({g = {_fzf_lua_server = "/var/folders/k5/jc_1pjxx7h781rvj5y3ysmth0000gp/T/nvim.nbr/GfPEDh/fzf-lua.1714918308.4081.1"}, _base64 = false, cmd = "fd --color=never --type f --hidden --follow --exclude .git", git_icons = true, formatter = "path.filename_first", color_icons = false, debug = true, file_icons = false},[==[return require("make_entry").file]==],[==[return require("make_entry").preprocess]==])' || true
[Fzf-lua]: fzf cmd: fzf --preview 'VIMRUNTIME='\''/opt/homebrew/Cellar/neovim/0.9.5/share/nvim/runtime'\'' '\''/opt/homebrew/Cellar/neovim/0.9.5/bin/nvim'\'' -n --headless --clean --cmd '\''lua loadfile([[/Users/nbr/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/shell_helper.lua]])().rpc_nvim_exec_lua({fzf_lua_server=[[/var/folders/k5/jc_1pjxx7h781rvj5y3ysmth0000gp/T/nvim.nbr/GfPEDh/fzf-lua.1714918308.4081.1]], fnc_id=3 , debug=true})'\'' -- {}' --bind 'ctrl-d:preview-page-down,f4:toggle-preview,ctrl-u:preview-page-up,ctrl-c:abort,ctrl-a:toggle-all,zero:execute-silent(mkdir '\''/var/folders/k5/jc_1pjxx7h781rvj5y3ysmth0000gp/T/nvim.nbr/GfPEDh/3'\'' && VIMRUNTIME='\''/opt/homebrew/Cellar/neovim/0.9.5/share/nvim/runtime'\'' '\''/opt/homebrew/Cellar/neovim/0.9.5/bin/nvim'\'' -n --headless --clean --cmd '\''lua loadfile([[/Users/nbr/.local/share/nvim/lazy/fzf-lua/lua/fzf-lua/shell_helper.lua]])().rpc_nvim_exec_lua({fzf_lua_server=[[/var/folders/k5/jc_1pjxx7h781rvj5y3ysmth0000gp/T/nvim.nbr/GfPEDh/fzf-lua.1714918308.4081.1]], fnc_id=4 , debug=true})'\'' -- ),f3:toggle-preview-wrap,ctrl-q:select-all+accept' --header ':: <^[[38;2;0;250;154mctrl-g^[[0m> to ^[[38;2;139;35;35mDisable .gitignore^[[0m' --preview-window 'nohidden:right:0' --height '100%' --no-info --border-label '[ Vin ]' --layout 'reverse' --no-scrollbar --keep-right --reverse --padding '0,3' --no-separator --ansi --prompt '  ' --multi --color 'header:#606a67,query:#998ed9,info:#998ed9,bg+:#e3e6e5,hl+:#998ed9,hl:#606a67,fg+:#4db290,gutter:#d6dbd9,fg:#313533,spinner:#998ed9,border:#4db290,marker:#7872c2,pointer:#998ed9,bg:#d6dbd9,label:#4db290' --print-query --expect 'ctrl-t,ctrl-v,ctrl-s,ctrl-y,alt-q,alt-l,ctrl-c,esc,ctrl-g,ctrl-q' --border 'none' > '/var/folders/k5/jc_1pjxx7h781rvj5y3ysmth0000gp/T/nvim.nbr/GfPEDh/5'

And yes. If I comment out the default option for the formatter, and comment in the fzf_colors.prompt again, I don't get the error.

So this works.

            defaults = {
                -- formatter = "path.filename_first",
            },

-- ...

            fzf_colors = {
                ["fg"] = { "fg", "Normal" },
                ["fg+"] = { "fg", "CursorLineNr" },
                ["bg"] = { "bg", "NormalFloat" },
                ["hl"] = { "fg", "Comment" },
                ["hl+"] = { "fg", "Statement" },
                ["bg+"] = { "bg", "Normal" },
                ["border"] = { "fg", "CursorLineNr" },
                ["query"] = { "fg", "Statement" },
                ["info"] = { "fg", "PreProc" },
                ["label"] = { "fg", "CursorLineNr" },
                ["prompt"] = { "fg", "Conditional" },
                ["pointer"] = { "fg", "Exception" },
                ["marker"] = { "fg", "Keyword" },
                ["spinner"] = { "fg", "Label" },
                ["header"] = { "fg", "Comment" },
                ["gutter"] = { "bg", "NormalFloat" },
            },

Ah and this is the output of lua vim.print(vim.api.nvim_get_hl(0,{name="Conditional"})):

{
  bold = true,
  cterm = {
    bold = true,
    italic = true
  },
  fg = 10063577,
  italic = true
}
ibhagwan commented 4 months ago

And yes. If I comment out the default option for the formatter, and comment in the fzf_colors.prompt again, I don't get the error.

Def related to the formatter, you can skip the issue if you want, I’ll take a look later when I’m no longer AFK, I’m a bit confused how the color spec gets into this mixed given the formatter is hidden inside a base64 string.

nikbrunner commented 4 months ago

Okay thank you for the update! I hope you can figure it out. :)

Well was just done writing out the issue. I tried to append the gathered information about this in it. If you need anything more, please just ask.

1176

oshchyhol commented 3 months ago

Hello, Is it expected that the formatter = 'path.filename_first' option impacts the way search query is interpreted? Asking because with this option on I have to also type the filename first rather than start with any path segments in order to be able to find anything :)

ibhagwan commented 3 months ago

Hello, Is it expected that the formatter = 'path.filename_first' option impacts the way search query is interpreted? Asking because with this option on I have to also type the filename first rather than start with any path segments in order to be able to find anything :)

Yes, once the line is transformed and sent to fzf it has no awareness of a “path”, just a string, I can look into changing the search order with --nth.

ibhagwan commented 3 months ago

@oshchyhol, unfortunately, that doesn't seem possible as --nth only limits the search scope but does not change the search order, this would require a feature request upstream.

oshchyhol commented 3 months ago

Gotcha, thanks for the quick response and for looking for workarounds!

folke commented 3 months ago

@ibhagwan I also just noticed the way searching is changed if using path.filename_first :)

What about adding the basename (filename) to the end of the string and then use --with-nth=..-2. Wouldn't that work more as expected?

folke commented 3 months ago

I made an experimental PR to allow better search with filename_first.

https://github.com/ibhagwan/fzf-lua/pull/1255

It's a bit of a hack, but works as intended:

towry commented 3 months ago

@ibhagwan If the query for 'a.txt foo/bar' is "foo bar a.txt" then it works as expected.

So is it possible to send "foo bar a.txt" to fzf if user input "foo/bar/a.txt" ?

ibhagwan commented 3 months ago

@ibhagwan If the query for 'a.txt foo/bar' is "foo bar a.txt" then it works as expected.

So is it possible to send "foo bar a.txt" to fzf if user input "foo/bar/a.txt" ?

Unfortuntely not, I have no control over parsing the query outside of "live" pickers which would mean no fuzzy matching.

ibhagwan commented 3 months ago

@towry, update to the latest commit and try:

:lua require("fzf-lua").files({ formatter={"path.filename_first",2} })
-- or
:FzfLua files formatter={"path.filename_first",2}

It will hide the highlighting when the match is on the hidden part but it will match the paths, see https://github.com/ibhagwan/fzf-lua/pull/1255#issuecomment-2163449012

towry commented 3 months ago

Thanks, I will give it a try.

And I just find a solution for files picker only, to replace path separator to space in query.

      files = {
        formatter = 'path.filename_first',
        keymap = {
          fzf = {
            ['/'] = [[transform-query(echo '{fzf:query} ')]],
          },
        },
      },
ibhagwan commented 3 months ago

And I just find a solution for files picker only, to replace path separator to space in query.

While this might work, playing with the query isn't something I would like to get into, has too many uninteded consequences.

epilande commented 1 month ago

When using :FzfLua files formatter={"path.filename_first",2} I'm seeing the following with top level files Unable to stat file. Then when I attempt to select it, it opens in an empty buffer

image

EDIT: Ah looks like this was fixed in https://github.com/ibhagwan/fzf-lua/issues/1304#issuecomment-2200500447