feat: add agenda plugin

Import the package from my dotfiles.
This commit is contained in:
2023-11-17 02:04:30 +01:00
parent 4d1c1d1535
commit 16a71cadc2
5 changed files with 357 additions and 1 deletions

132
lua/agenda.lua Normal file
View File

@@ -0,0 +1,132 @@
local api = vim.api
local fn = vim.fn
---Wrapper for using my `agenda` script from within neovim.
local M = {}
function M.on_attach(buf)
vim.bo[buf].bufhidden = 'delete'
end
---@return string
function M.dir()
if vim.env.AGENDA_DIR then
return vim.env.AGENDA_DIR
end
if vim.env.XDG_DATA_HOME then
return string.format('%/agenda', vim.env.XDG_DATA_HOME)
end
return string.format('%/.local/share/agenda', vim.env.HOME)
end
---Gets the first window with an agenda note buffer in a tabpage.
---@param tabpage integer Tabpage handle, or 0 for the current tabpage.
---@param opts {dir: string?}?
---@return integer? win Window handle
function M.tabpage_get_win(tabpage, opts)
local agenda_dir = opts and opts.dir or M.dir()
for _, win in ipairs(api.nvim_tabpage_list_wins(tabpage)) do
local buf = api.nvim_win_get_buf(win)
-- Better to look at the name (file path), since that state is always there
-- (e.g. after restoring a session).
local name = api.nvim_buf_get_name(buf)
if vim.startswith(name, agenda_dir) then
return win
end
end
end
---@class agenda.Opts
---@field backlog string? Set the -b flag.
---@field create boolean? Set the -c flag.
---@field dir string? Set this as AGENDA_DIR value.
---@field extension string? Set this as -e option value.
---@field name string? Set this as -t option value.
---Open an agenda note.
---@param date string Either the date or backlog name.
---@param opts agenda.Opts?
function M.open(date, opts)
local filepath, err = M.agenda(date, opts)
if not filepath or fn.filereadable(filepath) < 1 then
if not err then
err = string.format("agenda: file '%s' is not readable", filepath)
end
vim.notify(err, vim.log.levels.WARN)
return
end
local win = M.tabpage_get_win(0)
if win then
vim.api.nvim_set_current_win(win)
vim.cmd.edit(filepath)
else
vim.cmd.split(filepath)
end
M.on_attach(0)
end
---Query an agenda note's file path.
---@param date string Either the date or backlog name.
---@param opts agenda.Opts?
---@return string? filepath
---@return string? err
function M.agenda(date, opts)
local env = {
-- An empty environment table is invalid.
AGENDA_DIR = vim.env.AGENDA_DIR
}
local cmd = { 'agenda', '-E' }
if opts then
if opts.dir then
env.AGENDA_DIR = opts.dir
end
if opts.backlog then
cmd[#cmd + 1] = '-b'
end
if opts.create then
cmd[#cmd + 1] = '-c'
end
if opts.extension then
cmd[#cmd + 1] = '-e'
cmd[#cmd + 1] = opts.extension
end
if opts.name then
cmd[#cmd + 1] = '-t'
cmd[#cmd + 1] = opts.name
end
end
cmd[#cmd + 1] = date
local filepath = ''
local err = ''
local pid = fn.jobstart(cmd, {
env = env,
on_stdout = function(_, data)
filepath = filepath .. data[1]
end,
on_stderr = function(_, data)
err = err .. data[1]
end,
})
local code = unpack(fn.jobwait { pid })
if code > 0 then
return nil, err
end
return filepath
end
return M

78
lua/agenda/util.lua Normal file
View File

@@ -0,0 +1,78 @@
local M = {}
---Parses a list of arguments **kind of** like the getopts shell builtin.
---@param args string[]
---@param optstring string
---@return function
function M.getopts(args, optstring)
local spec = {}
local last
for i = 1, #optstring do
local c = optstring:sub(i, i)
if c == ':' and last then
spec[last] = true
elseif i == #optstring then
spec[c] = false
elseif last and last ~= ':' then
spec[last] = false
end
last = c
end
-- 1. if the option takes a value either
-- a. take the rest of the slice as its values if at least one char
-- remains in the slice
-- b. take the next whole argument as its value
-- 2. find the next option name (char)
local i = 1 -- arg index
local n = 1 -- slice index
local g = 1 -- I am not very smart
return function ()
while args[i] do
local arg = args[i]
-- TODO: Maybe write some tests instead :^)
if g > 2097152 then
error 'logic error'
end
g = g + 1
if n == 1 and not vim.startswith(arg, '-') then
-- "Normal" arguments end the iterator.
return
end
n = n + 1
local c = arg:sub(n, n)
if #c > n then
n = n + 1
end
if #c == 1 then
if spec[c] then
local optval = arg:sub(n + 1, #arg)
n = 1
i = i + 1
if #optval > 0 then
return i, c, optval
end
-- Remaining argument does not contain optval, consume the next argument
-- as optval.
i = i + 1
return i, c, args[i - 1]
end
-- Do not handle nil. If the opt is invalid the caller must handle it.
return i + 1, c, nil
end
-- Stray '-' ends the iterator.
return
end
end
end
return M