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('keybindings')
|
||||
require('layout')
|
||||
|
||||
require('stopwatch').setup({})
|
||||
|
||||
if vim.loop.os_gethostname() == 'ccflow' then
|
||||
require('diary')
|
||||
end
|
||||
|
||||
@@ -80,6 +80,19 @@ local workspaces_fzf_picker = function(opts)
|
||||
end
|
||||
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
|
||||
|
||||
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