init.lua/lua/scratch/tasks.lua(view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
local config = {
label = "done:%Y%m%d-%H%M",
archive_header = "# Archive",
tasks_file = vim.fn.stdpath "config" .. "/tasks.json",
}
-- TODO: multi line tasks support
-- TODO: add support for nested tasks(one level max)
-- completing a nested task will tick it, not archive
-- once the parent task get archived, only then it's "children" gets archived
-- TODO: show progress of task with nested tasks(visual text, with, e.g 5/9)
---@return string[]
local function get_tasks_files()
local f = io.open(config.tasks_file, "r")
if not f then
error("cannot read " .. config.tasks_file)
end
return vim.json.decode(f:read "*a")["files"]
end
local function get_done_label()
return os.date(config.label) --[[@as string]]
end
---@param str string
local function is_task(str)
return str:match "^%s*%- %[[x ]%]" ~= nil
end
---@param str string
local function is_task_labled(str)
return str:match "^%s*%- %[[x ]%] `%" ~= nil
end
---@param str string
local function has_next_tag(str)
return str:match "%#next" ~= nil
end
---@param str string
local function check_task_status(str)
return str:match "^(%s*%- )%[x%]" ~= nil
end
---@param str string
local function remove_task_prefix(str)
local res = str:gsub("^%- %[ %] ", "")
return res
end
---@param str string
local function remove_next_tag(str)
local res = str:gsub(" %#next", "")
return res
end
-- converts a like with markdown task to completed task, and removes `#next` in it, if there's one
---@param str string
local function to_complete_task(str)
local label = get_done_label()
local task_prefix = str:match "^(%s*%- %[[x ]%])"
if not task_prefix then
return nil
end
str = task_prefix .. " `" .. label .. "`" .. str:sub(#task_prefix + 1)
str = str:gsub("^(%s*%- )%[%s*%]", "%1[x]")
str = remove_next_tag(str)
str = str:gsub("%s+$", "")
return str
end
---@param lines string[]
---@return number? - Line of the heading, nil if not found
local function find_archive_heading(lines)
local heading_line = nil
for i, line in ipairs(lines) do
if line:match("^%s*" .. config.archive_header) then
heading_line = i
break
end
end
return heading_line
end
local tasks = {}
function tasks.agenda()
-- parse all `task_files` for `#next` tag
---@type table<string, {text: string, line: number}[]>
local agenda_tasks = {}
for _, fname in ipairs(get_tasks_files()) do
local lines = vim.fn.readfile(fname)
for i, line in ipairs(lines) do
if is_task(line) and has_next_tag(line) then
agenda_tasks[fname] = agenda_tasks[fname] or {}
table.insert(agenda_tasks[fname], {
text = line,
line = i,
})
end
end
end
-- build the output
local output = {}
for fname, ftasks in pairs(agenda_tasks) do
for _, ftask in pairs(ftasks) do
local task = remove_next_tag(ftask.text)
task = remove_task_prefix(task)
table.insert(output, {
lnum = ftask.line,
filename = fname,
text = task,
}--[[ @as vim.quickfix.entry ]])
end
end
vim.fn.setqflist(output, "r")
vim.cmd.copen()
end
function tasks.complete()
local bufnr = vim.api.nvim_get_current_buf()
local task_idx = vim.api.nvim_win_get_cursor(0)[1]
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local task = lines[task_idx]
if not is_task(task) then
vim.notify("Not a task", vim.log.levels.WARN)
return
end
if check_task_status(task) and is_task_labled(task) then
vim.notify("Task already completed", vim.log.levels.ERROR)
return
end
local archived_heading = find_archive_heading(lines)
if archived_heading == nil then
table.insert(lines, "")
table.insert(lines, config.archive_header)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
archived_heading = #lines
end
local completed_task = to_complete_task(task)
table.remove(lines, task_idx)
table.insert(lines, archived_heading, completed_task)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines)
vim.cmd.update()
end
return tasks
|