nvim-telescope / telescope-frecency.nvim

A telescope.nvim extension that offers intelligent prioritization when selecting files from your editing history.
MIT License
798 stars 38 forks source link

[feature] Can we use both fuzzy matching & frecency scoring at the same time? #165

Open delphinus opened 10 months ago

delphinus commented 10 months ago

From #163 & some issues, there are some demand that users want to use fuzzy matching for frecency results. The current build allows to use sorters.get_substr_matcher only. That is because it is the only “sorter” that does not sort but match candidates.

If a “sorter” that fuzzy-matches and does not sort exists, it is maybe useful for them. I want to create it on trial.

delphinus commented 10 months ago

I added naive implementation for fuzzy-matching in #166. It simply converts prompt string, such as foobar, into regexp: f.*o.*o.*b.*a.*r to match. I tested it, but it matches too much candidates to select. I want opinions from other users.

kirillrogovoy commented 7 months ago

I've just discovered this project and super-grateful for your effort!

The first thing I stumbled across was not matching stuff with fuzzy searches. By now, it's deeply ingrained in my brain to just type 2-4-letter pieces to find the file (serpy for server.py) which works fantastically well with built-in searches.

This would be a single most impactful improvement for me.

delphinus commented 7 months ago

I see. I tried #166 and didn't feel much useful. But it is a good idea that users can select “fuzzy” or “unfuzzy” matcher in config. I will implement that.

-- such as this way?
telescope.setup {
  extensions = {
    frecency = {
      matcher = "fuzzy" -- acceptable: "default" / "fuzzy"
    },
  },
}
delphinus commented 7 months ago

Now I'm testing a matcher: fuzzy in #166. This matcher only matches the pattern to basename only. I also created a matcher: fuzzy_full, to match full paths, but this matches too much candidates to narrow ones to select, I think.

I will use #166 for dog-fooding for a while.

good-idea commented 6 months ago

Question - would this feature solve what I'm looking for below?

I have a directory like:

src/lib/sanity.ts
src/lib/types.ts
image

I'd like to be able to type libsan to match src/lib/sanity.ts, but currently that does not work:

image

Instead, I need to include the /, i.e. lib/san:

image

This example is simple - but my project is quite large and i'm not always going to remember the full path nor want to type it out. For instance, typing comsch would ideally match src/components/ui/forms/schedule.tsx

delphinus commented 6 months ago

Yes. fuzzy_full matcher makes good for your use case. But this has a side effect, that is, it matches against candidates you probably have not intended.

# your input “libsan” matches below.
/path/to/www/src/lib/sanity.ts
# also this matches: `Lib……S……A……N` => “libsan”
/Users/foo/Library/Some/Arbitrary/Nice/project.ts

This is why I am hesitating to include this feature into frecency. There is two ways to reduce this inconvenience.

  1. Limit candidates to a certain project by workspace feature.
    • Do :Telescope frecency workspace=Foo makes it show candidates only in Foo project.
    • You can always do this with default_workspace option. For example, default_workspace = "CWD" makes you always see candadidates from your current working directory.
  2. Use fuzzy matcher instead of fuzzy_full.
    • This only matches your input against the basename of paths.

Do you want to use this feature even with these points?

kirillrogovoy commented 6 months ago

Just a few thoughts:

  1. Some/Arbitrary/Nice/project.ts is maybe not so arbitrary if it has a frecency score, right? :)
  2. The fuzzy matcher isn't changing the sorting, right? So the most frecent file will still be closer to the cursor? So even though some random undesired files will match, it might not be a problem in practice.
  3. As the escape hatch, maybe you could expose a setting to override the scoring function fn (frecency_score, fuzzy_score) -> final_score which essentially redefines sorting. For those who want more control over how much each score affects the final sequence of files.
delphinus commented 6 months ago
  1. ~Yes.~ No. It probably is not arbitrary, that is, you should have opened the file many times in such case.
  2. ~Also yes.~ No, it doesn’t change the order with the current implementaion. In my env, it makes too much noises to select one, but this is because my DB is so huge, maybe.
  3. It seems nice. fuzzy_full will match well for almost users, and some users that has huge DB, like me, should use the func. I will try to add that.

edited.

Sorry, I’ve mistaken yes/no at the first time because I use yes against negative questions when I agree in my native language, Japanese.;)

good-idea commented 6 months ago

Do you want to use this feature even with these points?

  1. Some/Arbitrary/Nice/project.ts is maybe not so arbitrary if it has a frecency score, right? :)

Yes - that would be fine. Plus, once I actually navigate to lib/sanity a couple of times, it will rank higher on the frecency score anyways - so /Library/Some/Arbitrary/Nice/project.ts showing up in the results wouldn't be an issue.

delphinus commented 6 months ago

I've updated #166. The current logic calculates scores with recency and fzy implemented by telescope.algos.fzy. It seems to work good for me. Still testing.

delphinus commented 6 months ago

It works well in my env. I merged #166 and added matcher = "fuzzy". Do not hesitate to reopen here when you all want to discuss further.

kirillrogovoy commented 6 months ago

Awesome, thanks a lot @delphinus!

For the reference, here's the config that does what I was looking for:

frecency = {
  matcher = "fuzzy",
  scoring_function = function(recency, fzy_score)
    return -recency
  end
}

It seems to only filter the list while the order still depends ONLY on the recency score. So now I basically use either of two file searches: fuzzy only (default telescope find_files with rg in my case) or frecency only.

I found that mixing the two leads to unpredictable sorting on every character I type (at least in my experience, ymmv).

Will keep it like that for a week to see if there's more to it. 🙌

delphinus commented 6 months ago

@kirillrogovoy I also tried your suggested function. I noticed it is difficult to select a candidate which has too small recency score, such as, I have opened it only once.

For example, consider a case like this.

  1. I want to open /path/to/hello.js that has recency score: 10 because I only opened this only once.
  2. DB has a file /path/to/high/element/low/foo.js that has much score: 1000.
  3. When I input hel, /path/to/high/element/low/foo.js is higher than /path/to/hello.js …… This is not intended, probably.
  4. When I input hello.j, then /path/to/hello.js is the first now.

But, it might be a matter of taste. I want to test own logic by many users.

delphinus commented 6 months ago

I reopen this just to be sure.

kirillrogovoy commented 6 months ago

Thanks for sharing! I think it's definitely a matter of taste or just preferred use cases.

I use the frecency search only to navigate among the last ~5-20 opened files while working on a task. When the file is not frecently visited, I look it up with the standard find_files. The biggest time saver here for me is that I can just type something like "controller" and have the most related controller to my current task as the first/second suggested option.

Some people handle it differently: e.g. they have a dozen of buffers open with the files they need and they use the buffer search to navigate. For me, the frecency search is like that, but without the need to manage those buffers. :)

This tactics definitely doesn't work if you intend to use the frecency search most of the time in place of find_files exactly for the reasons you've mentioned. 👍

good-idea commented 6 months ago

Fuzzy works great for me, thank you!

Bart97 commented 4 months ago

[SOLVED - See bottom] Hello, I've recently discovered this plugin and it seems to be almost perfect for my use case. However there's one thing I can't figure out. For example let's take my nvim config dir with multiple init.lua files:

nvim/init.lua
nvim/lua/bartosz/init.lua
nvim/lua/bartosz/lazy/init.lua

If I understand the previous comments correctly fuzzy search should let me write "lazyin" and "nvim/lua/bartosz/lazy/init.lua" should show up. But I get an empty list. If I search fot "lazy in" it comes up. But that's not the expected behavior?

For more context I'm migrating from VSCode and I work in a very large repository with inconsistent class names, like some files would be called SomeMessageReqSerializer.hpp, but others are AnotherMessageRequestSerializer.hpp. In default Telescope file search I can search for AnotherMessageReqSer and the file will come up anyway. I'd love to have such search algorithm combined with frecency.

Here's the relevant part of my config:

return {
    {
        'nvim-telescope/telescope.nvim',
        tag = '0.1.6',
        dependencies = { 'nvim-lua/plenary.nvim' },
        config = function()
            local builtin = require("telescope.builtin")
            vim.keymap.set('n', '<C-p>', builtin.git_files)
            vim.keymap.set('n', '<leader>pg', function()
                builtin.grep_string({ search = vim.fn.input("Grep > ") });
            end)
        end
    },
    {
        "nvim-telescope/telescope-frecency.nvim",
        config = function()
            local telescope = require("telescope")
            telescope.load_extension("frecency")
            telescope.setup({
                extensions = {
                    frecency = {
                        matcher = "fuzzy_full"
                    }
                }
            })
            vim.keymap.set('n', '<leader>pf', function()
                require("telescope").extensions.frecency.frecency({workspace = "CWD"})
            end)
        end,
        dependencies = {
            "nvim-telescope/telescope.nvim",
        },
    }
}

UPDATE: I figured it out. I wasn't using the fuzzy algorithm at all. telescope.setup has to be called before load_extension. I swapped those 2 lines and now it's amazing!

delphinus commented 4 months ago

@Bart97

It's good that the problem is solved. I found you are calling telescope.setup in the config of telescope-frecency, but I recommend you should call it in the config of telescope itself, because you will be worried when you add another telescope-* plugin.

require("lazy").setup {
  {
    "nvim-telescope/telescope.nvim",
    dependencies = {
        "nvim-lua/plenary.nvim",
        "nvim-telescope/telescope-frecency.nvim",
        -- and more telescope-* plugins
    },
    config = function()
      local telescope = require "telescope"
      telescope.setup {
        extensions = {
          frecency = {
            matcher = "fuzzy",
          },
          -- other telescope-* plugins' configs
        },
      }

      -- builtin settings
      local builtin = require "telescope.builtin"
      vim.keymap.set("n", "<C-p>", builtin.git_files)
      vim.keymap.set("n", "<leader>pg", function()
        builtin.grep_string { search = vim.fn.input "Grep > " }
      end)

      -- extensions' setting
      telescope.load_extension "frecency"

      vim.keymap.set("n", "<leader>pf", function()
        telescope.extensions.frecency.frecency { workspace = "CWD" }
      end)
    end,
  },
}