From 9672af7c085e262cc56a478e43c664430aafe11d Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Sun, 8 Mar 2026 20:49:05 -0400 Subject: [PATCH] feat: add / keymaps for priority increment/decrement (#114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(s3): create bucket interactively during auth when unconfigured Problem: when a user runs `:Pending s3 auth` with no bucket configured, auth succeeds but offers no way to create the bucket. The user must manually run `aws s3api create-bucket` and update their config. Solution: add `util.input()` coroutine-aware prompt wrapper and a `create_bucket()` flow in `s3.lua` that prompts for bucket name and region, handles the `us-east-1` LocationConstraint quirk, and logs a config snippet on success. Called automatically from `auth()` when `sync.s3.bucket` is absent. * ci: typing * feat(parse): add `parse_duration_to_days` for duration string conversion Problem: The archive command accepted only a bare integer for days, inconsistent with the `+Nd`/`+Nw`/`+Nm` duration syntax used elsewhere. Solution: Add `parse_duration_to_days()` supporting `Nd`, `Nw`, `Nm`, and bare integers. Returns nil on invalid input for caller error handling. * feat(archive): duration syntax and confirmation prompt Problem: `:Pending archive` accepted only a bare integer for days and silently deleted tasks with no confirmation, risking accidental data loss. Solution: Accept duration strings (`7d`, `3w`, `2m`) via `parse.parse_duration_to_days()`, show a `vim.ui.input` confirmation prompt before removing tasks, and skip the prompt when zero tasks match. * feat: add `` / `` keymaps for priority increment/decrement Problem: Priority could only be cycled with `g!` (0→1→2→3→0), with no way to directly increment or decrement. Solution: Add `adjust_priority()` with clamping at 0 and `max_priority`, exposed as `increment_priority()` / `decrement_priority()` on `` / ``. Includes `` mappings and vimdoc. * fix(s3): use parenthetical defaults in bucket creation prompts Problem: `util.input` with `default` pre-filled the input field, and the success message said "Add to your config" ambiguously. Solution: Show defaults in prompt text as `(default)` instead of pre-filling, and clarify the message to "Add to your pending.nvim config". * ci: format --- doc/pending.txt | 12 ++++++++ lua/pending/config.lua | 2 ++ lua/pending/init.lua | 64 ++++++++++++++++++++++++++++++++++++++++- lua/pending/parse.lua | 6 ++-- lua/pending/sync/s3.lua | 17 +++++++---- plugin/pending.lua | 8 ++++++ spec/s3_spec.lua | 12 ++++---- 7 files changed, 106 insertions(+), 15 deletions(-) diff --git a/doc/pending.txt b/doc/pending.txt index 59da208..739ca88 100644 --- a/doc/pending.txt +++ b/doc/pending.txt @@ -299,6 +299,8 @@ Default buffer-local keys: ~ `gz` 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`) + `` Increment priority (clamped at `max_priority`) (`priority_up`) + `` Decrement priority (clamped at 0) (`priority_down`) `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`) @@ -413,6 +415,16 @@ old keys to `false`: >lua Toggle blocked status for the task under the cursor. If the task is already `blocked`, reverts to `pending`. + *(pending-priority-up)* +(pending-priority-up) + Increment the priority level for the task under the cursor, clamped + at `max_priority`. Default key: ``. + + *(pending-priority-down)* +(pending-priority-down) + Decrement the priority level for the task under the cursor, clamped + at 0. Default key: ``. + *(pending-open-line)* (pending-open-line) Insert a correctly-formatted blank task line below the cursor. diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 599b050..c947b40 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -137,6 +137,8 @@ local defaults = { move_up = 'K', wip = 'gw', blocked = 'gb', + priority_up = '', + priority_down = '', }, sync = {}, icons = { diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 77959c9..02587c2 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -363,6 +363,12 @@ function M._setup_buf_mappings(bufnr) blocked = function() M.toggle_status('blocked') end, + priority_up = function() + M.increment_priority() + end, + priority_down = function() + M.decrement_priority() + end, undo = function() M.undo_write() end, @@ -646,6 +652,56 @@ function M.toggle_priority() end end +---@param delta integer +---@return nil +local function adjust_priority(delta) + 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 + local max = require('pending.config').get().max_priority or 3 + local new_priority = math.max(0, math.min(max, task.priority + delta)) + if new_priority == task.priority then + return + end + s:update(id, { priority = new_priority }) + _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.increment_priority() + adjust_priority(1) +end + +---@return nil +function M.decrement_priority() + adjust_priority(-1) +end + ---@return nil function M.prompt_date() local bufnr = buffer.bufnr() @@ -1032,7 +1088,13 @@ function M.archive(arg) return end confirm( - 'Archive ' .. count .. ' task' .. (count == 1 and '' or 's') .. ' completed/deleted more than ' .. days .. 'd ago?', + 'Archive ' + .. count + .. ' task' + .. (count == 1 and '' or 's') + .. ' completed/deleted more than ' + .. days + .. 'd ago?', function() local kept = {} for _, task in ipairs(tasks) do diff --git a/lua/pending/parse.lua b/lua/pending/parse.lua index e8fdfab..5a705ef 100644 --- a/lua/pending/parse.lua +++ b/lua/pending/parse.lua @@ -679,11 +679,13 @@ function M.parse_duration_to_days(s) end n = s:match('^(%d+)w$') if n then - return tonumber(n) --[[@as integer]] * 7 + return tonumber(n) --[[@as integer]] + * 7 end n = s:match('^(%d+)m$') if n then - return tonumber(n) --[[@as integer]] * 30 + return tonumber(n) --[[@as integer]] + * 30 end n = s:match('^(%d+)$') if n then diff --git a/lua/pending/sync/s3.lua b/lua/pending/sync/s3.lua index d3ad5b5..373052a 100644 --- a/lua/pending/sync/s3.lua +++ b/lua/pending/sync/s3.lua @@ -67,11 +67,14 @@ local function ensure_sync_id(task) end local function create_bucket() - local name = util.input({ prompt = 'S3 bucket name: ', default = 'pending.nvim' }) - if not name or name == '' then + local name = util.input({ prompt = 'S3 bucket name (pending.nvim): ' }) + if not name then log.info('s3: bucket creation cancelled') return end + if name == '' then + name = 'pending.nvim' + end local region_cmd = base_cmd() vim.list_extend(region_cmd, { 'configure', 'get', 'region' }) @@ -84,9 +87,11 @@ local function create_bucket() end end - local region = util.input({ prompt = 'AWS region: ', default = default_region }) - if not region or region == '' then - region = 'us-east-1' + local region = util.input({ prompt = 'AWS region (' .. default_region .. '): ' }) + if not region then + region = default_region + elseif region == '' then + region = default_region end local cmd = base_cmd() @@ -98,7 +103,7 @@ local function create_bucket() local result = util.system(cmd, { text = true }) if result.code == 0 then log.info( - 's3: bucket created. Add to your config:\n sync = { s3 = { bucket = "' + 's3: bucket created. Add to your pending.nvim config:\n sync = { s3 = { bucket = "' .. name .. '", region = "' .. region diff --git a/plugin/pending.lua b/plugin/pending.lua index 2d0d2be..084f162 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -328,6 +328,14 @@ vim.keymap.set('n', '(pending-blocked)', function() require('pending').toggle_status('blocked') end) +vim.keymap.set('n', '(pending-priority-up)', function() + require('pending').increment_priority() +end) + +vim.keymap.set('n', '(pending-priority-down)', function() + require('pending').decrement_priority() +end) + vim.keymap.set('n', '(pending-filter)', function() vim.ui.input({ prompt = 'Filter: ' }, function(input) if input then diff --git a/spec/s3_spec.lua b/spec/s3_spec.lua index c1b4c68..a9b1dbd 100644 --- a/spec/s3_spec.lua +++ b/spec/s3_spec.lua @@ -179,7 +179,7 @@ describe('s3', function() if opts.prompt:find('bucket') then return 'my-bucket' end - return opts.default + return '' end local create_args util.system = function(args) @@ -212,12 +212,12 @@ describe('s3', function() assert.truthy(msg and msg:find('bucket created')) end) - it('cancels when user provides empty bucket name', function() + it('cancels when user provides nil bucket name', function() util.input = function(opts) if opts.prompt:find('bucket') then return nil end - return opts.default + return '' end util.system = function(args) if vim.tbl_contains(args, 'get-caller-identity') then @@ -247,7 +247,7 @@ describe('s3', function() if opts.prompt:find('region') then return 'us-east-1' end - return opts.default + return '' end local create_args util.system = function(args) @@ -281,7 +281,7 @@ describe('s3', function() if opts.prompt:find('region') then return 'eu-west-1' end - return opts.default + return '' end local create_args util.system = function(args) @@ -311,7 +311,7 @@ describe('s3', function() if opts.prompt:find('bucket') then return 'bad-bucket' end - return opts.default + return '' end util.system = function(args) if vim.tbl_contains(args, 'get-caller-identity') then