Compare commits
No commits in common. "be81abfe48e59d9d6ff6b7acda6afbd351c4cbc7" and "b08d01fe787adac9e9af7a865c7191dcc0661028" have entirely different histories.
be81abfe48
...
b08d01fe78
14 changed files with 183 additions and 675 deletions
178
doc/pending.txt
178
doc/pending.txt
|
|
@ -30,16 +30,13 @@ concealed tokens and are never visible during editing.
|
|||
|
||||
Features: ~
|
||||
- Oil-style buffer editing: standard Vim motions for all task operations
|
||||
- Inline metadata syntax: `due:`, `cat:`, and `rec:` tokens parsed on `:w`
|
||||
- Relative date input: `today`, `tomorrow`, `+Nd`, `+Nw`, `+Nm`, weekday
|
||||
names, month names, ordinals, and more
|
||||
- Recurring tasks with automatic next-date spawning on completion
|
||||
- Inline metadata syntax: `due:` and `cat:` tokens parsed on `:w`
|
||||
- Relative date input: `today`, `tomorrow`, `+Nd`, weekday names
|
||||
- Two views: category (default) and priority flat list
|
||||
- Multi-level undo (up to 20 `:w` saves, session-only)
|
||||
- Quick-add from the command line with `:Pending add`
|
||||
- Quickfix list of overdue/due-today tasks via `:Pending due`
|
||||
- Foldable category sections (`zc`/`zo`) in category view
|
||||
- Omnifunc completion for `cat:`, `due:`, and `rec:` tokens (`<C-x><C-o>`)
|
||||
- Google Calendar one-way push via OAuth PKCE
|
||||
|
||||
==============================================================================
|
||||
|
|
@ -98,18 +95,20 @@ parsed from the right and consumed until a non-metadata token is reached.
|
|||
Supported tokens: ~
|
||||
|
||||
`due:YYYY-MM-DD` Set a due date using an absolute date.
|
||||
`due:<name>` Resolve a named date (see |pending-dates| below).
|
||||
`due:today` Resolve to today's date.
|
||||
`due:tomorrow` Resolve to tomorrow's date.
|
||||
`due:+Nd` Resolve to N days from today (e.g. `due:+3d`).
|
||||
`due:mon` Resolve to the next occurrence of that weekday.
|
||||
Supported: `sun` `mon` `tue` `wed` `thu` `fri` `sat`
|
||||
`cat:Name` Move the task to the named category on save.
|
||||
`rec:<pattern>` Set a recurrence rule (see |pending-recurrence|).
|
||||
|
||||
The token name for due dates defaults to `due` and is configurable via
|
||||
`date_syntax` in |pending-config|. The token name for recurrence defaults to
|
||||
`rec` and is configurable via `recur_syntax`.
|
||||
`date_syntax` in |pending-config|. If `date_syntax` is set to `by`, write
|
||||
`by:2026-03-15` instead.
|
||||
|
||||
Example: >
|
||||
|
||||
Buy milk due:2026-03-15 cat:Errands
|
||||
Take out trash due:monday rec:weekly
|
||||
<
|
||||
|
||||
On `:w`, the description becomes `Buy milk`, the due date is stored as
|
||||
|
|
@ -117,87 +116,8 @@ On `:w`, the description becomes `Buy milk`, the due date is stored as
|
|||
placed under the `Errands` category header.
|
||||
|
||||
Parsing stops at the first token that is not a recognised metadata token.
|
||||
Repeated tokens of the same type also stop parsing — only one `due:`, one
|
||||
`cat:`, and one `rec:` per task line are consumed.
|
||||
|
||||
Omnifunc completion is available for all three token types. In insert mode,
|
||||
type `due:`, `cat:`, or `rec:` and press `<C-x><C-o>` to see suggestions.
|
||||
|
||||
==============================================================================
|
||||
DATE INPUT *pending-dates*
|
||||
|
||||
Named dates can be used anywhere a date is accepted: the `due:` inline
|
||||
token, the `D` prompt, and `:Pending add`.
|
||||
|
||||
Token Resolves to ~
|
||||
----- -----------
|
||||
`today` Today's date
|
||||
`tomorrow` Tomorrow's date
|
||||
`yesterday` Yesterday's date
|
||||
`eod` Today (end of day semantics)
|
||||
`+Nd` N days from today (e.g. `+3d`)
|
||||
`+Nw` N weeks from today (e.g. `+2w`)
|
||||
`+Nm` N months from today (e.g. `+1m`)
|
||||
`-Nd` N days ago (e.g. `-2d`)
|
||||
`-Nw` N weeks ago (e.g. `-1w`)
|
||||
`mon`–`sun` Next occurrence of that weekday
|
||||
`jan`–`dec` 1st of next occurrence of that month
|
||||
`1st`–`31st` Next occurrence of that day-of-month
|
||||
`sow` / `eow` Monday / Sunday of current week
|
||||
`som` / `eom` First / last day of current month
|
||||
`soq` / `eoq` First / last day of current quarter
|
||||
`soy` / `eoy` January 1 / December 31 of current year
|
||||
`later` / `someday` Sentinel date (default: `9999-12-30`)
|
||||
|
||||
==============================================================================
|
||||
RECURRENCE *pending-recurrence*
|
||||
|
||||
Tasks can recur on a schedule. Add a `rec:` token to set recurrence: >
|
||||
|
||||
- [ ] Take out trash due:monday rec:weekly
|
||||
- [ ] Pay rent due:2026-03-01 rec:monthly
|
||||
- [ ] Standup due:tomorrow rec:weekdays
|
||||
<
|
||||
|
||||
When a recurring task is marked done with `<CR>`:
|
||||
1. The current task stays as done (preserving history).
|
||||
2. A new pending task is created with the same description, category,
|
||||
priority, and recurrence — with the due date advanced to the next
|
||||
occurrence.
|
||||
|
||||
Shorthand patterns: ~
|
||||
|
||||
Pattern Meaning ~
|
||||
------- -------
|
||||
`daily` Every day
|
||||
`weekdays` Monday through Friday
|
||||
`weekly` Every week
|
||||
`biweekly` Every 2 weeks (alias: `2w`)
|
||||
`monthly` Every month
|
||||
`quarterly` Every 3 months (alias: `3m`)
|
||||
`yearly` Every year (alias: `annual`)
|
||||
`Nd` Every N days (e.g. `3d`)
|
||||
`Nw` Every N weeks (e.g. `2w`)
|
||||
`Nm` Every N months (e.g. `6m`)
|
||||
`Ny` Every N years (e.g. `2y`)
|
||||
|
||||
For patterns the shorthand cannot express, use a raw RRULE fragment: >
|
||||
rec:FREQ=MONTHLY;BYDAY=1MO
|
||||
<
|
||||
|
||||
Completion-based recurrence: ~ *pending-recur-completion*
|
||||
By default, recurrence is schedule-based: the next due date advances from the
|
||||
original schedule, skipping to the next future occurrence. Prefix the pattern
|
||||
with `!` for completion-based mode, where the next due date advances from the
|
||||
completion date: >
|
||||
rec:!weekly
|
||||
<
|
||||
Schedule-based is like org-mode `++`; completion-based is like `.+`.
|
||||
|
||||
Google Calendar: ~
|
||||
Recurrence patterns map directly to iCalendar RRULE strings for future GCal
|
||||
sync support. Completion-based recurrence cannot be synced (it is inherently
|
||||
local).
|
||||
Repeated tokens of the same type also stop parsing — only one `due:` and one
|
||||
`cat:` per task line are consumed.
|
||||
|
||||
==============================================================================
|
||||
COMMANDS *pending-commands*
|
||||
|
|
@ -215,7 +135,6 @@ COMMANDS *pending-commands*
|
|||
:Pending add Buy groceries due:2026-03-15
|
||||
:Pending add School: Submit homework
|
||||
:Pending add Errands: Pick up dry cleaning due:fri
|
||||
:Pending add Work: standup due:tomorrow rec:weekdays
|
||||
<
|
||||
If the buffer is currently open it is re-rendered after the add.
|
||||
|
||||
|
|
@ -250,34 +169,27 @@ MAPPINGS *pending-mappings*
|
|||
The following keys are set buffer-locally when the task buffer opens. They
|
||||
are active only in the `pending://` buffer.
|
||||
|
||||
Buffer-local keys are configured via the `keymaps` table in |pending-config|.
|
||||
The defaults are shown below. Set any key to `false` to disable it.
|
||||
|
||||
Default buffer-local keys: ~
|
||||
Buffer-local keys: ~
|
||||
|
||||
Key Action ~
|
||||
------- ------------------------------------------------
|
||||
`q` Close the task buffer (`close`)
|
||||
`<CR>` Toggle complete / uncomplete (`toggle`)
|
||||
`!` Toggle the priority flag (`priority`)
|
||||
`D` Prompt for a due date (`date`)
|
||||
`<Tab>` Switch between category / priority view (`view`)
|
||||
`U` Undo the last `:w` save (`undo`)
|
||||
`o` Insert a new task line below (`open_line`)
|
||||
`O` Insert a new task line above (`open_line_above`)
|
||||
`<CR>` Toggle complete / uncomplete the task at cursor
|
||||
`!` Toggle the priority flag on the task at cursor
|
||||
`D` Prompt for a due date on the task at cursor
|
||||
`<Tab>` Switch between category view and priority view
|
||||
`U` Undo the last `:w` save
|
||||
`g?` Show a help popup with available keys
|
||||
`zc` Fold the current category section (category view only)
|
||||
`zo` Unfold the current category section (category view only)
|
||||
|
||||
`dd`, `p`, `P`, and `:w` work as standard Vim operations.
|
||||
`o` and `O` are overridden to insert a correctly-formatted blank task line
|
||||
at the position below or above the cursor rather than using standard Vim
|
||||
indentation. `dd`, `p`, `P`, and `:w` work as expected.
|
||||
|
||||
*<Plug>(pending-open)*
|
||||
<Plug>(pending-open)
|
||||
Open the task buffer. Maps to |:Pending| with no arguments.
|
||||
|
||||
*<Plug>(pending-close)*
|
||||
<Plug>(pending-close)
|
||||
Close the task buffer window.
|
||||
|
||||
*<Plug>(pending-toggle)*
|
||||
<Plug>(pending-toggle)
|
||||
Toggle complete / uncomplete for the task under the cursor.
|
||||
|
|
@ -294,18 +206,6 @@ Default buffer-local keys: ~
|
|||
<Plug>(pending-view)
|
||||
Switch between category view and priority view.
|
||||
|
||||
*<Plug>(pending-undo)*
|
||||
<Plug>(pending-undo)
|
||||
Undo the last `:w` save.
|
||||
|
||||
*<Plug>(pending-open-line)*
|
||||
<Plug>(pending-open-line)
|
||||
Insert a correctly-formatted blank task line below the cursor.
|
||||
|
||||
*<Plug>(pending-open-line-above)*
|
||||
<Plug>(pending-open-line-above)
|
||||
Insert a correctly-formatted blank task line above the cursor.
|
||||
|
||||
Example configuration: >lua
|
||||
vim.keymap.set('n', '<leader>t', '<Plug>(pending-open)')
|
||||
vim.keymap.set('n', '<leader>T', '<Plug>(pending-toggle)')
|
||||
|
|
@ -342,19 +242,7 @@ loads: >lua
|
|||
default_category = 'Inbox',
|
||||
date_format = '%b %d',
|
||||
date_syntax = 'due',
|
||||
recur_syntax = 'rec',
|
||||
someday_date = '9999-12-30',
|
||||
category_order = {},
|
||||
keymaps = {
|
||||
close = 'q',
|
||||
toggle = '<CR>',
|
||||
view = '<Tab>',
|
||||
priority = '!',
|
||||
date = 'D',
|
||||
undo = 'U',
|
||||
open_line = 'o',
|
||||
open_line_above = 'O',
|
||||
},
|
||||
gcal = {
|
||||
calendar = 'Tasks',
|
||||
credentials_path = '/path/to/client_secret.json',
|
||||
|
|
@ -390,28 +278,12 @@ Fields: ~
|
|||
this to use a different keyword, for example `'by'`
|
||||
to write `by:2026-03-15` instead of `due:2026-03-15`.
|
||||
|
||||
{recur_syntax} (string, default: 'rec')
|
||||
The token name for inline recurrence metadata. Change
|
||||
this to use a different keyword, for example
|
||||
`'repeat'` to write `repeat:weekly`.
|
||||
|
||||
{someday_date} (string, default: '9999-12-30')
|
||||
The date that `later` and `someday` resolve to. This
|
||||
acts as a "no date" sentinel for GTD-style workflows.
|
||||
|
||||
{category_order} (string[], default: {})
|
||||
Ordered list of category names. In category view,
|
||||
categories that appear in this list are shown in the
|
||||
given order. Categories not in the list are appended
|
||||
after the ordered ones in their natural order.
|
||||
|
||||
{keymaps} (table, default: see below) *pending.Keymaps*
|
||||
Buffer-local key bindings. Each field maps an action
|
||||
name to a key string. Set a field to `false` to
|
||||
disable that binding. Unset fields use the default.
|
||||
See |pending-mappings| for the full list of actions
|
||||
and their default keys.
|
||||
|
||||
{gcal} (table, default: nil)
|
||||
Google Calendar sync configuration. See
|
||||
|pending.GcalConfig|. Omit this field entirely to
|
||||
|
|
@ -499,11 +371,6 @@ PendingDone Applied to the text of completed tasks.
|
|||
PendingPriority Applied to the `! ` priority marker on priority tasks.
|
||||
Default: links to `DiagnosticWarn`.
|
||||
|
||||
*PendingRecur*
|
||||
PendingRecur Applied to the recurrence indicator virtual text shown
|
||||
alongside due dates for recurring tasks.
|
||||
Default: links to `DiagnosticInfo`.
|
||||
|
||||
To override a group in your colorscheme or config: >lua
|
||||
vim.api.nvim_set_hl(0, 'PendingDue', { fg = '#aaaaaa', italic = true })
|
||||
<
|
||||
|
|
@ -521,7 +388,6 @@ Checks performed: ~
|
|||
category, date format, date syntax)
|
||||
- Whether the data directory exists (warning if not yet created)
|
||||
- Whether the data file exists and can be parsed; reports total task count
|
||||
- Validates recurrence specs on stored tasks
|
||||
- Whether `curl` is available (required for Google Calendar sync)
|
||||
- Whether `openssl` is available (required for OAuth PKCE)
|
||||
|
||||
|
|
@ -548,8 +414,6 @@ Task fields: ~
|
|||
{category} (string) Category name. Defaults to `default_category`.
|
||||
{priority} (integer) `1` for priority tasks, `0` otherwise.
|
||||
{due} (string) ISO date string `YYYY-MM-DD`, or absent.
|
||||
{recur} (string) Recurrence shorthand (e.g. `weekly`), or absent.
|
||||
{recur_mode} (string) `'scheduled'` or `'completion'`, or absent.
|
||||
{entry} (string) ISO 8601 UTC timestamp of creation.
|
||||
{modified} (string) ISO 8601 UTC timestamp of last modification.
|
||||
{end} (string) ISO 8601 UTC timestamp of completion or deletion.
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ local function set_buf_options(bufnr)
|
|||
vim.bo[bufnr].swapfile = false
|
||||
vim.bo[bufnr].filetype = 'pending'
|
||||
vim.bo[bufnr].modifiable = true
|
||||
vim.bo[bufnr].omnifunc = 'v:lua.require("pending.complete").omnifunc'
|
||||
end
|
||||
|
||||
---@param winid integer
|
||||
|
|
@ -123,22 +122,24 @@ local function apply_extmarks(bufnr, line_meta)
|
|||
local row = i - 1
|
||||
if m.type == 'task' then
|
||||
local due_hl = m.overdue and 'PendingOverdue' or 'PendingDue'
|
||||
local virt_parts = {}
|
||||
if m.show_category and m.category then
|
||||
table.insert(virt_parts, { m.category, 'PendingHeader' })
|
||||
end
|
||||
if m.recur then
|
||||
table.insert(virt_parts, { '\u{21bb} ' .. m.recur, 'PendingRecur' })
|
||||
end
|
||||
if m.due then
|
||||
table.insert(virt_parts, { m.due, due_hl })
|
||||
end
|
||||
if #virt_parts > 0 then
|
||||
for p = 1, #virt_parts - 1 do
|
||||
virt_parts[p][1] = virt_parts[p][1] .. ' '
|
||||
if m.show_category then
|
||||
local virt_text
|
||||
if m.category and m.due then
|
||||
virt_text = { { m.category .. ' ', 'PendingHeader' }, { m.due, due_hl } }
|
||||
elseif m.category then
|
||||
virt_text = { { m.category, 'PendingHeader' } }
|
||||
elseif m.due then
|
||||
virt_text = { { m.due, due_hl } }
|
||||
end
|
||||
if virt_text then
|
||||
vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, {
|
||||
virt_text = virt_parts,
|
||||
virt_text = virt_text,
|
||||
virt_text_pos = 'eol',
|
||||
})
|
||||
end
|
||||
elseif m.due then
|
||||
vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, {
|
||||
virt_text = { { m.due, due_hl } },
|
||||
virt_text_pos = 'eol',
|
||||
})
|
||||
end
|
||||
|
|
@ -166,7 +167,6 @@ local function setup_highlights()
|
|||
vim.api.nvim_set_hl(0, 'PendingOverdue', { link = 'DiagnosticError', default = true })
|
||||
vim.api.nvim_set_hl(0, 'PendingDone', { link = 'Comment', default = true })
|
||||
vim.api.nvim_set_hl(0, 'PendingPriority', { link = 'DiagnosticWarn', default = true })
|
||||
vim.api.nvim_set_hl(0, 'PendingRecur', { link = 'DiagnosticInfo', default = true })
|
||||
end
|
||||
|
||||
local function snapshot_folds(bufnr)
|
||||
|
|
|
|||
|
|
@ -1,138 +0,0 @@
|
|||
local config = require('pending.config')
|
||||
|
||||
---@class pending.complete
|
||||
local M = {}
|
||||
|
||||
---@return string
|
||||
local function date_key()
|
||||
return config.get().date_syntax or 'due'
|
||||
end
|
||||
|
||||
---@return string
|
||||
local function recur_key()
|
||||
return config.get().recur_syntax or 'rec'
|
||||
end
|
||||
|
||||
---@return string[]
|
||||
local function get_categories()
|
||||
local store = require('pending.store')
|
||||
local seen = {}
|
||||
local result = {}
|
||||
for _, task in ipairs(store.active_tasks()) do
|
||||
local cat = task.category
|
||||
if cat and not seen[cat] then
|
||||
seen[cat] = true
|
||||
table.insert(result, cat)
|
||||
end
|
||||
end
|
||||
table.sort(result)
|
||||
return result
|
||||
end
|
||||
|
||||
---@return 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',
|
||||
}
|
||||
end
|
||||
|
||||
---@return 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)
|
||||
end
|
||||
for _, s in ipairs(list) do
|
||||
table.insert(result, '!' .. s)
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
---@type string?
|
||||
local _complete_source = nil
|
||||
|
||||
---@param findstart integer
|
||||
---@param base string
|
||||
---@return integer|table[]
|
||||
function M.omnifunc(findstart, base)
|
||||
if findstart == 1 then
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local col = vim.api.nvim_win_get_cursor(0)[2]
|
||||
local before = line:sub(1, col)
|
||||
|
||||
local dk = date_key()
|
||||
local rk = recur_key()
|
||||
|
||||
local checks = {
|
||||
{ vim.pesc(dk) .. ':([%S]*)$', dk },
|
||||
{ 'cat:([%S]*)$', 'cat' },
|
||||
{ vim.pesc(rk) .. ':([%S]*)$', rk },
|
||||
}
|
||||
|
||||
for _, check in ipairs(checks) do
|
||||
local start = before:find(check[1])
|
||||
if start then
|
||||
local colon_pos = before:find(':', start, true)
|
||||
if colon_pos then
|
||||
_complete_source = check[2]
|
||||
return colon_pos
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
_complete_source = nil
|
||||
return -1
|
||||
end
|
||||
|
||||
local candidates = {}
|
||||
local source = _complete_source or ''
|
||||
|
||||
local dk = date_key()
|
||||
local rk = recur_key()
|
||||
|
||||
if source == dk then
|
||||
candidates = date_completions()
|
||||
elseif source == 'cat' then
|
||||
candidates = get_categories()
|
||||
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 .. ']' })
|
||||
end
|
||||
end
|
||||
|
||||
return matches
|
||||
end
|
||||
|
||||
return M
|
||||
|
|
@ -2,16 +2,6 @@
|
|||
---@field calendar? string
|
||||
---@field credentials_path? string
|
||||
|
||||
---@class pending.Keymaps
|
||||
---@field close? string|false
|
||||
---@field toggle? string|false
|
||||
---@field view? string|false
|
||||
---@field priority? string|false
|
||||
---@field date? string|false
|
||||
---@field undo? string|false
|
||||
---@field open_line? string|false
|
||||
---@field open_line_above? string|false
|
||||
|
||||
---@class pending.Config
|
||||
---@field data_path string
|
||||
---@field default_view 'category'|'priority'
|
||||
|
|
@ -22,7 +12,6 @@
|
|||
---@field someday_date string
|
||||
---@field category_order? string[]
|
||||
---@field drawer_height? integer
|
||||
---@field keymaps pending.Keymaps
|
||||
---@field gcal? pending.GcalConfig
|
||||
|
||||
---@class pending.config
|
||||
|
|
@ -38,16 +27,6 @@ local defaults = {
|
|||
recur_syntax = 'rec',
|
||||
someday_date = '9999-12-30',
|
||||
category_order = {},
|
||||
keymaps = {
|
||||
close = 'q',
|
||||
toggle = '<CR>',
|
||||
view = '<Tab>',
|
||||
priority = '!',
|
||||
date = 'D',
|
||||
undo = 'U',
|
||||
open_line = 'o',
|
||||
open_line_above = 'O',
|
||||
},
|
||||
}
|
||||
|
||||
---@type pending.Config?
|
||||
|
|
|
|||
|
|
@ -27,17 +27,6 @@ function M.check()
|
|||
if load_ok then
|
||||
local tasks = store.tasks()
|
||||
vim.health.ok('Data file loaded: ' .. #tasks .. ' tasks')
|
||||
local recur = require('pending.recur')
|
||||
local invalid_count = 0
|
||||
for _, task in ipairs(tasks) do
|
||||
if task.recur and not recur.validate(task.recur) then
|
||||
invalid_count = invalid_count + 1
|
||||
vim.health.warn('Task ' .. task.id .. ' has invalid recurrence spec: ' .. task.recur)
|
||||
end
|
||||
end
|
||||
if invalid_count == 0 then
|
||||
vim.health.ok('All recurrence specs are valid')
|
||||
end
|
||||
else
|
||||
vim.health.error('Failed to load data file: ' .. tostring(err))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -50,44 +50,37 @@ end
|
|||
|
||||
---@param bufnr integer
|
||||
function M._setup_buf_mappings(bufnr)
|
||||
local cfg = require('pending.config').get()
|
||||
local km = cfg.keymaps
|
||||
local opts = { buffer = bufnr, silent = true }
|
||||
|
||||
---@type table<string, fun()>
|
||||
local actions = {
|
||||
close = function()
|
||||
vim.keymap.set('n', 'q', function()
|
||||
buffer.close()
|
||||
end,
|
||||
toggle = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', '<Esc>', function()
|
||||
buffer.close()
|
||||
end, opts)
|
||||
vim.keymap.set('n', '<CR>', function()
|
||||
M.toggle_complete()
|
||||
end,
|
||||
view = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', '<Tab>', function()
|
||||
buffer.toggle_view()
|
||||
end,
|
||||
priority = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'g?', function()
|
||||
M.show_help()
|
||||
end, opts)
|
||||
vim.keymap.set('n', '!', function()
|
||||
M.toggle_priority()
|
||||
end,
|
||||
date = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'D', function()
|
||||
M.prompt_date()
|
||||
end,
|
||||
undo = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'U', function()
|
||||
M.undo_write()
|
||||
end,
|
||||
open_line = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'o', function()
|
||||
buffer.open_line(false)
|
||||
end,
|
||||
open_line_above = function()
|
||||
end, opts)
|
||||
vim.keymap.set('n', 'O', function()
|
||||
buffer.open_line(true)
|
||||
end,
|
||||
}
|
||||
|
||||
for name, fn in pairs(actions) do
|
||||
local key = km[name]
|
||||
if key and key ~= false then
|
||||
vim.keymap.set('n', key --[[@as string]], fn, opts)
|
||||
end
|
||||
end
|
||||
end, opts)
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
|
|
@ -137,8 +130,7 @@ function M.toggle_complete()
|
|||
if task.recur and task.due then
|
||||
local recur = require('pending.recur')
|
||||
local mode = task.recur_mode or 'scheduled'
|
||||
local base = mode == 'completion' and os.date('%Y-%m-%d') --[[@as string]]
|
||||
or task.due
|
||||
local base = mode == 'completion' and os.date('%Y-%m-%d') --[[@as string]] or task.due
|
||||
local next_date = recur.next_due(base, task.recur, mode)
|
||||
store.add({
|
||||
description = task.description,
|
||||
|
|
@ -341,6 +333,75 @@ function M.due()
|
|||
vim.cmd('copen')
|
||||
end
|
||||
|
||||
function M.show_help()
|
||||
local cfg = require('pending.config').get()
|
||||
local dk = cfg.date_syntax or 'due'
|
||||
local rk = cfg.recur_syntax or 'rec'
|
||||
local lines = {
|
||||
'pending.nvim keybindings',
|
||||
'',
|
||||
'<CR> Toggle complete/uncomplete',
|
||||
'<Tab> Switch category/priority view',
|
||||
'! Toggle urgent',
|
||||
'D Set due date',
|
||||
'U Undo last write',
|
||||
'o / O Add new task line',
|
||||
'dd Delete task line (on :w)',
|
||||
'p / P Paste (duplicates get new IDs)',
|
||||
'zc / zo Fold/unfold category (category view)',
|
||||
':w Save all changes',
|
||||
'',
|
||||
':Pending add <text> Quick-add task',
|
||||
':Pending add Cat: <text> Quick-add with category',
|
||||
':Pending due Show overdue/due qflist',
|
||||
':Pending sync Push to Google Calendar',
|
||||
':Pending archive [days] Purge old done tasks',
|
||||
':Pending undo Undo last write',
|
||||
'',
|
||||
'Inline metadata (on new lines before :w):',
|
||||
' ' .. dk .. ':YYYY-MM-DD Set due date',
|
||||
' cat:Name Set category',
|
||||
' ' .. rk .. ':pattern Set recurrence',
|
||||
'',
|
||||
'Due date input:',
|
||||
' today, tomorrow, yesterday, +Nd, +Nw, +Nm',
|
||||
' -Nd, -Nw, mon-sun, jan-dec, 1st-31st',
|
||||
' eod, eow, eom, eoq, eoy, sow, som, soq, soy',
|
||||
' later, someday',
|
||||
' Empty input clears due date',
|
||||
'',
|
||||
'Recurrence patterns:',
|
||||
' daily, weekdays, weekly, biweekly',
|
||||
' monthly, quarterly, yearly, Nd, Nw, Nm, Ny',
|
||||
' Prefix ! for completion-based (e.g. !weekly)',
|
||||
'',
|
||||
'Completion: <C-x><C-o> after due:, cat:, rec:',
|
||||
'',
|
||||
'Press q or <Esc> to close',
|
||||
}
|
||||
local buf = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
vim.bo[buf].modifiable = false
|
||||
vim.bo[buf].bufhidden = 'wipe'
|
||||
local width = 54
|
||||
local height = #lines
|
||||
local win = vim.api.nvim_open_win(buf, true, {
|
||||
relative = 'editor',
|
||||
width = width,
|
||||
height = height,
|
||||
col = math.floor((vim.o.columns - width) / 2),
|
||||
row = math.floor((vim.o.lines - height) / 2),
|
||||
style = 'minimal',
|
||||
border = 'rounded',
|
||||
})
|
||||
vim.keymap.set('n', 'q', function()
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end, { buffer = buf, silent = true })
|
||||
vim.keymap.set('n', '<Esc>', function()
|
||||
vim.api.nvim_win_close(win, true)
|
||||
end, { buffer = buf, silent = true })
|
||||
end
|
||||
|
||||
---@param args string
|
||||
function M.command(args)
|
||||
if not args or args == '' then
|
||||
|
|
|
|||
|
|
@ -45,18 +45,9 @@ local weekday_map = {
|
|||
}
|
||||
|
||||
local month_map = {
|
||||
jan = 1,
|
||||
feb = 2,
|
||||
mar = 3,
|
||||
apr = 4,
|
||||
may = 5,
|
||||
jun = 6,
|
||||
jul = 7,
|
||||
aug = 8,
|
||||
sep = 9,
|
||||
oct = 10,
|
||||
nov = 11,
|
||||
dec = 12,
|
||||
jan = 1, feb = 2, mar = 3, apr = 4,
|
||||
may = 5, jun = 6, jul = 7, aug = 8,
|
||||
sep = 9, oct = 10, nov = 11, dec = 12,
|
||||
}
|
||||
|
||||
---@param today osdate
|
||||
|
|
@ -106,31 +97,49 @@ function M.resolve_date(text)
|
|||
end
|
||||
|
||||
if lower == 'som' then
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = 1 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = today.month, day = 1 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'eom' then
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = today.month + 1, day = 0 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'soq' then
|
||||
local q = math.ceil(today.month / 3)
|
||||
local first_month = (q - 1) * 3 + 1
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = first_month, day = 1 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = first_month, day = 1 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'eoq' then
|
||||
local q = math.ceil(today.month / 3)
|
||||
local last_month = q * 3
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = last_month + 1, day = 0 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'soy' then
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = 1, day = 1 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = 1, day = 1 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'eoy' then
|
||||
return os.date('%Y-%m-%d', os.time({ year = today.year, month = 12, day = 31 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = today.year, month = 12, day = 31 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
if lower == 'later' or lower == 'someday' then
|
||||
|
|
@ -144,9 +153,7 @@ function M.resolve_date(text)
|
|||
os.time({
|
||||
year = today.year,
|
||||
month = today.month,
|
||||
day = today.day + (
|
||||
tonumber(n) --[[@as integer]]
|
||||
),
|
||||
day = today.day + (tonumber(n) --[[@as integer]]),
|
||||
})
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
|
@ -158,9 +165,7 @@ function M.resolve_date(text)
|
|||
os.time({
|
||||
year = today.year,
|
||||
month = today.month,
|
||||
day = today.day + (
|
||||
tonumber(n) --[[@as integer]]
|
||||
) * 7,
|
||||
day = today.day + (tonumber(n) --[[@as integer]]) * 7,
|
||||
})
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
|
@ -171,9 +176,7 @@ function M.resolve_date(text)
|
|||
'%Y-%m-%d',
|
||||
os.time({
|
||||
year = today.year,
|
||||
month = today.month + (
|
||||
tonumber(n) --[[@as integer]]
|
||||
),
|
||||
month = today.month + (tonumber(n) --[[@as integer]]),
|
||||
day = today.day,
|
||||
})
|
||||
) --[[@as string]]
|
||||
|
|
@ -186,9 +189,7 @@ function M.resolve_date(text)
|
|||
os.time({
|
||||
year = today.year,
|
||||
month = today.month,
|
||||
day = today.day - (
|
||||
tonumber(n) --[[@as integer]]
|
||||
),
|
||||
day = today.day - (tonumber(n) --[[@as integer]]),
|
||||
})
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
|
@ -200,9 +201,7 @@ function M.resolve_date(text)
|
|||
os.time({
|
||||
year = today.year,
|
||||
month = today.month,
|
||||
day = today.day - (
|
||||
tonumber(n) --[[@as integer]]
|
||||
) * 7,
|
||||
day = today.day - (tonumber(n) --[[@as integer]]) * 7,
|
||||
})
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
|
@ -244,7 +243,10 @@ function M.resolve_date(text)
|
|||
if today.month >= target_month then
|
||||
y = y + 1
|
||||
end
|
||||
return os.date('%Y-%m-%d', os.time({ year = y, month = target_month, day = 1 })) --[[@as string]]
|
||||
return os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({ year = y, month = target_month, day = 1 })
|
||||
) --[[@as string]]
|
||||
end
|
||||
|
||||
local target_wday = weekday_map[lower]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
---@field interval integer
|
||||
---@field byday? string[]
|
||||
---@field from_completion boolean
|
||||
---@field _raw? string
|
||||
|
||||
---@class pending.recur
|
||||
local M = {}
|
||||
|
|
@ -11,12 +10,7 @@ local M = {}
|
|||
---@type table<string, pending.RecurSpec>
|
||||
local named = {
|
||||
daily = { freq = 'daily', interval = 1, from_completion = false },
|
||||
weekdays = {
|
||||
freq = 'weekly',
|
||||
interval = 1,
|
||||
byday = { 'MO', 'TU', 'WE', 'TH', 'FR' },
|
||||
from_completion = false,
|
||||
},
|
||||
weekdays = { freq = 'weekly', interval = 1, byday = { 'MO', 'TU', 'WE', 'TH', 'FR' }, from_completion = false },
|
||||
weekly = { freq = 'weekly', interval = 1, from_completion = false },
|
||||
biweekly = { freq = 'weekly', interval = 2, from_completion = false },
|
||||
monthly = { freq = 'monthly', interval = 1, from_completion = false },
|
||||
|
|
@ -102,12 +96,12 @@ local function advance_date(base_date, freq, interval)
|
|||
new_y = new_y + 1
|
||||
end
|
||||
local last_day = os.date('*t', os.time({ year = new_y, month = new_m + 1, day = 0 })) --[[@as osdate]]
|
||||
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
||||
local clamped_d = math.min(dn, last_day.day)
|
||||
return os.date('%Y-%m-%d', os.time({ year = new_y, month = new_m, day = clamped_d })) --[[@as string]]
|
||||
elseif freq == 'yearly' then
|
||||
local new_y = yn + interval
|
||||
local last_day = os.date('*t', os.time({ year = new_y, month = mn + 1, day = 0 })) --[[@as osdate]]
|
||||
local clamped_d = math.min(dn, last_day.day --[[@as integer]])
|
||||
local clamped_d = math.min(dn, last_day.day)
|
||||
return os.date('%Y-%m-%d', os.time({ year = new_y, month = mn, day = clamped_d })) --[[@as string]]
|
||||
end
|
||||
return base_date
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ local config = require('pending.config')
|
|||
---@field overdue? boolean
|
||||
---@field show_category? boolean
|
||||
---@field priority? integer
|
||||
---@field recur? string
|
||||
|
||||
---@class pending.views
|
||||
local M = {}
|
||||
|
|
@ -150,7 +149,6 @@ function M.category_view(tasks)
|
|||
status = task.status,
|
||||
category = cat,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and task.due < today or nil,
|
||||
recur = task.recur,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -202,7 +200,6 @@ function M.priority_view(tasks)
|
|||
category = task.category,
|
||||
overdue = task.status == 'pending' and task.due ~= nil and task.due < today or nil,
|
||||
show_category = true,
|
||||
recur = task.recur,
|
||||
})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -22,10 +22,6 @@ vim.keymap.set('n', '<Plug>(pending-open)', function()
|
|||
require('pending').open()
|
||||
end)
|
||||
|
||||
vim.keymap.set('n', '<Plug>(pending-close)', function()
|
||||
require('pending.buffer').close()
|
||||
end)
|
||||
|
||||
vim.keymap.set('n', '<Plug>(pending-toggle)', function()
|
||||
require('pending').toggle_complete()
|
||||
end)
|
||||
|
|
@ -41,15 +37,3 @@ end)
|
|||
vim.keymap.set('n', '<Plug>(pending-date)', function()
|
||||
require('pending').prompt_date()
|
||||
end)
|
||||
|
||||
vim.keymap.set('n', '<Plug>(pending-undo)', function()
|
||||
require('pending').undo_write()
|
||||
end)
|
||||
|
||||
vim.keymap.set('n', '<Plug>(pending-open-line)', function()
|
||||
require('pending.buffer').open_line(false)
|
||||
end)
|
||||
|
||||
vim.keymap.set('n', '<Plug>(pending-open-line-above)', function()
|
||||
require('pending.buffer').open_line(true)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -1,171 +0,0 @@
|
|||
require('spec.helpers')
|
||||
|
||||
local config = require('pending.config')
|
||||
local store = require('pending.store')
|
||||
|
||||
describe('complete', function()
|
||||
local tmpdir
|
||||
local complete = require('pending.complete')
|
||||
|
||||
before_each(function()
|
||||
tmpdir = vim.fn.tempname()
|
||||
vim.fn.mkdir(tmpdir, 'p')
|
||||
vim.g.pending = { data_path = tmpdir .. '/tasks.json' }
|
||||
config.reset()
|
||||
store.unload()
|
||||
store.load()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
vim.fn.delete(tmpdir, 'rf')
|
||||
vim.g.pending = nil
|
||||
config.reset()
|
||||
end)
|
||||
|
||||
describe('findstart', function()
|
||||
it('returns column after colon for cat: prefix', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task cat:Wo' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 16 })
|
||||
local result = complete.omnifunc(1, '')
|
||||
assert.are.equal(15, result)
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('returns column after colon for due: prefix', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task due:to' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 16 })
|
||||
local result = complete.omnifunc(1, '')
|
||||
assert.are.equal(15, result)
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('returns column after colon for rec: prefix', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task rec:we' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 16 })
|
||||
local result = complete.omnifunc(1, '')
|
||||
assert.are.equal(15, result)
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('returns -1 for non-token position', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] some task ' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 14 })
|
||||
local result = complete.omnifunc(1, '')
|
||||
assert.are.equal(-1, result)
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('completions', function()
|
||||
it('returns existing categories for cat:', function()
|
||||
store.add({ description = 'A', category = 'Work' })
|
||||
store.add({ description = 'B', category = 'Home' })
|
||||
store.add({ description = 'C', category = 'Work' })
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task cat: x' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 15 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, '')
|
||||
local words = {}
|
||||
for _, item in ipairs(result) do
|
||||
table.insert(words, item.word)
|
||||
end
|
||||
assert.is_true(vim.tbl_contains(words, 'Work'))
|
||||
assert.is_true(vim.tbl_contains(words, 'Home'))
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('filters categories by base', function()
|
||||
store.add({ description = 'A', category = 'Work' })
|
||||
store.add({ description = 'B', category = 'Home' })
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task cat:W' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 15 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, 'W')
|
||||
assert.are.equal(1, #result)
|
||||
assert.are.equal('Work', result[1].word)
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('returns named dates for due:', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task due: x' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 15 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, '')
|
||||
assert.is_true(#result > 0)
|
||||
local words = {}
|
||||
for _, item in ipairs(result) do
|
||||
table.insert(words, item.word)
|
||||
end
|
||||
assert.is_true(vim.tbl_contains(words, 'today'))
|
||||
assert.is_true(vim.tbl_contains(words, 'tomorrow'))
|
||||
assert.is_true(vim.tbl_contains(words, 'eom'))
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('filters dates by base prefix', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task due:to' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 16 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, 'to')
|
||||
local words = {}
|
||||
for _, item in ipairs(result) do
|
||||
table.insert(words, item.word)
|
||||
end
|
||||
assert.is_true(vim.tbl_contains(words, 'today'))
|
||||
assert.is_true(vim.tbl_contains(words, 'tomorrow'))
|
||||
assert.is_false(vim.tbl_contains(words, 'eom'))
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('returns recurrence shorthands for rec:', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task rec: x' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 15 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, '')
|
||||
assert.is_true(#result > 0)
|
||||
local words = {}
|
||||
for _, item in ipairs(result) do
|
||||
table.insert(words, item.word)
|
||||
end
|
||||
assert.is_true(vim.tbl_contains(words, 'daily'))
|
||||
assert.is_true(vim.tbl_contains(words, 'weekly'))
|
||||
assert.is_true(vim.tbl_contains(words, '!weekly'))
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
|
||||
it('filters recurrence by base prefix', function()
|
||||
local bufnr = vim.api.nvim_create_buf(false, true)
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, { '- [ ] task rec:we' })
|
||||
vim.api.nvim_set_current_buf(bufnr)
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 16 })
|
||||
complete.omnifunc(1, '')
|
||||
local result = complete.omnifunc(0, 'we')
|
||||
local words = {}
|
||||
for _, item in ipairs(result) do
|
||||
table.insert(words, item.word)
|
||||
end
|
||||
assert.is_true(vim.tbl_contains(words, 'weekly'))
|
||||
assert.is_true(vim.tbl_contains(words, 'weekdays'))
|
||||
assert.is_false(vim.tbl_contains(words, 'daily'))
|
||||
vim.api.nvim_buf_delete(bufnr, { force = true })
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
@ -193,8 +193,7 @@ describe('parse', function()
|
|||
|
||||
it('returns last day of current month for eom', function()
|
||||
local today = os.date('*t') --[[@as osdate]]
|
||||
local expected =
|
||||
os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 }))
|
||||
local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month + 1, day = 0 }))
|
||||
local result = parse.resolve_date('eom')
|
||||
assert.are.equal(expected, result)
|
||||
end)
|
||||
|
|
@ -212,8 +211,7 @@ describe('parse', function()
|
|||
local today = os.date('*t') --[[@as osdate]]
|
||||
local q = math.ceil(today.month / 3)
|
||||
local last_month = q * 3
|
||||
local expected =
|
||||
os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 }))
|
||||
local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = last_month + 1, day = 0 }))
|
||||
local result = parse.resolve_date('eoq')
|
||||
assert.are.equal(expected, result)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -151,14 +151,11 @@ describe('recur', function()
|
|||
|
||||
it('completion mode advances from today', function()
|
||||
local today = os.date('*t') --[[@as osdate]]
|
||||
local expected = os.date(
|
||||
'%Y-%m-%d',
|
||||
os.time({
|
||||
local expected = os.date('%Y-%m-%d', os.time({
|
||||
year = today.year,
|
||||
month = today.month,
|
||||
day = today.day + 7,
|
||||
})
|
||||
)
|
||||
}))
|
||||
local result = recur.next_due('2020-01-01', 'weekly', 'completion')
|
||||
assert.are.equal(expected, result)
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -204,30 +204,6 @@ describe('views', function()
|
|||
assert.is_falsy(task_meta.overdue)
|
||||
end)
|
||||
|
||||
it('includes recur in LineMeta for recurring tasks', function()
|
||||
store.add({ description = 'Recurring', category = 'Inbox', recur = 'weekly' })
|
||||
local _, meta = views.category_view(store.active_tasks())
|
||||
local task_meta
|
||||
for _, m in ipairs(meta) do
|
||||
if m.type == 'task' then
|
||||
task_meta = m
|
||||
end
|
||||
end
|
||||
assert.are.equal('weekly', task_meta.recur)
|
||||
end)
|
||||
|
||||
it('has nil recur in LineMeta for non-recurring tasks', function()
|
||||
store.add({ description = 'Normal', category = 'Inbox' })
|
||||
local _, meta = views.category_view(store.active_tasks())
|
||||
local task_meta
|
||||
for _, m in ipairs(meta) do
|
||||
if m.type == 'task' then
|
||||
task_meta = m
|
||||
end
|
||||
end
|
||||
assert.is_nil(task_meta.recur)
|
||||
end)
|
||||
|
||||
it('respects category_order when set', function()
|
||||
vim.g.pending = { data_path = tmpdir .. '/tasks.json', category_order = { 'Work', 'Inbox' } }
|
||||
config.reset()
|
||||
|
|
@ -423,29 +399,5 @@ describe('views', function()
|
|||
end
|
||||
assert.is_falsy(task_meta.overdue)
|
||||
end)
|
||||
|
||||
it('includes recur in LineMeta for recurring tasks', function()
|
||||
store.add({ description = 'Recurring', category = 'Inbox', recur = 'daily' })
|
||||
local _, meta = views.priority_view(store.active_tasks())
|
||||
local task_meta
|
||||
for _, m in ipairs(meta) do
|
||||
if m.type == 'task' then
|
||||
task_meta = m
|
||||
end
|
||||
end
|
||||
assert.are.equal('daily', task_meta.recur)
|
||||
end)
|
||||
|
||||
it('has nil recur in LineMeta for non-recurring tasks', function()
|
||||
store.add({ description = 'Normal', category = 'Inbox' })
|
||||
local _, meta = views.priority_view(store.active_tasks())
|
||||
local task_meta
|
||||
for _, m in ipairs(meta) do
|
||||
if m.type == 'task' then
|
||||
task_meta = m
|
||||
end
|
||||
end
|
||||
assert.is_nil(task_meta.recur)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue