my neovim config, who would've thought

feat: add task management helper

This is heavily inspired by:
https://github.com/linkarzu/dotfiles-latest/blob/50a71a729e7170ea6a4e39abe093891235cc1649/neovim/neobean/lua/config/keymaps.lua#L2275C7-L2275C8

olexsmir.xyz 5adb8be0 f8e32c31

verified
Changed files
+120 -1
after
ftplugin
lua
scratch
+3 -1
after/ftplugin/markdown.lua
··· 6 6 vim.opt_local.concealcursor = "cv" 7 7 8 8 map("n", "<localleader>v", "<cmd>Markview toggle<cr>", true) 9 - map("n", "<localleader>V", "<cmd>Markview Toggle<cr>", true) 9 + map("n", "<localleader>d", function() 10 + require("scratch.tasks").complete() 11 + end)
+117
lua/scratch/tasks.lua
··· 1 + local config = { 2 + label = "done:%Y%m%d-%H%M", 3 + archive_header = "# Archive", 4 + } 5 + 6 + -- TODO: if task has `#next` tag, remove it before moving to the archive 7 + -- TODO: add support for multiple line tasks 8 + -- TODO: show the progress of tasks(if task has subtasks, show in virtual text how many of them is done) 9 + -- sub tasks should be only archived with the parent task 10 + 11 + ---@return string 12 + local function get_done_label() 13 + return os.date(config.label) --[[@as string]] 14 + end 15 + 16 + ---@param str string 17 + ---@return boolean 18 + local function is_task(str) 19 + return str:match "^%s*%- %[[x ]%]" ~= nil 20 + end 21 + 22 + ---@param str string 23 + ---@return boolean 24 + local function check_task_status(str) 25 + return str:match "^(%s*%- )%[x%]" ~= nil 26 + end 27 + 28 + ---@param str string 29 + ---@return string? 30 + local function to_complete_task(str) 31 + local label = get_done_label() 32 + 33 + local task_prefix = str:match "^(%s*%- %[[x ]%])" 34 + if not task_prefix then 35 + return nil 36 + end 37 + 38 + str = task_prefix .. " `" .. label .. "`" .. str:sub(#task_prefix + 1) 39 + str = str:gsub("^(%s*%- )%[%s*%]", "%1[x]") 40 + 41 + return str 42 + end 43 + 44 + ---@param lines string[] 45 + ---@return number? Line of the heading, nil if not found 46 + local function find_archive_heading(lines) 47 + local heading_line = nil 48 + for i, line in ipairs(lines) do 49 + if line:match("^%s*" .. config.archive_header) then 50 + heading_line = i 51 + break 52 + end 53 + end 54 + return heading_line 55 + end 56 + 57 + local tasks = {} 58 + 59 + -- TODO: implement this 60 + function tasks.list_undone() 61 + error "unimplemented" 62 + end 63 + 64 + -- TODO: implement this 65 + function tasks.list_done() 66 + error "unimplemented" 67 + end 68 + 69 + function tasks.complete() 70 + vim.cmd.mkview() -- saves current folds/scroll 71 + 72 + local bufnr = vim.api.nvim_get_current_buf() 73 + local cur_pos = vim.api.nvim_win_get_cursor(0) 74 + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) 75 + 76 + local task_index = cur_pos[1] 77 + 78 + -- if cursor is beyond last line, exit 79 + if task_index > #lines then 80 + vim.cmd.loadview() 81 + return 82 + end 83 + 84 + if not is_task(lines[task_index]) then 85 + vim.notify("Not a task", vim.log.levels.WARN) 86 + vim.cmd.loadview() 87 + return 88 + end 89 + 90 + if check_task_status(lines[task_index]) then 91 + vim.notify("Task already completed", vim.log.levels.ERROR) 92 + vim.cmd.loadview() 93 + return 94 + end 95 + 96 + local archived_heading = find_archive_heading(lines) 97 + if archived_heading == nil then 98 + table.insert(lines, "") 99 + table.insert(lines, config.archive_header) 100 + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) 101 + 102 + archived_heading = #lines 103 + end 104 + 105 + local completed_task = to_complete_task(lines[task_index]) 106 + 107 + table.remove(lines, task_index) 108 + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) 109 + 110 + table.insert(lines, archived_heading, completed_task) 111 + vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, lines) 112 + 113 + vim.cmd "silent update" 114 + vim.cmd.loadview() 115 + end 116 + 117 + return tasks