Skip to content

Commit a0b1f01

Browse files
committed
feat: optimise, lazy load
1 parent 9d2c505 commit a0b1f01

27 files changed

+1513
-2813
lines changed

lua/codeme/backend.lua

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
local M = {}
2+
3+
-- Cache backend binary path
4+
local _binary_path = nil
5+
6+
---Find codeme binary
7+
---@return string|nil
8+
local function find_binary()
9+
if _binary_path then
10+
return _binary_path
11+
end
12+
13+
-- Check environment variable
14+
local env_path = os.getenv("CODEME_BIN")
15+
if env_path and vim.fn.executable(env_path) == 1 then
16+
_binary_path = env_path
17+
return _binary_path
18+
end
19+
20+
-- Check system PATH
21+
if vim.fn.executable("codeme") == 1 then
22+
_binary_path = "codeme"
23+
return _binary_path
24+
end
25+
26+
-- Check local installation
27+
local local_path = vim.fn.stdpath("data") .. "/codeme/codeme"
28+
if vim.fn.executable(local_path) == 1 then
29+
_binary_path = local_path
30+
return _binary_path
31+
end
32+
33+
return nil
34+
end
35+
36+
---Execute codeme command asynchronously
37+
---@param args string[] Command arguments
38+
---@param callback fun(success: boolean, data: any, error: string?)
39+
local function exec_async(args, callback)
40+
local binary = find_binary()
41+
if not binary then
42+
callback(false, nil, "codeme binary not found")
43+
return
44+
end
45+
46+
vim.system({ binary, unpack(args) }, { text = true }, function(obj)
47+
vim.schedule(function()
48+
if obj.code ~= 0 then
49+
callback(false, nil, obj.stderr or "Command failed")
50+
return
51+
end
52+
53+
-- Parse JSON output
54+
local stdout = obj.stdout or ""
55+
local json_str = stdout:match("({.*})")
56+
if not json_str then
57+
callback(false, nil, "No JSON in output")
58+
return
59+
end
60+
61+
local ok, data = pcall(vim.json.decode, json_str)
62+
if not ok then
63+
callback(false, nil, "JSON parse error: " .. tostring(data))
64+
return
65+
end
66+
67+
callback(true, data, nil)
68+
end)
69+
end)
70+
end
71+
72+
---Get stats from backend
73+
---@param today_only boolean? Only fetch today's stats
74+
---@param callback fun(stats: table)
75+
function M.get_stats(today_only, callback)
76+
local args = { "api", "--compact" }
77+
if today_only then
78+
table.insert(args, "--today")
79+
end
80+
81+
exec_async(args, function(success, data, err)
82+
if not success then
83+
vim.notify("CodeMe: " .. (err or "Unknown error"), vim.log.levels.ERROR)
84+
callback({})
85+
return
86+
end
87+
callback(data or {})
88+
end)
89+
end
90+
91+
---Send heartbeat to backend
92+
---@param opts table Heartbeat options
93+
---@param callback fun(success: boolean)?
94+
function M.send_heartbeat(opts, callback)
95+
local binary = find_binary()
96+
if not binary then
97+
if callback then
98+
callback(false)
99+
end
100+
return
101+
end
102+
103+
local args = {
104+
"track",
105+
"--file",
106+
opts.filepath,
107+
"--lang",
108+
opts.language or "",
109+
"--editor",
110+
"neovim",
111+
"--lines",
112+
tostring(opts.lines or 0),
113+
}
114+
115+
vim.system({ binary, unpack(args) }, { detach = true }, function(obj)
116+
if callback then
117+
vim.schedule(function()
118+
callback(obj.code == 0)
119+
end)
120+
end
121+
end)
122+
end
123+
124+
---Check if binary is installed
125+
---@return boolean
126+
function M.is_installed()
127+
return find_binary() ~= nil
128+
end
129+
130+
return M

lua/codeme/dashboard.lua

Lines changed: 0 additions & 33 deletions
This file was deleted.

lua/codeme/domain.lua

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
local M = {}
2+
3+
---Format seconds to human-readable duration
4+
---@param seconds number
5+
---@return string
6+
function M.format_duration(seconds)
7+
if not seconds or seconds == 0 then
8+
return "0s"
9+
end
10+
11+
if seconds < 60 then
12+
return string.format("%ds", seconds)
13+
elseif seconds < 3600 then
14+
return string.format("%dm", math.floor(seconds / 60))
15+
else
16+
local hours = math.floor(seconds / 3600)
17+
local mins = math.floor((seconds % 3600) / 60)
18+
if mins > 0 then
19+
return string.format("%dh %dm", hours, mins)
20+
end
21+
return string.format("%dh", hours)
22+
end
23+
end
24+
25+
---Format number with commas
26+
---@param n number
27+
---@return string
28+
function M.format_number(n)
29+
if not n then
30+
return "0"
31+
end
32+
local s = tostring(math.floor(n))
33+
return s:reverse():gsub("(%d%d%d)", "%1,"):reverse():gsub("^,", "")
34+
end
35+
36+
---Calculate percentage
37+
---@param value number
38+
---@param total number
39+
---@return number
40+
function M.calculate_percentage(value, total)
41+
if not total or total == 0 then
42+
return 0
43+
end
44+
return math.floor((value / total) * 100)
45+
end
46+
47+
---Get progress bar characters
48+
---@param percentage number 0-100
49+
---@param width number Bar width in characters
50+
---@return string filled, string empty
51+
function M.get_progress_bar(percentage, width)
52+
percentage = math.max(0, math.min(100, percentage))
53+
local filled = math.floor(percentage / 100 * width)
54+
return string.rep("", filled), string.rep("", width - filled)
55+
end
56+
57+
---Parse ISO date to timestamp
58+
---@param iso_string string
59+
---@return number|nil
60+
function M.parse_iso_date(iso_string)
61+
if not iso_string or iso_string == "" then
62+
return nil
63+
end
64+
local pattern = "(%d+)-(%d+)-(%d+)T(%d+):(%d+):(%d+)"
65+
local year, month, day, hour, min, sec = iso_string:match(pattern)
66+
if year then
67+
return os.time({
68+
year = tonumber(year),
69+
month = tonumber(month),
70+
day = tonumber(day),
71+
hour = tonumber(hour),
72+
min = tonumber(min),
73+
sec = tonumber(sec),
74+
})
75+
end
76+
return nil
77+
end
78+
79+
---Format date string
80+
---@param date_str string YYYY-MM-DD
81+
---@return string
82+
function M.format_date(date_str)
83+
if not date_str then
84+
return "Unknown"
85+
end
86+
local year, month, day = date_str:match("(%d+)-(%d+)-(%d+)")
87+
if not year then
88+
return "Unknown"
89+
end
90+
91+
local timestamp = os.time({ year = year, month = month, day = day, hour = 12 })
92+
local today = os.date("%Y-%m-%d")
93+
94+
if date_str == today then
95+
return os.date("%A, %b %d", timestamp)
96+
end
97+
return os.date("%a, %b %d", timestamp)
98+
end
99+
100+
---Calculate streak display
101+
---@param streak_days number
102+
---@return string icon, string color
103+
function M.get_streak_display(streak_days)
104+
if streak_days == 0 then
105+
return "", "commentfg"
106+
elseif streak_days < 3 then
107+
return string.rep("🔥", streak_days), "exred"
108+
elseif streak_days < 7 then
109+
return "🔥🔥🔥 +" .. (streak_days - 3), "exred"
110+
elseif streak_days < 30 then
111+
return "🔥🔥🔥 ⚡ +" .. (streak_days - 3), "exred"
112+
else
113+
return "🔥🔥🔥 ⚡⚡ +" .. (streak_days - 3), "exred"
114+
end
115+
end
116+
117+
---Get trend indicator
118+
---@param current number
119+
---@param previous number
120+
---@return string text, string color
121+
function M.get_trend(current, previous)
122+
if not current or not previous or previous == 0 then
123+
return "", "commentfg"
124+
end
125+
126+
local diff = current - previous
127+
local pct = math.floor(math.abs(diff) / previous * 100)
128+
129+
if diff > 0 then
130+
return "" .. pct .. "%", "exgreen"
131+
elseif diff < 0 then
132+
return "" .. pct .. "%", "exred"
133+
end
134+
return "", "commentfg"
135+
end
136+
137+
---Safe array length (handles vim.NIL)
138+
---@param arr any
139+
---@return number
140+
function M.safe_length(arr)
141+
if not arr or arr == vim.NIL then
142+
return 0
143+
end
144+
if type(arr) == "table" then
145+
return #arr
146+
end
147+
return 0
148+
end
149+
150+
---Get top N items from list
151+
---@param list table
152+
---@param max number
153+
---@return string
154+
function M.top_items(list, max)
155+
if not list or #list == 0 then
156+
return ""
157+
end
158+
159+
max = max or #list
160+
local result = {}
161+
162+
for i = 1, math.min(max, #list) do
163+
result[#result + 1] = tostring(list[i])
164+
end
165+
166+
if #list > max then
167+
result[#result + 1] = string.format("+%d more", #list - max)
168+
end
169+
170+
return table.concat(result, ", ")
171+
end
172+
173+
return M

0 commit comments

Comments
 (0)