feat: time-aware due dates, persistent undo, @return audit (#33)
* fix(plugin): allow command chaining with bar separator
Problem: :Pending|only failed because the command definition lacked the
bar attribute, causing | to be consumed as an argument.
Solution: Add bar = true to nvim_create_user_command so | is treated as
a command separator, matching fugitive's :Git behavior.
* refactor(buffer): remove opinionated window options
Problem: The plugin hardcoded number, relativenumber, wrap, spell,
signcolumn, foldcolumn, and cursorline in set_win_options, overriding
user preferences with no way to opt out.
Solution: Remove all cosmetic window options. Users who want them can
set them in after/ftplugin/pending.lua. Only conceallevel,
concealcursor, and winfixheight remain as functionally required.
* feat: time-aware due dates, persistent undo, @return audit
Problem: Due dates had no time component, the undo stack was lost on
restart and stored in a separate file, and many public functions lacked
required @return annotations.
Solution: Add YYYY-MM-DDThh:mm support across parse, views, recur,
complete, and init with time-aware overdue checks. Merge the undo stack
into the task store JSON so a single file holds all state. Add @return
nil annotations to all 27 void public functions across every module.
* feat(parse): flexible time parsing for @ suffix
Problem: the @HH:MM time suffix required zero-padded 24-hour format,
forcing users to write due:tomorrow@14:00 instead of due:tomorrow@2pm.
Solution: add normalize_time() that accepts bare hours (9, 14),
H:MM (9:30), am/pm (2pm, 9:30am, 12am), and existing HH:MM format,
normalizing all to canonical HH:MM on save.
* feat(complete): add info descriptions to omnifunc items
Problem: completion menu items had no description, making it hard to
distinguish between similar entries like date shorthands and recurrence
patterns.
Solution: return { word, info } tables from date_completions() and
recur_completions(), surfacing human-readable descriptions in the
completion popup.
* ci: format
This commit is contained in:
parent
72dbf037c7
commit
c57cc0845b
12 changed files with 580 additions and 158 deletions
|
|
@ -29,48 +29,75 @@ local function get_categories()
|
|||
return result
|
||||
end
|
||||
|
||||
---@return string[]
|
||||
---@return { word: string, info: string }[]
|
||||
local function date_completions()
|
||||
return {
|
||||
'today',
|
||||
'tomorrow',
|
||||
'yesterday',
|
||||
'+1d',
|
||||
'+2d',
|
||||
'+3d',
|
||||
'+1w',
|
||||
'+2w',
|
||||
'+1m',
|
||||
'mon',
|
||||
'tue',
|
||||
'wed',
|
||||
'thu',
|
||||
'fri',
|
||||
'sat',
|
||||
'sun',
|
||||
'eod',
|
||||
'eow',
|
||||
'eom',
|
||||
'eoq',
|
||||
'eoy',
|
||||
'sow',
|
||||
'som',
|
||||
'soq',
|
||||
'soy',
|
||||
'later',
|
||||
{ word = 'today', info = "Today's date" },
|
||||
{ word = 'tomorrow', info = "Tomorrow's date" },
|
||||
{ word = 'yesterday', info = "Yesterday's date" },
|
||||
{ word = '+1d', info = '1 day from today' },
|
||||
{ word = '+2d', info = '2 days from today' },
|
||||
{ word = '+3d', info = '3 days from today' },
|
||||
{ word = '+1w', info = '1 week from today' },
|
||||
{ word = '+2w', info = '2 weeks from today' },
|
||||
{ word = '+1m', info = '1 month from today' },
|
||||
{ word = 'mon', info = 'Next Monday' },
|
||||
{ word = 'tue', info = 'Next Tuesday' },
|
||||
{ word = 'wed', info = 'Next Wednesday' },
|
||||
{ word = 'thu', info = 'Next Thursday' },
|
||||
{ word = 'fri', info = 'Next Friday' },
|
||||
{ word = 'sat', info = 'Next Saturday' },
|
||||
{ word = 'sun', info = 'Next Sunday' },
|
||||
{ word = 'eod', info = 'End of day (today)' },
|
||||
{ word = 'eow', info = 'End of week (Sunday)' },
|
||||
{ word = 'eom', info = 'End of month' },
|
||||
{ word = 'eoq', info = 'End of quarter' },
|
||||
{ word = 'eoy', info = 'End of year (Dec 31)' },
|
||||
{ word = 'sow', info = 'Start of week (Monday)' },
|
||||
{ word = 'som', info = 'Start of month' },
|
||||
{ word = 'soq', info = 'Start of quarter' },
|
||||
{ word = 'soy', info = 'Start of year (Jan 1)' },
|
||||
{ word = 'later', info = 'Someday (sentinel date)' },
|
||||
{ word = 'today@08:00', info = 'Today at 08:00' },
|
||||
{ word = 'today@09:00', info = 'Today at 09:00' },
|
||||
{ word = 'today@10:00', info = 'Today at 10:00' },
|
||||
{ word = 'today@12:00', info = 'Today at 12:00' },
|
||||
{ word = 'today@14:00', info = 'Today at 14:00' },
|
||||
{ word = 'today@17:00', info = 'Today at 17:00' },
|
||||
}
|
||||
end
|
||||
|
||||
---@return string[]
|
||||
---@type table<string, string>
|
||||
local recur_descriptions = {
|
||||
daily = 'Every day',
|
||||
weekdays = 'Monday through Friday',
|
||||
weekly = 'Every week',
|
||||
biweekly = 'Every 2 weeks',
|
||||
monthly = 'Every month',
|
||||
quarterly = 'Every 3 months',
|
||||
yearly = 'Every year',
|
||||
['2d'] = 'Every 2 days',
|
||||
['3d'] = 'Every 3 days',
|
||||
['2w'] = 'Every 2 weeks',
|
||||
['3w'] = 'Every 3 weeks',
|
||||
['2m'] = 'Every 2 months',
|
||||
['3m'] = 'Every 3 months',
|
||||
['6m'] = 'Every 6 months',
|
||||
['2y'] = 'Every 2 years',
|
||||
}
|
||||
|
||||
---@return { word: string, info: string }[]
|
||||
local function recur_completions()
|
||||
local recur = require('pending.recur')
|
||||
local list = recur.shorthand_list()
|
||||
local result = {}
|
||||
for _, s in ipairs(list) do
|
||||
table.insert(result, s)
|
||||
local desc = recur_descriptions[s] or s
|
||||
table.insert(result, { word = s, info = desc })
|
||||
end
|
||||
for _, s in ipairs(list) do
|
||||
table.insert(result, '!' .. s)
|
||||
local desc = recur_descriptions[s] or s
|
||||
table.insert(result, { word = '!' .. s, info = desc .. ' (from completion date)' })
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
|
@ -111,24 +138,29 @@ function M.omnifunc(findstart, base)
|
|||
return -1
|
||||
end
|
||||
|
||||
local candidates = {}
|
||||
local matches = {}
|
||||
local source = _complete_source or ''
|
||||
|
||||
local dk = date_key()
|
||||
local rk = recur_key()
|
||||
|
||||
if source == dk then
|
||||
candidates = date_completions()
|
||||
for _, c in ipairs(date_completions()) do
|
||||
if base == '' or c.word:sub(1, #base) == base then
|
||||
table.insert(matches, { word = c.word, menu = '[' .. source .. ']', info = c.info })
|
||||
end
|
||||
end
|
||||
elseif source == 'cat' then
|
||||
candidates = get_categories()
|
||||
for _, c in ipairs(get_categories()) do
|
||||
if base == '' or c:sub(1, #base) == base then
|
||||
table.insert(matches, { word = c, menu = '[cat]' })
|
||||
end
|
||||
end
|
||||
elseif source == rk then
|
||||
candidates = recur_completions()
|
||||
end
|
||||
|
||||
local matches = {}
|
||||
for _, c in ipairs(candidates) do
|
||||
if base == '' or c:sub(1, #base) == base then
|
||||
table.insert(matches, { word = c, menu = '[' .. source .. ']' })
|
||||
for _, c in ipairs(recur_completions()) do
|
||||
if base == '' or c.word:sub(1, #base) == base then
|
||||
table.insert(matches, { word = c.word, menu = '[' .. source .. ']', info = c.info })
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue