------------------------------------------------------------------------------- -- Tools to deal with tasks ------------------------------------------------------------------------------- local Tasks = {} local Configuration = require('configuration') local Notes = require('notes') local Util = require('util') local api = vim.api ------------------------------------------------------------------------------- -- Internal Functions ------------------------------------------------------------------------------- local unchecked_pattern = '^(%s*)%- %[ %]' local unchecked_important_pattern = '^(%s*)%* %[ %]' local checked_pattern = '^(%s*)%- %[x%]' local checked_important_pattern = '^(%s*)%* %[x%]' local function is_open_task(line) return line:match(unchecked_pattern) or line:match(unchecked_important_pattern) end local function is_completed_task(line) return line:match(checked_pattern) or line:match(checked_important_pattern) end local function is_task(line) return is_open_task(line) or is_completed_task(line) end local function open(path) local parent = Util.directory_name(path) Util.ensure_directory_exists(parent) vim.cmd('edit ' .. path) end local function open_inbox() local path = Configuration.path_for(Configuration.configuration.tasks.inbox) open(path) end local function open_someday() local path = Configuration.path_for(Configuration.configuration.tasks.someday) open(path) end local function parse_tags(tag_string) local tags = {} for tag in tag_string:gmatch('([^,]+)') do table.insert(tags, tag) end return tags end local function table_contains_value(target_table, value) for _, table_value in ipairs(target_table) do if table_value == value then return true end end return false end local function remove_value(target_table, value) for i, table_value in ipairs(target_table) do if table_value == value then table.remove(target_table, i) end end end ------------------------------------------------------------------------------- -- Public Interface ------------------------------------------------------------------------------- --- Toggles a task completion status function Tasks.toggle() local line_number = api.nvim_win_get_cursor(0)[1] local line = api.nvim_get_current_line() if line:match(unchecked_pattern) then line = line:gsub(unchecked_pattern, '%1- [x]', 1) elseif line:match(unchecked_important_pattern) then line = line:gsub(unchecked_important_pattern, '%1* [x]', 1) elseif line:match(checked_pattern) then line = line:gsub(checked_pattern, '%1- [ ]', 1) elseif line:match(checked_important_pattern) then line = line:gsub(checked_important_pattern, '%1* [ ]', 1) else api.nvim_err_writeln('Toggle completion only works on tasks') end api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line}) end --- Toggles a task completion status function Tasks.toggle_importance() local line_number = api.nvim_win_get_cursor(0)[1] local line = api.nvim_get_current_line() if line:match(unchecked_pattern) then line = line:gsub(unchecked_pattern, '%1* [ ]', 1) elseif line:match(unchecked_important_pattern) then line = line:gsub(unchecked_important_pattern, '%1- [ ]', 1) elseif line:match(checked_pattern) then line = line:gsub(checked_pattern, '%1* [x]', 1) elseif line:match(checked_important_pattern) then line = line:gsub(checked_important_pattern, '%1- [x]', 1) else api.nvim_err_writeln('Toggle importance only works on tasks') end api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line}) end --- Inserts a new task at the cursor location function Tasks.insert() vim.cmd('normal! o- [ ] ') vim.cmd('startinsert!') end --- Captures a new task into the inbox function Tasks.capture() local prefix = '- [ ] ' open_inbox() vim.cmd('normal! Go'..prefix) vim.cmd('startinsert!') end --- Tag a task function Tasks.tag(tag) local line_number = api.nvim_win_get_cursor(0)[1] local line = api.nvim_get_current_line() if is_task(line) then local pattern = ':(.*):$' local tag_string = line:match(pattern) or '' if not tag or tag == '' then tag = vim.fn.input('Add new tag for task (' .. tag_string .. '): ') if not tag or tag == '' then return end end local tags = parse_tags(tag_string) if not table_contains_value(tags, tag) then table.insert(tags, tag) local new_tags = table.concat(tags, ',') if line:match(pattern) then line = line:gsub(pattern, ':' .. new_tags .. ':', 1) else line = line .. ' :' .. new_tags .. ':' end api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line}) end else api.nvim_err_writeln('Tagging only works on tasks') end end --- Remove tag on a task function Tasks.remove_tag(tag) local line_number = api.nvim_win_get_cursor(0)[1] local line = api.nvim_get_current_line() if is_task(line) then local pattern = ':(.*):$' local tag_string = line:match(pattern) if not tag_string then api.nvim_err_writeln('No tags to remove') return end if not tag or tag == '' then tag = vim.fn.input('Pick tag to remove (' .. tag_string .. '): ') end local tags = parse_tags(tag_string) remove_value(tags, tag) local new_tags = '' if #tags > 0 then new_tags = ':' .. table.concat(tags, ',') .. ':' end line = line:gsub(pattern, new_tags, 1):gsub('%s+$', '') api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line}) else api.nvim_err_writeln('Tagging only works on tasks') end end --- Reschedule a task for today function Tasks.reschedule_for_today() local today = os.date('%Y-%m-%d') Tasks.reschedule(today) end --- Reschedule a task for tomorrow function Tasks.reschedule_for_tomorrow() local tomorrow = os.date('%Y-%m-%d', os.time() + 24*60*60) Tasks.reschedule(tomorrow) end --- Reschedule a task for someday function Tasks.reschedule_for_someday() Tasks.reschedule('someday') end --- Reschedule a task for an arbitrary date function Tasks.reschedule(new_date) if not new_date or new_date == '' then new_date = vim.fn.input('Reschedule for which date (YYYY-mm-dd): ') end if not Util.is_valid_date(new_date) and new_date ~= 'someday' then api.nvim_err_writeln(new_date .. ' is not a valid date in the format YYYY-mm-dd') return end local line_number = api.nvim_win_get_cursor(0)[1] local buffer = api.nvim_get_current_buf() local line = api.nvim_get_current_line() local filename = vim.fn.expand('%:t:r') if is_open_task(line) then if Util.is_before_today(filename) and line:match(unchecked_pattern) then local rescheduled_line = line:gsub(unchecked_pattern, '%1- [>' .. new_date .. ']', 1) api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {rescheduled_line}) elseif Util.is_before_today(filename) and line:match(unchecked_important_pattern) then local rescheduled_line = line:gsub(unchecked_important_pattern, '%1* [>' .. new_date .. ']', 1) api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {rescheduled_line}) else api.nvim_buf_set_lines(buffer, line_number - 1, line_number, false, {}) end vim.cmd('write') if new_date == 'someday' then open_someday() else Notes.open_daily(new_date) end local line_count = api.nvim_buf_line_count(0) api.nvim_buf_set_lines(0, line_count, line_count, false, {line}) api.nvim_win_set_cursor(0, {line_count, 0}) else api.nvim_err_writeln('Reschedule only works on open tasks') end end return Tasks