mirror of
https://github.com/kristoferssolo/telescope-frecency.nvim.git
synced 2025-10-21 20:10:38 +00:00
feat: make query() faster and more lazier (#241)
* refactor: simplify logic to load web_devicons * refactor: make register() asynchronous * fix: load lazily modules outside this plugin * refactor: simplify logic to wait initialization * refactor: use uv.hrtime() instead of os.clock() * fix: avoid errors in calling plenary.log in async * test: store elapsed time to check in tests * test: fix module names This becomes a problem only in Ubuntu because macOS and Windows does not care cases in filenames. * test: fix types and unused modules * style: fix by stylua * refactor: make recency / entry_maker loaded lazily
This commit is contained in:
parent
673585ee99
commit
c140e6ff9c
@ -1,11 +1,13 @@
|
||||
local Table = require "frecency.database.table"
|
||||
local FileLock = require "frecency.file_lock"
|
||||
local Timer = require "frecency.timer"
|
||||
local config = require "frecency.config"
|
||||
local fs = require "frecency.fs"
|
||||
local watcher = require "frecency.watcher"
|
||||
local log = require "frecency.log"
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
|
||||
---@class FrecencyDatabaseEntry
|
||||
---@field ages number[]
|
||||
@ -184,7 +186,7 @@ end
|
||||
---@async
|
||||
---@return nil
|
||||
function Database:load()
|
||||
local start = os.clock()
|
||||
local timer = Timer.new "load()"
|
||||
local err, data = self:file_lock():with(function(target)
|
||||
local err, stat = async.uv.fs_stat(target)
|
||||
if err then
|
||||
@ -203,13 +205,13 @@ function Database:load()
|
||||
assert(not err, err)
|
||||
local tbl = vim.F.npcall(loadstring(data or ""))
|
||||
self.tbl:set(tbl)
|
||||
log.debug(("load() takes %f seconds"):format(os.clock() - start))
|
||||
timer:finish()
|
||||
end
|
||||
|
||||
---@async
|
||||
---@return nil
|
||||
function Database:save()
|
||||
local start = os.clock()
|
||||
local timer = Timer.new "save()"
|
||||
local err = self:file_lock():with(function(target)
|
||||
self:raw_save(self.tbl:raw(), target)
|
||||
local err, stat = async.uv.fs_stat(target)
|
||||
@ -218,7 +220,7 @@ function Database:save()
|
||||
return nil
|
||||
end)
|
||||
assert(not err, err)
|
||||
log.debug(("save() takes %f seconds"):format(os.clock() - start))
|
||||
timer:finish()
|
||||
end
|
||||
|
||||
---@async
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
local log = require "frecency.log"
|
||||
local async = require "plenary.async"
|
||||
local Timer = require "frecency.timer"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
|
||||
---@class FrecencyDatabaseRecordValue
|
||||
---@field count integer
|
||||
@ -47,13 +48,13 @@ end
|
||||
---@async
|
||||
---@return nil
|
||||
function Table:wait_ready()
|
||||
local start = os.clock()
|
||||
local timer = Timer.new "wait_ready()"
|
||||
local t = 0.2
|
||||
while not rawget(self, "is_ready") do
|
||||
async.util.sleep(t)
|
||||
t = t * 2
|
||||
end
|
||||
log.debug(("wait_ready() takes %f seconds"):format(os.clock() - start))
|
||||
timer:finish()
|
||||
end
|
||||
|
||||
return Table
|
||||
|
||||
@ -1,18 +1,18 @@
|
||||
local WebDevicons = require "frecency.web_devicons"
|
||||
local web_devicons = 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]]
|
||||
local Path = require "plenary.path"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local entry_display = lazy_require "telescope.pickers.entry_display" --[[@as FrecencyTelescopeEntryDisplay]]
|
||||
local utils = lazy_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 })
|
||||
return setmetatable({}, { __index = EntryMaker })
|
||||
end
|
||||
|
||||
---@class FrecencyEntry
|
||||
@ -86,7 +86,7 @@ function EntryMaker:width_items(workspace, workspace_tag)
|
||||
table.insert(width_items, { width = 6 }) -- fuzzy score
|
||||
end
|
||||
end
|
||||
if self.web_devicons.is_enabled then
|
||||
if not config.disable_devicons then
|
||||
table.insert(width_items, { width = 2 })
|
||||
end
|
||||
if config.show_filter_column and workspace and workspace_tag then
|
||||
@ -116,12 +116,11 @@ function EntryMaker:items(entry, workspace, workspace_tag, formatter)
|
||||
table.insert(items, { score, "TelescopeFrecencyScores" })
|
||||
end
|
||||
end
|
||||
if self.web_devicons.is_enabled then
|
||||
if not config.disable_devicons then
|
||||
local basename = utils.path_tail(entry.name)
|
||||
local icon, icon_highlight =
|
||||
self.web_devicons:get_icon(basename, utils.file_extension(basename), { default = false })
|
||||
local icon, icon_highlight = 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 })
|
||||
icon, icon_highlight = web_devicons.get_icon(basename, nil, { default = true })
|
||||
end
|
||||
table.insert(items, { icon, icon_highlight })
|
||||
end
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
local log = require "frecency.log"
|
||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
|
||||
---@class FrecencyFileLock
|
||||
---@field base string
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
local Timer = require "frecency.timer"
|
||||
local config = require "frecency.config"
|
||||
local fs = require "frecency.fs"
|
||||
local os_util = require "frecency.os_util"
|
||||
local recency = require "frecency.recency"
|
||||
local log = require "frecency.log"
|
||||
local Job = require "plenary.job"
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local Job = lazy_require "plenary.job" --[[@as FrecencyPlenaryJob]]
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
|
||||
---@class FrecencyFinder
|
||||
---@field config FrecencyFinderConfig
|
||||
@ -21,7 +24,6 @@ local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
---@field private need_scan_dir boolean
|
||||
---@field private seen table<string, boolean>
|
||||
---@field private process VimSystemObj?
|
||||
---@field private recency FrecencyRecency
|
||||
---@field private state FrecencyState
|
||||
local Finder = {}
|
||||
|
||||
@ -34,11 +36,10 @@ local Finder = {}
|
||||
---@param entry_maker FrecencyEntryMakerInstance
|
||||
---@param need_scandir boolean
|
||||
---@param path string?
|
||||
---@param recency FrecencyRecency
|
||||
---@param state FrecencyState
|
||||
---@param finder_config? FrecencyFinderConfig
|
||||
---@return FrecencyFinder
|
||||
Finder.new = function(database, entry_maker, need_scandir, path, recency, state, finder_config)
|
||||
Finder.new = function(database, entry_maker, need_scandir, path, state, finder_config)
|
||||
local tx, rx = async.control.channel.mpsc()
|
||||
local scan_tx, scan_rx = async.control.channel.mpsc()
|
||||
local self = setmetatable({
|
||||
@ -47,7 +48,6 @@ Finder.new = function(database, entry_maker, need_scandir, path, recency, state,
|
||||
database = database,
|
||||
entry_maker = entry_maker,
|
||||
path = path,
|
||||
recency = recency,
|
||||
state = state,
|
||||
|
||||
seen = {},
|
||||
@ -257,25 +257,21 @@ end
|
||||
---@return FrecencyFile[]
|
||||
function Finder:get_results(workspace, epoch)
|
||||
log.debug { workspace = workspace or "NONE" }
|
||||
local start_fetch = os.clock()
|
||||
local timer_fetch = Timer.new "fetching entries"
|
||||
local files = self.database:get_entries(workspace, epoch)
|
||||
log.debug(("it takes %f seconds in fetching entries"):format(os.clock() - start_fetch))
|
||||
local start_results = os.clock()
|
||||
local elapsed_recency = 0
|
||||
timer_fetch:finish()
|
||||
local timer_results = Timer.new "making results"
|
||||
for _, file in ipairs(files) do
|
||||
local start_recency = os.clock()
|
||||
file.score = file.ages and self.recency:calculate(file.count, file.ages) or 0
|
||||
file.score = file.ages and recency.calculate(file.count, file.ages) or 0
|
||||
file.ages = nil
|
||||
elapsed_recency = elapsed_recency + (os.clock() - start_recency)
|
||||
end
|
||||
log.debug(("it takes %f seconds in calculating recency"):format(elapsed_recency))
|
||||
log.debug(("it takes %f seconds in making results"):format(os.clock() - start_results))
|
||||
timer_results:finish()
|
||||
|
||||
local start_sort = os.clock()
|
||||
local timer_sort = Timer.new "sorting"
|
||||
table.sort(files, function(a, b)
|
||||
return a.score > b.score or (a.score == b.score and a.path > b.path)
|
||||
end)
|
||||
log.debug(("it takes %f seconds in sorting"):format(os.clock() - start_sort))
|
||||
timer_sort:finish()
|
||||
return files
|
||||
end
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
local config = require "frecency.config"
|
||||
local os_util = require "frecency.os_util"
|
||||
local log = require "frecency.log"
|
||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local scandir = require "plenary.scandir"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local scandir = lazy_require "plenary.scandir"
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
---@class FrecencyFS
|
||||
local M = {
|
||||
os_homedir = assert(uv.os_homedir()),
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
local config = require "frecency.config"
|
||||
local sorters = require "telescope.sorters"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local sorters = lazy_require "telescope.sorters"
|
||||
|
||||
---@param opts any options for get_fzy_sorter()
|
||||
return function(opts)
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
---@field complete fun(findstart: 1|0, base: string): integer|''|string[]
|
||||
---@field delete async fun(path: string): nil
|
||||
---@field query fun(opts?: FrecencyQueryOpts): FrecencyQueryEntry[]|string[]
|
||||
---@field register fun(bufnr: integer, datetime: string?): nil
|
||||
---@field register async fun(bufnr: integer, datetime: string?): nil
|
||||
---@field start fun(opts: FrecencyPickerOptions?): nil
|
||||
---@field validate_database async fun(force: boolean?): nil
|
||||
local frecency = setmetatable({}, {
|
||||
@ -43,7 +43,9 @@ local function setup(ext_config)
|
||||
return
|
||||
end
|
||||
|
||||
require("frecency.config").setup(ext_config)
|
||||
local config = require "frecency.config"
|
||||
|
||||
config.setup(ext_config)
|
||||
|
||||
vim.api.nvim_set_hl(0, "TelescopeBufferLoaded", { link = "String", default = true })
|
||||
vim.api.nvim_set_hl(0, "TelescopePathSeparator", { link = "Directory", default = true })
|
||||
@ -76,9 +78,10 @@ local function setup(ext_config)
|
||||
return
|
||||
end
|
||||
local is_floatwin = vim.api.nvim_win_get_config(0).relative ~= ""
|
||||
if not is_floatwin then
|
||||
frecency.register(args.buf)
|
||||
if is_floatwin or (config.ignore_register and config.ignore_register(args.buf)) then
|
||||
return
|
||||
end
|
||||
async_call(frecency.register, args.buf, vim.api.nvim_buf_get_name(args.buf))
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
@ -1,54 +1,46 @@
|
||||
local Database = require "frecency.database"
|
||||
local EntryMaker = require "frecency.entry_maker"
|
||||
local Picker = require "frecency.picker"
|
||||
local Recency = require "frecency.recency"
|
||||
local Timer = require "frecency.timer"
|
||||
local config = require "frecency.config"
|
||||
local fs = require "frecency.fs"
|
||||
local log = require "frecency.log"
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local recency = require "frecency.recency"
|
||||
local wait = require "frecency.wait"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
|
||||
---@class Frecency
|
||||
---@field private buf_registered table<integer, boolean> flag to indicate the buffer is registered to the database.
|
||||
---@field private database FrecencyDatabase
|
||||
---@field private entry_maker FrecencyEntryMaker
|
||||
---@field private picker FrecencyPicker
|
||||
---@field private recency FrecencyRecency
|
||||
local Frecency = {}
|
||||
|
||||
---@return Frecency
|
||||
Frecency.new = function()
|
||||
local self = setmetatable({ buf_registered = {} }, { __index = Frecency }) --[[@as Frecency]]
|
||||
self.database = Database.new()
|
||||
self.entry_maker = EntryMaker.new()
|
||||
self.recency = Recency.new()
|
||||
return self
|
||||
end
|
||||
|
||||
---This is called when `:Telescope frecency` is called at the first time.
|
||||
---@return nil
|
||||
function Frecency:setup()
|
||||
local done = false
|
||||
---@async
|
||||
local function init()
|
||||
local timer = Timer.new "init()"
|
||||
self.database:start()
|
||||
self:assert_db_entries()
|
||||
if config.auto_validate then
|
||||
self:validate_database()
|
||||
end
|
||||
done = true
|
||||
timer:finish()
|
||||
end
|
||||
|
||||
local is_async = not not coroutine.running()
|
||||
if is_async then
|
||||
init()
|
||||
else
|
||||
async.void(init)()
|
||||
local ok, status = vim.wait(10000, function()
|
||||
return done
|
||||
end)
|
||||
if not ok then
|
||||
error("failed to setup:" .. (status == -1 and "timed out" or "interrupted"))
|
||||
end
|
||||
wait(init)
|
||||
end
|
||||
end
|
||||
|
||||
@ -56,7 +48,7 @@ end
|
||||
---@param opts? FrecencyPickerOptions
|
||||
---@return nil
|
||||
function Frecency:start(opts)
|
||||
local start = os.clock()
|
||||
local timer = Timer.new "start()"
|
||||
log.debug "Frecency:start"
|
||||
opts = opts or {}
|
||||
if opts.cwd then
|
||||
@ -66,13 +58,13 @@ function Frecency:start(opts)
|
||||
if opts.hide_current_buffer or config.hide_current_buffer then
|
||||
ignore_filenames = { vim.api.nvim_buf_get_name(0) }
|
||||
end
|
||||
self.picker = Picker.new(self.database, self.entry_maker, self.recency, {
|
||||
self.picker = Picker.new(self.database, {
|
||||
editing_bufnr = vim.api.nvim_get_current_buf(),
|
||||
ignore_filenames = ignore_filenames,
|
||||
initial_workspace_tag = opts.workspace,
|
||||
})
|
||||
self.picker:start(vim.tbl_extend("force", config.get(), opts))
|
||||
log.debug(("Frecency:start picker:start takes %f seconds"):format(os.clock() - start))
|
||||
timer:finish()
|
||||
end
|
||||
|
||||
---This can be calledBy `require("telescope").extensions.frecency.complete`.
|
||||
@ -128,25 +120,21 @@ function Frecency:assert_db_entries()
|
||||
end
|
||||
end
|
||||
|
||||
---@async
|
||||
---@param bufnr integer
|
||||
---@param path string
|
||||
---@param epoch? integer
|
||||
function Frecency:register(bufnr, epoch)
|
||||
if (config.ignore_register and config.ignore_register(bufnr)) or self.buf_registered[bufnr] then
|
||||
function Frecency:register(bufnr, path, epoch)
|
||||
if self.buf_registered[bufnr] or not fs.is_valid_path(path) then
|
||||
return
|
||||
end
|
||||
local path = vim.api.nvim_buf_get_name(bufnr)
|
||||
async.void(function()
|
||||
if not fs.is_valid_path(path) then
|
||||
return
|
||||
end
|
||||
local err, realpath = async.uv.fs_realpath(path)
|
||||
if err or not realpath then
|
||||
return
|
||||
end
|
||||
self.database:update(realpath, epoch)
|
||||
self.buf_registered[bufnr] = true
|
||||
log.debug("registered:", bufnr, path)
|
||||
end)()
|
||||
local err, realpath = async.uv.fs_realpath(path)
|
||||
if err or not realpath then
|
||||
return
|
||||
end
|
||||
self.database:update(realpath, epoch)
|
||||
self.buf_registered[bufnr] = true
|
||||
log.debug("registered:", bufnr, path)
|
||||
end
|
||||
|
||||
---@async
|
||||
@ -191,7 +179,7 @@ function Frecency:query(opts, epoch)
|
||||
return {
|
||||
count = entry.count,
|
||||
path = entry.path,
|
||||
score = entry.ages and self.recency:calculate(entry.count, entry.ages) or 0,
|
||||
score = entry.ages and recency.calculate(entry.count, entry.ages) or 0,
|
||||
timestamps = entry.timestamps,
|
||||
}
|
||||
end, self.database:get_entries(opts.workspace, epoch))
|
||||
|
||||
11
lua/frecency/lazy_require.lua
Normal file
11
lua/frecency/lazy_require.lua
Normal file
@ -0,0 +1,11 @@
|
||||
---@param module string
|
||||
return function(module)
|
||||
return setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
return require(module)[key]
|
||||
end,
|
||||
__call = function(_, ...)
|
||||
return require(module)(...)
|
||||
end,
|
||||
})
|
||||
end
|
||||
@ -1,8 +1,11 @@
|
||||
local config = require "frecency.config"
|
||||
local log = require "plenary.log"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local log = lazy_require "plenary.log"
|
||||
|
||||
return setmetatable({}, {
|
||||
__index = function(_, key)
|
||||
return config.debug and log[key] or function() end
|
||||
return config.debug and vim.schedule_wrap(function(...)
|
||||
log[key](...)
|
||||
end) or function() end
|
||||
end,
|
||||
})
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
local Path = require "plenary.path"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
---@class FrecencyOSUtil
|
||||
local M = {
|
||||
is_windows = uv.os_uname().sysname == "Windows_NT",
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
local EntryMaker = require "frecency.entry_maker"
|
||||
local State = require "frecency.state"
|
||||
local Finder = require "frecency.finder"
|
||||
local config = require "frecency.config"
|
||||
@ -5,11 +6,12 @@ local fs = require "frecency.fs"
|
||||
local fuzzy_sorter = require "frecency.fuzzy_sorter"
|
||||
local substr_sorter = require "frecency.substr_sorter"
|
||||
local log = require "frecency.log"
|
||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local actions = require "telescope.actions"
|
||||
local config_values = require("telescope.config").values
|
||||
local pickers = require "telescope.pickers"
|
||||
local utils = require "telescope.utils" --[[@as FrecencyTelescopeUtils]]
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||
local actions = lazy_require "telescope.actions"
|
||||
local telescope_config = lazy_require "telescope.config"
|
||||
local pickers = lazy_require "telescope.pickers"
|
||||
local utils = lazy_require "telescope.utils" --[[@as FrecencyTelescopeUtils]]
|
||||
local uv = vim.loop or vim.uv
|
||||
|
||||
---@class FrecencyPicker
|
||||
@ -18,7 +20,6 @@ local uv = vim.loop or vim.uv
|
||||
---@field private entry_maker FrecencyEntryMaker
|
||||
---@field private lsp_workspaces string[]
|
||||
---@field private namespace integer
|
||||
---@field private recency FrecencyRecency
|
||||
---@field private state FrecencyState
|
||||
---@field private workspace string?
|
||||
---@field private workspace_tag_regex string
|
||||
@ -37,18 +38,15 @@ local Picker = {}
|
||||
---@field score number
|
||||
|
||||
---@param database FrecencyDatabase
|
||||
---@param entry_maker FrecencyEntryMaker
|
||||
---@param recency FrecencyRecency
|
||||
---@param picker_config FrecencyPickerConfig
|
||||
---@return FrecencyPicker
|
||||
Picker.new = function(database, entry_maker, recency, picker_config)
|
||||
Picker.new = function(database, picker_config)
|
||||
local self = setmetatable({
|
||||
config = picker_config,
|
||||
database = database,
|
||||
entry_maker = entry_maker,
|
||||
entry_maker = EntryMaker.new(),
|
||||
lsp_workspaces = {},
|
||||
namespace = vim.api.nvim_create_namespace "frecency",
|
||||
recency = recency,
|
||||
}, { __index = Picker })
|
||||
local d = config.filter_delimiter
|
||||
self.workspace_tag_regex = "^%s*" .. d .. "(%S+)" .. d
|
||||
@ -80,7 +78,6 @@ function Picker:finder(opts, workspace, workspace_tag)
|
||||
entry_maker,
|
||||
need_scandir,
|
||||
workspace,
|
||||
self.recency,
|
||||
self.state,
|
||||
{ ignore_filenames = self.config.ignore_filenames }
|
||||
)
|
||||
@ -93,7 +90,7 @@ function Picker:start(opts)
|
||||
path_display = function(picker_opts, path)
|
||||
return self:default_path_display(picker_opts, path)
|
||||
end,
|
||||
}, config_values, opts or {}) --[[@as FrecencyPickerOptions]]
|
||||
}, telescope_config.values, opts or {}) --[[@as FrecencyPickerOptions]]
|
||||
self.workspace = self:get_workspace(opts.cwd, self.config.initial_workspace_tag or config.default_workspace)
|
||||
log.debug { workspace = self.workspace }
|
||||
|
||||
@ -102,7 +99,7 @@ function Picker:start(opts)
|
||||
local picker = pickers.new(opts, {
|
||||
prompt_title = "Frecency",
|
||||
finder = finder,
|
||||
previewer = config_values.file_previewer(opts),
|
||||
previewer = telescope_config.values.file_previewer(opts),
|
||||
sorter = config.matcher == "default" and substr_sorter() or fuzzy_sorter(opts),
|
||||
on_input_filter_cb = self:on_input_filter_cb(opts),
|
||||
attach_mappings = function(prompt_bufnr)
|
||||
|
||||
@ -1,23 +1,15 @@
|
||||
local config = require "frecency.config"
|
||||
|
||||
---@class FrecencyRecency
|
||||
---@field private modifier table<integer, { age: integer, value: integer }>
|
||||
local Recency = {}
|
||||
|
||||
---@return FrecencyRecency
|
||||
Recency.new = function()
|
||||
return setmetatable({
|
||||
modifier = config.recency_values,
|
||||
}, { __index = Recency })
|
||||
end
|
||||
local M = {}
|
||||
|
||||
---@param count integer
|
||||
---@param ages number[]
|
||||
---@return number
|
||||
function Recency:calculate(count, ages)
|
||||
function M.calculate(count, ages)
|
||||
local score = 0
|
||||
for _, age in ipairs(ages) do
|
||||
for _, rank in ipairs(self.modifier) do
|
||||
for _, rank in ipairs(config.recency_values) do
|
||||
if age <= rank.age then
|
||||
score = score + rank.value
|
||||
goto continue
|
||||
@ -28,4 +20,4 @@ function Recency:calculate(count, ages)
|
||||
return count * score / config.max_timestamps
|
||||
end
|
||||
|
||||
return Recency
|
||||
return M
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
-- TODO: use this module until telescope's release include this below.
|
||||
-- https://github.com/nvim-telescope/telescope.nvim/pull/2950
|
||||
|
||||
local sorters = require "telescope.sorters"
|
||||
local util = require "telescope.utils"
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local sorters = lazy_require "telescope.sorters"
|
||||
local util = lazy_require "telescope.utils"
|
||||
|
||||
local substr_highlighter = function(make_display)
|
||||
return function(_, prompt, display)
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
-- https://github.com/nvim-lua/plenary.nvim/blob/663246936325062427597964d81d30eaa42ab1e4/lua/plenary/test_harness.lua#L86-L86
|
||||
vim.opt.runtimepath:append(vim.env.TELESCOPE_PATH)
|
||||
|
||||
local Timer = require "frecency.timer"
|
||||
local util = require "frecency.tests.util"
|
||||
local log = require "plenary.log"
|
||||
|
||||
@ -183,7 +184,7 @@ describe("frecency", function()
|
||||
register(file, make_epoch "2023-07-29T00:00:00+09:00")
|
||||
log.new({}, true)
|
||||
end
|
||||
local start = os.clock()
|
||||
local timer = Timer.new "all results"
|
||||
local results = vim.tbl_map(function(result)
|
||||
result.timestamps = nil
|
||||
return result
|
||||
@ -191,11 +192,10 @@ describe("frecency", function()
|
||||
table.sort(results, function(a, b)
|
||||
return a.path < b.path
|
||||
end)
|
||||
local elapsed = os.clock() - start
|
||||
log.info(("it takes %f seconds in fetching all results"):format(elapsed))
|
||||
timer:finish()
|
||||
|
||||
it("returns appropriate latency (<1.0 second)", function()
|
||||
assert.are.is_true(elapsed < 1.0)
|
||||
assert.are.is_true(timer.elapsed < 1.0)
|
||||
end)
|
||||
|
||||
it("returns valid response", function()
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
local Recency = require "frecency.recency"
|
||||
local recency = Recency.new()
|
||||
local recency = require "frecency.recency"
|
||||
|
||||
describe("frecency.recency", function()
|
||||
for _, c in ipairs {
|
||||
@ -13,7 +12,7 @@ describe("frecency.recency", function()
|
||||
} do
|
||||
local dumped = vim.inspect(c.ages, { indent = "", newline = "" })
|
||||
it(("%d, %s => %d"):format(c.count, dumped, c.score), function()
|
||||
assert.are.same(c.score, recency:calculate(c.count, c.ages))
|
||||
assert.are.same(c.score, recency.calculate(c.count, c.ages))
|
||||
end)
|
||||
end
|
||||
end)
|
||||
|
||||
@ -7,7 +7,7 @@ local log = require "plenary.log"
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local Path = require "plenary.path"
|
||||
local Job = require "plenary.job"
|
||||
local wait = require "frecency.tests.wait"
|
||||
local wait = require "frecency.wait"
|
||||
|
||||
---@return FrecencyPlenaryPath
|
||||
---@return fun(): nil close swwp all entries
|
||||
@ -101,7 +101,7 @@ local function with_files(files, cb_or_config, callback)
|
||||
frecency.database:start()
|
||||
frecency.database.tbl:wait_ready()
|
||||
end)
|
||||
frecency.picker = Picker.new(frecency.database, frecency.entry_maker, frecency.recency, { editing_bufnr = 0 })
|
||||
frecency.picker = Picker.new(frecency.database, { editing_bufnr = 0 })
|
||||
local finder = frecency.picker:finder {}
|
||||
callback(frecency, finder, dir)
|
||||
close()
|
||||
@ -116,16 +116,28 @@ end
|
||||
---@return fun(file: string, epoch: integer, reset: boolean?, wipeout?: boolean): nil reset: boolean?): nil
|
||||
local function make_register(frecency, dir)
|
||||
return function(file, epoch, reset, wipeout)
|
||||
-- NOTE: this function does the same thing as BufWinEnter autocmd.
|
||||
---@param bufnr integer
|
||||
local function register(bufnr)
|
||||
if vim.api.nvim_buf_get_name(bufnr) == "" then
|
||||
return
|
||||
end
|
||||
local is_floatwin = vim.api.nvim_win_get_config(0).relative ~= ""
|
||||
if is_floatwin or (config.ignore_register and config.ignore_register(bufnr)) then
|
||||
return
|
||||
end
|
||||
async.util.block_on(function()
|
||||
frecency:register(bufnr, vim.api.nvim_buf_get_name(bufnr), epoch)
|
||||
end)
|
||||
end
|
||||
|
||||
local path = filepath(dir, file)
|
||||
vim.cmd.edit(path)
|
||||
local bufnr = assert(vim.fn.bufnr(path))
|
||||
if reset then
|
||||
frecency.buf_registered[bufnr] = nil
|
||||
end
|
||||
frecency:register(bufnr, epoch)
|
||||
vim.wait(1000, function()
|
||||
return not not frecency.buf_registered[bufnr]
|
||||
end)
|
||||
register(bufnr)
|
||||
-- HACK: This is needed because almost the same filenames use the same
|
||||
-- buffer.
|
||||
if wipeout then
|
||||
@ -154,7 +166,7 @@ local function with_fake_register(frecency, dir, callback)
|
||||
bufnr = bufnr + 1
|
||||
buffers[bufnr] = path
|
||||
async.util.block_on(function()
|
||||
frecency:register(bufnr, epoch)
|
||||
frecency:register(bufnr, path, epoch)
|
||||
end)
|
||||
end
|
||||
callback(register)
|
||||
|
||||
26
lua/frecency/timer.lua
Normal file
26
lua/frecency/timer.lua
Normal file
@ -0,0 +1,26 @@
|
||||
local config = require "frecency.config"
|
||||
local log = require "frecency.log"
|
||||
local uv = vim.uv or vim.loop
|
||||
|
||||
---@class FrecencyTimer
|
||||
---@field elapsed number
|
||||
---@field start integer
|
||||
---@field title string
|
||||
local Timer = {}
|
||||
|
||||
---@param title string
|
||||
---@return FrecencyTimer
|
||||
Timer.new = function(title)
|
||||
return setmetatable({ start = uv.hrtime(), title = title }, { __index = Timer })
|
||||
end
|
||||
|
||||
---@return nil
|
||||
function Timer:finish()
|
||||
if not config.debug then
|
||||
return
|
||||
end
|
||||
self.elapsed = (uv.hrtime() - self.start) / 1000000000
|
||||
log.debug(("[%s] takes %.3f seconds"):format(self.title, self.elapsed))
|
||||
end
|
||||
|
||||
return Timer
|
||||
@ -2,6 +2,20 @@
|
||||
|
||||
-- NOTE: types are borrowed from plenary.nvim
|
||||
|
||||
---@class FrecencyPlenaryJob
|
||||
---@field new fun(self: FrecencyPlenaryJob, opts: FrecencyPlenaryJobOpts): FrecencyPlenaryJob
|
||||
---@field start fun(self: FrecencyPlenaryJob): nil
|
||||
---@field handle VimSystemObj uv_process_t
|
||||
|
||||
---@class FrecencyPlenaryJobOpts
|
||||
---@field cwd? string
|
||||
---@field command? string
|
||||
---@field args? string[]
|
||||
---@field on_stdout? FrecencyPlenaryJobCallback
|
||||
---@field on_stderr? FrecencyPlenaryJobCallback
|
||||
|
||||
---@alias FrecencyPlenaryJobCallback fun(error: string, data: string, self?: FrecencyPlenaryJob)
|
||||
|
||||
---@class FrecencyPlenaryPath
|
||||
---@field new fun(self: FrecencyPlenaryPath|string, path?: string): FrecencyPlenaryPath
|
||||
---@field absolute fun(): string
|
||||
|
||||
@ -1,19 +1,20 @@
|
||||
local log = require "frecency.log"
|
||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local lazy_require = require "frecency.lazy_require"
|
||||
local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||
local uv = vim.loop or vim.uv
|
||||
|
||||
---@class FrecencyNativeWatcherMtime
|
||||
---@class FrecencyWatcherMtime
|
||||
---@field sec integer
|
||||
---@field nsec integer
|
||||
local Mtime = {}
|
||||
|
||||
---@param mtime FsStatMtime
|
||||
---@return FrecencyNativeWatcherMtime
|
||||
---@return FrecencyWatcherMtime
|
||||
Mtime.new = function(mtime)
|
||||
return setmetatable({ sec = mtime.sec, nsec = mtime.nsec }, Mtime)
|
||||
end
|
||||
|
||||
---@param other FrecencyNativeWatcherMtime
|
||||
---@param other FrecencyWatcherMtime
|
||||
---@return boolean
|
||||
function Mtime:__eq(other)
|
||||
return self.sec == other.sec and self.nsec == other.nsec
|
||||
@ -24,13 +25,13 @@ function Mtime:__tostring()
|
||||
return string.format("%d.%d", self.sec, self.nsec)
|
||||
end
|
||||
|
||||
---@class FrecencyNativeWatcher
|
||||
---@class FrecencyWatcher
|
||||
---@field handler UvFsEventHandle
|
||||
---@field path string
|
||||
---@field mtime FrecencyNativeWatcherMtime
|
||||
---@field mtime FrecencyWatcherMtime
|
||||
local Watcher = {}
|
||||
|
||||
---@return FrecencyNativeWatcher
|
||||
---@return FrecencyWatcher
|
||||
Watcher.new = function()
|
||||
return setmetatable({ path = "", mtime = Mtime.new { sec = 0, nsec = 0 } }, { __index = Watcher })
|
||||
end
|
||||
|
||||
@ -1,32 +1,14 @@
|
||||
local config = require "frecency.config"
|
||||
---@class FrecencyWebDevicons
|
||||
local M = {
|
||||
---@param name string?
|
||||
---@param ext string?
|
||||
---@param opts table?
|
||||
---@return string
|
||||
---@return string
|
||||
get_icon = function(name, ext, opts)
|
||||
local ok, web_devicons = pcall(require, "nvim-web-devicons")
|
||||
return ok and web_devicons.get_icon(name, ext, opts) or "", ""
|
||||
end,
|
||||
}
|
||||
|
||||
---@class WebDeviconsModule
|
||||
---@field get_icon fun(name?: string, ext?: string, opts?: table): string, string
|
||||
|
||||
---@class WebDevicons
|
||||
---@field is_enabled boolean
|
||||
---@field private web_devicons WebDeviconsModule
|
||||
local WebDevicons = {}
|
||||
|
||||
---@return WebDevicons
|
||||
WebDevicons.new = function()
|
||||
local ok, web_devicons = pcall(require, "nvim-web-devicons")
|
||||
return setmetatable(
|
||||
{ is_enabled = not config.disable_devicons and ok, web_devicons = web_devicons },
|
||||
{ __index = WebDevicons }
|
||||
)
|
||||
end
|
||||
|
||||
---@param name string?
|
||||
---@param ext string?
|
||||
---@param opts table?
|
||||
---@return string
|
||||
---@return string
|
||||
function WebDevicons:get_icon(name, ext, opts)
|
||||
if self.is_enabled then
|
||||
return self.web_devicons.get_icon(name, ext, opts)
|
||||
end
|
||||
return "", ""
|
||||
end
|
||||
|
||||
return WebDevicons
|
||||
return M
|
||||
|
||||
Loading…
Reference in New Issue
Block a user