feat: add cancelled task status with configurable state chars
Problem: the task lifecycle only has `pending`, `wip`, `blocked`, and `done`. There is no way to mark a task as abandoned. Additionally, state characters (`>`, `=`) are hardcoded rather than reading from `config.icons`, so customizing them has no effect on rendering or parsing. Solution: add a `cancelled` status with default state char `c`, `g/` keymap, `PendingCancelled` highlight, filter predicate, and archive support. Unify state chars by making `state_char()`, `parse_buffer()`, and `infer_status()` read from `config.icons`. Change defaults to mnemonic chars: `w` (wip), `b` (blocked), `c` (cancelled).
This commit is contained in:
parent
b2456580b5
commit
cee291a20b
9 changed files with 109 additions and 45 deletions
|
|
@ -347,6 +347,7 @@ Default buffer-local keys: ~
|
||||||
`gr` Prompt for a recurrence pattern (`recur`)
|
`gr` Prompt for a recurrence pattern (`recur`)
|
||||||
`gw` Toggle work-in-progress status (`wip`)
|
`gw` Toggle work-in-progress status (`wip`)
|
||||||
`gb` Toggle blocked status (`blocked`)
|
`gb` Toggle blocked status (`blocked`)
|
||||||
|
`g/` Toggle cancelled status (`cancelled`)
|
||||||
`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`)
|
||||||
|
|
@ -470,6 +471,12 @@ old keys to `false`: >lua
|
||||||
Toggle blocked status for the task under the cursor.
|
Toggle blocked status for the task under the cursor.
|
||||||
If the task is already `blocked`, reverts to `pending`.
|
If the task is already `blocked`, reverts to `pending`.
|
||||||
|
|
||||||
|
*<Plug>(pending-cancelled)*
|
||||||
|
<Plug>(pending-cancelled)
|
||||||
|
Toggle cancelled status for the task under the cursor.
|
||||||
|
If the task is already `cancelled`, reverts to `pending`.
|
||||||
|
Toggling on a `done` task switches it to `cancelled`.
|
||||||
|
|
||||||
*<Plug>(pending-priority-up)*
|
*<Plug>(pending-priority-up)*
|
||||||
<Plug>(pending-priority-up)
|
<Plug>(pending-priority-up)
|
||||||
Increment the priority level for the task under the cursor, clamped
|
Increment the priority level for the task under the cursor, clamped
|
||||||
|
|
@ -537,14 +544,15 @@ Category view (default): ~ *pending-view-category*
|
||||||
Tasks are grouped under their category header. Categories appear in the
|
Tasks are grouped under their category header. Categories appear in the
|
||||||
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 → cancelled), then by
|
||||||
priority, then by insertion order. Category sections are foldable with
|
priority, then by insertion order. Category sections are foldable with
|
||||||
`zc` and `zo`.
|
`zc` and `zo`.
|
||||||
|
|
||||||
Queue view: ~ *pending-view-queue*
|
Queue view: ~ *pending-view-queue*
|
||||||
A flat list of all tasks sorted by a configurable tiebreak chain
|
A flat list of all tasks sorted by a configurable tiebreak chain
|
||||||
(default: status → priority → due → order → id). See
|
(default: status → priority → due → order → id). See
|
||||||
`view.queue.sort` in |pending-config| for customization. Category
|
`view.queue.sort` in |pending-config| for customization. Status
|
||||||
|
order: wip → pending → blocked → done → cancelled. Category
|
||||||
names are shown as right-aligned virtual text alongside the due date
|
names are shown as right-aligned virtual text alongside the due date
|
||||||
virtual text so tasks remain identifiable across categories. The
|
virtual text so tasks remain identifiable across categories. The
|
||||||
buffer is named `pending://queue`.
|
buffer is named `pending://queue`.
|
||||||
|
|
@ -581,6 +589,8 @@ Available predicates: ~
|
||||||
|
|
||||||
`blocked` Show only tasks with status `blocked`.
|
`blocked` Show only tasks with status `blocked`.
|
||||||
|
|
||||||
|
`cancelled` Show only tasks with status `cancelled`.
|
||||||
|
|
||||||
`clear` Special value for |:Pending-filter| — clears the active filter
|
`clear` Special value for |:Pending-filter| — clears the active filter
|
||||||
and shows all tasks.
|
and shows all tasks.
|
||||||
|
|
||||||
|
|
@ -778,6 +788,7 @@ loads: >lua
|
||||||
move_up = 'K',
|
move_up = 'K',
|
||||||
wip = 'gw',
|
wip = 'gw',
|
||||||
blocked = 'gb',
|
blocked = 'gb',
|
||||||
|
cancelled = 'g/',
|
||||||
},
|
},
|
||||||
sync = {
|
sync = {
|
||||||
gcal = {},
|
gcal = {},
|
||||||
|
|
@ -967,17 +978,21 @@ Fields: ~
|
||||||
See |pending-gcal|, |pending-gtasks|, |pending-s3|.
|
See |pending-gcal|, |pending-gtasks|, |pending-s3|.
|
||||||
|
|
||||||
{icons} (table) *pending.Icons*
|
{icons} (table) *pending.Icons*
|
||||||
Icon characters displayed in the buffer. The
|
Icon characters used for rendering and parsing
|
||||||
{pending}, {done}, {priority}, {wip}, and
|
task checkboxes. The {pending}, {done},
|
||||||
{blocked} characters appear inside brackets
|
{priority}, {wip}, {blocked}, and {cancelled}
|
||||||
(`[icon]`) as an overlay on the checkbox. The
|
characters determine what is written inside
|
||||||
{category} character prefixes both header lines
|
brackets (`[icon]`) in the buffer text and how
|
||||||
and EOL category labels. Fields:
|
status is inferred on `:w`. Each must be a
|
||||||
|
single character. The {category} character
|
||||||
|
prefixes header lines and EOL category labels.
|
||||||
|
Fields:
|
||||||
{pending} Pending task character. Default: ' '
|
{pending} Pending task character. Default: ' '
|
||||||
{done} Done task character. Default: 'x'
|
{done} Done task character. Default: 'x'
|
||||||
{priority} Priority task character. Default: '!'
|
{priority} Priority task character. Default: '!'
|
||||||
{wip} Work-in-progress character. Default: '>'
|
{wip} Work-in-progress character. Default: 'w'
|
||||||
{blocked} Blocked task character. Default: '='
|
{blocked} Blocked task character. Default: 'b'
|
||||||
|
{cancelled} Cancelled task character. Default: 'c'
|
||||||
{due} Due date prefix. Default: '.'
|
{due} Due date prefix. Default: '.'
|
||||||
{recur} Recurrence prefix. Default: '~'
|
{recur} Recurrence prefix. Default: '~'
|
||||||
{category} Category prefix. Default: '#'
|
{category} Category prefix. Default: '#'
|
||||||
|
|
@ -1034,6 +1049,10 @@ PendingWip Applied to the checkbox icon of work-in-progress tasks.
|
||||||
PendingBlocked Applied to the checkbox icon and text of blocked tasks.
|
PendingBlocked Applied to the checkbox icon and text of blocked tasks.
|
||||||
Default: links to `DiagnosticError`.
|
Default: links to `DiagnosticError`.
|
||||||
|
|
||||||
|
*PendingCancelled*
|
||||||
|
PendingCancelled Applied to the checkbox icon and text of cancelled tasks.
|
||||||
|
Default: links to `NonText`.
|
||||||
|
|
||||||
*PendingPriority*
|
*PendingPriority*
|
||||||
PendingPriority Applied to the checkbox icon of priority 1 tasks.
|
PendingPriority Applied to the checkbox icon of priority 1 tasks.
|
||||||
Default: links to `DiagnosticWarn`.
|
Default: links to `DiagnosticWarn`.
|
||||||
|
|
@ -1603,8 +1622,8 @@ with cached data and updates extmarks when the fetch completes.
|
||||||
|
|
||||||
State pull: ~
|
State pull: ~
|
||||||
Requires `forge.close = true`. After fetching, if the remote issue/PR
|
Requires `forge.close = true`. After fetching, if the remote issue/PR
|
||||||
is closed or merged and the local task is pending/wip/blocked, the task is
|
is closed or merged and the local task is pending/wip/blocked (not cancelled),
|
||||||
automatically marked as done. Disabled by default. One-way: local status
|
the task is automatically marked as done. Disabled by default. One-way: local status
|
||||||
changes do not push back to the forge.
|
changes do not push back to the forge.
|
||||||
|
|
||||||
Highlight groups: ~
|
Highlight groups: ~
|
||||||
|
|
@ -1632,7 +1651,7 @@ Task fields: ~
|
||||||
{id} (integer) Unique, auto-incrementing task identifier.
|
{id} (integer) Unique, auto-incrementing task identifier.
|
||||||
{description} (string) Task text as shown in the buffer.
|
{description} (string) Task text as shown in the buffer.
|
||||||
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
|
{status} (string) `'pending'`, `'wip'`, `'blocked'`, `'done'`,
|
||||||
or `'deleted'`.
|
`'cancelled'`, or `'deleted'`.
|
||||||
{category} (string) Category name. Defaults to `default_category`.
|
{category} (string) Category name. Defaults to `default_category`.
|
||||||
{priority} (integer) Priority level: `0` (none), `1`–`3` (or up to
|
{priority} (integer) Priority level: `0` (none), `1`–`3` (or up to
|
||||||
`max_priority`). Higher values sort first.
|
`max_priority`). Higher values sort first.
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,14 @@ local function apply_inline_row(bufnr, row, m, icons)
|
||||||
hl_group = 'PendingDone',
|
hl_group = 'PendingDone',
|
||||||
invalidate = true,
|
invalidate = true,
|
||||||
})
|
})
|
||||||
|
elseif m.status == 'cancelled' 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 = 'PendingCancelled',
|
||||||
|
invalidate = true,
|
||||||
|
})
|
||||||
elseif m.status == 'blocked' then
|
elseif m.status == 'blocked' then
|
||||||
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 col_start = line:find('/%d+/') and select(2, line:find('/%d+/')) or 0
|
local col_start = line:find('/%d+/') and select(2, line:find('/%d+/')) or 0
|
||||||
|
|
@ -160,10 +168,12 @@ local function apply_inline_row(bufnr, row, m, icons)
|
||||||
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 == 'cancelled' then
|
||||||
|
icon, icon_hl = icons.cancelled, 'PendingCancelled'
|
||||||
elseif m.status == 'wip' then
|
elseif m.status == 'wip' then
|
||||||
icon, icon_hl = icons.wip or '>', 'PendingWip'
|
icon, icon_hl = icons.wip, 'PendingWip'
|
||||||
elseif m.status == 'blocked' then
|
elseif m.status == 'blocked' then
|
||||||
icon, icon_hl = icons.blocked or '=', 'PendingBlocked'
|
icon, icon_hl = icons.blocked, 'PendingBlocked'
|
||||||
elseif m.priority and m.priority >= 3 then
|
elseif m.priority and m.priority >= 3 then
|
||||||
icon, icon_hl = icons.priority, 'PendingPriority3'
|
icon, icon_hl = icons.priority, 'PendingPriority3'
|
||||||
elseif m.priority and m.priority == 2 then
|
elseif m.priority and m.priority == 2 then
|
||||||
|
|
@ -216,11 +226,14 @@ local function infer_status(line)
|
||||||
if not ch then
|
if not ch then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
if ch == 'x' then
|
local icons = config.get().icons
|
||||||
|
if ch == icons.done then
|
||||||
return 'done'
|
return 'done'
|
||||||
elseif ch == '>' then
|
elseif ch == icons.cancelled then
|
||||||
|
return 'cancelled'
|
||||||
|
elseif ch == icons.wip then
|
||||||
return 'wip'
|
return 'wip'
|
||||||
elseif ch == '=' then
|
elseif ch == icons.blocked then
|
||||||
return 'blocked'
|
return 'blocked'
|
||||||
end
|
end
|
||||||
return 'pending'
|
return 'pending'
|
||||||
|
|
@ -573,6 +586,7 @@ local function setup_highlights()
|
||||||
vim.api.nvim_set_hl(0, 'PendingPriority3', { 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, 'PendingWip', { link = 'DiagnosticInfo', default = true })
|
||||||
vim.api.nvim_set_hl(0, 'PendingBlocked', { link = 'DiagnosticError', default = true })
|
vim.api.nvim_set_hl(0, 'PendingBlocked', { link = 'DiagnosticError', default = true })
|
||||||
|
vim.api.nvim_set_hl(0, 'PendingCancelled', { link = 'NonText', 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 })
|
||||||
vim.api.nvim_set_hl(0, 'PendingForge', { link = 'DiagnosticInfo', default = true })
|
vim.api.nvim_set_hl(0, 'PendingForge', { link = 'DiagnosticInfo', default = true })
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
---@field priority string
|
---@field priority string
|
||||||
---@field wip string
|
---@field wip string
|
||||||
---@field blocked string
|
---@field blocked string
|
||||||
|
---@field cancelled string
|
||||||
---@field due string
|
---@field due string
|
||||||
---@field recur string
|
---@field recur string
|
||||||
---@field category string
|
---@field category string
|
||||||
|
|
@ -80,6 +81,7 @@
|
||||||
---@field blocked? string|false
|
---@field blocked? string|false
|
||||||
---@field priority_up_visual? string|false
|
---@field priority_up_visual? string|false
|
||||||
---@field priority_down_visual? string|false
|
---@field priority_down_visual? string|false
|
||||||
|
---@field cancelled? string|false
|
||||||
|
|
||||||
---@class pending.CategoryViewConfig
|
---@class pending.CategoryViewConfig
|
||||||
---@field order? string[]
|
---@field order? string[]
|
||||||
|
|
@ -162,6 +164,7 @@ local defaults = {
|
||||||
move_up = 'K',
|
move_up = 'K',
|
||||||
wip = 'gw',
|
wip = 'gw',
|
||||||
blocked = 'gb',
|
blocked = 'gb',
|
||||||
|
cancelled = 'g/',
|
||||||
priority_up = '<C-a>',
|
priority_up = '<C-a>',
|
||||||
priority_down = '<C-x>',
|
priority_down = '<C-x>',
|
||||||
priority_up_visual = 'g<C-a>',
|
priority_up_visual = 'g<C-a>',
|
||||||
|
|
@ -192,8 +195,9 @@ local defaults = {
|
||||||
pending = ' ',
|
pending = ' ',
|
||||||
done = 'x',
|
done = 'x',
|
||||||
priority = '!',
|
priority = '!',
|
||||||
wip = '>',
|
wip = 'w',
|
||||||
blocked = '=',
|
blocked = 'b',
|
||||||
|
cancelled = 'c',
|
||||||
due = '.',
|
due = '.',
|
||||||
recur = '~',
|
recur = '~',
|
||||||
category = '#',
|
category = '#',
|
||||||
|
|
|
||||||
|
|
@ -43,14 +43,17 @@ function M.parse_buffer(lines)
|
||||||
table.insert(result, { type = 'blank', lnum = i })
|
table.insert(result, { type = 'blank', lnum = i })
|
||||||
elseif id or body then
|
elseif id or body then
|
||||||
local stripped = body:match('^- %[.?%] (.*)$') or body
|
local stripped = body:match('^- %[.?%] (.*)$') or body
|
||||||
local state_char = body:match('^- %[(.-)%]') or ' '
|
local icons = config.get().icons
|
||||||
local priority = state_char == '!' and 1 or 0
|
local state_char = body:match('^- %[(.-)%]') or icons.pending
|
||||||
|
local priority = state_char == icons.priority and 1 or 0
|
||||||
local status
|
local status
|
||||||
if state_char == 'x' then
|
if state_char == icons.done then
|
||||||
status = 'done'
|
status = 'done'
|
||||||
elseif state_char == '>' then
|
elseif state_char == icons.cancelled then
|
||||||
|
status = 'cancelled'
|
||||||
|
elseif state_char == icons.wip then
|
||||||
status = 'wip'
|
status = 'wip'
|
||||||
elseif state_char == '=' then
|
elseif state_char == icons.blocked then
|
||||||
status = 'blocked'
|
status = 'blocked'
|
||||||
else
|
else
|
||||||
status = 'pending'
|
status = 'pending'
|
||||||
|
|
@ -177,7 +180,7 @@ function M.apply(lines, s, hidden_ids)
|
||||||
end
|
end
|
||||||
if entry.status and task.status ~= entry.status then
|
if entry.status and task.status ~= entry.status then
|
||||||
task.status = entry.status
|
task.status = entry.status
|
||||||
if entry.status == 'done' then
|
if entry.status == 'done' or entry.status == 'cancelled' then
|
||||||
task['end'] = now
|
task['end'] = now
|
||||||
else
|
else
|
||||||
task['end'] = nil
|
task['end'] = nil
|
||||||
|
|
|
||||||
|
|
@ -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 ~= 'done' and task.status ~= 'deleted' then
|
if task.status ~= 'done' and task.status ~= 'deleted' and task.status ~= 'cancelled' then
|
||||||
pending = pending + 1
|
pending = pending + 1
|
||||||
if task.priority > 0 then
|
if task.priority > 0 then
|
||||||
priority = priority + 1
|
priority = priority + 1
|
||||||
|
|
@ -173,6 +173,11 @@ local function compute_hidden_ids(tasks, predicates)
|
||||||
visible = false
|
visible = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
|
elseif pred == 'cancelled' then
|
||||||
|
if task.status ~= 'cancelled' then
|
||||||
|
visible = false
|
||||||
|
break
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if not visible then
|
if not visible then
|
||||||
|
|
@ -368,6 +373,9 @@ function M._setup_buf_mappings(bufnr)
|
||||||
blocked = function()
|
blocked = function()
|
||||||
M.toggle_status('blocked')
|
M.toggle_status('blocked')
|
||||||
end,
|
end,
|
||||||
|
cancelled = function()
|
||||||
|
M.toggle_status('cancelled')
|
||||||
|
end,
|
||||||
priority_up = function()
|
priority_up = function()
|
||||||
M.increment_priority()
|
M.increment_priority()
|
||||||
end,
|
end,
|
||||||
|
|
@ -843,7 +851,7 @@ function M.prompt_date()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param target_status 'wip'|'blocked'
|
---@param target_status 'wip'|'blocked'|'cancelled'
|
||||||
---@return nil
|
---@return nil
|
||||||
function M.toggle_status(target_status)
|
function M.toggle_status(target_status)
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
|
|
@ -869,7 +877,7 @@ function M.toggle_status(target_status)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
if task.status == target_status then
|
if task.status == target_status then
|
||||||
s:update(id, { status = 'pending' })
|
s:update(id, { status = 'pending', ['end'] = vim.NIL })
|
||||||
else
|
else
|
||||||
s:update(id, { status = target_status })
|
s:update(id, { status = target_status })
|
||||||
end
|
end
|
||||||
|
|
@ -1187,7 +1195,7 @@ function M.archive(arg)
|
||||||
log.debug(('archive: days=%d cutoff=%s total_tasks=%d'):format(days, cutoff, #tasks))
|
log.debug(('archive: days=%d cutoff=%s total_tasks=%d'):format(days, cutoff, #tasks))
|
||||||
local count = 0
|
local count = 0
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(tasks) do
|
||||||
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
|
if (task.status == 'done' or task.status == 'deleted' or task.status == 'cancelled') and task['end'] then
|
||||||
if task['end'] < cutoff then
|
if task['end'] < cutoff then
|
||||||
count = count + 1
|
count = count + 1
|
||||||
end
|
end
|
||||||
|
|
@ -1208,7 +1216,7 @@ function M.archive(arg)
|
||||||
function()
|
function()
|
||||||
local kept = {}
|
local kept = {}
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(tasks) do
|
||||||
if (task.status == 'done' or task.status == 'deleted') and task['end'] then
|
if (task.status == 'done' or task.status == 'deleted' or task.status == 'cancelled') and task['end'] then
|
||||||
if task['end'] < cutoff then
|
if task['end'] < cutoff then
|
||||||
goto skip
|
goto skip
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
local config = require('pending.config')
|
local config = require('pending.config')
|
||||||
|
|
||||||
---@alias pending.TaskStatus 'pending'|'done'|'deleted'|'wip'|'blocked'
|
---@alias pending.TaskStatus 'pending'|'done'|'deleted'|'wip'|'blocked'|'cancelled'
|
||||||
---@alias pending.RecurMode 'scheduled'|'completion'
|
---@alias pending.RecurMode 'scheduled'|'completion'
|
||||||
|
|
||||||
---@class pending.TaskExtra
|
---@class pending.TaskExtra
|
||||||
|
|
@ -331,7 +331,7 @@ function Store:update(id, fields)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
task.modified = now
|
task.modified = now
|
||||||
if fields.status == 'done' or fields.status == 'deleted' then
|
if fields.status == 'done' or fields.status == 'deleted' or fields.status == 'cancelled' then
|
||||||
task['end'] = task['end'] or now
|
task['end'] = task['end'] or now
|
||||||
end
|
end
|
||||||
return task
|
return task
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,7 @@ function M.push()
|
||||||
and (
|
and (
|
||||||
task.status == 'done'
|
task.status == 'done'
|
||||||
or task.status == 'deleted'
|
or task.status == 'deleted'
|
||||||
|
or task.status == 'cancelled'
|
||||||
or (task.status == 'pending' and not task.due)
|
or (task.status == 'pending' and not task.due)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,21 +71,24 @@ local function compute_forge_spans(task, prefix_len)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@type table<string, integer>
|
---@type table<string, integer>
|
||||||
local status_rank = { wip = 0, pending = 1, blocked = 2, done = 3 }
|
local status_rank = { wip = 0, pending = 1, blocked = 2, done = 3, cancelled = 4 }
|
||||||
|
|
||||||
---@param task pending.Task
|
---@param task pending.Task
|
||||||
---@return string
|
---@return string
|
||||||
local function state_char(task)
|
local function state_char(task)
|
||||||
|
local icons = config.get().icons
|
||||||
if task.status == 'done' then
|
if task.status == 'done' then
|
||||||
return 'x'
|
return icons.done
|
||||||
|
elseif task.status == 'cancelled' then
|
||||||
|
return icons.cancelled
|
||||||
elseif task.status == 'wip' then
|
elseif task.status == 'wip' then
|
||||||
return '>'
|
return icons.wip
|
||||||
elseif task.status == 'blocked' then
|
elseif task.status == 'blocked' then
|
||||||
return '='
|
return icons.blocked
|
||||||
elseif task.priority > 0 then
|
elseif task.priority > 0 then
|
||||||
return '!'
|
return icons.priority
|
||||||
end
|
end
|
||||||
return ' '
|
return icons.pending
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param tasks pending.Task[]
|
---@param tasks pending.Task[]
|
||||||
|
|
@ -204,7 +207,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' or task.status == 'deleted' then
|
if task.status == 'done' or task.status == 'deleted' or task.status == 'cancelled' 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)
|
||||||
|
|
@ -279,7 +282,11 @@ function M.category_view(tasks)
|
||||||
status = task.status,
|
status = task.status,
|
||||||
category = cat,
|
category = cat,
|
||||||
priority = task.priority,
|
priority = task.priority,
|
||||||
overdue = task.status ~= 'done' and task.due ~= nil and parse.is_overdue(task.due) or nil,
|
overdue = task.status ~= 'done'
|
||||||
|
and task.status ~= 'cancelled'
|
||||||
|
and task.due ~= nil
|
||||||
|
and parse.is_overdue(task.due)
|
||||||
|
or nil,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
forge_spans = compute_forge_spans(task, prefix_len),
|
forge_spans = compute_forge_spans(task, prefix_len),
|
||||||
})
|
})
|
||||||
|
|
@ -299,7 +306,7 @@ function M.priority_view(tasks)
|
||||||
local done = {}
|
local done = {}
|
||||||
|
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(tasks) do
|
||||||
if task.status == 'done' then
|
if task.status == 'done' or task.status == 'cancelled' then
|
||||||
table.insert(done, task)
|
table.insert(done, task)
|
||||||
else
|
else
|
||||||
table.insert(pending, task)
|
table.insert(pending, task)
|
||||||
|
|
@ -322,7 +329,7 @@ function M.priority_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
|
||||||
local prefix_len = #prefix + #('- [' .. state .. '] ')
|
local prefix_len = #prefix + #('- [' .. state .. '] ')
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
|
|
@ -334,7 +341,11 @@ 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 ~= 'done' and task.due ~= nil and parse.is_overdue(task.due) or nil,
|
overdue = task.status ~= 'done'
|
||||||
|
and task.status ~= 'cancelled'
|
||||||
|
and task.due ~= nil
|
||||||
|
and parse.is_overdue(task.due)
|
||||||
|
or nil,
|
||||||
show_category = true,
|
show_category = true,
|
||||||
recur = task.recur,
|
recur = task.recur,
|
||||||
forge_ref = task._extra and task._extra._forge_ref or nil,
|
forge_ref = task._extra and task._extra._forge_ref or nil,
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ end, {
|
||||||
used[word] = true
|
used[word] = true
|
||||||
end
|
end
|
||||||
local candidates =
|
local candidates =
|
||||||
{ 'clear', 'overdue', 'today', 'priority', 'done', 'pending', 'wip', 'blocked' }
|
{ 'clear', 'overdue', 'today', 'priority', 'done', 'pending', 'wip', 'blocked', 'cancelled' }
|
||||||
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()
|
||||||
|
|
@ -394,6 +394,10 @@ vim.keymap.set('n', '<Plug>(pending-blocked)', function()
|
||||||
require('pending').toggle_status('blocked')
|
require('pending').toggle_status('blocked')
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-cancelled)', function()
|
||||||
|
require('pending').toggle_status('cancelled')
|
||||||
|
end)
|
||||||
|
|
||||||
vim.keymap.set('n', '<Plug>(pending-priority-up)', function()
|
vim.keymap.set('n', '<Plug>(pending-priority-up)', function()
|
||||||
require('pending').increment_priority()
|
require('pending').increment_priority()
|
||||||
end)
|
end)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue