From a4a470ce5a2c095aa2aed648110864121bdcca92 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 18:23:32 -0500 Subject: [PATCH 1/6] feat(config): add icons table with unicode defaults --- lua/pending/config.lua | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 000ac2b..6aeaf3a 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -1,3 +1,12 @@ +---@class pending.Icons +---@field pending string +---@field done string +---@field priority string +---@field header string +---@field due string +---@field recur string +---@field category string + ---@class pending.GcalConfig ---@field calendar? string ---@field credentials_path? string @@ -38,6 +47,7 @@ ---@field keymaps pending.Keymaps ---@field sync? pending.SyncConfig ---@field gcal? pending.GcalConfig +---@field icons pending.Icons ---@class pending.config local M = {} @@ -71,6 +81,15 @@ local defaults = { prev_task = '[t', }, sync = {}, + icons = { + pending = '○', + done = '✓', + priority = '●', + header = '▸', + due = '·', + recur = '↺', + category = '#', + }, } ---@type pending.Config? From e867078cbd346fe08767b4b35ca0a90a39af0acf Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 18:23:40 -0500 Subject: [PATCH 2/6] feat(buffer): render icon overlays from config.icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: status characters ([ ], [x], [!]) and metadata prefixes are hardcoded literals with no user customization. Solution: read config.icons in apply_extmarks and apply overlay extmarks for checkboxes/headers, replace hardcoded recur ↺ with icons.recur, and prefix due/category virt_text with configurable icon characters. --- lua/pending/buffer.lua | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lua/pending/buffer.lua b/lua/pending/buffer.lua index 0aa78bb..09412f3 100644 --- a/lua/pending/buffer.lua +++ b/lua/pending/buffer.lua @@ -143,6 +143,7 @@ end ---@param bufnr integer ---@param line_meta pending.LineMeta[] local function apply_extmarks(bufnr, line_meta) + local icons = config.get().icons vim.api.nvim_buf_clear_namespace(bufnr, task_ns, 0, -1) for i, m in ipairs(line_meta) do local row = i - 1 @@ -156,13 +157,13 @@ local function apply_extmarks(bufnr, line_meta) local due_hl = m.overdue and 'PendingOverdue' or 'PendingDue' local virt_parts = {} if m.show_category and m.category then - table.insert(virt_parts, { m.category, 'PendingHeader' }) + table.insert(virt_parts, { icons.category .. ' ' .. m.category, 'PendingHeader' }) end if m.recur then - table.insert(virt_parts, { '\u{21bb} ' .. m.recur, 'PendingRecur' }) + table.insert(virt_parts, { icons.recur .. ' ' .. m.recur, 'PendingRecur' }) end if m.due then - table.insert(virt_parts, { m.due, due_hl }) + table.insert(virt_parts, { icons.due .. ' ' .. m.due, due_hl }) end if m.file then local display = m.file:match('([^/]+:%d+)$') or m.file @@ -185,12 +186,33 @@ local function apply_extmarks(bufnr, line_meta) hl_group = 'PendingDone', }) end + local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or '' + local bracket_col = (line:find('%[') or 1) - 1 + local icon, icon_hl + if m.status == 'done' then + icon, icon_hl = icons.done, 'PendingDone' + elseif m.priority and m.priority > 0 then + icon, icon_hl = icons.priority, 'PendingPriority' + else + icon, icon_hl = icons.pending, 'Normal' + end + local icon_padded = icon .. ' ' + vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, bracket_col, { + virt_text = { { icon_padded, icon_hl } }, + virt_text_pos = 'overlay', + priority = 100, + }) elseif m.type == 'header' then local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or '' vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { end_col = #line, hl_group = 'PendingHeader', }) + vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { + virt_text = { { icons.header .. ' ', 'PendingHeader' } }, + virt_text_pos = 'overlay', + priority = 100, + }) end end end From 9fc24f52395029b8be7c7db4088c4304256cac25 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 18:23:43 -0500 Subject: [PATCH 3/6] feat(plugin): add PendingTab command and (pending-tab) --- plugin/pending.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugin/pending.lua b/plugin/pending.lua index 5cd94d0..ce62d1b 100644 --- a/plugin/pending.lua +++ b/plugin/pending.lua @@ -304,3 +304,13 @@ end) vim.keymap.set('n', '(pending-add-here)', function() require('pending').add_here() end) + +vim.keymap.set('n', '(pending-tab)', function() + vim.cmd.tabnew() + require('pending').open() +end) + +vim.api.nvim_create_user_command('PendingTab', function() + vim.cmd.tabnew() + require('pending').open() +end, {}) From cd96a269a44a60d8909603ae9503d321dd3e7e27 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 18:23:50 -0500 Subject: [PATCH 4/6] docs: add icons config, PendingTab recipes, and demo infrastructure Problem: icon customization and auto-start workflow are undocumented; no demo asset exists for the README. Solution: document pending.Icons in vimdoc with nerd font and ASCII recipes, add PendingTab to commands and mappings, add open-on-startup recipe, add demo-init.lua and demo.tape for VHS screenshot generation, add assets/ directory, add README icons section and demo placeholder. --- README.md | 17 ++++++++++++- assets/.gitkeep | 0 doc/pending.txt | 52 ++++++++++++++++++++++++++++++++++++++ scripts/demo-init.lua | 33 ++++++++++++++++++++++++ scripts/demo.tape | 28 ++++++++++++++++++++ spec/icons_spec.lua | 59 +++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 assets/.gitkeep create mode 100644 scripts/demo-init.lua create mode 100644 scripts/demo.tape create mode 100644 spec/icons_spec.lua diff --git a/README.md b/README.md index df7f3dd..f6add96 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Edit tasks like text. `:w` saves them. - +![demo](assets/demo.gif) ## Requirements @@ -24,6 +24,21 @@ luarocks install pending.nvim :help pending.nvim ``` +## Icons + +pending.nvim renders task status and metadata using configurable icon characters. The defaults use plain unicode (no nerd font required): + +```lua +vim.g.pending = { + icons = { + pending = '○', done = '✓', priority = '●', + header = '▸', due = '·', recur = '↺', category = '#', + }, +} +``` + +See `:help pending.Icons` for nerd font examples. + ## Acknowledgements - [dooing](https://github.com/atiladefreitas/dooing) diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/doc/pending.txt b/doc/pending.txt index 08c63f9..486ea32 100644 --- a/doc/pending.txt +++ b/doc/pending.txt @@ -356,6 +356,10 @@ COMMANDS *pending-commands* Equivalent to the `U` buffer-local key (see |pending-mappings|). Up to 20 levels of undo are persisted across sessions. + *:PendingTab* +:PendingTab + Open the task buffer in a new tab. + ============================================================================== MAPPINGS *pending-mappings* @@ -494,6 +498,9 @@ All motions support count: `3]]` jumps three headers forward. `]]` and to the current buffer's file and the cursor's line number. See |pending-file-token|. +(pending-tab) *(pending-tab)* + Open the task buffer in a new tab. See |:PendingTab|. + Example configuration: >lua vim.keymap.set('n', 't', '(pending-open)') vim.keymap.set('n', 'T', '(pending-toggle)') @@ -679,6 +686,16 @@ Fields: ~ automatically. New configs should use `sync.gcal` instead. See |pending.GcalConfig|. + {icons} (table) *pending.Icons* + Icon characters displayed in the buffer. Fields: + {pending} Uncompleted task icon. Default: '○' + {done} Completed task icon. Default: '✓' + {priority} Priority task icon. Default: '●' + {header} Category header prefix. Default: '▸' + {due} Due date prefix. Default: '·' + {recur} Recurrence prefix. Default: '↺' + {category} Category label prefix. Default: '#' + ============================================================================== LUA API *pending-api* @@ -860,6 +877,41 @@ directly, or disable `a_category` in `keymaps` and handle it via a `vim.b.miniai_config` entry that returns a linewise region if mini.ai's spec allows it in your version. +Nerd font icons: >lua + vim.g.pending = { + icons = { + pending = '', + done = '', + priority = '', + header = '', + due = '', + recur = '󰁯', + category = '', + }, + } +< + +ASCII fallback icons: >lua + vim.g.pending = { + icons = { + pending = '-', + done = 'x', + priority = '!', + header = '>', + due = '@', + recur = '~', + category = '+', + }, + } +< + +Open tasks in a new tab on startup: >lua + vim.api.nvim_create_autocmd('VimEnter', { + callback = function() + vim.cmd.PendingTab() + end, + }) +< ============================================================================== GOOGLE CALENDAR *pending-gcal* diff --git a/scripts/demo-init.lua b/scripts/demo-init.lua new file mode 100644 index 0000000..9c19e2a --- /dev/null +++ b/scripts/demo-init.lua @@ -0,0 +1,33 @@ +vim.opt.runtimepath:prepend(vim.fn.getcwd()) +local tmpdir = vim.fn.tempname() +vim.fn.mkdir(tmpdir, 'p') + +vim.g.pending = { + data_path = tmpdir .. '/tasks.json', + icons = { + pending = '○', + done = '✓', + priority = '●', + header = '▸', + due = '·', + recur = '↺', + category = '#', + }, +} + +local store = require('pending.store') +store.load() + +local today = os.date('%Y-%m-%d') +local yesterday = os.date('%Y-%m-%d', os.time() - 86400) +local tomorrow = os.date('%Y-%m-%d', os.time() + 86400) + +store.add({ description = 'Finish quarterly report', category = 'Work', due = tomorrow, recur = 'monthly', priority = 1 }) +store.add({ description = 'Review pull requests', category = 'Work' }) +store.add({ description = 'Update deployment docs', category = 'Work', status = 'done' }) +store.add({ description = 'Buy groceries', category = 'Personal', due = today }) +store.add({ description = 'Call dentist', category = 'Personal', due = yesterday, priority = 1 }) +store.add({ description = 'Read chapter 5', category = 'Personal' }) +store.add({ description = 'Learn a new language', category = 'Someday' }) +store.add({ description = 'Plan hiking trip', category = 'Someday' }) +store.save() diff --git a/scripts/demo.tape b/scripts/demo.tape new file mode 100644 index 0000000..3a1eee5 --- /dev/null +++ b/scripts/demo.tape @@ -0,0 +1,28 @@ +Output assets/demo.gif + +Require nvim + +Set Shell "bash" +Set FontSize 14 +Set Width 900 +Set Height 450 + +Type "nvim -u scripts/demo-init.lua -c 'autocmd VimEnter * Pending'" +Enter + +Sleep 2s + +Down +Down +Sleep 300ms +Down +Sleep 300ms + +Enter +Sleep 500ms + +Tab +Sleep 1s + +Type "q" +Sleep 200ms diff --git a/spec/icons_spec.lua b/spec/icons_spec.lua new file mode 100644 index 0000000..478400f --- /dev/null +++ b/spec/icons_spec.lua @@ -0,0 +1,59 @@ +require('spec.helpers') + +local config = require('pending.config') + +describe('icons', function() + before_each(function() + vim.g.pending = nil + config.reset() + end) + + after_each(function() + vim.g.pending = nil + config.reset() + end) + + it('has default icon values', function() + local icons = config.get().icons + assert.equals('○', icons.pending) + assert.equals('✓', icons.done) + assert.equals('●', icons.priority) + assert.equals('▸', icons.header) + assert.equals('·', icons.due) + assert.equals('↺', icons.recur) + assert.equals('#', icons.category) + end) + + it('allows overriding individual icons', function() + vim.g.pending = { icons = { pending = '-', done = 'x' } } + config.reset() + local icons = config.get().icons + assert.equals('-', icons.pending) + assert.equals('x', icons.done) + assert.equals('●', icons.priority) + assert.equals('▸', icons.header) + end) + + it('allows overriding all icons', function() + vim.g.pending = { + icons = { + pending = '-', + done = 'x', + priority = '!', + header = '>', + due = '@', + recur = '~', + category = '+', + }, + } + config.reset() + local icons = config.get().icons + assert.equals('-', icons.pending) + assert.equals('x', icons.done) + assert.equals('!', icons.priority) + assert.equals('>', icons.header) + assert.equals('@', icons.due) + assert.equals('~', icons.recur) + assert.equals('+', icons.category) + end) +end) From e182cc67a2ba5de64c66829829f68e55776d7136 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 19:17:58 -0500 Subject: [PATCH 5/6] ci: format --- lua/pending/config.lua | 10 +++++----- scripts/demo-init.lua | 18 ++++++++++++------ spec/icons_spec.lua | 10 +++++----- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/lua/pending/config.lua b/lua/pending/config.lua index 6aeaf3a..6adf1c3 100644 --- a/lua/pending/config.lua +++ b/lua/pending/config.lua @@ -82,12 +82,12 @@ local defaults = { }, sync = {}, icons = { - pending = '○', - done = '✓', + pending = '○', + done = '✓', priority = '●', - header = '▸', - due = '·', - recur = '↺', + header = '▸', + due = '·', + recur = '↺', category = '#', }, } diff --git a/scripts/demo-init.lua b/scripts/demo-init.lua index 9c19e2a..f2a6213 100644 --- a/scripts/demo-init.lua +++ b/scripts/demo-init.lua @@ -5,12 +5,12 @@ vim.fn.mkdir(tmpdir, 'p') vim.g.pending = { data_path = tmpdir .. '/tasks.json', icons = { - pending = '○', - done = '✓', + pending = '○', + done = '✓', priority = '●', - header = '▸', - due = '·', - recur = '↺', + header = '▸', + due = '·', + recur = '↺', category = '#', }, } @@ -22,7 +22,13 @@ local today = os.date('%Y-%m-%d') local yesterday = os.date('%Y-%m-%d', os.time() - 86400) local tomorrow = os.date('%Y-%m-%d', os.time() + 86400) -store.add({ description = 'Finish quarterly report', category = 'Work', due = tomorrow, recur = 'monthly', priority = 1 }) +store.add({ + description = 'Finish quarterly report', + category = 'Work', + due = tomorrow, + recur = 'monthly', + priority = 1, +}) store.add({ description = 'Review pull requests', category = 'Work' }) store.add({ description = 'Update deployment docs', category = 'Work', status = 'done' }) store.add({ description = 'Buy groceries', category = 'Personal', due = today }) diff --git a/spec/icons_spec.lua b/spec/icons_spec.lua index 478400f..fe3288f 100644 --- a/spec/icons_spec.lua +++ b/spec/icons_spec.lua @@ -37,12 +37,12 @@ describe('icons', function() it('allows overriding all icons', function() vim.g.pending = { icons = { - pending = '-', - done = 'x', + pending = '-', + done = 'x', priority = '!', - header = '>', - due = '@', - recur = '~', + header = '>', + due = '@', + recur = '~', category = '+', }, } From d672d68c60a2a23be72b868b3bbe576910fedcf0 Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 26 Feb 2026 19:30:49 -0500 Subject: [PATCH 6/6] docs(readme): remove icons section, covered in vimdoc --- README.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/README.md b/README.md index f6add96..b740f8f 100644 --- a/README.md +++ b/README.md @@ -24,21 +24,6 @@ luarocks install pending.nvim :help pending.nvim ``` -## Icons - -pending.nvim renders task status and metadata using configurable icon characters. The defaults use plain unicode (no nerd font required): - -```lua -vim.g.pending = { - icons = { - pending = '○', done = '✓', priority = '●', - header = '▸', due = '·', recur = '↺', category = '#', - }, -} -``` - -See `:help pending.Icons` for nerd font examples. - ## Acknowledgements - [dooing](https://github.com/atiladefreitas/dooing)