-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
163 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,164 @@ | ||
local async = require "plenary.async" --[[@as PlenaryAsync]] | ||
local log = require "plenary.log" | ||
|
||
---@class FrecencyFndr | ||
---@field private config FrecencyFndrConfig | ||
---@field config FrecencyFndrConfig | ||
---@field closed boolean | ||
---@field entries FrecencyEntry[] | ||
---@field private database FrecencyDatabase | ||
---@field private entry_maker FrecencyEntryMaker | ||
---@field private fs FrecencyFS | ||
---@field private recency FrecencyRecency | ||
---@field private rx PlenaryAsyncControlChannelRx | ||
---@field private state FrecencyState | ||
local Finder = {} | ||
|
||
---@class FrecencyFndrConfig | ||
---@field chunk_size integer default: 1000 | ||
---@field sleep_interval integer default: 50 | ||
|
||
---@param database FrecencyDatabase | ||
---@param entry_maker FrecencyEntryMaker | ||
---@param entry_maker fun(file: FrecencyFile): FrecencyEntry | ||
---@param fs FrecencyFS | ||
---@param path string | ||
---@param recency FrecencyRecency | ||
---@param state FrecencyState | ||
---@param workspace string? | ||
---@param datetime string? | ||
---@param config FrecencyFndrConfig? | ||
---@return FrecencyFndr | ||
Finder.new = function(database, entry_maker, fs, recency, config) | ||
return setmetatable({ | ||
config = vim.tbl_extend("force", { chunk_size = 1000 }, config or {}), | ||
Finder.new = function(database, entry_maker, fs, path, recency, state, workspace, datetime, config) | ||
local self = setmetatable({ | ||
config = vim.tbl_extend("force", { chunk_size = 1000, sleep_interval = 50 }, config or {}), | ||
closed = false, | ||
database = database, | ||
entry_maker = entry_maker, | ||
fs = fs, | ||
entries = {}, | ||
recency = recency, | ||
}, { __index = Finder }) | ||
state = state, | ||
}, { | ||
__index = Finder, | ||
---@param self FrecencyFndr | ||
__call = function(self, ...) | ||
return self:find(...) | ||
end, | ||
}) | ||
local tx, rx = async.control.channel.mpsc() | ||
self.rx = rx | ||
async.run(function() | ||
-- NOTE: return to the main loop | ||
async.util.sleep(0) | ||
local seen = {} | ||
for i, file in ipairs(self:get_results(workspace, datetime)) do | ||
local entry = entry_maker(file) | ||
seen[entry.filename] = true | ||
entry.index = i | ||
table.insert(self.entries, entry) | ||
tx.send(entry) | ||
end | ||
local count = 0 | ||
local index = #self.entries | ||
for name in fs:scan_dir(path) do | ||
if self.closed then | ||
break | ||
end | ||
local fullpath = fs.joinpath(path, name) | ||
if not seen[fullpath] then | ||
seen[fullpath] = true | ||
count = count + 1 | ||
local entry = entry_maker { id = 0, count = 0, path = fullpath, score = 0 } | ||
if entry then | ||
index = index + 1 | ||
entry.index = index | ||
table.insert(self.entries, entry) | ||
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 | ||
self:close() | ||
tx.send(nil) | ||
end) | ||
return self | ||
end | ||
|
||
---@class FrecencyFndrOptions | ||
---@field workspace string? | ||
---@field workspace_tag string? | ||
---@param _ string | ||
---@param process_result fun(entry: FrecencyEntry): nil | ||
---@param process_complete fun(): nil | ||
function Finder:find(_, process_result, process_complete) | ||
for _, entry in ipairs(self.entries) do | ||
if process_result(entry) then | ||
return | ||
end | ||
end | ||
while not self.closed do | ||
local entry = self.rx.recv() | ||
if not entry then | ||
break | ||
elseif entry.index > #self.entries and process_result(entry) then | ||
return | ||
end | ||
end | ||
process_complete() | ||
end | ||
|
||
---@param state FrecencyState | ||
---@param filepath_formatter FrecencyFilepathFormatter | ||
---@param opts FrecencyFndrOptions | ||
function Finder:start(state, filepath_formatter, opts) | ||
local entry_maker = self.entry_maker:create(filepath_formatter, opts.workspace, opts.workspace_tag) | ||
---@async | ||
---@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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters