feat: complete task editing coverage
Problem: the task editing surface had gaps — category and recurrence had no keymaps, `:Pending edit` required knowing the task ID, tasks couldn't be reordered with a keymap, priority was binary (0/1), and `wip`/`blocked` states were documented but unimplemented. Solution: fill every cell so every property is editable in every way. - `gc`/`gr` keymaps for category select and recurrence prompt - cursor-aware `:Pending edit` (omit ID to use task under cursor) - `J`/`K` keymaps to reorder tasks within a category - multi-level priorities (`max_priority` config, `g!` cycles 0→1→2→3→0) - `+!!`/`+!!!` tokens in `:Pending edit`, `:Pending add`, `parse.body()` - `PendingPriority2`/`PendingPriority3` highlight groups - `gw`/`gb` keymaps toggle `wip`/`blocked` status - `>`/`=` state chars in buffer rendering and diff parsing - `PendingWip`/`PendingBlocked` highlight groups - sort order: wip → pending → blocked → done - `wip`/`blocked` filter predicates and icons
This commit is contained in:
parent
073541424e
commit
6c3869b3c4
9 changed files with 498 additions and 101 deletions
143
doc/pending.txt
143
doc/pending.txt
|
|
@ -133,8 +133,11 @@ COMMANDS *pending-commands*
|
||||||
:Pending add School: Submit homework
|
:Pending add School: Submit homework
|
||||||
:Pending add Errands: Pick up dry cleaning due:fri
|
:Pending add Errands: Pick up dry cleaning due:fri
|
||||||
:Pending add Work: standup due:tomorrow rec:weekdays
|
:Pending add Work: standup due:tomorrow rec:weekdays
|
||||||
|
:Pending add Buy milk due:fri +!!
|
||||||
<
|
<
|
||||||
If the buffer is currently open it is re-rendered after the add.
|
Trailing `+!`, `+!!`, or `+!!!` tokens set the priority level (capped
|
||||||
|
at `max_priority`). If the buffer is currently open it is re-rendered
|
||||||
|
after the add.
|
||||||
|
|
||||||
*:Pending-archive*
|
*:Pending-archive*
|
||||||
:Pending archive [{days}]
|
:Pending archive [{days}]
|
||||||
|
|
@ -215,18 +218,22 @@ COMMANDS *pending-commands*
|
||||||
See |pending-filters| for the full list of supported predicates.
|
See |pending-filters| for the full list of supported predicates.
|
||||||
|
|
||||||
*:Pending-edit*
|
*:Pending-edit*
|
||||||
:Pending edit {id} [{operations}]
|
:Pending edit [{id}] [{operations}]
|
||||||
Edit metadata on an existing task without opening the buffer. {id} is the
|
Edit metadata on an existing task. {id} is the numeric task ID. When
|
||||||
numeric task ID. One or more operations follow: >vim
|
{id} is omitted and the task buffer is open, the task under the cursor
|
||||||
|
is used. This makes `:Pending edit +!` work without knowing the ID.
|
||||||
|
One or more operations follow: >vim
|
||||||
:Pending edit 5 due:tomorrow cat:Work +!
|
:Pending edit 5 due:tomorrow cat:Work +!
|
||||||
:Pending edit 5 -due -cat -rec
|
:Pending edit 5 -due -cat -rec
|
||||||
:Pending edit 5 rec:!weekly due:fri
|
:Pending edit +!!
|
||||||
<
|
<
|
||||||
Operations: ~
|
Operations: ~
|
||||||
`due:<date>` Set due date (accepts all |pending-dates| vocabulary).
|
`due:<date>` Set due date (accepts all |pending-dates| vocabulary).
|
||||||
`cat:<name>` Set category.
|
`cat:<name>` Set category.
|
||||||
`rec:<pattern>` Set recurrence (prefix `!` for completion-based).
|
`rec:<pattern>` Set recurrence (prefix `!` for completion-based).
|
||||||
`+!` Add priority flag.
|
`+!` Set priority to 1.
|
||||||
|
`+!!` Set priority to 2.
|
||||||
|
`+!!!` Set priority to 3 (capped at `max_priority`).
|
||||||
`-!` Remove priority flag.
|
`-!` Remove priority flag.
|
||||||
`-due` Clear due date.
|
`-due` Clear due date.
|
||||||
`-cat` Clear category.
|
`-cat` Clear category.
|
||||||
|
|
@ -267,13 +274,19 @@ Default buffer-local keys: ~
|
||||||
------- ------------------------------------------------
|
------- ------------------------------------------------
|
||||||
`q` Close the task buffer (`close`)
|
`q` Close the task buffer (`close`)
|
||||||
`<CR>` Toggle complete / uncomplete (`toggle`)
|
`<CR>` Toggle complete / uncomplete (`toggle`)
|
||||||
`g!` Toggle the priority flag (`priority`)
|
`g!` Cycle priority: 0→1→2→3→0 (`priority`)
|
||||||
`gd` Prompt for a due date (`date`)
|
`gd` Prompt for a due date (`date`)
|
||||||
|
`gc` Select a category from existing categories (`category`)
|
||||||
|
`gr` Prompt for a recurrence pattern (`recur`)
|
||||||
|
`gw` Toggle work-in-progress status (`wip`)
|
||||||
|
`gb` Toggle blocked status (`blocked`)
|
||||||
`gf` Prompt for filter predicates (`filter`)
|
`gf` Prompt for filter predicates (`filter`)
|
||||||
`<Tab>` Switch between category / queue view (`view`)
|
`<Tab>` Switch between category / queue view (`view`)
|
||||||
`gz` Undo the last `:w` save (`undo`)
|
`gz` Undo the last `:w` save (`undo`)
|
||||||
`o` Insert a new task line below (`open_line`)
|
`o` Insert a new task line below (`open_line`)
|
||||||
`O` Insert a new task line above (`open_line_above`)
|
`O` Insert a new task line above (`open_line_above`)
|
||||||
|
`J` Move task down within its category (`move_down`)
|
||||||
|
`K` Move task up within its category (`move_up`)
|
||||||
`zc` Fold the current category section (requires `folding`)
|
`zc` Fold the current category section (requires `folding`)
|
||||||
`zo` Unfold the current category section (requires `folding`)
|
`zo` Unfold the current category section (requires `folding`)
|
||||||
|
|
||||||
|
|
@ -338,7 +351,8 @@ old keys to `false`: >lua
|
||||||
|
|
||||||
*<Plug>(pending-priority)*
|
*<Plug>(pending-priority)*
|
||||||
<Plug>(pending-priority)
|
<Plug>(pending-priority)
|
||||||
Toggle the priority flag for the task under the cursor.
|
Cycle the priority level for the task under the cursor (0→1→2→3→0).
|
||||||
|
The maximum level is controlled by `max_priority` in |pending-config|.
|
||||||
|
|
||||||
*<Plug>(pending-date)*
|
*<Plug>(pending-date)*
|
||||||
<Plug>(pending-date)
|
<Plug>(pending-date)
|
||||||
|
|
@ -356,6 +370,35 @@ old keys to `false`: >lua
|
||||||
<Plug>(pending-filter)
|
<Plug>(pending-filter)
|
||||||
Prompt for filter predicates via |vim.ui.input|.
|
Prompt for filter predicates via |vim.ui.input|.
|
||||||
|
|
||||||
|
*<Plug>(pending-category)*
|
||||||
|
<Plug>(pending-category)
|
||||||
|
Select a category for the task under the cursor via |vim.ui.select|.
|
||||||
|
|
||||||
|
*<Plug>(pending-recur)*
|
||||||
|
<Plug>(pending-recur)
|
||||||
|
Prompt for a recurrence pattern for the task under the cursor.
|
||||||
|
Prefix with `!` for completion mode (e.g. `!weekly`). Empty input
|
||||||
|
removes recurrence.
|
||||||
|
|
||||||
|
*<Plug>(pending-move-down)*
|
||||||
|
<Plug>(pending-move-down)
|
||||||
|
Swap the task under the cursor with the one below it. In category
|
||||||
|
view, movement is limited to tasks within the same category.
|
||||||
|
|
||||||
|
*<Plug>(pending-move-up)*
|
||||||
|
<Plug>(pending-move-up)
|
||||||
|
Swap the task under the cursor with the one above it.
|
||||||
|
|
||||||
|
*<Plug>(pending-wip)*
|
||||||
|
<Plug>(pending-wip)
|
||||||
|
Toggle work-in-progress status for the task under the cursor.
|
||||||
|
If the task is already `wip`, reverts to `pending`.
|
||||||
|
|
||||||
|
*<Plug>(pending-blocked)*
|
||||||
|
<Plug>(pending-blocked)
|
||||||
|
Toggle blocked status for the task under the cursor.
|
||||||
|
If the task is already `blocked`, reverts to `pending`.
|
||||||
|
|
||||||
*<Plug>(pending-open-line)*
|
*<Plug>(pending-open-line)*
|
||||||
<Plug>(pending-open-line)
|
<Plug>(pending-open-line)
|
||||||
Insert a correctly-formatted blank task line below the cursor.
|
Insert a correctly-formatted blank task line below the cursor.
|
||||||
|
|
@ -414,16 +457,13 @@ Category view (default): ~ *pending-view-category*
|
||||||
order tasks were added unless `category_order` is set (see
|
order tasks were added unless `category_order` is set (see
|
||||||
|pending-config|). Blank lines separate categories. Within each category,
|
|pending-config|). Blank lines separate categories. Within each category,
|
||||||
tasks are sorted by status (wip → pending → blocked → done), then by
|
tasks are sorted by status (wip → pending → blocked → done), then by
|
||||||
priority, then by insertion order. The within-category sort order is
|
priority, then by insertion order. Category sections are foldable with
|
||||||
configurable via `category.sort` (see |pending-sort|). Category sections
|
`zc` and `zo`.
|
||||||
are foldable with `zc` and `zo`.
|
|
||||||
|
|
||||||
Queue view: ~ *pending-view-queue*
|
Queue view: ~ *pending-view-queue*
|
||||||
A flat list of all tasks sorted by status (wip → pending → blocked →
|
A flat list of all tasks sorted by status (wip → pending → blocked →
|
||||||
done), then by priority, then by due date (tasks without a due date sort
|
done), then by priority, then by due date (tasks without a due date sort
|
||||||
last), then by internal order. The sort order is configurable via
|
last), then by internal order. Category names are shown as right-aligned virtual
|
||||||
`queue.sort` (see |pending-sort|). Category names are shown as
|
|
||||||
right-aligned virtual
|
|
||||||
text alongside the due date virtual text so tasks remain identifiable
|
text alongside the due date virtual text so tasks remain identifiable
|
||||||
across categories. The buffer is named `pending://queue`.
|
across categories. The buffer is named `pending://queue`.
|
||||||
|
|
||||||
|
|
@ -618,6 +658,7 @@ loads: >lua
|
||||||
date_syntax = 'due',
|
date_syntax = 'due',
|
||||||
recur_syntax = 'rec',
|
recur_syntax = 'rec',
|
||||||
someday_date = '9999-12-30',
|
someday_date = '9999-12-30',
|
||||||
|
max_priority = 3,
|
||||||
view = {
|
view = {
|
||||||
default = 'category',
|
default = 'category',
|
||||||
eol_format = '%c %r %d',
|
eol_format = '%c %r %d',
|
||||||
|
|
@ -645,6 +686,12 @@ loads: >lua
|
||||||
prev_header = '[[',
|
prev_header = '[[',
|
||||||
next_task = ']t',
|
next_task = ']t',
|
||||||
prev_task = '[t',
|
prev_task = '[t',
|
||||||
|
category = 'gc',
|
||||||
|
recur = 'gr',
|
||||||
|
move_down = 'J',
|
||||||
|
move_up = 'K',
|
||||||
|
wip = 'gw',
|
||||||
|
blocked = 'gb',
|
||||||
},
|
},
|
||||||
sync = {
|
sync = {
|
||||||
gcal = {},
|
gcal = {},
|
||||||
|
|
@ -732,11 +779,6 @@ Fields: ~
|
||||||
in this list appear in the given order;
|
in this list appear in the given order;
|
||||||
others are appended after.
|
others are appended after.
|
||||||
|
|
||||||
{sort} (string|string[], default: 'default')
|
|
||||||
Sort order within each category. See
|
|
||||||
|pending-sort| for syntax. The `'default'`
|
|
||||||
preset is priority → order → id.
|
|
||||||
|
|
||||||
{folding} (boolean|table, default: true)
|
{folding} (boolean|table, default: true)
|
||||||
*pending.FoldingConfig*
|
*pending.FoldingConfig*
|
||||||
Controls category-level folds. `true`
|
Controls category-level folds. `true`
|
||||||
|
|
@ -751,43 +793,6 @@ Fields: ~
|
||||||
{queue} (table) *pending.QueueViewConfig*
|
{queue} (table) *pending.QueueViewConfig*
|
||||||
Queue (priority) view settings.
|
Queue (priority) view settings.
|
||||||
|
|
||||||
{sort} (string|string[], default: 'default')
|
|
||||||
Sort order for the queue view. See
|
|
||||||
|pending-sort| for syntax. The `'default'`
|
|
||||||
preset is priority → due → order → id.
|
|
||||||
|
|
||||||
Sort keys: ~ *pending-sort*
|
|
||||||
Both `category.sort` and `queue.sort` accept a named
|
|
||||||
preset string or an ordered list of sort keys.
|
|
||||||
|
|
||||||
Presets: ~
|
|
||||||
`'default'` priority → due → order → id
|
|
||||||
`'due-first'` due → priority → order → id
|
|
||||||
`'alphabetical'` description → priority → order → id
|
|
||||||
`'newest-first'` entry (desc) → priority → order → id
|
|
||||||
`'recent'` modified (desc) → priority → order → id
|
|
||||||
|
|
||||||
Available keys: ~
|
|
||||||
`'priority'` Higher priority first (descending)
|
|
||||||
`'due'` Earlier due date first (nil last)
|
|
||||||
`'status'` Pending before done
|
|
||||||
`'category'` Alphabetical by category
|
|
||||||
`'description'` Alphabetical by task text
|
|
||||||
`'entry'` Oldest creation date first
|
|
||||||
`'modified'` Oldest modification first
|
|
||||||
`'order'` Internal insertion order
|
|
||||||
`'id'` Task creation order
|
|
||||||
|
|
||||||
Prefix a key with `-` to flip its default direction
|
|
||||||
(e.g. `'-due'` for latest-first). `'priority'`
|
|
||||||
defaults to descending; all others default to
|
|
||||||
ascending. Implicit `order`, `id` tiebreakers are
|
|
||||||
appended when absent for stable, deterministic sort.
|
|
||||||
|
|
||||||
When `'status'` appears in the key list, the
|
|
||||||
pending-before-done split is disabled and status
|
|
||||||
participates as a normal sort field.
|
|
||||||
|
|
||||||
Examples: >lua
|
Examples: >lua
|
||||||
vim.g.pending = {
|
vim.g.pending = {
|
||||||
view = {
|
view = {
|
||||||
|
|
@ -795,12 +800,8 @@ Fields: ~
|
||||||
eol_format = '%d | %r',
|
eol_format = '%d | %r',
|
||||||
category = {
|
category = {
|
||||||
order = { 'Work', 'Personal' },
|
order = { 'Work', 'Personal' },
|
||||||
sort = { 'due', 'priority', 'order' },
|
|
||||||
folding = { foldtext = '%c: %n items' },
|
folding = { foldtext = '%c: %n items' },
|
||||||
},
|
},
|
||||||
queue = {
|
|
||||||
sort = 'due-first',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
<
|
<
|
||||||
|
|
@ -812,6 +813,15 @@ Fields: ~
|
||||||
See |pending-mappings| for the full list of actions
|
See |pending-mappings| for the full list of actions
|
||||||
and their default keys.
|
and their default keys.
|
||||||
|
|
||||||
|
{max_priority} (integer, default: 3)
|
||||||
|
Maximum priority level. The `g!` keymap cycles
|
||||||
|
through `0 → 1 → … → max_priority → 0`. Priority
|
||||||
|
levels map to highlight groups: `PendingPriority`
|
||||||
|
(1), `PendingPriority2` (2), `PendingPriority3`
|
||||||
|
(3+). `:Pending edit +!!` and `:Pending add +!!!`
|
||||||
|
accept multi-bang syntax capped at this value.
|
||||||
|
Set to `1` for the old binary on/off behavior.
|
||||||
|
|
||||||
{debug} (boolean, default: false)
|
{debug} (boolean, default: false)
|
||||||
Enable diagnostic logging. When `true`, textobj
|
Enable diagnostic logging. When `true`, textobj
|
||||||
motions, mapping registration, and cursor jumps
|
motions, mapping registration, and cursor jumps
|
||||||
|
|
@ -899,9 +909,17 @@ PendingBlocked Applied to the checkbox icon and text of blocked tasks.
|
||||||
Default: links to `DiagnosticError`.
|
Default: links to `DiagnosticError`.
|
||||||
|
|
||||||
*PendingPriority*
|
*PendingPriority*
|
||||||
PendingPriority Applied to the `! ` priority marker on priority tasks.
|
PendingPriority Applied to the checkbox icon of priority 1 tasks.
|
||||||
Default: links to `DiagnosticWarn`.
|
Default: links to `DiagnosticWarn`.
|
||||||
|
|
||||||
|
*PendingPriority2*
|
||||||
|
PendingPriority2 Applied to the checkbox icon of priority 2 tasks.
|
||||||
|
Default: links to `DiagnosticError`.
|
||||||
|
|
||||||
|
*PendingPriority3*
|
||||||
|
PendingPriority3 Applied to the checkbox icon of priority 3+ tasks.
|
||||||
|
Default: links to `DiagnosticError`.
|
||||||
|
|
||||||
*PendingRecur*
|
*PendingRecur*
|
||||||
PendingRecur Applied to the recurrence indicator virtual text shown
|
PendingRecur Applied to the recurrence indicator virtual text shown
|
||||||
alongside due dates for recurring tasks.
|
alongside due dates for recurring tasks.
|
||||||
|
|
@ -1266,7 +1284,8 @@ Task fields: ~
|
||||||
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
|
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
|
||||||
or `'deleted'`.
|
or `'deleted'`.
|
||||||
{category} (string) Category name. Defaults to `default_category`.
|
{category} (string) Category name. Defaults to `default_category`.
|
||||||
{priority} (integer) `1` for priority tasks, `0` otherwise.
|
{priority} (integer) Priority level: `0` (none), `1`–`3` (or up to
|
||||||
|
`max_priority`). Higher values sort first.
|
||||||
{due} (string) ISO date string `YYYY-MM-DD`, or absent.
|
{due} (string) ISO date string `YYYY-MM-DD`, or absent.
|
||||||
{recur} (string) Recurrence shorthand (e.g. `weekly`), or absent.
|
{recur} (string) Recurrence shorthand (e.g. `weekly`), or absent.
|
||||||
{recur_mode} (string) `'scheduled'` or `'completion'`, or absent.
|
{recur_mode} (string) `'scheduled'` or `'completion'`, or absent.
|
||||||
|
|
|
||||||
|
|
@ -137,12 +137,27 @@ local function apply_inline_row(bufnr, row, m, icons)
|
||||||
end_col = #line,
|
end_col = #line,
|
||||||
hl_group = 'PendingDone',
|
hl_group = 'PendingDone',
|
||||||
})
|
})
|
||||||
|
elseif m.status == 'blocked' then
|
||||||
|
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or ''
|
||||||
|
local col_start = line:find('/%d+/') and select(2, line:find('/%d+/')) or 0
|
||||||
|
vim.api.nvim_buf_set_extmark(bufnr, ns_inline, row, col_start, {
|
||||||
|
end_col = #line,
|
||||||
|
hl_group = 'PendingBlocked',
|
||||||
|
})
|
||||||
end
|
end
|
||||||
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or ''
|
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or ''
|
||||||
local bracket_col = (line:find('%[') or 1) - 1
|
local bracket_col = (line:find('%[') or 1) - 1
|
||||||
local icon, icon_hl
|
local icon, icon_hl
|
||||||
if m.status == 'done' then
|
if m.status == 'done' then
|
||||||
icon, icon_hl = icons.done, 'PendingDone'
|
icon, icon_hl = icons.done, 'PendingDone'
|
||||||
|
elseif m.status == 'wip' then
|
||||||
|
icon, icon_hl = icons.wip or '>', 'PendingWip'
|
||||||
|
elseif m.status == 'blocked' then
|
||||||
|
icon, icon_hl = icons.blocked or '=', 'PendingBlocked'
|
||||||
|
elseif m.priority and m.priority >= 3 then
|
||||||
|
icon, icon_hl = icons.priority, 'PendingPriority3'
|
||||||
|
elseif m.priority and m.priority == 2 then
|
||||||
|
icon, icon_hl = icons.priority, 'PendingPriority2'
|
||||||
elseif m.priority and m.priority > 0 then
|
elseif m.priority and m.priority > 0 then
|
||||||
icon, icon_hl = icons.priority, 'PendingPriority'
|
icon, icon_hl = icons.priority, 'PendingPriority'
|
||||||
else
|
else
|
||||||
|
|
@ -464,6 +479,10 @@ local function setup_highlights()
|
||||||
vim.api.nvim_set_hl(0, 'PendingOverdue', { link = 'DiagnosticError', default = true })
|
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, 'PendingDone', { link = 'Comment', default = true })
|
||||||
vim.api.nvim_set_hl(0, 'PendingPriority', { link = 'DiagnosticWarn', default = true })
|
vim.api.nvim_set_hl(0, 'PendingPriority', { link = 'DiagnosticWarn', default = true })
|
||||||
|
vim.api.nvim_set_hl(0, 'PendingPriority2', { link = 'DiagnosticError', default = true })
|
||||||
|
vim.api.nvim_set_hl(0, 'PendingPriority3', { link = 'DiagnosticError', default = true })
|
||||||
|
vim.api.nvim_set_hl(0, 'PendingWip', { link = 'DiagnosticInfo', default = true })
|
||||||
|
vim.api.nvim_set_hl(0, 'PendingBlocked', { link = 'DiagnosticError', default = true })
|
||||||
vim.api.nvim_set_hl(0, 'PendingRecur', { link = 'DiagnosticInfo', default = true })
|
vim.api.nvim_set_hl(0, 'PendingRecur', { link = 'DiagnosticInfo', default = true })
|
||||||
vim.api.nvim_set_hl(0, 'PendingFilter', { link = 'DiagnosticWarn', default = true })
|
vim.api.nvim_set_hl(0, 'PendingFilter', { link = 'DiagnosticWarn', default = true })
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@
|
||||||
---@field pending string
|
---@field pending string
|
||||||
---@field done string
|
---@field done string
|
||||||
---@field priority string
|
---@field priority string
|
||||||
|
---@field wip string
|
||||||
|
---@field blocked string
|
||||||
---@field due string
|
---@field due string
|
||||||
---@field recur string
|
---@field recur string
|
||||||
---@field category string
|
---@field category string
|
||||||
|
|
@ -48,6 +50,12 @@
|
||||||
---@field prev_header? string|false
|
---@field prev_header? string|false
|
||||||
---@field next_task? string|false
|
---@field next_task? string|false
|
||||||
---@field prev_task? string|false
|
---@field prev_task? string|false
|
||||||
|
---@field category? string|false
|
||||||
|
---@field recur? string|false
|
||||||
|
---@field move_down? string|false
|
||||||
|
---@field move_up? string|false
|
||||||
|
---@field wip? string|false
|
||||||
|
---@field blocked? string|false
|
||||||
|
|
||||||
---@class pending.CategoryViewConfig
|
---@class pending.CategoryViewConfig
|
||||||
---@field order? string[]
|
---@field order? string[]
|
||||||
|
|
@ -73,6 +81,7 @@
|
||||||
---@field debug? boolean
|
---@field debug? boolean
|
||||||
---@field keymaps pending.Keymaps
|
---@field keymaps pending.Keymaps
|
||||||
---@field view pending.ViewConfig
|
---@field view pending.ViewConfig
|
||||||
|
---@field max_priority? integer
|
||||||
---@field sync? pending.SyncConfig
|
---@field sync? pending.SyncConfig
|
||||||
---@field icons pending.Icons
|
---@field icons pending.Icons
|
||||||
|
|
||||||
|
|
@ -87,6 +96,7 @@ local defaults = {
|
||||||
date_syntax = 'due',
|
date_syntax = 'due',
|
||||||
recur_syntax = 'rec',
|
recur_syntax = 'rec',
|
||||||
someday_date = '9999-12-30',
|
someday_date = '9999-12-30',
|
||||||
|
max_priority = 3,
|
||||||
view = {
|
view = {
|
||||||
default = 'category',
|
default = 'category',
|
||||||
eol_format = '%c %r %d',
|
eol_format = '%c %r %d',
|
||||||
|
|
@ -114,12 +124,20 @@ local defaults = {
|
||||||
prev_header = '[[',
|
prev_header = '[[',
|
||||||
next_task = ']t',
|
next_task = ']t',
|
||||||
prev_task = '[t',
|
prev_task = '[t',
|
||||||
|
category = 'gc',
|
||||||
|
recur = 'gr',
|
||||||
|
move_down = 'J',
|
||||||
|
move_up = 'K',
|
||||||
|
wip = 'gw',
|
||||||
|
blocked = 'gb',
|
||||||
},
|
},
|
||||||
sync = {},
|
sync = {},
|
||||||
icons = {
|
icons = {
|
||||||
pending = ' ',
|
pending = ' ',
|
||||||
done = 'x',
|
done = 'x',
|
||||||
priority = '!',
|
priority = '!',
|
||||||
|
wip = '>',
|
||||||
|
blocked = '=',
|
||||||
due = '.',
|
due = '.',
|
||||||
recur = '~',
|
recur = '~',
|
||||||
category = '#',
|
category = '#',
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,16 @@ function M.parse_buffer(lines)
|
||||||
local stripped = body:match('^- %[.?%] (.*)$') or body
|
local stripped = body:match('^- %[.?%] (.*)$') or body
|
||||||
local state_char = body:match('^- %[(.-)%]') or ' '
|
local state_char = body:match('^- %[(.-)%]') or ' '
|
||||||
local priority = state_char == '!' and 1 or 0
|
local priority = state_char == '!' and 1 or 0
|
||||||
local status = state_char == 'x' and 'done' or 'pending'
|
local status
|
||||||
|
if state_char == 'x' then
|
||||||
|
status = 'done'
|
||||||
|
elseif state_char == '>' then
|
||||||
|
status = 'wip'
|
||||||
|
elseif state_char == '=' then
|
||||||
|
status = 'blocked'
|
||||||
|
else
|
||||||
|
status = 'pending'
|
||||||
|
end
|
||||||
local description, metadata = parse.body(stripped)
|
local description, metadata = parse.body(stripped)
|
||||||
if description and description ~= '' then
|
if description and description ~= '' then
|
||||||
table.insert(result, {
|
table.insert(result, {
|
||||||
|
|
@ -117,7 +126,10 @@ function M.apply(lines, s, hidden_ids)
|
||||||
task.category = entry.category
|
task.category = entry.category
|
||||||
changed = true
|
changed = true
|
||||||
end
|
end
|
||||||
if task.priority ~= entry.priority then
|
if entry.priority == 0 and task.priority > 0 then
|
||||||
|
task.priority = 0
|
||||||
|
changed = true
|
||||||
|
elseif entry.priority > 0 and task.priority == 0 then
|
||||||
task.priority = entry.priority
|
task.priority = entry.priority
|
||||||
changed = true
|
changed = true
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ function M._recompute_counts()
|
||||||
local today_str = os.date('%Y-%m-%d') --[[@as string]]
|
local today_str = os.date('%Y-%m-%d') --[[@as string]]
|
||||||
|
|
||||||
for _, task in ipairs(get_store():active_tasks()) do
|
for _, task in ipairs(get_store():active_tasks()) do
|
||||||
if task.status == 'pending' then
|
if task.status ~= 'done' and task.status ~= 'deleted' then
|
||||||
pending = pending + 1
|
pending = pending + 1
|
||||||
if task.priority > 0 then
|
if task.priority > 0 then
|
||||||
priority = priority + 1
|
priority = priority + 1
|
||||||
|
|
@ -163,6 +163,16 @@ local function compute_hidden_ids(tasks, predicates)
|
||||||
visible = false
|
visible = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
elseif pred == 'wip' then
|
||||||
|
if task.status ~= 'wip' then
|
||||||
|
visible = false
|
||||||
|
break
|
||||||
|
end
|
||||||
|
elseif pred == 'blocked' then
|
||||||
|
if task.status ~= 'blocked' then
|
||||||
|
visible = false
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not visible then
|
if not visible then
|
||||||
|
|
@ -335,6 +345,24 @@ function M._setup_buf_mappings(bufnr)
|
||||||
date = function()
|
date = function()
|
||||||
M.prompt_date()
|
M.prompt_date()
|
||||||
end,
|
end,
|
||||||
|
category = function()
|
||||||
|
M.prompt_category()
|
||||||
|
end,
|
||||||
|
recur = function()
|
||||||
|
M.prompt_recur()
|
||||||
|
end,
|
||||||
|
move_down = function()
|
||||||
|
M.move_task('down')
|
||||||
|
end,
|
||||||
|
move_up = function()
|
||||||
|
M.move_task('up')
|
||||||
|
end,
|
||||||
|
wip = function()
|
||||||
|
M.toggle_status('wip')
|
||||||
|
end,
|
||||||
|
blocked = function()
|
||||||
|
M.toggle_status('blocked')
|
||||||
|
end,
|
||||||
undo = function()
|
undo = function()
|
||||||
M.undo_write()
|
M.undo_write()
|
||||||
end,
|
end,
|
||||||
|
|
@ -605,7 +633,8 @@ function M.toggle_priority()
|
||||||
if not task then
|
if not task then
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local new_priority = task.priority > 0 and 0 or 1
|
local max = require('pending.config').get().max_priority or 3
|
||||||
|
local new_priority = (task.priority + 1) % (max + 1)
|
||||||
s:update(id, { priority = new_priority })
|
s:update(id, { priority = new_priority })
|
||||||
_save_and_notify()
|
_save_and_notify()
|
||||||
buffer.render(bufnr)
|
buffer.render(bufnr)
|
||||||
|
|
@ -658,6 +687,222 @@ function M.prompt_date()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param target_status 'wip'|'blocked'
|
||||||
|
---@return nil
|
||||||
|
function M.toggle_status(target_status)
|
||||||
|
local bufnr = buffer.bufnr()
|
||||||
|
if not bufnr then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not require_saved() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local meta = buffer.meta()
|
||||||
|
if not meta[row] or meta[row].type ~= 'task' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local id = meta[row].id
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local s = get_store()
|
||||||
|
local task = s:get(id)
|
||||||
|
if not task then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if task.status == target_status then
|
||||||
|
s:update(id, { status = 'pending' })
|
||||||
|
else
|
||||||
|
s:update(id, { status = target_status })
|
||||||
|
end
|
||||||
|
_save_and_notify()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
for lnum, m in ipairs(buffer.meta()) do
|
||||||
|
if m.id == id then
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param direction 'up'|'down'
|
||||||
|
---@return nil
|
||||||
|
function M.move_task(direction)
|
||||||
|
local bufnr = buffer.bufnr()
|
||||||
|
if not bufnr then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not require_saved() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local meta = buffer.meta()
|
||||||
|
if not meta[row] or meta[row].type ~= 'task' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local id = meta[row].id
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_row
|
||||||
|
if direction == 'down' then
|
||||||
|
target_row = row + 1
|
||||||
|
else
|
||||||
|
target_row = row - 1
|
||||||
|
end
|
||||||
|
if not meta[target_row] or meta[target_row].type ~= 'task' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local current_view_name = buffer.current_view_name() or 'category'
|
||||||
|
if current_view_name == 'category' then
|
||||||
|
if meta[target_row].category ~= meta[row].category then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local target_id = meta[target_row].id
|
||||||
|
if not target_id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local s = get_store()
|
||||||
|
local task_a = s:get(id)
|
||||||
|
local task_b = s:get(target_id)
|
||||||
|
if not task_a or not task_b then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
if task_a.order == 0 or task_b.order == 0 then
|
||||||
|
local tasks
|
||||||
|
if current_view_name == 'category' then
|
||||||
|
tasks = {}
|
||||||
|
for _, t in ipairs(s:active_tasks()) do
|
||||||
|
if t.category == task_a.category then
|
||||||
|
table.insert(tasks, t)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
tasks = s:active_tasks()
|
||||||
|
end
|
||||||
|
table.sort(tasks, function(a, b)
|
||||||
|
if a.order ~= b.order then
|
||||||
|
return a.order < b.order
|
||||||
|
end
|
||||||
|
return a.id < b.id
|
||||||
|
end)
|
||||||
|
for i, t in ipairs(tasks) do
|
||||||
|
s:update(t.id, { order = i })
|
||||||
|
end
|
||||||
|
task_a = s:get(id)
|
||||||
|
task_b = s:get(target_id)
|
||||||
|
if not task_a or not task_b then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local order_a, order_b = task_a.order, task_b.order
|
||||||
|
s:update(id, { order = order_b })
|
||||||
|
s:update(target_id, { order = order_a })
|
||||||
|
_save_and_notify()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
|
||||||
|
for lnum, m in ipairs(buffer.meta()) do
|
||||||
|
if m.id == id then
|
||||||
|
vim.api.nvim_win_set_cursor(0, { lnum, 0 })
|
||||||
|
break
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function M.prompt_category()
|
||||||
|
local bufnr = buffer.bufnr()
|
||||||
|
if not bufnr then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not require_saved() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local meta = buffer.meta()
|
||||||
|
if not meta[row] or meta[row].type ~= 'task' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local id = meta[row].id
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local s = get_store()
|
||||||
|
local seen = {}
|
||||||
|
local categories = {}
|
||||||
|
for _, task in ipairs(s:active_tasks()) do
|
||||||
|
if task.category and not seen[task.category] then
|
||||||
|
seen[task.category] = true
|
||||||
|
table.insert(categories, task.category)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(categories)
|
||||||
|
vim.ui.select(categories, { prompt = 'Category: ' }, function(choice)
|
||||||
|
if not choice then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
s:update(id, { category = choice })
|
||||||
|
_save_and_notify()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return nil
|
||||||
|
function M.prompt_recur()
|
||||||
|
local bufnr = buffer.bufnr()
|
||||||
|
if not bufnr then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not require_saved() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local meta = buffer.meta()
|
||||||
|
if not meta[row] or meta[row].type ~= 'task' then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local id = meta[row].id
|
||||||
|
if not id then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
vim.ui.input({ prompt = 'Recurrence (e.g. weekly, !daily): ' }, function(input)
|
||||||
|
if not input then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local s = get_store()
|
||||||
|
if input == '' then
|
||||||
|
s:update(id, { recur = vim.NIL, recur_mode = vim.NIL })
|
||||||
|
_save_and_notify()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
log.info('Task #' .. id .. ': recurrence removed.')
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local raw_spec = input
|
||||||
|
local rec_mode = nil
|
||||||
|
if raw_spec:sub(1, 1) == '!' then
|
||||||
|
rec_mode = 'completion'
|
||||||
|
raw_spec = raw_spec:sub(2)
|
||||||
|
end
|
||||||
|
local recur = require('pending.recur')
|
||||||
|
if not recur.validate(raw_spec) then
|
||||||
|
log.error('Invalid recurrence pattern: ' .. input)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
s:update(id, { recur = raw_spec, recur_mode = rec_mode })
|
||||||
|
_save_and_notify()
|
||||||
|
buffer.render(bufnr)
|
||||||
|
log.info('Task #' .. id .. ': recurrence set to ' .. raw_spec .. '.')
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
---@param text string
|
---@param text string
|
||||||
---@return nil
|
---@return nil
|
||||||
function M.add(text)
|
function M.add(text)
|
||||||
|
|
@ -678,6 +923,7 @@ function M.add(text)
|
||||||
due = metadata.due,
|
due = metadata.due,
|
||||||
recur = metadata.rec,
|
recur = metadata.rec,
|
||||||
recur_mode = metadata.rec_mode,
|
recur_mode = metadata.rec_mode,
|
||||||
|
priority = metadata.priority,
|
||||||
})
|
})
|
||||||
_save_and_notify()
|
_save_and_notify()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
|
|
@ -817,8 +1063,11 @@ local function parse_edit_token(token)
|
||||||
local dk = cfg.date_syntax or 'due'
|
local dk = cfg.date_syntax or 'due'
|
||||||
local rk = cfg.recur_syntax or 'rec'
|
local rk = cfg.recur_syntax or 'rec'
|
||||||
|
|
||||||
if token == '+!' then
|
local bangs = token:match('^%+(!+)$')
|
||||||
return 'priority', 1, nil
|
if bangs then
|
||||||
|
local max = cfg.max_priority or 3
|
||||||
|
local level = math.min(#bangs, max)
|
||||||
|
return 'priority', level, nil
|
||||||
end
|
end
|
||||||
if token == '-!' then
|
if token == '-!' then
|
||||||
return 'priority', 0, nil
|
return 'priority', 0, nil
|
||||||
|
|
@ -881,21 +1130,33 @@ local function parse_edit_token(token)
|
||||||
.. rk
|
.. rk
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param id_str string
|
---@param id_str? string
|
||||||
---@param rest string
|
---@param rest? string
|
||||||
---@return nil
|
---@return nil
|
||||||
function M.edit(id_str, rest)
|
function M.edit(id_str, rest)
|
||||||
if not id_str or id_str == '' then
|
local id = id_str and tonumber(id_str)
|
||||||
log.error(
|
|
||||||
'Usage: :Pending edit <id> [due:<date>] [cat:<name>] [rec:<pattern>] [+!] [-!] [-due] [-cat] [-rec]'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
local id = tonumber(id_str)
|
|
||||||
if not id then
|
if not id then
|
||||||
log.error('Invalid task ID: ' .. id_str)
|
local bufnr = buffer.bufnr()
|
||||||
return
|
if bufnr and vim.api.nvim_buf_is_valid(bufnr) then
|
||||||
|
local row = vim.api.nvim_win_get_cursor(0)[1]
|
||||||
|
local meta = buffer.meta()
|
||||||
|
if meta[row] and meta[row].type == 'task' and meta[row].id then
|
||||||
|
id = meta[row].id
|
||||||
|
if id_str and id_str ~= '' then
|
||||||
|
rest = rest and (id_str .. ' ' .. rest) or id_str
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if not id then
|
||||||
|
if id_str and id_str ~= '' then
|
||||||
|
log.error('Invalid task ID: ' .. id_str)
|
||||||
|
else
|
||||||
|
log.error(
|
||||||
|
'Usage: :Pending edit [<id>] [due:<date>] [cat:<name>] [rec:<pattern>] [+!] [-!] [-due] [-cat] [-rec]'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local s = get_store()
|
local s = get_store()
|
||||||
|
|
@ -955,7 +1216,11 @@ function M.edit(id_str, rest)
|
||||||
end
|
end
|
||||||
elseif field == 'priority' then
|
elseif field == 'priority' then
|
||||||
updates.priority = value
|
updates.priority = value
|
||||||
table.insert(feedback, value == 1 and 'priority added' or 'priority removed')
|
if value == 0 then
|
||||||
|
table.insert(feedback, 'priority removed')
|
||||||
|
else
|
||||||
|
table.insert(feedback, 'priority set to ' .. value)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -563,24 +563,34 @@ function M.body(text)
|
||||||
metadata.cat = cat_val
|
metadata.cat = cat_val
|
||||||
i = i - 1
|
i = i - 1
|
||||||
else
|
else
|
||||||
local rec_val = token:match(rec_pattern)
|
local pri_bangs = token:match('^%+(!+)$')
|
||||||
if rec_val then
|
if pri_bangs then
|
||||||
if metadata.rec then
|
if metadata.priority then
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
local recur = require('pending.recur')
|
local max = config.get().max_priority or 3
|
||||||
local raw_spec = rec_val
|
metadata.priority = math.min(#pri_bangs, max)
|
||||||
if raw_spec:sub(1, 1) == '!' then
|
|
||||||
metadata.rec_mode = 'completion'
|
|
||||||
raw_spec = raw_spec:sub(2)
|
|
||||||
end
|
|
||||||
if not recur.validate(raw_spec) then
|
|
||||||
break
|
|
||||||
end
|
|
||||||
metadata.rec = raw_spec
|
|
||||||
i = i - 1
|
i = i - 1
|
||||||
else
|
else
|
||||||
break
|
local rec_val = token:match(rec_pattern)
|
||||||
|
if rec_val then
|
||||||
|
if metadata.rec then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local recur = require('pending.recur')
|
||||||
|
local raw_spec = rec_val
|
||||||
|
if raw_spec:sub(1, 1) == '!' then
|
||||||
|
metadata.rec_mode = 'completion'
|
||||||
|
raw_spec = raw_spec:sub(2)
|
||||||
|
end
|
||||||
|
if not recur.validate(raw_spec) then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
metadata.rec = raw_spec
|
||||||
|
i = i - 1
|
||||||
|
else
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ local config = require('pending.config')
|
||||||
---@class pending.Task
|
---@class pending.Task
|
||||||
---@field id integer
|
---@field id integer
|
||||||
---@field description string
|
---@field description string
|
||||||
---@field status 'pending'|'done'|'deleted'
|
---@field status 'pending'|'done'|'deleted'|'wip'|'blocked'
|
||||||
---@field category? string
|
---@field category? string
|
||||||
---@field priority integer
|
---@field priority integer
|
||||||
---@field due? string
|
---@field due? string
|
||||||
|
|
|
||||||
|
|
@ -41,9 +41,32 @@ local function format_due(due)
|
||||||
return formatted
|
return formatted
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@type table<string, integer>
|
||||||
|
local status_rank = { wip = 0, pending = 1, blocked = 2, done = 3 }
|
||||||
|
|
||||||
|
---@param task pending.Task
|
||||||
|
---@return string
|
||||||
|
local function state_char(task)
|
||||||
|
if task.status == 'done' then
|
||||||
|
return 'x'
|
||||||
|
elseif task.status == 'wip' then
|
||||||
|
return '>'
|
||||||
|
elseif task.status == 'blocked' then
|
||||||
|
return '='
|
||||||
|
elseif task.priority > 0 then
|
||||||
|
return '!'
|
||||||
|
end
|
||||||
|
return ' '
|
||||||
|
end
|
||||||
|
|
||||||
---@param tasks pending.Task[]
|
---@param tasks pending.Task[]
|
||||||
local function sort_tasks(tasks)
|
local function sort_tasks(tasks)
|
||||||
table.sort(tasks, function(a, b)
|
table.sort(tasks, function(a, b)
|
||||||
|
local ra = status_rank[a.status] or 1
|
||||||
|
local rb = status_rank[b.status] or 1
|
||||||
|
if ra ~= rb then
|
||||||
|
return ra < rb
|
||||||
|
end
|
||||||
if a.priority ~= b.priority then
|
if a.priority ~= b.priority then
|
||||||
return a.priority > b.priority
|
return a.priority > b.priority
|
||||||
end
|
end
|
||||||
|
|
@ -57,6 +80,11 @@ end
|
||||||
---@param tasks pending.Task[]
|
---@param tasks pending.Task[]
|
||||||
local function sort_tasks_priority(tasks)
|
local function sort_tasks_priority(tasks)
|
||||||
table.sort(tasks, function(a, b)
|
table.sort(tasks, function(a, b)
|
||||||
|
local ra = status_rank[a.status] or 1
|
||||||
|
local rb = status_rank[b.status] or 1
|
||||||
|
if ra ~= rb then
|
||||||
|
return ra < rb
|
||||||
|
end
|
||||||
if a.priority ~= b.priority then
|
if a.priority ~= b.priority then
|
||||||
return a.priority > b.priority
|
return a.priority > b.priority
|
||||||
end
|
end
|
||||||
|
|
@ -95,7 +123,7 @@ function M.category_view(tasks)
|
||||||
by_cat[cat] = {}
|
by_cat[cat] = {}
|
||||||
done_by_cat[cat] = {}
|
done_by_cat[cat] = {}
|
||||||
end
|
end
|
||||||
if task.status == 'done' then
|
if task.status == 'done' or task.status == 'deleted' then
|
||||||
table.insert(done_by_cat[cat], task)
|
table.insert(done_by_cat[cat], task)
|
||||||
else
|
else
|
||||||
table.insert(by_cat[cat], task)
|
table.insert(by_cat[cat], task)
|
||||||
|
|
@ -146,7 +174,7 @@ function M.category_view(tasks)
|
||||||
|
|
||||||
for _, task in ipairs(all) do
|
for _, task in ipairs(all) do
|
||||||
local prefix = '/' .. task.id .. '/'
|
local prefix = '/' .. task.id .. '/'
|
||||||
local state = task.status == 'done' and 'x' or (task.priority > 0 and '!' or ' ')
|
local state = state_char(task)
|
||||||
local line = prefix .. '- [' .. state .. '] ' .. task.description
|
local line = prefix .. '- [' .. state .. '] ' .. task.description
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
table.insert(meta, {
|
table.insert(meta, {
|
||||||
|
|
@ -157,7 +185,7 @@ function M.category_view(tasks)
|
||||||
status = task.status,
|
status = task.status,
|
||||||
category = cat,
|
category = cat,
|
||||||
priority = task.priority,
|
priority = task.priority,
|
||||||
overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due)
|
overdue = task.status ~= 'done' and task.due ~= nil and parse.is_overdue(task.due)
|
||||||
or nil,
|
or nil,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
})
|
})
|
||||||
|
|
@ -209,7 +237,7 @@ function M.priority_view(tasks)
|
||||||
status = task.status,
|
status = task.status,
|
||||||
category = task.category,
|
category = task.category,
|
||||||
priority = task.priority,
|
priority = task.priority,
|
||||||
overdue = task.status == 'pending' and task.due ~= nil and parse.is_overdue(task.due) or nil,
|
overdue = task.status ~= 'done' and task.due ~= nil and parse.is_overdue(task.due) or nil,
|
||||||
show_category = true,
|
show_category = true,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@ local function edit_field_candidates()
|
||||||
'cat:',
|
'cat:',
|
||||||
rk .. ':',
|
rk .. ':',
|
||||||
'+!',
|
'+!',
|
||||||
|
'+!!',
|
||||||
|
'+!!!',
|
||||||
'-!',
|
'-!',
|
||||||
'-' .. dk,
|
'-' .. dk,
|
||||||
'-cat',
|
'-cat',
|
||||||
|
|
@ -181,7 +183,7 @@ end, {
|
||||||
for word in after_filter:gmatch('%S+') do
|
for word in after_filter:gmatch('%S+') do
|
||||||
used[word] = true
|
used[word] = true
|
||||||
end
|
end
|
||||||
local candidates = { 'clear', 'overdue', 'today', 'priority', 'done', 'pending' }
|
local candidates = { 'clear', 'overdue', 'today', 'priority', 'done', 'pending', 'wip', 'blocked' }
|
||||||
local store = require('pending.store')
|
local store = require('pending.store')
|
||||||
local s = store.new(store.resolve_path())
|
local s = store.new(store.resolve_path())
|
||||||
s:load()
|
s:load()
|
||||||
|
|
@ -280,6 +282,30 @@ vim.keymap.set('n', '<Plug>(pending-undo)', function()
|
||||||
require('pending').undo_write()
|
require('pending').undo_write()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-category)', function()
|
||||||
|
require('pending').prompt_category()
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-recur)', function()
|
||||||
|
require('pending').prompt_recur()
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-move-down)', function()
|
||||||
|
require('pending').move_task('down')
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-move-up)', function()
|
||||||
|
require('pending').move_task('up')
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-wip)', function()
|
||||||
|
require('pending').toggle_status('wip')
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-blocked)', function()
|
||||||
|
require('pending').toggle_status('blocked')
|
||||||
|
end)
|
||||||
|
|
||||||
vim.keymap.set('n', '<Plug>(pending-filter)', function()
|
vim.keymap.set('n', '<Plug>(pending-filter)', function()
|
||||||
vim.ui.input({ prompt = 'Filter: ' }, function(input)
|
vim.ui.input({ prompt = 'Filter: ' }, function(input)
|
||||||
if input then
|
if input then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue