X-Git-Url: https://git.r.bdr.sh/rbdr/nota.nvim/blobdiff_plain/56292c7915662bbf721ca8c3d2cee6a04134a9c6..ca10d2a36e3e7d8d28f15f36f0812384606c238d:/lua/tasks.lua?ds=sidebyside diff --git a/lua/tasks.lua b/lua/tasks.lua index 04e70df..33fce8f 100644 --- a/lua/tasks.lua +++ b/lua/tasks.lua @@ -1,59 +1,249 @@ ------------------------------------------------------------------------------- -- 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 --- @param configuration tNotaConfiguration the plugin configuration -function toggle(configuration) - error("Not yet implemented") +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 --- @param configuration tNotaConfiguration the plugin configuration -function insert(configuration) - error("Not yet implemented") +function Tasks.insert() + vim.cmd('normal! o- [ ] ') + vim.cmd('startinsert!') end --- Captures a new task into the inbox --- @param configuration tNotaConfiguration the plugin configuration -function capture(configuration) - error("Not yet implemented") +function Tasks.capture() + local prefix = '- [ ] ' + open_inbox() + vim.cmd('normal! Go'..prefix) + vim.cmd('startinsert!') end --- Tag a task --- @param configuration tNotaConfiguration the plugin configuration -function tag(configuration) - error("Not yet implemented") +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 --- @param configuration tNotaConfiguration the plugin configuration -function reschedule_for_today(configuration) - error("Not yet implemented") +function Tasks.reschedule_for_today() + local today = os.date('%Y-%m-%d') + Tasks.reschedule(today) end --- Reschedule a task for tomorrow --- @param configuration tNotaConfiguration the plugin configuration -function reschedule_for_tomorrow(configuration) - error("Not yet implemented") +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 --- @param configuration tNotaConfiguration the plugin someday -function reschedule_for_someday(configuration) - error("Not yet implemented") +function Tasks.reschedule_for_someday() + Tasks.reschedule('someday') end --- Reschedule a task for an arbitrary date --- @param configuration tNotaConfiguration the plugin someday -function reschedule(configuration, new_date) - error("Not yet implemented") -end +function Tasks.reschedule(new_date) -------------------------------------------------------------------------------- --- Internal Functions -------------------------------------------------------------------------------- + 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