telescope-frecency.nvim/lua/frecency/finder.lua
JINNOUCHI Yasushi 767fbf074f
feat: check DB file has been changed (#143)
* refactor: unite logic for finder & async_finder

* chore: fix types

* chore: add sleep to show results at first

* refactor: fix to find results separatedly

* test: remove unnecessary ones and fix others

* test: add matrix for 0.9.x & Windows

* test: use forked plenary.log for Windows

* test: fix to use strptime in Windows

* test: run again if segmentation fault in Windows

* test: loosen timeout for Perl

* test: use the latest plenary.nvim again

* chore: fix types

* chore: change variable name

* feat: watch changes of DB to reload

* chore: add comments to steps

* test: copy whole modules for testing in Windows

* fix: make valid paths for Windows

* test: add tests for Native

* test: use robust way to calculate time

vim.fn.strptime cannot be used in Lua loop

* chore: fix comments

* refactor: simplify the code

* test: loosen condition to detect failures

* test: disable some logging

Many loggings make the test fail.

* test: run tests sequentially in Windows

* test: loosen timeout not to fail on Windows
2023-09-17 15:21:01 +09:00

192 lines
5.4 KiB
Lua

local async = require "plenary.async" --[[@as PlenaryAsync]]
local log = require "plenary.log"
---@class FrecencyFinder
---@field config FrecencyFinderConfig
---@field closed boolean
---@field entries FrecencyEntry[]
---@field entry_maker FrecencyEntryMakerInstance
---@field fs FrecencyFS
---@field need_scandir boolean
---@field path string?
---@field private database FrecencyDatabase
---@field private recency FrecencyRecency
---@field private rx PlenaryAsyncControlChannelRx
---@field private state FrecencyState
---@field private tx PlenaryAsyncControlChannelTx
local Finder = {}
---@class FrecencyFinderConfig
---@field chunk_size integer default: 1000
---@field sleep_interval integer default: 50
---@param database FrecencyDatabase
---@param entry_maker FrecencyEntryMakerInstance
---@param fs FrecencyFS
---@param need_scandir boolean
---@param path string?
---@param recency FrecencyRecency
---@param state FrecencyState
---@param config FrecencyFinderConfig?
---@return FrecencyFinder
Finder.new = function(database, entry_maker, fs, need_scandir, path, recency, state, config)
local tx, rx = async.control.channel.mpsc()
return setmetatable({
config = vim.tbl_extend("force", { chunk_size = 1000, sleep_interval = 50 }, config or {}),
closed = false,
database = database,
entries = {},
entry_maker = entry_maker,
fs = fs,
need_scandir = need_scandir,
path = path,
recency = recency,
rx = rx,
state = state,
tx = tx,
}, {
__index = Finder,
---@param self FrecencyFinder
__call = function(self, ...)
return self:find(...)
end,
})
end
---@param datetime string?
---@return nil
function Finder:start(datetime)
async.void(function()
-- NOTE: return to the main loop to show the main window
async.util.sleep(0)
local seen = {}
for i, file in ipairs(self:get_results(self.path, datetime)) do
local entry = self.entry_maker(file)
seen[entry.filename] = true
entry.index = i
table.insert(self.entries, entry)
self.tx.send(entry)
end
if self.need_scandir and self.path then
-- NOTE: return to the main loop to show results from DB
async.util.sleep(self.config.sleep_interval)
self:scan_dir(seen)
end
self:close()
self.tx.send(nil)
end)()
end
---@param seen table<string, boolean>
---@return nil
function Finder:scan_dir(seen)
local count = 0
local index = #self.entries
for name in self.fs:scan_dir(self.path) do
if self.closed then
break
end
local fullpath = self.fs.joinpath(self.path, name)
if not seen[fullpath] then
seen[fullpath] = true
count = count + 1
local entry = self.entry_maker { id = 0, count = 0, path = fullpath, score = 0 }
if entry then
index = index + 1
entry.index = index
table.insert(self.entries, entry)
self.tx.send(entry)
if count % self.config.chunk_size == 0 then
self:reflow_results()
async.util.sleep(self.config.sleep_interval)
end
end
end
end
end
---@param _ string
---@param process_result fun(entry: FrecencyEntry): nil
---@param process_complete fun(): nil
---@return nil
function Finder:find(_, process_result, process_complete)
local index = 0
for _, entry in ipairs(self.entries) do
index = index + 1
if process_result(entry) then
return
end
end
local count = 0
while not self.closed do
count = count + 1
local entry = self.rx.recv()
if not entry then
break
elseif entry.index > index and process_result(entry) then
return
end
end
process_complete()
end
---@param workspace string?
---@param datetime string?
---@return FrecencyFile[]
function Finder:get_results(workspace, datetime)
log.debug { workspace = workspace or "NONE" }
local start_fetch = os.clock()
local files = self.database:get_entries(workspace, datetime)
log.debug(("it takes %f seconds in fetching entries"):format(os.clock() - start_fetch))
local start_results = os.clock()
local elapsed_recency = 0
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.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))
local start_sort = os.clock()
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))
return files
end
function Finder:close()
self.closed = true
end
function Finder:reflow_results()
local picker = self.state:get()
if not picker then
return
end
local bufnr = picker.results_bufnr
local win = picker.results_win
if not bufnr or not win then
return
end
picker:clear_extra_rows(bufnr)
if picker.sorting_strategy == "descending" then
local manager = picker.manager
if not manager then
return
end
local worst_line = picker:get_row(manager:num_results())
---@type WinInfo
local wininfo = vim.fn.getwininfo(win)[1]
local bottom = vim.api.nvim_buf_line_count(bufnr)
if not self.reflowed or worst_line > wininfo.botline then
self.reflowed = true
vim.api.nvim_win_set_cursor(win, { bottom, 0 })
end
end
end
return Finder