]>
Commit | Line | Data |
---|---|---|
56292c79 RBR |
1 | ------------------------------------------------------------------------------- |
2 | -- Tools to deal with tasks | |
3 | ------------------------------------------------------------------------------- | |
503d09fc RBR |
4 | local Tasks = {} |
5 | local Configuration = require('configuration') | |
2cc29448 RBR |
6 | local Notes = require('notes') |
7 | local Util = require('util') | |
503d09fc RBR |
8 | local api = vim.api |
9 | ------------------------------------------------------------------------------- | |
10 | -- Internal Functions | |
11 | ------------------------------------------------------------------------------- | |
2cc29448 RBR |
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 | ||
9e9e89d8 RBR |
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 | ||
2cc29448 RBR |
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 | |
9e9e89d8 | 39 | |
2cc29448 RBR |
40 | local function open_someday() |
41 | local path = Configuration.path_for(Configuration.configuration.tasks.someday) | |
42 | open(path) | |
43 | end | |
9e9e89d8 RBR |
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 | ||
56292c79 RBR |
70 | ------------------------------------------------------------------------------- |
71 | -- Public Interface | |
72 | ------------------------------------------------------------------------------- | |
73 | ||
74 | --- Toggles a task completion status | |
503d09fc RBR |
75 | function 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}) | |
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 | ||
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 |
112 | end |
113 | ||
114 | --- Inserts a new task at the cursor location | |
503d09fc RBR |
115 | function Tasks.insert() |
116 | vim.cmd('normal! o- [ ] ') | |
117 | vim.cmd('startinsert!') | |
56292c79 RBR |
118 | end |
119 | ||
120 | --- Captures a new task into the inbox | |
503d09fc | 121 | function Tasks.capture() |
2cc29448 RBR |
122 | local prefix = '- [ ] ' |
123 | open_inbox() | |
124 | vim.cmd('normal! Go'..prefix) | |
125 | vim.cmd('startinsert!') | |
56292c79 RBR |
126 | end |
127 | ||
128 | --- Tag a task | |
9e9e89d8 RBR |
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 | |
56292c79 RBR |
186 | end |
187 | ||
188 | --- Reschedule a task for today | |
503d09fc | 189 | function Tasks.reschedule_for_today() |
2cc29448 RBR |
190 | local today = os.date('%Y-%m-%d') |
191 | Tasks.reschedule(today) | |
56292c79 RBR |
192 | end |
193 | ||
194 | --- Reschedule a task for tomorrow | |
503d09fc | 195 | function 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 |
198 | end |
199 | ||
200 | --- Reschedule a task for someday | |
503d09fc | 201 | function Tasks.reschedule_for_someday() |
2cc29448 | 202 | Tasks.reschedule('someday') |
56292c79 RBR |
203 | end |
204 | ||
205 | --- Reschedule a task for an arbitrary date | |
503d09fc | 206 | function 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 |
247 | end |
248 | ||
503d09fc | 249 | return Tasks |