]> git.r.bdr.sh - rbdr/nota.nvim/blame - lua/tasks.lua
Add tagging
[rbdr/nota.nvim] / lua / tasks.lua
CommitLineData
56292c79
RBR
1-------------------------------------------------------------------------------
2-- Tools to deal with tasks
3-------------------------------------------------------------------------------
503d09fc
RBR
4local Tasks = {}
5local Configuration = require('configuration')
2cc29448
RBR
6local Notes = require('notes')
7local Util = require('util')
503d09fc
RBR
8local api = vim.api
9-------------------------------------------------------------------------------
10-- Internal Functions
11-------------------------------------------------------------------------------
2cc29448
RBR
12local unchecked_pattern = '^(%s*)%- %[ %]'
13local unchecked_important_pattern = '^(%s*)%* %[ %]'
14local checked_pattern = '^(%s*)%- %[x%]'
15local checked_important_pattern = '^(%s*)%* %[x%]'
16
9e9e89d8
RBR
17local function is_open_task(line)
18 return line:match(unchecked_pattern) or line:match(unchecked_important_pattern)
19end
20
21local function is_completed_task(line)
22 return line:match(checked_pattern) or line:match(checked_important_pattern)
23end
24
25local function is_task(line)
26 return is_open_task(line) or is_completed_task(line)
27end
28
2cc29448
RBR
29local function open(path)
30 local parent = Util.directory_name(path)
31 Util.ensure_directory_exists(parent)
32 vim.cmd('edit ' .. path)
33end
34
35local function open_inbox()
36 local path = Configuration.path_for(Configuration.configuration.tasks.inbox)
37 open(path)
38end
9e9e89d8 39
2cc29448
RBR
40local function open_someday()
41 local path = Configuration.path_for(Configuration.configuration.tasks.someday)
42 open(path)
43end
9e9e89d8
RBR
44
45local 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
51end
52
53local 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
60end
61
62local 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
68end
69
56292c79
RBR
70-------------------------------------------------------------------------------
71-- Public Interface
72-------------------------------------------------------------------------------
73
74--- Toggles a task completion status
503d09fc
RBR
75function Tasks.toggle()
76 local line_number = api.nvim_win_get_cursor(0)[1]
77 local line = api.nvim_get_current_line()
78
503d09fc
RBR
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)
9e9e89d8
RBR
87 else
88 api.nvim_err_writeln('Toggle completion only works on tasks')
503d09fc
RBR
89 end
90
91 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
92end
93
94--- Toggles a task completion status
95function Tasks.toggle_importance()
96 local line_number = api.nvim_win_get_cursor(0)[1]
97 local line = api.nvim_get_current_line()
98
503d09fc
RBR
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)
9e9e89d8
RBR
107 else
108 api.nvim_err_writeln('Toggle importance only works on tasks')
503d09fc
RBR
109 end
110
111 api.nvim_buf_set_lines(0, line_number - 1, line_number, false, {line})
56292c79
RBR
112end
113
114--- Inserts a new task at the cursor location
503d09fc
RBR
115function Tasks.insert()
116 vim.cmd('normal! o- [ ] ')
117 vim.cmd('startinsert!')
56292c79
RBR
118end
119
120--- Captures a new task into the inbox
503d09fc 121function Tasks.capture()
2cc29448
RBR
122 local prefix = '- [ ] '
123 open_inbox()
124 vim.cmd('normal! Go'..prefix)
125 vim.cmd('startinsert!')
56292c79
RBR
126end
127
128--- Tag a task
9e9e89d8
RBR
129function 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
155end
156
157--- Remove tag on a task
158function 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
56292c79
RBR
186end
187
188--- Reschedule a task for today
503d09fc 189function Tasks.reschedule_for_today()
2cc29448
RBR
190 local today = os.date('%Y-%m-%d')
191 Tasks.reschedule(today)
56292c79
RBR
192end
193
194--- Reschedule a task for tomorrow
503d09fc 195function Tasks.reschedule_for_tomorrow()
2cc29448
RBR
196 local tomorrow = os.date('%Y-%m-%d', os.time() + 24*60*60)
197 Tasks.reschedule(tomorrow)
56292c79
RBR
198end
199
200--- Reschedule a task for someday
503d09fc 201function Tasks.reschedule_for_someday()
2cc29448 202 Tasks.reschedule('someday')
56292c79
RBR
203end
204
205--- Reschedule a task for an arbitrary date
503d09fc 206function Tasks.reschedule(new_date)
2cc29448
RBR
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
9e9e89d8 222 if is_open_task(line) then
2cc29448
RBR
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
56292c79
RBR
247end
248
503d09fc 249return Tasks