mirror of
https://github.com/kristoferssolo/telescope-frecency.nvim.git
synced 2025-10-21 20:10:38 +00:00
feat: add fuzzy matching sorter experimentally (#166)
* feat: add fuzzy matching sorter experimentally * feat: add an option to select matcher logic * feat: separate logic to match: fuzzy, fuzzy_full * Revert "feat: separate logic to match: fuzzy, fuzzy_full" This reverts commit 64c022904871143ab12c7d6ba29c89fbabdbe15e. * feat: use fzy sorter and combine recency scores * feat: enable to change logic to calculate scores * feat: change the view for scores by config.matcher * docs: add note in README * docs: add note for `scoring_function`
This commit is contained in:
parent
94a532cb9c
commit
42b6421061
37
README.md
37
README.md
@ -218,6 +218,22 @@ See [default configuration](https://github.com/nvim-telescope/telescope.nvim#tel
|
|||||||
Patterns in this table control which files are indexed (and subsequently
|
Patterns in this table control which files are indexed (and subsequently
|
||||||
which you'll see in the finder results).
|
which you'll see in the finder results).
|
||||||
|
|
||||||
|
- `matcher` (default: `"default"`)
|
||||||
|
|
||||||
|
> ___CAUTION___<br>
|
||||||
|
> This option is highly experimental.
|
||||||
|
|
||||||
|
In default, it matches against candidates by the so-called “substr matcher”,
|
||||||
|
that is, you should input characters ordered properly. If you set here with
|
||||||
|
`"fuzzy"`, it uses [_fzy_ matcher][fzy] implemented in telescope itself, and
|
||||||
|
combines the result with recency scores. With this, you can select candidates
|
||||||
|
fully _fuzzily_, besides that, can select easily ones that has higher recency
|
||||||
|
scores.
|
||||||
|
|
||||||
|
See the discussion in https://github.com/nvim-telescope/telescope-frecency.nvim/issues/165.
|
||||||
|
|
||||||
|
[fzy]: https://github.com/jhawthorn/fzy
|
||||||
|
|
||||||
- `max_timestamps` (default: `10`)
|
- `max_timestamps` (default: `10`)
|
||||||
|
|
||||||
Set the max count of timestamps DB keeps when you open files. It ignores the
|
Set the max count of timestamps DB keeps when you open files. It ignores the
|
||||||
@ -245,6 +261,27 @@ See [default configuration](https://github.com/nvim-telescope/telescope.nvim#tel
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `scoring_function` (default: see below)
|
||||||
|
|
||||||
|
> ___CAUTION___<br>
|
||||||
|
> This option is highly experimental.
|
||||||
|
|
||||||
|
This will be used only when `matcher` option is `"fuzzy"`. You can customize the
|
||||||
|
logic to adjust scores between [fzy matcher][fzy] scores and recency ones.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- the default value
|
||||||
|
---@param recency integer
|
||||||
|
---@param fzy_score number
|
||||||
|
---@return number
|
||||||
|
scoring_function = function(recency, fzy_score)
|
||||||
|
return (10 / (recency == 0 and 1 or recency)) - 1 / fzy_score
|
||||||
|
end,
|
||||||
|
```
|
||||||
|
|
||||||
|
NOTE: telescope orders candidates in the ascending order. It also accepts
|
||||||
|
negative numbers, but `-1` means the candidates should not be shown.
|
||||||
|
|
||||||
- `show_filter_column` (default: `true`)
|
- `show_filter_column` (default: `true`)
|
||||||
|
|
||||||
Show the path of the active filter before file paths. In default, it uses the
|
Show the path of the active filter before file paths. In default, it uses the
|
||||||
|
|||||||
@ -15,6 +15,8 @@ local Config = {}
|
|||||||
---@field filter_delimiter string default: ":"
|
---@field filter_delimiter string default: ":"
|
||||||
---@field hide_current_buffer boolean default: false
|
---@field hide_current_buffer boolean default: false
|
||||||
---@field ignore_patterns string[] default: { "*.git/*", "*/tmp/*", "term://*" }
|
---@field ignore_patterns string[] default: { "*.git/*", "*/tmp/*", "term://*" }
|
||||||
|
---@field matcher "default"|"fuzzy" default: "default"
|
||||||
|
---@field scoring_function fun(recency: integer, fzy_score: number): number default: see lua/frecency/config.lua
|
||||||
---@field max_timestamps integer default: 10
|
---@field max_timestamps integer default: 10
|
||||||
---@field show_filter_column boolean|string[] default: true
|
---@field show_filter_column boolean|string[] default: true
|
||||||
---@field show_scores boolean default: false
|
---@field show_scores boolean default: false
|
||||||
@ -35,6 +37,7 @@ Config.new = function()
|
|||||||
hide_current_buffer = false,
|
hide_current_buffer = false,
|
||||||
ignore_patterns = os_util.is_windows and { [[*.git\*]], [[*\tmp\*]], "term://*" }
|
ignore_patterns = os_util.is_windows and { [[*.git\*]], [[*\tmp\*]], "term://*" }
|
||||||
or { "*.git/*", "*/tmp/*", "term://*" },
|
or { "*.git/*", "*/tmp/*", "term://*" },
|
||||||
|
matcher = "default",
|
||||||
max_timestamps = 10,
|
max_timestamps = 10,
|
||||||
recency_values = {
|
recency_values = {
|
||||||
{ age = 240, value = 100 }, -- past 4 hours
|
{ age = 240, value = 100 }, -- past 4 hours
|
||||||
@ -44,6 +47,12 @@ Config.new = function()
|
|||||||
{ age = 43200, value = 20 }, -- past month
|
{ age = 43200, value = 20 }, -- past month
|
||||||
{ age = 129600, value = 10 }, -- past 90 days
|
{ age = 129600, value = 10 }, -- past 90 days
|
||||||
},
|
},
|
||||||
|
---@param recency integer
|
||||||
|
---@param fzy_score number
|
||||||
|
---@return number
|
||||||
|
scoring_function = function(recency, fzy_score)
|
||||||
|
return (10 / (recency == 0 and 1 or recency)) - 1 / fzy_score
|
||||||
|
end,
|
||||||
show_filter_column = true,
|
show_filter_column = true,
|
||||||
show_scores = false,
|
show_scores = false,
|
||||||
show_unindexed = true,
|
show_unindexed = true,
|
||||||
@ -62,7 +71,9 @@ Config.new = function()
|
|||||||
filter_delimiter = true,
|
filter_delimiter = true,
|
||||||
hide_current_buffer = true,
|
hide_current_buffer = true,
|
||||||
ignore_patterns = true,
|
ignore_patterns = true,
|
||||||
|
matcher = true,
|
||||||
max_timestamps = true,
|
max_timestamps = true,
|
||||||
|
scoring_function = true,
|
||||||
show_filter_column = true,
|
show_filter_column = true,
|
||||||
show_scores = true,
|
show_scores = true,
|
||||||
show_unindexed = true,
|
show_unindexed = true,
|
||||||
@ -105,6 +116,13 @@ Config.setup = function(ext_config)
|
|||||||
filter_delimiter = { opts.filter_delimiter, "s" },
|
filter_delimiter = { opts.filter_delimiter, "s" },
|
||||||
hide_current_buffer = { opts.hide_current_buffer, "b" },
|
hide_current_buffer = { opts.hide_current_buffer, "b" },
|
||||||
ignore_patterns = { opts.ignore_patterns, "t" },
|
ignore_patterns = { opts.ignore_patterns, "t" },
|
||||||
|
matcher = {
|
||||||
|
opts.matcher,
|
||||||
|
function(v)
|
||||||
|
return type(v) == "string" and (v == "default" or v == "fuzzy")
|
||||||
|
end,
|
||||||
|
'"default" or "fuzzy"',
|
||||||
|
},
|
||||||
max_timestamps = {
|
max_timestamps = {
|
||||||
opts.max_timestamps,
|
opts.max_timestamps,
|
||||||
function(v)
|
function(v)
|
||||||
|
|||||||
@ -33,6 +33,7 @@ end
|
|||||||
---@field ordinal string
|
---@field ordinal string
|
||||||
---@field name string
|
---@field name string
|
||||||
---@field score number
|
---@field score number
|
||||||
|
---@field fuzzy_score? number
|
||||||
---@field display fun(entry: FrecencyEntry): string, table
|
---@field display fun(entry: FrecencyEntry): string, table
|
||||||
|
|
||||||
---@class FrecencyFile
|
---@class FrecencyFile
|
||||||
@ -77,7 +78,11 @@ end
|
|||||||
function EntryMaker:displayer_items(workspace, workspace_tag)
|
function EntryMaker:displayer_items(workspace, workspace_tag)
|
||||||
local items = {}
|
local items = {}
|
||||||
if config.show_scores then
|
if config.show_scores then
|
||||||
table.insert(items, { width = 8 })
|
table.insert(items, { width = 5 }) -- recency score
|
||||||
|
if config.matcher == "fuzzy" then
|
||||||
|
table.insert(items, { width = 5 }) -- index
|
||||||
|
table.insert(items, { width = 6 }) -- fuzzy score
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if self.web_devicons.is_enabled then
|
if self.web_devicons.is_enabled then
|
||||||
table.insert(items, { width = 2 })
|
table.insert(items, { width = 2 })
|
||||||
@ -99,6 +104,12 @@ function EntryMaker:items(entry, workspace, workspace_tag, formatter)
|
|||||||
local items = {}
|
local items = {}
|
||||||
if config.show_scores then
|
if config.show_scores then
|
||||||
table.insert(items, { entry.score, "TelescopeFrecencyScores" })
|
table.insert(items, { entry.score, "TelescopeFrecencyScores" })
|
||||||
|
if config.matcher == "fuzzy" then
|
||||||
|
table.insert(items, { entry.index, "TelescopeFrecencyScores" })
|
||||||
|
local score = (not entry.fuzzy_score or entry.fuzzy_score == 0) and "0"
|
||||||
|
or ("%.3f"):format(entry.fuzzy_score):sub(0, 5)
|
||||||
|
table.insert(items, { score, "TelescopeFrecencyScores" })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if self.web_devicons.is_enabled then
|
if self.web_devicons.is_enabled then
|
||||||
table.insert(items, { self.web_devicons:get_icon(entry.name, entry.name:match "%a+$", { default = true }) })
|
table.insert(items, { self.web_devicons:get_icon(entry.name, entry.name:match "%a+$", { default = true }) })
|
||||||
|
|||||||
26
lua/frecency/fuzzy_sorter.lua
Normal file
26
lua/frecency/fuzzy_sorter.lua
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
local config = require "frecency.config"
|
||||||
|
local sorters = require "telescope.sorters"
|
||||||
|
|
||||||
|
---@param opts any options for get_fzy_sorter()
|
||||||
|
return function(opts)
|
||||||
|
local fzy_sorter = sorters.get_fzy_sorter(opts)
|
||||||
|
|
||||||
|
return sorters.Sorter:new {
|
||||||
|
---@param prompt string
|
||||||
|
---@param entry FrecencyEntry
|
||||||
|
---@return number
|
||||||
|
scoring_function = function(_, prompt, _, entry)
|
||||||
|
if #prompt == 0 then
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
local fzy_score = fzy_sorter:scoring_function(prompt, entry.ordinal)
|
||||||
|
if fzy_score <= 0 then
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
entry.fuzzy_score = config.scoring_function(entry.score, fzy_score)
|
||||||
|
return entry.fuzzy_score
|
||||||
|
end,
|
||||||
|
|
||||||
|
highlighter = fzy_sorter.highlighter,
|
||||||
|
}
|
||||||
|
end
|
||||||
@ -1,6 +1,7 @@
|
|||||||
local State = require "frecency.state"
|
local State = require "frecency.state"
|
||||||
local Finder = require "frecency.finder"
|
local Finder = require "frecency.finder"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fuzzy_sorter = require "frecency.fuzzy_sorter"
|
||||||
local sorters = require "telescope.sorters"
|
local sorters = require "telescope.sorters"
|
||||||
local log = require "plenary.log"
|
local log = require "plenary.log"
|
||||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||||
@ -105,7 +106,7 @@ function Picker:start(opts)
|
|||||||
prompt_title = "Frecency",
|
prompt_title = "Frecency",
|
||||||
finder = finder,
|
finder = finder,
|
||||||
previewer = config_values.file_previewer(opts),
|
previewer = config_values.file_previewer(opts),
|
||||||
sorter = sorters.get_substr_matcher(),
|
sorter = config.matcher == "default" and sorters.get_substr_matcher() or fuzzy_sorter(opts),
|
||||||
on_input_filter_cb = self:on_input_filter_cb(opts),
|
on_input_filter_cb = self:on_input_filter_cb(opts),
|
||||||
attach_mappings = function(prompt_bufnr)
|
attach_mappings = function(prompt_bufnr)
|
||||||
return self:attach_mappings(prompt_bufnr)
|
return self:attach_mappings(prompt_bufnr)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user