my neovim config, who would've thought

tasks: quickfix, #n as #next, keymaps

- implement custom formatting for qf
- threat #n as #next
- some small refactoring
- add keymaps for new task, and tagging as #next
- add keymaps to open todo and inbox files

olexsmir.xyz 7a6a728e ebdcb5d9

verified
Changed files
+52 -22
after
ftplugin
lua
core
scratch
+3 -1
after/ftplugin/markdown.lua
··· 8 map("n", "<localleader>v", "<cmd>RenderMarkdown toggle<cr>", true) 9 map("n", "<localleader>t", require("scratch.tasks").complete, true) 10 map("n", "<localleader>c", require("scratch.tasks").clear_archive, true) 11 12 vim.b.minihipatterns_config = { 13 highlighters = { 14 - next = { pattern = "%#next", group = "MiniTestPass" }, 15 front = { pattern = "front%:", group = "@boolean" }, 16 feat = { pattern = "feat%:", group = "@boolean" }, 17 chore = { pattern = "chore%:", group = "@character" },
··· 8 map("n", "<localleader>v", "<cmd>RenderMarkdown toggle<cr>", true) 9 map("n", "<localleader>t", require("scratch.tasks").complete, true) 10 map("n", "<localleader>c", require("scratch.tasks").clear_archive, true) 11 + map("n", "<localleader>a", "<cmd>norm o- [ ] <cr>a", true) 12 + map("n", "<localleader>n", "<cmd>norm A #n<cr>", true) 13 14 vim.b.minihipatterns_config = { 15 highlighters = { 16 + next = { pattern = "%#n[ext]*", group = "MiniTestPass" }, 17 front = { pattern = "front%:", group = "@boolean" }, 18 feat = { pattern = "feat%:", group = "@boolean" }, 19 chore = { pattern = "chore%:", group = "@character" },
+2
lua/core/keymaps.lua
··· 10 11 -- notes 12 u.map("n", "<leader>a", require("scratch.tasks").agenda) 13 14 -- general 15 u.map({ "n", "x" }, "<leader>z", "1z=")
··· 10 11 -- notes 12 u.map("n", "<leader>a", require("scratch.tasks").agenda) 13 + u.map("n", "<leader>nt", "<cmd>e ~/org/notes/TODO.md<cr>") 14 + u.map("n", "<leader>nn", "<cmd>e ~/org/notes/0Inbox.md<cr>") 15 16 -- general 17 u.map({ "n", "x" }, "<leader>z", "1z=")
+47 -21
lua/scratch/tasks.lua
··· 4 tasks_file = vim.fn.stdpath "config" .. "/tasks.json", 5 } 6 7 - -- TODO: multi line tasks support 8 - -- TODO: add support for nested tasks(one level max) 9 - -- completing a nested task will tick it, not archive 10 - -- once the parent task get archived, only then it's "children" gets archived 11 - -- TODO: show progress of task with nested tasks(visual text, with, e.g 5/9) 12 13 ---@return string[] 14 local function get_tasks_files() ··· 19 return vim.json.decode(f:read "*a")["files"] or error "'files' is not found" 20 end 21 22 ---@param str string 23 local function is_task(str) 24 return str:match "^%s*%- %[[x ]%]" ~= nil ··· 31 32 ---@param str string 33 local function has_next_tag(str) 34 - return str:match "%#next" ~= nil 35 end 36 37 ---@param str string ··· 40 end 41 42 ---@param str string 43 - local function remove_task_prefix(str) 44 - local res = str:gsub("^%- %[ %] ", "") 45 - return res 46 - end 47 - 48 - ---@param str string 49 - local function remove_note_link(str) 50 local res = str:gsub("%[%[(.-)%]%]", "[%1]") 51 return res 52 end 53 54 ---@param str string 55 local function remove_next_tag(str) 56 - local res = str:gsub(" %#next", "") 57 return res 58 end 59 ··· 66 67 local label = os.date(config.label) --[[@as string]] 68 str = task_prefix .. " `" .. label .. "`" .. str:sub(#task_prefix + 1) 69 - str = str:gsub("^(%s*%- )%[%s*%]", "%1[x]") 70 - str = remove_note_link(str) 71 str = remove_next_tag(str) 72 - str = str:gsub("%s+$", "") 73 return str 74 end 75 ··· 83 84 local tasks = {} 85 function tasks.agenda() 86 - local qf_output = vim 87 .iter(get_tasks_files()) 88 :map(function(fname) 89 return vim ··· 93 end) 94 :map(function(lnum, line) 95 local task = remove_next_tag(line) 96 - task = remove_task_prefix(task) 97 - task = remove_note_link(task) 98 99 return { 100 lnum = lnum, 101 filename = fname, 102 text = task, 103 } --[[@as vim.quickfix.entry]] 104 end) 105 :totable() ··· 107 :flatten() 108 :totable() 109 110 - vim.fn.setqflist(qf_output, "r") 111 vim.cmd.copen() 112 end 113
··· 4 tasks_file = vim.fn.stdpath "config" .. "/tasks.json", 5 } 6 7 + local ns = vim.api.nvim_create_namespace "scratch.tasks" 8 9 ---@return string[] 10 local function get_tasks_files() ··· 15 return vim.json.decode(f:read "*a")["files"] or error "'files' is not found" 16 end 17 18 + local function display_filename(str) 19 + local res = vim.fs.basename(str) 20 + res = res:gsub("%.%w+$", "") 21 + return res 22 + end 23 + 24 ---@param str string 25 local function is_task(str) 26 return str:match "^%s*%- %[[x ]%]" ~= nil ··· 33 34 ---@param str string 35 local function has_next_tag(str) 36 + return str:match "%#n[ext]*" ~= nil 37 end 38 39 ---@param str string ··· 42 end 43 44 ---@param str string 45 + local function remove_task_link(str) 46 local res = str:gsub("%[%[(.-)%]%]", "[%1]") 47 return res 48 end 49 50 ---@param str string 51 local function remove_next_tag(str) 52 + local res = str:gsub(" %#n[ext]*", "") 53 return res 54 end 55 ··· 62 63 local label = os.date(config.label) --[[@as string]] 64 str = task_prefix .. " `" .. label .. "`" .. str:sub(#task_prefix + 1) 65 + str = str:gsub("^(%s*%- )%[%s*%]", "%1[x]") -- mark task as complete 66 + str = remove_task_link(str) 67 str = remove_next_tag(str) 68 + str = str:gsub("%s+$", "") -- white space in the end 69 return str 70 end 71 ··· 79 80 local tasks = {} 81 function tasks.agenda() 82 + local qf_items = vim 83 .iter(get_tasks_files()) 84 :map(function(fname) 85 return vim ··· 89 end) 90 :map(function(lnum, line) 91 local task = remove_next_tag(line) 92 + task = remove_task_link(task) 93 + task = task:gsub("^%- %[ %] ", "") -- remove task prefix 94 + task = task:gsub("`", "") 95 96 return { 97 lnum = lnum, 98 filename = fname, 99 text = task, 100 + user_data = { filename = fname }, 101 } --[[@as vim.quickfix.entry]] 102 end) 103 :totable() ··· 105 :flatten() 106 :totable() 107 108 + vim.fn.setqflist({}, "r", { 109 + nr = "$", 110 + title = "scratch.tasks", 111 + items = qf_items, 112 + quickfixtextfunc = function(info) 113 + local items = vim.fn.getqflist({ id = info.id, items = 1 }).items 114 + local lines, highlights = {}, {} 115 + for item = info.start_idx, info.end_idx do 116 + local entry = items[item] 117 + local fname = display_filename(entry.user_data.filename) 118 + local fname_paded = fname .. string.rep(" ", 7 - #fname) .. " " 119 + 120 + table.insert(lines, fname_paded .. entry.text) 121 + table.insert(highlights, { fname_len = #fname_paded }) 122 + end 123 + 124 + vim.schedule(function() 125 + vim.api.nvim_buf_clear_namespace(0, ns, 0, -1) 126 + for i, hl in ipairs(highlights) do 127 + local line = info.start_idx + i - 2 128 + vim.hl.range(0, ns, "qfFileName", { line, 0 }, { line, hl.fname_len }) 129 + vim.hl.range(0, ns, "Bold", { line, hl.fname_len }, { line, -1 }) 130 + end 131 + end) 132 + 133 + return lines 134 + end, 135 + }) 136 + 137 vim.cmd.copen() 138 end 139