Compare commits

...
Sign in to create a new pull request.

5 commits

Author SHA1 Message Date
ce00f28c96 refactor(init): replace multi-level priority with binary toggle
Problem: <C-a>/<C-x> overrode Vim's native number increment and the
visual g<C-a>/g<C-x> 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.
2026-02-24 23:15:02 -05:00
d243d5897a 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.
2026-02-24 23:14:53 -05:00
fe2ee47b5e 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.
2026-02-24 23:14:41 -05:00
afb9e65f8d 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.
2026-02-24 23:14:32 -05:00
4c8944c5ee refactor(config): change default category from Inbox to Todo 2026-02-24 23:14:23 -05:00
10 changed files with 94 additions and 123 deletions

View file

@ -56,9 +56,9 @@ local function setup_syntax(bufnr)
vim.cmd([[ vim.cmd([[
syntax clear syntax clear
syntax match taskId /^\/\d\+\// conceal syntax match taskId /^\/\d\+\// conceal
syntax match taskHeader /^\S.*$/ contains=taskId syntax match taskHeader /^## .*$/ contains=taskId
syntax match taskPriority /\[\d\+\] / contained containedin=taskLine syntax match taskCheckbox /\[!\]/ contained containedin=taskLine
syntax match taskLine /^\/\d\+\/ .*$/ contains=taskId,taskPriority syntax match taskLine /^\/\d\+\/- \[.\] .*$/ contains=taskId,taskCheckbox
]]) ]])
end) end)
end end
@ -72,8 +72,8 @@ function M.open_line(above)
local row = vim.api.nvim_win_get_cursor(0)[1] local row = vim.api.nvim_win_get_cursor(0)[1]
local insert_row = above and (row - 1) or row local insert_row = above and (row - 1) or row
vim.bo[bufnr].modifiable = true vim.bo[bufnr].modifiable = true
vim.api.nvim_buf_set_lines(bufnr, insert_row, insert_row, false, { ' ' }) 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_win_set_cursor(0, { insert_row + 1, 6 })
vim.cmd('startinsert!') vim.cmd('startinsert!')
end end
@ -113,18 +113,18 @@ local function apply_extmarks(bufnr, line_meta)
if virt_text then if virt_text then
vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, {
virt_text = virt_text, virt_text = virt_text,
virt_text_pos = 'right_align', virt_text_pos = 'eol',
}) })
end end
elseif m.due then elseif m.due then
vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, { vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, 0, {
virt_text = { { m.due, due_hl } }, virt_text = { { m.due, due_hl } },
virt_text_pos = 'right_align', virt_text_pos = 'eol',
}) })
end end
if m.status == 'done' then if m.status == 'done' then
local line = vim.api.nvim_buf_get_lines(bufnr, row, row + 1, false)[1] or '' 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, { vim.api.nvim_buf_set_extmark(bufnr, task_ns, row, col_start, {
end_col = #line, end_col = #line,
hl_group = 'PendingDone', hl_group = 'PendingDone',
@ -168,8 +168,11 @@ function M.render(bufnr)
_meta = line_meta _meta = line_meta
vim.bo[bufnr].modifiable = true 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.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.bo[bufnr].modified = false vim.bo[bufnr].modified = false
vim.bo[bufnr].undolevels = saved
setup_syntax(bufnr) setup_syntax(bufnr)
apply_extmarks(bufnr, line_meta) apply_extmarks(bufnr, line_meta)

View file

@ -18,7 +18,7 @@ local M = {}
local defaults = { local defaults = {
data_path = vim.fn.stdpath('data') .. '/pending/tasks.json', data_path = vim.fn.stdpath('data') .. '/pending/tasks.json',
default_view = 'category', default_view = 'category',
default_category = 'Inbox', default_category = 'Todo',
date_format = '%b %d', date_format = '%b %d',
date_syntax = 'due', date_syntax = 'due',
category_order = {}, category_order = {},

View file

@ -7,6 +7,7 @@ local store = require('pending.store')
---@field id? integer ---@field id? integer
---@field description? string ---@field description? string
---@field priority? integer ---@field priority? integer
---@field status? string
---@field category? string ---@field category? string
---@field due? string ---@field due? string
---@field lnum integer ---@field lnum integer
@ -26,20 +27,17 @@ function M.parse_buffer(lines)
local current_category = nil local current_category = nil
for i, line in ipairs(lines) do for i, line in ipairs(lines) do
local id, body = line:match('^/(%d+)/( .+)$') local id, body = line:match('^/(%d+)/(- %[.%] .*)$')
if not id then if not id then
body = line:match('^( .+)$') body = line:match('^(- %[.%] .*)$')
end end
if line == '' then if line == '' then
table.insert(result, { type = 'blank', lnum = i }) table.insert(result, { type = 'blank', lnum = i })
elseif id or body then elseif id or body then
local stripped = body:match('^ (.+)$') or body local stripped = body:match('^- %[.%] (.*)$') or body
local prio_str = stripped:match('^%[(%d+)%] ') local state_char = body:match('^- %[(.-)%]') or ' '
local priority = 0 local priority = state_char == '!' and 1 or 0
if prio_str then local status = state_char == 'x' and 'done' or 'pending'
priority = tonumber(prio_str)
stripped = stripped:sub(#prio_str + 4)
end
local description, metadata = parse.body(stripped) local description, metadata = parse.body(stripped)
if description and description ~= '' then if description and description ~= '' then
table.insert(result, { table.insert(result, {
@ -47,14 +45,15 @@ function M.parse_buffer(lines)
id = id and tonumber(id) or nil, id = id and tonumber(id) or nil,
description = description, description = description,
priority = priority, priority = priority,
status = status,
category = metadata.cat or current_category or config.get().default_category, category = metadata.cat or current_category or config.get().default_category,
due = metadata.due, due = metadata.due,
lnum = i, lnum = i,
}) })
end end
elseif line:match('^%S') then elseif line:match('^## (.+)$') then
current_category = line current_category = line:match('^## (.+)$')
table.insert(result, { type = 'header', category = line, lnum = i }) table.insert(result, { type = 'header', category = current_category, lnum = i })
end end
end end
@ -113,6 +112,15 @@ function M.apply(lines)
task.due = entry.due task.due = entry.due
changed = true changed = true
end 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 if task.order ~= order_counter then
task.order = order_counter task.order = order_counter
changed = true changed = true

View file

@ -52,17 +52,8 @@ function M._setup_buf_mappings(bufnr)
vim.keymap.set('n', 'g?', function() vim.keymap.set('n', 'g?', function()
M.show_help() M.show_help()
end, opts) end, opts)
vim.keymap.set('n', '<C-a>', function() vim.keymap.set('n', '!', function()
M.change_priority(1) M.toggle_priority()
end, opts)
vim.keymap.set('n', '<C-x>', function()
M.change_priority(-1)
end, opts)
vim.keymap.set('v', 'g<C-a>', function()
M.change_priority_visual(1)
end, opts)
vim.keymap.set('v', 'g<C-x>', function()
M.change_priority_visual(-1)
end, opts) end, opts)
vim.keymap.set('n', 'D', function() vim.keymap.set('n', 'D', function()
M.prompt_date() M.prompt_date()
@ -126,10 +117,15 @@ function M.toggle_complete()
end end
store.save() store.save()
buffer.render(bufnr) 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 end
---@param delta integer function M.toggle_priority()
function M.change_priority(delta)
local bufnr = buffer.bufnr() local bufnr = buffer.bufnr()
if not bufnr then if not bufnr then
return return
@ -147,7 +143,7 @@ function M.change_priority(delta)
if not task then if not task then
return return
end 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.update(id, { priority = new_priority })
store.save() store.save()
buffer.render(bufnr) buffer.render(bufnr)
@ -159,33 +155,6 @@ function M.change_priority(delta)
end end
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() function M.prompt_date()
local bufnr = buffer.bufnr() local bufnr = buffer.bufnr()
if not bufnr then if not bufnr then
@ -342,10 +311,7 @@ function M.show_help()
'', '',
'<CR> Toggle complete/uncomplete', '<CR> Toggle complete/uncomplete',
'<Tab> Switch category/priority view', '<Tab> Switch category/priority view',
'<C-a> Raise priority level', '! Toggle urgent',
'<C-x> Lower priority level',
'g<C-a> Raise priority for visual selection',
'g<C-x> Lower priority for visual selection',
'D Set due date', 'D Set due date',
'U Undo last write', 'U Undo last write',
'o / O Add new task line', 'o / O Add new task line',
@ -371,7 +337,7 @@ function M.show_help()
'', '',
'Highlights:', 'Highlights:',
' PendingOverdue overdue tasks (red)', ' PendingOverdue overdue tasks (red)',
' PendingPriority [N] priority prefix', ' PendingPriority [!] urgent tasks',
'', '',
'Press q or <Esc> to close', 'Press q or <Esc> to close',
} }

View file

@ -125,7 +125,7 @@ function M.category_view(tasks)
table.insert(lines, '') table.insert(lines, '')
table.insert(meta, { type = 'blank' }) table.insert(meta, { type = 'blank' })
end end
table.insert(lines, cat) table.insert(lines, '## ' .. cat)
table.insert(meta, { type = 'header', category = cat }) table.insert(meta, { type = 'header', category = cat })
local all = {} local all = {}
@ -138,9 +138,8 @@ function M.category_view(tasks)
for _, task in ipairs(all) do for _, task in ipairs(all) do
local prefix = '/' .. task.id .. '/' local prefix = '/' .. task.id .. '/'
local indent = ' ' local state = task.status == 'done' and 'x' or (task.priority > 0 and '!' or ' ')
local prio = task.priority > 0 and ('[' .. task.priority .. '] ') or '' local line = prefix .. '- [' .. state .. '] ' .. task.description
local line = prefix .. indent .. prio .. task.description
table.insert(lines, line) table.insert(lines, line)
table.insert(meta, { table.insert(meta, {
type = 'task', type = 'task',
@ -189,9 +188,8 @@ function M.priority_view(tasks)
for _, task in ipairs(all) do for _, task in ipairs(all) do
local prefix = '/' .. task.id .. '/' local prefix = '/' .. task.id .. '/'
local indent = ' ' local state = task.status == 'done' and 'x' or (task.priority > 0 and '!' or ' ')
local prio = task.priority == 1 and '! ' or '' local line = prefix .. '- [' .. state .. '] ' .. task.description
local line = prefix .. indent .. prio .. task.description
table.insert(lines, line) table.insert(lines, line)
table.insert(meta, { table.insert(meta, {
type = 'task', type = 'task',

View file

@ -8,7 +8,7 @@ vim.api.nvim_create_user_command('Pending', function(opts)
end, { end, {
nargs = '*', nargs = '*',
complete = function(arg_lead, cmd_line) 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 if not cmd_line:match('^Pending%s+%S') then
return vim.tbl_filter(function(s) return vim.tbl_filter(function(s)
return s:find(arg_lead, 1, true) == 1 return s:find(arg_lead, 1, true) == 1
@ -30,12 +30,8 @@ vim.keymap.set('n', '<Plug>(pending-view)', function()
require('pending.buffer').toggle_view() require('pending.buffer').toggle_view()
end) end)
vim.keymap.set('n', '<Plug>(pending-priority-up)', function() vim.keymap.set('n', '<Plug>(pending-priority)', function()
require('pending').change_priority(1) require('pending').toggle_priority()
end)
vim.keymap.set('n', '<Plug>(pending-priority-down)', function()
require('pending').change_priority(-1)
end) end)
vim.keymap.set('n', '<Plug>(pending-date)', function() vim.keymap.set('n', '<Plug>(pending-date)', function()

View file

@ -25,12 +25,12 @@ describe('diff', function()
describe('parse_buffer', function() describe('parse_buffer', function()
it('parses headers and tasks', function() it('parses headers and tasks', function()
local lines = { local lines = {
'School', '## School',
'/1/ Do homework', '/1/- [ ] Do homework',
'/2/ ! Read chapter 5', '/2/- [!] Read chapter 5',
'', '',
'Errands', '## Errands',
'/3/ Buy groceries', '/3/- [ ] Buy groceries',
} }
local result = diff.parse_buffer(lines) local result = diff.parse_buffer(lines)
assert.are.equal(6, #result) assert.are.equal(6, #result)
@ -48,8 +48,8 @@ describe('diff', function()
it('handles new tasks without ids', function() it('handles new tasks without ids', function()
local lines = { local lines = {
'Inbox', '## Inbox',
' New task here', '- [ ] New task here',
} }
local result = diff.parse_buffer(lines) local result = diff.parse_buffer(lines)
assert.are.equal(2, #result) assert.are.equal(2, #result)
@ -62,9 +62,9 @@ describe('diff', function()
describe('apply', function() describe('apply', function()
it('creates new tasks from buffer lines', function() it('creates new tasks from buffer lines', function()
local lines = { local lines = {
'Inbox', '## Inbox',
' First task', '- [ ] First task',
' Second task', '- [ ] Second task',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -80,8 +80,8 @@ describe('diff', function()
store.add({ description = 'Delete me' }) store.add({ description = 'Delete me' })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Keep me', '/1/- [ ] Keep me',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -97,8 +97,8 @@ describe('diff', function()
store.add({ description = 'Original' }) store.add({ description = 'Original' })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Renamed', '/1/- [ ] Renamed',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -111,9 +111,9 @@ describe('diff', function()
store.add({ description = 'Original' }) store.add({ description = 'Original' })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Original', '/1/- [ ] Original',
'/1/ Copy of original', '/1/- [ ] Copy of original',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -126,8 +126,8 @@ describe('diff', function()
store.add({ description = 'Moving task', category = 'Inbox' }) store.add({ description = 'Moving task', category = 'Inbox' })
store.save() store.save()
local lines = { local lines = {
'Work', '## Work',
'/1/ Moving task', '/1/- [ ] Moving task',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -140,8 +140,8 @@ describe('diff', function()
store.add({ description = 'Stable task', category = 'Inbox' }) store.add({ description = 'Stable task', category = 'Inbox' })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Stable task', '/1/- [ ] Stable task',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -158,8 +158,8 @@ describe('diff', function()
store.add({ description = 'Pay bill', due = '2026-03-15' }) store.add({ description = 'Pay bill', due = '2026-03-15' })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Pay bill', '/1/- [ ] Pay bill',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()
@ -172,8 +172,8 @@ describe('diff', function()
store.add({ description = 'Task name', priority = 1 }) store.add({ description = 'Task name', priority = 1 })
store.save() store.save()
local lines = { local lines = {
'Inbox', '## Inbox',
'/1/ Task name', '/1/- [ ] Task name',
} }
diff.apply(lines) diff.apply(lines)
store.unload() store.unload()

View file

@ -92,7 +92,7 @@ describe('store', function()
assert.are.equal(1, t1.id) assert.are.equal(1, t1.id)
assert.are.equal(2, t2.id) assert.are.equal(2, t2.id)
assert.are.equal('pending', t1.status) assert.are.equal('pending', t1.status)
assert.are.equal('Inbox', t1.category) assert.are.equal('Todo', t1.category)
end) end)
it('uses provided category', function() it('uses provided category', function()

View file

@ -27,7 +27,7 @@ describe('views', function()
store.add({ description = 'Task A', category = 'Work' }) store.add({ description = 'Task A', category = 'Work' })
store.add({ description = 'Task B', category = 'Work' }) store.add({ description = 'Task B', category = 'Work' })
local lines, meta = views.category_view(store.active_tasks()) 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.are.equal('header', meta[1].type)
assert.is_true(lines[2]:find('Task A') ~= nil) assert.is_true(lines[2]:find('Task A') ~= nil)
assert.is_true(lines[3]:find('Task B') ~= nil) assert.is_true(lines[3]:find('Task B') ~= nil)
@ -113,10 +113,10 @@ describe('views', function()
task_line = lines[i] task_line = lines[i]
end end
end end
assert.are.equal('/1/ My task', task_line) assert.are.equal('/1/- [ ] My task', task_line)
end) end)
it('formats priority task lines as /ID/ ! description', function() it('formats priority task lines as /ID/- [!] description', function()
store.add({ description = 'Important', category = 'Inbox', priority = 1 }) store.add({ description = 'Important', category = 'Inbox', priority = 1 })
local lines, meta = views.category_view(store.active_tasks()) local lines, meta = views.category_view(store.active_tasks())
local task_line local task_line
@ -125,7 +125,7 @@ describe('views', function()
task_line = lines[i] task_line = lines[i]
end end
end end
assert.are.equal('/1/ ! Important', task_line) assert.are.equal('/1/- [!] Important', task_line)
end) end)
it('sets LineMeta type=header for header lines with correct category', function() it('sets LineMeta type=header for header lines with correct category', function()
@ -220,8 +220,8 @@ describe('views', function()
end end
end end
end end
assert.are.equal('Work', first_header) assert.are.equal('## Work', first_header)
assert.are.equal('Inbox', second_header) assert.are.equal('## Inbox', second_header)
end) end)
it('appends categories not in category_order after ordered ones', function() it('appends categories not in category_order after ordered ones', function()
@ -236,8 +236,8 @@ describe('views', function()
table.insert(headers, lines[i]) table.insert(headers, lines[i])
end end
end end
assert.are.equal('Work', headers[1]) assert.are.equal('## Work', headers[1])
assert.are.equal('Errands', headers[2]) assert.are.equal('## Errands', headers[2])
end) end)
it('preserves insertion order when category_order is empty', function() it('preserves insertion order when category_order is empty', function()
@ -250,8 +250,8 @@ describe('views', function()
table.insert(headers, lines[i]) table.insert(headers, lines[i])
end end
end end
assert.are.equal('Alpha', headers[1]) assert.are.equal('## Alpha', headers[1])
assert.are.equal('Beta', headers[2]) assert.are.equal('## Beta', headers[2])
end) end)
end) end)
@ -325,10 +325,10 @@ describe('views', function()
assert.is_true(earlier_row < later_row) assert.is_true(earlier_row < later_row)
end) end)
it('formats task lines as /ID/ description', function() it('formats task lines as /ID/- [ ] description', function()
store.add({ description = 'My task', category = 'Inbox' }) store.add({ description = 'My task', category = 'Inbox' })
local lines, _ = views.priority_view(store.active_tasks()) 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) end)
it('sets show_category=true for all task meta entries', function() it('sets show_category=true for all task meta entries', function()

View file

@ -3,12 +3,12 @@ if exists('b:current_syntax')
endif endif
syntax match taskId /^\/\d\+\// conceal syntax match taskId /^\/\d\+\// conceal
syntax match taskHeader /^\S.*$/ contains=taskId syntax match taskHeader /^## .*$/ contains=taskId
syntax match taskPriority /!\ze / contained syntax match taskCheckbox /\[!\]/ contained containedin=taskLine
syntax match taskLine /^\/\d\+\/ .*$/ contains=taskId,taskPriority syntax match taskLine /^\/\d\+\/- \[.\] .*$/ contains=taskId,taskCheckbox
highlight default link taskHeader PendingHeader highlight default link taskHeader PendingHeader
highlight default link taskPriority PendingPriority highlight default link taskCheckbox PendingPriority
highlight default link taskLine Normal highlight default link taskLine Normal
let b:current_syntax = 'task' let b:current_syntax = 'task'