From d35f34d8e0fbd5cd2711c6a67f11f0a8b402bd1d Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:08:29 -0400 Subject: [PATCH] feat(complete): add metadata completion for `:Pending add` (#144) Problem: `:Pending add` had no tab completion for inline metadata tokens, unlike `:Pending edit` which already completed `due:`, `rec:`, and `cat:` values. Solution: Add `complete_add()` that handles `due:`, `rec:`, and `cat:` prefix matching with the same value sources used by `complete_edit()`, and wire it into the command completion dispatcher. --- plugin/pending.lua | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/plugin/pending.lua b/plugin/pending.lua index 394c064..63caadd 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -80,6 +80,65 @@ local function filter_candidates(lead, candidates) end, candidates) end +---@param arg_lead string +---@return string[] +local function complete_add(arg_lead) + local cfg = require('pending.config').get() + local dk = cfg.date_syntax or 'due' + local rk = cfg.recur_syntax or 'rec' + local ck = cfg.category_syntax or 'cat' + + local prefix = arg_lead:match('^(' .. vim.pesc(dk) .. ':)(.*)$') + if prefix then + local after_colon = arg_lead:sub(#prefix + 1) + local result = {} + for _, d in ipairs(edit_date_values()) do + if d:find(after_colon, 1, true) == 1 then + table.insert(result, prefix .. d) + end + end + return result + end + + local rec_prefix = arg_lead:match('^(' .. vim.pesc(rk) .. ':)(.*)$') + if rec_prefix then + local after_colon = arg_lead:sub(#rec_prefix + 1) + local result = {} + for _, p in ipairs(edit_recur_values()) do + if p:find(after_colon, 1, true) == 1 then + table.insert(result, rec_prefix .. p) + end + end + return result + end + + local cat_prefix = arg_lead:match('^(' .. vim.pesc(ck) .. ':)(.*)$') + if cat_prefix then + local after_colon = arg_lead:sub(#cat_prefix + 1) + local store = require('pending.store') + local s = store.new(store.resolve_path()) + s:load() + local seen = {} + local cats = {} + for _, task in ipairs(s:active_tasks()) do + if task.category and not seen[task.category] then + seen[task.category] = true + table.insert(cats, task.category) + end + end + table.sort(cats) + local result = {} + for _, c in ipairs(cats) do + if c:find(after_colon, 1, true) == 1 then + table.insert(result, cat_prefix .. c) + end + end + return result + end + + return {} +end + ---@param arg_lead string ---@param cmd_line string ---@return string[] @@ -207,6 +266,9 @@ end, { end return filtered end + if cmd_line:match('^Pending%s+add%s') then + return complete_add(arg_lead) + end if cmd_line:match('^Pending%s+archive%s') then return filter_candidates(arg_lead, { '7d', '2w', '30d', '3m', '6m', '1y' }) end