mirror of
https://github.com/kristoferssolo/telescope-frecency.nvim.git
synced 2025-10-21 20:10:38 +00:00
Now it uses realpath for registering and validating DB. This means, if you have entries that has filenames differing only for case, it can deal with them as they exist. Before this, it has miscalculated scores for such cases. For example, in case you have `/path/to/foo.lua` and `/path/to/Foo.lua`, it registers entries for each file. Now it detects accurate filename for the specified one, and removes it in validation. * test: separate logic for utils * fix!: register realpath for consistency * refactor: convert fs module from class * refactor: move db initialization phase to start() * fix: run database:start() truly asynchronously * fix: call each functions with async wrapping * refactor: add types for args in command * fix: run register() synchronously Because vim.api.nvim_* cannot be used in asynchronous functions. * docs: add note for calling setup() twice * fix: run non-fast logic on next tick
168 lines
5.8 KiB
Lua
168 lines
5.8 KiB
Lua
local WebDevicons = require "frecency.web_devicons"
|
|
local config = require "frecency.config"
|
|
local fs = require "frecency.fs"
|
|
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
|
local entry_display = require "telescope.pickers.entry_display" --[[@as FrecencyTelescopeEntryDisplay]]
|
|
local utils = require "telescope.utils" --[[@as FrecencyTelescopeUtils]]
|
|
|
|
---@class FrecencyEntryMaker
|
|
---@field loaded table<string,boolean>
|
|
---@field web_devicons WebDevicons
|
|
local EntryMaker = {}
|
|
|
|
---@return FrecencyEntryMaker
|
|
EntryMaker.new = function()
|
|
return setmetatable({ web_devicons = WebDevicons.new() }, { __index = EntryMaker })
|
|
end
|
|
|
|
---@class FrecencyEntry
|
|
---@field filename string
|
|
---@field index integer
|
|
---@field ordinal string
|
|
---@field name string
|
|
---@field score number
|
|
---@field fuzzy_score? number
|
|
---@field display fun(entry: FrecencyEntry): string, table
|
|
|
|
---@class FrecencyFile
|
|
---@field count integer
|
|
---@field id integer
|
|
---@field path string
|
|
---@field score integer calculated from count and age
|
|
|
|
---@alias FrecencyEntryMakerInstance fun(file: FrecencyFile): FrecencyEntry
|
|
|
|
---@param filepath_formatter FrecencyFilepathFormatter
|
|
---@param workspace? string
|
|
---@param workspace_tag? string
|
|
---@return FrecencyEntryMakerInstance
|
|
function EntryMaker:create(filepath_formatter, workspace, workspace_tag)
|
|
-- NOTE: entry_display.create calls non API-fast functions. We cannot call
|
|
-- in entry_maker because it will be called in a Lua loop.
|
|
local displayer = entry_display.create {
|
|
separator = "",
|
|
hl_chars = { [Path.path.sep] = "TelescopePathSeparator" },
|
|
items = self:width_items(workspace, workspace_tag),
|
|
}
|
|
|
|
-- set loaded buffers for highlight
|
|
self.loaded = {}
|
|
local loaded_bufnrs = vim.tbl_filter(function(v)
|
|
return vim.api.nvim_buf_is_loaded(v)
|
|
end, vim.api.nvim_list_bufs())
|
|
for _, bufnr in ipairs(loaded_bufnrs) do
|
|
local bufname = vim.api.nvim_buf_get_name(bufnr)
|
|
if bufname then
|
|
self.loaded[bufname] = true
|
|
end
|
|
end
|
|
|
|
return function(file)
|
|
return {
|
|
filename = file.path,
|
|
ordinal = file.path,
|
|
name = file.path,
|
|
score = file.score,
|
|
---@param entry FrecencyEntry
|
|
---@return table
|
|
display = function(entry)
|
|
local items = self:items(entry, workspace, workspace_tag, filepath_formatter(workspace))
|
|
return displayer(items)
|
|
end,
|
|
}
|
|
end
|
|
end
|
|
|
|
---@private
|
|
---@param workspace? string
|
|
---@param workspace_tag? string
|
|
---@return table[]
|
|
function EntryMaker:width_items(workspace, workspace_tag)
|
|
local width_items = {}
|
|
if config.show_scores then
|
|
table.insert(width_items, { width = 5 }) -- recency score
|
|
if config.matcher == "fuzzy" then
|
|
table.insert(width_items, { width = 5 }) -- index
|
|
table.insert(width_items, { width = 6 }) -- fuzzy score
|
|
end
|
|
end
|
|
if self.web_devicons.is_enabled then
|
|
table.insert(width_items, { width = 2 })
|
|
end
|
|
if config.show_filter_column and workspace and workspace_tag then
|
|
table.insert(width_items, { width = self:calculate_filter_column_width(workspace, workspace_tag) })
|
|
end
|
|
-- TODO: This is a stopgap measure to detect placeholders.
|
|
table.insert(width_items, {})
|
|
table.insert(width_items, {})
|
|
table.insert(width_items, {})
|
|
return width_items
|
|
end
|
|
|
|
---@private
|
|
---@param entry FrecencyEntry
|
|
---@param workspace? string
|
|
---@param workspace_tag? string
|
|
---@param formatter fun(filename: string): string, FrecencyTelescopePathStyle[]
|
|
---@return table[]
|
|
function EntryMaker:items(entry, workspace, workspace_tag, formatter)
|
|
local items = {}
|
|
if config.show_scores then
|
|
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
|
|
if self.web_devicons.is_enabled then
|
|
local basename = utils.path_tail(entry.name)
|
|
local icon, icon_highlight =
|
|
self.web_devicons:get_icon(basename, utils.file_extension(basename), { default = false })
|
|
if not icon then
|
|
icon, icon_highlight = self.web_devicons:get_icon(basename, nil, { default = true })
|
|
end
|
|
table.insert(items, { icon, icon_highlight })
|
|
end
|
|
if config.show_filter_column and workspace and workspace_tag then
|
|
local filtered = self:should_show_tail(workspace_tag) and utils.path_tail(workspace) .. Path.path.sep
|
|
or fs.relative_from_home(workspace) .. Path.path.sep
|
|
table.insert(items, { filtered, "Directory" })
|
|
end
|
|
local formatted_name, path_style = formatter(entry.name)
|
|
-- NOTE: this means it is formatted with the option: filename_first
|
|
if path_style and type(path_style) == "table" and #path_style > 0 then
|
|
local index = path_style[1][1]
|
|
local filename = formatted_name:sub(1, index[1])
|
|
local parent_path = formatted_name:sub(index[1] + 2, index[2])
|
|
local hl = path_style[1][2]
|
|
|
|
table.insert(items, { filename .. " ", self.loaded[entry.name] and "TelescopeBufferLoaded" or "" })
|
|
table.insert(items, { parent_path, hl })
|
|
else
|
|
table.insert(items, { formatted_name, self.loaded[entry.name] and "TelescopeBufferLoaded" or "" })
|
|
end
|
|
return items
|
|
end
|
|
|
|
---@private
|
|
---@param workspace string
|
|
---@param workspace_tag string
|
|
---@return integer
|
|
function EntryMaker:calculate_filter_column_width(workspace, workspace_tag)
|
|
return self:should_show_tail(workspace_tag) and #(utils.path_tail(workspace)) + 1
|
|
or #(fs.relative_from_home(workspace)) + 1
|
|
end
|
|
|
|
---@private
|
|
---@param workspace_tag string
|
|
---@return boolean
|
|
function EntryMaker:should_show_tail(workspace_tag)
|
|
local show_filter_column = config.show_filter_column
|
|
local filters = type(show_filter_column) == "table" and show_filter_column or { "LSP", "CWD" }
|
|
return vim.tbl_contains(filters, workspace_tag)
|
|
end
|
|
|
|
return EntryMaker
|