Added simple stopwatch as with <leader>S prefix
This commit is contained in:
3
init.lua
3
init.lua
@@ -14,6 +14,9 @@ require('colorscheme')
|
|||||||
require('lsp')
|
require('lsp')
|
||||||
require('keybindings')
|
require('keybindings')
|
||||||
require('layout')
|
require('layout')
|
||||||
|
|
||||||
|
require('stopwatch').setup({})
|
||||||
|
|
||||||
if vim.loop.os_gethostname() == 'ccflow' then
|
if vim.loop.os_gethostname() == 'ccflow' then
|
||||||
require('diary')
|
require('diary')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -80,6 +80,19 @@ local workspaces_fzf_picker = function(opts)
|
|||||||
end
|
end
|
||||||
vim.keymap.set('n', '<leader>w', workspaces_fzf_picker, { desc = "Workspaces" })
|
vim.keymap.set('n', '<leader>w', workspaces_fzf_picker, { desc = "Workspaces" })
|
||||||
|
|
||||||
|
------------------------------------------
|
||||||
|
--
|
||||||
|
-- Stopwatch
|
||||||
|
--
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<leader>Ss', ":StopwatchStart<CR>", { desc = "Stopwatch: Start" })
|
||||||
|
vim.keymap.set('n', '<leader>St', ":StopwatchToggle<CR>", { desc = "Stopwatch: Toggle" })
|
||||||
|
vim.keymap.set('n', '<leader>Sr', ":StopwatchReset<CR>", { desc = "Stopwatch: Reset" })
|
||||||
|
vim.keymap.set('n', '<leader>SS', ":StopwatchShow<CR>", { desc = "Stopwatch: Show" })
|
||||||
|
vim.keymap.set('n', '<leader>SH', ":StopwatchHide<CR>", { desc = "Stopwatch: Hide" })
|
||||||
|
|
||||||
|
|
||||||
------------------------------------------
|
------------------------------------------
|
||||||
--
|
--
|
||||||
-- Window layout save and restore
|
-- Window layout save and restore
|
||||||
|
|||||||
231
lua/stopwatch.lua
Normal file
231
lua/stopwatch.lua
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
-- ~/.config/nvim/lua/stopwatch.lua
|
||||||
|
-- Simple visible stopwatch for Neovim
|
||||||
|
local M = {}
|
||||||
|
local uv = vim.loop
|
||||||
|
|
||||||
|
local state = {
|
||||||
|
running = false,
|
||||||
|
start_hr = 0, -- hrtime at start (nanoseconds)
|
||||||
|
elapsed_ns = 0, -- accumulated nanoseconds when stopped
|
||||||
|
name = nil,
|
||||||
|
timer = nil,
|
||||||
|
bufnr = nil,
|
||||||
|
win = nil,
|
||||||
|
opts = nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
local function ns_to_time(ns)
|
||||||
|
local s = ns / 1e9
|
||||||
|
local h = math.floor(s / 3600)
|
||||||
|
s = s - h * 3600
|
||||||
|
local m = math.floor(s / 60)
|
||||||
|
local sec = s - m * 60
|
||||||
|
local secs = math.floor(sec)
|
||||||
|
local ms = math.floor((sec - secs) * 1000)
|
||||||
|
if h > 0 then
|
||||||
|
return string.format("%02d:%02d:%02d.%03d", h, m, secs, ms)
|
||||||
|
else
|
||||||
|
return string.format("%02d:%02d.%03d", m, secs, ms)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function get_elapsed_ns()
|
||||||
|
if state.running then
|
||||||
|
return state.elapsed_ns + (uv.hrtime() - state.start_hr)
|
||||||
|
else
|
||||||
|
return state.elapsed_ns
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_win()
|
||||||
|
if state.win and vim.api.nvim_win_is_valid(state.win) then return end
|
||||||
|
if not state.bufnr or not vim.api.nvim_buf_is_valid(state.bufnr) then
|
||||||
|
state.bufnr = vim.api.nvim_create_buf(false, true)
|
||||||
|
vim.api.nvim_buf_set_option(state.bufnr, "bufhidden", "wipe")
|
||||||
|
vim.api.nvim_buf_set_option(state.bufnr, "modifiable", false)
|
||||||
|
vim.api.nvim_buf_set_option(state.bufnr, "filetype", "stopwatch")
|
||||||
|
end
|
||||||
|
|
||||||
|
local width = state.opts.width or 24
|
||||||
|
local height = 1
|
||||||
|
local col = (vim.o.columns - width) - (state.opts.margin_right or 2)
|
||||||
|
local row = state.opts.row or 1
|
||||||
|
|
||||||
|
local win_opts = {
|
||||||
|
relative = "editor",
|
||||||
|
anchor = "NW",
|
||||||
|
row = row,
|
||||||
|
col = col,
|
||||||
|
width = width,
|
||||||
|
height = height,
|
||||||
|
style = "minimal",
|
||||||
|
focusable = false,
|
||||||
|
noautocmd = true,
|
||||||
|
}
|
||||||
|
state.win = vim.api.nvim_open_win(state.bufnr, false, win_opts)
|
||||||
|
vim.api.nvim_win_set_option(state.win, "winblend", state.opts.winblend or 5)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function close_win()
|
||||||
|
if state.win and vim.api.nvim_win_is_valid(state.win) then
|
||||||
|
pcall(vim.api.nvim_win_close, state.win, true)
|
||||||
|
end
|
||||||
|
state.win = nil
|
||||||
|
-- keep buf so it can be reused
|
||||||
|
end
|
||||||
|
|
||||||
|
local function update_win()
|
||||||
|
if not state.opts.visible then return end
|
||||||
|
ensure_win()
|
||||||
|
if not state.bufnr or not vim.api.nvim_buf_is_valid(state.bufnr) then return end
|
||||||
|
|
||||||
|
local label = state.name and ("[" .. state.name .. "] ") or ""
|
||||||
|
local status_sym = state.running and "⏱ " or "⏸ "
|
||||||
|
local txt = status_sym .. " " .. label .. ns_to_time(get_elapsed_ns())
|
||||||
|
vim.api.nvim_buf_set_option(state.bufnr, "modifiable", true)
|
||||||
|
vim.api.nvim_buf_set_lines(state.bufnr, 0, -1, false, {txt})
|
||||||
|
vim.api.nvim_buf_set_option(state.bufnr, "modifiable", false)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function start_timer_loop()
|
||||||
|
if state.timer then return end
|
||||||
|
state.timer = uv.new_timer()
|
||||||
|
state.timer:start(0, state.opts.update_ms or 250, vim.schedule_wrap(function()
|
||||||
|
if state.win and not vim.api.nvim_win_is_valid(state.win) then
|
||||||
|
state.win = nil
|
||||||
|
end
|
||||||
|
update_win()
|
||||||
|
end))
|
||||||
|
end
|
||||||
|
|
||||||
|
local function stop_timer_loop()
|
||||||
|
if state.timer then
|
||||||
|
pcall(function() state.timer:stop() end)
|
||||||
|
state.timer:close()
|
||||||
|
state.timer = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Public API
|
||||||
|
function M.start(name)
|
||||||
|
if state.running then return end
|
||||||
|
state.name = name or state.name
|
||||||
|
state.start_hr = uv.hrtime() - state.elapsed_ns
|
||||||
|
state.running = true
|
||||||
|
if state.opts.visible then
|
||||||
|
ensure_win()
|
||||||
|
end
|
||||||
|
start_timer_loop()
|
||||||
|
update_win()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.stop()
|
||||||
|
if not state.running then return end
|
||||||
|
state.elapsed_ns = get_elapsed_ns()
|
||||||
|
state.running = false
|
||||||
|
stop_timer_loop()
|
||||||
|
update_win()
|
||||||
|
if state.opts.log_on_stop then
|
||||||
|
local log_path = state.opts.log_path or (vim.fn.stdpath("data") .. "/stopwatch_log.md")
|
||||||
|
local time_str = os.date("%Y-%m-%d %H:%M:%S")
|
||||||
|
local name = state.name or "-"
|
||||||
|
local duration = ns_to_time(state.elapsed_ns)
|
||||||
|
local line = string.format("- %s | %s | %s\n", time_str, name, duration)
|
||||||
|
local f = io.open(log_path, "a")
|
||||||
|
if f then
|
||||||
|
f:write(line)
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.toggle(name)
|
||||||
|
if state.running then
|
||||||
|
M.stop()
|
||||||
|
else
|
||||||
|
M.start(name)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.reset()
|
||||||
|
state.running = false
|
||||||
|
state.elapsed_ns = 0
|
||||||
|
state.start_hr = 0
|
||||||
|
stop_timer_loop()
|
||||||
|
update_win()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.show()
|
||||||
|
state.opts.visible = true
|
||||||
|
ensure_win()
|
||||||
|
start_timer_loop()
|
||||||
|
update_win()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.hide()
|
||||||
|
state.opts.visible = false
|
||||||
|
close_win()
|
||||||
|
stop_timer_loop()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.log_now()
|
||||||
|
local log_path = state.opts.log_path or (vim.fn.stdpath("data") .. "/stopwatch_log.md")
|
||||||
|
local time_str = os.date("%Y-%m-%d %H:%M:%S")
|
||||||
|
local name = state.name or "-"
|
||||||
|
local duration = ns_to_time(get_elapsed_ns())
|
||||||
|
local line = string.format("- %s | %s | %s\n", time_str, name, duration)
|
||||||
|
local f = io.open(log_path, "a")
|
||||||
|
if f then
|
||||||
|
f:write(line)
|
||||||
|
f:close()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.status()
|
||||||
|
-- returns a short string for statusline
|
||||||
|
local elapsed = get_elapsed_ns()
|
||||||
|
local prefix = state.running and "⏱" or "⏸"
|
||||||
|
local name = state.name and (" " .. state.name) or ""
|
||||||
|
return prefix .. name .. " " .. ns_to_time(elapsed)
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.setup(opts)
|
||||||
|
opts = opts or {}
|
||||||
|
state.opts = vim.tbl_extend("keep", opts, {
|
||||||
|
visible = true,
|
||||||
|
update_ms = 250,
|
||||||
|
width = 28,
|
||||||
|
row = 1,
|
||||||
|
margin_right = 2,
|
||||||
|
winblend = 5,
|
||||||
|
log_on_stop = false,
|
||||||
|
log_path = nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Create user commands
|
||||||
|
vim.api.nvim_create_user_command("StopwatchStart", function(opts)
|
||||||
|
M.start(opts.args ~= "" and opts.args or nil)
|
||||||
|
end, { nargs = "?" })
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("StopwatchStop", function()
|
||||||
|
M.stop()
|
||||||
|
end, {})
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("StopwatchToggle", function(opts)
|
||||||
|
M.toggle(opts.args ~= "" and opts.args or nil)
|
||||||
|
end, { nargs = "?" })
|
||||||
|
|
||||||
|
vim.api.nvim_create_user_command("StopwatchReset", function() M.reset() end, {})
|
||||||
|
vim.api.nvim_create_user_command("StopwatchShow", function() M.show() end, {})
|
||||||
|
vim.api.nvim_create_user_command("StopwatchHide", function() M.hide() end, {})
|
||||||
|
vim.api.nvim_create_user_command("StopwatchLog", function() M.log_now() end, {})
|
||||||
|
|
||||||
|
-- optionally show initially
|
||||||
|
if state.opts.visible then
|
||||||
|
ensure_win()
|
||||||
|
update_win()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
|
|
||||||
Reference in New Issue
Block a user