diff --git a/lua/frecency/config.lua b/lua/frecency/config.lua index e6e41d9..df59769 100644 --- a/lua/frecency/config.lua +++ b/lua/frecency/config.lua @@ -4,6 +4,7 @@ local os_util = require "frecency.os_util" ---@class FrecencyOpts ---@field recency_values? { age: integer, value: integer }[] default: see lua/frecency/config.lua ---@field auto_validate? boolean default: true +---@field bootstrap? boolean default: true ---@field db_root? string default: vim.fn.stdpath "state" ---@field db_safe_mode? boolean default: true ---@field db_validate_threshold? integer default: 10 @@ -33,6 +34,7 @@ local Config = {} ---@class FrecencyRawConfig ---@field recency_values { age: integer, value: integer }[] default: see lua/frecency/config.lua ---@field auto_validate boolean default: true +---@field bootstrap boolean default: true ---@field db_root string default: vim.fn.stdpath "state" ---@field db_safe_mode boolean default: true ---@field db_validate_threshold integer default: 10 @@ -59,6 +61,7 @@ Config.new = function() local keys = { recency_values = true, auto_validate = true, + bootstrap = true, db_root = true, db_safe_mode = true, db_validate_threshold = true, @@ -98,6 +101,7 @@ end ---@type FrecencyRawConfig Config.default_values = { auto_validate = true, + bootstrap = true, db_root = vim.fn.stdpath "state" --[[@as string]], db_safe_mode = true, db_validate_threshold = 10, @@ -158,6 +162,7 @@ Config.setup = function(ext_config) vim.validate { recency_values = { opts.recency_values, "t" }, auto_validate = { opts.auto_validate, "b" }, + bootstrap = { opts.bootstrap, "b" }, db_root = { opts.db_root, "s" }, db_safe_mode = { opts.db_safe_mode, "b" }, db_validate_threshold = { opts.db_validate_threshold, "n" }, diff --git a/lua/frecency/database.lua b/lua/frecency/database.lua index f9d2916..233d523 100644 --- a/lua/frecency/database.lua +++ b/lua/frecency/database.lua @@ -22,6 +22,7 @@ local Path = lazy_require "plenary.path" --[[@as FrecencyPlenaryPath]] ---@field private _file_lock FrecencyFileLock ---@field private file_lock_rx async fun(): ... ---@field private file_lock_tx fun(...): nil +---@field private is_started boolean ---@field private tbl FrecencyDatabaseTable ---@field private version FrecencyDatabaseVersion ---@field private watcher_rx FrecencyPlenaryAsyncControlChannelRx @@ -36,6 +37,7 @@ Database.new = function() return setmetatable({ file_lock_rx = file_lock_rx, file_lock_tx = file_lock_tx, + is_started = false, tbl = Table.new(version), version = version, watcher_rx = watcher_rx, @@ -74,6 +76,11 @@ end ---@async ---@return nil function Database:start() + timer.track "Database:start() start" + if self.is_started then + return + end + self.is_started = true local target = self:filename() self.file_lock_tx(FileLock.new(target)) self.watcher_tx.send "load" @@ -94,6 +101,7 @@ function Database:start() log.debug("DB coroutine end:", mode) end end)() + timer.track "Database:start() finish" end ---@async diff --git a/lua/frecency/init.lua b/lua/frecency/init.lua index 5db13ac..f6b8e38 100644 --- a/lua/frecency/init.lua +++ b/lua/frecency/init.lua @@ -1,7 +1,11 @@ +---@type FrecencyDatabase? +local database + ---This object is intended to be used as a singleton, and is lazily loaded. ---When methods are called at the first time, it calls the constructor and ---setup() to be initialized. ---@class FrecencyInstance +---@field bootstrap async fun(): nil ---@field complete fun(findstart: 1|0, base: string): integer|''|string[] ---@field delete async fun(path: string): nil ---@field query fun(opts?: FrecencyQueryOpts): FrecencyQueryEntry[]|string[] @@ -20,7 +24,7 @@ local frecency = setmetatable({}, { return function(...) if not instance() then - rawset(self, "instance", require("frecency.klass").new()) + rawset(self, "instance", require("frecency.klass").new(database)) local is_async = key == "delete" or key == "validate_database" or key == "register" instance():setup(is_async) end @@ -45,8 +49,9 @@ local function setup(ext_config) end local config = require "frecency.config" - config.setup(ext_config) + local timer = require "frecency.timer" + timer.track "setup() start" vim.api.nvim_set_hl(0, "TelescopeBufferLoaded", { link = "String", default = true }) vim.api.nvim_set_hl(0, "TelescopePathSeparator", { link = "Directory", default = true }) @@ -86,7 +91,15 @@ local function setup(ext_config) end, }) + if config.bootstrap and vim.v.vim_did_enter == 0 then + database = require("frecency.database").new() + async_call(function() + database:start() + end) + end + setup_done = true + timer.track "setup() finish" end return { diff --git a/lua/frecency/klass.lua b/lua/frecency/klass.lua index 7de54e4..78d0cd4 100644 --- a/lua/frecency/klass.lua +++ b/lua/frecency/klass.lua @@ -9,16 +9,25 @@ local wait = require "frecency.wait" local lazy_require = require "frecency.lazy_require" local async = lazy_require "plenary.async" --[[@as FrecencyPlenaryAsync]] +---@enum FrecencyStatus +local STATUS = { + NEW = 0, + SETUP_CALLED = 1, + SETUP_FINISHED = 2, +} + ---@class Frecency ---@field private buf_registered table flag to indicate the buffer is registered to the database. ---@field private database FrecencyDatabase ---@field private picker FrecencyPicker +---@field private status FrecencyStatus local Frecency = {} +---@param database? FrecencyDatabase ---@return Frecency -Frecency.new = function() - local self = setmetatable({ buf_registered = {} }, { __index = Frecency }) --[[@as Frecency]] - self.database = Database.new() +Frecency.new = function(database) + local self = setmetatable({ buf_registered = {}, status = STATUS.NEW }, { __index = Frecency }) --[[@as Frecency]] + self.database = database or Database.new() return self end @@ -26,22 +35,35 @@ end ---@param is_async boolean ---@return nil function Frecency:setup(is_async) + if self.status >= STATUS.SETUP_CALLED then + return + end + self.status = STATUS.SETUP_CALLED + timer.track "frecency.setup() start" + ---@async local function init() - timer.track "init() start" self.database:start() self:assert_db_entries() if config.auto_validate then self:validate_database() end - timer.track "init() finish" + timer.track "frecency.setup() finish" + self.status = STATUS.SETUP_FINISHED end if is_async then init() - else - wait(init) + return end + + local ok, status = wait(init) + if ok then + return + end + -- NOTE: This means init() has failed. Try again. + self.status = STATUS.NEW + self:error(status == -1 and "init() never returns during the time" or "init() is interrupted during the time") end ---This can be calledBy `require("telescope").extensions.frecency.frecency`. @@ -79,8 +101,11 @@ end ---@param force? boolean ---@return nil function Frecency:validate_database(force) + timer.track "validate_database() start" local unlinked = self.database:unlinked_entries() + timer.track "validate_database() calculate unlinked" if #unlinked == 0 or (not force and #unlinked < config.db_validate_threshold) then + timer.track "validate_database() finish: no unlinked" return end local function remove_entries() @@ -89,6 +114,7 @@ function Frecency:validate_database(force) end if not config.db_safe_mode then remove_entries() + timer.track "validate_database() finish: removed" return end -- HACK: This is needed because the default implementaion of vim.ui.select() diff --git a/lua/frecency/timer.lua b/lua/frecency/timer.lua index 7d0fbf5..1dc5f49 100644 --- a/lua/frecency/timer.lua +++ b/lua/frecency/timer.lua @@ -18,21 +18,11 @@ function M.track(event) end if M.has_lazy then local stats = require "lazy.stats" - ---@param n integer - ---@return string - local function make_key(n) - return ("[telescope-frecency] %s: %d"):format(event, n) + local function make_key(num) + local key = num and ("[telescope-frecency] %s: %d"):format(event, num) or "[telescope-frecency] " .. event + return stats._stats.times[key] and make_key((num or 1) + 1) or key end - local key - local num = 0 - while true do - key = make_key(num) - if not stats._stats.times[key] then - break - end - num = num + 1 - end - stats.track(key) + stats.track(make_key()) end end diff --git a/lua/frecency/wait.lua b/lua/frecency/wait.lua index a27efd9..a8f5be4 100644 --- a/lua/frecency/wait.lua +++ b/lua/frecency/wait.lua @@ -40,15 +40,8 @@ end ---@param f FrecencyWaitCallback ---@param opts FrecencyWaitConfig? ----@return nil +---@return boolean ok +---@return nil|-1|-2 status return function(f, opts) - local wait = Wait.new(f, opts) - local ok, status = wait:run() - if ok then - return - elseif status == -1 then - error "callback never returnes during the time" - elseif status == -2 then - error "callback is interrupted during the time" - end + return Wait.new(f, opts):run() end