telescope-frecency.nvim/lua/frecency/entry_maker.lua
JINNOUCHI Yasushi 58c0089414
fix!: register realpath for consistency (#240)
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
2024-08-15 17:40:03 +09:00

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