+1
after/ftplugin/markdown.lua
+1
after/ftplugin/markdown.lua
+1
lua/core/keymaps.lua
+1
lua/core/keymaps.lua
···
12
12
-- notes
13
13
u.map("n", "<leader>ot", "<cmd>e $HOME/org/todo.txt<cr>") --codespell:ignore
14
14
u.map("n", "<leader>oi", "<cmd>e $HOME/org/notes/Inbox/Inbox.md<cr>")
15
+
u.map("n", "<leader>a", require("scratch.tasks").agenda)
15
16
16
17
-- general
17
18
u.map("n", "<leader>q", "<cmd>quit!<cr>")
+114
-6
lua/scratch/tasks.lua
+114
-6
lua/scratch/tasks.lua
···
1
+
---@param name string
2
+
---@return string
3
+
local function task_file(name)
4
+
return vim.fs.joinpath(vim.fn.expand "~/org/notes", name .. ".md")
5
+
end
6
+
1
7
local config = {
2
8
label = "done:%Y%m%d-%H%M",
3
9
archive_header = "# Archive",
10
+
task_files = {
11
+
task_file "TODO",
12
+
task_file "Onasty",
13
+
task_file "GBAC",
14
+
task_file "Projects/gopher.nvim",
15
+
},
4
16
}
5
17
6
-
-- TODO: highlight the `feat:`, `docs:`, `fix:`, probably should be done with `mini.hipatterns`
7
18
-- TODO: add support for multiple line tasks
8
19
-- TODO: undoing tasks, if task is marked as done, and has `done` label, it should replace done with `undone`
9
20
-- TODO: sort tasks under `# Tasks`, and move tasks with `#next` tag, on top
10
21
-- TODO: show the progress of tasks(if task has subtasks, show in virtual text how many of them is done)
11
22
-- sub tasks should be only archived with the parent task
23
+
24
+
---@param fpath string
25
+
---@return string
26
+
local function file_name_from_path(fpath)
27
+
return fpath:match "^.+/(.+)$" or fpath
28
+
end
29
+
30
+
---@param short_name string
31
+
---@return string?
32
+
local function full_file_path_from_short_name(short_name)
33
+
for _, fname in ipairs(config.task_files) do
34
+
if file_name_from_path(fname) == short_name .. ".md" then
35
+
return fname
36
+
end
37
+
end
38
+
return nil
39
+
end
12
40
13
41
---@return string
14
42
local function get_done_label()
···
23
51
24
52
---@param str string
25
53
---@return boolean
54
+
local function has_next_tag(str)
55
+
return str:match "%#next" ~= nil
56
+
end
57
+
58
+
---@param str string
59
+
---@return boolean
26
60
local function check_task_status(str)
27
61
return str:match "^(%s*%- )%[x%]" ~= nil
62
+
end
63
+
64
+
---@param str string
65
+
---@return string
66
+
local function remove_task_prefix(str)
67
+
local res = str:gsub("^%- %[ %] ", "")
68
+
return res
69
+
end
70
+
71
+
---@param fname string
72
+
---@param line number
73
+
---@return string
74
+
local function display_file(fname, line)
75
+
local str = "" .. (fname:match "^(.-)%.%w+$" or fname) .. ":" .. line
76
+
return (str .. string.rep(" ", 14 - #str))
28
77
end
29
78
30
79
-- converts a like with markdown task to completed task, and removes `#next` in it, if there's one
···
60
109
61
110
local tasks = {}
62
111
63
-
function tasks.list_undone()
64
-
error "unimplemented"
65
-
end
112
+
function tasks.agenda()
113
+
-- parse all `task_files` for `#next` tag
114
+
-- FIXME: that's probably should be cached
66
115
67
-
function tasks.list_done()
68
-
error "unimplemented"
116
+
---@type table<string, {task: string, line: number}[]>
117
+
local agenda_tasks = {}
118
+
for _, fname in ipairs(config.task_files) do
119
+
local lines = vim.fn.readfile(fname)
120
+
for i, line in ipairs(lines) do
121
+
if is_task(line) and has_next_tag(line) then
122
+
local short_name = file_name_from_path(fname)
123
+
agenda_tasks[short_name] = agenda_tasks[short_name] or {}
124
+
table.insert(agenda_tasks[short_name], { task = line, line = i })
125
+
end
126
+
end
127
+
end
128
+
129
+
-- build the output
130
+
local output = { "# Agenda" }
131
+
for fname, ftasks in pairs(agenda_tasks) do
132
+
for _, ftask in pairs(ftasks) do
133
+
table.insert(
134
+
output,
135
+
display_file(fname, ftask.line) .. " " .. remove_task_prefix(ftask.task)
136
+
)
137
+
end
138
+
end
139
+
140
+
-- open the agenda view
141
+
vim.cmd.new()
142
+
local buf = vim.api.nvim_get_current_buf()
143
+
vim.api.nvim_buf_set_name(buf, "scratch.tasks:agenda")
144
+
vim.api.nvim_set_option_value("filetype", "markdown", { buf = buf })
145
+
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = buf })
146
+
vim.api.nvim_set_option_value("buftype", "nofile", { buf = buf })
147
+
vim.api.nvim_set_option_value("swapfile", false, { buf = buf })
148
+
vim.api.nvim_buf_set_lines(buf, 0, -1, false, output)
149
+
vim.api.nvim_set_option_value("modifiable", false, { buf = buf })
150
+
vim.api.nvim_exec_autocmds("FileType", { buffer = buf }) -- loads the ftplugins
151
+
vim.api.nvim_win_set_height(0, 10)
152
+
vim.api.nvim_win_set_cursor(0, { 2, 0 })
153
+
154
+
-- FIXME: this should be a separate function
155
+
vim.keymap.set("n", "<CR>", function()
156
+
local line = vim.api.nvim_get_current_line()
157
+
local fname, lineno = line:match "^([^:]+):(%d+)"
158
+
if fname == nil or lineno == nil then
159
+
vim.notify(
160
+
"No file name or line number found in the line",
161
+
vim.log.levels.WARN
162
+
)
163
+
return
164
+
end
165
+
166
+
local fpath = full_file_path_from_short_name(fname)
167
+
if fpath == nil then
168
+
vim.notify("No file name found in the line", vim.log.levels.WARN)
169
+
return
170
+
end
171
+
172
+
vim.cmd.edit(fpath)
173
+
vim.api.nvim_win_set_cursor(0, { tonumber(lineno), 0 })
174
+
end, { buffer = buf, desc = "Open file under cursor", silent = true })
69
175
end
176
+
177
+
tasks.agenda()
70
178
71
179
function tasks.complete()
72
180
vim.cmd.mkview() -- saves current folds/scroll