feat: add <C-a> / <C-x> keymaps for priority increment/decrement (#114)
* 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 `<C-a>` / `<C-x>` 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 `<C-a>` / `<C-x>`. Includes `<Plug>` 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
This commit is contained in:
parent
aa63b9bd6c
commit
6b23e6810e
7 changed files with 106 additions and 15 deletions
|
|
@ -299,6 +299,8 @@ Default buffer-local keys: ~
|
||||||
`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`)
|
||||||
|
`<C-a>` Increment priority (clamped at `max_priority`) (`priority_up`)
|
||||||
|
`<C-x>` Decrement priority (clamped at 0) (`priority_down`)
|
||||||
`J` Move task down within its category (`move_down`)
|
`J` Move task down within its category (`move_down`)
|
||||||
`K` Move task up within its category (`move_up`)
|
`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`)
|
||||||
|
|
@ -413,6 +415,16 @@ 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-priority-up)*
|
||||||
|
<Plug>(pending-priority-up)
|
||||||
|
Increment the priority level for the task under the cursor, clamped
|
||||||
|
at `max_priority`. Default key: `<C-a>`.
|
||||||
|
|
||||||
|
*<Plug>(pending-priority-down)*
|
||||||
|
<Plug>(pending-priority-down)
|
||||||
|
Decrement the priority level for the task under the cursor, clamped
|
||||||
|
at 0. Default key: `<C-x>`.
|
||||||
|
|
||||||
*<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.
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,8 @@ local defaults = {
|
||||||
move_up = 'K',
|
move_up = 'K',
|
||||||
wip = 'gw',
|
wip = 'gw',
|
||||||
blocked = 'gb',
|
blocked = 'gb',
|
||||||
|
priority_up = '<C-a>',
|
||||||
|
priority_down = '<C-x>',
|
||||||
},
|
},
|
||||||
sync = {},
|
sync = {},
|
||||||
icons = {
|
icons = {
|
||||||
|
|
|
||||||
|
|
@ -363,6 +363,12 @@ function M._setup_buf_mappings(bufnr)
|
||||||
blocked = function()
|
blocked = function()
|
||||||
M.toggle_status('blocked')
|
M.toggle_status('blocked')
|
||||||
end,
|
end,
|
||||||
|
priority_up = function()
|
||||||
|
M.increment_priority()
|
||||||
|
end,
|
||||||
|
priority_down = function()
|
||||||
|
M.decrement_priority()
|
||||||
|
end,
|
||||||
undo = function()
|
undo = function()
|
||||||
M.undo_write()
|
M.undo_write()
|
||||||
end,
|
end,
|
||||||
|
|
@ -646,6 +652,56 @@ function M.toggle_priority()
|
||||||
end
|
end
|
||||||
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
|
---@return nil
|
||||||
function M.prompt_date()
|
function M.prompt_date()
|
||||||
local bufnr = buffer.bufnr()
|
local bufnr = buffer.bufnr()
|
||||||
|
|
@ -1032,7 +1088,13 @@ function M.archive(arg)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
confirm(
|
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()
|
function()
|
||||||
local kept = {}
|
local kept = {}
|
||||||
for _, task in ipairs(tasks) do
|
for _, task in ipairs(tasks) do
|
||||||
|
|
|
||||||
|
|
@ -679,11 +679,13 @@ function M.parse_duration_to_days(s)
|
||||||
end
|
end
|
||||||
n = s:match('^(%d+)w$')
|
n = s:match('^(%d+)w$')
|
||||||
if n then
|
if n then
|
||||||
return tonumber(n) --[[@as integer]] * 7
|
return tonumber(n) --[[@as integer]]
|
||||||
|
* 7
|
||||||
end
|
end
|
||||||
n = s:match('^(%d+)m$')
|
n = s:match('^(%d+)m$')
|
||||||
if n then
|
if n then
|
||||||
return tonumber(n) --[[@as integer]] * 30
|
return tonumber(n) --[[@as integer]]
|
||||||
|
* 30
|
||||||
end
|
end
|
||||||
n = s:match('^(%d+)$')
|
n = s:match('^(%d+)$')
|
||||||
if n then
|
if n then
|
||||||
|
|
|
||||||
|
|
@ -67,11 +67,14 @@ local function ensure_sync_id(task)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function create_bucket()
|
local function create_bucket()
|
||||||
local name = util.input({ prompt = 'S3 bucket name: ', default = 'pending.nvim' })
|
local name = util.input({ prompt = 'S3 bucket name (pending.nvim): ' })
|
||||||
if not name or name == '' then
|
if not name then
|
||||||
log.info('s3: bucket creation cancelled')
|
log.info('s3: bucket creation cancelled')
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
if name == '' then
|
||||||
|
name = 'pending.nvim'
|
||||||
|
end
|
||||||
|
|
||||||
local region_cmd = base_cmd()
|
local region_cmd = base_cmd()
|
||||||
vim.list_extend(region_cmd, { 'configure', 'get', 'region' })
|
vim.list_extend(region_cmd, { 'configure', 'get', 'region' })
|
||||||
|
|
@ -84,9 +87,11 @@ local function create_bucket()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local region = util.input({ prompt = 'AWS region: ', default = default_region })
|
local region = util.input({ prompt = 'AWS region (' .. default_region .. '): ' })
|
||||||
if not region or region == '' then
|
if not region then
|
||||||
region = 'us-east-1'
|
region = default_region
|
||||||
|
elseif region == '' then
|
||||||
|
region = default_region
|
||||||
end
|
end
|
||||||
|
|
||||||
local cmd = base_cmd()
|
local cmd = base_cmd()
|
||||||
|
|
@ -98,7 +103,7 @@ local function create_bucket()
|
||||||
local result = util.system(cmd, { text = true })
|
local result = util.system(cmd, { text = true })
|
||||||
if result.code == 0 then
|
if result.code == 0 then
|
||||||
log.info(
|
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
|
.. name
|
||||||
.. '", region = "'
|
.. '", region = "'
|
||||||
.. region
|
.. region
|
||||||
|
|
|
||||||
|
|
@ -328,6 +328,14 @@ 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-priority-up)', function()
|
||||||
|
require('pending').increment_priority()
|
||||||
|
end)
|
||||||
|
|
||||||
|
vim.keymap.set('n', '<Plug>(pending-priority-down)', function()
|
||||||
|
require('pending').decrement_priority()
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ describe('s3', function()
|
||||||
if opts.prompt:find('bucket') then
|
if opts.prompt:find('bucket') then
|
||||||
return 'my-bucket'
|
return 'my-bucket'
|
||||||
end
|
end
|
||||||
return opts.default
|
return ''
|
||||||
end
|
end
|
||||||
local create_args
|
local create_args
|
||||||
util.system = function(args)
|
util.system = function(args)
|
||||||
|
|
@ -212,12 +212,12 @@ describe('s3', function()
|
||||||
assert.truthy(msg and msg:find('bucket created'))
|
assert.truthy(msg and msg:find('bucket created'))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('cancels when user provides empty bucket name', function()
|
it('cancels when user provides nil bucket name', function()
|
||||||
util.input = function(opts)
|
util.input = function(opts)
|
||||||
if opts.prompt:find('bucket') then
|
if opts.prompt:find('bucket') then
|
||||||
return nil
|
return nil
|
||||||
end
|
end
|
||||||
return opts.default
|
return ''
|
||||||
end
|
end
|
||||||
util.system = function(args)
|
util.system = function(args)
|
||||||
if vim.tbl_contains(args, 'get-caller-identity') then
|
if vim.tbl_contains(args, 'get-caller-identity') then
|
||||||
|
|
@ -247,7 +247,7 @@ describe('s3', function()
|
||||||
if opts.prompt:find('region') then
|
if opts.prompt:find('region') then
|
||||||
return 'us-east-1'
|
return 'us-east-1'
|
||||||
end
|
end
|
||||||
return opts.default
|
return ''
|
||||||
end
|
end
|
||||||
local create_args
|
local create_args
|
||||||
util.system = function(args)
|
util.system = function(args)
|
||||||
|
|
@ -281,7 +281,7 @@ describe('s3', function()
|
||||||
if opts.prompt:find('region') then
|
if opts.prompt:find('region') then
|
||||||
return 'eu-west-1'
|
return 'eu-west-1'
|
||||||
end
|
end
|
||||||
return opts.default
|
return ''
|
||||||
end
|
end
|
||||||
local create_args
|
local create_args
|
||||||
util.system = function(args)
|
util.system = function(args)
|
||||||
|
|
@ -311,7 +311,7 @@ describe('s3', function()
|
||||||
if opts.prompt:find('bucket') then
|
if opts.prompt:find('bucket') then
|
||||||
return 'bad-bucket'
|
return 'bad-bucket'
|
||||||
end
|
end
|
||||||
return opts.default
|
return ''
|
||||||
end
|
end
|
||||||
util.system = function(args)
|
util.system = function(args)
|
||||||
if vim.tbl_contains(args, 'get-caller-identity') then
|
if vim.tbl_contains(args, 'get-caller-identity') then
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue