feat(filter): oil-like editable filter line (#43)
* feat(filter): oil-like editable filter line with predicate dispatch Problem: no way to narrow the pending buffer to a subset of tasks without manual scrolling; filtered-out tasks would be silently deleted on :w because diff.apply() marks unseen IDs as deleted. Solution: add a FILTER: line rendered at the top of the buffer when a filter is active. The line is editable — :w re-parses it and updates the hidden set. diff.apply() gains a hidden_ids param that prevents filtered-out tasks from being marked deleted. Predicates: cat:X, overdue, today, priority (space-separated AND). :Pending filter sets it programmatically; :Pending filter clear removes it. * ci: format
This commit is contained in:
parent
3da23c924a
commit
dcb6a4781d
7 changed files with 526 additions and 8 deletions
|
|
@ -94,6 +94,47 @@ function M.has_due()
|
|||
return c.overdue > 0 or c.today > 0
|
||||
end
|
||||
|
||||
---@param tasks pending.Task[]
|
||||
---@param predicates string[]
|
||||
---@return table<integer, true>
|
||||
local function compute_hidden_ids(tasks, predicates)
|
||||
if #predicates == 0 then
|
||||
return {}
|
||||
end
|
||||
local hidden = {}
|
||||
for _, task in ipairs(tasks) do
|
||||
local visible = true
|
||||
for _, pred in ipairs(predicates) do
|
||||
local cat_val = pred:match('^cat:(.+)$')
|
||||
if cat_val then
|
||||
if task.category ~= cat_val then
|
||||
visible = false
|
||||
break
|
||||
end
|
||||
elseif pred == 'overdue' then
|
||||
if not (task.status == 'pending' and task.due and parse.is_overdue(task.due)) then
|
||||
visible = false
|
||||
break
|
||||
end
|
||||
elseif pred == 'today' then
|
||||
if not (task.status == 'pending' and task.due and parse.is_today(task.due)) then
|
||||
visible = false
|
||||
break
|
||||
end
|
||||
elseif pred == 'priority' then
|
||||
if not (task.priority and task.priority > 0) then
|
||||
visible = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
if not visible then
|
||||
hidden[task.id] = true
|
||||
end
|
||||
end
|
||||
return hidden
|
||||
end
|
||||
|
||||
---@return integer bufnr
|
||||
function M.open()
|
||||
local bufnr = buffer.open()
|
||||
|
|
@ -102,6 +143,30 @@ function M.open()
|
|||
return bufnr
|
||||
end
|
||||
|
||||
---@param pred_str string
|
||||
---@return nil
|
||||
function M.filter(pred_str)
|
||||
if pred_str == 'clear' or pred_str == '' then
|
||||
buffer.set_filter({}, {})
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr then
|
||||
buffer.render(bufnr)
|
||||
end
|
||||
return
|
||||
end
|
||||
local predicates = {}
|
||||
for word in pred_str:gmatch('%S+') do
|
||||
table.insert(predicates, word)
|
||||
end
|
||||
local tasks = store.active_tasks()
|
||||
local hidden = compute_hidden_ids(tasks, predicates)
|
||||
buffer.set_filter(predicates, hidden)
|
||||
local bufnr = buffer.bufnr()
|
||||
if bufnr then
|
||||
buffer.render(bufnr)
|
||||
end
|
||||
end
|
||||
|
||||
---@param bufnr integer
|
||||
---@return nil
|
||||
function M._setup_autocmds(bufnr)
|
||||
|
|
@ -246,13 +311,27 @@ end
|
|||
---@return nil
|
||||
function M._on_write(bufnr)
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local predicates = buffer.filter_predicates()
|
||||
if lines[1] and lines[1]:match('^FILTER:') then
|
||||
local pred_str = lines[1]:match('^FILTER:%s*(.*)$') or ''
|
||||
predicates = {}
|
||||
for word in pred_str:gmatch('%S+') do
|
||||
table.insert(predicates, word)
|
||||
end
|
||||
lines = vim.list_slice(lines, 2)
|
||||
elseif #buffer.filter_predicates() > 0 then
|
||||
predicates = {}
|
||||
end
|
||||
local tasks = store.active_tasks()
|
||||
local hidden = compute_hidden_ids(tasks, predicates)
|
||||
buffer.set_filter(predicates, hidden)
|
||||
local snapshot = store.snapshot()
|
||||
local stack = store.undo_stack()
|
||||
table.insert(stack, snapshot)
|
||||
if #stack > UNDO_MAX then
|
||||
table.remove(stack, 1)
|
||||
end
|
||||
diff.apply(lines)
|
||||
diff.apply(lines, hidden)
|
||||
M._recompute_counts()
|
||||
buffer.render(bufnr)
|
||||
end
|
||||
|
|
@ -718,6 +797,8 @@ function M.command(args)
|
|||
M.archive(d)
|
||||
elseif cmd == 'due' then
|
||||
M.due()
|
||||
elseif cmd == 'filter' then
|
||||
M.filter(rest)
|
||||
elseif cmd == 'undo' then
|
||||
M.undo_write()
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue