Commit | Line | Data |
---|---|---|
ee7412e4 | 1 | ;;; Todos.el --- major mode for displaying and editing Todo lists |
3f031767 SB |
2 | |
3 | ;; Copyright (C) 1997, 1999, 2001, 2002, 2003, 2004, 2005, 2006, 2007, | |
4 | ;; 2008, 2009 Free Software Foundation, Inc. | |
5 | ||
6 | ;; Author: Oliver Seidel <privat@os10000.net> | |
7 | ;; Maintainer: Stephen Berman <stephen.berman@gmx.net> | |
8 | ;; Created: 2 Aug 1997 | |
9 | ;; Keywords: calendar, todo | |
10 | ||
11 | ;; This file is part of GNU Emacs. | |
12 | ||
13 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
14 | ;; it under the terms of the GNU General Public License as published by | |
15 | ;; the Free Software Foundation, either version 3 of the License, or | |
16 | ;; (at your option) any later version. | |
17 | ||
18 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
19 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
20 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
21 | ;; GNU General Public License for more details. | |
22 | ||
23 | ;; You should have received a copy of the GNU General Public License | |
24 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
25 | ||
26 | ;; --------------------------------------------------------------------------- | |
27 | ||
28 | ;;; Commentary: | |
29 | ||
30 | ;; Mode Description | |
31 | ;; | |
32 | ;; TODO is a major mode for EMACS which offers functionality to | |
33 | ;; treat most lines in one buffer as a list of items one has to | |
34 | ;; do. There are facilities to add new items, which are | |
35 | ;; categorised, to edit or even delete items from the buffer. | |
36 | ;; The buffer contents are currently compatible with the diary, | |
37 | ;; so that the list of todos-items will show up in the FANCY diary | |
38 | ;; mode. | |
39 | ;; | |
40 | ;; Notice: Besides the major mode, this file also exports the | |
41 | ;; function `todos-show' which will change to the one specific | |
42 | ;; TODO file that has been specified in the todos-file-do | |
43 | ;; variable. If this file does not conform to the TODO mode | |
44 | ;; conventions, the todos-show function will add the appropriate | |
45 | ;; header and footer. I don't anticipate this to cause much | |
46 | ;; grief, but be warned, in case you attempt to read a plain text | |
47 | ;; file. | |
48 | ;; | |
49 | ;; Preface, Quickstart Installation | |
50 | ;; | |
ee7412e4 | 51 | ;; To get this to work, make Emacs execute the line |
3f031767 SB |
52 | ;; |
53 | ;; (autoload 'todos "todos" | |
54 | ;; "Major mode for editing TODO lists." t) | |
55 | ;; (autoload 'todos-show "todos" | |
56 | ;; "Show TODO items." t) | |
57 | ;; (autoload 'todos-insert-item "todos" | |
58 | ;; "Add TODO item." t) | |
59 | ;; | |
60 | ;; You may now enter new items by typing "M-x todos-insert-item", | |
61 | ;; or enter your TODO list file by typing "M-x todos-show". | |
62 | ;; | |
63 | ;; The TODO list file has a special format and some auxiliary | |
64 | ;; information, which will be added by the todos-show function if | |
65 | ;; it attempts to visit an un-initialised file. Hence it is | |
66 | ;; recommended to use the todos-show function for the first time, | |
67 | ;; in order to initialise the file, but it is not necessary | |
68 | ;; afterwards. | |
69 | ;; | |
70 | ;; As these commands are quite long to type, I would recommend | |
71 | ;; the addition of two bindings to your to your global keymap. I | |
72 | ;; personally have the following in my initialisation file: | |
73 | ;; | |
74 | ;; (global-set-key "\C-ct" 'todos-show) ; switch to TODO buffer | |
75 | ;; (global-set-key "\C-ci" 'todos-insert-item) ; insert new item | |
76 | ;; | |
77 | ;; Note, however, that this recommendation has prompted some | |
78 | ;; criticism, since the keys C-c LETTER are reserved for user | |
79 | ;; functions. I believe my recommendation is acceptable, since | |
80 | ;; the Emacs Lisp Manual *Tips* section also details that the | |
81 | ;; mode itself should not bind any functions to those keys. The | |
82 | ;; express aim of the above two bindings is to work outside the | |
83 | ;; mode, which doesn't need the show function and offers a | |
84 | ;; different binding for the insert function. They serve as | |
85 | ;; shortcuts and are not even needed (since the TODO mode will be | |
86 | ;; entered by visiting the TODO file, and later by switching to | |
87 | ;; its buffer). | |
88 | ;; | |
89 | ;; If you are an advanced user of this package, please consult | |
90 | ;; the whole source code for autoloads, because there are several | |
91 | ;; extensions that are not explicitly listed in the above quick | |
92 | ;; installation. | |
93 | ;; | |
94 | ;; Pre-Requisites | |
95 | ;; | |
96 | ;; This package will require the following packages to be | |
97 | ;; available on the load-path: | |
98 | ;; | |
99 | ;; time-stamp | |
100 | ;; easymenu | |
101 | ;; | |
102 | ;; Operation | |
103 | ;; | |
104 | ;; You will have the following facilities available: | |
105 | ;; | |
106 | ;; M-x todos-show will enter the todo list screen, here type | |
107 | ;; | |
108 | ;; + to go to next category | |
109 | ;; - to go to previous category | |
110 | ;; d to file the current entry, including a | |
111 | ;; comment and timestamp | |
112 | ;; e to edit the current entry | |
113 | ;; E to edit a multi-line entry | |
114 | ;; f to file the current entry, including a | |
115 | ;; comment and timestamp | |
116 | ;; i to insert a new entry, with prefix, omit category | |
117 | ;; I to insert a new entry at current cursor position | |
118 | ;; j jump to category | |
119 | ;; k to kill the current entry | |
120 | ;; l to lower the current entry's priority | |
121 | ;; n for the next entry | |
122 | ;; p for the previous entry | |
123 | ;; P print | |
124 | ;; q to save the list and exit the buffer | |
125 | ;; r to raise the current entry's priority | |
126 | ;; s to save the list | |
127 | ;; S to save the list of top priorities | |
128 | ;; t show top priority items for each category | |
129 | ;; | |
130 | ;; When you add a new entry, you are asked for the text and then | |
131 | ;; for the category. I for example have categories for things | |
132 | ;; that I want to do in the office (like mail my mum), that I | |
133 | ;; want to do in town (like buy cornflakes) and things I want to | |
134 | ;; do at home (move my suitcases). The categories can be | |
135 | ;; selected with the cursor keys and if you type in the name of a | |
136 | ;; category which didn't exist before, an empty category of the | |
137 | ;; desired name will be added and filled with the new entry. | |
138 | ;; | |
139 | ;; Configuration | |
140 | ;; | |
141 | ;; Variable todos-prefix | |
142 | ;; | |
143 | ;; I would like to recommend that you use the prefix "*/*" (by | |
144 | ;; leaving the variable 'todos-prefix' untouched) so that the | |
145 | ;; diary displays each entry every day. | |
146 | ;; | |
147 | ;; To understand what I mean, please read the documentation that | |
148 | ;; goes with the calendar since that will tell you how you can | |
149 | ;; set up the fancy diary display and use the #include command to | |
150 | ;; include your todo list file as part of your diary. | |
151 | ;; | |
152 | ;; If you have the diary package set up to usually display more | |
153 | ;; than one day's entries at once, consider using | |
154 | ;; | |
155 | ;; "&%%(equal (calendar-current-date) date)" | |
156 | ;; | |
157 | ;; as the value of `todos-prefix'. Please note that this may slow | |
158 | ;; down the processing of your diary file some. | |
159 | ;; | |
160 | ;; Carsten Dominik <dominik@strw.LeidenUniv.nl> suggested that | |
161 | ;; | |
162 | ;; "&%%(todos-cp)" | |
163 | ;; | |
164 | ;; might be nicer and to that effect a function has been declared | |
165 | ;; further down in the code. You may wish to auto-load this. | |
166 | ;; | |
167 | ;; Carsten also writes that that *changing* the prefix after the | |
168 | ;; todo list is already established is not as simple as changing | |
169 | ;; the variable - the todo files have to be changed by hand. | |
170 | ;; | |
171 | ;; Variable todos-file-do | |
172 | ;; | |
173 | ;; This variable is fairly self-explanatory. You have to store | |
174 | ;; your TODO list somewhere. This variable tells the package | |
175 | ;; where to go and find this file. | |
176 | ;; | |
177 | ;; Variable todos-file-done | |
178 | ;; | |
179 | ;; Even when you're done, you may wish to retain the entries. | |
180 | ;; Given that they're timestamped and you are offered to add a | |
181 | ;; comment, this can make a useful diary of past events. It will | |
182 | ;; even blend in with the EMACS diary package. So anyway, this | |
183 | ;; variable holds the name of the file for the filed todos-items. | |
184 | ;; | |
185 | ;; Variable todos-file-top | |
186 | ;; | |
187 | ;; File storing the top priorities of your TODO list when | |
188 | ;; todos-save-top-priorities is non-nil. Nice to include in your | |
189 | ;; diary instead of the complete TODO list. | |
190 | ;; | |
191 | ;; Variable todos-mode-hook | |
192 | ;; | |
193 | ;; Just like other modes, too, this mode offers to call your | |
194 | ;; functions before it goes about its business. This variable | |
195 | ;; will be inspected for any functions you may wish to have | |
196 | ;; called once the other TODO mode preparations have been | |
197 | ;; completed. | |
198 | ;; | |
199 | ;; Variable todos-insert-threshold | |
200 | ;; | |
201 | ;; Another nifty feature is the insertion accuracy. If you have | |
202 | ;; 8 items in your TODO list, then you may get asked 4 questions | |
203 | ;; by the binary insertion algorithm. However, you may not | |
204 | ;; really have a need for such accurate priorities amongst your | |
205 | ;; TODO items. If you now think about the binary insertion | |
206 | ;; halving the size of the window each time, then the threshold | |
207 | ;; is the window size at which it will stop. If you set the | |
208 | ;; threshold to zero, the upper and lower bound will coincide at | |
209 | ;; the end of the loop and you will insert your item just before | |
210 | ;; that point. If you set the threshold to, e.g. 8, it will stop | |
211 | ;; as soon as the window size drops below that amount and will | |
212 | ;; insert the item in the approximate center of that window. I | |
213 | ;; got the idea for this feature after reading a very helpful | |
214 | ;; e-mail reply from Trey Jackson <trey@cs.berkeley.edu> who | |
215 | ;; corrected some of my awful coding and pointed me towards some | |
216 | ;; good reading. Thanks Trey! | |
217 | ;; | |
218 | ;; Things to do | |
219 | ;; | |
220 | ;; These originally were my ideas, but now also include all the | |
221 | ;; suggestions that I included before forgetting them: | |
222 | ;; | |
223 | ;; o Fancy fonts for todo/top-priority buffer | |
224 | ;; o Remove todos-prefix option in todos-top-priorities | |
225 | ;; o Rename category | |
226 | ;; o Move entry from one category to another one | |
227 | ;; o Entries which both have the generic */* prefix and a | |
228 | ;; "deadline" entry which are understood by diary, indicating | |
229 | ;; an event (unless marked by &) | |
230 | ;; o The optional COUNT variable of todos-forward-item should be | |
231 | ;; applied to the other functions performing similar tasks | |
232 | ;; o Modularization could be done for repeated elements of | |
233 | ;; the code, like the completing-read lines of code. | |
234 | ;; o license / version function | |
235 | ;; o export to diary file | |
236 | ;; o todos-report-bug | |
237 | ;; o GNATS support | |
238 | ;; o elide multiline (as in bbdb, or, to a lesser degree, in | |
239 | ;; outline mode) | |
ee7412e4 | 240 | ;; o rewrite complete package to store data as Lisp objects |
3f031767 SB |
241 | ;; and have display modes for display, for diary export, |
242 | ;; etc. (Richard Stallman pointed out this is a bad idea) | |
243 | ;; o so base todos.el on generic-mode.el instead | |
244 | ;; | |
245 | ;; History and Gossip | |
246 | ;; | |
247 | ;; Many thanks to all the ones who have contributed to the | |
248 | ;; evolution of this package! I hope I have listed all of you | |
249 | ;; somewhere in the documentation or at least in the RCS history! | |
250 | ;; | |
251 | ;; Enjoy this package and express your gratitude by sending nice | |
252 | ;; things to my parents' address! | |
253 | ;; | |
254 | ;; Oliver Seidel | |
255 | ;; (Lessingstr. 8, 65760 Eschborn, Federal Republic of Germany) | |
256 | ||
257 | ;;; Code: | |
258 | ||
b28025ed | 259 | ;; (require 'time-stamp) |
2c173503 | 260 | ;; (require 'calendar) ; required by diary-lib |
b28025ed | 261 | (require 'diary-lib) |
3f031767 | 262 | |
2c173503 | 263 | ;; --------------------------------------------------------------------------- |
2c173503 | 264 | ;;; Customizable options |
ee7412e4 | 265 | |
3f031767 | 266 | (defgroup todos nil |
d04d6b95 | 267 | "Maintain categorized lists of todo items." |
3f031767 | 268 | :link '(emacs-commentary-link "todos") |
d04d6b95 | 269 | :version "24.1" |
3f031767 SB |
270 | :group 'calendar) |
271 | ||
d04d6b95 SB |
272 | ;; FIXME: need this? |
273 | (defcustom todos-initial-category "Todo" | |
274 | "Default category name offered on initializing a new Todos file." | |
275 | :type 'string | |
276 | :group 'todos) | |
277 | ||
278 | (defcustom todos-display-categories-first nil | |
279 | "Non-nil to display category list on first visit to a Todos file." | |
280 | :type 'boolean | |
281 | :group 'todos) | |
282 | ||
283 | (defcustom todos-prefix "" | |
b28025ed SB |
284 | "String prefixed to todo items for visual distinction." |
285 | :type 'string | |
286 | :initialize 'custom-initialize-default | |
287 | :set 'todos-reset-prefix | |
288 | :group 'todos) | |
2c173503 SB |
289 | |
290 | (defcustom todos-number-prefix t | |
d04d6b95 SB |
291 | "Non-nil to show item prefixes as consecutively increasing integers. |
292 | These reflect the priorities of the items in each category." | |
2c173503 SB |
293 | :type 'boolean |
294 | :initialize 'custom-initialize-default | |
295 | :set 'todos-reset-prefix | |
296 | :group 'todos) | |
297 | ||
d04d6b95 SB |
298 | ;; FIXME: Update when window-width changes (add todos-reset-separator to |
299 | ;; window-configuration-change-hook in todos-mode?) | |
300 | (defcustom todos-done-separator (make-string (window-width) ?-) | |
2c173503 SB |
301 | "String used to visual separate done from not done items. |
302 | Displayed in a before-string overlay by `todos-toggle-view-done-items'." | |
303 | :type 'string | |
304 | :initialize 'custom-initialize-default | |
305 | :set 'todos-reset-separator | |
306 | :group 'todos) | |
307 | ||
308 | (defcustom todos-done-string "DONE " | |
309 | "Identifying string appended to the front of done todos items." | |
310 | :type 'string | |
311 | ;; :initialize 'custom-initialize-default | |
d04d6b95 | 312 | ;; :set 'todos-reset-done-string |
2c173503 SB |
313 | :group 'todos) |
314 | ||
315 | (defcustom todos-show-with-done nil | |
316 | "Non-nil to display done items in all categories." | |
317 | :type 'boolean | |
318 | :group 'todos) | |
319 | ||
d04d6b95 SB |
320 | (defcustom todos-files-directory (locate-user-emacs-file "todos/") |
321 | "Directory where user's Todos files are saved." | |
322 | :type 'directory | |
3f031767 | 323 | :group 'todos) |
2c173503 | 324 | |
d04d6b95 SB |
325 | (defun todos-files (&optional archives) |
326 | "Default value of `todos-files-function'. | |
327 | This returns the case-insensitive alphabetically sorted list of | |
328 | files in `todos-files-directory' with the extension \".todo\". | |
329 | With non-nil ARCHIVES return the list of archive files." | |
330 | (sort (directory-files todos-files-directory t | |
331 | (if archives "\.toda$" "\.todo$") t) | |
332 | (lambda (s1 s2) (let ((cis1 (upcase s1)) | |
333 | (cis2 (upcase s2))) | |
334 | (string< cis1 cis2))))) | |
335 | ||
336 | (defcustom todos-files-function 'todos-files | |
337 | "Function returning the value of the variable `todos-files'. | |
338 | If this function is called with an optional non-nil argument, | |
339 | then it returns the value of the variable `todos-archives'." | |
340 | :type 'function | |
2c173503 SB |
341 | :group 'todos) |
342 | ||
d04d6b95 SB |
343 | (defcustom todos-merged-files nil |
344 | "List of files for `todos-merged-top-priorities'." | |
345 | :type `(set ,@(mapcar (lambda (x) (list 'const x)) | |
346 | (funcall todos-files-function))) | |
3f031767 | 347 | :group 'todos) |
2c173503 | 348 | |
d04d6b95 SB |
349 | (defcustom todos-prompt-merged-files nil |
350 | "Non-nil to prompt for merging files for `todos-top-priorities'." | |
351 | :type 'boolean | |
352 | :group 'todos) | |
353 | ||
354 | (defcustom todos-auto-switch-todos-file nil ;FIXME: t by default? | |
355 | "Non-nil to make a Todos file current upon changing to it." | |
356 | :type 'boolean | |
357 | :initialize 'custom-initialize-default | |
358 | :set 'todos-toggle-switch-todos-file-noninteractively | |
359 | :group 'todos) | |
360 | ||
361 | (defcustom todos-default-todos-file (car (funcall todos-files-function)) | |
362 | "Todos file visited by first session invocation of `todos-show'. | |
363 | Normally this should be set by invoking `todos-change-default-file' | |
364 | either directly or as a side effect of `todos-add-file'." | |
365 | :type `(radio ,@(mapcar (lambda (x) (list 'const x)) | |
366 | (funcall todos-files-function))) | |
3f031767 | 367 | :group 'todos) |
2c173503 | 368 | |
d04d6b95 SB |
369 | ;; FIXME: make a defvar instead of a defcustom, and one for each member of todos-file |
370 | (defcustom todos-file-top "~/todos.todt" ;FIXME | |
371 | "TODO mode top priorities file." | |
372 | :type 'file | |
3f031767 | 373 | :group 'todos) |
2c173503 | 374 | |
d04d6b95 | 375 | (defcustom todos-categories-buffer "*Todos Categories*" |
ee7412e4 | 376 | "Name of buffer displayed by `todos-display-categories'." |
2c173503 SB |
377 | :type 'string |
378 | :group 'todos) | |
379 | ||
d04d6b95 SB |
380 | (defcustom todos-categories-category-label "Category" |
381 | "Category button label in `todos-categories-buffer'." | |
2c173503 SB |
382 | :type 'string |
383 | :group 'todos) | |
384 | ||
d04d6b95 SB |
385 | (defcustom todos-categories-todo-label "Todo" |
386 | "Todo button label in `todos-categories-buffer'." | |
2c173503 | 387 | :type 'string |
3f031767 | 388 | :group 'todos) |
2c173503 | 389 | |
d04d6b95 SB |
390 | (defcustom todos-categories-diary-label "Diary" |
391 | "Diary button label in `todos-categories-buffer'." | |
392 | :type 'string | |
393 | :group 'todos) | |
3f031767 | 394 | |
d04d6b95 SB |
395 | (defcustom todos-categories-done-label "Done" |
396 | "Done button label in `todos-categories-buffer'." | |
3f031767 SB |
397 | :type 'string |
398 | :group 'todos) | |
399 | ||
d04d6b95 SB |
400 | (defcustom todos-categories-archived-label "Archived" |
401 | "Archived button label in `todos-categories-buffer'." | |
402 | :type 'string | |
403 | :group 'todos) | |
404 | ||
405 | (defcustom todos-categories-number-separator " | " | |
406 | "String between number and category in `todos-categories-mode'. | |
407 | This separates the number from the category name in the default | |
408 | categories display according to priority." | |
409 | :type 'string | |
410 | :group 'todos) | |
411 | ||
412 | (defcustom todos-categories-align 'center | |
413 | "" | |
414 | :type '(radio (const left) (const center) (const right)) | |
415 | :group 'todos) | |
416 | ||
417 | ;; FIXME: set for each Todos file? | |
418 | (defcustom todos-ignore-archived-categories nil | |
419 | "Non-nil to ignore categories with only archived items. | |
420 | When non-nil such categories are omitted from `todos-categories' | |
421 | and hence from commands that use this variable. An exception is | |
422 | \\[todos-display-categories], which displays all categories; but | |
423 | those with only archived items are shown in `todos-archived-only' | |
424 | face and clicking them in Todos Categories mode visits the | |
425 | archived categories." | |
2c173503 | 426 | :type 'boolean |
d04d6b95 SB |
427 | :initialize 'custom-initialize-default |
428 | :set 'todos-reset-categories | |
2c173503 SB |
429 | :group 'todos) |
430 | ||
d04d6b95 SB |
431 | (defcustom todos-archived-categories-buffer "*Todos Archived Categories*" |
432 | "Name of buffer displayed by `todos-display-categories'." | |
2c173503 | 433 | :type 'string |
d04d6b95 | 434 | :group 'todos) |
2c173503 | 435 | |
d04d6b95 SB |
436 | (defcustom todos-edit-buffer "*Todos Edit*" |
437 | "TODO Edit buffer name." | |
2c173503 | 438 | :type 'string |
d04d6b95 SB |
439 | :group 'todos) |
440 | ||
441 | (defcustom todos-include-in-diary nil | |
442 | "Non-nil to allow new Todo items to be included in the diary." | |
443 | :type 'boolean | |
444 | :group 'todos) | |
445 | ||
446 | (defcustom todos-nondiary-marker '("[" "]") | |
447 | "List of strings surrounding item date to block diary inclusion. | |
448 | The first string is inserted before the item date and must be a | |
449 | non-empty string that does not match a diary date in order to | |
450 | have its intended effect. The second string is inserted after | |
451 | the diary date." | |
452 | :type '(list string string) | |
2c173503 | 453 | :group 'todos |
d04d6b95 SB |
454 | :initialize 'custom-initialize-default |
455 | :set 'todos-reset-nondiary-marker) | |
2c173503 | 456 | |
3f031767 SB |
457 | (defcustom todos-print-function 'ps-print-buffer-with-faces |
458 | "Function to print the current buffer." | |
459 | :type 'symbol | |
460 | :group 'todos) | |
2c173503 | 461 | |
3f031767 SB |
462 | (defcustom todos-show-priorities 1 |
463 | "Default number of priorities to show by \\[todos-top-priorities]. | |
464 | 0 means show all entries." | |
465 | :type 'integer | |
466 | :group 'todos) | |
2c173503 | 467 | |
3f031767 SB |
468 | (defcustom todos-print-priorities 0 |
469 | "Default number of priorities to print by \\[todos-print]. | |
470 | 0 means print all entries." | |
471 | :type 'integer | |
472 | :group 'todos) | |
2c173503 | 473 | |
3f031767 SB |
474 | (defcustom todos-save-top-priorities-too t |
475 | "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." | |
476 | :type 'boolean | |
477 | :group 'todos) | |
2c173503 | 478 | |
db2c5d34 | 479 | (defcustom todos-completion-ignore-case t ;; FIXME: nil for release |
ee7412e4 | 480 | "Non-nil means don't consider case significant in `todos-read-category'." |
db2c5d34 SB |
481 | :type 'boolean |
482 | :group 'todos) | |
3f031767 | 483 | |
d04d6b95 SB |
484 | (defcustom todos-always-add-time-string nil |
485 | "Non-nil adds current time to a new item's date header by default. | |
486 | When the Todos insertion commands have a non-nil \"maybe-notime\" | |
487 | argument, this reverses the effect of | |
488 | `todos-always-add-time-string': if t, these commands omit the | |
489 | current time, if nil, they include it." | |
b28025ed | 490 | :type 'boolean |
3f031767 SB |
491 | :group 'todos) |
492 | ||
2c173503 | 493 | (defcustom todos-wrap-lines t |
ee7412e4 | 494 | "" |
2c173503 SB |
495 | :group 'todos |
496 | :type 'boolean) | |
497 | ||
498 | (defcustom todos-line-wrapping-function 'todos-wrap-and-indent | |
ee7412e4 | 499 | "" |
2c173503 SB |
500 | :group 'todos |
501 | :type 'function) | |
502 | ||
ee7412e4 SB |
503 | (defcustom todos-indent-to-here 6 |
504 | "" | |
505 | :type 'integer | |
506 | :group 'todos) | |
3f031767 | 507 | |
ee7412e4 | 508 | ;; --------------------------------------------------------------------------- |
2c173503 | 509 | ;;; Faces |
ee7412e4 | 510 | |
d04d6b95 SB |
511 | (defgroup todos-faces nil |
512 | "Faces for the Todos modes." | |
513 | :version "24.1" | |
514 | :group 'todos) | |
515 | ||
db2c5d34 SB |
516 | (defface todos-prefix-string |
517 | '((t | |
b28025ed | 518 | :inherit font-lock-constant-face |
db2c5d34 SB |
519 | )) |
520 | "Face for Todos prefix string." | |
d04d6b95 | 521 | :group 'todos-faces) |
db2c5d34 | 522 | |
ee7412e4 SB |
523 | (defface todos-button |
524 | '((t | |
d04d6b95 | 525 | :inherit widget-field |
ee7412e4 SB |
526 | )) |
527 | "Face for buttons in todos-display-categories." | |
d04d6b95 SB |
528 | :group 'todos-faces) |
529 | ||
530 | (defface todos-sorted-column | |
531 | '((t | |
532 | :inherit fringe | |
533 | )) | |
534 | "Face for buttons in todos-display-categories." | |
535 | :group 'todos-faces) | |
536 | ||
537 | (defface todos-archived-only | |
538 | '((t | |
539 | (:inherit (shadow)) | |
540 | )) | |
541 | "Face for archived-only categories in todos-display-categories." | |
542 | :group 'todos-faces) | |
543 | ||
544 | (defface todos-search | |
545 | '((t | |
546 | :inherit match | |
547 | )) | |
548 | "Face for matches found by todos-search." | |
549 | :group 'todos-faces) | |
ee7412e4 | 550 | |
b28025ed | 551 | (defface todos-date |
db2c5d34 | 552 | '((t |
b28025ed | 553 | :inherit diary |
db2c5d34 | 554 | )) |
b28025ed | 555 | "Face for Todos prefix string." |
d04d6b95 | 556 | :group 'todos-faces) |
b28025ed SB |
557 | (defvar todos-date-face 'todos-date) |
558 | ||
559 | (defface todos-time | |
560 | '((t | |
561 | :inherit diary-time | |
562 | )) | |
563 | "Face for Todos prefix string." | |
d04d6b95 | 564 | :group 'todos-faces) |
b28025ed SB |
565 | (defvar todos-time-face 'todos-time) |
566 | ||
2c173503 SB |
567 | (defface todos-done |
568 | '((t | |
569 | :inherit font-lock-comment-face | |
570 | )) | |
571 | "Face for done Todos item header string." | |
d04d6b95 | 572 | :group 'todos-faces) |
2c173503 | 573 | (defvar todos-done-face 'todos-done) |
b28025ed | 574 | |
2c173503 SB |
575 | (defface todos-done-sep |
576 | '((t | |
577 | :inherit font-lock-type-face | |
578 | )) | |
579 | "Face for separator string bewteen done and not done Todos items." | |
d04d6b95 | 580 | :group 'todos-faces) |
2c173503 | 581 | (defvar todos-done-sep-face 'todos-done-sep) |
db2c5d34 SB |
582 | |
583 | (defvar todos-font-lock-keywords | |
584 | (list | |
b28025ed | 585 | '(todos-date-string-match 1 todos-date-face t) |
2c173503 SB |
586 | '(todos-time-string-match 1 todos-time-face t) |
587 | '(todos-done-string-match 0 todos-done-face t) | |
d04d6b95 | 588 | '(todos-category-string-match 1 todos-done-sep-face t)) |
db2c5d34 SB |
589 | "Font-locking for Todos mode.") |
590 | ||
3f031767 | 591 | ;; --------------------------------------------------------------------------- |
d04d6b95 | 592 | ;;; Modes setup |
3f031767 | 593 | |
d04d6b95 SB |
594 | (defvar todos-files (funcall todos-files-function) |
595 | "List of user's Todos files.") | |
596 | ||
597 | (defvar todos-archives (funcall todos-files-function t) | |
598 | "List of user's Todos archives.") | |
f730d273 | 599 | |
3f031767 | 600 | (defvar todos-categories nil |
d04d6b95 SB |
601 | "List of categories in the current Todos file. |
602 | The elements are lists whose car is a category name and whose cdr | |
603 | is the category's property list.") | |
604 | ||
605 | (defvar todos-insertion-map | |
606 | (let ((map (make-keymap))) | |
607 | (define-key map "i" 'todos-insert-item) | |
608 | (define-key map "h" 'todos-insert-item-here) | |
609 | (define-key map "dd" 'todos-insert-item-ask-date) | |
610 | (define-key map "dtt" 'todos-insert-item-ask-date-time) | |
611 | (define-key map "dtyy" 'todos-insert-item-ask-date-time-for-diary) | |
612 | (define-key map "dtyh" 'todos-insert-item-ask-date-time-for-diary-here) | |
613 | (define-key map "dth" 'todos-insert-item-ask-date-time-here) | |
614 | (define-key map "dmm" 'todos-insert-item-ask-date-maybe-notime) | |
615 | (define-key map "dmyy" 'todos-insert-item-ask-date-maybe-notime-for-diary) | |
616 | (define-key map "dmyh" 'todos-insert-item-ask-date-maybe-notime-for-diary-here) | |
617 | (define-key map "dmh" 'todos-insert-item-ask-date-maybe-notime-here) | |
618 | (define-key map "dyy" 'todos-insert-item-ask-date-for-diary) | |
619 | (define-key map "dyh" 'todos-insert-item-ask-date-for-diary-here) | |
620 | (define-key map "dh" 'todos-insert-item-ask-date-here) | |
621 | (define-key map "nn" 'todos-insert-item-ask-dayname) | |
622 | (define-key map "ntt" 'todos-insert-item-ask-dayname-time) | |
623 | (define-key map "ntyy" 'todos-insert-item-ask-dayname-time-for-diary) | |
624 | (define-key map "ntyh" 'todos-insert-item-ask-dayname-time-for-diary-here) | |
625 | (define-key map "nth" 'todos-insert-item-ask-dayname-time-here) | |
626 | (define-key map "nmm" 'todos-insert-item-ask-dayname-maybe-notime) | |
627 | (define-key map "nmyy" 'todos-insert-item-ask-dayname-maybe-notime-for-diary) | |
628 | (define-key map "nmyh" 'todos-insert-item-ask-dayname-maybe-notime-for-diary-here) | |
629 | (define-key map "nmh" 'todos-insert-item-ask-dayname-maybe-notime-here) | |
630 | (define-key map "nyy" 'todos-insert-item-ask-dayname-for-diary) | |
631 | (define-key map "nyh" 'todos-insert-item-ask-dayname-for-diary-here) | |
632 | (define-key map "nh" 'todos-insert-item-ask-dayname-here) | |
633 | (define-key map "tt" 'todos-insert-item-ask-time) | |
634 | (define-key map "tyy" 'todos-insert-item-ask-time-for-diary) | |
635 | (define-key map "tyh" 'todos-insert-item-ask-time-for-diary-here) | |
636 | (define-key map "th" 'todos-insert-item-ask-time-here) | |
637 | (define-key map "mm" 'todos-insert-item-maybe-notime) | |
638 | (define-key map "myy" 'todos-insert-item-maybe-notime-for-diary) | |
639 | (define-key map "myh" 'todos-insert-item-maybe-notime-for-diary-here) | |
640 | (define-key map "mh" 'todos-insert-item-maybe-notime-here) | |
641 | (define-key map "yy" 'todos-insert-item-for-diary) | |
642 | (define-key map "yh" 'todos-insert-item-for-diary-here) | |
643 | map) | |
644 | "Keymap for Todos mode insertion commands.") | |
3f031767 | 645 | |
3f031767 SB |
646 | (defvar todos-mode-map |
647 | (let ((map (make-keymap))) | |
648 | (suppress-keymap map t) | |
2c173503 | 649 | ;; navigation commands |
d04d6b95 SB |
650 | (define-key map "f" 'todos-forward-category) |
651 | (define-key map "b" 'todos-backward-category) | |
2c173503 | 652 | (define-key map "j" 'todos-jump-to-category) |
d04d6b95 | 653 | (define-key map "J" 'todos-jump-to-category-other-file) |
2c173503 SB |
654 | (define-key map "n" 'todos-forward-item) |
655 | (define-key map "p" 'todos-backward-item) | |
656 | (define-key map "S" 'todos-search) | |
d04d6b95 | 657 | (define-key map "X" 'todos-clear-matches) |
2c173503 | 658 | ;; display commands |
d04d6b95 | 659 | (define-key map "Cd" 'todos-display-categories) ;FIXME: Cs todos-show-categories? |
f730d273 | 660 | ;; (define-key map "" 'todos-display-categories-alphabetically) |
d04d6b95 | 661 | (define-key map "H" 'todos-highlight-item) |
2c173503 SB |
662 | (define-key map "N" 'todos-toggle-item-numbering) |
663 | ;; (define-key map "" 'todos-toggle-display-date-time) | |
664 | (define-key map "P" 'todos-print) | |
2c173503 | 665 | (define-key map "v" 'todos-toggle-view-done-items) |
d04d6b95 SB |
666 | (define-key map "V" 'todos-toggle-show-done-only) |
667 | (define-key map "Av" 'todos-view-archived-items) | |
668 | (define-key map "As" 'todos-switch-to-archive) | |
669 | (define-key map "Ac" 'todos-choose-archive) | |
2c173503 | 670 | (define-key map "Y" 'todos-diary-items) |
2c173503 | 671 | (define-key map "t" 'todos-top-priorities) |
d04d6b95 SB |
672 | (define-key map "T" 'todos-merged-top-priorities) |
673 | ;; (define-key map "" 'todos-save-top-priorities) | |
2c173503 | 674 | ;; editing commands |
d04d6b95 SB |
675 | (define-key map "Fa" 'todos-add-file) |
676 | (define-key map "Ca" 'todos-add-category) | |
677 | (define-key map "Cr" 'todos-rename-category) | |
678 | (define-key map "Cm" 'todos-move-category) | |
679 | (define-key map "Ck" 'todos-delete-category) | |
2c173503 | 680 | (define-key map "d" 'todos-item-done) |
d04d6b95 SB |
681 | (define-key map "ee" 'todos-edit-item) |
682 | (define-key map "em" 'todos-edit-multiline) | |
683 | (define-key map "eh" 'todos-edit-item-header) | |
684 | (define-key map "ed" 'todos-edit-item-date) | |
685 | (define-key map "et" 'todos-edit-item-time) | |
686 | (define-key map "i" todos-insertion-map) | |
3f031767 | 687 | (define-key map "k" 'todos-delete-item) |
db2c5d34 | 688 | (define-key map "m" 'todos-move-item) |
d04d6b95 SB |
689 | (define-key map "M" 'todos-move-item-to-file) |
690 | (define-key map "-" 'todos-raise-item-priority) | |
691 | (define-key map "+" 'todos-lower-item-priority) | |
692 | (define-key map "#" 'todos-set-item-priority) | |
2c173503 | 693 | (define-key map "u" 'todos-item-undo) |
d04d6b95 | 694 | (define-key map "Ad" 'todos-archive-done-items) |
2c173503 SB |
695 | (define-key map "y" 'todos-toggle-item-diary-inclusion) |
696 | ;; (define-key map "" 'todos-toggle-diary-inclusion) | |
d04d6b95 SB |
697 | (define-key map "s" 'todos-save) |
698 | (define-key map "q" 'todos-quit) | |
ee7412e4 | 699 | (define-key map [remap newline] 'newline-and-indent) |
2c173503 SB |
700 | map) |
701 | "Todos mode keymap.") | |
702 | ||
703 | (defvar todos-archive-mode-map | |
704 | (let ((map (make-sparse-keymap))) | |
705 | (suppress-keymap map t) | |
706 | ;; navigation commands | |
d04d6b95 SB |
707 | (define-key map "f" 'todos-forward-category) |
708 | (define-key map "b" 'todos-backward-category) | |
2c173503 | 709 | (define-key map "j" 'todos-jump-to-category) |
3f031767 SB |
710 | (define-key map "n" 'todos-forward-item) |
711 | (define-key map "p" 'todos-backward-item) | |
2c173503 SB |
712 | ;; display commands |
713 | (define-key map "C" 'todos-display-categories) | |
d04d6b95 | 714 | (define-key map "H" 'todos-highlight-item) |
2c173503 SB |
715 | (define-key map "N" 'todos-toggle-item-numbering) |
716 | ;; (define-key map "" 'todos-toggle-display-date-time) | |
3f031767 SB |
717 | (define-key map "P" 'todos-print) |
718 | (define-key map "q" 'todos-quit) | |
3f031767 | 719 | (define-key map "s" 'todos-save) |
2c173503 | 720 | (define-key map "S" 'todos-search) |
d04d6b95 SB |
721 | (define-key map "t" 'todos-show) ;FIXME: should show same category |
722 | (define-key map "u" 'todos-unarchive-category) | |
2c173503 SB |
723 | map) |
724 | "Todos Archive mode keymap.") | |
725 | ||
726 | (defvar todos-edit-mode-map | |
ee7412e4 | 727 | (let ((map (make-sparse-keymap))) |
d04d6b95 | 728 | (define-key map "\C-x\C-q" 'todos-edit-quit) |
ee7412e4 | 729 | (define-key map [remap newline] 'newline-and-indent) |
3f031767 | 730 | map) |
2c173503 | 731 | "Todos Edit mode keymap.") |
3f031767 | 732 | |
ee7412e4 SB |
733 | (defvar todos-categories-mode-map |
734 | (let ((map (make-sparse-keymap))) | |
735 | (suppress-keymap map t) | |
736 | (define-key map "a" 'todos-display-categories-alphabetically) | |
737 | (define-key map "c" 'todos-display-categories) | |
d04d6b95 SB |
738 | (define-key map "+" 'todos-lower-category) |
739 | (define-key map "-" 'todos-raise-category) | |
740 | (define-key map "n" 'forward-button) | |
741 | (define-key map "p" 'backward-button) | |
742 | (define-key map [tab] 'forward-button) | |
743 | (define-key map [backtab] 'backward-button) | |
744 | (define-key map "q" 'todos-quit) | |
ee7412e4 SB |
745 | ;; (define-key map "A" 'todos-add-category) |
746 | ;; (define-key map "D" 'todos-delete-category) | |
747 | ;; (define-key map "R" 'todos-rename-category) | |
748 | map) | |
749 | "Todos Categories mode keymap.") | |
750 | ||
d04d6b95 SB |
751 | (defvar todos-top-priorities-mode-map |
752 | (let ((map (make-keymap))) | |
753 | (suppress-keymap map t) | |
754 | ;; navigation commands | |
755 | (define-key map "j" 'todos-jump-to-category) | |
756 | (define-key map "n" 'todos-forward-item) | |
757 | (define-key map "p" 'todos-backward-item) | |
758 | ;; (define-key map "S" 'todos-search) | |
759 | ;; display commands | |
760 | (define-key map "C" 'todos-display-categories) | |
761 | ;; (define-key map "" 'todos-display-categories-alphabetically) | |
762 | (define-key map "H" 'todos-highlight-item) | |
763 | (define-key map "N" 'todos-toggle-item-numbering) | |
764 | ;; (define-key map "" 'todos-toggle-display-date-time) | |
765 | (define-key map "P" 'todos-print) | |
766 | (define-key map "q" 'todos-quit) | |
767 | (define-key map "s" 'todos-save) | |
768 | (define-key map "V" 'todos-view-archive) | |
769 | (define-key map "v" 'todos-toggle-view-done-items) | |
770 | (define-key map "Y" 'todos-diary-items) | |
771 | ;; (define-key map "S" 'todos-save-top-priorities) | |
772 | ;; editing commands | |
773 | (define-key map "l" 'todos-lower-item-priority) | |
774 | (define-key map "r" 'todos-raise-item-priority) | |
775 | (define-key map "#" 'todos-set-item-priority) | |
776 | map) | |
777 | "Todos Top Priorities mode keymap.") | |
778 | ||
779 | (defvar todos-current-todos-file nil | |
780 | "Variable holding the name of the currently active Todos file. | |
781 | Automatically set by `todos-switch-todos-file'.") | |
782 | ||
783 | (defvar todos-category-number 0 | |
784 | "Number.") | |
3f031767 SB |
785 | |
786 | (defvar todos-tmp-buffer-name " *todo tmp*") | |
787 | ||
2c173503 | 788 | (defvar todos-category-beg "--==-- " |
d04d6b95 SB |
789 | "String marking beginning of category (inserted with its name).") |
790 | ||
791 | (defvar todos-category-done "==--== DONE " | |
792 | "String marking beginning of category's done items.") | |
793 | ||
794 | (defvar todos-nondiary-start (nth 0 todos-nondiary-marker) | |
795 | "String inserted before item date to block diary inclusion.") | |
796 | ||
797 | (defvar todos-nondiary-end (nth 1 todos-nondiary-marker) | |
798 | "String inserted after item date matching todos-nondiary-start.") | |
799 | ||
800 | (defvar todos-show-done-only nil | |
801 | "If non-nil display only done items in current category. | |
802 | Set by `todos-toggle-show-done-only' and used by | |
803 | `todos-category-select'.") | |
804 | ||
805 | (easy-menu-define | |
806 | todos-menu todos-mode-map "Todos Menu" | |
807 | '("Todos" | |
808 | ("Navigation" | |
809 | ["Next Item" todos-forward-item t] | |
810 | ["Previous Item" todos-backward-item t] | |
811 | "---" | |
812 | ["Next Category" todos-forward-category t] | |
813 | ["Previous Category" todos-backward-category t] | |
814 | ["Jump to Category" todos-jump-to-category t] | |
815 | ["Jump to Category in Other File" todos-jump-to-category-other-file t] | |
816 | "---" | |
817 | ["Search Todos File" todos-search t] | |
818 | ["Clear Highlighting on Search Matches" todos-category-done t]) | |
819 | ("Display" | |
820 | ["List Current Categories" todos-display-categories t] | |
821 | ["List Categories Alphabetically" todos-display-categories-alphabetically t] | |
822 | ["Turn Item Highlighting on/off" todos-highlight-item t] | |
823 | ["Turn Item Numbering on/off" todos-toggle-item-numbering t] | |
824 | ["Turn Item Time Stamp on/off" todos-toggle-display-date-time t] | |
825 | ["View/Hide Done Items" todos-toggle-view-done-items t] | |
826 | "---" | |
827 | ["View Diary Items" todos-diary-items t] | |
828 | ["View Top Priority Items" todos-top-priorities t] | |
829 | ["View Merged Top Priority Items" todos-merged-top-priorities t] | |
830 | "---" | |
831 | ["View Archive" todos-view-archive t] | |
832 | ["Print Category" todos-print-category t]) | |
833 | ("Editing" | |
834 | ["Insert New Item" todos-insert-item t] | |
835 | ["Insert Item Here" todos-insert-item-here t] | |
836 | ("More Insertion Commands") | |
837 | ["Edit Item" todos-edit-item t] | |
838 | ["Edit Multiline Item" todos-edit-multiline t] | |
839 | ["Edit Item Header" todos-edit-item-header t] | |
840 | ["Edit Item Date" todos-edit-item-date t] | |
841 | ["Edit Item Time" todos-edit-item-time t] | |
842 | "---" | |
843 | ["Lower Item Priority" todos-lower-item-priority t] | |
844 | ["Raise Item Priority" todos-raise-item-priority t] | |
845 | ["Set Item Priority" todos-set-item-priority t] | |
846 | ["Move (Recategorize) Item" todos-move-item t] | |
847 | ["Delete Item" todos-delete-item t] | |
848 | ["Undo Done Item" todos-item-undo t] | |
849 | ["Mark/Unmark Item for Diary" todos-toggle-item-diary-inclusion t] | |
850 | ["Mark/Unmark Items for Diary" todos-toggle-diary-inclusion t] | |
851 | ["Mark & Hide Done Item" todos-item-done t] | |
852 | ["Archive Done Items" todos-archive-done-items t] | |
853 | "---" | |
854 | ["Add New Todos File" todos-add-file t] | |
855 | ["Add New Category" todos-add-category t] | |
856 | ["Delete Current Category" todos-delete-category t] | |
857 | ["Rename Current Category" todos-rename-category t] | |
858 | "---" | |
859 | ["Save Todos File" todos-save t] | |
860 | ["Save Top Priorities" todos-save-top-priorities t]) | |
861 | "---" | |
862 | ["Quit" todos-quit t] | |
863 | )) | |
b28025ed | 864 | |
d04d6b95 SB |
865 | ;; FIXME: remove when part of Emacs |
866 | (add-to-list 'auto-mode-alist '("\\.todo\\'" . todos-mode)) | |
867 | (add-to-list 'auto-mode-alist '("\\.toda\\'" . todos-archive-mode)) | |
868 | ||
869 | (defun todos-modes-set-1 () | |
870 | "" | |
871 | (set (make-local-variable 'font-lock-defaults) '(todos-font-lock-keywords t)) | |
872 | (set (make-local-variable 'indent-line-function) 'todos-indent) | |
ee7412e4 | 873 | (when todos-wrap-lines (funcall todos-line-wrapping-function)) |
d04d6b95 SB |
874 | ) |
875 | ||
876 | (defun todos-modes-set-2 () | |
877 | "" | |
ee7412e4 SB |
878 | (add-to-invisibility-spec 'todos) |
879 | (setq buffer-read-only t) | |
d04d6b95 SB |
880 | (set (make-local-variable 'hl-line-range-function) |
881 | (lambda() (when (todos-item-end) | |
882 | (cons (todos-item-start) (todos-item-end))))) | |
883 | ) | |
884 | ||
885 | ;; ;; As calendar reads included Todos file before todos-mode is loaded. | |
886 | ;; ;;;###autoload | |
887 | (define-derived-mode todos-mode nil "Todos" () | |
888 | "Major mode for displaying, navigating and editing Todo lists. | |
3f031767 | 889 | |
d04d6b95 SB |
890 | \\{todos-mode-map}" |
891 | (easy-menu-add todos-menu) | |
892 | (todos-modes-set-1) | |
893 | (todos-modes-set-2) | |
894 | (set (make-local-variable 'todos-show-done-only) nil) | |
895 | (when todos-auto-switch-todos-file | |
896 | (add-hook 'post-command-hook | |
897 | 'todos-switch-todos-file nil t))) | |
898 | ||
899 | (define-derived-mode todos-archive-mode nil "Todos-Arch" () | |
ee7412e4 SB |
900 | "Major mode for archived Todos categories. |
901 | ||
902 | \\{todos-archive-mode-map}" | |
d04d6b95 SB |
903 | (todos-modes-set-1) |
904 | (todos-modes-set-2) | |
905 | (set (make-local-variable 'todos-show-done-only) t) | |
906 | (when todos-auto-switch-todos-file | |
907 | (add-hook 'post-command-hook | |
908 | 'todos-switch-todos-file nil t))) | |
909 | ||
910 | (define-derived-mode todos-edit-mode nil "Todos-Ed" () | |
ee7412e4 SB |
911 | "Major mode for editing multiline Todo items. |
912 | ||
913 | \\{todos-edit-mode-map}" | |
d04d6b95 | 914 | (todos-modes-set-1)) |
3f031767 | 915 | |
d04d6b95 | 916 | (define-derived-mode todos-categories-mode nil "Todos-Cats" () |
ee7412e4 SB |
917 | "Major mode for displaying and editing Todos categories. |
918 | ||
919 | \\{todos-categories-mode-map}" | |
ee7412e4 SB |
920 | (make-local-variable 'font-lock-defaults) |
921 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | |
d04d6b95 SB |
922 | (setq buffer-read-only t)) |
923 | ||
924 | (define-derived-mode todos-top-priorities-mode nil "Todos-Top" () | |
925 | "Mode for displaying and reprioritizing top priority Todos. | |
926 | ||
927 | \\{todos-top-priorites-mode-map}" | |
928 | (todos-modes-set-1) | |
929 | (todos-modes-set-2)) | |
2c173503 | 930 | |
ee7412e4 SB |
931 | (defun todos-save () |
932 | "Save the TODO list." | |
933 | (interactive) | |
d04d6b95 SB |
934 | ;; (todos-update-categories-sexp) |
935 | (save-buffer) | |
936 | ;; (if todos-save-top-priorities-too (todos-save-top-priorities)) | |
937 | ) | |
3f031767 | 938 | |
ee7412e4 SB |
939 | (defun todos-quit () |
940 | "Done with TODO list for now." | |
941 | (interactive) | |
d04d6b95 SB |
942 | (cond ((eq major-mode 'todos-categories-mode) |
943 | (kill-buffer) | |
944 | (setq todos-descending-counts-store nil) | |
945 | (setq todos-categories nil) | |
946 | (todos-show)) | |
947 | ((member major-mode (list 'todos-mode 'todos-archive-mode)) | |
948 | (todos-save) | |
949 | (bury-buffer)))) | |
2c173503 | 950 | |
ee7412e4 SB |
951 | ;; --------------------------------------------------------------------------- |
952 | ;;; Commands | |
2c173503 | 953 | |
ee7412e4 SB |
954 | ;;; Display |
955 | ||
956 | ;;;###autoload | |
d04d6b95 SB |
957 | (defun todos-show (&optional solicit-file) |
958 | "Visit the current Todos file and display one of its categories. | |
959 | ||
960 | With non-nil prefix argument SOLICIT-FILE ask for file to visit, | |
961 | otherwise the first invocation of this command in a session | |
962 | visits `todos-default-todos-file' (creating it if it does not yet | |
963 | exist). Subsequent invocations from outside of Todos mode | |
964 | revisit this file or whichever Todos file has been made | |
965 | current (e.g. by calling `todos-switch-todos-file'). | |
966 | ||
967 | The category displayed is initially the first member of | |
968 | `todos-categories' for the current Todos file, subsequently | |
969 | whichever category is current. If | |
970 | `todos-display-categories-first' is non-nil, then the first | |
971 | invocation of `todos-show' displays a clickable listing of the | |
972 | categories in the current Todos file." | |
973 | (interactive "P") | |
974 | ;; ;; Make this a no-op if called interactively in narrowed Todos mode, since | |
975 | ;; ;; it is redundant in that case, but in particular to work around the bug of | |
976 | ;; ;; item prefix reduplication with show-paren-mode enabled. | |
977 | ;; (unless (and (called-interactively-p) | |
978 | ;; (or (eq major-mode 'todos-mode) (eq major-mode 'todos-archive-mode)) | |
979 | ;; (< (- ( point-max) (point-min)) (buffer-size))) | |
980 | (when (and (called-interactively-p) | |
981 | (or solicit-file | |
982 | (member todos-current-todos-file todos-archives))) | |
983 | (setq todos-current-todos-file nil | |
984 | todos-categories nil | |
985 | todos-category-number 0)) | |
986 | (let ((first-visit (or (not todos-current-todos-file) ;first call | |
987 | ;; after switching to a not yet visited Todos file | |
988 | (not (buffer-live-p | |
989 | (get-file-buffer todos-current-todos-file)))))) | |
990 | (if solicit-file | |
991 | (setq todos-current-todos-file | |
992 | (todos-read-file-name "Select a Todos file to visit: ")) | |
993 | (or todos-current-todos-file | |
994 | (setq todos-current-todos-file (or todos-default-todos-file | |
995 | (todos-add-file))))) | |
996 | (if (and first-visit todos-display-categories-first) | |
997 | (todos-display-categories) | |
998 | (find-file todos-current-todos-file) | |
999 | ;; (or (eq major-mode 'todos-mode) (todos-mode)) | |
1000 | ;; initialize new Todos file | |
1001 | (if (zerop (buffer-size)) | |
1002 | (setq todos-category-number (todos-add-category)) | |
1003 | ;; FIXME: let user choose category? | |
1004 | (if (zerop todos-category-number) (setq todos-category-number 1))) | |
1005 | (or todos-categories | |
1006 | (setq todos-categories (if todos-ignore-archived-categories | |
1007 | (todos-truncate-categories-list) | |
1008 | (todos-make-categories-list)))) | |
1009 | (save-excursion (todos-category-select)))));) | |
1010 | ||
1011 | ;; FIXME: make core of this internal? | |
1012 | (defun todos-display-categories (&optional sortkey) | |
1013 | "Display the category names of the current Todos file. | |
1014 | The numbers indicate the current order of the categories. | |
1015 | ||
1016 | With non-nil SORTKEY display a non-numbered alphabetical list. | |
ee7412e4 | 1017 | The lists are in Todos Categories mode. |
3f031767 | 1018 | |
ee7412e4 SB |
1019 | The category names are buttonized, and pressing a button displays |
1020 | the category in Todos mode." | |
3f031767 | 1021 | (interactive) |
d04d6b95 SB |
1022 | (let* ((cats0 (if (and todos-ignore-archived-categories |
1023 | (not (eq major-mode 'todos-categories-mode))) | |
1024 | (todos-make-categories-list t) | |
1025 | todos-categories)) | |
1026 | (cats (todos-sort cats0 sortkey)) | |
1027 | ;; used by todos-insert-category-line | |
1028 | (num 0)) | |
2c173503 SB |
1029 | (with-current-buffer (get-buffer-create todos-categories-buffer) |
1030 | (switch-to-buffer (current-buffer)) | |
ee7412e4 SB |
1031 | (let (buffer-read-only) |
1032 | (erase-buffer) | |
1033 | (kill-all-local-variables) | |
d04d6b95 SB |
1034 | (insert (format "Category counts for Todos file \"%s\"." |
1035 | (file-name-sans-extension | |
1036 | (file-name-nondirectory todos-current-todos-file)))) | |
1037 | (newline 2) | |
1038 | ;; FIXME: abstract format from here and todos-insert-category-line | |
1039 | (insert (make-string (+ 3 (length todos-categories-number-separator)) 32)) | |
ee7412e4 | 1040 | (save-excursion |
d04d6b95 SB |
1041 | (todos-insert-sort-button todos-categories-category-label) |
1042 | (if (member todos-current-todos-file todos-archives) | |
1043 | (insert (concat (make-string 6 32) | |
1044 | (format "%s" todos-categories-archived-label))) | |
1045 | (insert (make-string 3 32)) | |
1046 | (todos-insert-sort-button todos-categories-todo-label) | |
1047 | (insert (make-string 2 32)) | |
1048 | (todos-insert-sort-button todos-categories-diary-label) | |
1049 | (insert (make-string 2 32)) | |
1050 | (todos-insert-sort-button todos-categories-done-label) | |
1051 | (insert (make-string 2 32)) | |
1052 | (todos-insert-sort-button todos-categories-archived-label)) | |
1053 | (newline 2) | |
1054 | (mapc (lambda (cat) (todos-insert-category-line cat sortkey)) | |
1055 | (mapcar 'car cats)))) | |
ee7412e4 SB |
1056 | (todos-categories-mode)))) |
1057 | ||
d04d6b95 | 1058 | ;; FIXME: make this toggle with todos-display-categories |
ee7412e4 SB |
1059 | (defun todos-display-categories-alphabetically () |
1060 | "" | |
1061 | (interactive) | |
d04d6b95 SB |
1062 | (todos-display-sorted 'alpha)) |
1063 | ||
1064 | ;; FIXME: provide key bindings for these or delete them | |
1065 | (defun todos-display-categories-sorted-by-todo () | |
1066 | "" | |
1067 | (interactive) | |
1068 | (todos-display-sorted 'todo)) | |
1069 | ||
1070 | (defun todos-display-categories-sorted-by-diary () | |
1071 | "" | |
1072 | (interactive) | |
1073 | (todos-display-sorted 'diary)) | |
1074 | ||
1075 | (defun todos-display-categories-sorted-by-done () | |
1076 | "" | |
1077 | (interactive) | |
1078 | (todos-display-sorted 'done)) | |
1079 | ||
1080 | (defun todos-display-categories-sorted-by-archived () | |
1081 | "" | |
1082 | (interactive) | |
1083 | (todos-display-sorted 'archived)) | |
2c173503 SB |
1084 | |
1085 | (defun todos-toggle-item-numbering () | |
ee7412e4 | 1086 | "" |
2c173503 SB |
1087 | (interactive) |
1088 | (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix))) | |
3f031767 | 1089 | |
2c173503 | 1090 | (defun todos-toggle-view-done-items () |
ee7412e4 | 1091 | "" |
3f031767 | 1092 | (interactive) |
ee7412e4 SB |
1093 | (save-excursion |
1094 | (goto-char (point-min)) | |
d04d6b95 SB |
1095 | (let ((todos-show-with-done |
1096 | (if (re-search-forward todos-done-string-start nil t) | |
1097 | nil | |
1098 | t)) | |
1099 | (cat (todos-current-category))) | |
f730d273 | 1100 | (todos-category-select) |
d04d6b95 | 1101 | (when (zerop (todos-get-count 'done cat)) |
f730d273 | 1102 | (message "There are no done items in this category."))))) |
2c173503 | 1103 | |
d04d6b95 | 1104 | (defun todos-toggle-show-done-only () |
2c173503 SB |
1105 | "" |
1106 | (interactive) | |
d04d6b95 SB |
1107 | (setq todos-show-done-only (not todos-show-done-only)) |
1108 | (todos-category-select)) | |
1109 | ||
1110 | (defun todos-view-archived-items () | |
1111 | "Display the archived items of the current category. | |
1112 | The buffer showing these items is in Todos Archive mode." | |
1113 | (interactive) | |
1114 | (let ((cat (todos-current-category))) | |
1115 | (if (zerop (todos-get-count 'archived cat)) | |
1116 | (message "There are no archived items from this category.") | |
1117 | (let* ((tfile-base (file-name-sans-extension todos-current-todos-file)) | |
1118 | (afile (concat tfile-base ".toda"))) | |
1119 | (find-file afile) | |
f730d273 | 1120 | (todos-archive-mode) |
d04d6b95 SB |
1121 | (unless (string= todos-current-todos-file afile) |
1122 | (setq todos-current-todos-file afile) | |
f730d273 SB |
1123 | (setq todos-categories nil)) |
1124 | (unless todos-categories | |
1125 | (setq todos-categories (todos-make-categories-list))) | |
d04d6b95 SB |
1126 | (setq todos-category-number |
1127 | (- (length todos-categories) | |
1128 | (length (member cat todos-categories)))) ;FIXME | |
1129 | (todos-jump-to-category cat))))) | |
1130 | ||
1131 | (defun todos-switch-to-archive (&optional ask) | |
1132 | "Visit the archive of the current Todos file, if it exists. | |
1133 | The buffer showing the archive is in Todos Archive mode. The | |
1134 | first visit in a session displays the first category in the | |
1135 | archive, subsequent visits return to the last category | |
1136 | displayed." | |
1137 | (interactive) | |
1138 | (let* ((tfile-base (file-name-sans-extension todos-current-todos-file)) | |
1139 | (afile (if ask | |
1140 | (todos-read-file-name "Choose a Todos archive: " t) | |
1141 | (concat tfile-base ".toda")))) | |
1142 | (if (not (file-exists-p afile)) | |
1143 | (message "There is currently no Todos archive for this file.") | |
1144 | (find-file afile) | |
1145 | (todos-archive-mode) | |
1146 | (unless (string= todos-current-todos-file afile) | |
1147 | (setq todos-current-todos-file afile) | |
1148 | (setq todos-categories nil)) | |
1149 | (unless todos-categories | |
1150 | (setq todos-categories (todos-make-categories-list)) | |
1151 | (setq todos-category-number 1)) | |
1152 | (todos-category-select)))) | |
1153 | ||
1154 | (defun todos-choose-archive () | |
1155 | "Choose an archive and visit it." | |
1156 | (interactive) | |
1157 | (todos-switch-to-archive t)) | |
3f031767 | 1158 | |
2c173503 SB |
1159 | (defun todos-highlight-item () |
1160 | "Highlight the todo item the cursor is on." | |
3f031767 | 1161 | (interactive) |
2c173503 SB |
1162 | (if hl-line-mode ; todos-highlight-item |
1163 | (hl-line-mode 0) | |
1164 | (hl-line-mode 1))) | |
1165 | ||
1166 | ;; FIXME: make this a customizable option for whole Todos file | |
1167 | (defun todos-toggle-display-date-time () | |
ee7412e4 | 1168 | "" |
2c173503 SB |
1169 | (interactive) |
1170 | (save-excursion | |
1171 | (goto-char (point-min)) | |
1172 | (let ((ovs (overlays-in (point) (line-end-position))) | |
1173 | ov hidden) | |
1174 | (while ovs | |
1175 | (setq ov (car ovs)) | |
1176 | (if (equal (overlay-get ov 'display) "") | |
1177 | (setq ovs nil | |
1178 | hidden t) | |
1179 | (setq ovs (cdr ovs)))) | |
1180 | (if hidden (remove-overlays (point-min) (point-max) 'display "") | |
1181 | (while (not (eobp)) | |
d04d6b95 | 1182 | (re-search-forward (concat todos-date-string-start todos-date-pattern |
ee7412e4 | 1183 | "\\( " diary-time-regexp "\\)?\\]? ") |
2c173503 SB |
1184 | ; FIXME: this space in header? ^ |
1185 | nil t) | |
f730d273 | 1186 | ;; FIXME: wrong match data if search fails |
2c173503 SB |
1187 | (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) |
1188 | (overlay-put ov 'display "") | |
f730d273 | 1189 | (forward-line)))))) |
2c173503 | 1190 | |
d04d6b95 SB |
1191 | (defun todos-update-merged-files () |
1192 | "" | |
1193 | (interactive) | |
1194 | (let ((files (funcall todos-files-function))) | |
1195 | (dolist (f files) | |
1196 | (if (member f todos-merged-files) | |
1197 | (and (y-or-n-p | |
1198 | (format "Remove \"%s\" from list of merged Todos files? " | |
1199 | (file-name-sans-extension (file-name-nondirectory f)))) | |
1200 | (setq todos-merged-files (delete f todos-merged-files))) | |
1201 | (and (y-or-n-p | |
1202 | (format "Add \"%s\" to list of merged Todos files? " | |
1203 | (file-name-sans-extension (file-name-nondirectory f)))) | |
1204 | (setq todos-merged-files | |
1205 | (append todos-merged-files (list f))))))) | |
1206 | (customize-save-variable 'todos-merged-files todos-merged-files)) | |
1207 | ||
1208 | (defun todos-top-priorities (&optional num merge) ;FIXME: rename b/c of diary items | |
1209 | "List top priorities for each category. | |
2c173503 | 1210 | |
d04d6b95 SB |
1211 | Number of entries for each category is given by NUM which |
1212 | defaults to \'todos-show-priorities\'. With non-nil argument | |
1213 | MERGE list top priorities of all Todos files in | |
1214 | `todos-merged-files'. If `todos-prompt-merged-files' is non-nil, | |
1215 | prompt to update the list of merged files." | |
1216 | (interactive "p") | |
1217 | (or num (setq num todos-show-priorities)) | |
2c173503 | 1218 | (let ((todos-print-buffer-name todos-tmp-buffer-name) |
d04d6b95 SB |
1219 | (files (list todos-current-todos-file)) |
1220 | file bufstr cat beg end done) | |
1221 | (when merge | |
1222 | (if (or todos-prompt-merged-files (null todos-merged-files)) | |
1223 | (todos-update-merged-files)) | |
1224 | (setq files todos-merged-files)) | |
1225 | (if (buffer-live-p (get-buffer todos-print-buffer-name)) | |
1226 | (kill-buffer todos-print-buffer-name)) | |
1227 | (save-current-buffer | |
1228 | (dolist (f files) | |
1229 | (find-file f) | |
1230 | (todos-switch-todos-file) | |
1231 | (setq file (file-name-sans-extension | |
1232 | (file-name-nondirectory todos-current-todos-file))) | |
1233 | (with-current-buffer (get-file-buffer f) | |
1234 | (save-restriction | |
1235 | (widen) | |
1236 | (setq bufstr (buffer-string)))) | |
1237 | (with-temp-buffer | |
1238 | (insert bufstr) | |
1239 | (goto-char (point-min)) | |
1240 | (unless (looking-at (concat "^" (regexp-quote todos-category-beg))) | |
1241 | (kill-line 1)) | |
1242 | (while (re-search-forward | |
1243 | (concat "^" (regexp-quote todos-category-beg) "\\(.+\\)\n") | |
1244 | nil t) | |
1245 | (setq cat (match-string 1)) | |
1246 | (delete-region (match-beginning 0) (match-end 0)) | |
1247 | (setq beg (point)) ;Start of first entry. | |
1248 | (setq end (if (re-search-forward | |
1249 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
1250 | (match-beginning 0) | |
1251 | (point-max))) | |
1252 | (goto-char beg) | |
1253 | (setq done | |
1254 | (if (re-search-forward | |
1255 | (concat "\n" (regexp-quote todos-category-done)) end t) | |
2c173503 | 1256 | (match-beginning 0) |
d04d6b95 SB |
1257 | end)) |
1258 | (delete-region done end) | |
1259 | (setq end done) | |
1260 | (narrow-to-region beg end) ;In case we have too few entries. | |
1261 | (goto-char (point-min)) | |
1262 | (cond ((< num 0) ; get only diary items | |
1263 | (while (not (eobp)) | |
1264 | (if (looking-at (regexp-quote todos-nondiary-start)) | |
1265 | (todos-remove-item) | |
1266 | (todos-forward-item)))) | |
1267 | ((zerop num) ; keep all items | |
1268 | (goto-char end)) | |
1269 | (t | |
1270 | (todos-forward-item num))) | |
1271 | (setq beg (point)) | |
1272 | (if (>= num 0) (delete-region beg end)) | |
1273 | (goto-char (point-min)) | |
1274 | (while (not (eobp)) | |
1275 | (when (re-search-forward (concat todos-date-string-start | |
1276 | todos-date-pattern | |
1277 | "\\( " diary-time-regexp "\\)?\\]?") | |
1278 | nil t) | |
1279 | (insert (concat " [" (if merge (concat file ":")) cat "]"))) | |
1280 | (forward-line)) | |
1281 | (widen)) | |
1282 | (append-to-buffer todos-print-buffer-name (point-min) (point-max))))) | |
1283 | (with-current-buffer todos-print-buffer-name | |
1284 | (todos-prefix-overlays) | |
1285 | (todos-top-priorities-mode) | |
2c173503 | 1286 | (goto-char (point-min)) ;Due to display buffer |
d04d6b95 SB |
1287 | ;; (make-local-variable 'font-lock-defaults) |
1288 | ;; (setq font-lock-defaults '(todos-font-lock-keywords t)) | |
1289 | (font-lock-fontify-buffer)) | |
1290 | ;; (setq buffer-read-only t)) | |
2c173503 SB |
1291 | ;; Could have used switch-to-buffer as it has a norecord argument, |
1292 | ;; which is nice when we are called from e.g. todos-print. | |
1293 | ;; Else we could have used pop-to-buffer. | |
2c173503 SB |
1294 | (display-buffer todos-print-buffer-name) |
1295 | (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." | |
1296 | todos-print-buffer-name))) | |
1297 | ||
d04d6b95 SB |
1298 | (defun todos-merged-top-priorities (&optional num) |
1299 | "" | |
1300 | (interactive "p") | |
1301 | (todos-top-priorities num t)) | |
1302 | ||
1303 | (defun todos-diary-items (&optional merge) | |
1304 | "Display todo items marked for diary inclusion. | |
1305 | The items are those in the current Todos file, or with prefix | |
1306 | argument MERGE those in all Todos files in `todos-merged-files'." | |
1307 | (interactive "P") | |
1308 | (todos-top-priorities -1 merge)) | |
1309 | ||
ee7412e4 SB |
1310 | ;;; Navigation |
1311 | ||
1312 | (defun todos-forward-category () | |
1313 | "Go forward to TODO list of next category." | |
1314 | (interactive) | |
1315 | (setq todos-category-number | |
d04d6b95 SB |
1316 | (1+ (mod todos-category-number (length todos-categories)))) |
1317 | (todos-category-select) | |
1318 | (goto-char (point-min))) | |
ee7412e4 SB |
1319 | |
1320 | (defun todos-backward-category () | |
1321 | "Go back to TODO list of previous category." | |
1322 | (interactive) | |
1323 | (setq todos-category-number | |
d04d6b95 SB |
1324 | (1+ (mod (- todos-category-number 2) (length todos-categories)))) |
1325 | (todos-category-select) | |
1326 | (goto-char (point-min))) | |
ee7412e4 SB |
1327 | |
1328 | ;; FIXME: Document that a non-existing name creates that category, and add | |
1329 | ;; y-or-n-p confirmation -- or eliminate this possibility? | |
d04d6b95 SB |
1330 | (defun todos-jump-to-category (&optional cat other-file) |
1331 | "Jump to a category in a Todos file. | |
1332 | When called interactively, prompt for the category. | |
1333 | Non-interactively, the argument CAT provides the category. With | |
1334 | non-nil argument OTHER-FILE, prompt for a Todos file, otherwise | |
1335 | stay with the current Todos file. See also | |
1336 | `todos-jump-to-category-other-file'." | |
ee7412e4 | 1337 | (interactive) |
d04d6b95 SB |
1338 | (when (or (and other-file |
1339 | (setq todos-current-todos-file | |
1340 | (todos-read-file-name "Choose a Todos file: "))) | |
1341 | (and cat | |
1342 | todos-ignore-archived-categories | |
1343 | (zerop (todos-get-count 'todo cat)) | |
1344 | (zerop (todos-get-count 'done cat)) | |
1345 | (not (zerop (todos-get-count 'archived cat))) | |
1346 | (setq todos-current-todos-file | |
1347 | (concat (file-name-sans-extension todos-current-todos-file) | |
1348 | ".toda")))) | |
1349 | (with-current-buffer (find-file-noselect todos-current-todos-file) | |
1350 | ;; (or (eq major-mode 'todos-mode) (todos-mode)) | |
1351 | (setq todos-categories (todos-make-categories-list)))) | |
1352 | (let ((category (or (and (assoc cat todos-categories) cat) | |
1353 | (todos-read-category "Jump to category: ")))) | |
ee7412e4 SB |
1354 | (if (string= "" category) |
1355 | (setq category (todos-current-category))) | |
d04d6b95 SB |
1356 | (if (string= (buffer-name) todos-categories-buffer) |
1357 | (kill-buffer)) | |
1358 | (if (or cat other-file) | |
1359 | (switch-to-buffer (get-file-buffer todos-current-todos-file))) | |
ee7412e4 | 1360 | (setq todos-category-number |
d04d6b95 SB |
1361 | (or (todos-category-number category) |
1362 | (todos-add-category category))) | |
1363 | (todos-category-select) | |
1364 | (goto-char (point-min)))) | |
1365 | ||
1366 | (defun todos-jump-to-category-other-file () | |
1367 | "" | |
1368 | (interactive) | |
1369 | (todos-jump-to-category nil t)) | |
ee7412e4 SB |
1370 | |
1371 | ;; FIXME ? todos-{backward,forward}-item skip over empty line between done and | |
1372 | ;; not done items (but todos-forward-item gets there when done items are not | |
1373 | ;; displayed). Also disallow prefix arg value < 1 (re-search-* allows these) | |
1374 | (defun todos-backward-item (&optional count) | |
1375 | "Select COUNT-th previous entry of TODO list." | |
1376 | (interactive "P") | |
1377 | ;; FIXME ? this moves to bob if on the first item (but so does previous-line) | |
1378 | (todos-item-start) | |
1379 | (unless (bobp) | |
1380 | (re-search-backward todos-item-start nil t (or count 1)))) | |
1381 | ||
1382 | (defun todos-forward-item (&optional count) | |
1383 | "Select COUNT-th next entry of TODO list." | |
1384 | (interactive "P") | |
1385 | (goto-char (line-end-position)) | |
1386 | (if (re-search-forward todos-item-start nil t (or count 1)) | |
1387 | (goto-char (match-beginning 0)) | |
1388 | (goto-char (point-max)))) | |
1389 | ||
ee7412e4 | 1390 | (defun todos-search () |
d04d6b95 SB |
1391 | "Perform a search for a regular expression, with repetition. |
1392 | The search encompasses all todo and done items within the current Todos file; it excludes category names. Matches are highlighted | |
1393 | " | |
ee7412e4 SB |
1394 | (interactive) |
1395 | (let ((regex (read-from-minibuffer "Enter a search string (regexp): ")) | |
d04d6b95 SB |
1396 | (opoint (point)) |
1397 | matches match cat in-done ov mlen msg) | |
ee7412e4 SB |
1398 | (widen) |
1399 | (goto-char (point-min)) | |
d04d6b95 SB |
1400 | (while (not (eobp)) |
1401 | (setq match (re-search-forward regex nil t)) | |
1402 | (goto-char (line-beginning-position)) | |
1403 | (unless (or (equal (point) 1) | |
1404 | (looking-at (concat "^" (regexp-quote todos-category-beg)))) | |
1405 | (if match (push match matches))) | |
ee7412e4 | 1406 | (forward-line)) |
d04d6b95 SB |
1407 | (setq matches (reverse matches)) |
1408 | (if matches | |
1409 | (catch 'stop | |
1410 | (while matches | |
1411 | (setq match (pop matches)) | |
1412 | (goto-char match) | |
1413 | (todos-item-start) | |
1414 | (when (looking-at todos-done-string-start) | |
1415 | (setq in-done t)) | |
1416 | (re-search-backward (concat "^" (regexp-quote todos-category-beg) | |
1417 | "\\(.*\\)\n") nil t) | |
1418 | (setq cat (match-string-no-properties 1)) | |
1419 | (todos-category-number cat) | |
1420 | (todos-category-select) | |
1421 | (if in-done (unless todos-show-with-done (todos-toggle-view-done-items))) | |
1422 | (goto-char match) | |
1423 | (setq ov (make-overlay (- (point) (length regex)) (point))) | |
1424 | (overlay-put ov 'face 'todos-search) | |
1425 | (when matches | |
1426 | (setq mlen (length matches)) | |
1427 | (if (y-or-n-p | |
1428 | (if (> mlen 1) | |
1429 | (format "There are %d more matches; go to next match? " mlen) | |
1430 | "There is one more match; go to it? ")) | |
1431 | (widen) | |
1432 | (throw 'stop (setq msg (if (> mlen 1) | |
1433 | (format "There are %d more matches." mlen) | |
1434 | "There is one more match.")))))) | |
1435 | (setq msg "There are no more matches.")) | |
ee7412e4 | 1436 | (todos-category-select) |
d04d6b95 SB |
1437 | (goto-char opoint) |
1438 | (message "No match for \"%s\"" regex)) | |
1439 | (when msg | |
1440 | (if (y-or-n-p (concat msg "\nUnhighlight matches? ")) | |
1441 | (todos-clear-matches) | |
1442 | (message "You can unhighlight the matches later by typing %s" | |
1443 | (key-description (car (where-is-internal | |
1444 | 'todos-clear-matches)))))))) | |
1445 | ||
1446 | (defun todos-clear-matches () | |
1447 | "Removing highlighting on matches found by todos-search." | |
1448 | (interactive) | |
1449 | (remove-overlays 1 (1+ (buffer-size)) 'face 'todos-search)) | |
ee7412e4 | 1450 | |
2c173503 | 1451 | ;;; Editing |
3f031767 | 1452 | |
d04d6b95 SB |
1453 | (defun todos-add-file (&optional arg) |
1454 | "" | |
1455 | (interactive "p") | |
1456 | (let ((default-file (if todos-default-todos-file | |
1457 | (file-name-sans-extension | |
1458 | (file-name-nondirectory todos-default-todos-file)))) | |
1459 | file prompt) | |
1460 | (while | |
1461 | (and | |
1462 | (cond | |
1463 | ((or (not file) (member file todos-files)) | |
1464 | (setq prompt (concat "Enter name of new Todos file " | |
1465 | "(TAB or SPC to see existing Todos files): "))) | |
1466 | ((string-equal file "") | |
1467 | (setq prompt "Enter a non-empty name: ")) | |
1468 | ((string-match "\\`\\s-+\\'" file) | |
1469 | (setq prompt "Enter a name that is not only white space: "))) | |
1470 | (setq file (todos-read-file-name prompt)))) | |
1471 | (if (or (not default-file) | |
1472 | (yes-or-no-p (concat "Make %s new default Todos file " | |
1473 | "[current default is \"%s\"]? ") | |
1474 | file default-file)) | |
1475 | (todos-change-default-file file) | |
1476 | (message "\"%s\" remains the default Todos file." default-file)) | |
1477 | (with-current-buffer (get-buffer-create todos-default-todos-file) | |
1478 | (erase-buffer) | |
1479 | (write-region (point-min) (point-max) todos-default-todos-file | |
1480 | nil 'nomessage nil t)) | |
1481 | (if arg (todos-show) file))) | |
1482 | ||
1483 | ;; FIXME: omit this and just use defcustom? | |
1484 | (defun todos-change-default-file (&optional file) | |
1485 | "" | |
1486 | (interactive) | |
1487 | (let ((new-default (or file | |
1488 | (todos-read-file-name "Choose new default Todos file: ")))) | |
1489 | (customize-save-variable 'todos-default-todos-file new-default) | |
1490 | (message "\"%s\" is new default Todos file." | |
1491 | (file-name-sans-extension (file-name-nondirectory new-default))))) | |
1492 | ||
3f031767 SB |
1493 | (defun todos-add-category (&optional cat) |
1494 | "Add new category CAT to the TODO list." | |
1495 | (interactive) | |
d04d6b95 SB |
1496 | (let* ((buffer-read-only) |
1497 | (buf (find-file-noselect todos-current-todos-file t)) | |
1498 | (num (1+ (length todos-categories))) | |
1499 | (counts (make-vector 4 0))) ; [todo diary done archived] | |
1500 | ;; (counts (list 'todo 0 'diary 0 'done 0 'archived 0))) | |
3f031767 SB |
1501 | (unless (zerop (buffer-size buf)) |
1502 | (and (null todos-categories) | |
ee7412e4 | 1503 | (error "Error in %s: File is non-empty but contains no category" |
d04d6b95 SB |
1504 | todos-current-todos-file))) |
1505 | (unless cat (setq cat (read-from-minibuffer "Enter new category name: "))) | |
3f031767 | 1506 | (with-current-buffer buf |
d04d6b95 SB |
1507 | (setq cat (todos-validate-category-name cat)) |
1508 | (setq todos-categories (append todos-categories (list (cons cat counts)))) | |
3f031767 | 1509 | (widen) |
d04d6b95 SB |
1510 | (goto-char (point-max)) |
1511 | (save-excursion ; for subsequent todos-category-select | |
1512 | (insert todos-category-beg cat "\n\n" todos-category-done "\n")) | |
1513 | (todos-update-categories-sexp) | |
1514 | (if (called-interactively-p 'any) ; FIXME | |
3f031767 | 1515 | ;; properly display the newly added category |
d04d6b95 SB |
1516 | (progn |
1517 | (setq todos-category-number num) | |
1518 | (todos-category-select)) | |
1519 | num)))) | |
3f031767 | 1520 | |
2c173503 | 1521 | (defun todos-rename-category () |
db2c5d34 | 1522 | "Rename current Todos category." |
2c173503 | 1523 | (interactive) |
d04d6b95 | 1524 | (let* ((cat (todos-current-category)) |
ee7412e4 | 1525 | (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat)))) |
d04d6b95 SB |
1526 | (setq new (todos-validate-category-name new)) |
1527 | (let* ((ofile (buffer-file-name)) | |
1528 | (archive (concat (file-name-sans-extension ofile) ".toda")) | |
1529 | (buffers (append (list ofile) | |
1530 | (unless (zerop (todos-get-count 'archived cat)) | |
1531 | (list archive))))) | |
1532 | (dolist (buf buffers) | |
1533 | (with-current-buffer (find-file-noselect buf) | |
1534 | (let (buffer-read-only) | |
1535 | ;; (setq todos-categories (if (string= buf archive) | |
1536 | ;; (todos-make-categories-list t) | |
1537 | ;; todos-categories)) | |
1538 | (todos-set-categories) | |
1539 | (save-excursion | |
1540 | (save-restriction | |
1541 | (setcar (assoc cat todos-categories) new) | |
1542 | (widen) | |
1543 | (goto-char (point-min)) | |
1544 | (todos-update-categories-sexp) | |
1545 | (re-search-forward (concat (regexp-quote todos-category-beg) "\\(" | |
1546 | (regexp-quote cat) "\\)\n") nil t) | |
1547 | (replace-match new t t nil 1))))))) | |
1548 | (setq mode-line-buffer-identification | |
1549 | (format "Category %d: %s" todos-category-number new))) | |
1550 | (save-excursion (todos-category-select))) | |
db2c5d34 | 1551 | |
d04d6b95 | 1552 | ;; FIXME: what if cat has archived items? |
b28025ed SB |
1553 | (defun todos-delete-category (&optional arg) |
1554 | "Delete current Todos category provided it is empty. | |
1555 | With ARG non-nil delete the category unconditionally, | |
1556 | i.e. including all existing entries." | |
1557 | (interactive "P") | |
ee7412e4 | 1558 | (let* ((cat (todos-current-category)) |
d04d6b95 SB |
1559 | (todo (todos-get-count 'todo cat)) |
1560 | (done (todos-get-count 'done cat))) | |
1561 | (if (and (not arg) | |
f730d273 | 1562 | (or (> todo 0) (> done 0))) |
ee7412e4 | 1563 | (message "To delete a non-empty category, type C-u D.") |
b28025ed SB |
1564 | (when (y-or-n-p (concat "Permanently remove category \"" cat |
1565 | "\"" (and arg " and all its entries") "? ")) | |
d04d6b95 SB |
1566 | (widen) |
1567 | (let ((buffer-read-only) | |
1568 | (beg (re-search-backward | |
1569 | (concat "^" (regexp-quote (concat todos-category-beg cat)) | |
1570 | "\n") nil t)) | |
1571 | (end (if (re-search-forward | |
1572 | (concat "\n\\(" (regexp-quote todos-category-beg) | |
1573 | ".*\n\\)") nil t) | |
1574 | (match-beginning 1) | |
1575 | (point-max)))) | |
2c173503 | 1576 | (remove-overlays beg end) |
d04d6b95 SB |
1577 | (delete-region beg end) |
1578 | (setq todos-categories (delete (assoc cat todos-categories) | |
1579 | todos-categories)) | |
1580 | (todos-update-categories-sexp) | |
f730d273 | 1581 | (setq todos-category-number |
d04d6b95 | 1582 | (1+ (mod todos-category-number (length todos-categories)))) |
2c173503 | 1583 | (todos-category-select) |
d04d6b95 | 1584 | (goto-char (point-min)) |
2c173503 | 1585 | (message "Deleted category %s" cat)))))) |
db2c5d34 | 1586 | |
ee7412e4 SB |
1587 | (defun todos-raise-category (&optional lower) |
1588 | "Raise priority of category point is on in Categories buffer. | |
1589 | With non-nil argument LOWER, lower the category's priority." | |
1590 | (interactive) | |
1591 | (let (num) | |
1592 | (save-excursion | |
1593 | (forward-line 0) | |
1594 | (skip-chars-forward " ") | |
1595 | (setq num (number-at-point))) | |
1596 | (when (and num (if lower | |
1597 | (< num (length todos-categories)) | |
1598 | (> num 1))) | |
1599 | (let* ((col (current-column)) | |
1600 | (beg (progn (forward-line (if lower 0 -1)) (point))) | |
1601 | (num1 (progn (skip-chars-forward " ") (1- (number-at-point)))) | |
1602 | (num2 (1+ num1)) | |
1603 | (end (progn (forward-line 2) (point))) | |
1604 | (catvec (vconcat todos-categories)) | |
d04d6b95 SB |
1605 | (cat1-list (aref catvec num1)) |
1606 | (cat2-list (aref catvec num2)) | |
1607 | (cat1 (car cat1-list)) | |
1608 | (cat2 (car cat2-list)) | |
ee7412e4 SB |
1609 | (buffer-read-only)) |
1610 | (delete-region beg end) | |
d04d6b95 SB |
1611 | (setq num1 (1+ num1)) |
1612 | (setq num2 (1- num2)) | |
ee7412e4 | 1613 | (setq num num2) |
d04d6b95 | 1614 | (todos-insert-category-line cat2) |
ee7412e4 | 1615 | (setq num num1) |
d04d6b95 SB |
1616 | (todos-insert-category-line cat1) |
1617 | (aset catvec num2 (cons cat2 (cdr cat2-list))) | |
1618 | (aset catvec num1 (cons cat1 (cdr cat1-list))) | |
ee7412e4 | 1619 | (setq todos-categories (append catvec nil)) |
d04d6b95 SB |
1620 | (with-current-buffer (get-file-buffer todos-current-todos-file) |
1621 | (todos-update-categories-sexp)) | |
ee7412e4 SB |
1622 | (forward-line (if lower -1 -2)) |
1623 | (forward-char col))))) | |
1624 | ||
1625 | (defun todos-lower-category () | |
1626 | "Lower priority of category point is on in Categories buffer." | |
1627 | (interactive) | |
1628 | (todos-raise-category t)) | |
1629 | ||
d04d6b95 SB |
1630 | ;; FIXME: use save-restriction? |
1631 | (defun todos-move-category () | |
1632 | "Move current category to a different Todos file. | |
1633 | If current category has archived items, also move those to the | |
1634 | archive of the file moved to, creating it if it does not exist." | |
1635 | (interactive) | |
1636 | ;; FIXME: warn if only category in file? If so, delete file after moving category | |
1637 | (when (or (> (length todos-categories) 1) | |
1638 | (y-or-n-p (concat "This is the only category in this file; " | |
1639 | "moving it will delete the file.\n" | |
1640 | "Do you want to proceed? "))) | |
1641 | (let* ((ofile (buffer-file-name)) | |
1642 | (cat (todos-current-category)) | |
1643 | ;; FIXME: check if cat exists in nfile, and if so rename it | |
1644 | (nfile (todos-read-file-name "Choose a Todos file: ")) | |
1645 | (archive (concat (file-name-sans-extension ofile) ".toda")) | |
1646 | (buffers (append (list ofile) | |
1647 | (unless (zerop (todos-get-count 'archived cat)) | |
1648 | (list archive))))) | |
1649 | (dolist (buf buffers) | |
1650 | (with-current-buffer (find-file-noselect buf) | |
1651 | (save-excursion | |
1652 | (save-restriction | |
1653 | (widen) | |
1654 | (goto-char (point-max)) | |
1655 | (let ((buffer-read-only nil) | |
1656 | (beg (re-search-backward | |
1657 | (concat "^" | |
1658 | (regexp-quote (concat todos-category-beg cat))) | |
1659 | nil t)) | |
1660 | (end (if (re-search-forward | |
1661 | (concat "^" (regexp-quote todos-category-beg)) | |
1662 | nil t 2) | |
1663 | (match-beginning 0) | |
1664 | (point-max))) | |
1665 | (content (buffer-substring-no-properties beg end))) | |
1666 | (with-current-buffer | |
1667 | (find-file-noselect | |
1668 | ;; regenerate todos-archives in case there | |
1669 | ;; is a newly created archive | |
1670 | (if (member buf (funcall todos-files-function t)) | |
1671 | (concat (file-name-sans-extension nfile) ".toda") | |
1672 | nfile)) | |
1673 | (let (buffer-read-only) | |
1674 | (save-excursion | |
1675 | (save-restriction | |
1676 | (widen) | |
1677 | (goto-char (point-max)) | |
1678 | (insert content) | |
1679 | (goto-char (point-min)) | |
1680 | (if (zerop (buffer-size)) | |
1681 | (progn | |
1682 | (set-buffer-modified-p nil) ; no questions | |
1683 | (delete-file (buffer-file-name)) | |
1684 | (kill-buffer)) | |
1685 | (unless (looking-at | |
1686 | (concat "^" (regexp-quote todos-category-beg))) | |
1687 | (kill-whole-line)) | |
1688 | (save-buffer))))) | |
1689 | (remove-overlays beg end) | |
1690 | (delete-region beg end) | |
1691 | (goto-char (point-min)) | |
1692 | (if (zerop (buffer-size)) | |
1693 | (progn | |
1694 | (set-buffer-modified-p nil) | |
1695 | (delete-file (buffer-file-name)) | |
1696 | (kill-buffer)) | |
1697 | (unless (looking-at | |
1698 | (concat "^" (regexp-quote todos-category-beg))) | |
1699 | (kill-whole-line)) | |
1700 | (save-buffer)))))))) | |
1701 | ;; (todos-switch-todos-file nfile)))) | |
1702 | (find-file nfile) | |
1703 | (setq todos-current-todos-file nfile | |
1704 | todos-categories (todos-make-categories-list t) | |
1705 | todos-category-number (todos-category-number cat)) | |
1706 | (todos-category-select)))) | |
1707 | ||
1708 | (defun todos-merge-category () | |
1709 | "Merge this category's items to another category in this file. | |
1710 | The todo and done items are appended to the todo and done items, | |
1711 | respectively, of the category merged to, which becomes the | |
1712 | current category, and the category merged from is deleted." | |
1713 | (interactive) | |
1714 | (let ((buffer-read-only nil) | |
1715 | (cat (todos-current-category)) | |
1716 | (goal (todos-read-category "Category to merge to: "))) | |
1717 | (widen) | |
1718 | ;; FIXME: what if cat has archived items? | |
1719 | (let* ((cbeg (progn | |
1720 | (re-search-backward | |
1721 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
1722 | (point))) | |
1723 | (tbeg (progn (forward-line) (point))) | |
1724 | (dbeg (progn | |
1725 | (re-search-forward | |
1726 | (concat "^" (regexp-quote todos-category-done)) nil t) | |
1727 | (match-beginning 0))) | |
1728 | (tend (forward-line -1)) | |
1729 | (cend (progn | |
1730 | (if (re-search-forward | |
1731 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
1732 | (match-beginning 0) | |
1733 | (point-max)))) | |
1734 | (todo (buffer-substring-no-properties tbeg tend)) | |
1735 | (done (buffer-substring-no-properties dbeg cend)) | |
1736 | here) | |
1737 | (goto-char (point-min)) | |
1738 | (re-search-forward | |
1739 | (concat "^" (regexp-quote todos-category-beg goal)) nil t) | |
1740 | (re-search-forward | |
1741 | (concat "^" (regexp-quote todos-category-done)) nil t) | |
1742 | (forward-line -1) | |
1743 | (setq here (point)) | |
1744 | (insert todo) | |
1745 | (goto-char (if (re-search-forward | |
1746 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
1747 | (match-beginning 0) | |
1748 | (point-max))) | |
1749 | (insert done) | |
1750 | (remove-overlays cbeg cend) | |
1751 | (delete-region cbeg cend) | |
1752 | (setq todos-categories (delete (assoc cat todos-categories) | |
1753 | todos-categories)) | |
1754 | (todos-update-categories-sexp) | |
1755 | (setq todos-category-number (todos-category-number goal)) | |
1756 | (todos-category-select) | |
1757 | ;; Put point at the start of the merged todo items | |
1758 | ;; FIXME: what if there are no merged todo items but only done items? | |
1759 | (goto-char here)))) | |
1760 | ||
1761 | (defun todos-merge-categories () | |
1762 | "" | |
1763 | (interactive) | |
1764 | (let* ((cats (mapcar 'car todos-categories)) | |
1765 | (goal (todos-read-category "Category to merge to: ")) | |
1766 | (prompt (format "Merge to %s (type C-g to finish)? " goal)) | |
1767 | (source (let ((inhibit-quit t) l) | |
1768 | (while (not (eq last-input-event 7)) | |
1769 | (dolist (c cats) | |
1770 | (when (y-or-n-p prompt) | |
1771 | (push c l) | |
1772 | (setq cats (delete c cats)))))))) | |
1773 | (widen) | |
1774 | )) | |
1775 | ||
2c173503 | 1776 | ;;;###autoload |
ee7412e4 | 1777 | (defun todos-insert-item (&optional arg date-type time diary here) |
2c173503 | 1778 | "Insert new TODO list item. |
db2c5d34 | 1779 | |
2c173503 SB |
1780 | With prefix argument ARG solicit the category, otherwise use the |
1781 | current category. | |
db2c5d34 | 1782 | |
ee7412e4 SB |
1783 | Argument DATE-TYPE sets the form of the item's mandatory date |
1784 | string. With the value `date' this is the full date (whose | |
1785 | format is set by `calendar-date-display-form', with year, month | |
1786 | and day individually solicited (month with tab completion). With | |
1787 | the value `dayname' a weekday name is used, solicited with tab | |
1788 | completion. With the value `calendar' the full date string is | |
1789 | used and set by selecting from the Calendar. With any other | |
1790 | value (including none) the full current date is used. | |
1791 | ||
1792 | Argument TIME determines the occurrence and value of the time | |
1793 | string. With the value `omit' insert the item without a time | |
1794 | string. With the value `ask' solicit a time string; this may be | |
1795 | empty or else must match `date-time-regexp'. With any other | |
1796 | value add or omit the current time in accordance with | |
1797 | `todos-always-add-time-string'. | |
1798 | ||
1799 | With non-nil argument DIARY mark item for inclusion in user's diary. If `todos-include-in-diary' is non-nil | |
1800 | ||
2c173503 SB |
1801 | With non-nil argument HERE insert the new item directly above the |
1802 | item at point. If point is on an empty line, insert the new item | |
ee7412e4 | 1803 | there." |
3f031767 | 1804 | (interactive "P") |
2c173503 SB |
1805 | (unless (or (todos-done-item-p) |
1806 | (save-excursion (forward-line -1) (todos-done-item-p))) | |
d04d6b95 | 1807 | ;; FIXME: deletable if command not autoloaded |
f730d273 | 1808 | (when (not (derived-mode-p 'todos-mode)) (todos-show)) |
2c173503 | 1809 | (let* ((buffer-read-only) |
ee7412e4 SB |
1810 | (date-string (cond |
1811 | ((eq date-type 'ask-date) | |
1812 | (todos-read-date)) | |
1813 | ((eq date-type 'ask-dayname) | |
1814 | (todos-read-dayname)) | |
1815 | ((eq date-type 'calendar) | |
1816 | ;; FIXME: should only be executed from Calendar | |
2c173503 SB |
1817 | (with-current-buffer "*Calendar*" |
1818 | (calendar-date-string (calendar-cursor-to-date t) t t))) | |
1819 | (t (calendar-date-string (calendar-current-date) t t)))) | |
d04d6b95 | 1820 | (time-string (cond ((eq time 'ask-time) |
ee7412e4 SB |
1821 | (todos-read-time)) |
1822 | (todos-always-add-time-string | |
d04d6b95 SB |
1823 | (substring (current-time-string) 11 16)) |
1824 | (t nil))) | |
1825 | (new-item (concat (unless (or diary todos-include-in-diary) | |
1826 | todos-nondiary-start) | |
ee7412e4 | 1827 | date-string (when time-string (concat " " time-string)) |
d04d6b95 SB |
1828 | (unless (or diary todos-include-in-diary) |
1829 | todos-nondiary-end) | |
1830 | " " | |
98c97dee | 1831 | (read-from-minibuffer "New TODO entry: "))) |
d04d6b95 SB |
1832 | (cat (if arg (todos-read-category "Insert item in category: ") |
1833 | (todos-current-category)))) | |
ee7412e4 SB |
1834 | ;; indent newlines inserted by C-q C-j if nonspace char follows |
1835 | (setq new-item (replace-regexp-in-string | |
1836 | "\\(\n\\)[^[:blank:]]" | |
1837 | (concat "\n" (make-string todos-indent-to-here 32)) new-item | |
1838 | nil nil 1)) | |
d04d6b95 SB |
1839 | (unless (assoc cat todos-categories) (todos-add-category cat)) |
1840 | ;; (unless here (todos-set-item-priority new-item cat)) | |
1841 | ;; (todos-insert-with-overlays new-item) | |
1842 | (if here | |
1843 | (todos-insert-with-overlays new-item) | |
1844 | (todos-set-item-priority new-item cat)) | |
1845 | (todos-item-counts cat 'insert) | |
1846 | (if (or diary todos-include-in-diary) (todos-item-counts cat 'diary)) | |
1847 | (todos-update-categories-sexp)))) | |
ee7412e4 SB |
1848 | |
1849 | ;; FIXME: make insertion options customizable per category | |
ee7412e4 | 1850 | |
f730d273 | 1851 | ;; current date ~ current day ~ ask date ~ ask day |
d04d6b95 | 1852 | ;; current time ~ ask time ~ maybe no time |
f730d273 SB |
1853 | ;; for diary ~ not for diary |
1854 | ;; here ~ ask priority | |
ee7412e4 | 1855 | |
d04d6b95 | 1856 | ;; date-type: date name (calendar) - (maybe-no)time - diary - here |
ee7412e4 | 1857 | |
d04d6b95 SB |
1858 | ;; ii todos-insert-item + current-date/dayname + current/no-time |
1859 | ;; ih todos-insert-item-here | |
f730d273 SB |
1860 | ;; idd todos-insert-item-ask-date |
1861 | ;; idtt todos-insert-item-ask-date-time | |
1862 | ;; idtyy todos-insert-item-ask-date-time-for-diary | |
1863 | ;; idtyh todos-insert-item-ask-date-time-for-diary-here | |
1864 | ;; idth todos-insert-item-ask-date-time-here | |
d04d6b95 SB |
1865 | ;; idmm todos-insert-item-ask-date-maybe-notime |
1866 | ;; idmyy todos-insert-item-ask-date-maybe-notime-for-diary | |
1867 | ;; idmyh todos-insert-item-ask-date-maybe-notime-for-diary-here | |
1868 | ;; idmh todos-insert-item-ask-date-maybe-notime-here | |
f730d273 SB |
1869 | ;; idyy todos-insert-item-ask-date-for-diary |
1870 | ;; idyh todos-insert-item-ask-date-for-diary-here | |
1871 | ;; idh todos-insert-item-ask-date-here | |
1872 | ;; inn todos-insert-item-ask-dayname | |
1873 | ;; intt todos-insert-item-ask-dayname-time | |
1874 | ;; intyy todos-insert-item-ask-dayname-time-for-diary | |
1875 | ;; intyh todos-insert-item-ask-dayname-time-for-diary-here | |
1876 | ;; inth todos-insert-item-ask-dayname-time-here | |
d04d6b95 SB |
1877 | ;; inmm todos-insert-item-ask-dayname-maybe-notime |
1878 | ;; inmyy todos-insert-item-ask-dayname-maybe-notime-for-diary | |
1879 | ;; inmyh todos-insert-item-ask-dayname-maybe-notime-for-diary-here | |
1880 | ;; inmh todos-insert-item-ask-dayname-maybe-notime-here | |
f730d273 SB |
1881 | ;; inyy todos-insert-item-ask-dayname-for-diary |
1882 | ;; inyh todos-insert-item-ask-dayname-for-diary-here | |
1883 | ;; inh todos-insert-item-ask-dayname-here | |
d04d6b95 SB |
1884 | ;; itt todos-insert-item-ask-time |
1885 | ;; ityy todos-insert-item-ask-time-for-diary | |
1886 | ;; ityh todos-insert-item-ask-time-for-diary-here | |
1887 | ;; ith todos-insert-item-ask-time-here | |
1888 | ;; im todos-insert-item-maybe-notime | |
1889 | ;; imyy todos-insert-item-maybe-notime-for-diary | |
1890 | ;; imyh todos-insert-item-maybe-notime-for-diary-here | |
1891 | ;; imh todos-insert-item-maybe-notime-here | |
f730d273 SB |
1892 | ;; iyy todos-insert-item-for-diary |
1893 | ;; iyh todos-insert-item-for-diary-here | |
2c173503 | 1894 | |
d04d6b95 | 1895 | (defun todos-insert-item-ask-date (&optional arg) |
ee7412e4 | 1896 | "" |
d04d6b95 SB |
1897 | (interactive "P") |
1898 | (todos-insert-item arg 'ask-date)) | |
2c173503 | 1899 | |
d04d6b95 SB |
1900 | (defun todos-insert-item-ask-date-time (&optional arg) |
1901 | "" | |
1902 | (interactive "P") | |
1903 | (todos-insert-item arg 'ask-date 'ask-time)) | |
1904 | ||
1905 | (defun todos-insert-item-ask-date-time-for-diary (&optional arg) | |
1906 | "" | |
1907 | (interactive "P") | |
1908 | (todos-insert-item arg 'ask-date 'ask-time t)) | |
1909 | ||
1910 | (defun todos-insert-item-ask-date-time-for-diary-here () | |
1911 | "" | |
1912 | (interactive) | |
1913 | (todos-insert-item nil 'ask-date 'ask-time t t)) | |
1914 | ||
1915 | (defun todos-insert-item-ask-date-time-here () | |
1916 | "" | |
1917 | (interactive) | |
1918 | (todos-insert-item nil 'ask-date 'ask-time nil t)) | |
1919 | ||
1920 | (defun todos-insert-item-ask-date-maybe-notime (&optional arg) | |
1921 | "" | |
1922 | (interactive "P") | |
1923 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1924 | (todos-insert-item arg 'ask-date))) | |
1925 | ||
1926 | (defun todos-insert-item-ask-date-maybe-notime-for-diary (&optional arg) | |
1927 | "" | |
1928 | (interactive "P") | |
1929 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1930 | (todos-insert-item arg 'ask-date nil t))) | |
1931 | ||
1932 | (defun todos-insert-item-ask-date-maybe-notime-for-diary-here () | |
1933 | "" | |
1934 | (interactive) | |
1935 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1936 | (todos-insert-item nil 'ask-date nil t t))) | |
1937 | ||
1938 | (defun todos-insert-item-ask-date-maybe-notime-here () | |
1939 | "" | |
1940 | (interactive) | |
1941 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1942 | (todos-insert-item nil 'ask-date nil nil nil t))) | |
1943 | ||
1944 | (defun todos-insert-item-ask-date-for-diary (&optional arg) | |
1945 | "" | |
1946 | (interactive "P") | |
1947 | (todos-insert-item arg 'ask-date nil t)) | |
1948 | ||
1949 | (defun todos-insert-item-ask-date-for-diary-here () | |
1950 | "" | |
1951 | (interactive) | |
1952 | (todos-insert-item nil 'ask-date nil t t)) | |
1953 | ||
1954 | (defun todos-insert-item-ask-date-here () | |
1955 | "" | |
1956 | (interactive) | |
1957 | (todos-insert-item nil 'ask-date nil nil t)) | |
1958 | ||
1959 | (defun todos-insert-item-ask-dayname (&optional arg) | |
1960 | "" | |
1961 | (interactive "P") | |
1962 | (todos-insert-item arg 'ask-dayname)) | |
1963 | ||
1964 | (defun todos-insert-item-ask-dayname-time (&optional arg) | |
1965 | "" | |
1966 | (interactive "P") | |
1967 | (todos-insert-item arg 'ask-dayname 'ask-time)) | |
1968 | ||
1969 | (defun todos-insert-item-ask-dayname-time-for-diary (&optional arg) | |
1970 | "" | |
1971 | (interactive "P") | |
1972 | (todos-insert-item arg 'ask-dayname 'ask-time t)) | |
1973 | ||
1974 | (defun todos-insert-item-ask-dayname-time-for-diary-here () | |
1975 | "" | |
1976 | (interactive) | |
1977 | (todos-insert-item nil 'ask-dayname 'ask-time t t)) | |
1978 | ||
1979 | (defun todos-insert-item-ask-dayname-time-here () | |
1980 | "" | |
1981 | (interactive) | |
1982 | (todos-insert-item nil 'ask-dayname 'ask-time nil t)) | |
1983 | ||
1984 | (defun todos-insert-item-ask-dayname-maybe-notime (&optional arg) | |
1985 | "" | |
1986 | (interactive "P") | |
1987 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1988 | (todos-insert-item arg 'ask-dayname))) | |
1989 | ||
1990 | (defun todos-insert-item-ask-dayname-maybe-notime-for-diary (&optional arg) | |
1991 | "" | |
1992 | (interactive "P") | |
1993 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
1994 | (todos-insert-item arg 'ask-dayname nil t))) | |
1995 | ||
1996 | (defun todos-insert-item-ask-dayname-maybe-notime-for-diary-here () | |
1997 | "" | |
1998 | (interactive) | |
1999 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2000 | (todos-insert-item nil 'ask-dayname nil t t))) | |
2001 | ||
2002 | (defun todos-insert-item-ask-dayname-maybe-notime-here () | |
2003 | "" | |
2004 | (interactive) | |
2005 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2006 | (todos-insert-item nil 'ask-dayname nil nil t))) | |
2007 | ||
2008 | (defun todos-insert-item-ask-dayname-for-diary (&optional arg) | |
2009 | "" | |
2010 | (interactive "P") | |
2011 | (todos-insert-item arg 'ask-dayname nil t)) | |
2012 | ||
2013 | (defun todos-insert-item-ask-dayname-for-diary-here () | |
2014 | "" | |
2015 | (interactive) | |
2016 | (todos-insert-item nil 'ask-dayname nil t t)) | |
2017 | ||
2018 | (defun todos-insert-item-ask-dayname-here () | |
2019 | "" | |
2020 | (interactive) | |
2021 | (todos-insert-item nil 'ask-dayname nil nil t)) | |
2022 | ||
2023 | (defun todos-insert-item-ask-time (&optional arg) | |
2024 | "" | |
2025 | (interactive "P") | |
2026 | (todos-insert-item arg nil 'ask-time)) | |
2027 | ||
2028 | (defun todos-insert-item-ask-time-for-diary (&optional arg) | |
2029 | "" | |
2030 | (interactive "P") | |
2031 | (todos-insert-item arg nil 'ask-time t)) | |
2032 | ||
2033 | (defun todos-insert-item-ask-time-for-diary-here () | |
2034 | "" | |
2035 | (interactive) | |
2036 | (todos-insert-item nil nil 'ask-time t t)) | |
2037 | ||
2038 | (defun todos-insert-item-ask-time-here () | |
2039 | "" | |
2040 | (interactive) | |
2041 | (todos-insert-item nil nil 'ask-time nil t)) | |
2042 | ||
2043 | (defun todos-insert-item-maybe-notime (&optional arg) | |
2044 | "" | |
2045 | (interactive "P") | |
2046 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2047 | (todos-insert-item arg))) | |
2048 | ||
2049 | (defun todos-insert-item-maybe-notime-for-diary (&optional arg) | |
2050 | "" | |
2051 | (interactive "P") | |
2052 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2053 | (todos-insert-item arg nil nil t))) | |
2054 | ||
2055 | (defun todos-insert-item-maybe-notime-for-diary-here () | |
2056 | "" | |
2057 | (interactive) | |
2058 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2059 | (todos-insert-item nil nil nil t t))) | |
2060 | ||
2061 | (defun todos-insert-item-maybe-notime-here () | |
2062 | "" | |
2063 | (interactive) | |
2064 | (let ((todos-always-add-time-string (not todos-always-add-time-string))) | |
2065 | (todos-insert-item nil nil nil nil t))) | |
2066 | ||
2067 | (defun todos-insert-item-for-diary (&optional arg) | |
2068 | "" | |
2069 | (interactive "P") | |
2070 | (todos-insert-item nil nil nil t)) | |
2071 | ||
2072 | (defun todos-insert-item-for-diary-here () | |
2073 | "" | |
2074 | (interactive) | |
2075 | (todos-insert-item nil nil nil t t)) | |
2076 | ||
2077 | (defun todos-insert-item-here () | |
2078 | "Insert new Todo item directly above the item at point. | |
2079 | If point is on an empty line, insert the new item there." | |
2080 | (interactive) | |
2081 | (todos-insert-item nil nil nil nil t)) | |
2082 | ||
2083 | ;; FIXME: autoload when key-binding is defined in calendar.el | |
2084 | (defun todos-insert-item-from-calendar () | |
ee7412e4 | 2085 | "" |
2c173503 | 2086 | (interactive) |
d04d6b95 SB |
2087 | (pop-to-buffer (file-name-nondirectory todos-current-todos-file)) |
2088 | (todos-show) | |
ee7412e4 | 2089 | (todos-insert-item t 'calendar)) |
2c173503 SB |
2090 | |
2091 | ;; FIXME: calendar is loaded before todos | |
2092 | ;; (add-hook 'calendar-load-hook | |
2093 | ;; (lambda () | |
2094 | (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);)) | |
3f031767 SB |
2095 | |
2096 | (defun todos-delete-item () | |
2097 | "Delete current TODO list entry." | |
2098 | (interactive) | |
2099 | (if (> (count-lines (point-min) (point-max)) 0) | |
2c173503 | 2100 | (let* ((buffer-read-only) |
d04d6b95 SB |
2101 | (item (todos-item-string-start)) |
2102 | (diary-item (todos-diary-item-p)) | |
2103 | (cat (todos-current-category)) | |
2104 | (answer (y-or-n-p (concat "Permanently remove '" item "'? ")))) | |
2105 | (when answer | |
3f031767 | 2106 | (todos-remove-item) |
2c173503 SB |
2107 | (when (and (bolp) (eolp) |
2108 | ;; not if last item was deleted | |
2109 | (< (point-min) (point-max))) | |
2110 | (todos-backward-item)) | |
d04d6b95 SB |
2111 | (todos-item-counts cat 'delete) |
2112 | (and diary-item (todos-item-counts cat 'nondiary)) | |
2113 | (todos-update-categories-sexp) | |
f730d273 SB |
2114 | (todos-prefix-overlays))) |
2115 | (message "No TODO list entry to delete"))) ;FIXME: better message | |
3f031767 | 2116 | |
2c173503 SB |
2117 | (defun todos-edit-item () |
2118 | "Edit current TODO list entry." | |
2119 | (interactive) | |
d04d6b95 SB |
2120 | (when (todos-item-string) |
2121 | (let* ((buffer-read-only) | |
2122 | (start (todos-item-start)) | |
2123 | (item-beg (progn | |
2124 | (re-search-forward | |
2125 | (concat todos-date-string-start todos-date-pattern | |
2126 | "\\( " diary-time-regexp "\\)?" | |
2127 | (regexp-quote todos-nondiary-end) "?") | |
2128 | (line-end-position) t) | |
2129 | (1+ (- (point) start)))) | |
2130 | (item (todos-item-string)) | |
2131 | (opoint (point))) | |
2132 | (if (todos-string-multiline-p item) | |
2133 | (todos-edit-multiline) | |
2134 | (let ((new (read-string "Edit: " (cons item item-beg)))) | |
2135 | (while (not (string-match (concat todos-date-string-start | |
2136 | todos-date-pattern) new)) | |
2137 | (setq new (read-from-minibuffer "Item must start with a date: " new))) | |
2138 | ;; indent newlines inserted by C-q C-j if nonspace char follows | |
2139 | (setq new (replace-regexp-in-string | |
2140 | "\\(\n\\)[^[:blank:]]" | |
2141 | (concat "\n" (make-string todos-indent-to-here 32)) new | |
2142 | nil nil 1)) | |
2143 | ;; If user moved point during editing, make sure it moves back. | |
2144 | (goto-char opoint) | |
2145 | (todos-remove-item) | |
2146 | (todos-insert-with-overlays new) | |
2147 | (move-to-column item-beg)))))) | |
2c173503 SB |
2148 | |
2149 | ;; FIXME: run todos-check-format on exiting buffer (or check for date string | |
2150 | ;; and indentation) | |
2151 | (defun todos-edit-multiline () | |
2152 | "Set up a buffer for editing a multiline TODO list entry." | |
2153 | (interactive) | |
2154 | (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) | |
2155 | (switch-to-buffer | |
2156 | (make-indirect-buffer | |
d04d6b95 SB |
2157 | (file-name-nondirectory todos-current-todos-file) buffer-name)) |
2158 | (narrow-to-region (todos-item-start) (todos-item-end)) | |
2c173503 | 2159 | (todos-edit-mode) |
d04d6b95 SB |
2160 | (message "Type %s to return to Todos mode." |
2161 | (key-description (car (where-is-internal 'todos-edit-quit)))))) | |
2c173503 SB |
2162 | |
2163 | (defun todos-edit-quit () | |
ee7412e4 | 2164 | "" |
2c173503 | 2165 | (interactive) |
d04d6b95 SB |
2166 | (todos-save) |
2167 | ;; (unlock-buffer) | |
2168 | (kill-buffer) | |
2c173503 SB |
2169 | (save-excursion (todos-category-select))) |
2170 | ||
d04d6b95 | 2171 | (defun todos-edit-item-header (&optional part) |
ee7412e4 | 2172 | "" |
2c173503 | 2173 | (interactive) |
ee7412e4 | 2174 | (todos-item-start) |
d04d6b95 SB |
2175 | (re-search-forward (concat todos-date-string-start "\\(?1:" todos-date-pattern |
2176 | "\\)\\(?2: " diary-time-regexp "\\)?") | |
ee7412e4 | 2177 | (line-end-position) t) |
d04d6b95 SB |
2178 | (let* ((odate (match-string-no-properties 1)) |
2179 | (otime (match-string-no-properties 2)) | |
2180 | (buffer-read-only) | |
2181 | ndate ntime nheader) | |
2182 | (unless (eq part 'timeonly) | |
2183 | (setq ndate (if (save-match-data (string-match "[0-9]+" odate)) | |
2184 | (if (y-or-n-p "Change date? ") | |
2185 | (todos-read-date) | |
2186 | (todos-read-dayname)) | |
2187 | (if (y-or-n-p "Change day? ") | |
2188 | (todos-read-dayname) | |
2189 | (todos-read-date)))) | |
2190 | (replace-match ndate nil nil nil 1)) | |
2191 | (unless (eq part 'dateonly) | |
2192 | (setq ntime (save-match-data (todos-read-time))) | |
2193 | (when (< 0 (length ntime)) (setq ntime (concat " " ntime))) | |
2194 | (if otime | |
2195 | (replace-match ntime nil nil nil 2) | |
2196 | (goto-char (match-end 1)) | |
2197 | (insert ntime))))) | |
2198 | ||
2199 | (defun todos-edit-item-date () | |
2200 | "" | |
2201 | (interactive) | |
2202 | (todos-edit-item-header 'dateonly)) | |
2203 | ||
2204 | (defun todos-edit-item-date-is-today () | |
2205 | "" | |
2206 | (interactive) | |
2207 | (todos-edit-item-header 'today)) | |
2208 | ||
2209 | (defun todos-edit-item-time () | |
2210 | "" | |
2211 | (interactive) | |
2212 | (todos-edit-item-header 'timeonly)) | |
2213 | ||
2214 | ;; (progn | |
2215 | ;; (re-search-forward "\\(?1:foo\\)\\(ba\\)\\(?2:z\\)?" nil t) | |
2216 | ;; (goto-char (point-max)) | |
2217 | ;; (concat (match-string-no-properties 1) ", " (match-string-no-properties 2))) | |
2218 | ||
2219 | ;; foobaz | |
2220 | ||
2c173503 | 2221 | |
d04d6b95 | 2222 | (defun todos-raise-item-priority () |
3f031767 SB |
2223 | "Raise priority of current entry." |
2224 | (interactive) | |
2c173503 SB |
2225 | (unless (or (todos-done-item-p) |
2226 | (looking-at "^$")) ; between done and not done items | |
2227 | (let (buffer-read-only) | |
2228 | (if (> (count-lines (point-min) (point)) 0) | |
2229 | (let ((item (todos-item-string))) | |
d04d6b95 SB |
2230 | (when (eq major-mode 'todos-top-priorities-mode) |
2231 | (let ((cat1 (save-excursion | |
2232 | (re-search-forward | |
2233 | (concat todos-date-string-start todos-date-pattern | |
2234 | "\\( " diary-time-regexp | |
2235 | "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") | |
2236 | nil t) | |
2237 | (match-string 1))) | |
2238 | (cat2 (save-excursion | |
2239 | (todos-backward-item) | |
2240 | (re-search-forward | |
2241 | (concat todos-date-string-start todos-date-pattern | |
2242 | "\\( " diary-time-regexp | |
2243 | "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") | |
2244 | nil t) | |
2245 | (match-string 1)))) | |
2246 | (if (string= cat1 cat2) | |
2247 | (error "Cannot change item's priority in its category; do this in Todos mode")))) | |
2c173503 SB |
2248 | (todos-remove-item) |
2249 | (todos-backward-item) | |
2250 | (todos-insert-with-overlays item)) | |
f730d273 | 2251 | (message "No TODO list entry to raise"))))) ;FIXME: better message |
3f031767 | 2252 | |
d04d6b95 | 2253 | (defun todos-lower-item-priority () |
3f031767 SB |
2254 | "Lower priority of current entry." |
2255 | (interactive) | |
2c173503 SB |
2256 | (unless (or (todos-done-item-p) |
2257 | (looking-at "^$")) ; between done and not done items | |
d04d6b95 | 2258 | (let (buffer-read-only) |
ee7412e4 SB |
2259 | (if (save-excursion |
2260 | ;; can only lower non-final unfinished item | |
2261 | (todos-forward-item) | |
2262 | (and (looking-at todos-item-start) | |
2263 | (not (todos-done-item-p)))) | |
2c173503 | 2264 | ;; Assume there is a final newline |
ee7412e4 | 2265 | (let ((item (todos-item-string))) |
d04d6b95 SB |
2266 | (when (eq major-mode 'todos-top-priorities-mode) |
2267 | (let ((cat1 (save-excursion | |
2268 | (re-search-forward | |
2269 | (concat todos-date-string-start todos-date-pattern | |
2270 | "\\( " diary-time-regexp | |
2271 | "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") | |
2272 | nil t) | |
2273 | (match-string 1))) | |
2274 | (cat2 (save-excursion | |
2275 | (todos-forward-item) | |
2276 | (re-search-forward | |
2277 | (concat todos-date-string-start todos-date-pattern | |
2278 | "\\( " diary-time-regexp | |
2279 | "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") | |
2280 | nil t) | |
2281 | (match-string 1)))) | |
2282 | (if (string= cat1 cat2) | |
2283 | (error "Cannot change item's priority in its category; do this in Todos mode")))) | |
2c173503 SB |
2284 | (todos-remove-item) |
2285 | (todos-forward-item) | |
ee7412e4 | 2286 | (when (todos-done-item-p) (forward-line -1)) |
2c173503 | 2287 | (todos-insert-with-overlays item)) |
f730d273 | 2288 | (message "No TODO list entry to lower"))))) ;FIXME: better message |
3f031767 | 2289 | |
d04d6b95 SB |
2290 | (defun todos-set-item-priority (item cat) |
2291 | "Set priority of todo ITEM in category CAT and move item to suit." | |
2292 | (interactive (list (todos-item-string) (todos-current-category))) | |
2293 | (unless (called-interactively-p t) | |
2294 | (todos-category-number cat) | |
2295 | (todos-category-select)) | |
2296 | (let* ((todo (todos-get-count 'todo cat)) | |
2297 | (maxnum (1+ todo)) | |
2298 | (buffer-read-only) | |
2299 | priority candidate prompt) | |
2300 | (unless (zerop todo) | |
2301 | (while (not priority) | |
2302 | (setq candidate | |
2303 | (string-to-number (read-from-minibuffer | |
2304 | (concat prompt | |
2305 | (format "Set item priority (1-%d): " | |
2306 | maxnum))))) | |
2307 | (setq prompt | |
2308 | (when (or (< candidate 1) (> candidate maxnum)) | |
2309 | (format "Priority must be an integer between 1 and %d.\n" maxnum))) | |
2310 | (unless prompt (setq priority candidate))) | |
2311 | ;; interactively, just relocate the item within its category | |
2312 | (when (called-interactively-p) (todos-remove-item)) | |
2313 | (goto-char (point-min)) | |
2314 | (unless (= priority 1) (todos-forward-item (1- priority)))) | |
2315 | (todos-insert-with-overlays item))) | |
2316 | ||
2317 | ;; (defun todos-set-item-top-priority () | |
2318 | ;; "Set priority of item at point in the top priorities listing." | |
2319 | ;; (interactive) | |
2320 | ;; (let* ((item (todos-item-string)) | |
2321 | ;; (cat (save-excursion | |
2322 | ;; (re-search-forward | |
2323 | ;; (concat todos-date-string-start todos-date-pattern | |
2324 | ;; "\\( " diary-time-regexp | |
2325 | ;; "\\)?\\]?\\(?1: \\[\\(.+:\\)?.+\\]\\)") | |
2326 | ;; nil t) | |
2327 | ;; (match-string 1))) | |
2328 | ;; (opoint (point)) | |
2329 | ;; (count 1) | |
2330 | ;; (old-priority (save-excursion | |
2331 | ;; (goto-char (point-min)) | |
2332 | ;; (while (< (point) opoint) | |
2333 | ;; (todos-forward-item) | |
2334 | ;; (setq count (1+ count)))))) | |
2335 | ;; ) | |
2336 | ||
2337 | (defun todos-move-item (&optional file) | |
db2c5d34 SB |
2338 | "Move the current todo item to another, interactively named, category. |
2339 | ||
d04d6b95 SB |
2340 | If the named category is not one of the current todo categories, |
2341 | then it is created and the item becomes the first entry in that | |
2342 | category. | |
2343 | ||
2344 | With optional non-nil argument FILE, first ask for another Todos | |
2345 | file and then solicit a category within that file to move the | |
2346 | item to." | |
db2c5d34 | 2347 | (interactive) |
2c173503 SB |
2348 | (unless (or (todos-done-item-p) |
2349 | (looking-at "^$")) ; between done and not done items | |
2350 | (let ((buffer-read-only) | |
d04d6b95 SB |
2351 | (modified (buffer-modified-p)) |
2352 | (oldfile todos-current-todos-file) | |
2c173503 | 2353 | (oldnum todos-category-number) |
ee7412e4 | 2354 | (oldcat (todos-current-category)) |
2c173503 | 2355 | (item (todos-item-string)) |
d04d6b95 SB |
2356 | (diary-item (todos-diary-item-p)) |
2357 | (newfile (if file (todos-read-file-name "Choose a Todos file: "))) | |
2c173503 | 2358 | (opoint (point)) |
ee7412e4 | 2359 | (orig-mrk (progn (todos-item-start) (point-marker))) |
d04d6b95 | 2360 | newcat moved) |
2c173503 SB |
2361 | (unwind-protect |
2362 | (progn | |
d04d6b95 SB |
2363 | (todos-remove-item) |
2364 | (todos-item-counts oldcat 'delete) | |
2365 | (and diary-item (todos-item-counts oldcat 'nondiary)) | |
2366 | (when newfile | |
2367 | (find-file-existing newfile) | |
2368 | (setq todos-current-todos-file newfile | |
2369 | todos-categories (todos-make-categories-list))) | |
2370 | (setq newcat (todos-read-category "Move item to category: ")) | |
2371 | (unless (assoc newcat todos-categories) (todos-add-category newcat)) | |
f730d273 | 2372 | (todos-set-item-priority item newcat) |
ee7412e4 | 2373 | (setq moved t) |
d04d6b95 SB |
2374 | (todos-item-counts newcat 'insert) |
2375 | (and diary-item (todos-item-counts newcat 'diary))) | |
2c173503 | 2376 | (unless moved |
d04d6b95 SB |
2377 | (if newfile |
2378 | (find-file-existing oldfile) | |
2379 | (setq todos-current-todos-file oldfile | |
2380 | todos-categories (todos-make-categories-list))) | |
2c173503 SB |
2381 | (widen) |
2382 | (goto-char orig-mrk) | |
2383 | (todos-insert-with-overlays item) | |
2384 | (setq todos-category-number oldnum) | |
d04d6b95 SB |
2385 | (todos-item-counts oldcat 'insert) |
2386 | (and diary-item (todos-item-counts oldcat 'diary)) | |
2c173503 | 2387 | (todos-category-select) |
d04d6b95 | 2388 | (set-buffer-modified-p modified) |
2c173503 SB |
2389 | (goto-char opoint)) |
2390 | (set-marker orig-mrk nil))))) | |
2391 | ||
d04d6b95 SB |
2392 | (defun todos-move-item-to-file () |
2393 | "" | |
2394 | (interactive) | |
2395 | (todos-move-item t)) | |
2396 | ||
2c173503 SB |
2397 | (defun todos-item-done () |
2398 | "Mark current item as done and move it to category's done section." | |
b28025ed | 2399 | (interactive) |
2c173503 SB |
2400 | (unless (or (todos-done-item-p) |
2401 | (looking-at "^$")) | |
2402 | (let* ((buffer-read-only) | |
d04d6b95 | 2403 | (cat (todos-current-category)) |
2c173503 | 2404 | (item (todos-item-string)) |
d04d6b95 | 2405 | (diary-item (todos-diary-item-p)) |
2c173503 | 2406 | (date-string (calendar-date-string (calendar-current-date) t t)) |
ee7412e4 | 2407 | (time-string (if todos-always-add-time-string ;FIXME: delete condition |
2c173503 SB |
2408 | (concat " " (substring (current-time-string) 11 16)) |
2409 | "")) | |
d04d6b95 SB |
2410 | ;; FIXME: todos-nondiary-* |
2411 | (done-item (concat "[" todos-done-string date-string time-string "] " | |
2412 | item))) | |
2c173503 SB |
2413 | (todos-remove-item) |
2414 | (save-excursion | |
2415 | (widen) | |
d04d6b95 SB |
2416 | (re-search-forward (concat "^" (regexp-quote todos-category-done)) nil t) |
2417 | (forward-char) | |
2418 | (todos-insert-with-overlays done-item)) | |
2419 | (todos-item-counts cat 'done) | |
2420 | (and diary-item (todos-item-counts cat 'nondiary)) | |
2421 | (save-excursion (todos-category-select))))) | |
2c173503 | 2422 | |
2c173503 | 2423 | (defun todos-item-undo () |
ee7412e4 | 2424 | "" |
2c173503 SB |
2425 | (interactive) |
2426 | (when (todos-done-item-p) | |
2427 | (let* ((buffer-read-only) | |
ee7412e4 SB |
2428 | (cat (todos-current-category)) |
2429 | (done-item (todos-item-string)) | |
2430 | (opoint (point)) | |
2431 | (orig-mrk (progn (todos-item-start) (point-marker))) | |
2432 | (start (search-forward "] ")) ; end of done date string | |
2433 | (item (buffer-substring start (todos-item-end))) | |
2434 | undone) | |
2c173503 | 2435 | (todos-remove-item) |
ee7412e4 SB |
2436 | (unwind-protect |
2437 | (progn | |
ee7412e4 | 2438 | (todos-set-item-priority item cat) |
ee7412e4 | 2439 | (setq undone t) |
d04d6b95 SB |
2440 | (todos-item-counts cat 'undo) |
2441 | (and (todos-diary-item-p) (todos-item-counts cat 'diary))) | |
ee7412e4 SB |
2442 | (unless undone |
2443 | (widen) | |
2444 | (goto-char orig-mrk) | |
2445 | (todos-insert-with-overlays done-item) | |
ee7412e4 SB |
2446 | (let ((todos-show-with-done t)) |
2447 | (todos-category-select) | |
2448 | (goto-char opoint))) | |
2449 | (set-marker orig-mrk nil))))) | |
2c173503 | 2450 | |
d04d6b95 SB |
2451 | (defun todos-archive-done-items () |
2452 | "Archive the done items in the current category." | |
2453 | (interactive) | |
2454 | (let ((cat (todos-current-category))) | |
2455 | (if (zerop (todos-get-count 'done cat)) | |
2456 | (message "No done items in this category") | |
2457 | (when (y-or-n-p "Move all done items in this category to the archive? ") | |
2458 | (let* ((afile (concat (file-name-sans-extension (buffer-file-name)) ".toda")) | |
2459 | (archive (find-file-noselect afile t)) | |
2460 | beg end | |
2461 | (buffer-read-only nil)) | |
2462 | (save-excursion | |
2463 | (save-restriction | |
2464 | (goto-char (point-min)) | |
2465 | (widen) | |
2466 | (setq beg (progn | |
2467 | (re-search-forward todos-done-string-start nil t) | |
2468 | (match-beginning 0))) | |
2469 | (setq end (if (re-search-forward | |
2470 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
2471 | (match-beginning 0) | |
2472 | (point-max))) | |
2473 | (setq done (buffer-substring beg end)) | |
2474 | (with-current-buffer archive | |
2475 | (let (buffer-read-only) | |
2476 | (widen) | |
2477 | (goto-char (point-min)) | |
2478 | (if (progn | |
2479 | (re-search-forward | |
2480 | (concat "^" (regexp-quote (concat todos-category-beg cat))) | |
2481 | nil t) | |
2482 | (re-search-forward (regexp-quote todos-category-done) nil t)) | |
2483 | (forward-char) | |
2484 | (insert todos-category-beg cat "\n\n" todos-category-done "\n")) | |
2485 | (insert done) | |
2486 | (save-buffer))) | |
2487 | (remove-overlays beg end) | |
2488 | (delete-region beg end) | |
2489 | (todos-item-counts cat 'archive))))) | |
2490 | (message "Done items archived.")))) | |
2491 | ||
2492 | (defun todos-unarchive-category () | |
2493 | "Restore this archived category to done items in Todos file." | |
2494 | (interactive) | |
2495 | (when (y-or-n-p "Restore all items in this category to Todos file as done items? ") | |
2496 | (let ((buffer-read-only nil) | |
2497 | (tbuf (find-file-noselect | |
2498 | (concat (file-name-sans-extension (buffer-file-name)) ".todo") | |
2499 | t)) | |
2500 | (cat (todos-current-category)) | |
2501 | (items (buffer-substring (point-min) (point-max)))) | |
2502 | (with-current-buffer tbuf | |
2503 | (let (buffer-read-only) | |
2504 | (widen) | |
2505 | (goto-char (point-min)) | |
2506 | (re-search-forward (concat "^" (regexp-quote | |
2507 | (concat todos-category-beg cat))) | |
2508 | nil t) | |
2509 | (if (re-search-forward (concat "^" (regexp-quote todos-category-beg)) | |
2510 | nil t) | |
2511 | (goto-char (match-beginning 0)) | |
2512 | (goto-char (point-max))) | |
2513 | (insert items))) | |
2514 | (widen) | |
2515 | (let ((beg (re-search-backward (concat "^" | |
2516 | (regexp-quote todos-category-beg) | |
2517 | cat) nil t)) | |
2518 | (end (if (re-search-forward | |
2519 | (concat "^" (regexp-quote todos-category-beg)) nil t 2) | |
2520 | (match-beginning 0) | |
2521 | (point-max)))) | |
2522 | (remove-overlays beg end) | |
2523 | (delete-region beg end)) | |
2524 | (goto-char (point-min)) | |
2525 | (if (re-search-forward | |
2526 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
2527 | (progn | |
2528 | ;; delete category from archive | |
2529 | (setq todos-categories (delete (assoc cat todos-categories) | |
2530 | todos-categories)) | |
2531 | (todos-update-categories-sexp)) | |
2532 | ;; no more categories in archive, so delete it | |
2533 | (set-buffer-modified-p nil) ; no questions | |
2534 | (delete-file (buffer-file-name)) | |
2535 | (kill-buffer)) | |
2536 | (let ((tfile (buffer-file-name tbuf)) | |
2537 | (todos-show-with-done t)) | |
2538 | (find-file tfile) | |
2539 | (setq todos-current-todos-file tfile | |
2540 | ;; also updates item counts | |
2541 | todos-categories (todos-make-categories-list t) | |
2542 | todos-category-number (todos-category-number cat)) | |
2543 | (todos-show) | |
2544 | (message "Items unarchived."))))) | |
2545 | ||
2c173503 | 2546 | (defun todos-toggle-item-diary-inclusion () |
ee7412e4 | 2547 | "" |
2c173503 SB |
2548 | (interactive) |
2549 | (save-excursion | |
2550 | (let* ((buffer-read-only) | |
2551 | (beg (todos-item-start)) | |
2552 | (lim (save-excursion (todos-item-end))) | |
2553 | (end (save-excursion | |
2554 | (or (todos-time-string-match lim) | |
d04d6b95 SB |
2555 | (todos-date-string-match lim)))) |
2556 | (cat (todos-current-category))) | |
2557 | (if (looking-at (regexp-quote todos-nondiary-start)) | |
2c173503 SB |
2558 | (progn |
2559 | (replace-match "") | |
d04d6b95 SB |
2560 | (search-forward todos-nondiary-end (1+ end) t) |
2561 | (replace-match "") | |
2562 | (todos-item-counts cat 'nondiary)) | |
2c173503 | 2563 | (when end |
d04d6b95 | 2564 | (insert todos-nondiary-start) |
2c173503 | 2565 | (goto-char (1+ end)) |
d04d6b95 SB |
2566 | (insert todos-nondiary-end) |
2567 | (todos-item-counts cat 'diary)))))) | |
2c173503 SB |
2568 | |
2569 | (defun todos-toggle-diary-inclusion (arg) | |
ee7412e4 | 2570 | "" |
2c173503 SB |
2571 | (interactive "p") |
2572 | (save-excursion | |
2573 | (save-restriction | |
2574 | (when (eq arg 2) (widen)) ;FIXME: don't toggle done items | |
2575 | (when (or (eq arg 1) (eq arg 2)) | |
2576 | (goto-char (point-min)) | |
2577 | (when (eq arg 2) | |
2578 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) | |
2579 | (forward-line) | |
d04d6b95 | 2580 | (when (looking-at (regexp-quote todos-category-done)) (forward-line))) |
2c173503 SB |
2581 | (while (not (eobp)) |
2582 | (todos-toggle-item-diary-inclusion) | |
2583 | (todos-forward-item)))))) | |
3f031767 | 2584 | |
d04d6b95 SB |
2585 | (defun todos-toggle-item-diary-nonmarking () |
2586 | "" | |
2587 | (interactive) | |
2588 | (let ((buffer-read-only)) | |
2589 | (save-excursion | |
2590 | (todos-item-start) | |
2591 | (unless (looking-at (regexp-quote todos-nondiary-start)) | |
2592 | (if (looking-at (regexp-quote diary-nonmarking-symbol)) | |
2593 | (replace-match "") | |
2594 | (insert diary-nonmarking-symbol)))))) | |
2595 | ||
2596 | (defun todos-toggle-diary-nonmarking () | |
2597 | "" | |
2598 | (interactive) | |
2599 | (save-excursion | |
2600 | (goto-char (point-min)) | |
2601 | (while (not (eobp)) | |
2602 | (todos-toggle-item-diary-nonmarking) | |
2603 | (todos-forward-item)))) | |
2604 | ||
2605 | ;; FIXME: save to a file named according to the current todos file | |
3f031767 SB |
2606 | (defun todos-save-top-priorities (&optional nof-priorities) |
2607 | "Save top priorities for each category in `todos-file-top'. | |
2608 | ||
2609 | Number of entries for each category is given by NOF-PRIORITIES which | |
2610 | defaults to `todos-show-priorities'." | |
2611 | (interactive "P") | |
2612 | (save-window-excursion | |
2613 | (save-excursion | |
2614 | (save-restriction | |
2615 | (todos-top-priorities nof-priorities) | |
2616 | (set-buffer todos-tmp-buffer-name) | |
2617 | (write-file todos-file-top) | |
2618 | (kill-this-buffer))))) | |
2619 | ||
d04d6b95 SB |
2620 | ;; ;;;###autoload |
2621 | ;; (defun todos-print (&optional category-pr-page) | |
2622 | ;; "Print todo summary using `todos-print-function'. | |
2623 | ;; If CATEGORY-PR-PAGE is non-nil, a page separator `^L' is inserted | |
2624 | ;; between each category. | |
3f031767 | 2625 | |
d04d6b95 SB |
2626 | ;; Number of entries for each category is given by `todos-print-priorities'." |
2627 | ;; (interactive "P") | |
2628 | ;; (when (yes-or-no-p "Print Todos list? ") | |
2629 | ;; (save-window-excursion | |
2630 | ;; (save-excursion | |
2631 | ;; (save-restriction | |
2632 | ;; (todos-top-priorities todos-print-priorities | |
2633 | ;; category-pr-page) | |
2634 | ;; (set-buffer todos-tmp-buffer-name) | |
2635 | ;; (and (funcall todos-print-function) | |
2636 | ;; (kill-this-buffer)) | |
2637 | ;; (message "Todo printing done.")))))) | |
2638 | ||
2639 | (defun todos-print () | |
2640 | "" | |
2641 | (interactive) | |
2642 | (let ((buf (cond ((eq major-mode 'todos-mode) | |
2643 | (concat "Category: " (todos-current-category) " (" | |
2644 | (file-name-nondirectory todos-current-todos-file) ") ")) | |
2645 | ((eq major-mode 'todos-top-priorities-mode) | |
2646 | "Todos Top Priorities"))) | |
2647 | (prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) | |
2648 | (num 0) | |
2649 | (fill-prefix (make-string todos-indent-to-here 32)) | |
2650 | (content (buffer-string))) | |
2651 | (with-current-buffer (get-buffer-create buf) | |
2652 | (insert content) | |
2653 | (goto-char (point-min)) | |
2654 | (while (not (eobp)) | |
2655 | (let ((beg (point)) | |
2656 | (end (save-excursion (todos-item-end)))) | |
2657 | (when todos-number-prefix | |
2658 | (setq num (1+ num)) | |
2659 | (setq prefix (propertize (concat (number-to-string num) " ") | |
2660 | 'face 'todos-prefix-string))) | |
2661 | (insert prefix) | |
2662 | (fill-region beg end)) | |
2663 | (todos-forward-item)) | |
2664 | ;; FIXME: ask user to choose between sending to printer: | |
2665 | ;; (ps-print-buffer-with-faces) | |
2666 | ;; and printing to a file: | |
2667 | (ps-spool-buffer-with-faces) | |
2668 | ;; (write-file ) | |
2669 | ) | |
2670 | (kill-buffer buf))) | |
2c173503 SB |
2671 | |
2672 | ;; --------------------------------------------------------------------------- | |
2673 | ||
d04d6b95 | 2674 | ;;; Internals |
2c173503 | 2675 | |
d04d6b95 | 2676 | (defvar todos-date-pattern ;FIXME: start with "^" ? |
ee7412e4 | 2677 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) |
d04d6b95 | 2678 | (concat "\\(?:" dayname "\\|" |
ee7412e4 | 2679 | (let ((dayname) |
d04d6b95 | 2680 | (monthname (format "\\(?:%s\\|\\*\\)" |
ee7412e4 SB |
2681 | (diary-name-pattern calendar-month-name-array |
2682 | calendar-month-abbrev-array | |
2683 | t))) | |
d04d6b95 SB |
2684 | (month "\\(?:[0-9]+\\|\\*\\)") |
2685 | (day "\\(?:[0-9]+\\|\\*\\)") | |
2686 | (year "-?\\(?:[0-9]+\\|\\*\\)")) | |
ee7412e4 SB |
2687 | (mapconcat 'eval calendar-date-display-form "")) |
2688 | "\\)")) | |
2c173503 SB |
2689 | "Regular expression matching a Todos date header.") |
2690 | ||
d04d6b95 SB |
2691 | (defvar todos-date-string-start |
2692 | (concat "^\\(" (regexp-quote todos-nondiary-start) "\\|" | |
2693 | (regexp-quote diary-nonmarking-symbol) "\\)?") ;FIXME: matches anything | |
2694 | "Regular expression matching part of item header before the date.") | |
2695 | ||
2696 | (defvar todos-done-string-start | |
2697 | (concat "^" (regexp-quote todos-nondiary-start) (regexp-quote todos-done-string)) | |
2698 | "Regular expression matching start of done item.") | |
2699 | ||
2700 | ;; FIXME: rename these *-matcher | |
2c173503 | 2701 | (defun todos-date-string-match (lim) |
d04d6b95 SB |
2702 | "Search for Todos date strings within LIM for font-locking." |
2703 | (re-search-forward (concat todos-date-string-start "\\(?1:" | |
2704 | todos-date-pattern "\\)") lim t)) | |
2c173503 SB |
2705 | |
2706 | (defun todos-time-string-match (lim) | |
d04d6b95 SB |
2707 | "Search for Todos time strings within LIM for font-locking." |
2708 | (re-search-forward (concat todos-date-string-start todos-date-pattern | |
2709 | " \\(?1:" diary-time-regexp "\\)") lim t)) | |
2c173503 SB |
2710 | |
2711 | (defun todos-done-string-match (lim) | |
d04d6b95 SB |
2712 | "Search for Todos done headers within LIM for font-locking." |
2713 | (re-search-forward (concat todos-done-string-start | |
2714 | "[^][]+]") | |
2715 | lim t)) | |
2c173503 SB |
2716 | |
2717 | (defun todos-category-string-match (lim) | |
d04d6b95 SB |
2718 | "Search for Todos category headers within LIM for font-locking." |
2719 | (if (eq major-mode 'todos-top-priorities-mode) | |
2720 | (re-search-forward | |
2721 | ;; (concat "^\\(?1:" (regexp-quote todos-category-beg) ".*\\)$") | |
2722 | (concat "\\(?:^\\[?" todos-date-pattern "\\(?: " diary-time-regexp | |
2723 | "\\)?\\]?\\) \\(?1:\\[.+\\]\\)") lim t))) | |
2c173503 SB |
2724 | |
2725 | (defun todos-check-format () | |
2726 | "Signal an error if the current Todos file is ill-formatted." | |
2727 | (save-excursion | |
2728 | (save-restriction | |
2729 | (widen) | |
2730 | (goto-char (point-min)) | |
2731 | (let ((legit (concat "^\\(" (regexp-quote todos-category-beg) "\\)" | |
2732 | "\\|\\(\\[?" todos-date-pattern "\\)" | |
2733 | "\\|\\([ \t]+[^ \t]*\\)" | |
2734 | "\\|$"))) | |
2735 | (while (not (eobp)) | |
2736 | (unless (looking-at legit) | |
2737 | (error "Illegitimate Todos file format at line %d" | |
2738 | (line-number-at-pos (point)))) | |
2739 | (forward-line))))) | |
2740 | (message "This Todos file is well-formatted.")) | |
2741 | ||
2742 | (defun todos-wrap-and-indent () | |
ee7412e4 | 2743 | "" |
2c173503 SB |
2744 | (make-local-variable 'word-wrap) |
2745 | (setq word-wrap t) | |
2746 | (make-local-variable 'wrap-prefix) | |
ee7412e4 | 2747 | (setq wrap-prefix (make-string todos-indent-to-here 32)) |
2c173503 SB |
2748 | (unless (member '(continuation) fringe-indicator-alist) |
2749 | (push '(continuation) fringe-indicator-alist))) | |
2750 | ||
ee7412e4 SB |
2751 | (defun todos-indent () |
2752 | "" | |
2753 | (indent-to todos-indent-to-here todos-indent-to-here)) | |
2754 | ||
2c173503 | 2755 | (defun todos-prefix-overlays () |
ee7412e4 | 2756 | "" |
2c173503 SB |
2757 | (when (or todos-number-prefix |
2758 | (not (string-match "^[[:space:]]*$" todos-prefix))) | |
2759 | (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) | |
ee7412e4 | 2760 | (num 0)) |
2c173503 SB |
2761 | (save-excursion |
2762 | (goto-char (point-min)) | |
ee7412e4 SB |
2763 | (while (not (eobp)) |
2764 | (when (or (todos-date-string-match (line-end-position)) | |
2765 | (todos-done-string-match (line-end-position))) | |
2766 | (goto-char (match-beginning 0)) | |
2767 | (when todos-number-prefix | |
2768 | (setq num (1+ num)) | |
2769 | ;; reset number for done items | |
f730d273 SB |
2770 | (when |
2771 | ;; FIXME: really need this? | |
ee7412e4 SB |
2772 | ;; if last not done item is multiline, then |
2773 | ;; todos-done-string-match skips empty line, so have | |
2774 | ;; to look back. | |
d04d6b95 SB |
2775 | (and (looking-at ;; (concat "^\\[" (regexp-quote todos-done-string)) |
2776 | todos-done-string-start) | |
2777 | (looking-back (concat "^" (regexp-quote todos-category-done) | |
2778 | "\n"))) | |
f730d273 | 2779 | (setq num 1)) |
ee7412e4 SB |
2780 | (setq prefix (propertize (concat (number-to-string num) " ") |
2781 | 'face 'todos-prefix-string))) | |
ee7412e4 SB |
2782 | (let* ((ovs (overlays-in (point) (point))) |
2783 | (ov-pref (car ovs)) | |
2784 | (val (when ov-pref (overlay-get ov-pref 'before-string)))) | |
d04d6b95 | 2785 | ;; FIXME: is this possible? |
ee7412e4 SB |
2786 | (when (and (> (length ovs) 1) |
2787 | (not (equal val prefix))) | |
2788 | (setq ov-pref (cadr ovs))) | |
2789 | (when (not (equal val prefix)) | |
d04d6b95 | 2790 | ;; (when ov-pref (delete-overlay ov-pref)) ; why doesn't this work ??? |
f730d273 | 2791 | (remove-overlays (point) (point)); 'before-string val) ; or this ??? |
ee7412e4 SB |
2792 | (setq ov-pref (make-overlay (point) (point))) |
2793 | (overlay-put ov-pref 'before-string prefix)))) | |
2794 | (forward-line)))))) | |
2c173503 | 2795 | |
f730d273 SB |
2796 | (defun todos-reset-prefix (symbol value) |
2797 | "Set SYMBOL's value to VALUE, and ." ; FIXME | |
d04d6b95 SB |
2798 | (let ((oldvalue (symbol-value symbol)) |
2799 | (files (append todos-files todos-archives))) | |
f730d273 SB |
2800 | (custom-set-default symbol value) |
2801 | (when (not (equal value oldvalue)) | |
d04d6b95 SB |
2802 | (dolist (f files) |
2803 | (with-current-buffer (find-file-noselect f) | |
2804 | (save-window-excursion | |
2805 | (todos-show) | |
2806 | (save-excursion | |
2807 | (widen) | |
2808 | (goto-char (point-min)) | |
2809 | (while (not (eobp)) | |
2810 | (remove-overlays (point) (point)); 'before-string prefix) | |
2811 | (forward-line))) | |
2812 | ;; activate the new setting (save-restriction does not help) | |
2813 | (save-excursion (todos-category-select)))))))) | |
f730d273 | 2814 | |
2c173503 SB |
2815 | (defun todos-reset-separator (symbol value) |
2816 | "Set SYMBOL's value to VALUE, and ." ; FIXME | |
d04d6b95 SB |
2817 | (let ((oldvalue (symbol-value symbol)) |
2818 | (files (append todos-files todos-archives))) | |
2c173503 | 2819 | (custom-set-default symbol value) |
2c173503 | 2820 | (when (not (equal value oldvalue)) |
d04d6b95 SB |
2821 | (dolist (f files) |
2822 | (with-current-buffer (find-file-noselect f) | |
2823 | (save-window-excursion | |
2824 | (todos-show) | |
2825 | (save-excursion | |
2826 | (goto-char (point-min)) | |
2827 | (when (re-search-forward | |
2828 | ;; (concat "^\\[" (regexp-quote todos-done-string)) | |
2829 | todos-done-string-start nil t) | |
2830 | (remove-overlays (point) (point)))) | |
2831 | ;; activate the new setting (save-restriction does not help) | |
2832 | ;; FIXME: need to wrap in save-excursion ? | |
2833 | (todos-category-select))))))) | |
2834 | ||
2835 | (defun todos-reset-done-string (symbol value) | |
2836 | "Set SYMBOL's value to VALUE, and ." ; FIXME | |
2837 | ;; (let ((oldvalue (symbol-value symbol))) | |
2838 | ;; (custom-set-default symbol value) | |
2839 | ;; (when (not (equal value oldvalue)) | |
2840 | ;; (save-window-excursion | |
2841 | ;; (todos-show) | |
2842 | ;; (save-excursion | |
2843 | ;; (goto-char (point-min)) | |
2844 | ;; (when (re-search-forward ;; (concat "^\\[" (regexp-quote todos-done-string)) | |
2845 | ;; todos-done-string-start nil t) | |
2846 | ;; (remove-overlays (point) (point)))) | |
2847 | ;; ;; activate the new setting (save-restriction does not help) | |
2848 | ;; ;; FIXME: need to wrap in save-excursion ? | |
2849 | ;; (todos-category-select)))) | |
2850 | ) | |
2851 | ||
2852 | (defun todos-reset-categories (symbol value) | |
2853 | "Set SYMBOL's value to VALUE, and ." ; FIXME | |
2854 | (custom-set-default symbol value) | |
2855 | (save-window-excursion | |
2856 | (todos-show) | |
2857 | (setq todos-categories | |
2858 | (if value | |
2859 | (todos-truncate-categories-list) | |
2860 | ;; FIXME: with-current-buffer Todos | |
2861 | ;; file and update | |
2862 | ;; todos-categories-sexp | |
2863 | (todos-make-categories-list t))))) | |
2864 | ;; (save-excursion | |
2865 | ;; ;; activate the new setting (save-restriction does not help) | |
2866 | ;; ;; FIXME: need to wrap in save-excursion ? | |
2867 | ;; (todos-category-select))))) | |
2868 | ||
2869 | (defun todos-toggle-switch-todos-file-noninteractively (symbol value) | |
2870 | "" | |
2871 | (custom-set-default symbol value) | |
2872 | (if value | |
2873 | (add-hook 'post-command-hook | |
2874 | 'todos-switch-todos-file nil t) | |
2875 | (remove-hook 'post-command-hook | |
2876 | 'todos-switch-todos-file t))) | |
2877 | ||
2878 | (defun todos-switch-todos-file (&optional file) ;FIXME: need FILE? | |
2879 | "Make another Todos file the current Todos file. | |
2880 | Called by post-command-hook if `todos-auto-switch-todos-file' is | |
2881 | non-nil (and also in `todos-top-priorities'), it makes the | |
2882 | current buffer the current Todos file if it is visiting a Todos | |
2883 | file." | |
2884 | (let ((file (or file (buffer-file-name))) | |
2885 | (files (if todos-show-done-only ;FIXME: should only hold for | |
2886 | (funcall todos-files-function t) ; todos-archives | |
2887 | (funcall todos-files-function))) | |
2888 | cat) | |
2889 | (when (and (member file files) | |
2890 | (not (equal todos-current-todos-file file))) | |
2891 | ;; (let ((catbuf (get-buffer todos-categories-buffer))) | |
2892 | ;; (if catbuf (not (eq (other-buffer) catbuf))))) | |
2893 | (if todos-ignore-archived-categories | |
2894 | (progn | |
2895 | (setq todos-categories nil) | |
2896 | (setq todos-categories (todos-truncate-categories-list))) | |
2897 | (setq todos-categories (todos-make-categories-list))) | |
2898 | ;; if file is already in a buffer, redisplay the previous current category | |
2899 | (when (< (- (point-max) (point-min)) (buffer-size)) | |
2900 | (widen) | |
2901 | (when (re-search-backward (concat "^" (regexp-quote todos-category-beg) | |
2902 | "\\(.+\\)\n") nil t) | |
2903 | (setq cat (match-string-no-properties 1)) | |
2904 | (setq todos-category-number (todos-category-number cat)))) | |
2905 | (setq todos-current-todos-file file) | |
2906 | ;; (or todos-category-number (setq todos-category-number 1)) | |
2907 | ;; (if (zerop todos-category-number) (setq todos-category-number 1)) | |
2908 | (todos-show)))) | |
2c173503 | 2909 | |
ee7412e4 SB |
2910 | (defun todos-category-number (cat) |
2911 | "Set todos-category-number to index of CAT in todos-categories." | |
d04d6b95 SB |
2912 | (let ((categories (mapcar 'car todos-categories))) |
2913 | (setq todos-category-number | |
2914 | (1+ (- (length categories) | |
2915 | (length (member cat categories))))))) | |
2916 | ||
2c173503 SB |
2917 | (defun todos-current-category () |
2918 | "Return the name of the current category." | |
d04d6b95 | 2919 | (car (nth (1- todos-category-number) todos-categories))) |
2c173503 | 2920 | |
d04d6b95 SB |
2921 | ;; FIXME: wrap in save-excursion (or else have to use todos-show in |
2922 | ;; e.g. todos-{forward, backward}-category) | |
2c173503 | 2923 | (defun todos-category-select () |
d04d6b95 SB |
2924 | "Display the current category correctly. |
2925 | ||
2926 | With non-nil `todos-show-with-done' display the category's done | |
2927 | \(but not archived) items below the unfinished todo items; else | |
2928 | display just the todo items." | |
2929 | (let ((name (todos-current-category)) | |
2930 | cat-begin cat-end done-start done-sep-start done-end) | |
2c173503 SB |
2931 | (widen) |
2932 | (goto-char (point-min)) | |
d04d6b95 SB |
2933 | (re-search-forward |
2934 | (concat "^" (regexp-quote (concat todos-category-beg name)) "$") nil t) | |
2935 | (setq cat-begin (1+ (line-end-position))) | |
2936 | (setq cat-end (if (re-search-forward | |
2937 | (concat "^" (regexp-quote todos-category-beg)) nil t) | |
2938 | (match-beginning 0) | |
2939 | (point-max))) | |
2940 | (setq mode-line-buffer-identification | |
2941 | (concat (format "Category %d: %s" todos-category-number name))) | |
2942 | (narrow-to-region cat-begin cat-end) | |
2943 | (todos-prefix-overlays) | |
2944 | (goto-char (point-min)) | |
2945 | (if (re-search-forward (concat "\n\\(" (regexp-quote todos-category-done) | |
2946 | "\\)") nil t) | |
2947 | (progn | |
2948 | (setq done-start (match-beginning 0)) | |
2949 | (setq done-sep-start (match-beginning 1)) | |
2950 | (setq done-end (match-end 0))) | |
2951 | (error "Category %s is missing todos-category-done string" name)) | |
2952 | (if todos-show-done-only | |
2953 | (narrow-to-region (1+ done-end) (point-max)) | |
2954 | ;; display or hide done items as per todos-show-with-done | |
2955 | ;; FIXME: use todos-done-string-start ? | |
f730d273 SB |
2956 | (when (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string) |
2957 | "\\)") nil t) | |
d04d6b95 SB |
2958 | (let (done-sep prefix ov-pref ov-done) |
2959 | ;; FIXME: delete overlay when not viewing done items | |
2960 | (when todos-show-with-done | |
2961 | (setq done-sep todos-done-separator) | |
2962 | (setq done-start cat-end) | |
2963 | (setq ov-pref (make-overlay done-sep-start done-end)) | |
2964 | (overlay-put ov-pref 'display done-sep)))) | |
2965 | (narrow-to-region (point-min) done-start)))) | |
2c173503 SB |
2966 | |
2967 | (defun todos-insert-with-overlays (item) | |
ee7412e4 SB |
2968 | "" |
2969 | (todos-item-start) | |
2c173503 SB |
2970 | (insert item "\n") |
2971 | (todos-backward-item) | |
f730d273 | 2972 | (todos-prefix-overlays)) |
3f031767 SB |
2973 | |
2974 | (defun todos-item-string-start () | |
2975 | "Return the start of this TODO list entry as a string." | |
2976 | ;; Suitable for putting in the minibuffer when asking the user | |
2977 | (let ((item (todos-item-string))) | |
2978 | (if (> (length item) 60) | |
2979 | (setq item (concat (substring item 0 56) "..."))) | |
2980 | item)) | |
2981 | ||
d04d6b95 SB |
2982 | (defvar todos-item-start ;; (concat "^\\(\\[\\(" (regexp-quote todos-done-string) |
2983 | ;; "\\)?\\)?" todos-date-pattern) | |
2984 | (concat "\\(" todos-date-string-start "\\|" todos-done-string-start | |
2985 | "\\)" todos-date-pattern) | |
ee7412e4 SB |
2986 | "String identifying start of a Todos item.") |
2987 | ||
3f031767 | 2988 | (defun todos-item-start () |
2c173503 SB |
2989 | "Move to start of current TODO list item and return its position." |
2990 | (unless (or (looking-at "^$") ; last item or between done and not done | |
2991 | (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items | |
2992 | (goto-char (line-beginning-position)) | |
ee7412e4 | 2993 | (while (not (looking-at todos-item-start)) |
d04d6b95 SB |
2994 | (forward-line -1)) |
2995 | (point))) | |
3f031767 SB |
2996 | |
2997 | (defun todos-item-end () | |
2c173503 | 2998 | "Move to end of current TODO list item and return its position." |
ee7412e4 SB |
2999 | (unless (looking-at "^$") ; FIXME: |
3000 | (let ((done (todos-done-item-p))) | |
3001 | (todos-forward-item) | |
3002 | ;; adjust if item is last unfinished one before displayed done items | |
3003 | (when (and (not done) (todos-done-item-p)) | |
3004 | (forward-line -1)) | |
d04d6b95 SB |
3005 | (backward-char)) |
3006 | (point))) | |
3f031767 SB |
3007 | |
3008 | (defun todos-remove-item () | |
3009 | "Delete the current entry from the TODO list." | |
ee7412e4 SB |
3010 | (let* ((beg (todos-item-start)) |
3011 | (end (progn (todos-item-end) (1+ (point)))) | |
2c173503 SB |
3012 | (ov-start (car (overlays-in beg beg)))) |
3013 | (when ov-start | |
2c173503 SB |
3014 | (delete-overlay ov-start)) |
3015 | (delete-region beg end))) | |
3f031767 SB |
3016 | |
3017 | (defun todos-item-string () | |
3018 | "Return current TODO list entry as a string." | |
d04d6b95 SB |
3019 | (let ((opoint (point)) |
3020 | (start (todos-item-start)) | |
3021 | (end (todos-item-end))) | |
3022 | (goto-char opoint) | |
3023 | (and start end (buffer-substring-no-properties start end)))) | |
3024 | ||
3025 | (defun todos-diary-item-p () | |
3026 | "" | |
3027 | (save-excursion | |
3028 | (todos-item-start) | |
3029 | (looking-at todos-date-pattern))) | |
3f031767 | 3030 | |
2c173503 | 3031 | (defun todos-done-item-p () |
ee7412e4 | 3032 | "" |
2c173503 SB |
3033 | (save-excursion |
3034 | (todos-item-start) | |
d04d6b95 | 3035 | (looking-at todos-done-string-start))) |
2c173503 | 3036 | |
d04d6b95 SB |
3037 | ;; FIXME: should be defsubst? |
3038 | (defun todos-counts (cat) | |
3039 | "Plist/Vector of item type counts in category CAT. | |
3040 | The counted types are all todo items, todo items for diary | |
3041 | inclusion, done items and archived items." | |
3042 | (cdr (assoc cat todos-categories))) | |
3043 | ||
3044 | (defun todos-get-count (type cat) | |
3045 | "Return count of TYPE items in category CAT." | |
3046 | (let (idx) | |
3047 | (cond ((eq type 'todo) | |
3048 | (setq idx 0)) | |
3049 | ((eq type 'diary) | |
3050 | (setq idx 1)) | |
3051 | ((eq type 'done) | |
3052 | (setq idx 2)) | |
3053 | ((eq type 'archived) | |
3054 | (setq idx 3))) | |
3055 | (aref (todos-counts cat) idx) | |
3056 | ;; (plist-get (todos-counts cat) type) | |
3057 | )) | |
3058 | ||
3059 | (defun todos-set-count (type counts increment) | |
3060 | "Increment count of item TYPE in vector COUNTS by INCREMENT." | |
3061 | (let (idx) | |
3062 | (cond ((eq type 'todo) | |
3063 | (setq idx 0)) | |
3064 | ((eq type 'diary) | |
3065 | (setq idx 1)) | |
3066 | ((eq type 'done) | |
3067 | (setq idx 2)) | |
3068 | ((eq type 'archived) | |
3069 | (setq idx 3))) | |
3070 | (aset counts idx (+ increment (aref counts idx))) | |
3071 | ;; (plist-put counts type (1+ (plist-get counts type))) | |
3072 | )) | |
3073 | ||
3074 | (defun todos-set-categories () | |
3075 | "Set todos-categories from the sexp at the top of the file." | |
3076 | (save-excursion | |
3077 | (save-restriction | |
3078 | (widen) | |
3079 | (goto-char (point-min)) | |
3080 | (if (looking-at "\(\(\"") | |
3081 | (setq todos-categories (read (buffer-substring-no-properties | |
3082 | (line-beginning-position) | |
3083 | (line-end-position)))) | |
3084 | (error "Invalid or missing todos-categories sexp"))))) | |
3085 | ||
3086 | (defun todos-make-categories-list (&optional force) | |
3087 | "Return a list of Todos categories and their item counts. | |
3088 | The items counts are contained in a vector specifying the numbers | |
3089 | of todo items, done items and archived items in the category, in | |
3090 | that order." | |
3091 | (setq todos-categories nil) | |
3092 | (save-excursion | |
3093 | (save-restriction | |
3094 | (widen) | |
3095 | (goto-char (point-min)) | |
3096 | (let (counts cat archive) | |
3097 | ;; FIXME: can todos-archives be too old here? | |
3098 | (unless (member buffer-file-name (funcall todos-files-function t)) | |
3099 | (setq archive (concat (file-name-sans-extension | |
3100 | todos-current-todos-file) ".toda"))) | |
3101 | (while (not (eobp)) | |
3102 | (cond ((looking-at (concat (regexp-quote todos-category-beg) | |
3103 | "\\(.*\\)\n")) | |
3104 | (setq cat (match-string-no-properties 1)) | |
3105 | ;; counts for each category: [todo diary done archive] | |
3106 | (setq counts (make-vector 4 0)) | |
3107 | ;; (setq counts (list 'todo 0 'diary 0 'done 0 'archived 0)) | |
3108 | (setq todos-categories | |
3109 | (append todos-categories (list (cons cat counts)))) | |
3110 | ;; todos-archives may be too old here (e.g. during | |
3111 | ;; todos-move-category) | |
3112 | (when (member archive (funcall todos-files-function t)) | |
3113 | (with-current-buffer (find-file-noselect archive) | |
3114 | (widen) | |
3115 | (goto-char (point-min)) | |
3116 | (when (re-search-forward | |
3117 | (concat (regexp-quote todos-category-beg) cat) | |
3118 | (point-max) t) | |
3119 | (forward-line) | |
3120 | (while (not (or (looking-at | |
3121 | (concat (regexp-quote todos-category-beg) | |
3122 | "\\(.*\\)\n")) | |
3123 | (eobp))) | |
3124 | (when (looking-at todos-done-string-start) | |
3125 | (todos-set-count 'archived counts 1)) | |
3126 | (forward-line)))))) | |
3127 | ((looking-at todos-done-string-start) | |
3128 | (todos-set-count 'done counts 1)) | |
3129 | ((looking-at (concat "^\\(" (regexp-quote diary-nonmarking-symbol) | |
3130 | "\\)?" todos-date-pattern)) | |
3131 | (todos-set-count 'diary counts 1) | |
3132 | (todos-set-count 'todo counts 1)) | |
3133 | ((looking-at (concat todos-date-string-start todos-date-pattern)) | |
3134 | (todos-set-count 'todo counts 1)) | |
3135 | ;; if first line is todos-categories list, use it and end loop | |
3136 | ;; unless forced by non-nil parameter `force' to scan whole file | |
3137 | ((bobp) | |
3138 | (unless force | |
3139 | (setq todos-categories (read (buffer-substring-no-properties | |
3140 | (line-beginning-position) | |
3141 | (line-end-position)))) | |
3142 | (goto-char (1- (point-max)))))) | |
3143 | (forward-line))))) | |
3144 | todos-categories) | |
3145 | ||
3146 | ;; FIXME: don't let truncated list get written by todos-update-categories-sexp | |
3147 | (defun todos-truncate-categories-list () | |
3148 | "Return a truncated list of Todos categories plus item counts. | |
3149 | Categories containing only archived items are omitted. This list | |
3150 | is used in Todos mode when `todos-ignore-archived-categories' is | |
3151 | non-nil." | |
3152 | (let (cats) | |
3153 | (unless todos-categories | |
3154 | (setq todos-categories (todos-make-categories-list))) | |
3155 | (dolist (catcons todos-categories cats) | |
3156 | (let ((cat (car catcons))) | |
3157 | (setq cats | |
3158 | (append cats | |
3159 | (unless (and (zerop (todos-get-count 'todo cat)) | |
3160 | (zerop (todos-get-count 'done cat)) | |
3161 | (not (zerop (todos-get-count 'archived cat)))) | |
3162 | (list catcons)))))))) | |
3163 | ||
3164 | (defun todos-update-categories-sexp () | |
3165 | "" | |
3166 | (let (buffer-read-only) | |
2c173503 SB |
3167 | (save-excursion |
3168 | (save-restriction | |
3169 | (widen) | |
3170 | (goto-char (point-min)) | |
d04d6b95 SB |
3171 | (if (looking-at (concat "^" (regexp-quote todos-category-beg))) |
3172 | (progn (newline) (goto-char (point-min))) | |
3173 | (kill-line)) | |
3174 | (prin1 todos-categories (current-buffer)))))) | |
3175 | ||
3176 | ;; FIXME: should done diary items count as diary? | |
3177 | (defun todos-item-counts (cat &optional type) | |
3178 | "" | |
3179 | (let ((counts (todos-counts cat))) | |
3180 | (cond ((eq type 'insert) | |
3181 | (todos-set-count 'todo counts 1)) | |
3182 | ((eq type 'diary) | |
3183 | (todos-set-count 'diary counts 1)) | |
3184 | ((eq type 'nondiary) | |
3185 | (todos-set-count 'diary counts -1)) | |
3186 | ((eq type 'delete) | |
3187 | ;; FIXME: ok if last done item was deleted? | |
3188 | (if (save-excursion | |
3189 | (re-search-backward (concat "^" (regexp-quote | |
3190 | todos-category-done)) nil t)) | |
3191 | (todos-set-count 'done counts -1) | |
3192 | (todos-set-count 'todo counts -1))) | |
3193 | ((eq type 'done) | |
3194 | (todos-set-count 'todo counts -1) | |
3195 | (todos-set-count 'done counts 1)) | |
3196 | ((eq type 'undo) | |
3197 | (todos-set-count 'todo counts 1) | |
3198 | (todos-set-count 'done counts -1)) | |
3199 | ((eq type 'archive) | |
3200 | (todos-set-count 'archived counts (todos-get-count 'done cat)) ;arch+done | |
3201 | (todos-set-count 'done counts (- (todos-get-count 'done cat))))) ; 0 | |
3202 | (todos-update-categories-sexp))) | |
ee7412e4 SB |
3203 | |
3204 | (defun todos-longest-category-name-length (categories) | |
2c173503 SB |
3205 | "" |
3206 | (let ((longest 0)) | |
ee7412e4 | 3207 | (dolist (c categories longest) |
f730d273 | 3208 | (setq longest (max longest (length c)))))) |
2c173503 | 3209 | |
3f031767 SB |
3210 | (defun todos-string-count-lines (string) |
3211 | "Return the number of lines STRING spans." | |
3212 | (length (split-string string "\n"))) | |
3213 | ||
3214 | (defun todos-string-multiline-p (string) | |
3215 | "Return non-nil if STRING spans several lines." | |
3216 | (> (todos-string-count-lines string) 1)) | |
3217 | ||
d04d6b95 SB |
3218 | (defun todos-read-file-name (prompt &optional archive) |
3219 | "" | |
3220 | (unless (file-exists-p todos-files-directory) | |
3221 | (make-directory todos-files-directory)) | |
3222 | (let* ((completion-ignore-case t) | |
3223 | (files (mapcar 'file-name-sans-extension | |
3224 | (directory-files todos-files-directory nil | |
3225 | (if archive "\.toda$" "\.todo$")))) | |
3226 | (file (concat todos-files-directory | |
3227 | (completing-read prompt files nil t) | |
3228 | (if archive ".toda" ".todo")))) | |
3229 | (expand-file-name file))) | |
3230 | ||
3231 | (defun todos-read-category (prompt) | |
3232 | "Return a category name from the current Todos file, with completion. | |
3233 | Prompt with PROMPT." | |
db2c5d34 SB |
3234 | ;; allow SPC to insert spaces, for adding new category names with |
3235 | ;; todos-move-item | |
3236 | (let ((map minibuffer-local-completion-map)) | |
3237 | (define-key map " " nil) | |
3238 | ;; make a copy of todos-categories in case history-delete-duplicates is | |
3239 | ;; non-nil, which makes completing-read alter todos-categories | |
3240 | (let* ((categories (copy-sequence todos-categories)) | |
3241 | (history (cons 'todos-categories (1+ todos-category-number))) | |
d04d6b95 | 3242 | ;; (default (todos-current-category)) ;FIXME: why this default? |
db2c5d34 | 3243 | (completion-ignore-case todos-completion-ignore-case) |
d04d6b95 SB |
3244 | (category (completing-read prompt |
3245 | ;; (concat "Category [" default "]: ") | |
3246 | todos-categories nil nil nil history))); default))) | |
db2c5d34 SB |
3247 | ;; restore the original value of todos-categories |
3248 | (setq todos-categories categories) | |
3249 | category))) | |
3f031767 | 3250 | |
d04d6b95 SB |
3251 | (defun todos-validate-category-name (cat) |
3252 | "Check new category name CAT and when valid return it." | |
ee7412e4 | 3253 | (let (prompt) |
d04d6b95 SB |
3254 | (while |
3255 | (and (cond ((string= "" cat) | |
3256 | (if todos-categories | |
3257 | (setq prompt "Enter a non-empty category name: ") | |
3258 | ;; prompt for initial category of a new Todos file | |
3259 | (setq prompt (concat "Initial category name [" | |
3260 | todos-initial-category "]: ")))) | |
3261 | ((string-match "\\`\\s-+\\'" cat) | |
3262 | (setq prompt | |
3263 | "Enter a category name that is not only white space: ")) | |
3264 | ((assoc cat todos-categories) | |
3265 | (setq prompt "Enter a non-existing category name: "))) | |
3266 | (setq cat (if todos-categories | |
3267 | (read-from-minibuffer prompt) | |
3268 | ;; offer default initial category name | |
3269 | ;; FIXME: if input is just whitespace, raises "End of | |
3270 | ;; file during parsing" error | |
3271 | (prin1-to-string | |
3272 | (read-from-minibuffer prompt nil nil t nil | |
3273 | (list todos-initial-category)))))))) | |
ee7412e4 SB |
3274 | cat) |
3275 | ||
d04d6b95 | 3276 | ;; adapted from calendar-read-date and calendar-date-string |
ee7412e4 | 3277 | (defun todos-read-date () |
d04d6b95 SB |
3278 | "Prompt for Gregorian date and return it in the current format. |
3279 | Also accepts `*' as an unspecified month, day, or year." | |
ee7412e4 | 3280 | (let* ((year (calendar-read |
d04d6b95 SB |
3281 | "Year (>0 or * for any year): " |
3282 | (lambda (x) (or (eq x '*) (> x 0))) | |
ee7412e4 SB |
3283 | (number-to-string (calendar-extract-year |
3284 | (calendar-current-date))))) | |
d04d6b95 SB |
3285 | (month-array (vconcat calendar-month-name-array (vector "*"))) |
3286 | (abbrevs (vconcat calendar-month-abbrev-array (vector "*"))) | |
ee7412e4 | 3287 | (completion-ignore-case t) |
d04d6b95 SB |
3288 | (monthname (completing-read |
3289 | "Month name (RET for current month, * for any month): " | |
3290 | (mapcar 'list (append month-array nil)) | |
3291 | nil t nil nil | |
3292 | (calendar-month-name (calendar-extract-month | |
3293 | (calendar-current-date)) t))) | |
ee7412e4 | 3294 | (month (cdr (assoc-string |
d04d6b95 SB |
3295 | monthname (calendar-make-alist month-array nil nil abbrevs)))) |
3296 | (last (if (eq month 13) | |
3297 | 31 ; FIXME: what about shorter months? | |
3298 | (let ((yr (if (eq year '*) | |
3299 | 1999 ; FIXME: no Feb. 29 | |
3300 | year))) | |
3301 | (calendar-last-day-of-month month yr)))) | |
3302 | day dayname) | |
3303 | (while (if (numberp day) (or (< day 0) (< last day)) (not (eq day '*))) | |
ee7412e4 | 3304 | (setq day (read-from-minibuffer |
d04d6b95 SB |
3305 | (format "Day (1-%d or RET for today or * for any day): " last) |
3306 | nil nil t nil | |
3307 | (number-to-string | |
3308 | (calendar-extract-day (calendar-current-date)))))) | |
3309 | (setq year (if (eq year '*) (symbol-name '*) (number-to-string year))) | |
3310 | (setq day (if (eq day '*) (symbol-name '*) (number-to-string day))) | |
3311 | ;; FIXME: make abbreviation customizable | |
3312 | (setq monthname | |
3313 | (calendar-month-name (calendar-extract-month (list month day year)) t)) | |
3314 | (mapconcat 'eval calendar-date-display-form ""))) | |
ee7412e4 SB |
3315 | |
3316 | (defun todos-read-dayname () | |
3317 | "" | |
3318 | (let ((completion-ignore-case t)) | |
3319 | (completing-read "Enter a day name: " | |
3320 | (append calendar-day-name-array nil) | |
3321 | nil t))) | |
3322 | ||
3323 | (defun todos-read-time () | |
3324 | "" | |
3325 | (let (valid answer) | |
3326 | (while (not valid) | |
3327 | (setq answer (read-from-minibuffer | |
f730d273 | 3328 | "Enter a clock time (or return for none): ")) |
ee7412e4 SB |
3329 | (when (or (string= "" answer) |
3330 | (string-match diary-time-regexp answer)) | |
3331 | (setq valid t))) | |
3332 | answer)) | |
3333 | ||
ee7412e4 SB |
3334 | (defun todos-padded-string (str) |
3335 | "" | |
d04d6b95 SB |
3336 | (let* ((categories (mapcar 'car todos-categories)) |
3337 | (len (todos-longest-category-name-length categories)) | |
ee7412e4 SB |
3338 | (strlen (length str)) |
3339 | (strlen-odd (eq (logand strlen 1) 1)) ; oddp from cl.el | |
d04d6b95 SB |
3340 | (padding (max 0 (/ (- len strlen) 2))) |
3341 | (padding-left (cond ((eq todos-categories-align 'left) 0) | |
3342 | ((eq todos-categories-align 'center) padding) | |
3343 | ((eq todos-categories-align 'right) | |
3344 | (if strlen-odd (1+ (* padding 2)) (* padding 2))))) | |
3345 | (padding-right (cond ((eq todos-categories-align 'left) | |
3346 | (if strlen-odd (1+ (* padding 2)) (* padding 2))) | |
3347 | ((eq todos-categories-align 'center) | |
3348 | (if strlen-odd (1+ padding) padding)) | |
3349 | ((eq todos-categories-align 'right) 0)))) | |
3350 | (concat (make-string padding-left 32) str (make-string padding-right 32)))) | |
3351 | ||
3352 | (defvar todos-descending-counts-store nil | |
3353 | "Alist of current sorted category counts, keyed by sort key.") | |
3354 | ||
3355 | ;; FIXME: rename to todos-insert-category-info ? | |
3356 | (defun todos-sort (list &optional key) | |
3357 | "Return a copy of LIST, possibly sorted according to KEY." ;FIXME | |
3358 | (let* ((l (copy-sequence list)) | |
3359 | (fn (if (eq key 'alpha) | |
3360 | (lambda (x) (upcase x)) ;alphabetize case insensitively | |
3361 | (lambda (x) (todos-get-count key x)))) | |
3362 | (descending (member key todos-descending-counts-store)) | |
3363 | (cmp (if (eq key 'alpha) | |
3364 | 'string< | |
3365 | (if descending '< '>))) | |
3366 | (pred (lambda (s1 s2) (let ((t1 (funcall fn (car s1))) | |
3367 | (t2 (funcall fn (car s2)))) | |
3368 | (funcall cmp t1 t2))))) | |
3369 | (when key | |
3370 | (setq l (sort l pred)) | |
3371 | (if descending | |
3372 | (setq todos-descending-counts-store | |
3373 | (delete key todos-descending-counts-store)) | |
3374 | (push key todos-descending-counts-store))) | |
3375 | l)) | |
3376 | ||
3377 | (defun todos-display-sorted (type) | |
3378 | "Keep point on the count sorting button just clicked." | |
3379 | (let ((opoint (point))) | |
3380 | (todos-display-categories type) | |
3381 | (goto-char opoint))) | |
3382 | ||
3383 | (defun todos-label-to-key (label) | |
3384 | "Return symbol for sort key associated with LABEL." | |
3385 | (let (key) | |
3386 | (cond ((string= label todos-categories-category-label) | |
3387 | (setq key 'alpha)) | |
3388 | ((string= label todos-categories-todo-label) | |
3389 | (setq key 'todo)) | |
3390 | ((string= label todos-categories-diary-label) | |
3391 | (setq key 'diary)) | |
3392 | ((string= label todos-categories-done-label) | |
3393 | (setq key 'done)) | |
3394 | ((string= label todos-categories-archived-label) | |
3395 | (setq key 'archived))) | |
3396 | key)) | |
3397 | ||
3398 | (defun todos-insert-sort-button (label) | |
ee7412e4 | 3399 | "" |
d04d6b95 SB |
3400 | (setq str (if (string= label todos-categories-category-label) |
3401 | (todos-padded-string label) | |
3402 | label)) | |
3403 | (setq beg (point)) | |
3404 | (setq end (+ beg (length str))) | |
3405 | (insert-button str 'face nil | |
3406 | 'action | |
3407 | `(lambda (button) | |
3408 | (let ((key (todos-label-to-key ,label))) | |
3409 | (if (and (member key todos-descending-counts-store) | |
3410 | (eq key 'alpha)) | |
3411 | (progn | |
3412 | (todos-display-categories) | |
3413 | (setq todos-descending-counts-store | |
3414 | (delete key todos-descending-counts-store))) | |
3415 | (todos-display-sorted key))))) | |
3416 | (setq ovl (make-overlay beg end)) | |
3417 | (overlay-put ovl 'face 'todos-button)) | |
3418 | ||
3419 | (defun todos-insert-category-line (cat &optional nonum) | |
3420 | "" | |
3421 | (let ((archive (member todos-current-todos-file todos-archives)) | |
3422 | (str (todos-padded-string cat)) | |
3423 | (opoint (point))) | |
3424 | ;; beg end ovl) | |
ee7412e4 SB |
3425 | ;; num is declared in caller |
3426 | (setq num (1+ num)) | |
d04d6b95 SB |
3427 | ;; (if nonum |
3428 | ;; (insert (make-string 4 32)) | |
3429 | ;; (insert " " (format "%2d" num) " | ")) | |
3430 | ;; (setq beg (point)) | |
3431 | ;; (setq end (+ beg (length str))) | |
3432 | (insert-button | |
3433 | ;; FIXME: use mapconcat? | |
3434 | (concat (if nonum | |
3435 | (make-string (+ 3 (length todos-categories-number-separator)) 32) | |
3436 | (format " %2d%s" num todos-categories-number-separator)) | |
3437 | str | |
3438 | (make-string (+ 2 (/ (length todos-categories-todo-label) 2)) 32) | |
3439 | (unless archive | |
3440 | (concat | |
3441 | (format "%2d" (todos-get-count 'todo cat)) | |
3442 | (make-string (+ 2 (/ (length todos-categories-diary-label) 2)) 32))) | |
3443 | (unless archive | |
3444 | (concat | |
3445 | (format "%2d" (todos-get-count 'diary cat)) | |
3446 | (make-string (+ 3 (/ (length todos-categories-done-label) 2)) 32))) | |
3447 | (format "%2d" (todos-get-count 'done cat)) | |
3448 | (unless archive | |
3449 | (concat | |
3450 | (make-string (+ 2 (/ (length todos-categories-archived-label) 2)) 32) | |
3451 | (format "%2d" (todos-get-count 'archived cat)) | |
3452 | (make-string 2 32)))) | |
3453 | 'face (if (and todos-ignore-archived-categories | |
3454 | (zerop (todos-get-count 'todo cat)) | |
3455 | (zerop (todos-get-count 'done cat)) | |
3456 | (not (zerop (todos-get-count 'archived cat)))) | |
3457 | 'todos-archived-only | |
3458 | nil) | |
3459 | 'action `(lambda (button) (todos-jump-to-category ,cat))) | |
3460 | ;; (setq ovl (make-overlay beg end)) | |
3461 | ;; (overlay-put ovl 'face 'todos-button) | |
3462 | (let* ((beg1 (+ opoint 6 (length str))) | |
3463 | end1 ovl1) | |
3464 | (cond ((eq nonum 'todo) | |
3465 | (setq beg1 (+ beg1 1 (/ (length todos-categories-todo-label) 2)))) | |
3466 | ((eq nonum 'diary) | |
3467 | (setq beg1 (+ beg1 1 (length todos-categories-todo-label) | |
3468 | 2 (/ (length todos-categories-diary-label) 2)))) | |
3469 | ((eq nonum 'done) | |
3470 | (setq beg1 (+ beg1 1 (length todos-categories-todo-label) | |
3471 | 2 (length todos-categories-diary-label) | |
3472 | 2 (/ (length todos-categories-done-label) 2)))) | |
3473 | ((eq nonum 'archived) | |
3474 | (setq beg1 (+ beg1 1 (length todos-categories-todo-label) | |
3475 | 2 (length todos-categories-diary-label) | |
3476 | 2 (length todos-categories-done-label) | |
3477 | 2 (/ (length todos-categories-archived-label) 2))))) | |
3478 | (unless (= beg1 (+ opoint 6 (length str))) | |
3479 | (setq end1 (+ beg1 4)) | |
3480 | (setq ovl1 (make-overlay beg1 end1)) | |
3481 | (overlay-put ovl1 'face 'todos-sorted-column))) | |
3482 | (insert (concat "\n")))) | |
3f031767 SB |
3483 | |
3484 | (provide 'todos) | |
3485 | ||
f730d273 SB |
3486 | ;;; UI |
3487 | ;; - display | |
3488 | ;; - show todos in cat | |
3489 | ;; - show done in cat | |
3490 | ;; - show catlist | |
3491 | ;; - show top priorities in all cats | |
3492 | ;; - show archived | |
3493 | ;; - navigation | |
3494 | ;; - | |
3495 | ;; - editing | |
3496 | ;; | |
3497 | ;;; Internals | |
3498 | ;; - cat props: name, number, todos, done, archived | |
3499 | ;; - item props: priority, date-time, status? | |
3500 | ;; - file format | |
3501 | ;; - cat begin | |
3502 | ;; - todo items 0...n | |
3503 | ;; - empty line | |
3504 | ;; - done-separator | |
3505 | ;; - done item 0...n | |
3506 | ||
3f031767 | 3507 | ;;; todos.el ends here |