mirror of
https://github.com/kristoferssolo/telescope-frecency.nvim.git
synced 2025-12-30 13:21:50 +00:00
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
This commit is contained in:
parent
39f70a87a2
commit
58c0089414
@ -326,14 +326,15 @@ This will be called by |telescope.nvim| for its initialization. You can also
|
|||||||
call this to initialize this plugin separated from |telescope.nvim|'s
|
call this to initialize this plugin separated from |telescope.nvim|'s
|
||||||
initialization phase.
|
initialization phase.
|
||||||
|
|
||||||
This is useful when you want to load |telescope.nvim| lazily, but want to
|
This is useful when you want to load |telescope.nvim| lazily and want to
|
||||||
register opened files as soon as Neovim has started. Example configuration for
|
register opened files as soon as Neovim has started. Example configuration for
|
||||||
|lazy.nvim| is below.
|
|lazy.nvim| is below.
|
||||||
>lua
|
>lua
|
||||||
{
|
{
|
||||||
"nvim-telescope/telescope-frecency.nvim",
|
"nvim-telescope/telescope-frecency.nvim",
|
||||||
main = "frecency",
|
main = "frecency",
|
||||||
---@type FrecencyOpts
|
-- `opts` property calls the plugin's setup() function.
|
||||||
|
-- In this case, this calls frecency.setup().
|
||||||
opts = {
|
opts = {
|
||||||
db_safe_mode = false,
|
db_safe_mode = false,
|
||||||
},
|
},
|
||||||
@ -358,7 +359,10 @@ register opened files as soon as Neovim has started. Example configuration for
|
|||||||
telescope.load_extension "frecency"
|
telescope.load_extension "frecency"
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
|
<
|
||||||
|
This function does nothing when it is called for the second times and later.
|
||||||
|
If you want to set another configuration, use
|
||||||
|
|telescope-frecency-configuration-config.setup()|.
|
||||||
|
|
||||||
==============================================================================
|
==============================================================================
|
||||||
CONFIGURATION *telescope-frecency-configuration*
|
CONFIGURATION *telescope-frecency-configuration*
|
||||||
|
|||||||
@ -26,6 +26,7 @@ local os_util = require "frecency.os_util"
|
|||||||
|
|
||||||
---@class FrecencyConfig: FrecencyRawConfig
|
---@class FrecencyConfig: FrecencyRawConfig
|
||||||
---@field ext_config FrecencyRawConfig
|
---@field ext_config FrecencyRawConfig
|
||||||
|
---@field private cached_ignore_regexes? string[]
|
||||||
---@field private values FrecencyRawConfig
|
---@field private values FrecencyRawConfig
|
||||||
local Config = {}
|
local Config = {}
|
||||||
|
|
||||||
@ -79,6 +80,7 @@ Config.new = function()
|
|||||||
workspaces = true,
|
workspaces = true,
|
||||||
}
|
}
|
||||||
return setmetatable({
|
return setmetatable({
|
||||||
|
cached_ignore_regexes = {},
|
||||||
ext_config = {},
|
ext_config = {},
|
||||||
values = Config.default_values,
|
values = Config.default_values,
|
||||||
}, {
|
}, {
|
||||||
@ -138,6 +140,17 @@ Config.get = function()
|
|||||||
return config.values
|
return config.values
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@return string[]
|
||||||
|
Config.ignore_regexes = function()
|
||||||
|
if not config.cached_ignore_regexes then
|
||||||
|
config.cached_ignore_regexes = vim.tbl_map(function(pattern)
|
||||||
|
local regex = vim.pesc(pattern):gsub("%%%*", ".*"):gsub("%%%?", ".")
|
||||||
|
return "^" .. regex .. "$"
|
||||||
|
end, config.ignore_patterns)
|
||||||
|
end
|
||||||
|
return config.cached_ignore_regexes
|
||||||
|
end
|
||||||
|
|
||||||
---@param ext_config any
|
---@param ext_config any
|
||||||
---@return nil
|
---@return nil
|
||||||
Config.setup = function(ext_config)
|
Config.setup = function(ext_config)
|
||||||
@ -174,6 +187,7 @@ Config.setup = function(ext_config)
|
|||||||
workspace_scan_cmd = { opts.workspace_scan_cmd, { "s", "t" }, true },
|
workspace_scan_cmd = { opts.workspace_scan_cmd, { "s", "t" }, true },
|
||||||
workspaces = { opts.workspaces, "t" },
|
workspaces = { opts.workspaces, "t" },
|
||||||
}
|
}
|
||||||
|
config.cached_ignore_regexes = nil
|
||||||
config.ext_config = ext_config
|
config.ext_config = ext_config
|
||||||
config.values = opts
|
config.values = opts
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
local Table = require "frecency.database.table"
|
local Table = require "frecency.database.table"
|
||||||
local FileLock = require "frecency.file_lock"
|
local FileLock = require "frecency.file_lock"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fs = require "frecency.fs"
|
||||||
local watcher = require "frecency.watcher"
|
local watcher = require "frecency.watcher"
|
||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||||
@ -13,48 +14,73 @@ local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
|||||||
---@field score number
|
---@field score number
|
||||||
---@field timestamps integer[]
|
---@field timestamps integer[]
|
||||||
|
|
||||||
|
---@alias FrecencyDatabaseVersion "v1"
|
||||||
|
|
||||||
---@class FrecencyDatabase
|
---@class FrecencyDatabase
|
||||||
---@field tx FrecencyPlenaryAsyncControlChannelTx
|
---@field private _file_lock FrecencyFileLock
|
||||||
---@field private file_lock FrecencyFileLock
|
---@field private file_lock_rx async fun(): ...
|
||||||
---@field private filename string
|
---@field private file_lock_tx fun(...): nil
|
||||||
---@field private fs FrecencyFS
|
|
||||||
---@field private tbl FrecencyDatabaseTable
|
---@field private tbl FrecencyDatabaseTable
|
||||||
---@field private version "v1"
|
---@field private version FrecencyDatabaseVersion
|
||||||
|
---@field private watcher_rx FrecencyPlenaryAsyncControlChannelRx
|
||||||
|
---@field private watcher_tx FrecencyPlenaryAsyncControlChannelTx
|
||||||
local Database = {}
|
local Database = {}
|
||||||
|
|
||||||
---@param fs FrecencyFS
|
|
||||||
---@return FrecencyDatabase
|
---@return FrecencyDatabase
|
||||||
Database.new = function(fs)
|
Database.new = function()
|
||||||
local version = "v1"
|
local version = "v1"
|
||||||
local self = setmetatable({
|
local file_lock_tx, file_lock_rx = async.control.channel.oneshot()
|
||||||
fs = fs,
|
local watcher_tx, watcher_rx = async.control.channel.mpsc()
|
||||||
|
return setmetatable({
|
||||||
|
file_lock_rx = file_lock_rx,
|
||||||
|
file_lock_tx = file_lock_tx,
|
||||||
tbl = Table.new(version),
|
tbl = Table.new(version),
|
||||||
version = version,
|
version = version,
|
||||||
|
watcher_rx = watcher_rx,
|
||||||
|
watcher_tx = watcher_tx,
|
||||||
}, { __index = Database })
|
}, { __index = Database })
|
||||||
self.filename = (function()
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@return string
|
||||||
|
function Database:filename()
|
||||||
|
local file_v1 = "file_frecency.bin"
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@return string
|
||||||
|
local function filename_v1()
|
||||||
-- NOTE: for backward compatibility
|
-- NOTE: for backward compatibility
|
||||||
-- If the user does not set db_root specifically, search DB in
|
-- If the user does not set db_root specifically, search DB in
|
||||||
-- $XDG_DATA_HOME/nvim in addition to $XDG_STATE_HOME/nvim (default value).
|
-- $XDG_DATA_HOME/nvim in addition to $XDG_STATE_HOME/nvim (default value).
|
||||||
local file = "file_frecency.bin"
|
local db = Path.new(config.db_root, file_v1).filename
|
||||||
local db = Path.new(config.db_root, file)
|
if not config.ext_config.db_root and not fs.exists(db) then
|
||||||
if not config.ext_config.db_root and not db:exists() then
|
local old_location = Path.new(vim.fn.stdpath "data", file_v1).filename
|
||||||
local old_location = Path.new(vim.fn.stdpath "data", file)
|
if fs.exists(old_location) then
|
||||||
if old_location:exists() then
|
return old_location
|
||||||
return old_location.filename
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return db.filename
|
return db
|
||||||
end)()
|
end
|
||||||
self.file_lock = FileLock.new(self.filename)
|
|
||||||
local rx
|
if self.version == "v1" then
|
||||||
self.tx, rx = async.control.channel.mpsc()
|
return filename_v1()
|
||||||
self.tx.send "load"
|
else
|
||||||
watcher.watch(self.filename, function()
|
error(("unknown version: %s"):format(self.version))
|
||||||
self.tx.send "load"
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@return nil
|
||||||
|
function Database:start()
|
||||||
|
local target = self:filename()
|
||||||
|
self.file_lock_tx(FileLock.new(target))
|
||||||
|
self.watcher_tx.send "load"
|
||||||
|
watcher.watch(target, function()
|
||||||
|
self.watcher_tx.send "load"
|
||||||
end)
|
end)
|
||||||
async.void(function()
|
async.void(function()
|
||||||
while true do
|
while true do
|
||||||
local mode = rx.recv()
|
local mode = self.watcher_rx.recv()
|
||||||
log.debug("DB coroutine start:", mode)
|
log.debug("DB coroutine start:", mode)
|
||||||
if mode == "load" then
|
if mode == "load" then
|
||||||
self:load()
|
self:load()
|
||||||
@ -66,14 +92,15 @@ Database.new = function(fs)
|
|||||||
log.debug("DB coroutine end:", mode)
|
log.debug("DB coroutine end:", mode)
|
||||||
end
|
end
|
||||||
end)()
|
end)()
|
||||||
return self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Database:has_entry()
|
function Database:has_entry()
|
||||||
return not vim.tbl_isempty(self.tbl.records)
|
return not vim.tbl_isempty(self.tbl.records)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param paths string[]
|
---@param paths string[]
|
||||||
---@return nil
|
---@return nil
|
||||||
function Database:insert_files(paths)
|
function Database:insert_files(paths)
|
||||||
@ -83,28 +110,32 @@ function Database:insert_files(paths)
|
|||||||
for _, path in ipairs(paths) do
|
for _, path in ipairs(paths) do
|
||||||
self.tbl.records[path] = { count = 1, timestamps = { 0 } }
|
self.tbl.records[path] = { count = 1, timestamps = { 0 } }
|
||||||
end
|
end
|
||||||
self.tx.send "save"
|
self.watcher_tx.send "save"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@return string[]
|
---@return string[]
|
||||||
function Database:unlinked_entries()
|
function Database:unlinked_entries()
|
||||||
local paths = {}
|
return vim.tbl_flatten(async.util.join(vim.tbl_map(function(path)
|
||||||
for file in pairs(self.tbl.records) do
|
return function()
|
||||||
if not self.fs:is_valid_path(file) then
|
local err, realpath = async.uv.fs_realpath(path)
|
||||||
table.insert(paths, file)
|
if err or not realpath or realpath ~= path or fs.is_ignored(realpath) then
|
||||||
|
return path
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return paths
|
end, vim.tbl_keys(self.tbl.records))))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param paths string[]
|
---@param paths string[]
|
||||||
function Database:remove_files(paths)
|
function Database:remove_files(paths)
|
||||||
for _, file in ipairs(paths) do
|
for _, file in ipairs(paths) do
|
||||||
self.tbl.records[file] = nil
|
self.tbl.records[file] = nil
|
||||||
end
|
end
|
||||||
self.tx.send "save"
|
self.watcher_tx.send "save"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param path string
|
---@param path string
|
||||||
---@param epoch? integer
|
---@param epoch? integer
|
||||||
function Database:update(path, epoch)
|
function Database:update(path, epoch)
|
||||||
@ -120,9 +151,10 @@ function Database:update(path, epoch)
|
|||||||
record.timestamps = new_table
|
record.timestamps = new_table
|
||||||
end
|
end
|
||||||
self.tbl.records[path] = record
|
self.tbl.records[path] = record
|
||||||
self.tx.send "save"
|
self.watcher_tx.send "save"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param workspace? string
|
---@param workspace? string
|
||||||
---@param epoch? integer
|
---@param epoch? integer
|
||||||
---@return FrecencyDatabaseEntry[]
|
---@return FrecencyDatabaseEntry[]
|
||||||
@ -130,7 +162,7 @@ function Database:get_entries(workspace, epoch)
|
|||||||
local now = epoch or os.time()
|
local now = epoch or os.time()
|
||||||
local items = {}
|
local items = {}
|
||||||
for path, record in pairs(self.tbl.records) do
|
for path, record in pairs(self.tbl.records) do
|
||||||
if self.fs:starts_with(path, workspace) then
|
if fs.starts_with(path, workspace) then
|
||||||
table.insert(items, {
|
table.insert(items, {
|
||||||
path = path,
|
path = path,
|
||||||
count = record.count,
|
count = record.count,
|
||||||
@ -148,13 +180,13 @@ end
|
|||||||
---@return nil
|
---@return nil
|
||||||
function Database:load()
|
function Database:load()
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
local err, data = self.file_lock:with(function()
|
local err, data = self:file_lock():with(function(target)
|
||||||
local err, stat = async.uv.fs_stat(self.filename)
|
local err, stat = async.uv.fs_stat(target)
|
||||||
if err then
|
if err then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
local fd
|
local fd
|
||||||
err, fd = async.uv.fs_open(self.filename, "r", tonumber("644", 8))
|
err, fd = async.uv.fs_open(target, "r", tonumber("644", 8))
|
||||||
assert(not err, err)
|
assert(not err, err)
|
||||||
local data
|
local data
|
||||||
err, data = async.uv.fs_read(fd, stat.size)
|
err, data = async.uv.fs_read(fd, stat.size)
|
||||||
@ -173,9 +205,9 @@ end
|
|||||||
---@return nil
|
---@return nil
|
||||||
function Database:save()
|
function Database:save()
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
local err = self.file_lock:with(function()
|
local err = self:file_lock():with(function(target)
|
||||||
self:raw_save(self.tbl:raw())
|
self:raw_save(self.tbl:raw(), target)
|
||||||
local err, stat = async.uv.fs_stat(self.filename)
|
local err, stat = async.uv.fs_stat(target)
|
||||||
assert(not err, err)
|
assert(not err, err)
|
||||||
watcher.update(stat)
|
watcher.update(stat)
|
||||||
return nil
|
return nil
|
||||||
@ -185,16 +217,18 @@ function Database:save()
|
|||||||
end
|
end
|
||||||
|
|
||||||
---@async
|
---@async
|
||||||
|
---@param target string
|
||||||
---@param tbl FrecencyDatabaseRawTable
|
---@param tbl FrecencyDatabaseRawTable
|
||||||
function Database:raw_save(tbl)
|
function Database:raw_save(tbl, target)
|
||||||
local f = assert(load("return " .. vim.inspect(tbl)))
|
local f = assert(load("return " .. vim.inspect(tbl)))
|
||||||
local data = string.dump(f)
|
local data = string.dump(f)
|
||||||
local err, fd = async.uv.fs_open(self.filename, "w", tonumber("644", 8))
|
local err, fd = async.uv.fs_open(target, "w", tonumber("644", 8))
|
||||||
assert(not err, err)
|
assert(not err, err)
|
||||||
assert(not async.uv.fs_write(fd, data))
|
assert(not async.uv.fs_write(fd, data))
|
||||||
assert(not async.uv.fs_close(fd))
|
assert(not async.uv.fs_close(fd))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function Database:remove_entry(path)
|
function Database:remove_entry(path)
|
||||||
@ -202,8 +236,18 @@ function Database:remove_entry(path)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
self.tbl.records[path] = nil
|
self.tbl.records[path] = nil
|
||||||
self.tx.send "save"
|
self.watcher_tx.send "save"
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@async
|
||||||
|
---@return FrecencyFileLock
|
||||||
|
function Database:file_lock()
|
||||||
|
if not self._file_lock then
|
||||||
|
self._file_lock = self.file_lock_rx()
|
||||||
|
end
|
||||||
|
return self._file_lock
|
||||||
|
end
|
||||||
|
|
||||||
return Database
|
return Database
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
|
local async = require "plenary.async"
|
||||||
|
|
||||||
---@class FrecencyDatabaseRecordValue
|
---@class FrecencyDatabaseRecordValue
|
||||||
---@field count integer
|
---@field count integer
|
||||||
@ -18,14 +19,12 @@ Table.new = function(version)
|
|||||||
return setmetatable({ is_ready = false, version = version }, { __index = Table.__index })
|
return setmetatable({ is_ready = false, version = version }, { __index = Table.__index })
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@param key string
|
||||||
function Table:__index(key)
|
function Table:__index(key)
|
||||||
if key == "records" and not rawget(self, "is_ready") then
|
if key == "records" and not rawget(self, "is_ready") then
|
||||||
local start = os.clock()
|
|
||||||
log.debug "waiting start"
|
|
||||||
Table.wait_ready(self)
|
Table.wait_ready(self)
|
||||||
log.debug(("waiting until DB become clean takes %f seconds"):format(os.clock() - start))
|
|
||||||
end
|
end
|
||||||
log.debug(("is_ready: %s, key: %s, value: %s"):format(rawget(self, "is_ready"), key, rawget(self, key)))
|
|
||||||
return vim.F.if_nil(rawget(self, key), Table[key])
|
return vim.F.if_nil(rawget(self, key), Table[key])
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -45,11 +44,16 @@ function Table:set(raw_table)
|
|||||||
end
|
end
|
||||||
|
|
||||||
---This is for internal or testing use only.
|
---This is for internal or testing use only.
|
||||||
|
---@async
|
||||||
---@return nil
|
---@return nil
|
||||||
function Table:wait_ready()
|
function Table:wait_ready()
|
||||||
vim.wait(2000, function()
|
local start = os.clock()
|
||||||
return rawget(self, "is_ready")
|
local t = 0.2
|
||||||
end)
|
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))
|
||||||
end
|
end
|
||||||
|
|
||||||
return Table
|
return Table
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
local WebDevicons = require "frecency.web_devicons"
|
local WebDevicons = require "frecency.web_devicons"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fs = require "frecency.fs"
|
||||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||||
local entry_display = require "telescope.pickers.entry_display" --[[@as FrecencyTelescopeEntryDisplay]]
|
local entry_display = require "telescope.pickers.entry_display" --[[@as FrecencyTelescopeEntryDisplay]]
|
||||||
local utils = require "telescope.utils" --[[@as FrecencyTelescopeUtils]]
|
local utils = require "telescope.utils" --[[@as FrecencyTelescopeUtils]]
|
||||||
|
|
||||||
---@class FrecencyEntryMaker
|
---@class FrecencyEntryMaker
|
||||||
---@field fs FrecencyFS
|
|
||||||
---@field loaded table<string,boolean>
|
---@field loaded table<string,boolean>
|
||||||
---@field web_devicons WebDevicons
|
---@field web_devicons WebDevicons
|
||||||
local EntryMaker = {}
|
local EntryMaker = {}
|
||||||
|
|
||||||
---@param fs FrecencyFS
|
|
||||||
---@return FrecencyEntryMaker
|
---@return FrecencyEntryMaker
|
||||||
EntryMaker.new = function(fs)
|
EntryMaker.new = function()
|
||||||
return setmetatable({ fs = fs, web_devicons = WebDevicons.new() }, { __index = EntryMaker })
|
return setmetatable({ web_devicons = WebDevicons.new() }, { __index = EntryMaker })
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class FrecencyEntry
|
---@class FrecencyEntry
|
||||||
@ -128,7 +127,7 @@ function EntryMaker:items(entry, workspace, workspace_tag, formatter)
|
|||||||
end
|
end
|
||||||
if config.show_filter_column and workspace and workspace_tag then
|
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
|
local filtered = self:should_show_tail(workspace_tag) and utils.path_tail(workspace) .. Path.path.sep
|
||||||
or self.fs:relative_from_home(workspace) .. Path.path.sep
|
or fs.relative_from_home(workspace) .. Path.path.sep
|
||||||
table.insert(items, { filtered, "Directory" })
|
table.insert(items, { filtered, "Directory" })
|
||||||
end
|
end
|
||||||
local formatted_name, path_style = formatter(entry.name)
|
local formatted_name, path_style = formatter(entry.name)
|
||||||
@ -153,7 +152,7 @@ end
|
|||||||
---@return integer
|
---@return integer
|
||||||
function EntryMaker:calculate_filter_column_width(workspace, workspace_tag)
|
function EntryMaker:calculate_filter_column_width(workspace, workspace_tag)
|
||||||
return self:should_show_tail(workspace_tag) and #(utils.path_tail(workspace)) + 1
|
return self:should_show_tail(workspace_tag) and #(utils.path_tail(workspace)) + 1
|
||||||
or #(self.fs:relative_from_home(workspace)) + 1
|
or #(fs.relative_from_home(workspace)) + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
|
|||||||
@ -4,23 +4,28 @@ local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
|||||||
|
|
||||||
---@class FrecencyFileLock
|
---@class FrecencyFileLock
|
||||||
---@field base string
|
---@field base string
|
||||||
---@field config FrecencyFileLockConfig
|
---@field config FrecencyFileLockRawConfig
|
||||||
---@field filename string
|
---@field lock string
|
||||||
|
---@field target string
|
||||||
local FileLock = {}
|
local FileLock = {}
|
||||||
|
|
||||||
---@class FrecencyFileLockConfig
|
---@class FrecencyFileLockConfig
|
||||||
|
---@field retry? integer default: 5
|
||||||
|
---@field unlink_retry? integer default: 5
|
||||||
|
---@field interval? integer default: 500
|
||||||
|
|
||||||
|
---@class FrecencyFileLockRawConfig
|
||||||
---@field retry integer default: 5
|
---@field retry integer default: 5
|
||||||
---@field unlink_retry integer default: 5
|
---@field unlink_retry integer default: 5
|
||||||
---@field interval integer default: 500
|
---@field interval integer default: 500
|
||||||
|
|
||||||
---@param path string
|
---@param target string
|
||||||
---@param file_lock_config? FrecencyFileLockConfig
|
---@param file_lock_config? FrecencyFileLockConfig
|
||||||
---@return FrecencyFileLock
|
---@return FrecencyFileLock
|
||||||
FileLock.new = function(path, file_lock_config)
|
FileLock.new = function(target, file_lock_config)
|
||||||
|
log.debug(("file_lock new(): %s"):format(target))
|
||||||
local config = vim.tbl_extend("force", { retry = 5, unlink_retry = 5, interval = 500 }, file_lock_config or {})
|
local config = vim.tbl_extend("force", { retry = 5, unlink_retry = 5, interval = 500 }, file_lock_config or {})
|
||||||
local self = setmetatable({ config = config }, { __index = FileLock })
|
return setmetatable({ config = config, lock = target .. ".lock", target = target }, { __index = FileLock })
|
||||||
self.filename = path .. ".lock"
|
|
||||||
return self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@async
|
---@async
|
||||||
@ -31,21 +36,21 @@ function FileLock:get()
|
|||||||
local err, fd
|
local err, fd
|
||||||
while true do
|
while true do
|
||||||
count = count + 1
|
count = count + 1
|
||||||
local dir = Path.new(self.filename):parent()
|
local dir = Path.new(self.lock):parent()
|
||||||
if not dir:exists() then
|
if not dir:exists() then
|
||||||
-- TODO: make this call be async
|
-- TODO: make this call be async
|
||||||
log.debug(("file_lock get(): mkdir parent: %s"):format(dir.filename))
|
log.debug(("file_lock get(): mkdir parent: %s"):format(dir.filename))
|
||||||
---@diagnostic disable-next-line: undefined-field
|
---@diagnostic disable-next-line: undefined-field
|
||||||
dir:mkdir { parents = true }
|
dir:mkdir { parents = true }
|
||||||
end
|
end
|
||||||
err, fd = async.uv.fs_open(self.filename, "wx", tonumber("600", 8))
|
err, fd = async.uv.fs_open(self.lock, "wx", tonumber("600", 8))
|
||||||
if not err then
|
if not err then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
async.util.sleep(self.config.interval)
|
async.util.sleep(self.config.interval)
|
||||||
if count >= self.config.retry then
|
if count >= self.config.retry then
|
||||||
log.debug(("file_lock get(): retry count reached. try to delete the lock file: %d"):format(count))
|
log.debug(("file_lock get(): retry count reached. try to delete the lock file: %d"):format(count))
|
||||||
err = async.uv.fs_unlink(self.filename)
|
err = async.uv.fs_unlink(self.lock)
|
||||||
if err then
|
if err then
|
||||||
log.debug("file_lock get() failed: " .. err)
|
log.debug("file_lock get() failed: " .. err)
|
||||||
unlink_count = unlink_count + 1
|
unlink_count = unlink_count + 1
|
||||||
@ -67,12 +72,12 @@ end
|
|||||||
---@async
|
---@async
|
||||||
---@return string? err
|
---@return string? err
|
||||||
function FileLock:release()
|
function FileLock:release()
|
||||||
local err = async.uv.fs_stat(self.filename)
|
local err = async.uv.fs_stat(self.lock)
|
||||||
if err then
|
if err then
|
||||||
log.debug("file_lock release() not found: " .. err)
|
log.debug("file_lock release() not found: " .. err)
|
||||||
return "lock not found"
|
return "lock not found"
|
||||||
end
|
end
|
||||||
err = async.uv.fs_unlink(self.filename)
|
err = async.uv.fs_unlink(self.lock)
|
||||||
if err then
|
if err then
|
||||||
log.debug("file_lock release() unlink failed: " .. err)
|
log.debug("file_lock release() unlink failed: " .. err)
|
||||||
return err
|
return err
|
||||||
@ -81,7 +86,7 @@ end
|
|||||||
|
|
||||||
---@async
|
---@async
|
||||||
---@generic T
|
---@generic T
|
||||||
---@param f fun(): T
|
---@param f fun(target: string): T
|
||||||
---@return string? err
|
---@return string? err
|
||||||
---@return T
|
---@return T
|
||||||
function FileLock:with(f)
|
function FileLock:with(f)
|
||||||
@ -89,7 +94,7 @@ function FileLock:with(f)
|
|||||||
if err then
|
if err then
|
||||||
return err, nil
|
return err, nil
|
||||||
end
|
end
|
||||||
local ok, result_or_err = pcall(f)
|
local ok, result_or_err = pcall(f, self.target)
|
||||||
err = self:release()
|
err = self:release()
|
||||||
if err then
|
if err then
|
||||||
return err, nil
|
return err, nil
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fs = require "frecency.fs"
|
||||||
local os_util = require "frecency.os_util"
|
local os_util = require "frecency.os_util"
|
||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
local Job = require "plenary.job"
|
local Job = require "plenary.job"
|
||||||
@ -10,7 +11,6 @@ local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
|||||||
---@field entries FrecencyEntry[]
|
---@field entries FrecencyEntry[]
|
||||||
---@field scanned_entries FrecencyEntry[]
|
---@field scanned_entries FrecencyEntry[]
|
||||||
---@field entry_maker FrecencyEntryMakerInstance
|
---@field entry_maker FrecencyEntryMakerInstance
|
||||||
---@field fs FrecencyFS
|
|
||||||
---@field path? string
|
---@field path? string
|
||||||
---@field private database FrecencyDatabase
|
---@field private database FrecencyDatabase
|
||||||
---@field private rx FrecencyPlenaryAsyncControlChannelRx
|
---@field private rx FrecencyPlenaryAsyncControlChannelRx
|
||||||
@ -32,14 +32,13 @@ local Finder = {}
|
|||||||
|
|
||||||
---@param database FrecencyDatabase
|
---@param database FrecencyDatabase
|
||||||
---@param entry_maker FrecencyEntryMakerInstance
|
---@param entry_maker FrecencyEntryMakerInstance
|
||||||
---@param fs FrecencyFS
|
|
||||||
---@param need_scandir boolean
|
---@param need_scandir boolean
|
||||||
---@param path string?
|
---@param path string?
|
||||||
---@param recency FrecencyRecency
|
---@param recency FrecencyRecency
|
||||||
---@param state FrecencyState
|
---@param state FrecencyState
|
||||||
---@param finder_config? FrecencyFinderConfig
|
---@param finder_config? FrecencyFinderConfig
|
||||||
---@return FrecencyFinder
|
---@return FrecencyFinder
|
||||||
Finder.new = function(database, entry_maker, fs, need_scandir, path, recency, state, finder_config)
|
Finder.new = function(database, entry_maker, need_scandir, path, recency, state, finder_config)
|
||||||
local tx, rx = async.control.channel.mpsc()
|
local tx, rx = async.control.channel.mpsc()
|
||||||
local scan_tx, scan_rx = async.control.channel.mpsc()
|
local scan_tx, scan_rx = async.control.channel.mpsc()
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
@ -47,7 +46,6 @@ Finder.new = function(database, entry_maker, fs, need_scandir, path, recency, st
|
|||||||
closed = false,
|
closed = false,
|
||||||
database = database,
|
database = database,
|
||||||
entry_maker = entry_maker,
|
entry_maker = entry_maker,
|
||||||
fs = fs,
|
|
||||||
path = path,
|
path = path,
|
||||||
recency = recency,
|
recency = recency,
|
||||||
state = state,
|
state = state,
|
||||||
@ -168,7 +166,7 @@ end
|
|||||||
---@return nil
|
---@return nil
|
||||||
function Finder:scan_dir_lua()
|
function Finder:scan_dir_lua()
|
||||||
local count = 0
|
local count = 0
|
||||||
for name in self.fs:scan_dir(self.path) do
|
for name in fs.scan_dir(self.path) do
|
||||||
if self.closed then
|
if self.closed then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
@ -296,6 +294,8 @@ function Finder:reflow_results()
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
async.util.scheduler()
|
async.util.scheduler()
|
||||||
|
|
||||||
|
local function reflow()
|
||||||
local bufnr = picker.results_bufnr
|
local bufnr = picker.results_bufnr
|
||||||
local win = picker.results_win
|
local win = picker.results_win
|
||||||
if not bufnr or not win or not vim.api.nvim_buf_is_valid(bufnr) or not vim.api.nvim_win_is_valid(win) then
|
if not bufnr or not win or not vim.api.nvim_buf_is_valid(bufnr) or not vim.api.nvim_win_is_valid(win) then
|
||||||
@ -317,4 +317,11 @@ function Finder:reflow_results()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if vim.in_fast_event() then
|
||||||
|
reflow()
|
||||||
|
else
|
||||||
|
vim.schedule(reflow)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
return Finder
|
return Finder
|
||||||
|
|||||||
@ -2,57 +2,57 @@ local config = require "frecency.config"
|
|||||||
local os_util = require "frecency.os_util"
|
local os_util = require "frecency.os_util"
|
||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
local Path = require "plenary.path" --[[@as FrecencyPlenaryPath]]
|
||||||
|
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||||
local scandir = require "plenary.scandir"
|
local scandir = require "plenary.scandir"
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
|
||||||
---@class FrecencyFS
|
local M = {
|
||||||
---@field os_homedir string
|
os_homedir = assert(uv.os_homedir()),
|
||||||
---@field private config FrecencyFSConfig
|
}
|
||||||
---@field private ignore_regexes string[]
|
|
||||||
local FS = {}
|
|
||||||
|
|
||||||
---@class FrecencyFSConfig
|
-- TODO: make this configurable
|
||||||
---@field scan_depth integer?
|
local SCAN_DEPTH = 100
|
||||||
|
|
||||||
---@param fs_config? FrecencyFSConfig
|
---@param path string
|
||||||
---@return FrecencyFS
|
---@return boolean
|
||||||
FS.new = function(fs_config)
|
function M.is_ignored(path)
|
||||||
local self= setmetatable(
|
for _, regex in ipairs(config.ignore_regexes()) do
|
||||||
{ config = vim.tbl_extend("force", { scan_depth = 100 }, fs_config or {}), os_homedir = assert(uv.os_homedir()) },
|
if path:find(regex) then
|
||||||
{ __index = FS }
|
return true
|
||||||
)
|
end
|
||||||
---@param pattern string
|
end
|
||||||
self.ignore_regexes = vim.tbl_map(function(pattern)
|
return false
|
||||||
local regex = vim.pesc(pattern):gsub("%%%*", ".*"):gsub("%%%?", ".")
|
|
||||||
return "^" .. regex .. "$"
|
|
||||||
end, config.ignore_patterns)
|
|
||||||
return self
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param path? string
|
---@param path? string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function FS:is_valid_path(path)
|
function M.is_valid_path(path)
|
||||||
return not not path and Path:new(path):is_file() and not self:is_ignored(path)
|
if not path then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
local err, st = async.uv.fs_stat(path)
|
||||||
|
return not err and st.type == "file" and not M.is_ignored(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return function
|
---@return function
|
||||||
function FS:scan_dir(path)
|
function M.scan_dir(path)
|
||||||
log.debug { path = path }
|
log.debug { path = path }
|
||||||
local gitignore = self:make_gitignore(path)
|
local gitignore = M.make_gitignore(path)
|
||||||
return coroutine.wrap(function()
|
return coroutine.wrap(function()
|
||||||
for name, type in
|
for name, type in
|
||||||
vim.fs.dir(path, {
|
vim.fs.dir(path, {
|
||||||
depth = self.config.scan_depth,
|
depth = SCAN_DEPTH,
|
||||||
skip = function(dirname)
|
skip = function(dirname)
|
||||||
if self:is_ignored(os_util.join_path(path, dirname)) then
|
if M.is_ignored(os_util.join_path(path, dirname)) then
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
do
|
do
|
||||||
local fullpath = os_util.join_path(path, name)
|
local fullpath = os_util.join_path(path, name)
|
||||||
if type == "file" and not self:is_ignored(fullpath) and gitignore({ path }, fullpath) then
|
if type == "file" and not M.is_ignored(fullpath) and gitignore({ path }, fullpath) then
|
||||||
coroutine.yield(name)
|
coroutine.yield(name)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -61,8 +61,8 @@ end
|
|||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string
|
---@return string
|
||||||
function FS:relative_from_home(path)
|
function M.relative_from_home(path)
|
||||||
return Path:new(path):make_relative(self.os_homedir)
|
return Path:new(path):make_relative(M.os_homedir)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type table<string,string>
|
---@type table<string,string>
|
||||||
@ -71,7 +71,7 @@ local with_sep = {}
|
|||||||
---@param path string
|
---@param path string
|
||||||
---@param base? string
|
---@param base? string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function FS:starts_with(path, base)
|
function M.starts_with(path, base)
|
||||||
if not base then
|
if not base then
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
@ -81,25 +81,20 @@ function FS:starts_with(path, base)
|
|||||||
return path:find(with_sep[base], 1, true) == 1
|
return path:find(with_sep[base], 1, true) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@async
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return boolean
|
---@return boolean
|
||||||
function FS:is_ignored(path)
|
function M.exists(path)
|
||||||
for _, regex in ipairs(self.ignore_regexes) do
|
return not (async.uv.fs_stat(path))
|
||||||
if path:find(regex) then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@private
|
||||||
---@param basepath string
|
---@param basepath string
|
||||||
---@return fun(base_paths: string[], entry: string): boolean
|
---@return fun(base_paths: string[], entry: string): boolean
|
||||||
function FS:make_gitignore(basepath)
|
function M.make_gitignore(basepath)
|
||||||
return scandir.__make_gitignore { basepath } or function(_, _)
|
return scandir.__make_gitignore { basepath } or function(_, _)
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return FS
|
return M
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
---setup() to be initialized.
|
---setup() to be initialized.
|
||||||
---@class FrecencyInstance
|
---@class FrecencyInstance
|
||||||
---@field complete fun(findstart: 1|0, base: string): integer|''|string[]
|
---@field complete fun(findstart: 1|0, base: string): integer|''|string[]
|
||||||
---@field delete fun(path: string): nil
|
---@field delete async fun(path: string): nil
|
||||||
---@field query fun(opts?: FrecencyQueryOpts): FrecencyQueryEntry[]|string[]
|
---@field query fun(opts?: FrecencyQueryOpts): FrecencyQueryEntry[]|string[]
|
||||||
---@field register fun(bufnr: integer, datetime: string?): nil
|
---@field register fun(bufnr: integer, datetime: string?): nil
|
||||||
---@field start fun(opts: FrecencyPickerOptions?): nil
|
---@field start fun(opts: FrecencyPickerOptions?): nil
|
||||||
---@field validate_database fun(force: boolean?): nil
|
---@field validate_database async fun(force: boolean?): nil
|
||||||
local frecency = setmetatable({}, {
|
local frecency = setmetatable({}, {
|
||||||
---@param self FrecencyInstance
|
---@param self FrecencyInstance
|
||||||
---@param key "complete"|"delete"|"register"|"start"|"validate_database"
|
---@param key "complete"|"delete"|"register"|"start"|"validate_database"
|
||||||
@ -28,6 +28,10 @@ local frecency = setmetatable({}, {
|
|||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
local function async_call(f, ...)
|
||||||
|
require("plenary.async").void(f)(...)
|
||||||
|
end
|
||||||
|
|
||||||
local setup_done = false
|
local setup_done = false
|
||||||
|
|
||||||
---When this func is called, Frecency instance is NOT created but only
|
---When this func is called, Frecency instance is NOT created but only
|
||||||
@ -46,15 +50,20 @@ local function setup(ext_config)
|
|||||||
vim.api.nvim_set_hl(0, "TelescopeFrecencyScores", { link = "Number", default = true })
|
vim.api.nvim_set_hl(0, "TelescopeFrecencyScores", { link = "Number", default = true })
|
||||||
vim.api.nvim_set_hl(0, "TelescopeQueryFilter", { link = "WildMenu", default = true })
|
vim.api.nvim_set_hl(0, "TelescopeQueryFilter", { link = "WildMenu", default = true })
|
||||||
|
|
||||||
---@param cmd_info { bang: boolean }
|
---@class FrecencyCommandInfo
|
||||||
|
---@field args string
|
||||||
|
---@field bang boolean
|
||||||
|
|
||||||
|
---@param cmd_info FrecencyCommandInfo
|
||||||
vim.api.nvim_create_user_command("FrecencyValidate", function(cmd_info)
|
vim.api.nvim_create_user_command("FrecencyValidate", function(cmd_info)
|
||||||
frecency.validate_database(cmd_info.bang)
|
async_call(frecency.validate_database, cmd_info.bang)
|
||||||
end, { bang = true, desc = "Clean up DB for telescope-frecency" })
|
end, { bang = true, desc = "Clean up DB for telescope-frecency" })
|
||||||
|
|
||||||
vim.api.nvim_create_user_command("FrecencyDelete", function(info)
|
---@param cmd_info FrecencyCommandInfo
|
||||||
local path_string = info.args == "" and "%:p" or info.args
|
vim.api.nvim_create_user_command("FrecencyDelete", function(cmd_info)
|
||||||
|
local path_string = cmd_info.args == "" and "%:p" or cmd_info.args
|
||||||
local path = vim.fn.expand(path_string) --[[@as string]]
|
local path = vim.fn.expand(path_string) --[[@as string]]
|
||||||
frecency.delete(path)
|
async_call(frecency.delete, path)
|
||||||
end, { nargs = "?", complete = "file", desc = "Delete entry from telescope-frecency" })
|
end, { nargs = "?", complete = "file", desc = "Delete entry from telescope-frecency" })
|
||||||
|
|
||||||
local group = vim.api.nvim_create_augroup("TelescopeFrecency", {})
|
local group = vim.api.nvim_create_augroup("TelescopeFrecency", {})
|
||||||
|
|||||||
@ -1,16 +1,16 @@
|
|||||||
local Database = require "frecency.database"
|
local Database = require "frecency.database"
|
||||||
local EntryMaker = require "frecency.entry_maker"
|
local EntryMaker = require "frecency.entry_maker"
|
||||||
local FS = require "frecency.fs"
|
|
||||||
local Picker = require "frecency.picker"
|
local Picker = require "frecency.picker"
|
||||||
local Recency = require "frecency.recency"
|
local Recency = require "frecency.recency"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fs = require "frecency.fs"
|
||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
|
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||||
|
|
||||||
---@class Frecency
|
---@class Frecency
|
||||||
---@field private buf_registered table<integer, boolean> flag to indicate the buffer is registered to the database.
|
---@field private buf_registered table<integer, boolean> flag to indicate the buffer is registered to the database.
|
||||||
---@field private database FrecencyDatabase
|
---@field private database FrecencyDatabase
|
||||||
---@field private entry_maker FrecencyEntryMaker
|
---@field private entry_maker FrecencyEntryMaker
|
||||||
---@field private fs FrecencyFS
|
|
||||||
---@field private picker FrecencyPicker
|
---@field private picker FrecencyPicker
|
||||||
---@field private recency FrecencyRecency
|
---@field private recency FrecencyRecency
|
||||||
local Frecency = {}
|
local Frecency = {}
|
||||||
@ -18,9 +18,8 @@ local Frecency = {}
|
|||||||
---@return Frecency
|
---@return Frecency
|
||||||
Frecency.new = function()
|
Frecency.new = function()
|
||||||
local self = setmetatable({ buf_registered = {} }, { __index = Frecency }) --[[@as Frecency]]
|
local self = setmetatable({ buf_registered = {} }, { __index = Frecency }) --[[@as Frecency]]
|
||||||
self.fs = FS.new()
|
self.database = Database.new()
|
||||||
self.database = Database.new(self.fs)
|
self.entry_maker = EntryMaker.new()
|
||||||
self.entry_maker = EntryMaker.new(self.fs)
|
|
||||||
self.recency = Recency.new()
|
self.recency = Recency.new()
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
@ -28,14 +27,29 @@ end
|
|||||||
---This is called when `:Telescope frecency` is called at the first time.
|
---This is called when `:Telescope frecency` is called at the first time.
|
||||||
---@return nil
|
---@return nil
|
||||||
function Frecency:setup()
|
function Frecency:setup()
|
||||||
-- HACK: Wihout this wrapping, it spoils background color detection.
|
local done = false
|
||||||
-- See https://github.com/nvim-telescope/telescope-frecency.nvim/issues/210
|
---@async
|
||||||
vim.defer_fn(function()
|
local function init()
|
||||||
|
self.database:start()
|
||||||
self:assert_db_entries()
|
self:assert_db_entries()
|
||||||
if config.auto_validate then
|
if config.auto_validate then
|
||||||
self:validate_database()
|
self:validate_database()
|
||||||
end
|
end
|
||||||
end, 0)
|
done = true
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_async = not not coroutine.running()
|
||||||
|
if is_async then
|
||||||
|
init()
|
||||||
|
else
|
||||||
|
async.void(init)()
|
||||||
|
local ok, status = vim.wait(1000, function()
|
||||||
|
return done
|
||||||
|
end)
|
||||||
|
if not ok then
|
||||||
|
log.error("failed to setup:", status == -1 and "timed out" or "interrupted")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
---This can be calledBy `require("telescope").extensions.frecency.frecency`.
|
---This can be calledBy `require("telescope").extensions.frecency.frecency`.
|
||||||
@ -52,7 +66,7 @@ function Frecency:start(opts)
|
|||||||
if opts.hide_current_buffer or config.hide_current_buffer then
|
if opts.hide_current_buffer or config.hide_current_buffer then
|
||||||
ignore_filenames = { vim.api.nvim_buf_get_name(0) }
|
ignore_filenames = { vim.api.nvim_buf_get_name(0) }
|
||||||
end
|
end
|
||||||
self.picker = Picker.new(self.database, self.entry_maker, self.fs, self.recency, {
|
self.picker = Picker.new(self.database, self.entry_maker, self.recency, {
|
||||||
editing_bufnr = vim.api.nvim_get_current_buf(),
|
editing_bufnr = vim.api.nvim_get_current_buf(),
|
||||||
ignore_filenames = ignore_filenames,
|
ignore_filenames = ignore_filenames,
|
||||||
initial_workspace_tag = opts.workspace,
|
initial_workspace_tag = opts.workspace,
|
||||||
@ -69,16 +83,7 @@ function Frecency:complete(findstart, base)
|
|||||||
return self.picker:complete(findstart, base)
|
return self.picker:complete(findstart, base)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@private
|
---@async
|
||||||
---@return nil
|
|
||||||
function Frecency:assert_db_entries()
|
|
||||||
if not self.database:has_entry() then
|
|
||||||
self.database:insert_files(vim.v.oldfiles)
|
|
||||||
self:notify("Imported %d entries from oldfiles.", #vim.v.oldfiles)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@private
|
|
||||||
---@param force? boolean
|
---@param force? boolean
|
||||||
---@return nil
|
---@return nil
|
||||||
function Frecency:validate_database(force)
|
function Frecency:validate_database(force)
|
||||||
@ -110,20 +115,38 @@ function Frecency:validate_database(force)
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@private
|
||||||
|
---@async
|
||||||
|
---@return nil
|
||||||
|
function Frecency:assert_db_entries()
|
||||||
|
if not self.database:has_entry() then
|
||||||
|
self.database:insert_files(vim.v.oldfiles)
|
||||||
|
self:notify("Imported %d entries from oldfiles.", #vim.v.oldfiles)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param epoch? integer
|
---@param epoch? integer
|
||||||
function Frecency:register(bufnr, epoch)
|
function Frecency:register(bufnr, epoch)
|
||||||
if config.ignore_register and config.ignore_register(bufnr) then
|
if (config.ignore_register and config.ignore_register(bufnr)) or self.buf_registered[bufnr] then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local path = vim.api.nvim_buf_get_name(bufnr)
|
local path = vim.api.nvim_buf_get_name(bufnr)
|
||||||
if self.buf_registered[bufnr] or not self.fs:is_valid_path(path) then
|
async.void(function()
|
||||||
|
if not fs.is_valid_path(path) then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
self.database:update(path, epoch)
|
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
|
self.buf_registered[bufnr] = true
|
||||||
|
log.debug("registered:", bufnr, path)
|
||||||
|
end)()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@async
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return nil
|
---@return nil
|
||||||
function Frecency:delete(path)
|
function Frecency:delete(path)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
local State = require "frecency.state"
|
local State = require "frecency.state"
|
||||||
local Finder = require "frecency.finder"
|
local Finder = require "frecency.finder"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
|
local fs = require "frecency.fs"
|
||||||
local fuzzy_sorter = require "frecency.fuzzy_sorter"
|
local fuzzy_sorter = require "frecency.fuzzy_sorter"
|
||||||
local substr_sorter = require "frecency.substr_sorter"
|
local substr_sorter = require "frecency.substr_sorter"
|
||||||
local log = require "frecency.log"
|
local log = require "frecency.log"
|
||||||
@ -15,7 +16,6 @@ local uv = vim.loop or vim.uv
|
|||||||
---@field private config FrecencyPickerConfig
|
---@field private config FrecencyPickerConfig
|
||||||
---@field private database FrecencyDatabase
|
---@field private database FrecencyDatabase
|
||||||
---@field private entry_maker FrecencyEntryMaker
|
---@field private entry_maker FrecencyEntryMaker
|
||||||
---@field private fs FrecencyFS
|
|
||||||
---@field private lsp_workspaces string[]
|
---@field private lsp_workspaces string[]
|
||||||
---@field private namespace integer
|
---@field private namespace integer
|
||||||
---@field private recency FrecencyRecency
|
---@field private recency FrecencyRecency
|
||||||
@ -38,16 +38,14 @@ local Picker = {}
|
|||||||
|
|
||||||
---@param database FrecencyDatabase
|
---@param database FrecencyDatabase
|
||||||
---@param entry_maker FrecencyEntryMaker
|
---@param entry_maker FrecencyEntryMaker
|
||||||
---@param fs FrecencyFS
|
|
||||||
---@param recency FrecencyRecency
|
---@param recency FrecencyRecency
|
||||||
---@param picker_config FrecencyPickerConfig
|
---@param picker_config FrecencyPickerConfig
|
||||||
---@return FrecencyPicker
|
---@return FrecencyPicker
|
||||||
Picker.new = function(database, entry_maker, fs, recency, picker_config)
|
Picker.new = function(database, entry_maker, recency, picker_config)
|
||||||
local self = setmetatable({
|
local self = setmetatable({
|
||||||
config = picker_config,
|
config = picker_config,
|
||||||
database = database,
|
database = database,
|
||||||
entry_maker = entry_maker,
|
entry_maker = entry_maker,
|
||||||
fs = fs,
|
|
||||||
lsp_workspaces = {},
|
lsp_workspaces = {},
|
||||||
namespace = vim.api.nvim_create_namespace "frecency",
|
namespace = vim.api.nvim_create_namespace "frecency",
|
||||||
recency = recency,
|
recency = recency,
|
||||||
@ -80,7 +78,6 @@ function Picker:finder(opts, workspace, workspace_tag)
|
|||||||
return Finder.new(
|
return Finder.new(
|
||||||
self.database,
|
self.database,
|
||||||
entry_maker,
|
entry_maker,
|
||||||
self.fs,
|
|
||||||
need_scandir,
|
need_scandir,
|
||||||
workspace,
|
workspace,
|
||||||
self.recency,
|
self.recency,
|
||||||
@ -159,8 +156,8 @@ end
|
|||||||
function Picker:default_path_display(opts, path)
|
function Picker:default_path_display(opts, path)
|
||||||
local filename = Path:new(path):make_relative(opts.cwd)
|
local filename = Path:new(path):make_relative(opts.cwd)
|
||||||
if not self.workspace then
|
if not self.workspace then
|
||||||
if vim.startswith(filename, self.fs.os_homedir) then
|
if vim.startswith(filename, fs.os_homedir) then
|
||||||
filename = "~" .. Path.path.sep .. self.fs:relative_from_home(filename)
|
filename = "~" .. Path.path.sep .. fs.relative_from_home(filename)
|
||||||
elseif filename ~= path then
|
elseif filename ~= path then
|
||||||
filename = "." .. Path.path.sep .. filename
|
filename = "." .. Path.path.sep .. filename
|
||||||
end
|
end
|
||||||
@ -269,7 +266,7 @@ function Picker:filepath_formatter(picker_opts)
|
|||||||
for k, v in pairs(picker_opts) do
|
for k, v in pairs(picker_opts) do
|
||||||
opts[k] = v
|
opts[k] = v
|
||||||
end
|
end
|
||||||
opts.cwd = workspace or self.fs.os_homedir
|
opts.cwd = workspace or fs.os_homedir
|
||||||
|
|
||||||
return function(filename)
|
return function(filename)
|
||||||
return utils.transform_path(opts, filename)
|
return utils.transform_path(opts, filename)
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
local FS = require "frecency.fs"
|
|
||||||
local Database = require "frecency.database"
|
local Database = require "frecency.database"
|
||||||
local config = require "frecency.config"
|
local config = require "frecency.config"
|
||||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||||
local util = require "frecency.tests.util"
|
local util = require "frecency.tests.util"
|
||||||
async.tests.add_to_env()
|
async.tests.add_to_env()
|
||||||
|
|
||||||
---@param datetime string?
|
local make_epoch = util.make_epoch
|
||||||
---@return integer
|
|
||||||
local function make_epoch(datetime)
|
|
||||||
if not datetime then
|
|
||||||
return os.time()
|
|
||||||
end
|
|
||||||
local tz_fix = datetime:gsub("+(%d%d):(%d%d)$", "+%1%2")
|
|
||||||
return util.time_piece(tz_fix)
|
|
||||||
end
|
|
||||||
|
|
||||||
local function with_database(f)
|
local function with_database(f)
|
||||||
local fs = FS.new {}
|
|
||||||
local dir, close = util.tmpdir()
|
local dir, close = util.tmpdir()
|
||||||
dir:joinpath("file_frecency.bin"):touch()
|
dir:joinpath("file_frecency.bin"):touch()
|
||||||
return function()
|
return function()
|
||||||
config.setup { debug = true, db_root = dir.filename }
|
config.setup { debug = true, db_root = dir.filename }
|
||||||
local database = Database.new(fs)
|
local database = Database.new()
|
||||||
|
database:start()
|
||||||
f(database)
|
f(database)
|
||||||
close()
|
close()
|
||||||
end
|
end
|
||||||
@ -33,7 +24,8 @@ end
|
|||||||
---@param epoch integer
|
---@param epoch integer
|
||||||
---@return FrecencyEntry[]
|
---@return FrecencyEntry[]
|
||||||
local function save_and_load(database, tbl, epoch)
|
local function save_and_load(database, tbl, epoch)
|
||||||
database:raw_save(util.v1_table(tbl))
|
---@diagnostic disable-next-line: invisible
|
||||||
|
database:raw_save(util.v1_table(tbl), database:file_lock().target)
|
||||||
async.util.sleep(100)
|
async.util.sleep(100)
|
||||||
local entries = database:get_entries(nil, epoch)
|
local entries = database:get_entries(nil, epoch)
|
||||||
table.sort(entries, function(a, b)
|
table.sort(entries, function(a, b)
|
||||||
|
|||||||
@ -41,7 +41,7 @@ a.describe("file_lock", function()
|
|||||||
with_dir(function(filename)
|
with_dir(function(filename)
|
||||||
local fl = FileLock.new(filename, { retry = 1, interval = 10 })
|
local fl = FileLock.new(filename, { retry = 1, interval = 10 })
|
||||||
a.it("gets successfully", function()
|
a.it("gets successfully", function()
|
||||||
local err, fd = async.uv.fs_open(fl.filename, "wx", tonumber("600", 8))
|
local err, fd = async.uv.fs_open(fl.lock, "wx", tonumber("600", 8))
|
||||||
assert.is.Nil(err)
|
assert.is.Nil(err)
|
||||||
assert.is.Nil(async.uv.fs_close(fd))
|
assert.is.Nil(async.uv.fs_close(fd))
|
||||||
assert.is.Nil(fl:get())
|
assert.is.Nil(fl:get())
|
||||||
@ -131,7 +131,7 @@ a.describe("file_lock", function()
|
|||||||
assert.are.same(
|
assert.are.same(
|
||||||
"lock not found",
|
"lock not found",
|
||||||
fl:with(function()
|
fl:with(function()
|
||||||
assert.is.Nil(async.uv.fs_unlink(fl.filename))
|
assert.is.Nil(async.uv.fs_unlink(fl.lock))
|
||||||
return nil
|
return nil
|
||||||
end)
|
end)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,112 +2,14 @@
|
|||||||
-- https://github.com/nvim-lua/plenary.nvim/blob/663246936325062427597964d81d30eaa42ab1e4/lua/plenary/test_harness.lua#L86-L86
|
-- https://github.com/nvim-lua/plenary.nvim/blob/663246936325062427597964d81d30eaa42ab1e4/lua/plenary/test_harness.lua#L86-L86
|
||||||
vim.opt.runtimepath:append(vim.env.TELESCOPE_PATH)
|
vim.opt.runtimepath:append(vim.env.TELESCOPE_PATH)
|
||||||
|
|
||||||
---@diagnostic disable: invisible, undefined-field
|
|
||||||
local Frecency = require "frecency.klass"
|
|
||||||
local Picker = require "frecency.picker"
|
|
||||||
local util = require "frecency.tests.util"
|
local util = require "frecency.tests.util"
|
||||||
local log = require "plenary.log"
|
local log = require "plenary.log"
|
||||||
local Path = require "plenary.path"
|
|
||||||
local config = require "frecency.config"
|
|
||||||
|
|
||||||
---@param datetime string?
|
local filepath = util.filepath
|
||||||
---@return integer
|
local make_epoch = util.make_epoch
|
||||||
local function make_epoch(datetime)
|
local make_register = util.make_register
|
||||||
if not datetime then
|
local with_fake_register = util.with_fake_register
|
||||||
return os.time()
|
local with_files = util.with_files
|
||||||
end
|
|
||||||
local tz_fix = datetime:gsub("+(%d%d):(%d%d)$", "+%1%2")
|
|
||||||
return util.time_piece(tz_fix)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param files string[]
|
|
||||||
---@param cb_or_config table|fun(frecency: Frecency, finder: FrecencyFinder, dir: FrecencyPlenaryPath): nil
|
|
||||||
---@param callback? fun(frecency: Frecency, finder: FrecencyFinder, dir: FrecencyPlenaryPath): nil
|
|
||||||
---@return nil
|
|
||||||
local function with_files(files, cb_or_config, callback)
|
|
||||||
local dir, close = util.make_tree(files)
|
|
||||||
local cfg
|
|
||||||
if type(cb_or_config) == "table" then
|
|
||||||
cfg = vim.tbl_extend("force", { debug = true, db_root = dir.filename }, cb_or_config)
|
|
||||||
else
|
|
||||||
cfg = { debug = true, db_root = dir.filename }
|
|
||||||
callback = cb_or_config
|
|
||||||
end
|
|
||||||
assert(callback)
|
|
||||||
log.debug(cfg)
|
|
||||||
config.setup(cfg)
|
|
||||||
local frecency = Frecency.new()
|
|
||||||
frecency.database.tbl:wait_ready()
|
|
||||||
frecency.picker =
|
|
||||||
Picker.new(frecency.database, frecency.entry_maker, frecency.fs, frecency.recency, { editing_bufnr = 0 })
|
|
||||||
local finder = frecency.picker:finder {}
|
|
||||||
callback(frecency, finder, dir)
|
|
||||||
close()
|
|
||||||
end
|
|
||||||
|
|
||||||
local function filepath(dir, file)
|
|
||||||
return dir:joinpath(file):absolute()
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param frecency Frecency
|
|
||||||
---@param dir FrecencyPlenaryPath
|
|
||||||
---@return fun(file: string, epoch: integer, reset: boolean?): nil
|
|
||||||
local function make_register(frecency, dir)
|
|
||||||
return function(file, epoch, reset)
|
|
||||||
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)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param frecency Frecency
|
|
||||||
---@param dir FrecencyPlenaryPath
|
|
||||||
---@param callback fun(register: fun(file: string, epoch?: integer): nil): nil
|
|
||||||
---@return nil
|
|
||||||
local function with_fake_register(frecency, dir, callback)
|
|
||||||
local bufnr = 0
|
|
||||||
local buffers = {}
|
|
||||||
local original_nvim_buf_get_name = vim.api.nvim_buf_get_name
|
|
||||||
---@diagnostic disable-next-line: redefined-local, duplicate-set-field
|
|
||||||
vim.api.nvim_buf_get_name = function(bufnr)
|
|
||||||
return buffers[bufnr]
|
|
||||||
end
|
|
||||||
---@param file string
|
|
||||||
---@param epoch integer
|
|
||||||
local function register(file, epoch)
|
|
||||||
local path = filepath(dir, file)
|
|
||||||
Path.new(path):touch()
|
|
||||||
bufnr = bufnr + 1
|
|
||||||
buffers[bufnr] = path
|
|
||||||
frecency:register(bufnr, epoch)
|
|
||||||
end
|
|
||||||
callback(register)
|
|
||||||
vim.api.nvim_buf_get_name = original_nvim_buf_get_name
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param choice "y"|"n"
|
|
||||||
---@param callback fun(called: fun(): integer): nil
|
|
||||||
---@return nil
|
|
||||||
local function with_fake_vim_ui_select(choice, callback)
|
|
||||||
local original_vim_ui_select = vim.ui.select
|
|
||||||
local count = 0
|
|
||||||
local function called()
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
---@diagnostic disable-next-line: duplicate-set-field
|
|
||||||
vim.ui.select = function(_, opts, on_choice)
|
|
||||||
count = count + 1
|
|
||||||
log.info(opts.prompt)
|
|
||||||
log.info(opts.format_item(choice))
|
|
||||||
on_choice(choice)
|
|
||||||
end
|
|
||||||
callback(called)
|
|
||||||
vim.ui.select = original_vim_ui_select
|
|
||||||
end
|
|
||||||
|
|
||||||
describe("frecency", function()
|
describe("frecency", function()
|
||||||
describe("register", function()
|
describe("register", function()
|
||||||
@ -116,7 +18,10 @@ describe("frecency", function()
|
|||||||
local register = make_register(frecency, dir)
|
local register = make_register(frecency, dir)
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
local epoch2 = make_epoch "2023-07-29T01:00:00+09:00"
|
local epoch2 = make_epoch "2023-07-29T01:00:00+09:00"
|
||||||
|
-- HACK: This suspicious 'swapfile' setting is for avoiding E303.
|
||||||
|
vim.o.swapfile = false
|
||||||
register("hoge1.txt", epoch1)
|
register("hoge1.txt", epoch1)
|
||||||
|
vim.o.swapfile = true
|
||||||
register("hoge2.txt", epoch2)
|
register("hoge2.txt", epoch2)
|
||||||
|
|
||||||
it("has valid records in DB", function()
|
it("has valid records in DB", function()
|
||||||
@ -301,208 +206,6 @@ describe("frecency", function()
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
describe("validate_database", function()
|
|
||||||
describe("when no files are unlinked", function()
|
|
||||||
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
|
|
||||||
it("removes no entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("when with not force", function()
|
|
||||||
describe("when files are unlinked but it is less than threshold", function()
|
|
||||||
with_files(
|
|
||||||
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
|
||||||
{ db_validate_threshold = 3 },
|
|
||||||
function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
|
||||||
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
|
||||||
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
register("hoge3.txt", epoch3)
|
|
||||||
register("hoge4.txt", epoch4)
|
|
||||||
register("hoge5.txt", epoch5)
|
|
||||||
dir:joinpath("hoge1.txt"):rm()
|
|
||||||
dir:joinpath("hoge2.txt"):rm()
|
|
||||||
frecency:validate_database()
|
|
||||||
|
|
||||||
it("removes no entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
table.sort(results, function(a, b)
|
|
||||||
return a.path < b.path
|
|
||||||
end)
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge3.txt"), score = 10, timestamps = { epoch3 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("when files are unlinked and it is more than threshold", function()
|
|
||||||
describe('when the user response "yes"', function()
|
|
||||||
with_files(
|
|
||||||
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
|
||||||
{ db_validate_threshold = 3 },
|
|
||||||
function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
|
||||||
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
|
||||||
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
register("hoge3.txt", epoch3)
|
|
||||||
register("hoge4.txt", epoch4)
|
|
||||||
register("hoge5.txt", epoch5)
|
|
||||||
dir:joinpath("hoge1.txt"):rm()
|
|
||||||
dir:joinpath("hoge2.txt"):rm()
|
|
||||||
dir:joinpath("hoge3.txt"):rm()
|
|
||||||
|
|
||||||
with_fake_vim_ui_select("y", function(called)
|
|
||||||
frecency:validate_database()
|
|
||||||
|
|
||||||
it("called vim.ui.select()", function()
|
|
||||||
assert.are.same(1, called())
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("removes entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
table.sort(results, function(a, b)
|
|
||||||
return a.path < b.path
|
|
||||||
end)
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe('when the user response "no"', function()
|
|
||||||
with_files(
|
|
||||||
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
|
||||||
{ db_validate_threshold = 3 },
|
|
||||||
function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
|
||||||
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
|
||||||
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
register("hoge3.txt", epoch3)
|
|
||||||
register("hoge4.txt", epoch4)
|
|
||||||
register("hoge5.txt", epoch5)
|
|
||||||
dir:joinpath("hoge1.txt"):rm()
|
|
||||||
dir:joinpath("hoge2.txt"):rm()
|
|
||||||
dir:joinpath("hoge3.txt"):rm()
|
|
||||||
|
|
||||||
with_fake_vim_ui_select("n", function(called)
|
|
||||||
frecency:validate_database()
|
|
||||||
|
|
||||||
it("called vim.ui.select()", function()
|
|
||||||
assert.are.same(1, called())
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("removes no entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
table.sort(results, function(a, b)
|
|
||||||
return a.path < b.path
|
|
||||||
end)
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge3.txt"), score = 10, timestamps = { epoch3 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
|
||||||
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("when with force", function()
|
|
||||||
describe("when db_safe_mode is true", function()
|
|
||||||
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
dir:joinpath("hoge1.txt"):rm()
|
|
||||||
|
|
||||||
with_fake_vim_ui_select("y", function(called)
|
|
||||||
frecency:validate_database(true)
|
|
||||||
|
|
||||||
it("called vim.ui.select()", function()
|
|
||||||
assert.are.same(1, called())
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("needs confirmation for removing entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("when db_safe_mode is false", function()
|
|
||||||
with_files({ "hoge1.txt", "hoge2.txt" }, { db_safe_mode = false }, function(frecency, finder, dir)
|
|
||||||
local register = make_register(frecency, dir)
|
|
||||||
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
|
||||||
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
|
||||||
register("hoge1.txt", epoch1)
|
|
||||||
register("hoge2.txt", epoch2)
|
|
||||||
dir:joinpath("hoge1.txt"):rm()
|
|
||||||
|
|
||||||
with_fake_vim_ui_select("y", function(called)
|
|
||||||
frecency:validate_database(true)
|
|
||||||
|
|
||||||
it("did not call vim.ui.select()", function()
|
|
||||||
assert.are.same(0, called())
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
it("needs no confirmation for removing entries", function()
|
|
||||||
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
|
||||||
assert.are.same({
|
|
||||||
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
|
||||||
}, results)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
|
|
||||||
describe("delete", function()
|
describe("delete", function()
|
||||||
describe("when file exists", function()
|
describe("when file exists", function()
|
||||||
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
||||||
@ -515,8 +218,9 @@ describe("frecency", function()
|
|||||||
it("deletes the file successfully", function()
|
it("deletes the file successfully", function()
|
||||||
local path = filepath(dir, "hoge2.txt")
|
local path = filepath(dir, "hoge2.txt")
|
||||||
local result
|
local result
|
||||||
---@diagnostic disable-next-line: duplicate-set-field
|
---@diagnostic disable-next-line: duplicate-set-field, invisible
|
||||||
frecency.notify = function(self, fmt, ...)
|
frecency.notify = function(self, fmt, ...)
|
||||||
|
---@diagnostic disable-next-line: invisible
|
||||||
vim.notify(self:message(fmt, ...))
|
vim.notify(self:message(fmt, ...))
|
||||||
result = true
|
result = true
|
||||||
end
|
end
|
||||||
|
|||||||
258
lua/frecency/tests/frecency_validate_database_spec.lua
Normal file
258
lua/frecency/tests/frecency_validate_database_spec.lua
Normal file
@ -0,0 +1,258 @@
|
|||||||
|
local util = require "frecency.tests.util"
|
||||||
|
local async = require "plenary.async"
|
||||||
|
|
||||||
|
local filepath = util.filepath
|
||||||
|
local make_epoch = util.make_epoch
|
||||||
|
local make_register = util.make_register
|
||||||
|
local with_fake_vim_ui_select = util.with_fake_vim_ui_select
|
||||||
|
local with_files = util.with_files
|
||||||
|
|
||||||
|
-- HACK: avoid error:
|
||||||
|
-- E5560: nvim_echo must not be called in a lua loop callback
|
||||||
|
vim.notify = function(_, _) end
|
||||||
|
|
||||||
|
describe("frecency", function()
|
||||||
|
describe("validate_database", function()
|
||||||
|
describe("when no files are unlinked", function()
|
||||||
|
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
|
||||||
|
it("removes no entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("when with not force", function()
|
||||||
|
describe("when files are unlinked but it is less than threshold", function()
|
||||||
|
with_files(
|
||||||
|
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
||||||
|
{ db_validate_threshold = 3 },
|
||||||
|
function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
||||||
|
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
||||||
|
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
register("hoge3.txt", epoch3)
|
||||||
|
register("hoge4.txt", epoch4)
|
||||||
|
register("hoge5.txt", epoch5)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
dir:joinpath("hoge2.txt"):rm()
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("removes no entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
table.sort(results, function(a, b)
|
||||||
|
return a.path < b.path
|
||||||
|
end)
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge3.txt"), score = 10, timestamps = { epoch3 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("when files are unlinked and it is more than threshold", function()
|
||||||
|
describe('when the user response "yes"', function()
|
||||||
|
with_files(
|
||||||
|
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
||||||
|
{ db_validate_threshold = 3 },
|
||||||
|
function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
||||||
|
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
||||||
|
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
register("hoge3.txt", epoch3)
|
||||||
|
register("hoge4.txt", epoch4)
|
||||||
|
register("hoge5.txt", epoch5)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
dir:joinpath("hoge2.txt"):rm()
|
||||||
|
dir:joinpath("hoge3.txt"):rm()
|
||||||
|
|
||||||
|
with_fake_vim_ui_select("y", function(called)
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("called vim.ui.select()", function()
|
||||||
|
assert.are.same(1, called())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("removes entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
table.sort(results, function(a, b)
|
||||||
|
return a.path < b.path
|
||||||
|
end)
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe('when the user response "no"', function()
|
||||||
|
with_files(
|
||||||
|
{ "hoge1.txt", "hoge2.txt", "hoge3.txt", "hoge4.txt", "hoge5.txt" },
|
||||||
|
{ db_validate_threshold = 3 },
|
||||||
|
function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
||||||
|
local epoch4 = make_epoch "2023-07-29T00:03:00+09:00"
|
||||||
|
local epoch5 = make_epoch "2023-07-29T00:04:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
register("hoge3.txt", epoch3)
|
||||||
|
register("hoge4.txt", epoch4)
|
||||||
|
register("hoge5.txt", epoch5)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
dir:joinpath("hoge2.txt"):rm()
|
||||||
|
dir:joinpath("hoge3.txt"):rm()
|
||||||
|
|
||||||
|
with_fake_vim_ui_select("n", function(called)
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database()
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("called vim.ui.select()", function()
|
||||||
|
assert.are.same(1, called())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("removes no entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
table.sort(results, function(a, b)
|
||||||
|
return a.path < b.path
|
||||||
|
end)
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge1.txt"), score = 10, timestamps = { epoch1 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge3.txt"), score = 10, timestamps = { epoch3 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge4.txt"), score = 10, timestamps = { epoch4 } },
|
||||||
|
{ count = 1, path = filepath(dir, "hoge5.txt"), score = 10, timestamps = { epoch5 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("when with force", function()
|
||||||
|
describe("when db_safe_mode is true", function()
|
||||||
|
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
|
||||||
|
with_fake_vim_ui_select("y", function(called)
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("called vim.ui.select()", function()
|
||||||
|
assert.are.same(1, called())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("needs confirmation for removing entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("when db_safe_mode is false", function()
|
||||||
|
with_files({ "hoge1.txt", "hoge2.txt" }, { db_safe_mode = false }, function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
|
||||||
|
with_fake_vim_ui_select("y", function(called)
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("did not call vim.ui.select()", function()
|
||||||
|
assert.are.same(0, called())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("needs no confirmation for removing entries", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T02:00:00+09:00")
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "hoge2.txt"), score = 10, timestamps = { epoch2 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
describe("when case sensive filename", function()
|
||||||
|
with_files({ "hoge1.txt", "hoge2.txt" }, function(frecency, finder, dir)
|
||||||
|
local register = make_register(frecency, dir)
|
||||||
|
local epoch1 = make_epoch "2023-07-29T00:00:00+09:00"
|
||||||
|
local epoch2 = make_epoch "2023-07-29T00:01:00+09:00"
|
||||||
|
local epoch3 = make_epoch "2023-07-29T00:02:00+09:00"
|
||||||
|
register("hoge1.txt", epoch1)
|
||||||
|
register("hoge2.txt", epoch2, nil, true)
|
||||||
|
dir:joinpath("hoge1.txt"):rm()
|
||||||
|
dir:joinpath("hoge2.txt"):rename { new_name = dir:joinpath("_hoge2.txt").filename }
|
||||||
|
dir:joinpath("_hoge2.txt"):rename { new_name = dir:joinpath("Hoge2.txt").filename }
|
||||||
|
register("Hoge2.txt", epoch3)
|
||||||
|
|
||||||
|
with_fake_vim_ui_select("y", function(called)
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:validate_database(true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("calls vim.ui.select()", function()
|
||||||
|
assert.are.same(1, called())
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
|
||||||
|
it("removes duplicated case sensitive filenames", function()
|
||||||
|
local results = finder:get_results(nil, make_epoch "2023-07-29T03:00:00+09:00")
|
||||||
|
assert.are.same({
|
||||||
|
{ count = 1, path = filepath(dir, "Hoge2.txt"), score = 10, timestamps = { epoch3 } },
|
||||||
|
}, results)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end)
|
||||||
@ -1,4 +1,9 @@
|
|||||||
|
---@diagnostic disable: invisible, undefined-field
|
||||||
|
local Frecency = require "frecency.klass"
|
||||||
|
local Picker = require "frecency.picker"
|
||||||
|
local config = require "frecency.config"
|
||||||
local uv = vim.uv or vim.loop
|
local uv = vim.uv or vim.loop
|
||||||
|
local log = require "plenary.log"
|
||||||
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
local async = require "plenary.async" --[[@as FrecencyPlenaryAsync]]
|
||||||
local Path = require "plenary.path"
|
local Path = require "plenary.path"
|
||||||
local Job = require "plenary.job"
|
local Job = require "plenary.job"
|
||||||
@ -7,7 +12,18 @@ local wait = require "frecency.tests.wait"
|
|||||||
---@return FrecencyPlenaryPath
|
---@return FrecencyPlenaryPath
|
||||||
---@return fun(): nil close swwp all entries
|
---@return fun(): nil close swwp all entries
|
||||||
local function tmpdir()
|
local function tmpdir()
|
||||||
local dir = Path:new(Path:new(assert(uv.fs_mkdtemp "tests_XXXXXX")):absolute())
|
local ci = uv.os_getenv "CI"
|
||||||
|
local dir
|
||||||
|
if ci then
|
||||||
|
dir = Path:new(assert(uv.fs_mkdtemp "tests_XXXXXX"))
|
||||||
|
else
|
||||||
|
local tmp = assert(uv.os_tmpdir())
|
||||||
|
-- HACK: plenary.path resolves paths later, so here it resolves in advance.
|
||||||
|
if uv.os_uname().sysname == "Darwin" then
|
||||||
|
tmp = tmp:gsub("^/var", "/private/var")
|
||||||
|
end
|
||||||
|
dir = Path:new(assert(uv.fs_mkdtemp(Path:new(tmp, "tests_XXXXXX").filename)))
|
||||||
|
end
|
||||||
return dir, function()
|
return dir, function()
|
||||||
dir:rm { recursive = true }
|
dir:rm { recursive = true }
|
||||||
end
|
end
|
||||||
@ -37,6 +53,8 @@ local AsyncJob = async.wrap(function(cmd, callback)
|
|||||||
end, 2)
|
end, 2)
|
||||||
|
|
||||||
-- NOTE: vim.fn.strptime cannot be used in Lua loop
|
-- NOTE: vim.fn.strptime cannot be used in Lua loop
|
||||||
|
---@param iso8601 string
|
||||||
|
---@return integer?
|
||||||
local function time_piece(iso8601)
|
local function time_piece(iso8601)
|
||||||
local epoch
|
local epoch
|
||||||
wait(function()
|
wait(function()
|
||||||
@ -47,9 +65,130 @@ local function time_piece(iso8601)
|
|||||||
return epoch
|
return epoch
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param datetime string?
|
||||||
|
---@return integer
|
||||||
|
local function make_epoch(datetime)
|
||||||
|
if not datetime then
|
||||||
|
return os.time()
|
||||||
|
end
|
||||||
|
local tz_fix = datetime:gsub("+(%d%d):(%d%d)$", "+%1%2")
|
||||||
|
return time_piece(tz_fix) or 0
|
||||||
|
end
|
||||||
|
|
||||||
---@param records table<string, FrecencyDatabaseRecordValue>
|
---@param records table<string, FrecencyDatabaseRecordValue>
|
||||||
local function v1_table(records)
|
local function v1_table(records)
|
||||||
return { version = "v1", records = records }
|
return { version = "v1", records = records }
|
||||||
end
|
end
|
||||||
|
|
||||||
return { make_tree = make_tree, tmpdir = tmpdir, v1_table = v1_table, time_piece = time_piece }
|
---@param files string[]
|
||||||
|
---@param cb_or_config table|fun(frecency: Frecency, finder: FrecencyFinder, dir: FrecencyPlenaryPath): nil
|
||||||
|
---@param callback? fun(frecency: Frecency, finder: FrecencyFinder, dir: FrecencyPlenaryPath): nil
|
||||||
|
---@return nil
|
||||||
|
local function with_files(files, cb_or_config, callback)
|
||||||
|
local dir, close = make_tree(files)
|
||||||
|
local cfg
|
||||||
|
if type(cb_or_config) == "table" then
|
||||||
|
cfg = vim.tbl_extend("force", { debug = true, db_root = dir.filename }, cb_or_config)
|
||||||
|
else
|
||||||
|
cfg = { debug = true, db_root = dir.filename }
|
||||||
|
callback = cb_or_config
|
||||||
|
end
|
||||||
|
assert(callback)
|
||||||
|
log.debug(cfg)
|
||||||
|
config.setup(cfg)
|
||||||
|
local frecency = Frecency.new()
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency.database:start()
|
||||||
|
frecency.database.tbl:wait_ready()
|
||||||
|
end)
|
||||||
|
frecency.picker = Picker.new(frecency.database, frecency.entry_maker, frecency.recency, { editing_bufnr = 0 })
|
||||||
|
local finder = frecency.picker:finder {}
|
||||||
|
callback(frecency, finder, dir)
|
||||||
|
close()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function filepath(dir, file)
|
||||||
|
return dir:joinpath(file):absolute()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param frecency Frecency
|
||||||
|
---@param dir FrecencyPlenaryPath
|
||||||
|
---@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)
|
||||||
|
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)
|
||||||
|
-- HACK: This is needed because almost the same filenames use the same
|
||||||
|
-- buffer.
|
||||||
|
if wipeout then
|
||||||
|
vim.cmd.bwipeout()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param frecency Frecency
|
||||||
|
---@param dir FrecencyPlenaryPath
|
||||||
|
---@param callback fun(register: fun(file: string, epoch?: integer): nil): nil
|
||||||
|
---@return nil
|
||||||
|
local function with_fake_register(frecency, dir, callback)
|
||||||
|
local bufnr = 0
|
||||||
|
local buffers = {}
|
||||||
|
local original_nvim_buf_get_name = vim.api.nvim_buf_get_name
|
||||||
|
---@diagnostic disable-next-line: redefined-local, duplicate-set-field
|
||||||
|
vim.api.nvim_buf_get_name = function(bufnr)
|
||||||
|
return buffers[bufnr]
|
||||||
|
end
|
||||||
|
---@param file string
|
||||||
|
---@param epoch integer
|
||||||
|
local function register(file, epoch)
|
||||||
|
local path = filepath(dir, file)
|
||||||
|
Path.new(path):touch()
|
||||||
|
bufnr = bufnr + 1
|
||||||
|
buffers[bufnr] = path
|
||||||
|
async.util.block_on(function()
|
||||||
|
frecency:register(bufnr, epoch)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
callback(register)
|
||||||
|
vim.api.nvim_buf_get_name = original_nvim_buf_get_name
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param choice "y"|"n"
|
||||||
|
---@param callback fun(called: fun(): integer): nil
|
||||||
|
---@return nil
|
||||||
|
local function with_fake_vim_ui_select(choice, callback)
|
||||||
|
local original_vim_ui_select = vim.ui.select
|
||||||
|
local count = 0
|
||||||
|
local function called()
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
---@diagnostic disable-next-line: duplicate-set-field
|
||||||
|
vim.ui.select = function(_, opts, on_choice)
|
||||||
|
count = count + 1
|
||||||
|
log.info(opts.prompt)
|
||||||
|
log.info(opts.format_item(choice))
|
||||||
|
on_choice(choice)
|
||||||
|
end
|
||||||
|
callback(called)
|
||||||
|
vim.ui.select = original_vim_ui_select
|
||||||
|
end
|
||||||
|
|
||||||
|
return {
|
||||||
|
filepath = filepath,
|
||||||
|
make_epoch = make_epoch,
|
||||||
|
make_register = make_register,
|
||||||
|
make_tree = make_tree,
|
||||||
|
tmpdir = tmpdir,
|
||||||
|
v1_table = v1_table,
|
||||||
|
with_fake_register = with_fake_register,
|
||||||
|
with_fake_vim_ui_select = with_fake_vim_ui_select,
|
||||||
|
with_files = with_files,
|
||||||
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
---@field make_relative fun(self: FrecencyPlenaryPath, cwd: string): string
|
---@field make_relative fun(self: FrecencyPlenaryPath, cwd: string): string
|
||||||
---@field parent fun(self: FrecencyPlenaryPath): FrecencyPlenaryPath
|
---@field parent fun(self: FrecencyPlenaryPath): FrecencyPlenaryPath
|
||||||
---@field path { sep: string }
|
---@field path { sep: string }
|
||||||
|
---@field rename fun(self: FrecencyPlenaryPath, opts: { new_name: string }): nil
|
||||||
---@field rm fun(self: FrecencyPlenaryPath, opts?: { recursive: boolean }): nil
|
---@field rm fun(self: FrecencyPlenaryPath, opts?: { recursive: boolean }): nil
|
||||||
---@field touch fun(self: FrecencyPlenaryPath, opts?: { parents: boolean }): nil
|
---@field touch fun(self: FrecencyPlenaryPath, opts?: { parents: boolean }): nil
|
||||||
|
|
||||||
@ -47,6 +48,11 @@ function FrecencyPlenaryAsync.run(f) end
|
|||||||
---@class FrecencyPlenaryAsyncControlChannel
|
---@class FrecencyPlenaryAsyncControlChannel
|
||||||
---@field mpsc fun(): FrecencyPlenaryAsyncControlChannelTx, FrecencyPlenaryAsyncControlChannelRx
|
---@field mpsc fun(): FrecencyPlenaryAsyncControlChannelTx, FrecencyPlenaryAsyncControlChannelRx
|
||||||
---@field counter fun(): FrecencyPlenaryAsyncControlChannelTx, FrecencyPlenaryAsyncControlChannelRx
|
---@field counter fun(): FrecencyPlenaryAsyncControlChannelTx, FrecencyPlenaryAsyncControlChannelRx
|
||||||
|
local FrecencyPlenaryAsyncControlChannel
|
||||||
|
|
||||||
|
---@return fun(...): nil tx
|
||||||
|
---@return async fun(): ... rx
|
||||||
|
function FrecencyPlenaryAsyncControlChannel.oneshot() end
|
||||||
|
|
||||||
---@class FrecencyPlenaryAsyncControlChannelTx
|
---@class FrecencyPlenaryAsyncControlChannelTx
|
||||||
---@field send fun(entry?: any): nil
|
---@field send fun(entry?: any): nil
|
||||||
@ -92,6 +98,12 @@ function FrecencyPlenaryAsyncUv.fs_stat(path) end
|
|||||||
---@return integer fd
|
---@return integer fd
|
||||||
function FrecencyPlenaryAsyncUv.fs_open(path, flags, mode) end
|
function FrecencyPlenaryAsyncUv.fs_open(path, flags, mode) end
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@param path string
|
||||||
|
---@return string? err
|
||||||
|
---@return string? path
|
||||||
|
function FrecencyPlenaryAsyncUv.fs_realpath(path) end
|
||||||
|
|
||||||
---@async
|
---@async
|
||||||
---@param fd integer
|
---@param fd integer
|
||||||
---@param size integer
|
---@param size integer
|
||||||
@ -119,6 +131,11 @@ function FrecencyPlenaryAsyncUv.fs_unlink(path) end
|
|||||||
---@return string? err
|
---@return string? err
|
||||||
function FrecencyPlenaryAsyncUv.fs_close(fd) end
|
function FrecencyPlenaryAsyncUv.fs_close(fd) end
|
||||||
|
|
||||||
|
---@async
|
||||||
|
---@param async_fns (async fun(...): ...)[]
|
||||||
|
---@return table
|
||||||
|
function FrecencyPlenaryAsyncUtil.join(async_fns) end
|
||||||
|
|
||||||
---@async
|
---@async
|
||||||
---@param ms integer
|
---@param ms integer
|
||||||
---@return nil
|
---@return nil
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user