From 6b33ec8f962b090dbb117b88640f24a84c3542e6 Mon Sep 17 00:00:00 2001 From: Guennadi Maximov C Date: Sat, 14 Feb 2026 20:18:38 -0600 Subject: [PATCH] fix: improve/extend LuaLS annotations, code greatly optimized Signed-off-by: Guennadi Maximov C --- lua/wrapped/config.lua | 39 +++---- lua/wrapped/core/files.lua | 15 +-- lua/wrapped/core/git.lua | 103 ++++++++++--------- lua/wrapped/core/plugins.lua | 30 +++--- lua/wrapped/init.lua | 7 +- lua/wrapped/state.lua | 7 +- lua/wrapped/types.lua | 20 ++-- lua/wrapped/ui/highlights.lua | 37 ++++--- lua/wrapped/ui/ui.lua | 184 +++++++++++++++++++++++----------- plugin/wrapped.lua | 6 ++ 10 files changed, 272 insertions(+), 176 deletions(-) diff --git a/lua/wrapped/config.lua b/lua/wrapped/config.lua index 0dd1787..32f961a 100644 --- a/lua/wrapped/config.lua +++ b/lua/wrapped/config.lua @@ -1,22 +1,25 @@ +---@class Wrapped.Config local M = {} ----@type Wrapped.Config -M.defaults = { - path = vim.fn.stdpath "config", - border = false, - size = { - width = 120, - height = 40, - }, - exclude_filetype = { - ".gitmodules", - }, - cap = { - commits = 1000, - plugins = 100, - plugins_ever = 200, - lines = 10000, - }, -} +---@return WrappedConfig defaults +function M.defaults() + return { ---@type WrappedConfig + path = vim.fn.stdpath "config", + border = false, + size = { + width = 120, + height = 40, + }, + exclude_filetype = { + ".gitmodules", + }, + cap = { + commits = 1000, + plugins = 100, + plugins_ever = 200, + lines = 10000, + }, + } +end return M diff --git a/lua/wrapped/core/files.lua b/lua/wrapped/core/files.lua index 2ba0331..e962572 100644 --- a/lua/wrapped/core/files.lua +++ b/lua/wrapped/core/files.lua @@ -1,28 +1,29 @@ +---@class Wrapped.Core.Files local M = {} -local scandir = require "plenary.scandir" +---@return string path local function get_path() return require("wrapped").config.path end ----@return Wrapped.FileStats +---@return Wrapped.FileStats stats function M.get_stats() local config_path = get_path() - local stats = { + local stats = { ---@type Wrapped.FileStats total_lines = 0, biggest = { name = "", lines = 0 }, smallest = { name = "", lines = math.huge }, lines_by_type = {}, } - local files = scandir.scan_dir( + local files = require("plenary.scandir").scan_dir( ---@type string[] config_path, { hidden = true, add_dirs = false, respect_gitignore = false, depth = 20 } ) - local excluded = {} + local excluded = {} ---@type table for _, ext in ipairs(require("wrapped").config.exclude_filetype or {}) do excluded[ext] = true end for _, path in ipairs(files) do - if not path:match "%.git[/\\]" and not path:match "%.git$" then + if not (path:match "%.git[/\\]" or path:match "%.git$") then local filename = vim.fn.fnamemodify(path, ":t") local ext = filename:match "^%." and filename or vim.fn.fnamemodify(filename, ":e") @@ -55,7 +56,7 @@ function M.get_stats() stats.smallest = { name = "None", lines = 0 } end - local sorted = {} + local sorted = {} ---@type Wrapped.FileStat[] for k, v in pairs(stats.lines_by_type) do table.insert(sorted, { name = k, lines = v }) end diff --git a/lua/wrapped/core/git.lua b/lua/wrapped/core/git.lua index 66c5409..58ebb88 100644 --- a/lua/wrapped/core/git.lua +++ b/lua/wrapped/core/git.lua @@ -1,8 +1,12 @@ -local Job = require "plenary.job" +---@class Wrapped.Core.Git local M = {} +local Job = require "plenary.job" +---@return string path local function get_path() return require("wrapped").config.path end +---@param args string[] +---@return string[] result local function exec_git(args) local job = Job:new { command = "git", @@ -13,16 +17,16 @@ local function exec_git(args) return job:result() end ----@return string[] +---@return string[] commits function M.get_commits() local all = exec_git { "log", "--format=%s" } if #all <= 5 then return all end - local random_commits = {} - local indices = {} + local random_commits = {} ---@type string[] + local indices = {} ---@type integer[] while #indices < 5 do local idx = math.random(1, #all) - if not vim.tbl_contains(indices, idx) then + if not vim.list_contains(indices, idx) then table.insert(indices, idx) table.insert(random_commits, all[idx]) end @@ -30,55 +34,52 @@ function M.get_commits() return random_commits end ----@return string +---@return string total function M.get_total_count() return exec_git({ "rev-list", "--count", "HEAD" })[1] or "0" end ----@return string +---@return string first_date function M.get_first_commit_date() return exec_git({ "log", "--reverse", "--format=%ad", "--date=short" })[1] or "Unknown" end -- converts seconds to human-readable +---@param secs integer +---@return string ago local function format_ago(secs) local days = math.floor(secs / 86400) - if days >= 365 then return string.format("%.1f years ago", days / 365) end + if days >= 365 then return ("%.1f years ago"):format(days / 365) end if days >= 30 then return math.floor(days / 30) .. " months ago" end if days >= 1 then return days .. " days ago" end - local hours = math.floor(secs / 3600) - if hours >= 1 then return hours .. " hours ago" end + if secs >= 3600 then return math.floor(secs / 3600) .. " hours ago" end return math.floor(secs / 60) .. " minutes ago" end ----@return Wrapped.ConfigStats +---@return Wrapped.ConfigStats stats function M.get_config_stats() local dates = exec_git { "log", "--format=%ad", "--date=short" } - local unique = {} + local unique = {} ---@type table for _, d in ipairs(dates) do unique[d] = true end -- sort unique dates - local sorted = {} + local sorted = {} ---@type string[] for d in pairs(unique) do table.insert(sorted, d) end table.sort(sorted) -- longest consecutive day streak - local max_streak, cur_streak = 1, 1 + local max_streak, cur_streak = 1, 1 ---@type integer, integer for i = 2, #sorted do - local y, m, d = sorted[i]:match "(%d+)-(%d+)-(%d+)" - local py, pm, pd = sorted[i - 1]:match "(%d+)-(%d+)-(%d+)" + local y, m, d = sorted[i]:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string + local py, pm, pd = sorted[i - 1]:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string local t1 = os.time { year = y, month = m, day = d } local t0 = os.time { year = py, month = pm, day = pd } - if math.abs(t1 - t0) <= 86400 then - cur_streak = cur_streak + 1 - else - cur_streak = 1 - end + cur_streak = math.abs(t1 - t0) <= 86400 and (cur_streak + 1) or 1 if cur_streak > max_streak then max_streak = cur_streak end end @@ -88,12 +89,13 @@ function M.get_config_stats() local last = dates[1] local last_change = "Unknown" if last then - local y, m, d = last:match "(%d+)-(%d+)-(%d+)" + local y, m, d = last:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string if y and m and d then - last_change = format_ago( - now - - os.time { year = tonumber(y), month = tonumber(m), day = tonumber(d) } - ) + last_change = format_ago(now - os.time { + year = tonumber(y, 10), + month = tonumber(m, 10), + day = tonumber(d, 10), + }) end end @@ -101,14 +103,18 @@ function M.get_config_stats() local first = sorted[1] local lifetime = "Unknown" if first then - local y, m, d = first:match "(%d+)-(%d+)-(%d+)" + local y, m, d = first:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string if y and m and d then local age_days = ( now - - os.time { year = tonumber(y), month = tonumber(m), day = tonumber(d) } + - os.time { + year = tonumber(y, 10), + month = tonumber(m, 10), + day = tonumber(d, 10), + } ) / 86400 if age_days >= 365 then - lifetime = string.format("%.1f years old", age_days / 365) + lifetime = ("%.1f years old"):format(age_days / 365) elseif age_days >= 30 then lifetime = math.floor(age_days / 30) .. " months old" else @@ -120,11 +126,11 @@ function M.get_config_stats() local subjects = exec_git { "log", "--format=%s" } local shortest, longest = subjects[1] or "", subjects[1] or "" for _, s in ipairs(subjects) do - if #s < #shortest and #s > 0 then shortest = s end - if #s > #longest then longest = s end + if s:len() < shortest:len() and s:len() > 0 then shortest = s end + if s:len() > longest:len() then longest = s end end - return { + return { ---@type Wrapped.ConfigStats longest_streak = #sorted > 0 and max_streak or 0, last_change = last_change, lifetime = lifetime, @@ -134,15 +140,14 @@ function M.get_config_stats() end -- commit count per day for a given year, keyed as ddmmyyyy ----@param year number ----@return table +---@param year integer +---@return table counts function M.get_commit_activity(year) local dates = exec_git { "log", "--format=%ad", "--date=short" } - local yr = tostring(year) - local counts = {} + local counts = {} ---@type table for _, d in ipairs(dates) do - local y, m, day = d:match "(%d+)-(%d+)-(%d+)" - if y == yr then + local y, m, day = d:match "(%d+)-(%d+)-(%d+)" ---@type string, string, string + if y == tostring(year) then local key = day .. m .. y counts[key] = (counts[key] or 0) + 1 end @@ -150,18 +155,16 @@ function M.get_commit_activity(year) return counts end ----@return number +---@return integer year function M.get_first_commit_year() - local d = M.get_first_commit_date() - local y = d:match "(%d+)" - return tonumber(y) or tonumber(os.date "%Y") + return tonumber(M.get_first_commit_date():match "(%d+)" or os.date "%Y", 10) end -- sample ~12 commits and get total line count at each point ----@return { values: number[], labels: string[] } +---@return { values: integer[], labels: string[] } history function M.get_size_history() local log = exec_git { "log", "--reverse", "--format=%H %ad", "--date=short" } - if #log == 0 then return { values = {}, labels = {} } end + if vim.tbl_isempty(log) then return { values = {}, labels = {} } end -- get the empty tree hash for this repo local empty = Job:new { @@ -170,26 +173,26 @@ function M.get_size_history() cwd = get_path(), } empty:sync() - local empty_tree = (empty:result()[1] or ""):match "%S+" + local empty_tree = (empty:result()[1] or ""):match "%S+" ---@type string -- sample ~20 evenly spaced commits - local samples = {} + local samples = {} ---@type string[] local step = math.max(1, math.floor(#log / 49)) for i = 1, #log, step do table.insert(samples, log[i]) end -- always include latest - if #samples > 0 and samples[#samples] ~= log[#log] then + if not vim.tbl_isempty(samples) and samples[#samples] ~= log[#log] then table.insert(samples, log[#log]) end - local values, labels = {}, {} + local values, labels = {}, {} ---@type integer[], string[] for _, entry in ipairs(samples) do - local hash, date = entry:match "(%S+)%s+(%S+)" + local hash, date = entry:match "(%S+)%s+(%S+)" ---@type string, string if hash and empty_tree then local stat = exec_git { "diff", "--shortstat", empty_tree, hash } local ins = (stat[1] or ""):match "(%d+) insertion" - table.insert(values, tonumber(ins) or 0) + table.insert(values, ins and tonumber(ins, 10) or 0) table.insert(labels, date) end end diff --git a/lua/wrapped/core/plugins.lua b/lua/wrapped/core/plugins.lua index 93fa0a7..8cc5c8b 100644 --- a/lua/wrapped/core/plugins.lua +++ b/lua/wrapped/core/plugins.lua @@ -1,15 +1,16 @@ -local Job = require "plenary.job" +---@class Wrapped.Core.Plugins local M = {} +local Job = require "plenary.job" local function get_path() return require("wrapped").config.path end ----@return number +---@return integer count function M.get_count() local ok, lazy = pcall(require, "lazy") - return ok and lazy.stats().count or 0 + return (ok and lazy) and lazy.stats().count or 0 end ----@return Wrapped.PluginHistory +---@return Wrapped.PluginHistory plugin_history function M.get_history() local job = Job:new { command = "git", @@ -26,12 +27,14 @@ function M.get_history() } job:sync() - local lines = job:result() + local lines = job:result() ---@type string[] + + ---@type table, string|nil, integer local seen, cur_date, total_ever = {}, nil, 0 for _, line in ipairs(lines) do if line:match "^COMMIT_DATE:" then - cur_date = line:match "^COMMIT_DATE:(%d+%-%d+%-%d+)" + cur_date = line:match "^COMMIT_DATE:(%d+%-%d+%-%d+)" --[[@as string]] elseif line:match "^%+" and not line:match "^%+%+%+" then for plugin in line:gmatch "['\"]([%w%-%.%_]+/[%w%-%.%_]+)['\"]" do if not seen[plugin] then @@ -42,13 +45,14 @@ function M.get_history() end end + ---@type string, integer, string, integer local oldest, old_date, newest, new_date local ok, lazy = pcall(require, "lazy") - if ok and lazy.plugins then + if ok and lazy and lazy.plugins then local plugins = lazy.plugins() - local completed, total = 0, 0 - local timestamps = {} + local completed, total = 0, 0 ---@type integer, integer + local timestamps = {} ---@type table for _, plugin in pairs(plugins) do if plugin.dir and vim.fn.isdirectory(plugin.dir) == 1 then @@ -60,8 +64,10 @@ function M.get_history() cwd = plugin.dir, on_exit = function(j, code) if code == 0 then - local res = j:result() - if res[1] then timestamps[plugin.name] = tonumber(res[1]) end + local res = j:result() --[[@as string[]\]] + if res[1] then + timestamps[plugin.name] = tonumber(res[1], 10) + end end completed = completed + 1 end, @@ -82,7 +88,7 @@ function M.get_history() end end - return { + return { ---@type Wrapped.PluginHistory total_ever_installed = total_ever, oldest_plugin = oldest and { name = oldest, date = old_date } or nil, newest_plugin = newest and { name = newest, date = new_date } or nil, diff --git a/lua/wrapped/init.lua b/lua/wrapped/init.lua index 5101c18..585b500 100644 --- a/lua/wrapped/init.lua +++ b/lua/wrapped/init.lua @@ -1,9 +1,10 @@ +---@class Wrapped local M = {} ----@type Wrapped.Config -M.config = require("wrapped.config").defaults +---@type WrappedConfig +M.config = require("wrapped.config").defaults() ----@param opts Wrapped.Config? +---@param opts? WrappedConfig function M.setup(opts) M.config = vim.tbl_deep_extend("force", M.config, opts or {}) end diff --git a/lua/wrapped/state.lua b/lua/wrapped/state.lua index 9204a35..0d9a5b9 100644 --- a/lua/wrapped/state.lua +++ b/lua/wrapped/state.lua @@ -1,7 +1,12 @@ +---@class Wrapped.State +---@field xpad integer +---@field ypad integer +---@field heatmap_year integer +---@field first_commit_year? integer local M = { xpad = 2, ypad = 1, - heatmap_year = tonumber(os.date "%Y"), + heatmap_year = tonumber(os.date "%Y", 10), } return M diff --git a/lua/wrapped/types.lua b/lua/wrapped/types.lua index de3c58b..0f2ba72 100644 --- a/lua/wrapped/types.lua +++ b/lua/wrapped/types.lua @@ -1,33 +1,33 @@ ---@meta ----@class Wrapped.Config ----@field path string? +---@class WrappedConfig +---@field path string|nil ---@field border boolean ----@field size {width: number, height: number} +---@field size { width: integer, height: integer } ---@field exclude_filetype string[] ----@field cap {commits: number, plugins: number, plugins_ever: number, lines: number} +---@field cap { commits: integer, plugins: integer, plugins_ever: integer, lines: integer } ---@class Wrapped.FileStat ---@field name string ---@field lines number ---@class Wrapped.FileStats ----@field total_lines number +---@field total_lines integer ---@field biggest Wrapped.FileStat ---@field smallest Wrapped.FileStat ---@field lines_by_type Wrapped.FileStat[] ---@class Wrapped.PluginInfo ---@field name string ----@field date number +---@field date integer ---@class Wrapped.PluginHistory ----@field total_ever_installed number ----@field oldest_plugin Wrapped.PluginInfo? ----@field newest_plugin Wrapped.PluginInfo? +---@field total_ever_installed integer +---@field oldest_plugin Wrapped.PluginInfo|nil +---@field newest_plugin Wrapped.PluginInfo|nil ---@class Wrapped.ConfigStats ----@field longest_streak number +---@field longest_streak integer ---@field last_change string ---@field lifetime string ---@field shortest_msg string diff --git a/lua/wrapped/ui/highlights.lua b/lua/wrapped/ui/highlights.lua index 39fb3c1..31bc53d 100644 --- a/lua/wrapped/ui/highlights.lua +++ b/lua/wrapped/ui/highlights.lua @@ -1,11 +1,13 @@ -local api = vim.api require "volt.highlights" +local api = vim.api local get_hl = require("volt.utils").get_hl local lighten = require("volt.color").change_hex_lightness local mix = require("volt.color").mix +---@class Wrapped.UI.Highlights local M = {} +---@return string bg local function get_bg() if vim.g.base46_cache then return dofile(vim.g.base46_cache .. "colors").black @@ -16,37 +18,42 @@ end -- 4 base colors cycling across months M.month_colors = { "Red", "Green", "Blue", "Yellow" } +---@param ns integer function M.apply_float(ns) local config = require("wrapped").config local bg = get_bg() - local win_bg = config.border and bg or lighten(bg, 2) - local text_light = get_hl("Normal").fg - local border_bg = config.border and "NONE" or win_bg - local border_fg = config.border and lighten(bg, 15) or win_bg - - api.nvim_set_hl(ns, "Normal", { bg = win_bg, fg = text_light }) - api.nvim_set_hl(ns, "FloatBorder", { fg = border_fg, bg = border_bg }) + local win_bg = config.border and bg or lighten(bg, 2) ---@type string + local text_light = get_hl("Normal").fg ---@type string + local border_bg = config.border and "NONE" or win_bg ---@type string + local border_fg = config.border and lighten(bg, 15) or win_bg ---@type string local title_fg = get_hl("ExBlue").fg - api.nvim_set_hl(ns, "WrappedTitle", { fg = title_fg, bold = true }) - api.nvim_set_hl(ns, "WrappedKey", { fg = text_light, bg = lighten(bg, 10) }) local comment_fg = get_hl("Comment").fg - api.nvim_set_hl(ns, "WrappedLabel", { fg = lighten(comment_fg, 20) }) - api.nvim_set_hl(ns, "WrappedSeparator", { fg = mix(comment_fg, win_bg, 60) }) + + local hl = { ---@type table + Normal = { bg = win_bg, fg = text_light }, + FloatBorder = { fg = border_fg, bg = border_bg }, + WrappedTitle = { fg = title_fg, bold = true }, + WrappedKey = { fg = text_light, bg = lighten(bg, 10) }, + WrappedLabel = { fg = lighten(comment_fg, 20) }, + WrappedSeparator = { fg = mix(comment_fg, win_bg, 60) }, + } + for group, opts in pairs(hl) do + api.nvim_set_hl(ns, group, opts) + end -- per-color intensity levels (0=brightest, 3=dimmest) - local color_sources = { + local color_sources = { ---@type table Red = get_hl("ExRed").fg, Green = get_hl("ExGreen").fg, Blue = get_hl("ExBlue").fg, Yellow = get_hl("ExYellow").fg, } - local mix_levels = { 10, 40, 60, 80 } for name, fg in pairs(color_sources) do for i, pct in ipairs(mix_levels) do api.nvim_set_hl( ns, - "Wrapped" .. name .. (i - 1), + ("Wrapped%s%s"):format(name, i - 1), { fg = mix(fg, win_bg, pct) } ) end diff --git a/lua/wrapped/ui/ui.lua b/lua/wrapped/ui/ui.lua index 0f26cc1..56833e6 100644 --- a/lua/wrapped/ui/ui.lua +++ b/lua/wrapped/ui/ui.lua @@ -5,43 +5,67 @@ local highlights = require "wrapped.ui.highlights" local state = require "wrapped.state" local git = require "wrapped.core.git" +---@class Wrapped.UiState +---@field buf integer|nil +---@field win integer|nil +---@field ns integer +---@field commit_activity? table + +---@class Wrapped.SizeHistory +---@field values integer[] +---@field labels string[] + +---@class Wrapped.Ui local M = {} -local ui_state = - { buf = nil, win = nil, ns = api.nvim_create_namespace "WrappedUI" } +local ui_state = { ---@type Wrapped.UiState + buf = nil, + win = nil, + ns = api.nvim_create_namespace "WrappedUI", +} +---@return WrappedConfig config local function get_config() return require("wrapped").config end local function close() - if ui_state.win and api.nvim_win_is_valid(ui_state.win) then - api.nvim_win_close(ui_state.win, true) - end - if ui_state.buf and api.nvim_buf_is_valid(ui_state.buf) then - api.nvim_buf_delete(ui_state.buf, { force = true }) + if ui_state.win then pcall(api.nvim_win_close, ui_state.win, true) end + if ui_state.buf then + pcall(api.nvim_buf_delete, ui_state.buf, { force = true }) end ui_state.win, ui_state.buf = nil, nil end -local function dd(n) return n > 9 and tostring(n) or "0" .. n end +---@param n integer +---@return string dd +local function dd(n) return (n > 9 and tostring(n) or "0") .. n end +---@param str string +---@param max integer +---@return string str local function truncate(str, max) - if #str > max then return str:sub(1, max - 3) .. "..." end + if str:len() > max then return str:sub(1, max - 3) .. "..." end return str end -- wraps text into multiple lines of max width +---@param str string +---@param max integer +---@param hl? string +---@return string[][][] lines local function wrap_lines(str, max, hl) - local result = {} - while #str > max do + local result = {} ---@type string[][][] + while str:len() > max do local cut = str:sub(1, max) local space = cut:match ".*()%s" or max table.insert(result, { { str:sub(1, space), hl or "Normal" } }) str = str:sub(space + 1):gsub("^%s+", "") end - if #str > 0 then table.insert(result, { { str, hl or "Normal" } }) end + if str:len() > 0 then table.insert(result, { { str, hl or "Normal" } }) end return result end -- returns intensity index 0 (brightest) to 3 (dimmest) +---@param n integer +---@return "0"|"1"|"2"|"3" intensity local function get_intensity(n) if n > 5 then return "0" end if n > 2 then return "1" end @@ -55,8 +79,13 @@ local color_cycle = vim.list_extend( vim.list_extend(vim.list_extend({}, month_colors), month_colors) ) +---@param y integer +---@return boolean leap local function is_leap(y) return (y % 4 == 0 and y % 100 ~= 0) or (y % 400 == 0) end +---@param activity table +---@param width integer +---@return string[][][] heatmap local function build_heatmap(activity, width) local year = state.heatmap_year local months = { @@ -78,15 +107,17 @@ local function build_heatmap(activity, width) local day_names = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" } -- month header row with per-month color + ---@type string[][] local header = { { " ", "Comment" }, { " " } } for i = 1, 12 do table.insert(header, { " " .. months[i] .. " ", "Ex" .. color_cycle[i] }) if i < 12 then table.insert(header, { " " }) end end + ---@type string[][] local sep = voltui.separator("─", width - (state.xpad or 0) * 2 - 4, "Comment") - local lines = { header, sep } + local lines = { header, sep } ---@type string[][][] -- 7 weekday rows for d = 1, 7 do @@ -96,7 +127,8 @@ local function build_heatmap(activity, width) -- fill grid per month for m = 1, 12 do local start_dow = tonumber( - os.date("%w", os.time { year = year, month = m, day = 1 }) + os.date("%w", os.time { year = year, month = m, day = 1 }), + 10 ) + 1 if m == 1 then @@ -110,8 +142,7 @@ local function build_heatmap(activity, width) local dow = tonumber( os.date("%w", os.time { year = year, month = m, day = day }) ) + 1 - local key = dd(day) .. dd(m) .. year - local count = activity[key] or 0 + local count = activity[dd(day) .. dd(m) .. year] or 0 local hl = count > 0 and ("Wrapped" .. color .. get_intensity(count)) or "Linenr" table.insert(lines[dow + 2], { "󱓻 ", hl }) @@ -121,7 +152,7 @@ local function build_heatmap(activity, width) voltui.border(lines, "Comment") -- legend with green levels (matching typr) - local legend = { + local legend = { ---@type string[][] { " Commit Activity", "WrappedGreen0" }, { " " }, { "« ", "Comment" }, @@ -139,6 +170,9 @@ local function build_heatmap(activity, width) return lines end +---@param size_history Wrapped.SizeHistory +---@param target_width integer +---@return string[][][] size_chart local function build_size_chart(size_history, target_width) local vals = size_history.values if #vals == 0 then return {} end @@ -147,7 +181,7 @@ local function build_size_chart(size_history, target_width) if max_val == 0 then max_val = 1 end -- normalize to 1-100 scale for bar graph - local scaled = {} + local scaled = {} ---@type integer[] for _, v in ipairs(vals) do table.insert(scaled, math.floor((v / max_val) * 100)) end @@ -176,7 +210,7 @@ local function build_size_chart(size_history, target_width) baropts = { w = bar_w, gap = bar_gap, - format_hl = function(x) + format_hl = function(x) ---@param x integer if x > 85 then return "WrappedGreen0" end if x > 60 then return "WrappedGreen1" end if x > 30 then return "WrappedGreen2" end @@ -185,7 +219,7 @@ local function build_size_chart(size_history, target_width) }, } - local chart = voltui.graphs.bar(chart_data) + local chart = voltui.graphs.bar(chart_data) ---@type string[][][] return chart end @@ -202,10 +236,16 @@ local function build_stats_bars( local barlen = math.floor((width - 2) / 2) local table_w = math.floor((barlen - 2) / 2) + ---@param label string + ---@param val? integer + ---@param goal integer + ---@param icon string + ---@param hl string + ---@return string[][][] bar local function build_bar(label, val, goal, icon, hl) val = val or 0 local perc = math.min(math.floor((val / goal) * 100), 100) - return { + return { ---@type string[][][] { { icon .. " ", hl }, { label .. " ~ ", hl }, @@ -250,6 +290,9 @@ local function build_stats_bars( } end +---@param plugin_history Wrapped.PluginHistory +---@param file_stats Wrapped.FileStats +---@return string[][][] plugins_files local function build_plugins_files_table(plugin_history, file_stats) local width = get_config().size.width - (state.xpad or 0) * 2 local barlen = math.floor((width - 2) / 2) @@ -258,7 +301,7 @@ local function build_plugins_files_table(plugin_history, file_stats) local function build_plugin_table(title, name, date) local truncated_name = truncate(name or "None", table_w - 2) local info_date = date and os.date("%Y-%m-%d", date) or "None" - local data = { + local data = { ---@type string[][][][] { { { truncated_name, "Normal" } } }, { { { info_date, "Comment" } } }, } @@ -292,7 +335,7 @@ local function build_plugins_files_table(plugin_history, file_stats) local b_lines = tostring(file_stats.biggest.lines or 0) .. " lines" local s_lines = tostring(file_stats.smallest.lines or 0) .. " lines" - local file_tbl_data = { + local file_tbl_data = { ---@type string[][][][] { { { b_name, "Normal" }, { " - ", "Comment" }, { b_lines, "Comment" } } }, { { { s_name, "Normal" }, { " - ", "Comment" }, { s_lines, "Comment" } } }, } @@ -310,8 +353,11 @@ local function build_plugins_files_table(plugin_history, file_stats) } end +---@param file_stats Wrapped.FileStats +---@param width integer +---@return string[][][] top_files local function build_top_files_table(file_stats, width) - local data = { { " File Extension", "󰅪 Total Lines" } } + local data = { { " File Extension", "󰅪 Total Lines" } } ---@type string[][] for i, stat in ipairs(file_stats.lines_by_type) do if i > 5 then break end table.insert(data, { stat.name, tostring(stat.lines) }) @@ -324,6 +370,24 @@ local function build_top_files_table(file_stats, width) ) end +---@param lines string[][][] +---@param text string +---@param hl? string +---@return string[][][] lines +local function add(lines, text, hl) + table.insert(lines, { { text, hl or "Special" } }) + return lines +end + +---@param commits string[] +---@param total_count integer +---@param plugin_count integer +---@param first_commit_date string +---@param file_stats Wrapped.FileStats +---@param plugin_history Wrapped.PluginHistory +---@param config_stats Wrapped.ConfigStats +---@param commit_activity table +---@param size_history Wrapped.SizeHistory local function build_content( commits, total_count, @@ -336,9 +400,6 @@ local function build_content( size_history ) local lines = {} - local function add(text, hl) - table.insert(lines, { { text, hl or "Special" } }) - end vim.list_extend( lines, @@ -349,8 +410,7 @@ local function build_content( file_stats.total_lines ) ) - add(" ", "") - add(" ", "") + lines = add(add(lines, " ", ""), " ", "") vim.list_extend(lines, build_plugins_files_table(plugin_history, file_stats)) @@ -387,11 +447,11 @@ local function build_content( build_heatmap(ui_state.commit_activity, get_config().size.width) ) end - add(" ", "") + lines = add(lines, " ", "") local inner_w = get_config().size.width - (state.xpad or 0) * 2 - add(" Commit Messages", "WrappedRed0") - add(" ", "") + lines = add(lines, " Commit Messages", "WrappedRed0") + lines = add(lines, " ", "") table.insert(lines, { { "Shortest: ", "WrappedRed0" }, { config_stats.shortest_msg, "WrappedLabel" }, @@ -399,7 +459,7 @@ local function build_content( local long_prefix = "Longest: " -- wrap narrower to account for prefix and padding - local wrap_w = inner_w - #long_prefix - 1 + local wrap_w = inner_w - long_prefix:len() - 1 local long_lines = wrap_lines(config_stats.longest_msg, wrap_w, "WrappedLabel") @@ -409,11 +469,11 @@ local function build_content( table.insert(long_lines, { { long_prefix, "WrappedRed0" } }) end vim.list_extend(lines, long_lines) - add(" ", "") + lines = add(lines, " ", "") -- random commits - add(" Random Commits", "WrappedBlue0") - add(" ", "") + lines = add(lines, " Random Commits", "WrappedBlue0") + lines = add(lines, " ", "") for _, commit in ipairs(commits) do vim.list_extend(lines, wrap_lines(commit, inner_w)) end @@ -426,7 +486,7 @@ local function build_content( local left_w = math.floor((width - 2) / 2) local right_w = width - left_w - 2 - local left_col = {} + local left_col = {} ---@type string[][][] if size_history and #size_history.values > 0 then left_col = build_size_chart(size_history, left_w) -- Add padding to align with table (2 lines) @@ -461,14 +521,14 @@ local function refresh_heatmap() end ---@param commits string[] ----@param total_count number|string ----@param plugin_count number +---@param total_count integer|string +---@param plugin_count integer ---@param first_commit_date string ---@param file_stats Wrapped.FileStats ---@param plugin_history Wrapped.PluginHistory ---@param config_stats Wrapped.ConfigStats ----@param commit_activity table ----@param size_history { values: number[], labels: string[] } +---@param commit_activity table +---@param size_history Wrapped.SizeHistory function M.open( commits, total_count, @@ -515,23 +575,27 @@ function M.open( highlights.apply_float(ui_state.ns) api.nvim_win_set_hl_ns(ui_state.win, ui_state.ns) - local function get_content() - return build_content( - commits, - total_count, - plugin_count, - first_commit_date, - file_stats, - plugin_history, - config_stats, - commit_activity, - size_history - ) - end volt.gen_data { { buf = ui_state.buf, - layout = { { lines = get_content, name = "git_log" } }, + layout = { + { + lines = function() + return build_content( + commits, + total_count, + plugin_count, + first_commit_date, + file_stats, + plugin_history, + config_stats, + commit_activity, + size_history + ) + end, + name = "git_log", + }, + }, xpad = state.xpad, ns = ui_state.ns, }, @@ -559,7 +623,7 @@ function M.open( api.nvim_buf_set_keymap(buf, "n", "", "", map_opts) -- year cycling keymaps - local cur_year = tonumber(os.date "%Y") + local cur_year = tonumber(os.date "%Y", 10) local function cycle_year(delta) local new_year = state.heatmap_year + delta if new_year < state.first_commit_year or new_year > cur_year then return end @@ -568,15 +632,15 @@ function M.open( volt.redraw(buf, "git_log") end - api.nvim_buf_set_keymap(buf, "n", "<", "", { + vim.keymap.set("n", "<", function() cycle_year(-1) end, { noremap = true, + buffer = buf, silent = true, - callback = function() cycle_year(-1) end, }) - api.nvim_buf_set_keymap(buf, "n", ">", "", { + vim.keymap.set("n", ">", function() cycle_year(1) end, { noremap = true, + buffer = buf, silent = true, - callback = function() cycle_year(1) end, }) end diff --git a/plugin/wrapped.lua b/plugin/wrapped.lua index b8d5a92..89ed2aa 100644 --- a/plugin/wrapped.lua +++ b/plugin/wrapped.lua @@ -1,3 +1,9 @@ +if vim.g.wrapped_loaded == 1 then + return +end + +vim.g.wrapped_loaded = 1 + vim.api.nvim_create_user_command( "NvimWrapped", function() require("wrapped").run() end,