feat(customization): icons config, PendingTab, and demo infrastructure (#46)

* feat(config): add icons table with unicode defaults

* feat(buffer): render icon overlays from config.icons

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.

* feat(plugin): add PendingTab command and <Plug>(pending-tab)

* 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.

* ci: format
This commit is contained in:
Barrett Ruth 2026-02-26 19:20:29 -05:00 committed by GitHub
parent 1748e5caa1
commit 64b19360b1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 248 additions and 4 deletions

View file

@ -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

View file

@ -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?