]> git.r.bdr.sh - rbdr/nota.nvim/blob - lua/tasks.lua
Add tagging
[rbdr/nota.nvim] / lua / tasks.lua
1 -------------------------------------------------------------------------------
2 -- Tools to deal with tasks
3 -------------------------------------------------------------------------------
4 local Tasks = {}
5 local Configuration = require('configuration')
6 local Notes = require('notes')
7 local Util = require('util')
8 local api = vim.api
9 -------------------------------------------------------------------------------
10 -- Internal Functions
11 -------------------------------------------------------------------------------
12 local unchecked_pattern = '^(%s*)%- %[ %]'
13 local unchecked_important_pattern = '^(%s*)%* %[ %]'
14 local checked_pattern = '^(%s*)%- %[x%]'
15 local checked_important_pattern = '^(%s*)%* %[x%]'
16
17 local function is_open_task(line)
18 return line:match(unchecked_pattern) or line:match(unchecked_important_pattern)
19 end
20
21 local function is_completed_task(line)
22 return line:match(checked_pattern) or line:match(checked_important_pattern)
23 end
24
25 local function is_task(line)
26 return is_open_task(line) or is_completed_task(line)
27 end
28
29 local function open(path)
30 local parent = Util.directory_name(path)
31 Util.ensure_directory_exists(parent)
32 vim.cmd('edit ' .. path)
33 end
34
35 local function open_inbox()
36 local path = Configuration.path_for(Configuration.configuration.tasks.inbox)
37 open(path)
38 end
39
40 local function open_someday()
41 local path = Configuration.path_for(Configuration.configuration.tasks.someday)
42 open(path)
43 end
44
45 local function parse_tags(tag_string)
46 local tags = {}
47 for tag in tag_string:gmatch('([^,]+)') do
48 table.insert(tags, tag)
49 end
50 return tags
51 end
52
53 local function table_contains_value(target_table, value)
54 for _, table_value in ipairs(target_table) do
55 if table_value == value then
56 return true
57 end
58 end
59 return false
60 end
61
62 local function remove_value(target_table, value)
63 for i, table_value in ipairs(target_table) do
64 if table_value == value then
65 table.remove(target_table, i)
66 end
67 end
68 end
69
70 -------------------------------------------------------------------------------
71 -- Public Interface
72 -------------------------------------------------------------------------------
73
74 --- Toggles a task completion status
75 function Tasks.toggle()
76 local line_number = api.nvim_win_get_cursor(0)[1]
77 local line = api.nvim_get_current_line()
78
79 if line:match(unchecked_pattern) then
80 line = line:gsub(unchecked_pattern, '%1- [x]', 1)
81 elseif line:match(unchecked_important_pattern) then
82 line = line:gsub(unchecked_important_pattern, '%1* [x]', 1)
83 elseif line:match(checked_pattern) then
84 line = line:gsub(checked_pattern, '%1- [ ]', 1)
85 elseif line:match(checked_important_pattern) then
86 line = line:gsub(checked_important_pattern, '%1* [ ]', 1)
87 else
88 api.nvim_err_writeln('Toggle completion only works on tasks')
89 end
90
91 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
92 end
93
94 --- Toggles a task completion status
95 function Tasks.toggle_importance()
96 local line_number = api.nvim_win_get_cursor(0)[1]
97 local line = api.nvim_get_current_line()
98
99 if line:match(unchecked_pattern) then
100 line = line:gsub(unchecked_pattern, '%1* [ ]', 1)
101 elseif line:match(unchecked_important_pattern) then
102 line = line:gsub(unchecked_important_pattern, '%1- [ ]', 1)
103 elseif line:match(checked_pattern) then
104 line = line:gsub(checked_pattern, '%1* [x]', 1)
105 elseif line:match(checked_important_pattern) then
106 line = line:gsub(checked_important_pattern, '%1- [x]', 1)
107 else
108 api.nvim_err_writeln('Toggle importance only works on tasks')
109 end
110
111 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
112 end
113
114 --- Inserts a new task at the cursor location
115 function Tasks.insert()
116 vim.cmd('normal! o- [ ] ')
117 vim.cmd('startinsert!')
118 end
119
120 --- Captures a new task into the inbox
121 function Tasks.capture()
122 local prefix = '- [ ] '
123 open_inbox()
124 vim.cmd('normal! Go'..prefix)
125 vim.cmd('startinsert!')
126 end
127
128 --- Tag a task
129 function Tasks.tag(tag)
130 local line_number = api.nvim_win_get_cursor(0)[1]
131 local line = api.nvim_get_current_line()
132 if is_task(line) then
133 local pattern = ':(.*):$'
134 local tag_string = line:match(pattern) or ''
135 if not tag or tag == '' then
136 tag = vim.fn.input('Add new tag for task (' .. tag_string .. '): ')
137 if not tag or tag == '' then
138 return
139 end
140 end
141 local tags = parse_tags(tag_string)
142 if not table_contains_value(tags, tag) then
143 table.insert(tags, tag)
144 local new_tags = table.concat(tags, ',')
145 if line:match(pattern) then
146 line = line:gsub(pattern, ':' .. new_tags .. ':', 1)
147 else
148 line = line .. ' :' .. new_tags .. ':'
149 end
150 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
151 end
152 else
153 api.nvim_err_writeln('Tagging only works on tasks')
154 end
155 end
156
157 --- Remove tag on a task
158 function Tasks.remove_tag(tag)
159 local line_number = api.nvim_win_get_cursor(0)[1]
160 local line = api.nvim_get_current_line()
161 if is_task(line) then
162 local pattern = ':(.*):$'
163 local tag_string = line:match(pattern)
164 if not tag_string then
165 api.nvim_err_writeln('No tags to remove')
166 return
167 end
168
169 if not tag or tag == '' then
170 tag = vim.fn.input('Pick tag to remove (' .. tag_string .. '): ')
171 end
172
173 local tags = parse_tags(tag_string)
174 remove_value(tags, tag)
175 local new_tags = ''
176
177 if #tags > 0 then
178 new_tags = ':' .. table.concat(tags, ',') .. ':'
179 end
180
181 line = line:gsub(pattern, new_tags, 1):gsub('%s+$', '')
182 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
183 else
184 api.nvim_err_writeln('Tagging only works on tasks')
185 end
186 end
187
188 --- Reschedule a task for today
189 function Tasks.reschedule_for_today()
190 local today = os.date('%Y-%m-%d')
191 Tasks.reschedule(today)
192 end
193
194 --- Reschedule a task for tomorrow
195 function Tasks.reschedule_for_tomorrow()
196 local tomorrow = os.date('%Y-%m-%d', os.time() + 24*60*60)
197 Tasks.reschedule(tomorrow)
198 end
199
200 --- Reschedule a task for someday
201 function Tasks.reschedule_for_someday()
202 Tasks.reschedule('someday')
203 end
204
205 --- Reschedule a task for an arbitrary date
206 function Tasks.reschedule(new_date)
207
208 if not new_date or new_date == '' then
209 new_date = vim.fn.input('Reschedule for which date (YYYY-mm-dd): ')
210 end
211
212 if not Util.is_valid_date(new_date) and new_date ~= 'someday' then
213 api.nvim_err_writeln(new_date .. ' is not a valid date in the format YYYY-mm-dd')
214 return
215 end
216
217 local line_number = api.nvim_win_get_cursor(0)[1]
218 local buffer = api.nvim_get_current_buf()
219 local line = api.nvim_get_current_line()
220 local filename = vim.fn.expand('%:t:r')
221
222 if is_open_task(line) then
223 if Util.is_before_today(filename) and line:match(unchecked_pattern) then
224 local rescheduled_line = line:gsub(unchecked_pattern, '%1- [>' .. new_date .. ']', 1)
225 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {rescheduled_line})
226 elseif Util.is_before_today(filename) and line:match(unchecked_important_pattern) then
227 local rescheduled_line = line:gsub(unchecked_important_pattern, '%1* [>' .. new_date .. ']', 1)
228 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {rescheduled_line})
229 else
230 api.nvim_buf_set_lines(buffer, line_number - 1, line_number, false, {})
231 end
232
233 vim.cmd('write')
234
235 if new_date == 'someday' then
236 open_someday()
237 else
238 Notes.open_daily(new_date)
239 end
240
241 local line_count = api.nvim_buf_line_count(0)
242 api.nvim_buf_set_lines(0, line_count, line_count, false, {line})
243 api.nvim_win_set_cursor(0, {line_count, 0})
244 else
245 api.nvim_err_writeln('Reschedule only works on open tasks')
246 end
247 end
248
249 return Tasks