From 7f0bd43b34e6c79b6c96f8fa76d33ef554dbc159 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 22:01:57 -0500 Subject: [PATCH 1/9] feat(buffer): preserve category fold state across re-renders Problem: pressing :w, toggling priority, or any other operation that calls buffer.render() reset foldlevel = 99, causing all manually collapsed category sections to snap back open. Solution: snapshot which categories are folded (per window) before nvim_buf_set_lines destroys the fold tree, then restore them after fold options are re-applied by calling normal! zc on each previously closed header line. State persists across all render call sites within a session. --- lua/pending/buffer.lua | 48 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index 2036357..861aca3 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -12,6 +12,8 @@ local task_ns = vim.api.nvim_create_namespace('pending') local current_view = nil ---@type pending.LineMeta[] local _meta = {} +---@type table> +local _fold_state = {} ---@return pending.LineMeta[] function M.meta() @@ -148,6 +150,50 @@ local function setup_highlights() vim.api.nvim_set_hl(0, 'PendingPriority', { link = 'DiagnosticWarn', default = true }) end +local function snapshot_folds(bufnr) + if current_view ~= 'category' then + return + end + for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do + local state = {} + vim.api.nvim_win_call(winid, function() + for lnum, m in ipairs(_meta) do + if m.type == 'header' and m.category then + if vim.fn.foldclosed(lnum) ~= -1 then + state[m.category] = true + end + end + end + end) + _fold_state[winid] = state + end +end + +local function restore_folds(bufnr) + if current_view ~= 'category' then + return + end + for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do + local state = _fold_state[winid] + if not state or next(state) == nil then + goto continue + end + vim.api.nvim_win_call(winid, function() + vim.cmd('normal! zx') + local saved = vim.api.nvim_win_get_cursor(0) + for lnum, m in ipairs(_meta) do + if m.type == 'header' and m.category and state[m.category] then + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + vim.cmd('normal! zc') + end + end + vim.api.nvim_win_set_cursor(0, saved) + end) + _fold_state[winid] = nil + ::continue:: + end +end + ---@param bufnr? integer function M.render(bufnr) bufnr = bufnr or task_bufnr @@ -167,6 +213,7 @@ function M.render(bufnr) _meta = line_meta + snapshot_folds(bufnr) vim.bo[bufnr].modifiable = true vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.bo[bufnr].modified = false @@ -185,6 +232,7 @@ function M.render(bufnr) vim.wo[winid].foldenable = false end end + restore_folds(bufnr) end function M.toggle_view() From 8f9052bad1c70dcbd7ee1cf2ae300b4c5c60c4eb Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 22:25:00 -0500 Subject: [PATCH 2/9] ci: format --- lua/pending/buffer.lua | 28 +++++++++++++--------------- spec/parse_spec.lua | 14 ++++++++++---- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index 861aca3..7c730e6 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -175,22 +175,20 @@ local function restore_folds(bufnr) end for _, winid in ipairs(vim.fn.win_findbuf(bufnr)) do local state = _fold_state[winid] - if not state or next(state) == nil then - goto continue - end - vim.api.nvim_win_call(winid, function() - vim.cmd('normal! zx') - local saved = vim.api.nvim_win_get_cursor(0) - for lnum, m in ipairs(_meta) do - if m.type == 'header' and m.category and state[m.category] then - vim.api.nvim_win_set_cursor(0, { lnum, 0 }) - vim.cmd('normal! zc') + if state and next(state) ~= nil then + vim.api.nvim_win_call(winid, function() + vim.cmd('normal! zx') + local saved = vim.api.nvim_win_get_cursor(0) + for lnum, m in ipairs(_meta) do + if m.type == 'header' and m.category and state[m.category] then + vim.api.nvim_win_set_cursor(0, { lnum, 0 }) + vim.cmd('normal! zc') + end end - end - vim.api.nvim_win_set_cursor(0, saved) - end) - _fold_state[winid] = nil - ::continue:: + vim.api.nvim_win_set_cursor(0, saved) + end) + _fold_state[winid] = nil + end end end diff --git a/spec/parse_spec.lua b/spec/parse_spec.lua index b4442e9..ca8047c 100644 --- a/spec/parse_spec.lua +++ b/spec/parse_spec.lua @@ -96,7 +96,10 @@ describe('parse', function() it('resolves due:+2d to today plus 2 days', function() local today = os.date('*t') --[[@as osdate]] - local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 2 })) + local expected = os.date( + '%Y-%m-%d', + os.time({ year = today.year, month = today.month, day = today.day + 2 }) + ) local desc, meta = parse.body('Task due:+2d') assert.are.equal('Task', desc) assert.are.equal(expected, meta.due) @@ -123,7 +126,10 @@ describe('parse', function() it("returns today + 3 days for '+3d'", function() local today = os.date('*t') --[[@as osdate]] - local expected = os.date('%Y-%m-%d', os.time({ year = today.year, month = today.month, day = today.day + 3 })) + local expected = os.date( + '%Y-%m-%d', + os.time({ year = today.year, month = today.month, day = today.day + 3 }) + ) local result = parse.resolve_date('+3d') assert.are.equal(expected, result) end) @@ -139,12 +145,12 @@ describe('parse', function() assert.truthy(result:match('^%d%d%d%d%-%d%d%-%d%d$')) end) - it("returns nil for garbage input", function() + it('returns nil for garbage input', function() local result = parse.resolve_date('notadate') assert.is_nil(result) end) - it("returns nil for empty string", function() + it('returns nil for empty string', function() local result = parse.resolve_date('') assert.is_nil(result) end) From 437944d441bf7e9cebb0e90cfa5a6ea9c28cf4d4 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 22:31:37 -0500 Subject: [PATCH 3/9] fix(diff): cast tonumber result to integer Problem: LuaLS infers priority as integer from the = 0 initialiser but tonumber returns number?, causing a cast-local-type diagnostic. Solution: inline --[[@as integer]] cast after the tonumber call. --- lua/pending/diff.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index 1107b31..607aef6 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -37,7 +37,7 @@ function M.parse_buffer(lines) local prio_str = stripped:match('^%[(%d+)%] ') local priority = 0 if prio_str then - priority = tonumber(prio_str) + priority = tonumber(prio_str) --[[@as integer]] stripped = stripped:sub(#prio_str + 4) end local description, metadata = parse.body(stripped) From cfdffdadfeee5fd0c858eb8102f6ad467138a5fe Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Tue, 24 Feb 2026 22:31:43 -0500 Subject: [PATCH 4/9] test: update priority format assertions from ! to [N] Problem: fc4a47a changed the priority display format from '! ' to '[N] ' in views.lua and diff.lua but left two existing test assertions and their descriptions using the old format, causing both to fail. Solution: update the input line in diff parse_buffer test, update the expected string and description names in views category_view test, and rename the diff.apply description to match the new idiom. --- spec/diff_spec.lua | 4 ++-- spec/views_spec.lua | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index b8fcfd9..7a73c5d 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -27,7 +27,7 @@ describe('diff', function() local lines = { 'School', '/1/ Do homework', - '/2/ ! Read chapter 5', + '/2/ [1] Read chapter 5', '', 'Errands', '/3/ Buy groceries', @@ -168,7 +168,7 @@ describe('diff', function() assert.is_nil(task.due) end) - it('clears priority when ! is removed from buffer line', function() + it('clears priority when [N] is removed from buffer line', function() store.add({ description = 'Task name', priority = 1 }) store.save() local lines = { diff --git a/spec/views_spec.lua b/spec/views_spec.lua index 9ba12f9..0f5e5e5 100644 --- a/spec/views_spec.lua +++ b/spec/views_spec.lua @@ -116,7 +116,7 @@ describe('views', function() assert.are.equal('/1/ My task', task_line) end) - it('formats priority task lines as /ID/ ! description', function() + it('formats priority task lines as /ID/ [N] description', function() store.add({ description = 'Important', category = 'Inbox', priority = 1 }) local lines, meta = views.category_view(store.active_tasks()) local task_line @@ -125,7 +125,7 @@ describe('views', function() task_line = lines[i] end end - assert.are.equal('/1/ ! Important', task_line) + assert.are.equal('/1/ [1] Important', task_line) end) it('sets LineMeta type=header for header lines with correct category', function() From 8e16744ebec6d2d8ed38ba4916250dd808801821 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 24 Feb 2026 22:33:13 -0500 Subject: [PATCH 5/9] test: add missing coverage (#19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: add top-priority missing test coverage Problem: several critical code paths had zero test coverage — parse.resolve_date (relative date resolution), store.snapshot (foundation of the undo stack), and the diff.apply invariant that unchanged tasks do not get their modified timestamp bumped. The diff.apply due/priority clearing paths were also untested. Solution: add six targeted test blocks across parse_spec, store_spec, and diff_spec: resolve_date happy/failure paths, parse.body with relative due tokens, snapshot copy-semantics and deleted-task exclusion, diff unchanged-modified invariant, due cleared on removal, priority cleared on ! removal. * test: add second batch of missing test coverage Problem: six more gaps from the audit remained after the first batch — archive persistence verification, diff modified-on-rename, parse_buffer inline cat:/due: token parsing, and store.update immutability invariants. Solution: add six it() blocks across archive_spec, diff_spec, and store_spec: archive unload/reload persistence check, modified timestamp updated on description change, inline cat: overrides header category, inline due: token parsed from buffer line, id/entry fields immutable under store.update, and end timestamp not overwritten on second completion. --- spec/archive_spec.lua | 9 +++++++++ spec/diff_spec.lua | 38 ++++++++++++++++++++++++++++++++++++++ spec/store_spec.lua | 21 +++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/spec/archive_spec.lua b/spec/archive_spec.lua index a71eee8..df1a912 100644 --- a/spec/archive_spec.lua +++ b/spec/archive_spec.lua @@ -128,4 +128,13 @@ describe('archive', function() assert.is_true(descs['Keep pending']) assert.is_true(descs['Keep recent done']) end) + + it('persists archived tasks to disk after unload/reload', function() + local t = store.add({ description = 'Archived task' }) + store.update(t.id, { status = 'done', ['end'] = '2020-01-01T00:00:00Z' }) + pending.archive() + store.unload() + store.load() + assert.are.equal(0, #store.active_tasks()) + end) end) diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index 7a73c5d..3073879 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -57,6 +57,28 @@ describe('diff', function() assert.is_nil(result[2].id) assert.are.equal('New task here', result[2].description) end) + + it('inline cat: token overrides header category', function() + local lines = { + 'Inbox', + '/1/ Buy milk cat:Work', + } + local result = diff.parse_buffer(lines) + assert.are.equal(2, #result) + assert.are.equal('task', result[2].type) + assert.are.equal('Work', result[2].category) + end) + + it('inline due: token is parsed', function() + local lines = { + 'Inbox', + '/1/ Buy milk due:2026-03-15', + } + local result = diff.parse_buffer(lines) + assert.are.equal(2, #result) + assert.are.equal('task', result[2].type) + assert.are.equal('2026-03-15', result[2].due) + end) end) describe('apply', function() @@ -107,6 +129,22 @@ describe('diff', function() assert.are.equal('Renamed', task.description) end) + it('updates modified when description is renamed', function() + local t = store.add({ description = 'Original', category = 'Inbox' }) + t.modified = '2020-01-01T00:00:00Z' + store.save() + local lines = { + 'Inbox', + '/1/ Renamed', + } + diff.apply(lines) + store.unload() + store.load() + local task = store.get(1) + assert.are.equal('Renamed', task.description) + assert.is_not.equal('2020-01-01T00:00:00Z', task.modified) + end) + it('handles duplicate ids as copies', function() store.add({ description = 'Original' }) store.save() diff --git a/spec/store_spec.lua b/spec/store_spec.lua index 930fbc0..25b8b7c 100644 --- a/spec/store_spec.lua +++ b/spec/store_spec.lua @@ -121,6 +121,27 @@ describe('store', function() local updated = store.get(t.id) assert.is_not_nil(updated['end']) end) + + it('does not overwrite id or entry', function() + store.load() + local t = store.add({ description = 'Immutable fields' }) + local original_id = t.id + local original_entry = t.entry + store.update(t.id, { id = 999, entry = 'x' }) + local updated = store.get(original_id) + assert.are.equal(original_id, updated.id) + assert.are.equal(original_entry, updated.entry) + end) + + it('does not overwrite end on second completion', function() + store.load() + local t = store.add({ description = 'Complete twice' }) + store.update(t.id, { status = 'done', ['end'] = '2026-01-15T10:00:00Z' }) + local first_end = store.get(t.id)['end'] + store.update(t.id, { status = 'done' }) + local task = store.get(t.id) + assert.are.equal(first_end, task['end']) + end) end) describe('delete', function() From 5db242a9cfa1043522baedc7fb841fa244fdca00 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Tue, 24 Feb 2026 23:21:55 -0500 Subject: [PATCH 6/9] refactor: adopt markdown-style checkbox buffer format (#20) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(config): change default category from Inbox to Todo * refactor(views): adopt markdown checkbox line format Problem: task lines used an opaque /ID/ [N] prefix format that was hard to read and inconsistent between category and priority views. Header lines had no visual marker distinguishing them from tasks. Solution: render headers as '## Cat', task lines as '/ID/- [x|!| ] description'. State encoding: [x]=done, [!]=urgent, [ ]=pending. Both views use the same construction. * refactor(diff): parse and reconcile markdown checkbox format Problem: parse_buffer matched the old ' text' indent pattern and detected headers via '^%S'. Priority was read from a '[N] ' prefix. apply() never reconciled status changes written into the buffer. Solution: match '- [.] text' for tasks and '^## ' for headers. Extract state char to derive priority (! -> 1) and status (x -> done). apply() now reconciles status from the buffer, setting/clearing 'end' timestamps — enabling the oil-style edit-checkbox-then-:w workflow. * refactor(buffer): update syntax, extmarks, and render for checkbox format Problem: syntax patterns matched the old indent/[N] format; right_align virtual text produced a broken layout in narrow windows; the done strikethrough skipped past the ' ' indent leaving '- [x] ' unstyled; render() added undo history entries so 'u' could undo a re-render. Solution: update taskHeader/taskLine patterns for '## '/'- [.]'; rename taskPriority -> taskCheckbox matching '[!]'; switch virt_text_pos to 'eol'; drop the +2 col_start offset so strikethrough covers '- [x] '; guard nvim_buf_set_lines with undolevels=-1 so renders are not undoable. Also fix open_line to insert '- [ ] ' and position cursor at col 6. * refactor(init): replace multi-level priority with binary toggle Problem: / overrode Vim's native number increment and the visual g/g variants added complexity for marginal value. toggle_complete() left the cursor on the wrong line after re-render. Solution: remove change_priority/change_priority_visual; add toggle_priority() (0<->1) mapped to '!', with cursor-follow after render matching the pattern already used in priority toggle. Add cursor-follow to toggle_complete() for the same reason. Update plugin plugs (priority-up/down -> priority) and add 'due'/'undo' to the :Pending completion list. Update help text accordingly. * feat(buffer): reflect current view in buffer name Problem: no way to tell at a glance which view (category vs priority) is active — the buffer was always named 'pending://'. Solution: update the buffer name to 'pending://category' or 'pending://priority' on every render, so the view is visible in the statusline/tabline without any extra UI. --- lua/pending/buffer.lua | 21 ++++++++------ lua/pending/config.lua | 2 +- lua/pending/diff.lua | 32 ++++++++++++++-------- lua/pending/init.lua | 58 ++++++++------------------------------- lua/pending/views.lua | 12 ++++---- plugin/pending.lua | 10 ++----- spec/diff_spec.lua | 62 +++++++++++++++++++++--------------------- spec/store_spec.lua | 2 +- spec/views_spec.lua | 24 ++++++++-------- syntax/pending.vim | 8 +++--- 10 files changed, 101 insertions(+), 130 deletions(-) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index 7c730e6..8f9fb59 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -58,9 +58,9 @@ local function setup_syntax(bufnr) vim.cmd([[ syntax clear syntax match taskId /^\/\d\+\// conceal - syntax match taskHeader /^\S.*$/ contains=taskId - syntax match taskPriority /\[\d\+\] / contained containedin=taskLine - syntax match taskLine /^\/\d\+\/ .*$/ contains=taskId,taskPriority + syntax match taskHeader /^## .*$/ contains=taskId + syntax match taskCheckbox /\[!\]/ contained containedin=taskLine + syntax match taskLine /^\/\d\+\/- \[.\] .*$/ contains=taskId,taskCheckbox ]]) end) end @@ -74,8 +74,8 @@ function M.open_line(above) local row = vim.api.nvim_win_get_cursor(0)[1] local insert_row = above and (row - 1) or row vim.bo[bufnr].modifiable = true - vim.api.nvim_buf_set_lines(bufnr, insert_row, insert_row, false, { ' ' }) - vim.api.nvim_win_set_cursor(0, { insert_row + 1, 2 }) + vim.api.nvim_buf_set_lines(bufnr, insert_row, insert_row, false, { '- [ ] ' }) + vim.api.nvim_win_set_cursor(0, { insert_row + 1, 6 }) vim.cmd('startinsert!') end @@ -115,18 +115,18 @@ local function apply_extmarks(bufnr, line_meta) if virt_text then vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { virt_text = virt_text, - virt_text_pos = 'right_align', + virt_text_pos = 'eol', }) end elseif m.due then vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { virt_text = { { m.due, due_hl } }, - virt_text_pos = 'right_align', + virt_text_pos = 'eol', }) end if m.status == 'done' 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+/')) + 2 or 0 + local col_start = line:find('/%d+/') and select(2, line:find('/%d+/')) or 0 vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, col_start, { end_col = #line, hl_group = 'PendingDone', @@ -200,6 +200,7 @@ function M.render(bufnr) end current_view = current_view or config.get().default_view + vim.api.nvim_buf_set_name(bufnr, 'pending://' .. current_view) local tasks = store.active_tasks() local lines, line_meta @@ -213,8 +214,11 @@ function M.render(bufnr) snapshot_folds(bufnr) vim.bo[bufnr].modifiable = true + local saved = vim.bo[bufnr].undolevels + vim.bo[bufnr].undolevels = -1 vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.bo[bufnr].modified = false + vim.bo[bufnr].undolevels = saved setup_syntax(bufnr) apply_extmarks(bufnr, line_meta) @@ -261,7 +265,6 @@ function M.open() end task_bufnr = vim.api.nvim_create_buf(true, false) - vim.api.nvim_buf_set_name(task_bufnr, 'pending://') set_buf_options(task_bufnr) vim.api.nvim_set_current_buf(task_bufnr) diff --git a/lua/pending/config.lua b/lua/pending/config.lua index d137acb..2e647e4 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -18,7 +18,7 @@ local M = {} local defaults = { data_path = vim.fn.stdpath('data') .. '/pending/tasks.json', default_view = 'category', - default_category = 'Inbox', + default_category = 'Todo', date_format = '%b %d', date_syntax = 'due', category_order = {}, diff --git a/lua/pending/diff.lua b/lua/pending/diff.lua index 607aef6..85f083c 100644 --- a/lua/pending/diff.lua +++ b/lua/pending/diff.lua @@ -7,6 +7,7 @@ local store = require('pending.store') ---@field id? integer ---@field description? string ---@field priority? integer +---@field status? string ---@field category? string ---@field due? string ---@field lnum integer @@ -26,20 +27,17 @@ function M.parse_buffer(lines) local current_category = nil for i, line in ipairs(lines) do - local id, body = line:match('^/(%d+)/( .+)$') + local id, body = line:match('^/(%d+)/(- %[.%] .*)$') if not id then - body = line:match('^( .+)$') + body = line:match('^(- %[.%] .*)$') end if line == '' then table.insert(result, { type = 'blank', lnum = i }) elseif id or body then - local stripped = body:match('^ (.+)$') or body - local prio_str = stripped:match('^%[(%d+)%] ') - local priority = 0 - if prio_str then - priority = tonumber(prio_str) --[[@as integer]] - stripped = stripped:sub(#prio_str + 4) - end + local stripped = body:match('^- %[.%] (.*)$') or body + local state_char = body:match('^- %[(.-)%]') or ' ' + local priority = state_char == '!' and 1 or 0 + local status = state_char == 'x' and 'done' or 'pending' local description, metadata = parse.body(stripped) if description and description ~= '' then table.insert(result, { @@ -47,14 +45,15 @@ function M.parse_buffer(lines) id = id and tonumber(id) or nil, description = description, priority = priority, + status = status, category = metadata.cat or current_category or config.get().default_category, due = metadata.due, lnum = i, }) end - elseif line:match('^%S') then - current_category = line - table.insert(result, { type = 'header', category = line, lnum = i }) + elseif line:match('^## (.+)$') then + current_category = line:match('^## (.+)$') + table.insert(result, { type = 'header', category = current_category, lnum = i }) end end @@ -113,6 +112,15 @@ function M.apply(lines) task.due = entry.due changed = true end + if entry.status and task.status ~= entry.status then + task.status = entry.status + if entry.status == 'done' then + task['end'] = now + else + task['end'] = nil + end + changed = true + end if task.order ~= order_counter then task.order = order_counter changed = true diff --git a/lua/pending/init.lua b/lua/pending/init.lua index 1593bc5..ec69d89 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -52,17 +52,8 @@ function M._setup_buf_mappings(bufnr) vim.keymap.set('n', 'g?', function() M.show_help() end, opts) - vim.keymap.set('n', '', function() - M.change_priority(1) - end, opts) - vim.keymap.set('n', '', function() - M.change_priority(-1) - end, opts) - vim.keymap.set('v', 'g', function() - M.change_priority_visual(1) - end, opts) - vim.keymap.set('v', 'g', function() - M.change_priority_visual(-1) + vim.keymap.set('n', '!', function() + M.toggle_priority() end, opts) vim.keymap.set('n', 'D', function() M.prompt_date() @@ -126,10 +117,15 @@ function M.toggle_complete() end store.save() 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 delta integer -function M.change_priority(delta) +function M.toggle_priority() local bufnr = buffer.bufnr() if not bufnr then return @@ -147,7 +143,7 @@ function M.change_priority(delta) if not task then return end - local new_priority = math.max(0, task.priority + delta) + local new_priority = task.priority > 0 and 0 or 1 store.update(id, { priority = new_priority }) store.save() buffer.render(bufnr) @@ -159,33 +155,6 @@ function M.change_priority(delta) end end ----@param delta integer -function M.change_priority_visual(delta) - local bufnr = buffer.bufnr() - if not bufnr then - return - end - local start_row = vim.fn.line("'<") - local end_row = vim.fn.line("'>") - local meta = buffer.meta() - local changed = false - for row = start_row, end_row do - local m = meta[row] - if m and m.type == 'task' and m.id then - local task = store.get(m.id) - if task then - local new_priority = math.max(0, task.priority + delta) - store.update(m.id, { priority = new_priority }) - changed = true - end - end - end - if changed then - store.save() - buffer.render(bufnr) - end -end - function M.prompt_date() local bufnr = buffer.bufnr() if not bufnr then @@ -342,10 +311,7 @@ function M.show_help() '', ' Toggle complete/uncomplete', ' Switch category/priority view', - ' Raise priority level', - ' Lower priority level', - 'g Raise priority for visual selection', - 'g Lower priority for visual selection', + '! Toggle urgent', 'D Set due date', 'U Undo last write', 'o / O Add new task line', @@ -371,7 +337,7 @@ function M.show_help() '', 'Highlights:', ' PendingOverdue overdue tasks (red)', - ' PendingPriority [N] priority prefix', + ' PendingPriority [!] urgent tasks', '', 'Press q or to close', } diff --git a/lua/pending/views.lua b/lua/pending/views.lua index 84567e9..7bcfaca 100644 --- a/lua/pending/views.lua +++ b/lua/pending/views.lua @@ -125,7 +125,7 @@ function M.category_view(tasks) table.insert(lines, '') table.insert(meta, { type = 'blank' }) end - table.insert(lines, cat) + table.insert(lines, '## ' .. cat) table.insert(meta, { type = 'header', category = cat }) local all = {} @@ -138,9 +138,8 @@ function M.category_view(tasks) for _, task in ipairs(all) do local prefix = '/' .. task.id .. '/' - local indent = ' ' - local prio = task.priority > 0 and ('[' .. task.priority .. '] ') or '' - local line = prefix .. indent .. prio .. task.description + local state = task.status == 'done' and 'x' or (task.priority > 0 and '!' or ' ') + local line = prefix .. '- [' .. state .. '] ' .. task.description table.insert(lines, line) table.insert(meta, { type = 'task', @@ -189,9 +188,8 @@ function M.priority_view(tasks) for _, task in ipairs(all) do local prefix = '/' .. task.id .. '/' - local indent = ' ' - local prio = task.priority == 1 and '! ' or '' - local line = prefix .. indent .. prio .. task.description + local state = task.status == 'done' and 'x' or (task.priority > 0 and '!' or ' ') + local line = prefix .. '- [' .. state .. '] ' .. task.description table.insert(lines, line) table.insert(meta, { type = 'task', diff --git a/plugin/pending.lua b/plugin/pending.lua index 56dedc3..465ee65 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -8,7 +8,7 @@ vim.api.nvim_create_user_command('Pending', function(opts) end, { nargs = '*', complete = function(arg_lead, cmd_line) - local subcmds = { 'add', 'sync', 'archive' } + local subcmds = { 'add', 'sync', 'archive', 'due', 'undo' } if not cmd_line:match('^Pending%s+%S') then return vim.tbl_filter(function(s) return s:find(arg_lead, 1, true) == 1 @@ -30,12 +30,8 @@ vim.keymap.set('n', '(pending-view)', function() require('pending.buffer').toggle_view() end) -vim.keymap.set('n', '(pending-priority-up)', function() - require('pending').change_priority(1) -end) - -vim.keymap.set('n', '(pending-priority-down)', function() - require('pending').change_priority(-1) +vim.keymap.set('n', '(pending-priority)', function() + require('pending').toggle_priority() end) vim.keymap.set('n', '(pending-date)', function() diff --git a/spec/diff_spec.lua b/spec/diff_spec.lua index 3073879..fda2165 100644 --- a/spec/diff_spec.lua +++ b/spec/diff_spec.lua @@ -25,12 +25,12 @@ describe('diff', function() describe('parse_buffer', function() it('parses headers and tasks', function() local lines = { - 'School', - '/1/ Do homework', - '/2/ [1] Read chapter 5', + '## School', + '/1/- [ ] Do homework', + '/2/- [!] Read chapter 5', '', - 'Errands', - '/3/ Buy groceries', + '## Errands', + '/3/- [ ] Buy groceries', } local result = diff.parse_buffer(lines) assert.are.equal(6, #result) @@ -48,8 +48,8 @@ describe('diff', function() it('handles new tasks without ids', function() local lines = { - 'Inbox', - ' New task here', + '## Inbox', + '- [ ] New task here', } local result = diff.parse_buffer(lines) assert.are.equal(2, #result) @@ -60,8 +60,8 @@ describe('diff', function() it('inline cat: token overrides header category', function() local lines = { - 'Inbox', - '/1/ Buy milk cat:Work', + '## Inbox', + '/1/- [ ] Buy milk cat:Work', } local result = diff.parse_buffer(lines) assert.are.equal(2, #result) @@ -71,8 +71,8 @@ describe('diff', function() it('inline due: token is parsed', function() local lines = { - 'Inbox', - '/1/ Buy milk due:2026-03-15', + '## Inbox', + '/1/- [ ] Buy milk due:2026-03-15', } local result = diff.parse_buffer(lines) assert.are.equal(2, #result) @@ -84,9 +84,9 @@ describe('diff', function() describe('apply', function() it('creates new tasks from buffer lines', function() local lines = { - 'Inbox', - ' First task', - ' Second task', + '## Inbox', + '- [ ] First task', + '- [ ] Second task', } diff.apply(lines) store.unload() @@ -102,8 +102,8 @@ describe('diff', function() store.add({ description = 'Delete me' }) store.save() local lines = { - 'Inbox', - '/1/ Keep me', + '## Inbox', + '/1/- [ ] Keep me', } diff.apply(lines) store.unload() @@ -119,8 +119,8 @@ describe('diff', function() store.add({ description = 'Original' }) store.save() local lines = { - 'Inbox', - '/1/ Renamed', + '## Inbox', + '/1/- [ ] Renamed', } diff.apply(lines) store.unload() @@ -134,8 +134,8 @@ describe('diff', function() t.modified = '2020-01-01T00:00:00Z' store.save() local lines = { - 'Inbox', - '/1/ Renamed', + '## Inbox', + '/1/- [ ] Renamed', } diff.apply(lines) store.unload() @@ -149,9 +149,9 @@ describe('diff', function() store.add({ description = 'Original' }) store.save() local lines = { - 'Inbox', - '/1/ Original', - '/1/ Copy of original', + '## Inbox', + '/1/- [ ] Original', + '/1/- [ ] Copy of original', } diff.apply(lines) store.unload() @@ -164,8 +164,8 @@ describe('diff', function() store.add({ description = 'Moving task', category = 'Inbox' }) store.save() local lines = { - 'Work', - '/1/ Moving task', + '## Work', + '/1/- [ ] Moving task', } diff.apply(lines) store.unload() @@ -178,8 +178,8 @@ describe('diff', function() store.add({ description = 'Stable task', category = 'Inbox' }) store.save() local lines = { - 'Inbox', - '/1/ Stable task', + '## Inbox', + '/1/- [ ] Stable task', } diff.apply(lines) store.unload() @@ -196,8 +196,8 @@ describe('diff', function() store.add({ description = 'Pay bill', due = '2026-03-15' }) store.save() local lines = { - 'Inbox', - '/1/ Pay bill', + '## Inbox', + '/1/- [ ] Pay bill', } diff.apply(lines) store.unload() @@ -210,8 +210,8 @@ describe('diff', function() store.add({ description = 'Task name', priority = 1 }) store.save() local lines = { - 'Inbox', - '/1/ Task name', + '## Inbox', + '/1/- [ ] Task name', } diff.apply(lines) store.unload() diff --git a/spec/store_spec.lua b/spec/store_spec.lua index 25b8b7c..bb6266d 100644 --- a/spec/store_spec.lua +++ b/spec/store_spec.lua @@ -92,7 +92,7 @@ describe('store', function() assert.are.equal(1, t1.id) assert.are.equal(2, t2.id) assert.are.equal('pending', t1.status) - assert.are.equal('Inbox', t1.category) + assert.are.equal('Todo', t1.category) end) it('uses provided category', function() diff --git a/spec/views_spec.lua b/spec/views_spec.lua index 0f5e5e5..4d91e06 100644 --- a/spec/views_spec.lua +++ b/spec/views_spec.lua @@ -27,7 +27,7 @@ describe('views', function() store.add({ description = 'Task A', category = 'Work' }) store.add({ description = 'Task B', category = 'Work' }) local lines, meta = views.category_view(store.active_tasks()) - assert.are.equal('Work', lines[1]) + assert.are.equal('## Work', lines[1]) assert.are.equal('header', meta[1].type) assert.is_true(lines[2]:find('Task A') ~= nil) assert.is_true(lines[3]:find('Task B') ~= nil) @@ -113,10 +113,10 @@ describe('views', function() task_line = lines[i] end end - assert.are.equal('/1/ My task', task_line) + assert.are.equal('/1/- [ ] My task', task_line) end) - it('formats priority task lines as /ID/ [N] description', function() + it('formats priority task lines as /ID/- [!] description', function() store.add({ description = 'Important', category = 'Inbox', priority = 1 }) local lines, meta = views.category_view(store.active_tasks()) local task_line @@ -125,7 +125,7 @@ describe('views', function() task_line = lines[i] end end - assert.are.equal('/1/ [1] Important', task_line) + assert.are.equal('/1/- [!] Important', task_line) end) it('sets LineMeta type=header for header lines with correct category', function() @@ -220,8 +220,8 @@ describe('views', function() end end end - assert.are.equal('Work', first_header) - assert.are.equal('Inbox', second_header) + assert.are.equal('## Work', first_header) + assert.are.equal('## Inbox', second_header) end) it('appends categories not in category_order after ordered ones', function() @@ -236,8 +236,8 @@ describe('views', function() table.insert(headers, lines[i]) end end - assert.are.equal('Work', headers[1]) - assert.are.equal('Errands', headers[2]) + assert.are.equal('## Work', headers[1]) + assert.are.equal('## Errands', headers[2]) end) it('preserves insertion order when category_order is empty', function() @@ -250,8 +250,8 @@ describe('views', function() table.insert(headers, lines[i]) end end - assert.are.equal('Alpha', headers[1]) - assert.are.equal('Beta', headers[2]) + assert.are.equal('## Alpha', headers[1]) + assert.are.equal('## Beta', headers[2]) end) end) @@ -325,10 +325,10 @@ describe('views', function() assert.is_true(earlier_row < later_row) end) - it('formats task lines as /ID/ description', function() + it('formats task lines as /ID/- [ ] description', function() store.add({ description = 'My task', category = 'Inbox' }) local lines, _ = views.priority_view(store.active_tasks()) - assert.are.equal('/1/ My task', lines[1]) + assert.are.equal('/1/- [ ] My task', lines[1]) end) it('sets show_category=true for all task meta entries', function() diff --git a/syntax/pending.vim b/syntax/pending.vim index b5f3da1..a8a0258 100644 --- a/syntax/pending.vim +++ b/syntax/pending.vim @@ -3,12 +3,12 @@ if exists('b:current_syntax') endif syntax match taskId /^\/\d\+\// conceal -syntax match taskHeader /^\S.*$/ contains=taskId -syntax match taskPriority /!\ze / contained -syntax match taskLine /^\/\d\+\/ .*$/ contains=taskId,taskPriority +syntax match taskHeader /^## .*$/ contains=taskId +syntax match taskCheckbox /\[!\]/ contained containedin=taskLine +syntax match taskLine /^\/\d\+\/- \[.\] .*$/ contains=taskId,taskCheckbox highlight default link taskHeader PendingHeader -highlight default link taskPriority PendingPriority +highlight default link taskCheckbox PendingPriority highlight default link taskLine Normal let b:current_syntax = 'task' From fbeb0e2bee725a1d8336b50b6e3529027594fa00 Mon Sep 17 00:00:00 2001 From: Barrett Ruth <62671086+barrettruth@users.noreply.github.com> Date: Wed, 25 Feb 2026 09:34:17 -0500 Subject: [PATCH 7/9] feat(buffer): open as bottom-drawer split like fugitive (#23) * feat(buffer): open as bottom-drawer split like fugitive Problem: :Pending replaced the current buffer, making it impossible to view tasks alongside the file being edited. No way to close the drawer without :q or switching buffers manually. Solution: open the task buffer in a botright horizontal split instead of replacing the current buffer. Track the drawer window ID so re-opening focuses it rather than creating a second split. Set winfixheight so the drawer keeps its height when other windows open or close. Add q/ mappings to close the drawer, and a WinClosed autocmd to clear the tracked window ID when the user closes it manually. Add drawer_height config option (default 15). * fix(buffer): default to natural split height like fugitive Problem: hardcoded drawer_height=15 was too small and diverged from fugitive's model. Fugitive issues a plain botright split and lets Vim's own split rules (equalalways, winheight) divide the available space. Solution: remove the default height so the split sizes naturally. Only call nvim_win_set_height when the user sets drawer_height to a positive value, preserving the opt-in customization path. --- lua/pending/buffer.lua | 48 ++++++++++++++++++++++++++++++------------ lua/pending/config.lua | 1 + lua/pending/init.lua | 14 ++++++++++++ 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index 8f9fb59..d11254b 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -7,6 +7,8 @@ local M = {} ---@type integer? local task_bufnr = nil +---@type integer? +local task_winid = nil local task_ns = vim.api.nvim_create_namespace('pending') ---@type 'category'|'priority'|nil local current_view = nil @@ -25,11 +27,27 @@ function M.bufnr() return task_bufnr end +---@return integer? +function M.winid() + return task_winid +end + ---@return string? function M.current_view_name() return current_view end +function M.clear_winid() + task_winid = nil +end + +function M.close() + if task_winid and vim.api.nvim_win_is_valid(task_winid) then + vim.api.nvim_win_close(task_winid, false) + end + task_winid = nil +end + ---@param bufnr integer local function set_buf_options(bufnr) vim.bo[bufnr].buftype = 'acwrite' @@ -50,6 +68,7 @@ local function set_win_options(winid) vim.wo[winid].foldcolumn = '0' vim.wo[winid].spell = false vim.wo[winid].cursorline = true + vim.wo[winid].winfixheight = true end ---@param bufnr integer @@ -251,24 +270,25 @@ function M.open() setup_highlights() store.load() - if task_bufnr and vim.api.nvim_buf_is_valid(task_bufnr) then - local wins = vim.fn.win_findbuf(task_bufnr) - if #wins > 0 then - vim.api.nvim_set_current_win(wins[1]) - M.render(task_bufnr) - return task_bufnr - end - vim.api.nvim_set_current_buf(task_bufnr) - set_win_options(vim.api.nvim_get_current_win()) + if task_winid and vim.api.nvim_win_is_valid(task_winid) then + vim.api.nvim_set_current_win(task_winid) M.render(task_bufnr) - return task_bufnr + return task_bufnr --[[@as integer]] end - task_bufnr = vim.api.nvim_create_buf(true, false) + if not (task_bufnr and vim.api.nvim_buf_is_valid(task_bufnr)) then + task_bufnr = vim.api.nvim_create_buf(true, false) + set_buf_options(task_bufnr) + end - set_buf_options(task_bufnr) - vim.api.nvim_set_current_buf(task_bufnr) - set_win_options(vim.api.nvim_get_current_win()) + vim.cmd('botright new') + task_winid = vim.api.nvim_get_current_win() + vim.api.nvim_win_set_buf(task_winid, task_bufnr) + local h = config.get().drawer_height + if h and h > 0 then + vim.api.nvim_win_set_height(task_winid, h) + end + set_win_options(task_winid) M.render(task_bufnr) diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 2e647e4..b61f44a 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -9,6 +9,7 @@ ---@field date_format string ---@field date_syntax string ---@field category_order? string[] +---@field drawer_height? integer ---@field gcal? pending.GcalConfig ---@class pending.config diff --git a/lua/pending/init.lua b/lua/pending/init.lua index ec69d89..14b9c24 100644 --- a/lua/pending/init.lua +++ b/lua/pending/init.lua @@ -38,11 +38,25 @@ function M._setup_autocmds(bufnr) end end, }) + vim.api.nvim_create_autocmd('WinClosed', { + group = group, + callback = function(ev) + if tonumber(ev.match) == buffer.winid() then + buffer.clear_winid() + end + end, + }) end ---@param bufnr integer function M._setup_buf_mappings(bufnr) local opts = { buffer = bufnr, silent = true } + vim.keymap.set('n', 'q', function() + buffer.close() + end, opts) + vim.keymap.set('n', '', function() + buffer.close() + end, opts) vim.keymap.set('n', '', function() M.toggle_complete() end, opts) From fa1103ad4e089804ec3dd27e91a3d93378464187 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Wed, 25 Feb 2026 09:37:49 -0500 Subject: [PATCH 8/9] doc: minify readme --- README.md | 143 ++++++------------------------------------------------ 1 file changed, 14 insertions(+), 129 deletions(-) diff --git a/README.md b/README.md index 98e14d3..df7f3dd 100644 --- a/README.md +++ b/README.md @@ -2,145 +2,30 @@ Edit tasks like text. `:w` saves them. -A buffer-centric task manager for Neovim. Tasks live in a plain buffer — add -with `o`, delete with `dd`, reorder with `dd`/`p`, rename by editing. Write the -buffer and the diff is computed against a JSON store. No UI chrome, no floating -windows, no abstractions between you and your tasks. + -## How it works +## Requirements -``` -School - ! Read chapter 5 Feb 28 - Submit homework Feb 25 +- Neovim 0.10+ +- (Optionally) `curl` and `openssl` for Google Calendar and Google Task sync -Errands - Buy groceries Mar 01 - Clean apartment -``` +## Installation -Category headers sit at column 0. Tasks are indented below them. `!` marks -priority. Due dates appear as right-aligned virtual text. Done tasks get -strikethrough. Everything you see is editable buffer text — the IDs are -concealed, and metadata is parsed from inline syntax on save. - -## Install +Install with your package manager of choice or via +[luarocks](https://luarocks.org/modules/barrettruth/pending.nvim): ``` luarocks install pending.nvim ``` -**lazy.nvim:** - -```lua -{ 'barrettruth/pending.nvim' } -``` - -Requires Neovim 0.10+. No external dependencies for local use. Google Calendar -sync requires `curl` and `openssl`. - -## Usage - -`:Pending` opens the task buffer. From there, it's just vim: - -| Key | Action | -| --------- | ------------------------------- | -| `o` / `O` | Add a new task | -| `dd` | Delete a task (on `:w`) | -| `p` | Paste (duplicates get new IDs) | -| `:w` | Save all changes | -| `` | Toggle complete (immediate) | -| `` | Switch category / priority view | -| `g?` | Show keybind help | - -### Inline metadata - -Type metadata tokens at the end of a task line before saving: - -``` -Buy milk due:2026-03-15 cat:Errands -``` - -On `:w`, the date and category are extracted. The description becomes `Buy milk`, -the due date renders as virtual text, and the task moves under the `Errands` -header. - -### Quick add - -```vim -:Pending add Buy groceries due:2026-03-15 -:Pending add School: Submit homework -``` - -### Archive - -```vim -:Pending archive " purge done tasks older than 30 days -:Pending archive 7 " purge done tasks older than 7 days -``` - -## Configuration - -No `setup()` call required. Set `vim.g.pending` before the plugin loads: - -```lua -vim.g.pending = { - data_path = vim.fn.stdpath('data') .. '/pending/tasks.json', - default_view = 'category', -- 'category' or 'priority' - default_category = 'Inbox', - date_format = '%b %d', -- strftime format for virtual text - date_syntax = 'due', -- inline token name (e.g. 'by' for by:2026-03-15) -} -``` - -All fields are optional. Absent keys use the defaults shown above. - -## Google Calendar sync - -One-way push of tasks with due dates to a dedicated Google Calendar as all-day -events. - -```lua -vim.g.pending = { - gcal = { - calendar = 'Tasks', - credentials_path = '/path/to/client_secret.json', - }, -} -``` - -```vim -:Pending sync -``` - -On first run, a browser window opens for OAuth consent. The refresh token is -stored at `stdpath('data')/pending/gcal_tokens.json`. Completed or deleted tasks -have their calendar events removed. Due date changes update events in place. - -## Mappings - -The plugin defines `` mappings for custom keybinds: - -```lua -vim.keymap.set('n', 't', '(pending-open)') -vim.keymap.set('n', 'T', '(pending-toggle)') -``` - -| Plug mapping | Action | -| -------------------------- | -------------------- | -| `(pending-open)` | Open task buffer | -| `(pending-toggle)` | Toggle complete | -| `(pending-view)` | Switch view | -| `(pending-priority)` | Toggle priority flag | -| `(pending-date)` | Prompt for due date | - -## Data format - -Tasks are stored as JSON at `stdpath('data')/pending/tasks.json`. The schema is -versioned and forward-compatible — unknown fields are preserved on round-trip. - ## Documentation ```vim -:checkhealth pending +:help pending.nvim ``` + +## Acknowledgements + +- [dooing](https://github.com/atiladefreitas/dooing) +- [todo-comments.nvim](https://github.com/folke/todo-comments.nvim) +- [todotxt.nvim](https://github.com/arnarg/todotxt.nvim) From 8433d928575e692cf74f20edb124580a9146db3e Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Wed, 25 Feb 2026 09:39:11 -0500 Subject: [PATCH 9/9] ci: format --- .github/DISCUSSION_TEMPLATE/q-a.yaml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yaml | 17 ++++++++--------- .github/ISSUE_TEMPLATE/feature_request.yaml | 5 ++--- .github/workflows/luarocks.yaml | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.github/DISCUSSION_TEMPLATE/q-a.yaml b/.github/DISCUSSION_TEMPLATE/q-a.yaml index a65fd46..0e657eb 100644 --- a/.github/DISCUSSION_TEMPLATE/q-a.yaml +++ b/.github/DISCUSSION_TEMPLATE/q-a.yaml @@ -1,4 +1,4 @@ -title: 'Q&A' +title: "Q&A" labels: [] body: - type: markdown diff --git a/.github/ISSUE_TEMPLATE/bug_report.yaml b/.github/ISSUE_TEMPLATE/bug_report.yaml index baae06b..0796c39 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yaml +++ b/.github/ISSUE_TEMPLATE/bug_report.yaml @@ -1,14 +1,13 @@ name: Bug Report description: Report a bug -title: 'bug: ' +title: "bug: " labels: [bug] body: - type: checkboxes attributes: label: Prerequisites options: - - label: - I have searched [existing + - label: I have searched [existing issues](https://github.com/barrettruth/pending.nvim/issues) required: true - label: I have updated to the latest version @@ -16,16 +15,16 @@ body: - type: textarea attributes: - label: 'Neovim version' - description: 'Output of `nvim --version`' + label: "Neovim version" + description: "Output of `nvim --version`" render: text validations: required: true - type: input attributes: - label: 'Operating system' - placeholder: 'e.g. Arch Linux, macOS 15, Ubuntu 24.04' + label: "Operating system" + placeholder: "e.g. Arch Linux, macOS 15, Ubuntu 24.04" validations: required: true @@ -49,8 +48,8 @@ body: - type: textarea attributes: - label: 'Health check' - description: 'Output of `:checkhealth task`' + label: "Health check" + description: "Output of `:checkhealth task`" render: text - type: textarea diff --git a/.github/ISSUE_TEMPLATE/feature_request.yaml b/.github/ISSUE_TEMPLATE/feature_request.yaml index cabb27c..f4c02eb 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yaml +++ b/.github/ISSUE_TEMPLATE/feature_request.yaml @@ -1,14 +1,13 @@ name: Feature Request description: Suggest a feature -title: 'feat: ' +title: "feat: " labels: [enhancement] body: - type: checkboxes attributes: label: Prerequisites options: - - label: - I have searched [existing + - label: I have searched [existing issues](https://github.com/barrettruth/pending.nvim/issues) required: true diff --git a/.github/workflows/luarocks.yaml b/.github/workflows/luarocks.yaml index 9b6664e..9f934a5 100644 --- a/.github/workflows/luarocks.yaml +++ b/.github/workflows/luarocks.yaml @@ -3,7 +3,7 @@ name: luarocks on: push: tags: - - 'v*' + - "v*" jobs: quality: