From 363f3e78a33282b2efb4446e7615efe9c9d39d74 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Mon, 26 May 2025 01:10:15 +0300 Subject: [PATCH] refactor: optimize --- lua/lualine-harpoon/component.lua | 127 ++++++++++++++++++++++++++++-- lua/lualine-harpoon/config.lua | 28 +++++++ lua/lualine-harpoon/health.lua | 25 ++++-- lua/lualine-harpoon/init.lua | 25 +++++- lua/lualine-harpoon/status.lua | 34 ++++++-- lua/lualine-harpoon/utils.lua | 37 +++++++-- 6 files changed, 247 insertions(+), 29 deletions(-) diff --git a/lua/lualine-harpoon/component.lua b/lua/lualine-harpoon/component.lua index b8aa8d1..90c2349 100644 --- a/lua/lualine-harpoon/component.lua +++ b/lua/lualine-harpoon/component.lua @@ -4,23 +4,134 @@ local cfg = require("lualine-harpoon.config") local req = require("lualine_require") local Component = req.require("lualine.component") +---@class LualineHarpoonComponent : lualine.Component +---@field cache LualineHarpoonCache +---@field options LualineHarpoonConfig local M = Component:extend() +---@class LualineHarpoonCache +---@field result string? +---@field is_valid boolean +---@field last_buf string? +---@field last_changedtick integer? +---@field last_status_hash string? + +---@class LualineHarpoonConfig +---@field symbol LualineHarpoonSymbols +---@field icon string +---@field show_when_empty boolean +---@field show_icon boolean +---@field format function? +---@field colors LualineHarpoonColors +---@field cache_timeout integer + +---@class LualineHarpoonSymbols +---@field open string +---@field close string +---@field separator string +---@field unknown string + +---@class LualineHarpoonColors +---@field active string? +---@field inactive string? + +---@param opts table? function M:init(opts) - M.super.init(self, opts) + M.super:init(opts) self.options = vim.tbl_deep_extend("force", cfg, self.options or {}) + + self.cache = { + result = nil, + is_valid = false, + last_buf = nil, + last_changedtick = nil, + last_status_hash = nil, + } + + self:setup_cache_invalidation() end -function M:update_status() - local st = status.get_status() - if st.total == 0 then - return "" +---@private +function M:setup_cache_invalidation() + local group_name = "LualineHarpoon_" .. tostring(self) + local group = vim.api.nvim_create_augroup(group_name, { clear = true }) + + vim.api.nvim_create_autocmd({ + "BufEnter", + "BufWritePost", + "User", + }, { + group = group, + callback = function() + self.cache.is_valid = false + end, + }) +end + +---@param st HarpoonStatus +---@return string +function M:hash_status(st) + return string.format("%s:%s", tostring(st.current or "nil"), st.total) +end + +---@return boolean +function M:is_cache_valid() + if not self.cache.is_valid then + return false end - local s = self.options.symbol - local n = st.current and tostring(st.current) or s.unknown - return string.format("%s%s%s%d%s", s.open, n, s.separator, st.total, s.close) + + local current_buf = vim.api.nvim_buf_get_name(0) + local current_changetick = vim.api.nvim_buf_get_changedtick(0) + + if self.cache.last_buf ~= current_buf or self.cache.last_changedtick ~= current_changetick then + return false + end + + local current_status = status.get_status() + local current_hash = self:hash_status(current_status) + + return self.cache.last_status_hash == current_hash end +---@param new_cfg LualineHarpoonConfig +function M.upadte_config(new_cfg) + cfg = new_cfg +end + +---@param result string +---@private +function M:update_cache(result) + local current_status = status.get_status() + + self.cache.result = result + self.cache.is_valid = true + self.cache.last_buf = vim.api.nvim_buf_get_name(0) + self.cache.last_changedtick = vim.api.nvim_buf_get_changedtick(0) + self.cache.last_status_hash = self:hash_status(current_status) +end + +---@return string +function M:update_status() + if self:is_cache_valid() then + return self.cache.result + end + + local st = status.get_status() + local result = "" + + if type(self.options.format) == "function" then + result = self.options.format(st.current, st.total) + elseif st.total > 0 then + local s = self.options.symbol + local n = st.current and tostring(st.current) or s.unknown + result = string.format("%s%s%s%d%s", s.open, n, s.separator, st.total, s.close) + end + + self:update_cache(result) + return result +end + +---@return string function M:draw_status() local text = self:update_status() if self.color_hl and text ~= "" then diff --git a/lua/lualine-harpoon/config.lua b/lua/lualine-harpoon/config.lua index cbfb1dd..c802974 100644 --- a/lua/lualine-harpoon/config.lua +++ b/lua/lualine-harpoon/config.lua @@ -1,3 +1,23 @@ +---@class LualineHarpoonConfig +---@field symbol LualineHarpoonSymbols +---@field icon string +---@field show_when_empty boolean +---@field show_icon boolean +---@field format function? +---@field colors LualineHarpoonColors +---@field cache_timeout integer + +---@class LualineHarpoonSymbols +---@field open string +---@field close string +---@field separator string +---@field unknown string + +---@class LualineHarpoonColors +---@field active string? +---@field inactive string? + +---@type LualineHarpoonConfig local M = { symbol = { open = "[", @@ -6,6 +26,14 @@ local M = { unknown = "?", }, icon = "󰀱", + show_when_empty = false, + show_icon = true, + format = nil, + colors = { + active = nil, + inactive = nil, + }, + cache_timeout = 100, } return M diff --git a/lua/lualine-harpoon/health.lua b/lua/lualine-harpoon/health.lua index 18d9a6f..a1f4617 100644 --- a/lua/lualine-harpoon/health.lua +++ b/lua/lualine-harpoon/health.lua @@ -1,27 +1,40 @@ +---@class LualineHarpoonHealth local M = {} +---@return nil function M.check() vim.health.start("lualine-harpoon") - local has_lualine = pcall(require, "lualine") + -- Check for required dependencies + local has_lualine, lualine_version = pcall(require, "lualine") if has_lualine then vim.health.ok("lualine is installed") + if lualine_version and lualine_version.version then + vim.health.info("lualine version: " .. lualine_version.version) + end else vim.health.error("lualine is not installed") end - local has_harpoon = pcall(require, "harpoon") + local has_harpoon, harpoon_module = pcall(require, "harpoon") if has_harpoon then vim.health.ok("harpoon is installed") + -- Check if it's harpoon 2.x + if harpoon_module and harpoon_module.list then + vim.health.info("harpoon 2.x detected") + else + vim.health.warn("harpoon 1.x detected - may not be fully compatible") + end else vim.health.warn("harpoon is not installed - component will show empty") end - local has_plenary = pcall(require, "plenary.path") - if has_plenary then - vim.health.ok("plenary.nvim is installed (recommended)") + -- Check Neovim version + local nvim_version = vim.version() + if nvim_version.major == 0 and nvim_version.minor < 8 then + vim.health.error("Neovim 0.8+ is required") else - vim.health.warn("plenary.nvim is not installed - path normalization disabled") + vim.health.ok(string.format("Neovim %d.%d.%d", nvim_version.major, nvim_version.minor, nvim_version.patch)) end end diff --git a/lua/lualine-harpoon/init.lua b/lua/lualine-harpoon/init.lua index c7c3386..6f2361c 100644 --- a/lua/lualine-harpoon/init.lua +++ b/lua/lualine-harpoon/init.lua @@ -1,9 +1,32 @@ local component = require("lualine-harpoon.component") +local cfg = require("lualine-harpoon.config") +---@class LualineHarpoonPlugin +---@field component LualineHarpoonComponent local M = { component = component, } -function M.setup(_opts) end +---@param opts LualineHarpoonConfig? +---@return nil +function M.setup(opts) + opts = opts or {} + + -- Validate options + if opts.cache_timeout and type(opts.cache_timeout) ~= "number" then + vim.notify("lualine-harpoon: cache_timeout must be a number", vim.log.levels.WARN) + opts.cache_timeout = nil + end + + if opts.format and type(opts.format) ~= "function" then + vim.notify("lualine-harpoon: format must be a function", vim.log.levels.WARN) + opts.format = nil + end + + ---@type LualineHarpoonConfig + local updated_cfg = vim.tbl_deep_extend("force", cfg, opts) + + component.upadte_config(updated_cfg) +end return M diff --git a/lua/lualine-harpoon/status.lua b/lua/lualine-harpoon/status.lua index a387978..7a05629 100644 --- a/lua/lualine-harpoon/status.lua +++ b/lua/lualine-harpoon/status.lua @@ -1,27 +1,49 @@ local ok, harpoon = pcall(require, "harpoon") local utils = require("lualine-harpoon.utils") + +---@class HarpoonStatus +---@field current integer? 1-based index of current file in harpoon list +---@field total integer Total number of files in harpoon list + +---@class LualineHarpoonStatus local M = {} --- Get current 1-based index and total Harpoon marks ----@return { current: integer?, total: integer } +---@return HarpoonStatus function M.get_status() if not ok then return { current = nil, total = 0 } end - local list = harpoon:list() - local total = list:length() + local success, list = pcall(function() + return harpoon:list() + end) + if not success or not list then + return { current = nil, total = 0 } + end + + local total = list:length() if total == 0 then return { current = nil, total = 0 } end - local root = list.config.get_root_dir() + local success_root, root = pcall(function() + return list.config.get_root_dir() + end) + + if not success_root then + return { current = nil, total = 0 } + end + local buf = vim.api.nvim_buf_get_name(0) local rel_buf = utils.normalize(buf, root) - local _, idx = list:get_by_value(rel_buf) - if type(idx) ~= "number" or idx < 1 or idx > total then + local success_get, idx = pcall(function() + local _, index = list:get_by_value(rel_buf) + return index + end) + if not success_get or type(idx) ~= "number" or idx < 1 or idx > total then idx = nil end return { current = idx, total = total } diff --git a/lua/lualine-harpoon/utils.lua b/lua/lualine-harpoon/utils.lua index b385952..2e17dc1 100644 --- a/lua/lualine-harpoon/utils.lua +++ b/lua/lualine-harpoon/utils.lua @@ -1,16 +1,37 @@ -local ok_path, Path = pcall(require, "plenary.path") +---@class LualineHarpoonUtils local M = {} ---- Normalize `buf` relative to `root`, if plenary.path is available. ----@param buf string ----@param root string ----@return string +---Normalize `buf` relative to `root` using Neovim built-ins +---@param buf string Absolute path to buffer +---@param root string Root directory path +---@return string Normalized path relative to root function M.normalize(buf, root) - if ok_path then - return Path:new(buf):make_relative(root) - else + if not buf or buf == "" then + return "" + end + + if not root or root == "" then return buf end + + -- Use vim.fs.normalize for consistent path separators (Neovim 0.8+) + if vim.fs and vim.fs.normalize then + buf = vim.fs.normalize(buf) + root = vim.fs.normalize(root) + end + + -- Use vim.fn.fnamemodify to make path relative + local relative = vim.fn.fnamemodify(buf, ":p:.") + + -- If the buffer is within the root directory, make it relative to root + if buf:find("^" .. vim.pesc(root), 1) then + relative = buf:sub(#root + 1) + -- Remove leading path separator + relative = relative:gsub("^[/\\]", "") + return relative ~= "" and relative or "." + end + + return relative end return M