refactor(config): nest view settings under view key (#103)

Problem: View-related config fields (`default_view`, `eol_format`,
`category_order`, `folding`) are scattered as top-level siblings
alongside unrelated fields like `data_path` and `date_syntax`.

Solution: Group them under a `view` table with per-view sub-tables:
`view.default`, `view.eol_format`, `view.category.order`,
`view.category.folding`, and `view.queue` (empty, ready for #100).
Update all call sites, tests, and vimdoc.
This commit is contained in:
Barrett Ruth 2026-03-08 19:13:17 -04:00 committed by GitHub
parent 91cce0a82e
commit a43f769383
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 82 additions and 73 deletions

View file

@ -589,14 +589,20 @@ Configuration is done via `vim.g.pending`. Set this before the plugin
loads: >lua
vim.g.pending = {
data_path = vim.fn.stdpath('data') .. '/pending/tasks.json',
default_view = 'category',
default_category = 'Todo',
date_format = '%b %d',
date_syntax = 'due',
recur_syntax = 'rec',
someday_date = '9999-12-30',
folding = true,
category_order = {},
view = {
default = 'category',
eol_format = '%c %r %d',
category = {
order = {},
folding = true,
},
queue = {},
},
keymaps = {
close = 'q',
toggle = '<CR>',
@ -634,10 +640,6 @@ Fields: ~
See |pending-store-resolution| for how the active
store is chosen at runtime.
{default_view} ('category'|'priority', default: 'category')
The view to use when the buffer is opened for the
first time in a session.
{default_category} (string, default: 'Todo')
Category assigned to new tasks when no `cat:` token
is present and no `Category: ` prefix is used with
@ -648,32 +650,6 @@ Fields: ~
virtual text in the buffer. Examples: `'%Y-%m-%d'`
for ISO dates, `'%d %b'` for day-first.
{eol_format} (string, default: '%c %r %d')
Format string controlling the order, content, and
separators of end-of-line virtual text on task lines.
Three specifiers are available:
`%c` category icon + name (`PendingHeader`)
`%r` recurrence icon + pattern (`PendingRecur`)
`%d` due icon + date (`PendingDue` / `PendingOverdue`)
Literal text between specifiers is rendered with the
`Normal` highlight group and acts as a separator.
When a specifier's data is absent (e.g. `%d` on a
task with no due date), the specifier and any
surrounding literal text up to the next specifier
are omitted — missing fields never leave gaps.
`%c` only renders in priority view (where
`show_category` is true). In category view it is
always omitted regardless of the format string.
Examples: >lua
vim.g.pending = { eol_format = '%d %r' }
vim.g.pending = { eol_format = '%d | %r' }
vim.g.pending = { eol_format = '%c %d %r' }
<
{input_date_formats} (string[], default: {}) *pending-input-formats*
List of strftime-like format strings tried in order
when parsing a `due:` token that does not match the
@ -705,38 +681,57 @@ Fields: ~
The date that `later` and `someday` resolve to. This
acts as a "no date" sentinel for GTD-style workflows.
{category_order} (string[], default: {})
Ordered list of category names. In category view,
categories that appear in this list are shown in the
given order. Categories not in the list are appended
after the ordered ones in their natural order.
{view} (table) *pending.ViewConfig*
View rendering configuration. Groups all settings
that affect how the buffer displays tasks.
{folding} (boolean|table, default: true) *pending.FoldingConfig*
Controls category-level folds in category view. When
`true`, folds are enabled with the default foldtext
`'%c (%n tasks)'`. When `false`, folds are disabled
entirely. When a table, folds are enabled and the
table may contain:
{default} ('category'|'priority', default: 'category')
The view to use when the buffer is opened
for the first time in a session.
{foldtext} (string|false, default: '%c (%n tasks)')
Custom foldtext format string. Set to
`false` to use Vim's built-in
foldtext. Two specifiers are
available:
`%c` category name
`%n` number of tasks in the fold
The category icon is prepended
automatically. When `false`, the
default Vim foldtext is used.
{eol_format} (string, default: '%c %r %d')
Format string for end-of-line virtual text.
Specifiers:
`%c` category icon + name (`PendingHeader`)
`%r` recurrence icon + pattern (`PendingRecur`)
`%d` due icon + date (`PendingDue`/`PendingOverdue`)
Literal text between specifiers acts as a
separator. Absent fields and surrounding
literals are collapsed automatically. `%c`
only renders in priority view.
Folds only apply to category view; priority view
is always fold-free regardless of this setting.
{category} (table) *pending.CategoryViewConfig*
Category view settings.
{order} (string[], default: {})
Ordered list of category names. Categories
in this list appear in the given order;
others are appended after.
{folding} (boolean|table, default: true)
*pending.FoldingConfig*
Controls category-level folds. `true`
enables with default foldtext `'%c (%n
tasks)'`. `false` disables entirely. A
table may contain:
{foldtext} (string|false) Format string
with `%c` (category) and `%n` (count).
`false` uses Vim's built-in foldtext.
Folds only apply to category view.
{queue} (table) *pending.QueueViewConfig*
Queue (priority) view settings.
Examples: >lua
vim.g.pending = { folding = true }
vim.g.pending = { folding = false }
vim.g.pending = {
folding = { foldtext = '%c (%n tasks)' },
view = {
default = 'priority',
eol_format = '%d | %r',
category = {
order = { 'Work', 'Personal' },
folding = { foldtext = '%c: %n items' },
},
},
}
<

View file

@ -440,7 +440,7 @@ end
local function apply_extmarks(bufnr, line_meta)
local cfg = config.get()
local icons = cfg.icons
local eol_segments = parse_eol_format(cfg.eol_format or '%c %r %d')
local eol_segments = parse_eol_format(cfg.view.eol_format or '%c %r %d')
vim.api.nvim_buf_clear_namespace(bufnr, ns_eol, 0, -1)
vim.api.nvim_buf_clear_namespace(bufnr, ns_inline, 0, -1)
for i, m in ipairs(line_meta) do
@ -578,7 +578,7 @@ function M.render(bufnr)
return
end
current_view = current_view or config.get().default_view
current_view = current_view or config.get().view.default
local view_label = current_view == 'priority' and 'queue' or current_view
vim.api.nvim_buf_set_name(bufnr, 'pending://' .. view_label)
local all_tasks = _store and _store:active_tasks() or {}

View file

@ -49,22 +49,31 @@
---@field next_task? string|false
---@field prev_task? string|false
---@class pending.CategoryViewConfig
---@field order? string[]
---@field folding? boolean|pending.FoldingConfig
---@class pending.QueueViewConfig
---@class pending.ViewConfig
---@field default? 'category'|'priority'
---@field eol_format? string
---@field category? pending.CategoryViewConfig
---@field queue? pending.QueueViewConfig
---@class pending.Config
---@field data_path string
---@field default_view 'category'|'priority'
---@field default_category string
---@field date_format string
---@field date_syntax string
---@field recur_syntax string
---@field someday_date string
---@field input_date_formats? string[]
---@field category_order? string[]
---@field drawer_height? integer
---@field debug? boolean
---@field keymaps pending.Keymaps
---@field folding? boolean|pending.FoldingConfig
---@field view pending.ViewConfig
---@field sync? pending.SyncConfig
---@field eol_format? string
---@field icons pending.Icons
---@class pending.config
@ -73,15 +82,20 @@ local M = {}
---@type pending.Config
local defaults = {
data_path = vim.fn.stdpath('data') .. '/pending/tasks.json',
default_view = 'category',
default_category = 'Todo',
date_format = '%b %d',
date_syntax = 'due',
recur_syntax = 'rec',
someday_date = '9999-12-30',
eol_format = '%c %r %d',
folding = true,
category_order = {},
view = {
default = 'category',
eol_format = '%c %r %d',
category = {
order = {},
folding = true,
},
queue = {},
},
keymaps = {
close = 'q',
toggle = '<CR>',
@ -132,7 +146,7 @@ end
---@return pending.ResolvedFolding
function M.resolve_folding()
local raw = M.get().folding
local raw = M.get().view.category.folding
if raw == false then
return { enabled = false, foldtext = false }
elseif raw == true or raw == nil then

View file

@ -102,7 +102,7 @@ function M.category_view(tasks)
end
end
local cfg_order = config.get().category_order
local cfg_order = config.get().view.category.order
if cfg_order and #cfg_order > 0 then
local ordered = {}
local seen = {}

View file

@ -228,7 +228,7 @@ describe('views', function()
end)
it('respects category_order when set', function()
vim.g.pending = { data_path = tmpdir .. '/tasks.json', category_order = { 'Work', 'Inbox' } }
vim.g.pending = { data_path = tmpdir .. '/tasks.json', view = { category = { order = { 'Work', 'Inbox' } } } }
config.reset()
s:add({ description = 'Inbox task', category = 'Inbox' })
s:add({ description = 'Work task', category = 'Work' })
@ -248,7 +248,7 @@ describe('views', function()
end)
it('appends categories not in category_order after ordered ones', function()
vim.g.pending = { data_path = tmpdir .. '/tasks.json', category_order = { 'Work' } }
vim.g.pending = { data_path = tmpdir .. '/tasks.json', view = { category = { order = { 'Work' } } } }
config.reset()
s:add({ description = 'Errand', category = 'Errands' })
s:add({ description = 'Work task', category = 'Work' })