diff options
| author | Stephen Berman | 2010-05-25 01:19:50 +0100 |
|---|---|---|
| committer | Stephen Berman | 2010-05-25 01:19:50 +0100 |
| commit | 2c173503dcb83660571ab5abe244cc559eb61596 (patch) | |
| tree | 08802bec7cfcd727be6560ac4fa1d496c99b3256 | |
| parent | b28025eddc48b2a17a0b1c011ef97eea77917acc (diff) | |
| download | emacs-2c173503dcb83660571ab5abe244cc559eb61596.tar.gz emacs-2c173503dcb83660571ab5abe244cc559eb61596.zip | |
* calendar/todos.el: Comment out calendar require, since diary-lib
requires calendar. Rearrange file to group definitions according
to their use (types of commands, internal functions, etc.)
(todos-file-done, todos-insert-threshold, todos-remove-separator)
(todos-date-string, todos-time-string, todos-check-overlay)
(todos-show-paren-hack, todos-file-item, todos-more-important-p):
Comment out.
(todos-current-date, todos-item-end-overlays)
(todos-list-categories): Remove.
(todos-item-end): Remove (the variable, not the function).
(todos-item-overlays): Rename to todos-prefix-overlays and adjust
callers.
(todos-prefix-overlays): Rename from todos-item-overlays.
(todos-done-separator, todos-done-string, todos-show-with-done)
(todos-files, todos-archive-file, todos-categories-buffer)
(todos-archived-categories-buffer, todos-wrap-lines)
(todos-line-wrapping-function): New defcustoms.
(todos-done, todos-done-sep): New faces.
(todos-done-face, todos-done-sep-face): Corresponding new variables.
(todos-search-string, todos-date-nodayname-pattern)
(todos-dayname-date-pattern, todos-date-pattern): New variables.
(todos-done-string-match, todos-category-string-match)
(todos-check-format, todos-wrap-and-indent, todos-reset-separator)
(todos-current-category, todos-count-items-in-category)
(todos-done-item-p, todos-categories-alist, todos-count-all-items)
(todos-longest-category-name-length): New functions.
(todos-categories-list): New buffer-specific function replacing
todos-list-categories.
(todos-toggle-item-numbering, todos-toggle-view-done-items)
(todos-search, todos-view-archive, todos-diary-items)
(todos-toggle-display-date-time, todos-insert-item-no-time)
(todos-insert-item-ask-date, todos-insert-item-for-diary)
(todos-insert-item-from-calendar, todos-edit-quit)
(todos-change-date, todos-item-done, todos-archive-done-items)
(todos-item-undo): New commands.
(todos-archive-mode): New mode.
(todos-archive-mode-map, todos-edit-mode-map): New keymaps.
(todos-category-beg): Change value.
(todos-number-prefix): Change default value.
(todos-edit-buffer): Change from defvar to defcustom.
(todos-font-lock-keywords): Use todos-done-string-match and
todos-category-string-match.
(todos-backward-item, todos-forward-item): Use todos-done-string
and todos-date-pattern.
(todos-display-categories): Reimplement using buttons from
button.el instead of widgets.
(todos-top-priorities): Use with-current-buffer; take done items
into account; ensure buffers gets fontified.
(todos-add-category): Ensure new category does not begin with
empty lines.
(todos-jump-to-category): Use todos-category-select instead of
todos-show.
(todos-rename-category): Prompt for new name in body instead of in
interactive spec.
(todos-insert-item): Don't insert in done items section of
category. Add two optional arguments to control insertion: (i) to
insert near point without prompting for priority; (ii) to use
defaults for date and time strings, to prompt for these, or to
choose date from the Calendar.
(todos-insert-item-here): Reimplement using todos-insert-item.
(todos-delete-item): Don't move point after deleting last item.
(todos-raise-item, todos-lower-item): Take done items into account.
(todos-move-item): Don't move done items; update item numbering;
restore if user quits before inserting moved item.
(todos-print): Prompt for confirmation to print.
(todos-reset-prefix): Search backward from end of file instead of
forward from top.
(todos-jump-to-category-noninteractively): Take Todos archive into
account.
(todos-category-select): Show or hide done items according to
todos-show-with-done; if shown, coordinate separator and prefix
overlays.
(todos-add-item-non-interactively): Replace binary insertion
algorithm with prompting for numerical priority.
(todos-insert-with-overlays): Remove use of variable todos-item-end.
(todos-item-start): Take done items into account; use
todos-date-pattern.
(todos-item-end): Reimplement using todos-forward-item.
(todos-remove-item): Reimplement using todos-forward-item and
todos-backward-item; redo overlay handling.
(todos-mode-map): Add some new key bindings and change numerous
existing bindings; use "i" as prefix key for item insertion
commands.
(todos-mode): Use todos-wrap-lines and delegate word-wrap and
wrap-prefix settings to todos-wrap-and-indent; add to invisibility
spec; set buffer-read-only to t and consequently let-bind this
variable in all Todos commands that change buffer content.
(todos-edit-mode): Make an indepent mode, not derived from text-mode.
(todos-save): Don't save top priorities buffer.
(todos-show): Make a no-op if called interactively in narrowed
Todos mode, since, also to work around item prefix reduplication
bug with show-paren-mode enabled; use todos-categories-list.
| -rw-r--r-- | lisp/ChangeLog | 95 | ||||
| -rw-r--r-- | lisp/calendar/todos.el | 2190 |
2 files changed, 1573 insertions, 712 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 4110a4ad087..e2d88a96ac4 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,98 @@ | |||
| 1 | 2012-09-13 Stephen Berman <stephen.berman@gmx.net> | ||
| 2 | |||
| 3 | * calendar/todos.el: Comment out calendar require, since diary-lib | ||
| 4 | requires calendar. Rearrange file to group definitions according | ||
| 5 | to their use (types of commands, internal functions, etc.) | ||
| 6 | (todos-file-done, todos-insert-threshold, todos-remove-separator) | ||
| 7 | (todos-date-string, todos-time-string, todos-check-overlay) | ||
| 8 | (todos-show-paren-hack, todos-file-item, todos-more-important-p): | ||
| 9 | Comment out. | ||
| 10 | (todos-current-date, todos-item-end-overlays) | ||
| 11 | (todos-list-categories): Remove. | ||
| 12 | (todos-item-end): Remove (the variable, not the function). | ||
| 13 | (todos-item-overlays): Rename to todos-prefix-overlays and adjust | ||
| 14 | callers. | ||
| 15 | (todos-prefix-overlays): Rename from todos-item-overlays. | ||
| 16 | (todos-done-separator, todos-done-string, todos-show-with-done) | ||
| 17 | (todos-files, todos-archive-file, todos-categories-buffer) | ||
| 18 | (todos-archived-categories-buffer, todos-wrap-lines) | ||
| 19 | (todos-line-wrapping-function): New defcustoms. | ||
| 20 | (todos-done, todos-done-sep): New faces. | ||
| 21 | (todos-done-face, todos-done-sep-face): Corresponding new variables. | ||
| 22 | (todos-search-string, todos-date-nodayname-pattern) | ||
| 23 | (todos-dayname-date-pattern, todos-date-pattern): New variables. | ||
| 24 | (todos-done-string-match, todos-category-string-match) | ||
| 25 | (todos-check-format, todos-wrap-and-indent, todos-reset-separator) | ||
| 26 | (todos-current-category, todos-count-items-in-category) | ||
| 27 | (todos-done-item-p, todos-categories-alist, todos-count-all-items) | ||
| 28 | (todos-longest-category-name-length): New functions. | ||
| 29 | (todos-categories-list): New buffer-specific function replacing | ||
| 30 | todos-list-categories. | ||
| 31 | (todos-toggle-item-numbering, todos-toggle-view-done-items) | ||
| 32 | (todos-search, todos-view-archive, todos-diary-items) | ||
| 33 | (todos-toggle-display-date-time, todos-insert-item-no-time) | ||
| 34 | (todos-insert-item-ask-date, todos-insert-item-for-diary) | ||
| 35 | (todos-insert-item-from-calendar, todos-edit-quit) | ||
| 36 | (todos-change-date, todos-item-done, todos-archive-done-items) | ||
| 37 | (todos-item-undo): New commands. | ||
| 38 | (todos-archive-mode): New mode. | ||
| 39 | (todos-archive-mode-map, todos-edit-mode-map): New keymaps. | ||
| 40 | (todos-category-beg): Change value. | ||
| 41 | (todos-number-prefix): Change default value. | ||
| 42 | (todos-edit-buffer): Change from defvar to defcustom. | ||
| 43 | (todos-font-lock-keywords): Use todos-done-string-match and | ||
| 44 | todos-category-string-match. | ||
| 45 | (todos-backward-item, todos-forward-item): Use todos-done-string | ||
| 46 | and todos-date-pattern. | ||
| 47 | (todos-display-categories): Reimplement using buttons from | ||
| 48 | button.el instead of widgets. | ||
| 49 | (todos-top-priorities): Use with-current-buffer; take done items | ||
| 50 | into account; ensure buffers gets fontified. | ||
| 51 | (todos-add-category): Ensure new category does not begin with | ||
| 52 | empty lines. | ||
| 53 | (todos-jump-to-category): Use todos-category-select instead of | ||
| 54 | todos-show. | ||
| 55 | (todos-rename-category): Prompt for new name in body instead of in | ||
| 56 | interactive spec. | ||
| 57 | (todos-insert-item): Don't insert in done items section of | ||
| 58 | category. Add two optional arguments to control insertion: (i) to | ||
| 59 | insert near point without prompting for priority; (ii) to use | ||
| 60 | defaults for date and time strings, to prompt for these, or to | ||
| 61 | choose date from the Calendar. | ||
| 62 | (todos-insert-item-here): Reimplement using todos-insert-item. | ||
| 63 | (todos-delete-item): Don't move point after deleting last item. | ||
| 64 | (todos-raise-item, todos-lower-item): Take done items into account. | ||
| 65 | (todos-move-item): Don't move done items; update item numbering; | ||
| 66 | restore if user quits before inserting moved item. | ||
| 67 | (todos-print): Prompt for confirmation to print. | ||
| 68 | (todos-reset-prefix): Search backward from end of file instead of | ||
| 69 | forward from top. | ||
| 70 | (todos-jump-to-category-noninteractively): Take Todos archive into | ||
| 71 | account. | ||
| 72 | (todos-category-select): Show or hide done items according to | ||
| 73 | todos-show-with-done; if shown, coordinate separator and prefix | ||
| 74 | overlays. | ||
| 75 | (todos-add-item-non-interactively): Replace binary insertion | ||
| 76 | algorithm with prompting for numerical priority. | ||
| 77 | (todos-insert-with-overlays): Remove use of variable todos-item-end. | ||
| 78 | (todos-item-start): Take done items into account; use | ||
| 79 | todos-date-pattern. | ||
| 80 | (todos-item-end): Reimplement using todos-forward-item. | ||
| 81 | (todos-remove-item): Reimplement using todos-forward-item and | ||
| 82 | todos-backward-item; redo overlay handling. | ||
| 83 | (todos-mode-map): Add some new key bindings and change numerous | ||
| 84 | existing bindings; use "i" as prefix key for item insertion | ||
| 85 | commands. | ||
| 86 | (todos-mode): Use todos-wrap-lines and delegate word-wrap and | ||
| 87 | wrap-prefix settings to todos-wrap-and-indent; add to invisibility | ||
| 88 | spec; set buffer-read-only to t and consequently let-bind this | ||
| 89 | variable in all Todos commands that change buffer content. | ||
| 90 | (todos-edit-mode): Make an indepent mode, not derived from text-mode. | ||
| 91 | (todos-save): Don't save top priorities buffer. | ||
| 92 | (todos-show): Make a no-op if called interactively in narrowed | ||
| 93 | Todos mode, since, also to work around item prefix reduplication | ||
| 94 | bug with show-paren-mode enabled; use todos-categories-list. | ||
| 95 | |||
| 1 | 2012-09-12 Stephen Berman <stephen.berman@gmx.net> | 96 | 2012-09-12 Stephen Berman <stephen.berman@gmx.net> |
| 2 | 97 | ||
| 3 | * calendar/todos.el: Comment out time-stamp require; require | 98 | * calendar/todos.el: Comment out time-stamp require; require |
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el index 5e1a3cda104..c4788044520 100644 --- a/lisp/calendar/todos.el +++ b/lisp/calendar/todos.el | |||
| @@ -168,14 +168,6 @@ | |||
| 168 | ;; todo list is already established is not as simple as changing | 168 | ;; todo list is already established is not as simple as changing |
| 169 | ;; the variable - the todo files have to be changed by hand. | 169 | ;; the variable - the todo files have to be changed by hand. |
| 170 | ;; | 170 | ;; |
| 171 | ;; FIXME: eliminate variable todos-prefix, use overlays: | ||
| 172 | ;; (defun todos-prefix () | ||
| 173 | ;; "Display a Todo prefix string as an overlay." | ||
| 174 | ;; (let (ov) | ||
| 175 | ;; (setq ov (make-overlay (line-beginning-position) (line-end-position))) | ||
| 176 | ;; (overlay-put ov 'before-string | ||
| 177 | ;; (propertize todos-prefix 'face 'todos-prefix-string)))) | ||
| 178 | ;; | ||
| 179 | ;; Variable todos-file-do | 171 | ;; Variable todos-file-do |
| 180 | ;; | 172 | ;; |
| 181 | ;; This variable is fairly self-explanatory. You have to store | 173 | ;; This variable is fairly self-explanatory. You have to store |
| @@ -265,18 +257,19 @@ | |||
| 265 | ;;; Code: | 257 | ;;; Code: |
| 266 | 258 | ||
| 267 | ;; (require 'time-stamp) | 259 | ;; (require 'time-stamp) |
| 268 | (require 'calendar) | 260 | ;; (require 'calendar) ; required by diary-lib |
| 269 | (require 'diary-lib) | 261 | (require 'diary-lib) |
| 270 | 262 | ||
| 271 | ;; User-configurable variables: | 263 | ;; --------------------------------------------------------------------------- |
| 272 | 264 | ||
| 265 | ;;; Customizable options | ||
| 273 | (defgroup todos nil | 266 | (defgroup todos nil |
| 274 | "Maintain a list of todo items." | 267 | "Maintain lists of todo items." |
| 275 | :link '(emacs-commentary-link "todos") | 268 | :link '(emacs-commentary-link "todos") |
| 276 | :version "21.1" | 269 | :version "21.1" |
| 277 | :group 'calendar) | 270 | :group 'calendar) |
| 278 | 271 | ||
| 279 | (defcustom todos-prefix "§" ; "*/*" | 272 | (defcustom todos-prefix "§" ; "*/*" FIXME ascii default |
| 280 | "String prefixed to todo items for visual distinction." | 273 | "String prefixed to todo items for visual distinction." |
| 281 | :type 'string | 274 | :type 'string |
| 282 | :initialize 'custom-initialize-default | 275 | :initialize 'custom-initialize-default |
| @@ -297,40 +290,97 @@ | |||
| 297 | ;; the diary file somewhat." | 290 | ;; the diary file somewhat." |
| 298 | ;; :type 'string | 291 | ;; :type 'string |
| 299 | ;; :group 'todos) | 292 | ;; :group 'todos) |
| 300 | (defcustom todos-file-do (convert-standard-filename "~/.emacs.d/.todos-do") | 293 | |
| 294 | (defcustom todos-number-prefix t | ||
| 295 | "Non-nil to show item prefixes as consecutively increasing integers." | ||
| 296 | :type 'boolean | ||
| 297 | :initialize 'custom-initialize-default | ||
| 298 | :set 'todos-reset-prefix | ||
| 299 | :group 'todos) | ||
| 300 | |||
| 301 | (defcustom todos-done-separator (make-string (window-width) ?-) | ||
| 302 | "String used to visual separate done from not done items. | ||
| 303 | Displayed in a before-string overlay by `todos-toggle-view-done-items'." | ||
| 304 | :type 'string | ||
| 305 | :initialize 'custom-initialize-default | ||
| 306 | :set 'todos-reset-separator | ||
| 307 | :group 'todos) | ||
| 308 | |||
| 309 | (defcustom todos-done-string "DONE " | ||
| 310 | "Identifying string appended to the front of done todos items." | ||
| 311 | :type 'string | ||
| 312 | ;; :initialize 'custom-initialize-default | ||
| 313 | ;; :set | ||
| 314 | :group 'todos) | ||
| 315 | |||
| 316 | (defcustom todos-show-with-done nil | ||
| 317 | "Non-nil to display done items in all categories." | ||
| 318 | :type 'boolean | ||
| 319 | :group 'todos) | ||
| 320 | |||
| 321 | ;; FIXME: use user-emacs-directory here and below | ||
| 322 | (defcustom todos-file-do (convert-standard-filename "~/.emacs.d/.todos-do") | ||
| 301 | "TODO mode list file." | 323 | "TODO mode list file." |
| 302 | :type 'file | 324 | :type 'file |
| 303 | :group 'todos) | 325 | :group 'todos) |
| 304 | (defcustom todos-file-done (convert-standard-filename "~/.emacs.d/.todos-done") | 326 | |
| 305 | "TODO mode archive file." | 327 | (defcustom todos-files '((convert-standard-filename "~/.emacs.d/.todos")) |
| 328 | "List of Todos files." | ||
| 329 | :type 'list | ||
| 330 | :group 'todos) | ||
| 331 | |||
| 332 | (defcustom todos-archive-file (convert-standard-filename "~/.emacs.d/.todos-archive") | ||
| 333 | "File of finished Todos categories." | ||
| 306 | :type 'file | 334 | :type 'file |
| 307 | :group 'todos) | 335 | :group 'todos) |
| 308 | (defcustom todos-mode-hook nil | 336 | |
| 337 | ;; (defcustom todos-file-done (convert-standard-filename "~/.emacs.d/.todos-done") | ||
| 338 | ;; "TODO mode archive file." | ||
| 339 | ;; :type 'file | ||
| 340 | ;; :group 'todos) | ||
| 341 | |||
| 342 | (defcustom todos-mode-hook nil | ||
| 309 | "TODO mode hooks." | 343 | "TODO mode hooks." |
| 310 | :type 'hook | 344 | :type 'hook |
| 311 | :group 'todos) | 345 | :group 'todos) |
| 346 | |||
| 312 | (defcustom todos-edit-mode-hook nil | 347 | (defcustom todos-edit-mode-hook nil |
| 313 | "TODO Edit mode hooks." | 348 | "TODO Edit mode hooks." |
| 314 | :type 'hook | 349 | :type 'hook |
| 315 | :group 'todos) | 350 | :group 'todos) |
| 316 | (defcustom todos-insert-threshold 0 | 351 | |
| 317 | "TODO mode insertion accuracy. | 352 | ;; (defcustom todos-insert-threshold 0 |
| 318 | 353 | ;; "TODO mode insertion accuracy. | |
| 319 | If you have 8 items in your TODO list, then you may get asked 4 | 354 | |
| 320 | questions by the binary insertion algorithm. However, you may not | 355 | ;; If you have 8 items in your TODO list, then you may get asked 4 |
| 321 | really have a need for such accurate priorities amongst your TODO | 356 | ;; questions by the binary insertion algorithm. However, you may not |
| 322 | items. If you now think about the binary insertion halving the size | 357 | ;; really have a need for such accurate priorities amongst your TODO |
| 323 | of the window each time, then the threshold is the window size at | 358 | ;; items. If you now think about the binary insertion halving the size |
| 324 | which it will stop. If you set the threshold to zero, the upper and | 359 | ;; of the window each time, then the threshold is the window size at |
| 325 | lower bound will coincide at the end of the loop and you will insert | 360 | ;; which it will stop. If you set the threshold to zero, the upper and |
| 326 | your item just before that point. If you set the threshold to, | 361 | ;; lower bound will coincide at the end of the loop and you will insert |
| 327 | e.g. 8, it will stop as soon as the window size drops below that | 362 | ;; your item just before that point. If you set the threshold to, |
| 328 | amount and will insert the item in the approximate center of that | 363 | ;; e.g. 8, it will stop as soon as the window size drops below that |
| 329 | window." | 364 | ;; amount and will insert the item in the approximate center of that |
| 330 | :type 'integer | 365 | ;; window." |
| 366 | ;; :type 'integer | ||
| 367 | ;; :group 'todos) | ||
| 368 | |||
| 369 | (defcustom todos-categories-buffer "*TODOS Categories*" | ||
| 370 | "Name of buffer displayed by `todos-display-categories'" | ||
| 371 | :type 'string | ||
| 372 | :group 'todos) | ||
| 373 | |||
| 374 | (defcustom todos-archived-categories-buffer "*TODOS Archived Categories*" | ||
| 375 | "Name of buffer displayed by `todos-display-categories'" | ||
| 376 | :type 'string | ||
| 331 | :group 'todos) | 377 | :group 'todos) |
| 332 | (defvar todos-edit-buffer " *TODO Edit*" | 378 | |
| 333 | "TODO Edit buffer name.") | 379 | (defcustom todos-edit-buffer " *TODO Edit*" |
| 380 | "TODO Edit buffer name." | ||
| 381 | :type 'string | ||
| 382 | :group 'todos) | ||
| 383 | |||
| 334 | (defcustom todos-file-top (convert-standard-filename "~/.todos-top") | 384 | (defcustom todos-file-top (convert-standard-filename "~/.todos-top") |
| 335 | "TODO mode top priorities file. | 385 | "TODO mode top priorities file. |
| 336 | 386 | ||
| @@ -339,102 +389,83 @@ Automatically generated when `todos-save-top-priorities' is non-nil." | |||
| 339 | :type 'string | 389 | :type 'string |
| 340 | :group 'todos) | 390 | :group 'todos) |
| 341 | 391 | ||
| 392 | (defcustom todos-include-in-diary nil | ||
| 393 | "Non-nil to allow new Todo items to be included in the diary." | ||
| 394 | :type 'boolean | ||
| 395 | :group 'todos) | ||
| 396 | |||
| 397 | (defcustom todos-exclusion-start "[" | ||
| 398 | "String prepended to item date to block diary inclusion." | ||
| 399 | :type 'string | ||
| 400 | :group 'todos | ||
| 401 | ;; :initialize 'custom-initialize-default | ||
| 402 | ;; :set ; change in whole Todos file | ||
| 403 | ) | ||
| 404 | |||
| 405 | (defcustom todos-exclusion-end "]" | ||
| 406 | "String appended to item date to match todos-exclusion-start." | ||
| 407 | :type 'string | ||
| 408 | :group 'todos | ||
| 409 | ;; :initialize 'custom-initialize-default | ||
| 410 | ;; :set ; change in whole Todos file | ||
| 411 | ) | ||
| 412 | |||
| 342 | (defcustom todos-print-function 'ps-print-buffer-with-faces | 413 | (defcustom todos-print-function 'ps-print-buffer-with-faces |
| 343 | "Function to print the current buffer." | 414 | "Function to print the current buffer." |
| 344 | :type 'symbol | 415 | :type 'symbol |
| 345 | :group 'todos) | 416 | :group 'todos) |
| 417 | |||
| 346 | (defcustom todos-show-priorities 1 | 418 | (defcustom todos-show-priorities 1 |
| 347 | "Default number of priorities to show by \\[todos-top-priorities]. | 419 | "Default number of priorities to show by \\[todos-top-priorities]. |
| 348 | 0 means show all entries." | 420 | 0 means show all entries." |
| 349 | :type 'integer | 421 | :type 'integer |
| 350 | :group 'todos) | 422 | :group 'todos) |
| 423 | |||
| 351 | (defcustom todos-print-priorities 0 | 424 | (defcustom todos-print-priorities 0 |
| 352 | "Default number of priorities to print by \\[todos-print]. | 425 | "Default number of priorities to print by \\[todos-print]. |
| 353 | 0 means print all entries." | 426 | 0 means print all entries." |
| 354 | :type 'integer | 427 | :type 'integer |
| 355 | :group 'todos) | 428 | :group 'todos) |
| 356 | (defcustom todos-remove-separator t | 429 | ;; (defcustom todos-remove-separator t |
| 357 | "Non-nil to remove category separators in\ | 430 | ;; "Non-nil to remove category separators in\ |
| 358 | \\[todos-top-priorities] and \\[todos-print]." | 431 | ;; \\[todos-top-priorities] and \\[todos-print]." |
| 359 | :type 'boolean | 432 | ;; :type 'boolean |
| 360 | :group 'todos) | 433 | ;; :group 'todos) |
| 434 | |||
| 361 | (defcustom todos-save-top-priorities-too t | 435 | (defcustom todos-save-top-priorities-too t |
| 362 | "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." | 436 | "Non-nil makes `todos-save' automatically save top-priorities in `todos-file-top'." |
| 363 | :type 'boolean | 437 | :type 'boolean |
| 364 | :group 'todos) | 438 | :group 'todos) |
| 439 | |||
| 365 | (defcustom todos-completion-ignore-case t ;; FIXME: nil for release | 440 | (defcustom todos-completion-ignore-case t ;; FIXME: nil for release |
| 366 | "Non-nil means don't consider case significant in todos-completing-read." | 441 | "Non-nil means don't consider case significant in todos-completing-read." |
| 367 | :type 'boolean | 442 | :type 'boolean |
| 368 | :group 'todos) | 443 | :group 'todos) |
| 369 | 444 | ||
| 370 | ;; ;; Thanks for the ISO time stamp format go to Karl Eichwalder <ke@suse.de> | ||
| 371 | ;; ;; My format string for the appt.el package is "%3b %2d, %y, %02I:%02M%p". | ||
| 372 | ;; ;; | ||
| 373 | ;; ;; FIXME: use calendar format instead | ||
| 374 | ;; (defcustom todos-time-string-format | ||
| 375 | ;; "%:y-%02m-%02d %02H:%02M" | ||
| 376 | ;; "TODO mode time string format for done entries. | ||
| 377 | ;; For details see the variable `time-stamp-format'." | ||
| 378 | ;; :type 'string | ||
| 379 | ;; :group 'todos) | ||
| 380 | |||
| 381 | ;; (defcustom todos-entry-prefix-function 'todos-entry-timestamp-initials | ||
| 382 | ;; "Function producing text to insert at start of todo entry." | ||
| 383 | ;; :type 'symbol | ||
| 384 | ;; :group 'todos) | ||
| 385 | ;; (defcustom todos-initials (or (getenv "INITIALS") (user-login-name)) | ||
| 386 | ;; "Initials of todo item author." | ||
| 387 | ;; :type 'string | ||
| 388 | ;; :group 'todos) | ||
| 389 | |||
| 390 | ;; (defun todos-entry-timestamp-initials () | ||
| 391 | ;; "Prepend timestamp and your initials to the head of a TODO entry." | ||
| 392 | ;; (let ((time-stamp-format todos-time-string-format)) | ||
| 393 | ;; (concat (time-stamp-string) " " todos-initials ": "))) | ||
| 394 | |||
| 395 | ;; (defcustom todos-date (calendar-date-string (calendar-current-date) t t) | ||
| 396 | ;; "Date string inserted in front of a todo item." | ||
| 397 | ;; :type 'string | ||
| 398 | ;; :group 'todos) | ||
| 399 | |||
| 400 | ;; (defcustom todos-time (substring (current-time-string) 11 16) | ||
| 401 | ;; "Time string inserted in front of a todo item." | ||
| 402 | ;; :type 'string | ||
| 403 | ;; :group 'todos) | ||
| 404 | |||
| 405 | (defun todos-current-date (&optional time) | ||
| 406 | "Return current date as a string for insertion in front of a todo item. | ||
| 407 | With non-nil TIME append the current time." | ||
| 408 | (concat (calendar-date-string (calendar-current-date) t t) | ||
| 409 | (when time | ||
| 410 | (concat " " (substring (current-time-string) 11 16))))) | ||
| 411 | |||
| 412 | (defcustom todos-add-time-string t | 445 | (defcustom todos-add-time-string t |
| 413 | "Add current time to date string inserted in front of new items." | 446 | "Add current time to date string inserted in front of new items." |
| 414 | :type 'boolean | 447 | :type 'boolean |
| 415 | :group 'todos) | 448 | :group 'todos) |
| 416 | 449 | ||
| 417 | ;; "Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec") | 450 | (defcustom todos-wrap-lines t |
| 418 | ;; (regexp-opt (list "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31"))) | 451 | "" ;FIXME |
| 419 | (defun todos-date-string () | 452 | :group 'todos |
| 420 | "Return a regexp matching a diary date string." | 453 | :type 'boolean) |
| 421 | (let ((month (regexp-opt (list "Jan" "Feb" "Mar" "Apr" "May" "Jun" | ||
| 422 | "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))) | ||
| 423 | (day "[0-3]?[0-9]") | ||
| 424 | (year "[0-9]\\{4\\}")) | ||
| 425 | (concat month " " day ", " year))) | ||
| 426 | |||
| 427 | (defun todos-time-string () | ||
| 428 | "Return a regexp matching a diary time string." | ||
| 429 | "[0-9]?[0-9][:.][0-9]\\{2\\}") | ||
| 430 | 454 | ||
| 455 | (defcustom todos-line-wrapping-function 'todos-wrap-and-indent | ||
| 456 | "" ;FIXME | ||
| 457 | :group 'todos | ||
| 458 | :type 'function) | ||
| 459 | |||
| 460 | ;; --------------------------------------------------------------------------- | ||
| 461 | |||
| 462 | ;;; Faces | ||
| 431 | (defface todos-prefix-string | 463 | (defface todos-prefix-string |
| 432 | '((t | 464 | '((t |
| 433 | :inherit font-lock-constant-face | 465 | :inherit font-lock-constant-face |
| 434 | )) | 466 | )) |
| 435 | "Face for Todos prefix string." | 467 | "Face for Todos prefix string." |
| 436 | :group 'todos) | 468 | :group 'todos) |
| 437 | ;; (defvar todos-prefix-face 'todos-prefix-string) | ||
| 438 | 469 | ||
| 439 | (defface todos-date | 470 | (defface todos-date |
| 440 | '((t | 471 | '((t |
| @@ -452,83 +483,33 @@ With non-nil TIME append the current time." | |||
| 452 | :group 'todos) | 483 | :group 'todos) |
| 453 | (defvar todos-time-face 'todos-time) | 484 | (defvar todos-time-face 'todos-time) |
| 454 | 485 | ||
| 455 | (defun todos-date-string-match (lim) | 486 | (defface todos-done |
| 456 | "Find Todos date strings for font-locking." | 487 | '((t |
| 457 | (let ((lim (point-max))) | 488 | :inherit font-lock-comment-face |
| 458 | (re-search-forward (concat "^\\[?\\(" (todos-date-string) "\\)") lim t))) | 489 | )) |
| 490 | "Face for done Todos item header string." | ||
| 491 | :group 'todos) | ||
| 492 | (defvar todos-done-face 'todos-done) | ||
| 459 | 493 | ||
| 460 | (defun todos-time-string-match (lim) | 494 | (defface todos-done-sep |
| 461 | "Find Todos time strings for font-locking." | 495 | '((t |
| 462 | (let ((lim (point-max))) | 496 | :inherit font-lock-type-face |
| 463 | (re-search-forward (concat "^\\[?" (todos-date-string) | 497 | )) |
| 464 | " \\(" (todos-time-string) "\\)") lim t))) | 498 | "Face for separator string bewteen done and not done Todos items." |
| 499 | :group 'todos) | ||
| 500 | (defvar todos-done-sep-face 'todos-done-sep) | ||
| 465 | 501 | ||
| 466 | (defvar todos-font-lock-keywords | 502 | (defvar todos-font-lock-keywords |
| 467 | (list | 503 | (list |
| 468 | '(todos-date-string-match 1 todos-date-face t) | 504 | '(todos-date-string-match 1 todos-date-face t) |
| 469 | '(todos-time-string-match 1 todos-time-face t)) | 505 | '(todos-time-string-match 1 todos-time-face t) |
| 506 | '(todos-done-string-match 0 todos-done-face t) | ||
| 507 | '(todos-category-string-match 0 todos-done-sep-face t)) | ||
| 470 | "Font-locking for Todos mode.") | 508 | "Font-locking for Todos mode.") |
| 471 | 509 | ||
| 472 | (defcustom todos-include-in-diary nil | ||
| 473 | "Non-nil to allow new Todo items to be included in the diary." | ||
| 474 | :type 'boolean | ||
| 475 | :group 'todos) | ||
| 476 | |||
| 477 | (defcustom todos-exclusion-start "[" | ||
| 478 | "String prepended to item date to block diary inclusion." | ||
| 479 | :type 'string | ||
| 480 | :group 'todos | ||
| 481 | ;; :initialize 'custom-initialize-default | ||
| 482 | ;; :set ; change in whole Todos file | ||
| 483 | ) | ||
| 484 | |||
| 485 | (defcustom todos-exclusion-end "]" | ||
| 486 | "String appended to item date to match todos-exclusion-start." | ||
| 487 | :type 'string | ||
| 488 | :group 'todos | ||
| 489 | ;; :initialize 'custom-initialize-default | ||
| 490 | ;; :set ; change in whole Todos file | ||
| 491 | ) | ||
| 492 | |||
| 493 | (defun todos-toggle-item-diary-inclusion () | ||
| 494 | "" ;FIXME add docstring | ||
| 495 | (interactive) | ||
| 496 | (save-excursion | ||
| 497 | (let ((beg (goto-char (todos-item-start))) | ||
| 498 | (end (save-excursion | ||
| 499 | (or (todos-time-string-match (todos-item-end)) | ||
| 500 | (todos-date-string-match (todos-item-end)))))) | ||
| 501 | (if (looking-at "\\[") ; FIXME use todos-exclusion-start | ||
| 502 | (progn | ||
| 503 | (replace-match "") | ||
| 504 | (search-forward "]" (1+ end) t) ; FIXME use todos-exclusion-end | ||
| 505 | (replace-match "")) | ||
| 506 | (when end | ||
| 507 | (insert "[") ; FIXME use todos-exclusion-start | ||
| 508 | (goto-char (1+ end)) | ||
| 509 | (insert "]")))))) ; FIXME use todos-exclusion-end | ||
| 510 | |||
| 511 | (defun todos-toggle-diary-inclusion (arg) | ||
| 512 | "" ;FIXME add docstring | ||
| 513 | (interactive "p") | ||
| 514 | (save-excursion | ||
| 515 | (save-restriction | ||
| 516 | (when (eq arg 2) (widen)) | ||
| 517 | (when (or (eq arg 1) (eq arg 2)) | ||
| 518 | (goto-char (point-min)) | ||
| 519 | (when (eq arg 2) | ||
| 520 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) | ||
| 521 | (point-max) t) | ||
| 522 | (forward-line) | ||
| 523 | (when (looking-at (regexp-quote todos-category-end)) (forward-line))) | ||
| 524 | (while (not (eobp)) | ||
| 525 | (todos-toggle-item-diary-inclusion) | ||
| 526 | (todos-forward-item)))))) | ||
| 527 | |||
| 528 | ;; --------------------------------------------------------------------------- | 510 | ;; --------------------------------------------------------------------------- |
| 529 | 511 | ||
| 530 | ;; Set up some helpful context ... | 512 | ;;; Internal variables |
| 531 | |||
| 532 | (defvar todos-categories nil | 513 | (defvar todos-categories nil |
| 533 | "TODO categories.") | 514 | "TODO categories.") |
| 534 | 515 | ||
| @@ -541,172 +522,89 @@ With non-nil TIME append the current time." | |||
| 541 | (defvar todos-mode-map | 522 | (defvar todos-mode-map |
| 542 | (let ((map (make-keymap))) | 523 | (let ((map (make-keymap))) |
| 543 | (suppress-keymap map t) | 524 | (suppress-keymap map t) |
| 525 | ;; navigation commands | ||
| 544 | (define-key map "+" 'todos-forward-category) | 526 | (define-key map "+" 'todos-forward-category) |
| 545 | (define-key map "-" 'todos-backward-category) | 527 | (define-key map "-" 'todos-backward-category) |
| 546 | (define-key map "A" 'todos-add-category) | 528 | (define-key map "j" 'todos-jump-to-category) |
| 529 | (define-key map "n" 'todos-forward-item) | ||
| 530 | (define-key map "p" 'todos-backward-item) | ||
| 531 | (define-key map "S" 'todos-search) | ||
| 532 | ;; display commands | ||
| 547 | (define-key map "C" 'todos-display-categories) | 533 | (define-key map "C" 'todos-display-categories) |
| 548 | (define-key map "d" 'todos-file-item) ;done/delete | 534 | (define-key map "h" 'todos-highlight-item) |
| 535 | (define-key map "N" 'todos-toggle-item-numbering) | ||
| 536 | ;; (define-key map "" 'todos-toggle-display-date-time) | ||
| 537 | (define-key map "P" 'todos-print) | ||
| 538 | (define-key map "q" 'todos-quit) | ||
| 539 | (define-key map "s" 'todos-save) | ||
| 540 | (define-key map "v" 'todos-toggle-view-done-items) | ||
| 541 | (define-key map "Y" 'todos-diary-items) | ||
| 542 | ;; (define-key map "S" 'todos-save-top-priorities) | ||
| 543 | (define-key map "t" 'todos-top-priorities) | ||
| 544 | ;; editing commands | ||
| 545 | (define-key map "A" 'todos-add-category) | ||
| 546 | (define-key map "d" 'todos-item-done) | ||
| 547 | ;; (define-key map "" 'todos-archive-done-items) | ||
| 549 | (define-key map "D" 'todos-delete-category) | 548 | (define-key map "D" 'todos-delete-category) |
| 550 | (define-key map "e" 'todos-edit-item) | 549 | (define-key map "e" 'todos-edit-item) |
| 551 | (define-key map "E" 'todos-edit-multiline) | 550 | (define-key map "E" 'todos-edit-multiline) |
| 552 | (define-key map "f" 'todos-file-item) | 551 | ;; (define-key map "" 'todos-change-date) |
| 553 | (define-key map "i" 'todos-insert-item) | 552 | ;; (define-key map "f" 'todos-file-item) |
| 554 | (define-key map "I" 'todos-insert-item-here) | 553 | (define-key map "ii" 'todos-insert-item) |
| 555 | (define-key map "j" 'todos-jump-to-category) | 554 | (define-key map "ih" 'todos-insert-item-here) |
| 555 | (define-key map "ia" 'todos-insert-item-ask-date) | ||
| 556 | (define-key map "id" 'todos-insert-item-for-diary) | ||
| 557 | ;; (define-key map "in" 'todos-insert-item-no-time) | ||
| 556 | (define-key map "k" 'todos-delete-item) | 558 | (define-key map "k" 'todos-delete-item) |
| 557 | (define-key map "l" 'todos-lower-item) | 559 | (define-key map "l" 'todos-lower-item) |
| 558 | (define-key map "m" 'todos-move-item) | 560 | (define-key map "m" 'todos-move-item) |
| 561 | (define-key map "r" 'todos-raise-item) | ||
| 562 | (define-key map "R" 'todos-rename-category) | ||
| 563 | (define-key map "u" 'todos-item-undo) | ||
| 564 | (define-key map "y" 'todos-toggle-item-diary-inclusion) | ||
| 565 | ;; (define-key map "" 'todos-toggle-diary-inclusion) | ||
| 566 | map) | ||
| 567 | "Todos mode keymap.") | ||
| 568 | |||
| 569 | (defvar todos-archive-mode-map | ||
| 570 | (let ((map (make-sparse-keymap))) | ||
| 571 | (suppress-keymap map t) | ||
| 572 | ;; navigation commands | ||
| 573 | (define-key map "+" 'todos-forward-category) | ||
| 574 | (define-key map "-" 'todos-backward-category) | ||
| 575 | (define-key map "j" 'todos-jump-to-category) | ||
| 559 | (define-key map "n" 'todos-forward-item) | 576 | (define-key map "n" 'todos-forward-item) |
| 560 | (define-key map "p" 'todos-backward-item) | 577 | (define-key map "p" 'todos-backward-item) |
| 578 | ;; display commands | ||
| 579 | (define-key map "C" 'todos-display-categories) | ||
| 580 | (define-key map "h" 'todos-highlight-item) | ||
| 581 | (define-key map "N" 'todos-toggle-item-numbering) | ||
| 582 | ;; (define-key map "" 'todos-toggle-display-date-time) | ||
| 561 | (define-key map "P" 'todos-print) | 583 | (define-key map "P" 'todos-print) |
| 562 | (define-key map "q" 'todos-quit) | 584 | (define-key map "q" 'todos-quit) |
| 563 | (define-key map "r" 'todos-raise-item) | ||
| 564 | (define-key map "R" 'todos-rename-category) | ||
| 565 | (define-key map "s" 'todos-save) | 585 | (define-key map "s" 'todos-save) |
| 566 | (define-key map "S" 'todos-save-top-priorities) | 586 | (define-key map "S" 'todos-search) |
| 567 | (define-key map "t" 'todos-top-priorities) | 587 | map) |
| 588 | "Todos Archive mode keymap.") | ||
| 589 | |||
| 590 | (defvar todos-edit-mode-map | ||
| 591 | (let ((map (make-keymap))) | ||
| 592 | (define-key map "\C-c\C-q" 'todos-edit-quit) | ||
| 568 | map) | 593 | map) |
| 569 | "TODO mode keymap.") | 594 | "Todos Edit mode keymap.") |
| 570 | 595 | ||
| 571 | (defvar todos-category-number 0 "TODO category number.") | 596 | (defvar todos-category-number 0 "TODO category number.") |
| 572 | 597 | ||
| 573 | (defvar todos-tmp-buffer-name " *todo tmp*") | 598 | (defvar todos-tmp-buffer-name " *todo tmp*") |
| 574 | 599 | ||
| 575 | ;; ;; FIXME: should the following four be defconsts? | 600 | (defvar todos-category-beg "--==-- " |
| 576 | ;; (defvar todos-category-sep (make-string 75 ?-) | ||
| 577 | ;; "Category separator.") | ||
| 578 | |||
| 579 | (defvar todos-category-beg "--- " ;" --- " | ||
| 580 | "Category start separator to be prepended onto category name.") | 601 | "Category start separator to be prepended onto category name.") |
| 581 | 602 | ||
| 582 | ;; (defvar todos-category-end "--- End" | ||
| 583 | ;; "Separator after a category.") | ||
| 584 | |||
| 585 | (defvar todos-item-end " :::" | ||
| 586 | "String marking the end of a todo item. | ||
| 587 | In Todos mode it is made invisible with an overlay.") | ||
| 588 | |||
| 589 | ;; --------------------------------------------------------------------------- | 603 | ;; --------------------------------------------------------------------------- |
| 590 | 604 | ||
| 591 | (defcustom todos-number-prefix nil | 605 | ;;; Commands |
| 592 | "Non-nil to show item prefixes as consecutively increasing integers." | ||
| 593 | :type 'boolean | ||
| 594 | :initialize 'custom-initialize-default | ||
| 595 | :set 'todos-reset-prefix | ||
| 596 | :group 'todos) | ||
| 597 | |||
| 598 | (defun todos-reset-prefix (symbol value) | ||
| 599 | "Set SYMBOL's value to VALUE, and ." ; FIXME | ||
| 600 | (let ((oldvalue (symbol-value symbol))) | ||
| 601 | (custom-set-default symbol value) | ||
| 602 | (when (not (equal value oldvalue)) | ||
| 603 | (save-window-excursion | ||
| 604 | (todos-show) | ||
| 605 | (save-excursion | ||
| 606 | (widen) | ||
| 607 | (goto-char (point-min)) | ||
| 608 | (while (not (eobp)) | ||
| 609 | (re-search-forward | ||
| 610 | (concat "^" (regexp-quote todos-category-beg)) (point-max) t) | ||
| 611 | (forward-line) | ||
| 612 | (or (eobp) | ||
| 613 | (while (not (looking-at (regexp-quote todos-category-end))) | ||
| 614 | (remove-overlays (1- (point)) (1+ (point))) | ||
| 615 | (forward-line))))) | ||
| 616 | ;; activate the prefix setting (save-restriction does not help) | ||
| 617 | (todos-show))))) | ||
| 618 | |||
| 619 | ;; FIXME: rename and/or rewrite | ||
| 620 | (defun todos-update-numbered-prefix () | ||
| 621 | "Update consecutive item numbering in the current category." | ||
| 622 | (save-excursion | ||
| 623 | (goto-char (point-min)) | ||
| 624 | (while (not (eobp)) | ||
| 625 | (remove-overlays (1- (point)) (1+ (point))) | ||
| 626 | (todos-forward-item)) | ||
| 627 | (todos-show))) | ||
| 628 | |||
| 629 | (defvar todos-item-start-overlays nil "") | ||
| 630 | |||
| 631 | (defvar todos-item-end-overlays nil "") | ||
| 632 | 606 | ||
| 633 | (defun todos-check-overlay (prop) | 607 | ;;; Navigation |
| 634 | "" ;FIXME add docstring | ||
| 635 | (let ((ovlist (overlays-in (1- (point)) (1+ (point))))) | ||
| 636 | ;; (let ((ovlist (overlays-in (point) (point)))) | ||
| 637 | (when ovlist (overlay-get (car ovlist) prop)))) | ||
| 638 | |||
| 639 | (defun todos-item-overlays () | ||
| 640 | "" ;FIXME add docstring | ||
| 641 | (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) | ||
| 642 | (num 1) | ||
| 643 | ;; (paren show-paren-mode) | ||
| 644 | ov-pref ov-end) | ||
| 645 | ;; turn off show-paren-mode to avoid overlay reduplication problem | ||
| 646 | ;; (if paren (show-paren-mode 0)) | ||
| 647 | (save-excursion | ||
| 648 | (goto-char (point-min)) | ||
| 649 | (while (not (eobp)) | ||
| 650 | (if todos-number-prefix | ||
| 651 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 652 | 'face 'todos-prefix-string))) | ||
| 653 | (unless (todos-check-overlay 'before-string) | ||
| 654 | (or (and (setq ov-pref (pop todos-item-start-overlays)) | ||
| 655 | (move-overlay ov-pref (point) (point))) | ||
| 656 | (and (setq ov-pref (make-overlay (point) (point))) | ||
| 657 | (overlay-put ov-pref 'before-string prefix)))) | ||
| 658 | (re-search-forward (concat "\\(" (regexp-quote todos-item-end) "\\)\n")) | ||
| 659 | (backward-char) | ||
| 660 | (unless (todos-check-overlay 'invisible) | ||
| 661 | (or (and (setq ov-end (pop | ||
| 662 | todos-item-end-overlays)) | ||
| 663 | (move-overlay ov-end (match-beginning 1) (match-end 1))) | ||
| 664 | (and (setq ov-end (make-overlay (match-beginning 1) (match-end 1))) | ||
| 665 | (overlay-put ov-end 'invisible t)))) | ||
| 666 | (forward-line) | ||
| 667 | (if todos-number-prefix (setq num (1+ num)))) | ||
| 668 | ;; (if paren (show-paren-mode 1)) | ||
| 669 | ;; (todos-show-paren-hack) | ||
| 670 | ))) | ||
| 671 | |||
| 672 | (defun todos-show-paren-hack () | ||
| 673 | "Purge overlay duplication due to show-paren-mode." | ||
| 674 | (save-excursion | ||
| 675 | (when show-paren-mode | ||
| 676 | (goto-char (point-min)) | ||
| 677 | (while (not (eobp)) | ||
| 678 | ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))) | ||
| 679 | (let ((ovlist (overlays-in (point) (point))) | ||
| 680 | ov) | ||
| 681 | (while (> (length ovlist) 1) | ||
| 682 | (setq ov (pop ovlist)) | ||
| 683 | (delete-overlay ov))) | ||
| 684 | (forward-line)) | ||
| 685 | (if (and (bolp) (eolp)) | ||
| 686 | ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))) | ||
| 687 | (let ((ovlist (overlays-in (point) (point)))) | ||
| 688 | (remove-overlays (1- (point)) (1+ (point)))))))) | ||
| 689 | |||
| 690 | (defun todos-category-select () | ||
| 691 | "Make TODO mode display the current category correctly." | ||
| 692 | (let ((name (nth todos-category-number todos-categories))) | ||
| 693 | (setq mode-line-buffer-identification | ||
| 694 | ;; (concat "Category: " name)) | ||
| 695 | (concat "Category: " (format "%18s" name))) | ||
| 696 | (widen) | ||
| 697 | (goto-char (point-min)) | ||
| 698 | (search-forward-regexp | ||
| 699 | (concat "\n" ;"^" (regexp-quote todos-category-sep) "\n" | ||
| 700 | (regexp-quote (concat todos-category-beg name)) | ||
| 701 | "$")) | ||
| 702 | (let ((begin (1+ (line-end-position)))) | ||
| 703 | ;; (search-forward-regexp (concat "^" todos-category-end)) | ||
| 704 | (re-search-forward (concat "^" todos-category-beg) (point-max) t) | ||
| 705 | (narrow-to-region begin (line-beginning-position)) | ||
| 706 | (goto-char (point-min)))) | ||
| 707 | (todos-item-overlays) | ||
| 708 | ;; (todos-show-paren-hack) | ||
| 709 | ) | ||
| 710 | 608 | ||
| 711 | (defun todos-forward-category () | 609 | (defun todos-forward-category () |
| 712 | "Go forward to TODO list of next category." | 610 | "Go forward to TODO list of next category." |
| @@ -722,65 +620,398 @@ In Todos mode it is made invisible with an overlay.") | |||
| 722 | (mod (1- todos-category-number) (length todos-categories))) | 620 | (mod (1- todos-category-number) (length todos-categories))) |
| 723 | (todos-category-select)) | 621 | (todos-category-select)) |
| 724 | 622 | ||
| 623 | ;; FIXME: Document that a non-existing name creates that category, and add | ||
| 624 | ;; y-or-n-p confirmation -- or eliminate this possibility? | ||
| 625 | (defun todos-jump-to-category () | ||
| 626 | "Jump to a category. Default is previous category." | ||
| 627 | (interactive) | ||
| 628 | (let ((category (todos-completing-read))) | ||
| 629 | (if (string= "" category) | ||
| 630 | (setq category (nth todos-category-number todos-categories))) | ||
| 631 | (setq todos-category-number | ||
| 632 | (if (member category todos-categories) | ||
| 633 | (- (length todos-categories) | ||
| 634 | (length (member category todos-categories))) | ||
| 635 | (todos-add-category category))) | ||
| 636 | ;; (todos-show))) | ||
| 637 | (todos-category-select))) | ||
| 638 | |||
| 639 | ;; FIXME ? todos-{backward,forward}-item skip over empty line between done and | ||
| 640 | ;; not done items (but todos-forward-item gets there when done items are not | ||
| 641 | ;; displayed) | ||
| 725 | (defun todos-backward-item (&optional count) | 642 | (defun todos-backward-item (&optional count) |
| 726 | "Select previous entry of TODO list." | 643 | "Select previous entry of TODO list." |
| 727 | (interactive "P") | 644 | (interactive "P") |
| 728 | (re-search-backward (concat (regexp-quote todos-item-end) "\n") nil t count) | 645 | ;; FIXME ? this moves to bob if on the first item (but so does previous-line) |
| 729 | (goto-char (todos-item-start))) | 646 | (todos-item-start) |
| 647 | (unless (bobp) | ||
| 648 | (re-search-backward (concat "^\\(\\[\\(" (regexp-quote todos-done-string) | ||
| 649 | "\\)?\\)?\\(" todos-date-pattern "\\)") | ||
| 650 | nil t (or count 1)))) | ||
| 730 | 651 | ||
| 731 | (defun todos-forward-item (&optional count) | 652 | (defun todos-forward-item (&optional count) |
| 732 | "Select COUNT-th next entry of TODO list." | 653 | "Select COUNT-th next entry of TODO list." |
| 733 | (interactive "P") | 654 | (interactive "P") |
| 734 | (when (todos-check-overlay 'invisible) (goto-char (todos-item-start))) | 655 | (goto-char (line-end-position)) |
| 735 | (re-search-forward (concat (regexp-quote todos-item-end) "\n") nil t count)) | 656 | (if (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string) |
| 657 | "\\)?\\)?\\(" todos-date-pattern "\\)") | ||
| 658 | nil t (or count 1)) | ||
| 659 | (goto-char (match-beginning 0)) | ||
| 660 | (goto-char (point-max)))) | ||
| 661 | |||
| 662 | ;; (defun todos-forward-item (&optional count) | ||
| 663 | ;; "Select COUNT-th next entry of TODO list." | ||
| 664 | ;; (interactive "P") | ||
| 665 | ;; (let ((opoint (point)) | ||
| 666 | ;; (done (save-excursion | ||
| 667 | ;; (if (re-search-forward (concat "\n\n\\\(\\[" | ||
| 668 | ;; (regexp-quote todos-done-string) | ||
| 669 | ;; "\\)") nil t) | ||
| 670 | ;; (match-beginning 1))))) | ||
| 671 | ;; ;; FIXME: can this be simplified? | ||
| 672 | ;; (if (looking-at (concat "^\\(\\[\\(" (regexp-quote todos-done-string) "\\)?\\)?" | ||
| 673 | ;; todos-date-pattern)) ; on item header | ||
| 674 | ;; (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string) | ||
| 675 | ;; "\\)?\\)?\\(" todos-date-pattern "\\)") | ||
| 676 | ;; nil t (if count (1+ count) 2)) | ||
| 677 | ;; (re-search-forward (concat "^\\(\\[\\(" (regexp-quote todos-done-string) | ||
| 678 | ;; "\\)?\\)?\\(" todos-date-pattern "\\)") | ||
| 679 | ;; nil t (or count 1))) | ||
| 680 | ;; (cond ((save-excursion | ||
| 681 | ;; (goto-char opoint) | ||
| 682 | ;; (looking-at "^$")) ; between done and not done items | ||
| 683 | ;; (forward-line 0)) | ||
| 684 | ;; ((and done (> (point) done)) | ||
| 685 | ;; (forward-line -1)) ; FIXME: count ? | ||
| 686 | ;; ((eq (point) opoint) ; on last item | ||
| 687 | ;; (goto-char (point-max))) | ||
| 688 | ;; (t | ||
| 689 | ;; (goto-char (match-beginning 0)))))) | ||
| 690 | |||
| 691 | (defvar todos-search-string nil | ||
| 692 | "" ;FIXME | ||
| 693 | ) | ||
| 694 | (defun todos-search () | ||
| 695 | "" ;FIXME | ||
| 696 | (interactive) | ||
| 697 | (let ((regex (read-from-minibuffer "Enter a search string (regexp): ")) | ||
| 698 | (start (point)) | ||
| 699 | found cat in-done) | ||
| 700 | (widen) | ||
| 701 | (goto-char (point-min)) | ||
| 702 | (while (and (setq found (re-search-forward regex nil t)) | ||
| 703 | (save-excursion | ||
| 704 | (goto-char (line-beginning-position)) | ||
| 705 | (looking-at (concat "^" (regexp-quote todos-category-beg))))) | ||
| 706 | (forward-line)) | ||
| 707 | (if found | ||
| 708 | (progn | ||
| 709 | (setq found (match-beginning 0)) | ||
| 710 | (todos-item-start) | ||
| 711 | (when (looking-at (concat "^\\[" (regexp-quote todos-done-string))) | ||
| 712 | (setq in-done t)) | ||
| 713 | (re-search-backward (concat "^" (regexp-quote todos-category-beg) | ||
| 714 | "\\(.*\\)\n") nil t) | ||
| 715 | (setq cat (match-string-no-properties 1)) | ||
| 716 | (setq todos-category-number | ||
| 717 | (- (length todos-categories) (length (member cat todos-categories)))) | ||
| 718 | (todos-category-select) | ||
| 719 | (when in-done (unless todos-show-with-done (todos-toggle-view-done-items))) | ||
| 720 | (goto-char found)) | ||
| 721 | (todos-category-select) | ||
| 722 | (goto-char start) | ||
| 723 | (message "No match for \"%s\"" regex)))) | ||
| 724 | |||
| 725 | ;;; Display | ||
| 736 | 726 | ||
| 737 | (defun todos-save () | 727 | (defun todos-display-categories () |
| 738 | "Save the TODO list." | 728 | "Display an alphabetical list of clickable Todos category names. |
| 729 | Click or type RET on a category name to go to it." | ||
| 739 | (interactive) | 730 | (interactive) |
| 740 | (save-excursion | 731 | (let ((categories (copy-sequence todos-categories)) |
| 741 | (save-restriction | 732 | (cat-alist (todos-categories-alist)) |
| 742 | (save-buffer))) | 733 | (len (todos-longest-category-name-length)) |
| 743 | (if todos-save-top-priorities-too (todos-save-top-priorities))) | 734 | beg) |
| 735 | ;; alphabetize the list case insensitively | ||
| 736 | (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1)) | ||
| 737 | (cis2 (upcase s2))) | ||
| 738 | (string< cis1 cis2))))) | ||
| 739 | (with-current-buffer (get-buffer-create todos-categories-buffer) | ||
| 740 | (switch-to-buffer (current-buffer)) | ||
| 741 | (erase-buffer) | ||
| 742 | (kill-all-local-variables) | ||
| 743 | (insert "Press a button to display the corresponding category.\n\n") | ||
| 744 | (setq beg (point)) | ||
| 745 | (mapc (lambda (cat) | ||
| 746 | (let* ((catlen (length cat)) | ||
| 747 | (catlen-odd (eq (logand catlen 1) 1)) ; oddp from cl.el | ||
| 748 | (padding (/ (- len catlen) 2))) | ||
| 749 | (insert-button (concat (make-string padding 32) cat | ||
| 750 | (make-string (if catlen-odd | ||
| 751 | (1+ padding) | ||
| 752 | padding) | ||
| 753 | 32)) | ||
| 754 | 'face 'tool-bar | ||
| 755 | 'action | ||
| 756 | `(lambda (button) | ||
| 757 | (todos-jump-to-category-noninteractively ,cat))) | ||
| 758 | (insert (make-string 8 32) | ||
| 759 | "(not done: " | ||
| 760 | (number-to-string (car (cadr (assoc cat cat-alist)))) | ||
| 761 | ", done: " | ||
| 762 | (number-to-string (cdr (cadr (assoc cat cat-alist)))) | ||
| 763 | ")") | ||
| 764 | (newline))) | ||
| 765 | categories)))) | ||
| 766 | ;; (require 'widget) | ||
| 767 | ;; (eval-when-compile | ||
| 768 | ;; (require 'wid-edit)) | ||
| 769 | ;; (with-current-buffer (get-buffer-create todos-categories-buffer) | ||
| 770 | ;; (switch-to-buffer (current-buffer)) | ||
| 771 | ;; (erase-buffer) | ||
| 772 | ;; (kill-all-local-variables) | ||
| 773 | ;; (widget-insert "Press a button to display the corresponding category.\n\n") | ||
| 774 | ;; (setq beg (point)) | ||
| 775 | ;; (mapc (lambda (cat) | ||
| 776 | ;; (widget-create 'push-button | ||
| 777 | ;; :notify (lambda (widget &rest ignore) | ||
| 778 | ;; (todos-jump-to-category-noninteractively | ||
| 779 | ;; (widget-get widget :value))) | ||
| 780 | |||
| 781 | ;; cat) | ||
| 782 | ;; (widget-insert " (not done: " | ||
| 783 | ;; (number-to-string (car (cadr (assoc cat cat-alist)))) | ||
| 784 | ;; ", done: " | ||
| 785 | ;; (number-to-string (cdr (cadr (assoc cat cat-alist)))) | ||
| 786 | ;; ")\n")) | ||
| 787 | ;; categories) | ||
| 788 | ;; (use-local-map widget-keymap) | ||
| 789 | ;; (widget-setup)))) | ||
| 790 | |||
| 791 | (defun todos-toggle-item-numbering () | ||
| 792 | "" ;FIXME | ||
| 793 | (interactive) | ||
| 794 | (todos-reset-prefix 'todos-number-prefix (not todos-number-prefix))) | ||
| 744 | 795 | ||
| 745 | (defun todos-quit () | 796 | (defun todos-toggle-view-done-items () |
| 746 | "Done with TODO list for now." | 797 | "" ; FIXME |
| 747 | (interactive) | 798 | (interactive) |
| 748 | (widen) | 799 | (let ((beg (point-min)) |
| 749 | (todos-save) | 800 | (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator) |
| 750 | (message "") | 801 | todos-done-separator |
| 751 | (bury-buffer)) | 802 | (propertize (concat todos-done-separator "\n") |
| 803 | 'face 'todos-done-sep))) | ||
| 804 | (todos-show-with-done nil) | ||
| 805 | (done (point-max)) | ||
| 806 | end ov) | ||
| 807 | (save-excursion | ||
| 808 | (goto-char beg) | ||
| 809 | (if (re-search-forward (concat "\n\\[" (regexp-quote todos-done-string)) | ||
| 810 | nil t) | ||
| 811 | ;; hide done items | ||
| 812 | (progn (setq end (match-beginning 0)) | ||
| 813 | (narrow-to-region beg end)) | ||
| 814 | (widen) | ||
| 815 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 816 | (setq end (or (match-beginning 0) (point-max))) | ||
| 817 | (goto-char beg) | ||
| 818 | (if (re-search-forward | ||
| 819 | (concat (if (eq beg done) "" "\n") ; no newline if no unfinished items | ||
| 820 | "\n\\(\\[" (regexp-quote todos-done-string) "\\)") | ||
| 821 | end t) | ||
| 822 | ;; show done items | ||
| 823 | (let ((prefix (propertize | ||
| 824 | (concat (if todos-number-prefix "1" todos-prefix) " ") | ||
| 825 | 'face 'todos-prefix-string)) | ||
| 826 | ov-done ov-pref) | ||
| 827 | (setq done (match-beginning 1)) | ||
| 828 | (narrow-to-region beg end) | ||
| 829 | (todos-prefix-overlays) | ||
| 830 | ;; add non-empty separator overlay in front of prefix overlay on | ||
| 831 | ;; first done item | ||
| 832 | (unless (string= done-sep todos-done-separator) | ||
| 833 | (goto-char done) | ||
| 834 | (remove-overlays done done) | ||
| 835 | ;; must make separator overlay after making prefix overlay to | ||
| 836 | ;; get the order separator before prefix | ||
| 837 | (setq ov-pref (make-overlay done done) | ||
| 838 | ov-done (make-overlay done done)) | ||
| 839 | (overlay-put ov-pref 'before-string prefix) | ||
| 840 | (overlay-put ov-done 'before-string done-sep))) | ||
| 841 | ;; (when (setq ov (car (overlays-in done done))) | ||
| 842 | ;; (when (equal (overlay-get ov 'before-string) done-sep) | ||
| 843 | ;; (push ov todos-done-overlays) | ||
| 844 | ;; (delete-overlay ov))) | ||
| 845 | (todos-category-select) | ||
| 846 | (error "No done items in this category")))))) | ||
| 847 | |||
| 848 | (defun todos-view-archive (&optional cat) | ||
| 849 | "" | ||
| 850 | (interactive) | ||
| 851 | (if (file-exists-p todos-archive-file) | ||
| 852 | (progn | ||
| 853 | (find-file todos-archive-file) | ||
| 854 | (if cat | ||
| 855 | (if (member cat (todos-categories-list (current-buffer))) | ||
| 856 | (todos-jump-to-category-noninteractively cat) | ||
| 857 | (error "No archived items from this category")) | ||
| 858 | (todos-category-select))) | ||
| 859 | (error "There is currently no Todos archive"))) | ||
| 860 | |||
| 861 | ;; FIXME: very slow | ||
| 862 | (defun todos-diary-items () | ||
| 863 | "Display all todo items marked for diary inclusion." | ||
| 864 | (interactive) | ||
| 865 | (let ((bufname "*Todo diary entries*") | ||
| 866 | opoint) | ||
| 867 | (save-restriction | ||
| 868 | (save-current-buffer | ||
| 869 | (widen) | ||
| 870 | (copy-to-buffer bufname (point-min) (point-max)))) | ||
| 871 | (with-current-buffer bufname | ||
| 872 | ;; (todos-mode) | ||
| 873 | (goto-char (point-min)) | ||
| 874 | (while (not (eobp)) | ||
| 875 | (setq opoint (point)) | ||
| 876 | (cond ((looking-at "\\[") | ||
| 877 | (progn | ||
| 878 | (todos-forward-item) | ||
| 879 | (if (string-match | ||
| 880 | (concat "^" (regexp-quote todos-category-beg) ".*$") | ||
| 881 | (buffer-substring opoint (point))) | ||
| 882 | (kill-region opoint (+ opoint (match-beginning 0))) | ||
| 883 | (kill-region opoint (point))))) | ||
| 884 | ((looking-at "^$") | ||
| 885 | (kill-line)) | ||
| 886 | (t | ||
| 887 | (todos-forward-item)))) | ||
| 888 | (goto-char (point-min)) | ||
| 889 | (while (not (eobp)) | ||
| 890 | (setq opoint (point)) | ||
| 891 | (if (looking-at (regexp-quote todos-category-beg)) | ||
| 892 | (when (progn (forward-line) | ||
| 893 | (or (looking-at (regexp-quote todos-category-beg)) | ||
| 894 | ;; category has done but no unfinished items | ||
| 895 | (and (looking-at "^$") (forward-line)) | ||
| 896 | (eobp))) | ||
| 897 | (kill-region opoint (point))) | ||
| 898 | (forward-line))) | ||
| 899 | (make-local-variable 'font-lock-defaults) | ||
| 900 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | ||
| 901 | (font-lock-fontify-buffer) | ||
| 902 | (setq buffer-read-only t)) | ||
| 903 | (display-buffer bufname))) | ||
| 752 | 904 | ||
| 753 | (defun todos-edit-item () | 905 | (defun todos-highlight-item () |
| 754 | "Edit current TODO list entry." | 906 | "Highlight the todo item the cursor is on." |
| 755 | (interactive) | 907 | (interactive) |
| 756 | (let ((item (todos-item-string))) | 908 | (if hl-line-mode ; todos-highlight-item |
| 757 | (if (todos-string-multiline-p item) | 909 | (hl-line-mode 0) |
| 758 | (todos-edit-multiline) | 910 | (hl-line-mode 1))) |
| 759 | (let ((new (read-from-minibuffer "Edit: " item))) | ||
| 760 | (todos-remove-item) | ||
| 761 | (insert new todos-item-end "\n") | ||
| 762 | (todos-backward-item) | ||
| 763 | (if todos-number-prefix | ||
| 764 | (todos-update-numbered-prefix) | ||
| 765 | (todos-item-overlays)))))) | ||
| 766 | 911 | ||
| 767 | ;; FIXME to work with overlays | 912 | ;; FIXME: make this a customizable option for whole Todos file |
| 768 | (defun todos-edit-multiline () | 913 | (defun todos-toggle-display-date-time () |
| 769 | "Set up a buffer for editing a multiline TODO list entry." | 914 | "" ; FIXME |
| 770 | (interactive) | 915 | (interactive) |
| 771 | (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) | 916 | (save-excursion |
| 772 | (switch-to-buffer | 917 | (goto-char (point-min)) |
| 773 | (make-indirect-buffer | 918 | (let ((ovs (overlays-in (point) (line-end-position))) |
| 774 | (file-name-nondirectory todos-file-do) buffer-name)) | 919 | ov hidden) |
| 775 | (message "To exit, simply kill this buffer and return to list.") | 920 | (while ovs |
| 776 | (todos-edit-mode) | 921 | (setq ov (car ovs)) |
| 777 | (narrow-to-region (todos-item-start) (todos-item-end)))) | 922 | (if (equal (overlay-get ov 'display) "") |
| 923 | (setq ovs nil | ||
| 924 | hidden t) | ||
| 925 | (setq ovs (cdr ovs)))) | ||
| 926 | (if hidden (remove-overlays (point-min) (point-max) 'display "") | ||
| 927 | (while (not (eobp)) | ||
| 928 | (re-search-forward (concat "^\\[?" todos-date-pattern | ||
| 929 | " \\(" diary-time-regexp "\\)?\\]? ") | ||
| 930 | ; FIXME: this space in header? ^ | ||
| 931 | nil t) | ||
| 932 | (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) | ||
| 933 | (overlay-put ov 'display "") | ||
| 934 | (forward-line))) | ||
| 935 | (todos-update-numbered-prefix)))) | ||
| 936 | |||
| 937 | ;;;###autoload | ||
| 938 | (defun todos-top-priorities (&optional nof-priorities category-pr-page show-done) | ||
| 939 | "List top priorities for each category. | ||
| 940 | |||
| 941 | Number of entries for each category is given by NOF-PRIORITIES which | ||
| 942 | defaults to \'todos-show-priorities\'. | ||
| 943 | |||
| 944 | If CATEGORY-PR-PAGE is non-nil, a page separator \'^L\' is inserted | ||
| 945 | between each category. | ||
| 946 | |||
| 947 | With non-nil SHOW-DONE, include done items in the listing." | ||
| 948 | |||
| 949 | (interactive "P") | ||
| 950 | (or nof-priorities (setq nof-priorities todos-show-priorities)) | ||
| 951 | (if (listp nof-priorities) ;universal argument | ||
| 952 | (setq nof-priorities (car nof-priorities))) | ||
| 953 | (let ((todos-print-buffer-name todos-tmp-buffer-name) | ||
| 954 | (todos-category-break (if category-pr-page "" "")) | ||
| 955 | beg end done) | ||
| 956 | (save-excursion | ||
| 957 | (todos-show)) | ||
| 958 | (save-restriction | ||
| 959 | (save-current-buffer | ||
| 960 | (widen) | ||
| 961 | (if (buffer-live-p (get-buffer todos-print-buffer-name)) | ||
| 962 | (kill-buffer todos-print-buffer-name)) | ||
| 963 | (copy-to-buffer todos-print-buffer-name (point-min) (point-max)))) | ||
| 964 | (with-current-buffer todos-print-buffer-name | ||
| 965 | (goto-char (point-min)) | ||
| 966 | (while (re-search-forward ;Find category start | ||
| 967 | (concat "^" (regexp-quote todos-category-beg)) | ||
| 968 | nil t) | ||
| 969 | (setq beg (+ (line-end-position) 1)) ;Start of first entry. | ||
| 970 | (setq end (if (re-search-forward todos-category-beg nil t) | ||
| 971 | (match-beginning 0) | ||
| 972 | (point-max))) | ||
| 973 | (goto-char beg) | ||
| 974 | (setq done | ||
| 975 | (if (re-search-forward | ||
| 976 | (concat | ||
| 977 | (if (looking-at "^$") "" "\n") ; no unfinished items | ||
| 978 | "\n\\(\\[" (regexp-quote todos-done-string) "\\)") | ||
| 979 | end t) | ||
| 980 | (match-beginning 1) | ||
| 981 | end)) | ||
| 982 | (unless show-done | ||
| 983 | (delete-region done end) | ||
| 984 | (setq end done)) | ||
| 985 | (narrow-to-region beg end) ;In case we have too few entries. | ||
| 986 | (goto-char (point-min)) | ||
| 987 | (if (zerop nof-priorities) ;Traverse entries. | ||
| 988 | (goto-char end) ;All entries | ||
| 989 | (todos-forward-item nof-priorities)) | ||
| 990 | (setq beg (point)) | ||
| 991 | (delete-region beg end) | ||
| 992 | (widen)) | ||
| 993 | (and (looking-at "") (replace-match "")) ;Remove trailing form-feed. | ||
| 994 | (goto-char (point-min)) ;Due to display buffer | ||
| 995 | (make-local-variable 'font-lock-defaults) | ||
| 996 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | ||
| 997 | (font-lock-fontify-buffer) | ||
| 998 | (setq buffer-read-only t)) | ||
| 999 | ;; Could have used switch-to-buffer as it has a norecord argument, | ||
| 1000 | ;; which is nice when we are called from e.g. todos-print. | ||
| 1001 | ;; Else we could have used pop-to-buffer. | ||
| 1002 | ;; (display-buffer todos-print-buffer-name) | ||
| 1003 | (display-buffer todos-print-buffer-name) | ||
| 1004 | (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." | ||
| 1005 | todos-print-buffer-name))) | ||
| 1006 | |||
| 1007 | ;;; Editing | ||
| 778 | 1008 | ||
| 779 | ;;;###autoload | 1009 | ;;;###autoload |
| 780 | (defun todos-add-category (&optional cat) | 1010 | (defun todos-add-category (&optional cat) |
| 781 | "Add new category CAT to the TODO list." | 1011 | "Add new category CAT to the TODO list." |
| 782 | (interactive) | 1012 | (interactive) |
| 783 | (let ((buf (find-file-noselect todos-file-do t)) | 1013 | (let ((buffer-read-only) |
| 1014 | (buf (find-file-noselect todos-file-do t)) | ||
| 784 | (prompt "Category: ")) | 1015 | (prompt "Category: ")) |
| 785 | (unless (zerop (buffer-size buf)) | 1016 | (unless (zerop (buffer-size buf)) |
| 786 | (and (null todos-categories) | 1017 | (and (null todos-categories) |
| @@ -801,54 +1032,25 @@ In Todos mode it is made invisible with an overlay.") | |||
| 801 | (setq todos-categories (cons cat todos-categories)) | 1032 | (setq todos-categories (cons cat todos-categories)) |
| 802 | (widen) | 1033 | (widen) |
| 803 | (goto-char (point-min)) | 1034 | (goto-char (point-min)) |
| 804 | ;; (insert (format "%s\n%s%s\n%s\n" todos-category-sep todos-category-beg cat | 1035 | ;; make sure file does not begin with empty lines (shouldn't, but may be |
| 805 | ;; todos-category-end)) | 1036 | ;; added by mistake), otherwise new categories will contain them, so |
| 1037 | ;; won't be really empty | ||
| 1038 | (while (looking-at "^$") (kill-line)) | ||
| 806 | (insert todos-category-beg cat "\n") | 1039 | (insert todos-category-beg cat "\n") |
| 807 | (if (interactive-p) | 1040 | (if (interactive-p) |
| 808 | ;; properly display the newly added category | 1041 | ;; properly display the newly added category |
| 809 | (progn (setq todos-category-number 0) (todos-show)) | 1042 | (progn (setq todos-category-number 0) (todos-show)) |
| 810 | 0)))) | 1043 | 0)))) |
| 811 | 1044 | ||
| 812 | ;;;###autoload | 1045 | ;; FIXME: use function for category name choice here and in todos-add-category |
| 813 | (defun todos-add-item-non-interactively (new-item category) | 1046 | (defun todos-rename-category () |
| 814 | "Insert NEW-ITEM in TODO list as a new entry in CATEGORY." | ||
| 815 | ;; FIXME: really need this? (and in save-excursion?) | ||
| 816 | (save-excursion | ||
| 817 | (todos-show)) | ||
| 818 | ;; (save-excursion | ||
| 819 | (if (string= "" category) | ||
| 820 | (setq category (nth todos-category-number todos-categories))) | ||
| 821 | (let ((cat-exists (member category todos-categories))) | ||
| 822 | (setq todos-category-number | ||
| 823 | (if cat-exists | ||
| 824 | (- (length todos-categories) (length cat-exists)) | ||
| 825 | (todos-add-category category)))) | ||
| 826 | ;; FIXME: really need this? (yes for todos-move-item, to show moved to category) | ||
| 827 | (todos-show) | ||
| 828 | (setq todos-previous-line 0) | ||
| 829 | (let ((top 1) | ||
| 830 | (bottom (1+ (count-lines (point-min) (point-max))))) | ||
| 831 | (while (> (- bottom top) todos-insert-threshold) | ||
| 832 | (let* ((current (/ (+ top bottom) 2)) | ||
| 833 | (answer (if (< current bottom) | ||
| 834 | (todos-more-important-p current) nil))) | ||
| 835 | (if answer | ||
| 836 | (setq bottom current) | ||
| 837 | (setq top (1+ current))))) | ||
| 838 | (setq top (/ (+ top bottom) 2)) | ||
| 839 | ;; goto-line doesn't have the desired behavior in a narrowed buffer. | ||
| 840 | (goto-char (point-min)) | ||
| 841 | (forward-line (1- top))) | ||
| 842 | (todos-insert-with-overlays new-item) | ||
| 843 | ;; (todos-show-paren-hack) | ||
| 844 | );) | ||
| 845 | |||
| 846 | (defun todos-rename-category (new) | ||
| 847 | "Rename current Todos category." | 1047 | "Rename current Todos category." |
| 848 | (interactive "sCategory: ") | 1048 | (interactive) |
| 849 | (let ((cat (nth todos-category-number todos-categories)) | 1049 | (let* ((buffer-read-only) |
| 850 | (vec (vconcat todos-categories)) | 1050 | (cat (nth todos-category-number todos-categories)) |
| 851 | prompt) | 1051 | (vec (vconcat todos-categories)) |
| 1052 | (new (read-from-minibuffer (format "Rename category \"%s\" to: " cat))) | ||
| 1053 | prompt) | ||
| 852 | (while (and (cond ((string= "" new) | 1054 | (while (and (cond ((string= "" new) |
| 853 | (setq prompt "Enter a non-empty category name: ")) | 1055 | (setq prompt "Enter a non-empty category name: ")) |
| 854 | ((string-match "\\`\\s-+\\'" new) | 1056 | ((string-match "\\`\\s-+\\'" new) |
| @@ -860,12 +1062,8 @@ In Todos mode it is made invisible with an overlay.") | |||
| 860 | (setq todos-categories (append vec nil)) | 1062 | (setq todos-categories (append vec nil)) |
| 861 | (save-excursion | 1063 | (save-excursion |
| 862 | (widen) | 1064 | (widen) |
| 863 | (re-search-backward (concat ;(regexp-quote todos-category-sep) "\n" | 1065 | (re-search-backward (concat (regexp-quote todos-category-beg) "\\(" |
| 864 | (regexp-quote todos-category-beg) "\\(" | 1066 | (regexp-quote cat) "\\)\n") nil t) |
| 865 | (regexp-quote cat) "\\)\n") (point-min) t) | ||
| 866 | ;; (goto-char (match-end 0)) | ||
| 867 | ;; (when (looking-at (regexp-quote cat)) | ||
| 868 | ;; (replace-match new t)) | ||
| 869 | (replace-match new t t nil 1) | 1067 | (replace-match new t t nil 1) |
| 870 | (goto-char (point-min)) | 1068 | (goto-char (point-min)) |
| 871 | (setq mode-line-buffer-identification | 1069 | (setq mode-line-buffer-identification |
| @@ -879,161 +1077,242 @@ With ARG non-nil delete the category unconditionally, | |||
| 879 | i.e. including all existing entries." | 1077 | i.e. including all existing entries." |
| 880 | (interactive "P") | 1078 | (interactive "P") |
| 881 | (if (and (null arg) | 1079 | (if (and (null arg) |
| 1080 | ;; FIXME: what about done items? | ||
| 882 | (not (eq (point-max) (point-min)))) | 1081 | (not (eq (point-max) (point-min)))) |
| 883 | (message "This category is not empty, so it cannot be deleted") | 1082 | (message "To delete a non-empty category, call the command with a prefix argument.") |
| 884 | (let ((cat (nth todos-category-number todos-categories)) beg end) | 1083 | (let ((cat (nth todos-category-number todos-categories)) beg end) |
| 885 | (when (y-or-n-p (concat "Permanently remove category \"" cat | 1084 | (when (y-or-n-p (concat "Permanently remove category \"" cat |
| 886 | "\"" (and arg " and all its entries") "? ")) | 1085 | "\"" (and arg " and all its entries") "? ")) |
| 887 | (widen) | 1086 | (let ((buffer-read-only)) |
| 888 | (setq beg (re-search-backward | 1087 | (widen) |
| 889 | (concat "^" ;(regexp-quote todos-category-sep) "\n" | 1088 | (setq beg (re-search-backward (concat "^" (regexp-quote todos-category-beg) |
| 890 | (regexp-quote todos-category-beg) cat "\n") | 1089 | cat "\n") nil t) |
| 891 | (point-min) nil) | 1090 | end (progn |
| 892 | end (progn | 1091 | (re-search-forward (concat "\n\\(" |
| 893 | (re-search-forward | 1092 | (regexp-quote todos-category-beg) |
| 894 | ;; (concat "^" (regexp-quote todos-category-end) "\n"))) | 1093 | ".*\n\\)") nil t) |
| 895 | (concat "\n" (regexp-quote todos-category-beg) ".*\n") | 1094 | (match-beginning 1))) |
| 896 | (point-max) t) | 1095 | (remove-overlays beg end) |
| 897 | (match-beginning 0))) | 1096 | (kill-region beg end) |
| 898 | (remove-overlays beg end) | 1097 | (setq todos-categories (delete cat todos-categories)) |
| 899 | (kill-region beg end) | 1098 | (todos-category-select) |
| 900 | (setq todos-categories (delete cat todos-categories)) | 1099 | (message "Deleted category %s" cat)))))) |
| 901 | (todos-category-select) | ||
| 902 | (message "Deleted category %s" cat))))) | ||
| 903 | 1100 | ||
| 904 | (defcustom todos-categories-buffer "*TODOS Categories*" | 1101 | ;;;###autoload |
| 905 | "Name of buffer displayed by `todos-display-categories'" | 1102 | (defun todos-insert-item (&optional arg here date-time) ; FIXME revise docstring |
| 906 | :type 'string | 1103 | "Insert new TODO list item. |
| 907 | :group 'todos) | ||
| 908 | 1104 | ||
| 909 | (defun todos-display-categories () | 1105 | With prefix argument ARG solicit the category, otherwise use the |
| 910 | "Display an alphabetical list of clickable Todos category names. | 1106 | current category. |
| 911 | Click or type RET on a category name to go to it." | ||
| 912 | (interactive) | ||
| 913 | ;; (setq todos-window-configuration (current-window-configuration)) | ||
| 914 | (let ((categories (copy-sequence todos-categories)) | ||
| 915 | beg) | ||
| 916 | ;; alphabetize the list case insensitively | ||
| 917 | (setq categories (sort categories (lambda (s1 s2) (let ((cis1 (upcase s1)) | ||
| 918 | (cis2 (upcase s2))) | ||
| 919 | (string< cis1 cis2))))) | ||
| 920 | (require 'widget) | ||
| 921 | (eval-when-compile | ||
| 922 | (require 'wid-edit)) | ||
| 923 | (with-current-buffer (get-buffer-create todos-categories-buffer) | ||
| 924 | (switch-to-buffer (current-buffer)) | ||
| 925 | (erase-buffer) | ||
| 926 | (kill-all-local-variables) | ||
| 927 | (widget-insert "Press a button to display the corresponding category.\n\n") | ||
| 928 | (setq beg (point)) | ||
| 929 | (mapc (lambda (cat) | ||
| 930 | (widget-create 'push-button | ||
| 931 | :notify (lambda (widget &rest ignore) | ||
| 932 | (todos-jump-to-category-noninteractively | ||
| 933 | (widget-get widget :value))) | ||
| 934 | |||
| 935 | cat) | ||
| 936 | (widget-insert "\n")) | ||
| 937 | categories) | ||
| 938 | (use-local-map widget-keymap) | ||
| 939 | (widget-setup)))) | ||
| 940 | 1107 | ||
| 941 | (defun todos-jump-to-category-noninteractively (cat) | 1108 | With non-nil argument HERE insert the new item directly above the |
| 942 | (let ((name todos-categories-buffer)) | 1109 | item at point. If point is on an empty line, insert the new item |
| 943 | (if (string= (buffer-name) name) | 1110 | there. |
| 944 | (kill-buffer name))) | ||
| 945 | ;; (set-window-configuration todos-window-configuration) | ||
| 946 | (switch-to-buffer (file-name-nondirectory todos-file-do)) | ||
| 947 | (widen) | ||
| 948 | (goto-char (point-min)) | ||
| 949 | (setq todos-category-number (- (length todos-categories) | ||
| 950 | (length (member cat todos-categories)))) | ||
| 951 | (todos-category-select)) | ||
| 952 | 1111 | ||
| 953 | ;;;###autoload | 1112 | If the value of TIME is `omit', insert the item without a time |
| 954 | (defun todos-insert-item (arg) | 1113 | string; with the value `ask', solicit a time string; with any |
| 955 | "Insert new TODO list entry. | 1114 | other value, add or omit the current time in accordance with |
| 956 | With a prefix argument solicit the category, otherwise use the current | 1115 | `todos-add-time-string'." |
| 957 | category." | ||
| 958 | (interactive "P") | 1116 | (interactive "P") |
| 959 | ;; (save-excursion | 1117 | (unless (or (todos-done-item-p) |
| 1118 | (save-excursion (forward-line -1) (todos-done-item-p))) | ||
| 960 | (if (not (derived-mode-p 'todos-mode)) (todos-show)) | 1119 | (if (not (derived-mode-p 'todos-mode)) (todos-show)) |
| 961 | (let* ((new-item (concat (unless todos-include-in-diary "[") | 1120 | (let* ((buffer-read-only) |
| 962 | (todos-current-date todos-add-time-string) | 1121 | (date-string (cond ;; ((eq date-time 'omit) "") |
| 1122 | ((eq date-time 'ask) | ||
| 1123 | (read-from-minibuffer "Enter a date: ")) | ||
| 1124 | ((eq date-time 'to-date) | ||
| 1125 | (with-current-buffer "*Calendar*" | ||
| 1126 | (calendar-date-string (calendar-cursor-to-date t) t t))) | ||
| 1127 | (t (calendar-date-string (calendar-current-date) t t)))) | ||
| 1128 | (time-string (if todos-add-time-string | ||
| 1129 | (cond ((eq date-time 'omit) "") | ||
| 1130 | ((eq date-time 'ask) | ||
| 1131 | (read-from-minibuffer "Enter a clock time: ")) | ||
| 1132 | (t (substring (current-time-string) 11 16))) | ||
| 1133 | "")) | ||
| 1134 | (new-item (concat (unless todos-include-in-diary "[") | ||
| 1135 | date-string (unless (string= time-string "") | ||
| 1136 | (concat " " time-string)) | ||
| 963 | (unless todos-include-in-diary "]") " " | 1137 | (unless todos-include-in-diary "]") " " |
| 964 | (read-from-minibuffer "New TODO entry: "))) | 1138 | (read-from-minibuffer "New TODO entry: "))) |
| 965 | (current-category (nth todos-category-number todos-categories)) | 1139 | (current-category (nth todos-category-number todos-categories)) |
| 966 | (category (if arg (todos-completing-read) current-category))) | 1140 | (category (if arg (todos-completing-read) current-category))) |
| 967 | (todos-add-item-non-interactively new-item category)));) | 1141 | (if here |
| 1142 | (todos-insert-with-overlays new-item) | ||
| 1143 | (todos-add-item-non-interactively new-item category))))) | ||
| 968 | 1144 | ||
| 969 | (defun todos-insert-item-here () | 1145 | (defun todos-insert-item-here (&optional date-time) |
| 970 | "Insert a new TODO list entry directly above the entry at point. | 1146 | "" ;FIXME add docstring |
| 971 | If point is on an empty line, insert the entry there." | ||
| 972 | (interactive) | 1147 | (interactive) |
| 973 | (if (not (derived-mode-p 'todos-mode)) (todos-show)) | 1148 | (todos-insert-item nil t date-time)) |
| 974 | (let ((new (concat (unless todos-include-in-diary "[") | ||
| 975 | (todos-current-date todos-add-time-string) | ||
| 976 | (unless todos-include-in-diary "]") " " | ||
| 977 | (read-from-minibuffer "New TODO entry: ")))) | ||
| 978 | (todos-insert-with-overlays new))) | ||
| 979 | 1149 | ||
| 980 | (defun todos-insert-with-overlays (item) | 1150 | (defun todos-insert-item-no-time (&optional here) |
| 981 | "" ;FIXME add docstring | 1151 | "" ;FIXME add docstring |
| 982 | (let (ov-start ov-end p1 p2) | 1152 | (interactive) |
| 983 | (unless (and (bolp) (eolp)) (goto-char (todos-item-start))) | 1153 | (todos-insert-item nil here 'omit)) |
| 984 | (insert item todos-item-end "\n") | 1154 | |
| 985 | (todos-backward-item) | 1155 | (defun todos-insert-item-ask-date (&optional here) |
| 986 | (if todos-number-prefix | 1156 | "" ;FIXME add docstring |
| 987 | (todos-update-numbered-prefix) | 1157 | (interactive) |
| 988 | (todos-item-overlays)))) | 1158 | (todos-insert-item nil here 'ask)) |
| 989 | 1159 | ||
| 990 | (defun todos-more-important-p (line) | 1160 | (defun todos-insert-item-for-diary (&optional arg here date-time) |
| 991 | "Ask whether entry is more important than the one at LINE." | 1161 | "" ;FIXME |
| 992 | (unless (equal todos-previous-line line) | 1162 | (interactive "P") |
| 993 | (setq todos-previous-line line) | 1163 | (let ((todos-include-in-diary t)) |
| 994 | (goto-char (point-min)) | 1164 | (todos-insert-item arg here date-time))) |
| 995 | (forward-line (1- todos-previous-line)) | 1165 | |
| 996 | (let ((item (todos-item-string-start))) | 1166 | ;; FIXME: autoload when key-binding is defined in calendar.el |
| 997 | (setq todos-previous-answer | 1167 | (defun todos-insert-item-from-calendar () |
| 998 | (y-or-n-p (concat "More important than '" item "'? "))))) | 1168 | "" ;FIXME |
| 999 | todos-previous-answer) | 1169 | (interactive) |
| 1170 | (pop-to-buffer (file-name-nondirectory todos-file-do)) | ||
| 1171 | (todos-show) | ||
| 1172 | (todos-insert-item t nil 'to-date)) | ||
| 1173 | |||
| 1174 | ;; FIXME: calendar is loaded before todos | ||
| 1175 | ;; (add-hook 'calendar-load-hook | ||
| 1176 | ;; (lambda () | ||
| 1177 | (define-key calendar-mode-map "it" 'todos-insert-item-from-calendar);)) | ||
| 1000 | 1178 | ||
| 1001 | (defun todos-delete-item () | 1179 | (defun todos-delete-item () |
| 1002 | "Delete current TODO list entry." | 1180 | "Delete current TODO list entry." |
| 1003 | (interactive) | 1181 | (interactive) |
| 1004 | (if (> (count-lines (point-min) (point-max)) 0) | 1182 | (if (> (count-lines (point-min) (point-max)) 0) |
| 1005 | (let* ((todos-entry (todos-item-string-start)) | 1183 | (let* ((buffer-read-only) |
| 1184 | (todos-entry (todos-item-string-start)) | ||
| 1006 | (todos-answer (y-or-n-p (concat "Permanently remove '" | 1185 | (todos-answer (y-or-n-p (concat "Permanently remove '" |
| 1007 | todos-entry "'? ")))) | 1186 | todos-entry "'? ")))) |
| 1008 | (when todos-answer | 1187 | (when todos-answer |
| 1009 | (todos-remove-item) | 1188 | (todos-remove-item) |
| 1010 | (when (and (bolp) (eolp)) (todos-backward-item)) | 1189 | (when (and (bolp) (eolp) |
| 1190 | ;; not if last item was deleted | ||
| 1191 | (< (point-min) (point-max))) | ||
| 1192 | (todos-backward-item)) | ||
| 1193 | ;; FIXME: is todos-prefix-overlays part of if-sexp, and is it needed | ||
| 1194 | ;; at all? | ||
| 1011 | (if todos-number-prefix | 1195 | (if todos-number-prefix |
| 1012 | (todos-update-numbered-prefix) | 1196 | (todos-update-numbered-prefix) |
| 1013 | (todos-item-overlays)))) | 1197 | (todos-prefix-overlays)))) |
| 1014 | (error "No TODO list entry to delete"))) | 1198 | (error "No TODO list entry to delete"))) |
| 1015 | 1199 | ||
| 1200 | (defun todos-edit-item () | ||
| 1201 | "Edit current TODO list entry." | ||
| 1202 | (interactive) | ||
| 1203 | (let ((buffer-read-only) | ||
| 1204 | (item (todos-item-string)) | ||
| 1205 | (opoint (point))) | ||
| 1206 | (if (todos-string-multiline-p item) | ||
| 1207 | (todos-edit-multiline) | ||
| 1208 | (let ((new (read-from-minibuffer "Edit: " item))) | ||
| 1209 | (while (not (string-match (concat "^\\[?" todos-date-pattern) new)) | ||
| 1210 | (setq new (read-from-minibuffer "Item must start with a date: " new))) | ||
| 1211 | ;; If user moved point during editing, make sure it moves back. | ||
| 1212 | (goto-char opoint) | ||
| 1213 | (todos-remove-item) | ||
| 1214 | (todos-insert-with-overlays new))))) | ||
| 1215 | |||
| 1216 | ;; FIXME: run todos-check-format on exiting buffer (or check for date string | ||
| 1217 | ;; and indentation) | ||
| 1218 | (defun todos-edit-multiline () | ||
| 1219 | "Set up a buffer for editing a multiline TODO list entry." | ||
| 1220 | (interactive) | ||
| 1221 | (let ((buffer-name (generate-new-buffer-name todos-edit-buffer))) | ||
| 1222 | (switch-to-buffer | ||
| 1223 | (make-indirect-buffer | ||
| 1224 | (file-name-nondirectory todos-file-do) buffer-name)) | ||
| 1225 | (message "To exit, simply kill this buffer and return to list.") | ||
| 1226 | (todos-edit-mode) | ||
| 1227 | (narrow-to-region (todos-item-start) (todos-item-end)))) | ||
| 1228 | |||
| 1229 | (defun todos-edit-quit () | ||
| 1230 | "" ;FIXME | ||
| 1231 | (interactive) | ||
| 1232 | (save-excursion (todos-category-select))) | ||
| 1233 | |||
| 1234 | ;; FIXME | ||
| 1235 | (defun todos-change-date (&optional event) | ||
| 1236 | "" ;FIXME | ||
| 1237 | (interactive) | ||
| 1238 | (let (dmarker | ||
| 1239 | calendar-view-diary-initially-flag | ||
| 1240 | new-date) | ||
| 1241 | (save-excursion | ||
| 1242 | (todos-item-start) | ||
| 1243 | (setq dmarker (point-marker))) | ||
| 1244 | (calendar) | ||
| 1245 | (message "Put the cursor on the desired date in the Calendar and press `q'") | ||
| 1246 | (setq new-date | ||
| 1247 | (calendar-date-string (calendar-cursor-to-date t) t t)) | ||
| 1248 | ;; (pop-to-buffer (file-name-nondirectory todos-file-do)) | ||
| 1249 | ;; (todos-show) | ||
| 1250 | (when (eq last-command 'calendar-exit) | ||
| 1251 | (goto-char (marker-position dmarker)) | ||
| 1252 | (re-search-forward (concat "^\\[?\\(" todos-date-pattern "\\)\\]?") | ||
| 1253 | (line-end-position) t) | ||
| 1254 | (replace-match new-date nil nil nil 1)))) | ||
| 1255 | |||
| 1016 | (defun todos-raise-item () | 1256 | (defun todos-raise-item () |
| 1017 | "Raise priority of current entry." | 1257 | "Raise priority of current entry." |
| 1018 | (interactive) | 1258 | (interactive) |
| 1019 | (if (and (not (and (bolp) (eolp))) | 1259 | (unless (or (todos-done-item-p) |
| 1020 | (> (count-lines (point-min) (point)) 0)) | 1260 | (looking-at "^$")) ; between done and not done items |
| 1021 | (let ((item (todos-item-string))) | 1261 | (let (buffer-read-only) |
| 1022 | (todos-remove-item) | 1262 | (if (> (count-lines (point-min) (point)) 0) |
| 1023 | (todos-backward-item) | 1263 | (let ((item (todos-item-string))) |
| 1024 | (todos-insert-with-overlays item)) | 1264 | (todos-remove-item) |
| 1025 | (error "No TODO list entry to raise"))) | 1265 | (todos-backward-item) |
| 1266 | (todos-insert-with-overlays item)) | ||
| 1267 | (error "No TODO list entry to raise"))))) | ||
| 1026 | 1268 | ||
| 1027 | (defun todos-lower-item () | 1269 | (defun todos-lower-item () |
| 1028 | "Lower priority of current entry." | 1270 | "Lower priority of current entry." |
| 1029 | (interactive) | 1271 | (interactive) |
| 1030 | (if (> (count-lines (point) (point-max)) 1) | 1272 | (unless (or (todos-done-item-p) |
| 1031 | ;; Assume there is a final newline | 1273 | (looking-at "^$")) ; between done and not done items |
| 1032 | (let ((item (todos-item-string))) | 1274 | (let ((buffer-read-only) |
| 1033 | (todos-remove-item) | 1275 | (done (save-excursion |
| 1034 | (todos-forward-item) | 1276 | (if (re-search-forward (concat "\n\n\\\[" |
| 1035 | (todos-insert-with-overlays item)) | 1277 | (regexp-quote todos-done-string)) |
| 1036 | (error "No TODO list entry to lower"))) | 1278 | nil t) |
| 1279 | (match-beginning 0) | ||
| 1280 | (point-max))))) | ||
| 1281 | (if (> (count-lines (point) done) 1) | ||
| 1282 | ;; Assume there is a final newline | ||
| 1283 | (let ((item (todos-item-string)) | ||
| 1284 | opoint) | ||
| 1285 | (todos-remove-item) | ||
| 1286 | (todos-forward-item) | ||
| 1287 | (todos-insert-with-overlays item)) | ||
| 1288 | (error "No TODO list entry to lower"))))) | ||
| 1289 | |||
| 1290 | ;; FIXME: moves last not done item when point on empty line below it | ||
| 1291 | ;; (defun todos-move-item () | ||
| 1292 | ;; "Move the current todo item to another, interactively named, category. | ||
| 1293 | |||
| 1294 | ;; If the named category is not one of the current todo categories, then | ||
| 1295 | ;; it is created and the item becomes the first entry in that category." | ||
| 1296 | ;; (interactive) | ||
| 1297 | ;; (unless (or (todos-done-item-p) | ||
| 1298 | ;; (looking-at "^$")) ; between done and not done items | ||
| 1299 | ;; (let ((item (todos-item-string)) | ||
| 1300 | ;; (category (todos-completing-read)) | ||
| 1301 | ;; orig moved) | ||
| 1302 | ;; (setq (save-excursion (todos-item-start))) | ||
| 1303 | ;; (todos-remove-item) | ||
| 1304 | ;; ;; numbered prefix isn't cached (see todos-remove-item) so have to update | ||
| 1305 | ;; (if todos-number-prefix (todos-update-numbered-prefix)) | ||
| 1306 | ;; (setq chgr (prepare-change-group)) | ||
| 1307 | ;; ;; FIXME | ||
| 1308 | ;; (unwind-protect | ||
| 1309 | ;; (progn | ||
| 1310 | ;; (activate-change-group chgr) | ||
| 1311 | ;; (todos-add-item-non-interactively item category) | ||
| 1312 | ;; (setq moved t)) | ||
| 1313 | ;; (if moved | ||
| 1314 | ;; (accept-change-group chgr) | ||
| 1315 | ;; (cancel-change-group chgr)))))) | ||
| 1037 | 1316 | ||
| 1038 | (defun todos-move-item () | 1317 | (defun todos-move-item () |
| 1039 | "Move the current todo item to another, interactively named, category. | 1318 | "Move the current todo item to another, interactively named, category. |
| @@ -1041,111 +1320,175 @@ If point is on an empty line, insert the entry there." | |||
| 1041 | If the named category is not one of the current todo categories, then | 1320 | If the named category is not one of the current todo categories, then |
| 1042 | it is created and the item becomes the first entry in that category." | 1321 | it is created and the item becomes the first entry in that category." |
| 1043 | (interactive) | 1322 | (interactive) |
| 1044 | (let ((item (todos-item-string)) | 1323 | (unless (or (todos-done-item-p) |
| 1045 | (inhibit-quit t) | 1324 | (looking-at "^$")) ; between done and not done items |
| 1046 | (category (todos-completing-read))) | 1325 | (let ((buffer-read-only) |
| 1047 | (todos-remove-item) | 1326 | (oldnum todos-category-number) |
| 1048 | (todos-add-item-non-interactively item category))) | 1327 | (oldcat (nth todos-category-number todos-categories)) |
| 1049 | 1328 | (item (todos-item-string)) | |
| 1050 | (defun todos-file-item (&optional comment) | 1329 | (newcat (todos-completing-read)) |
| 1051 | "File the current TODO list entry away, annotated with an optional COMMENT." | 1330 | (opoint (point)) |
| 1052 | (interactive "sComment: ") | 1331 | (orig-mrk (save-excursion (todos-item-start) (point-marker))) |
| 1053 | (or (> (count-lines (point-min) (point-max)) 0) | 1332 | moved) |
| 1054 | (error "No TODO list entry to file away")) | 1333 | (todos-remove-item) |
| 1055 | (let ((time-stamp-format todos-time-string-format)) | 1334 | ;; numbered prefix isn't cached (see todos-remove-item) so have to update |
| 1056 | (when (and comment (> (length comment) 0)) | 1335 | (if todos-number-prefix (todos-update-numbered-prefix)) |
| 1057 | (goto-char (todos-item-end)) | 1336 | (unwind-protect |
| 1058 | (insert | 1337 | (progn |
| 1059 | (if (save-excursion (beginning-of-line) | 1338 | (todos-add-item-non-interactively item newcat) |
| 1060 | (looking-at (regexp-quote todos-prefix))) | 1339 | (setq moved t)) |
| 1061 | " " | 1340 | (unless moved |
| 1062 | "\n\t") | 1341 | (widen) |
| 1063 | "(" comment ")")) | 1342 | (goto-char orig-mrk) |
| 1064 | (goto-char (todos-item-end)) | 1343 | (todos-insert-with-overlays item) |
| 1065 | (insert " [" (nth todos-category-number todos-categories) "]") | 1344 | (setq todos-category-number oldnum) |
| 1066 | (goto-char (todos-item-start)) | 1345 | (todos-category-select) |
| 1067 | (let ((temp-point (point))) | 1346 | ;; FIXME: does this work? |
| 1068 | (if (looking-at (regexp-quote todos-prefix)) | 1347 | (goto-char opoint)) |
| 1069 | (replace-match (time-stamp-string)) | 1348 | (set-marker orig-mrk nil))))) |
| 1070 | ;; Standard prefix -> timestamp | 1349 | |
| 1071 | ;; Else prefix non-standard item start with timestamp | 1350 | ;; (defun todos-file-item (&optional comment) |
| 1072 | (insert (time-stamp-string))) | 1351 | ;; "File the current TODO list entry away, annotated with an optional COMMENT." |
| 1073 | (append-to-file temp-point (1+ (todos-item-end)) todos-file-done) | 1352 | ;; (interactive "sComment: ") |
| 1074 | (delete-region temp-point (1+ (todos-item-end)))) | 1353 | ;; (or (> (count-lines (point-min) (point-max)) 0) |
| 1075 | (todos-backward-item) | 1354 | ;; (error "No TODO list entry to file away")) |
| 1076 | (message ""))) | 1355 | ;; (let ((time-stamp-format todos-time-string-format)) |
| 1077 | 1356 | ;; (when (and comment (> (length comment) 0)) | |
| 1078 | (defun todos-highlight-item () | 1357 | ;; (goto-char (todos-item-end)) |
| 1079 | "Highlight the todo item the cursor is on." | 1358 | ;; (insert |
| 1359 | ;; (if (save-excursion (beginning-of-line) | ||
| 1360 | ;; (looking-at (regexp-quote todos-prefix))) | ||
| 1361 | ;; " " | ||
| 1362 | ;; "\n\t") | ||
| 1363 | ;; "(" comment ")")) | ||
| 1364 | ;; (goto-char (todos-item-end)) | ||
| 1365 | ;; (insert " [" (nth todos-category-number todos-categories) "]") | ||
| 1366 | ;; (goto-char (todos-item-start)) | ||
| 1367 | ;; (let ((temp-point (point))) | ||
| 1368 | ;; (if (looking-at (regexp-quote todos-prefix)) | ||
| 1369 | ;; (replace-match (time-stamp-string)) | ||
| 1370 | ;; ;; Standard prefix -> timestamp | ||
| 1371 | ;; ;; Else prefix non-standard item start with timestamp | ||
| 1372 | ;; (insert (time-stamp-string))) | ||
| 1373 | ;; (append-to-file temp-point (1+ (todos-item-end)) todos-file-done) | ||
| 1374 | ;; (delete-region temp-point (1+ (todos-item-end)))) | ||
| 1375 | ;; (todos-backward-item) | ||
| 1376 | ;; (message "")) | ||
| 1377 | |||
| 1378 | (defun todos-item-done () | ||
| 1379 | "Mark current item as done and move it to category's done section." | ||
| 1080 | (interactive) | 1380 | (interactive) |
| 1081 | (if hl-line-mode ; todos-highlight-item | 1381 | (unless (or (todos-done-item-p) |
| 1082 | (hl-line-mode 0) | 1382 | (looking-at "^$")) |
| 1083 | (hl-line-mode 1))) | 1383 | (let* ((buffer-read-only) |
| 1084 | ;; --------------------------------------------------------------------------- | 1384 | (item (todos-item-string)) |
| 1085 | 1385 | (date-string (calendar-date-string (calendar-current-date) t t)) | |
| 1086 | ;; Utility functions: | 1386 | (time-string (if todos-add-time-string |
| 1087 | 1387 | (concat " " (substring (current-time-string) 11 16)) | |
| 1088 | 1388 | "")) | |
| 1089 | ;;;###autoload | 1389 | (done-item (concat "[" todos-done-string date-string time-string "] " item)) |
| 1090 | (defun todos-top-priorities (&optional nof-priorities category-pr-page) | 1390 | (items-end (point-max)) |
| 1091 | "List top priorities for each category. | 1391 | next-cat) |
| 1092 | 1392 | (todos-remove-item) | |
| 1093 | Number of entries for each category is given by NOF-PRIORITIES which | 1393 | (save-excursion |
| 1094 | defaults to \'todos-show-priorities\'. | 1394 | (widen) |
| 1095 | 1395 | (setq next-cat | |
| 1096 | If CATEGORY-PR-PAGE is non-nil, a page separator \'^L\' is inserted | 1396 | (save-excursion |
| 1097 | between each category." | 1397 | (if (re-search-forward (concat "^" (regexp-quote todos-category-beg)) |
| 1398 | nil t) | ||
| 1399 | (match-beginning 0) | ||
| 1400 | (point-max)))) | ||
| 1401 | ;; insert next done item at the top of the done items list | ||
| 1402 | (if (re-search-forward (concat "^\\[" (regexp-quote todos-done-string)) | ||
| 1403 | next-cat t) | ||
| 1404 | (goto-char (match-beginning 0)) | ||
| 1405 | ;; need empty line between done and not done items in order not to have | ||
| 1406 | ;; hanging todos-prefix when done items are hidden | ||
| 1407 | (goto-char next-cat) | ||
| 1408 | (newline)) | ||
| 1409 | (todos-insert-with-overlays done-item))) | ||
| 1410 | (todos-show))) | ||
| 1098 | 1411 | ||
| 1099 | (interactive "P") | 1412 | (defun todos-archive-done-items () |
| 1100 | (or nof-priorities (setq nof-priorities todos-show-priorities)) | 1413 | "Archive the done items in the current category." |
| 1101 | (if (listp nof-priorities) ;universal argument | 1414 | (interactive) |
| 1102 | (setq nof-priorities (car nof-priorities))) | 1415 | (let ((archive (find-file-noselect todos-archive-file t)) |
| 1103 | (let ((todos-print-buffer-name todos-tmp-buffer-name) | 1416 | (cat (nth todos-category-number todos-categories)) |
| 1104 | ;;(todos-print-category-number 0) | 1417 | beg end) |
| 1105 | (todos-category-break (if category-pr-page "" "")) | 1418 | (save-excursion |
| 1106 | (cat-end | ||
| 1107 | (concat | ||
| 1108 | (if todos-remove-separator | ||
| 1109 | (concat todos-category-end "\n" | ||
| 1110 | (regexp-quote todos-prefix) " " todos-category-sep "\n") | ||
| 1111 | (concat todos-category-end "\n")))) | ||
| 1112 | beg end) | ||
| 1113 | (save-excursion ; FIXME: need this? | ||
| 1114 | (todos-show) | ||
| 1115 | (save-restriction | 1419 | (save-restriction |
| 1116 | (save-current-buffer | 1420 | (widen) |
| 1117 | (widen) | 1421 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) |
| 1118 | (copy-to-buffer todos-print-buffer-name (point-min) (point-max)) | 1422 | (setq end (or (match-beginning 0) (point-max))) |
| 1119 | (set-buffer todos-print-buffer-name) | 1423 | (re-search-backward (concat "^" (regexp-quote todos-category-beg) |
| 1120 | (goto-char (point-min)) | 1424 | (regexp-quote cat)) |
| 1121 | ;; (when (re-search-forward (regexp-quote todos-header) nil t) | 1425 | nil t) |
| 1122 | ;; (beginning-of-line 1) | 1426 | (if (not (re-search-forward (concat "\\[" (regexp-quote todos-done-string)) |
| 1123 | ;; (delete-region (point) (line-end-position))) | 1427 | nil t)) |
| 1124 | (while (re-search-forward ;Find category start | 1428 | (error "No done items in this category") |
| 1125 | (regexp-quote (concat todos-prefix todos-category-beg)) | 1429 | (setq beg (match-beginning 0)) |
| 1126 | nil t) | 1430 | (setq done (buffer-substring beg end)) |
| 1127 | (setq beg (+ (line-end-position) 1)) ;Start of first entry. | 1431 | (with-current-buffer archive |
| 1128 | (re-search-forward cat-end nil t) | ||
| 1129 | (setq end (match-beginning 0)) | ||
| 1130 | (replace-match todos-category-break) | ||
| 1131 | (narrow-to-region beg end) ;In case we have too few entries. | ||
| 1132 | (goto-char (point-min)) | 1432 | (goto-char (point-min)) |
| 1133 | (if (zerop nof-priorities) ;Traverse entries. | 1433 | (if (re-search-forward (regexp-quote (concat "^" todos-category-beg cat)) |
| 1134 | (goto-char end) ;All entries | 1434 | nil t) |
| 1135 | (todos-forward-item nof-priorities)) | 1435 | (forward-char) |
| 1136 | (setq beg (point)) | 1436 | (insert todos-category-beg cat "\n")) |
| 1137 | (delete-region beg end) | 1437 | (insert done)) |
| 1138 | (widen)) | 1438 | (delete-region beg end) |
| 1139 | (and (looking-at "") (replace-match "")) ;Remove trailing form-feed. | 1439 | (remove-overlays beg end) |
| 1140 | (goto-char (point-min)) ;Due to display buffer | 1440 | (kill-line -1))))) |
| 1141 | ;; FIXME: after todos-edit-multiline widening remains | 1441 | (message "Done items archived.")) |
| 1142 | ))) | 1442 | |
| 1143 | ;; Could have used switch-to-buffer as it has a norecord argument, | 1443 | ;; FIXME: undone item leaves item number overlay behind |
| 1144 | ;; which is nice when we are called from e.g. todos-print. | 1444 | (defun todos-item-undo () |
| 1145 | ;; Else we could have used pop-to-buffer. | 1445 | "" ;FIXME |
| 1146 | (display-buffer todos-print-buffer-name) | 1446 | (interactive) |
| 1147 | (message "Type C-x 1 to remove %s window. M-C-v to scroll the help." | 1447 | (when (todos-done-item-p) |
| 1148 | todos-print-buffer-name))) | 1448 | (let* ((buffer-read-only) |
| 1449 | (cat (nth todos-category-number todos-categories)) | ||
| 1450 | (start (progn | ||
| 1451 | (todos-item-start) | ||
| 1452 | (search-forward "] "))) ; end of done date string | ||
| 1453 | (item (buffer-substring start (todos-item-end)))) | ||
| 1454 | (todos-remove-item) | ||
| 1455 | (todos-add-item-non-interactively item cat)))) | ||
| 1456 | |||
| 1457 | (defun todos-toggle-item-diary-inclusion () | ||
| 1458 | "" ;FIXME add docstring | ||
| 1459 | (interactive) | ||
| 1460 | (save-excursion | ||
| 1461 | (let* ((buffer-read-only) | ||
| 1462 | (beg (todos-item-start)) | ||
| 1463 | (lim (save-excursion (todos-item-end))) | ||
| 1464 | (end (save-excursion | ||
| 1465 | (or (todos-time-string-match lim) | ||
| 1466 | (todos-date-string-match lim))))) | ||
| 1467 | (if (looking-at "\\[") ; FIXME use todos-exclusion-start | ||
| 1468 | (progn | ||
| 1469 | (replace-match "") | ||
| 1470 | (search-forward "]" (1+ end) t) ; FIXME use todos-exclusion-end | ||
| 1471 | (replace-match "")) | ||
| 1472 | (when end | ||
| 1473 | (insert "[") ; FIXME use todos-exclusion-start | ||
| 1474 | (goto-char (1+ end)) | ||
| 1475 | (insert "]")))))) ; FIXME use todos-exclusion-end | ||
| 1476 | |||
| 1477 | (defun todos-toggle-diary-inclusion (arg) | ||
| 1478 | "" ;FIXME add docstring | ||
| 1479 | (interactive "p") | ||
| 1480 | (save-excursion | ||
| 1481 | (save-restriction | ||
| 1482 | (when (eq arg 2) (widen)) ;FIXME: don't toggle done items | ||
| 1483 | (when (or (eq arg 1) (eq arg 2)) | ||
| 1484 | (goto-char (point-min)) | ||
| 1485 | (when (eq arg 2) | ||
| 1486 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 1487 | (forward-line) | ||
| 1488 | (when (looking-at (regexp-quote todos-category-end)) (forward-line))) | ||
| 1489 | (while (not (eobp)) | ||
| 1490 | (todos-toggle-item-diary-inclusion) | ||
| 1491 | (todos-forward-item)))))) | ||
| 1149 | 1492 | ||
| 1150 | (defun todos-save-top-priorities (&optional nof-priorities) | 1493 | (defun todos-save-top-priorities (&optional nof-priorities) |
| 1151 | "Save top priorities for each category in `todos-file-top'. | 1494 | "Save top priorities for each category in `todos-file-top'. |
| @@ -1169,47 +1512,359 @@ between each category. | |||
| 1169 | 1512 | ||
| 1170 | Number of entries for each category is given by `todos-print-priorities'." | 1513 | Number of entries for each category is given by `todos-print-priorities'." |
| 1171 | (interactive "P") | 1514 | (interactive "P") |
| 1172 | (save-window-excursion | 1515 | (when (yes-or-no-p "Print Todos list? ") |
| 1173 | (save-excursion | 1516 | (save-window-excursion |
| 1174 | (save-restriction | ||
| 1175 | (todos-top-priorities todos-print-priorities | ||
| 1176 | category-pr-page) | ||
| 1177 | (set-buffer todos-tmp-buffer-name) | ||
| 1178 | (and (funcall todos-print-function) | ||
| 1179 | (kill-this-buffer)) | ||
| 1180 | (message "Todo printing done."))))) | ||
| 1181 | |||
| 1182 | (defun todos-list-categories () | ||
| 1183 | "Return a list of the Todo mode categories." | ||
| 1184 | (let ((todos-buf (find-file-noselect todos-file-do)) | ||
| 1185 | categories) | ||
| 1186 | (with-current-buffer todos-buf | ||
| 1187 | (save-excursion | 1517 | (save-excursion |
| 1188 | (save-restriction | 1518 | (save-restriction |
| 1519 | (todos-top-priorities todos-print-priorities | ||
| 1520 | category-pr-page) | ||
| 1521 | (set-buffer todos-tmp-buffer-name) | ||
| 1522 | (and (funcall todos-print-function) | ||
| 1523 | (kill-this-buffer)) | ||
| 1524 | (message "Todo printing done.")))))) | ||
| 1525 | |||
| 1526 | ;; --------------------------------------------------------------------------- | ||
| 1527 | |||
| 1528 | ;;; Internal functions | ||
| 1529 | |||
| 1530 | ;; "Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|Sep\\|Oct\\|Nov\\|Dec") | ||
| 1531 | ;; (regexp-opt (list "1" "2" "3" "4" "5" "6" "7" "8" "9" "10" "11" "12" "13" "14" "15" "16" "17" "18" "19" "20" "21" "22" "23" "24" "25" "26" "27" "28" "29" "30" "31"))) | ||
| 1532 | |||
| 1533 | ;; FIXME: use diary-date-forms instead? | ||
| 1534 | ;; (defun todos-date-string () | ||
| 1535 | ;; "Return a regexp matching a diary date string." | ||
| 1536 | ;; (let ((month (regexp-opt (list "Jan" "Feb" "Mar" "Apr" "May" "Jun" | ||
| 1537 | ;; "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"))) | ||
| 1538 | ;; (day "[0-3]?[0-9]") | ||
| 1539 | ;; (year "[0-9]\\{4\\}")) | ||
| 1540 | ;; (concat month " " day ", " year))) | ||
| 1541 | |||
| 1542 | ;; FIXME: use diary-time-regexp | ||
| 1543 | ;; (defun todos-time-string () | ||
| 1544 | ;; "Return a regexp matching a diary time string." | ||
| 1545 | ;; "[0-9]?[0-9][:.][0-9]\\{2\\}") | ||
| 1546 | |||
| 1547 | (defvar todos-date-nodayname-pattern | ||
| 1548 | (let ((dayname) | ||
| 1549 | (monthname (format "\\(%s\\|\\*\\)" | ||
| 1550 | (diary-name-pattern calendar-month-name-array | ||
| 1551 | calendar-month-abbrev-array t))) | ||
| 1552 | (month "\\([0-9]+\\|\\*\\)") | ||
| 1553 | (day "\\([0-9]+\\|\\*\\)") | ||
| 1554 | (year "-?\\([0-9]+\\|\\*\\)")) | ||
| 1555 | (mapconcat 'eval calendar-date-display-form "")) | ||
| 1556 | "Regular expression matching a Todos date header without day name.") | ||
| 1557 | |||
| 1558 | ;; (defvar todos-dayname-pattern | ||
| 1559 | ;; (diary-name-pattern calendar-day-name-array nil t) | ||
| 1560 | ;; "Regular expression matching a day name in a Todos date header.") | ||
| 1561 | |||
| 1562 | (defvar todos-dayname-date-pattern | ||
| 1563 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) | ||
| 1564 | (concat dayname "\\(?:, " todos-date-nodayname-pattern "\\)?")) | ||
| 1565 | "Regular expression matching a Todos date header with day name.") | ||
| 1566 | |||
| 1567 | (defvar todos-date-pattern | ||
| 1568 | (concat "\\(?:" todos-date-nodayname-pattern "\\)\\|" | ||
| 1569 | "\\(?:" todos-date-dayname-pattern "\\)") | ||
| 1570 | "Regular expression matching a Todos date header.") | ||
| 1571 | |||
| 1572 | (defun todos-date-string-match (lim) | ||
| 1573 | "Find Todos date strings for font-locking." | ||
| 1574 | (re-search-forward (concat "^\\[?\\(" todos-date-pattern "\\)") lim t)) | ||
| 1575 | |||
| 1576 | (defun todos-time-string-match (lim) | ||
| 1577 | "Find Todos time strings for font-locking." | ||
| 1578 | (re-search-forward (concat "^\\[?\\(?:" todos-date-pattern "\\)" | ||
| 1579 | " \\(?1:" diary-time-regexp "\\)") lim t)) | ||
| 1580 | |||
| 1581 | (defun todos-done-string-match (lim) | ||
| 1582 | "Find Todos done headers for font-locking." | ||
| 1583 | (re-search-forward (concat "^\\[" (regexp-quote todos-done-string) "[^][]+]") | ||
| 1584 | lim t)) | ||
| 1585 | |||
| 1586 | (defun todos-category-string-match (lim) | ||
| 1587 | "Find Todos category headers for font-locking." | ||
| 1588 | (re-search-forward (concat "^" (regexp-quote todos-category-beg) ".*$") | ||
| 1589 | lim t)) | ||
| 1590 | |||
| 1591 | (defun todos-check-format () | ||
| 1592 | "Signal an error if the current Todos file is ill-formatted." | ||
| 1593 | (save-excursion | ||
| 1594 | (save-restriction | ||
| 1595 | (widen) | ||
| 1596 | (goto-char (point-min)) | ||
| 1597 | (let ((legit (concat "^\\(" (regexp-quote todos-category-beg) "\\)" | ||
| 1598 | "\\|\\(\\[?" todos-date-pattern "\\)" | ||
| 1599 | "\\|\\([ \t]+[^ \t]*\\)" | ||
| 1600 | "\\|$"))) | ||
| 1601 | (while (not (eobp)) | ||
| 1602 | (unless (looking-at legit) | ||
| 1603 | (error "Illegitimate Todos file format at line %d" | ||
| 1604 | (line-number-at-pos (point)))) | ||
| 1605 | (forward-line))))) | ||
| 1606 | (message "This Todos file is well-formatted.")) | ||
| 1607 | |||
| 1608 | (defun todos-wrap-and-indent () | ||
| 1609 | "" ;FIXME | ||
| 1610 | (make-local-variable 'word-wrap) | ||
| 1611 | (setq word-wrap t) | ||
| 1612 | (make-local-variable 'wrap-prefix) | ||
| 1613 | (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32)) | ||
| 1614 | (unless (member '(continuation) fringe-indicator-alist) | ||
| 1615 | (push '(continuation) fringe-indicator-alist))) | ||
| 1616 | |||
| 1617 | (defun todos-reset-prefix (symbol value) | ||
| 1618 | "Set SYMBOL's value to VALUE, and ." ; FIXME | ||
| 1619 | (let ((oldvalue (symbol-value symbol))) | ||
| 1620 | (custom-set-default symbol value) | ||
| 1621 | (when (not (equal value oldvalue)) | ||
| 1622 | (save-window-excursion | ||
| 1623 | (todos-show) | ||
| 1624 | (save-excursion | ||
| 1189 | (widen) | 1625 | (widen) |
| 1190 | (goto-char (point-max)) | 1626 | (goto-char (point-min)) |
| 1191 | (while (re-search-backward | 1627 | (while (not (eobp)) |
| 1192 | ;; (concat "^" (regexp-quote (concat todos-prefix todos-category-beg)) | 1628 | (remove-overlays (point) (point)); 'before-string prefix) |
| 1193 | (concat "^" ;(regexp-quote todos-category-sep) "\n" | 1629 | (forward-line))) |
| 1194 | (regexp-quote todos-category-beg) | 1630 | ;; activate the prefix setting (save-restriction does not help) |
| 1195 | "\\(.*\\)\n") | 1631 | (todos-show))))) |
| 1196 | (point-min) t) | ||
| 1197 | (push (match-string-no-properties 1) categories))))) | ||
| 1198 | categories)) | ||
| 1199 | 1632 | ||
| 1200 | (defun todos-jump-to-category () | 1633 | ;; FIXME: rename and/or rewrite |
| 1201 | "Jump to a category. Default is previous category." | 1634 | (defun todos-update-numbered-prefix () |
| 1202 | (interactive) | 1635 | "Update consecutive item numbering in the current category." |
| 1203 | (let ((category (todos-completing-read))) | 1636 | (save-excursion |
| 1204 | (if (string= "" category) | 1637 | (goto-char (point-min)) |
| 1205 | (setq category (nth todos-category-number todos-categories))) | 1638 | (while (not (eobp)) |
| 1206 | (setq todos-category-number | 1639 | (remove-overlays (point) (point) 'before-string) |
| 1207 | (if (member category todos-categories) | 1640 | (todos-forward-item)) |
| 1208 | (- (length todos-categories) | ||
| 1209 | (length (member category todos-categories))) | ||
| 1210 | (todos-add-category category))) | ||
| 1211 | (todos-show))) | 1641 | (todos-show))) |
| 1212 | 1642 | ||
| 1643 | (defvar todos-item-start-overlays nil "") | ||
| 1644 | |||
| 1645 | ;; (defvar todos-done-overlays nil "") | ||
| 1646 | |||
| 1647 | ;; (defun todos-check-overlay (prop) | ||
| 1648 | ;; "" ;FIXME add docstring | ||
| 1649 | ;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))) | ||
| 1650 | ;; (let ((ovlist (overlays-in (point) (point)))) | ||
| 1651 | ;; (when ovlist (overlay-get (car ovlist) prop)))) | ||
| 1652 | |||
| 1653 | (defun todos-prefix-overlays () | ||
| 1654 | "" ;FIXME add docstring | ||
| 1655 | (when (or todos-number-prefix | ||
| 1656 | (not (string-match "^[[:space:]]*$" todos-prefix))) | ||
| 1657 | (let ((prefix (propertize (concat todos-prefix " ") 'face 'todos-prefix-string)) | ||
| 1658 | (num 0) | ||
| 1659 | lim ov-pref) | ||
| 1660 | (save-excursion | ||
| 1661 | (goto-char (point-min)) | ||
| 1662 | (while (or (todos-date-string-match lim) | ||
| 1663 | (todos-done-string-match lim)) | ||
| 1664 | (goto-char (match-beginning 0)) | ||
| 1665 | (when todos-number-prefix | ||
| 1666 | (setq num (1+ num)) | ||
| 1667 | ;; reset number for done items | ||
| 1668 | (if (or (looking-at (concat "\n\\[" (regexp-quote todos-done-string))) | ||
| 1669 | ;; if last not done item is multiline, then | ||
| 1670 | ;; todos-done-string-match skips empty line, so have | ||
| 1671 | ;; to look back | ||
| 1672 | (and (looking-at (concat "^\\[" (regexp-quote todos-done-string))) | ||
| 1673 | (looking-back "\n\n"))) | ||
| 1674 | (setq num 1)) | ||
| 1675 | (setq prefix (propertize (concat (number-to-string num) " ") | ||
| 1676 | 'face 'todos-prefix-string))) | ||
| 1677 | (or (and (setq ov-pref (car (overlays-in (point) (point)))) | ||
| 1678 | (equal (overlay-get ov-pref 'before-string) prefix)) | ||
| 1679 | (and (setq ov-pref (pop todos-item-start-overlays)) | ||
| 1680 | (move-overlay ov-pref (point) (point))) | ||
| 1681 | (and (setq ov-pref (make-overlay (point) (point))) | ||
| 1682 | (overlay-put ov-pref 'before-string prefix))) | ||
| 1683 | (forward-line)))))) | ||
| 1684 | |||
| 1685 | ;; (defun todos-show-paren-hack () | ||
| 1686 | ;; "Purge overlay duplication due to show-paren-mode." | ||
| 1687 | ;; (save-excursion | ||
| 1688 | ;; (when show-paren-mode | ||
| 1689 | ;; (goto-char (point-min)) | ||
| 1690 | ;; (while (not (eobp)) | ||
| 1691 | ;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point)))) | ||
| 1692 | ;; (let ((ovlist (overlays-in (point) (point))) | ||
| 1693 | ;; ov) | ||
| 1694 | ;; (while (> (length ovlist) 1) | ||
| 1695 | ;; (setq ov (pop ovlist)) | ||
| 1696 | ;; (delete-overlay ov))) | ||
| 1697 | ;; (forward-line)) | ||
| 1698 | ;; (if (and (bolp) (eolp)) | ||
| 1699 | ;; ;; (let ((ovlist (overlays-in (1- (point)) (1+ (point))))) | ||
| 1700 | ;; (let ((ovlist (overlays-in (point) (point)))) | ||
| 1701 | ;; (remove-overlays (1- (point)) (1+ (point)))))))) | ||
| 1702 | |||
| 1703 | (defun todos-reset-separator (symbol value) | ||
| 1704 | "Set SYMBOL's value to VALUE, and ." ; FIXME | ||
| 1705 | (let ((oldvalue (symbol-value symbol))) | ||
| 1706 | (custom-set-default symbol value) | ||
| 1707 | ;; (setq todos-done-overlays nil) | ||
| 1708 | (when (not (equal value oldvalue)) | ||
| 1709 | (save-window-excursion | ||
| 1710 | (todos-show) | ||
| 1711 | (save-excursion | ||
| 1712 | (goto-char (point-min)) | ||
| 1713 | (when (re-search-forward (concat "^\\[" (regexp-quote todos-done-string)) nil t) | ||
| 1714 | (remove-overlays (point) (point)))) | ||
| 1715 | ;; activate the prefix setting (save-restriction does not help) | ||
| 1716 | (todos-show))))) | ||
| 1717 | |||
| 1718 | ;; FIXME: use this; should be defsubst? | ||
| 1719 | (defun todos-current-category () | ||
| 1720 | "Return the name of the current category." | ||
| 1721 | (nth todos-category-number todos-categories)) | ||
| 1722 | |||
| 1723 | (defun todos-category-select () | ||
| 1724 | "Make TODO mode display the current category correctly." | ||
| 1725 | (let ((name (nth todos-category-number todos-categories))) | ||
| 1726 | (setq mode-line-buffer-identification (concat "Category: " name)) | ||
| 1727 | (widen) | ||
| 1728 | (goto-char (point-min)) | ||
| 1729 | (search-forward-regexp | ||
| 1730 | (concat "^" (regexp-quote (concat todos-category-beg name)) | ||
| 1731 | "$")) | ||
| 1732 | (let ((begin (1+ (line-end-position))) | ||
| 1733 | (end (or (and (re-search-forward (concat "^" todos-category-beg) nil t) | ||
| 1734 | (match-beginning 0)) | ||
| 1735 | (point-max)))) | ||
| 1736 | (narrow-to-region begin end) | ||
| 1737 | (goto-char (point-min)))) | ||
| 1738 | (todos-prefix-overlays) | ||
| 1739 | (let ((beg (point-min)) | ||
| 1740 | (done-sep (if (string-match "^[[:space:]]*$" todos-done-separator) | ||
| 1741 | todos-done-separator | ||
| 1742 | (propertize (concat todos-done-separator "\n") | ||
| 1743 | 'face 'todos-done-sep))) | ||
| 1744 | done ov) | ||
| 1745 | (when (re-search-forward (concat "\n\\(\\[" (regexp-quote todos-done-string) | ||
| 1746 | "\\)") nil t) | ||
| 1747 | (setq done (match-beginning 1) | ||
| 1748 | end (match-beginning 0)) | ||
| 1749 | (if todos-show-with-done | ||
| 1750 | ;; with an empty separator just display the done items | ||
| 1751 | (if (string= done-sep todos-done-separator) | ||
| 1752 | (narrow-to-region (point-min) (point-max)) | ||
| 1753 | ;; else display the separator in an overlay in front of the prefix | ||
| 1754 | ;; overlay on first done item | ||
| 1755 | (let ((prefix (propertize | ||
| 1756 | (concat (if todos-number-prefix "1" todos-prefix) " ") | ||
| 1757 | 'face 'todos-prefix-string))) | ||
| 1758 | (goto-char done) | ||
| 1759 | (remove-overlays done done) | ||
| 1760 | ;; must make separator overlay after making prefix overlay to get | ||
| 1761 | ;; the order separator before prefix | ||
| 1762 | (setq ov-pref (make-overlay done done) | ||
| 1763 | ov-done (make-overlay done done)) | ||
| 1764 | (overlay-put ov-pref 'before-string prefix) | ||
| 1765 | (overlay-put ov-done 'before-string done-sep))) | ||
| 1766 | ;; hide done items | ||
| 1767 | (narrow-to-region (point-min) end)))) | ||
| 1768 | (goto-char (point-min))) | ||
| 1769 | |||
| 1770 | ;; FIXME: using numbering for priority instead of importance? | ||
| 1771 | ;;;###autoload | ||
| 1772 | (defun todos-add-item-non-interactively (new-item category) | ||
| 1773 | "Insert NEW-ITEM in TODO list as a new entry in CATEGORY." | ||
| 1774 | ;; FIXME: really need this? (and in save-excursion?) | ||
| 1775 | (save-excursion | ||
| 1776 | (todos-show)) | ||
| 1777 | (if (string= "" category) | ||
| 1778 | (setq category (nth todos-category-number todos-categories))) | ||
| 1779 | (let ((cat-exists (member category todos-categories))) | ||
| 1780 | (setq todos-category-number | ||
| 1781 | (if cat-exists | ||
| 1782 | (- (length todos-categories) (length cat-exists)) | ||
| 1783 | (todos-add-category category)))) | ||
| 1784 | ;; FIXME: really need this? (yes for todos-move-item, to show moved to category) | ||
| 1785 | (todos-show) ; now at point-min | ||
| 1786 | ;; (setq todos-previous-line 0) | ||
| 1787 | ;; (let* ((top 1) | ||
| 1788 | ;; (end (save-excursion | ||
| 1789 | ;; (goto-char (point-min)) | ||
| 1790 | ;; (if (re-search-forward (concat "\n\n\\\(\\[" | ||
| 1791 | ;; (regexp-quote todos-done-string) | ||
| 1792 | ;; "\\)") nil t) | ||
| 1793 | ;; (match-beginning 1) | ||
| 1794 | ;; (point-max)))) | ||
| 1795 | ;; (bottom (count-lines (point-min) end))) | ||
| 1796 | ;; (while (> (- bottom top) todos-insert-threshold) | ||
| 1797 | ;; (let* ((current (/ (+ top bottom) 2)) | ||
| 1798 | ;; (answer (if (< current bottom) | ||
| 1799 | ;; (todos-more-important-p current) nil))) | ||
| 1800 | ;; (if answer | ||
| 1801 | ;; (setq bottom current) | ||
| 1802 | ;; (setq top (1+ current))))) | ||
| 1803 | ;; (setq top (/ (+ top bottom) 2)) | ||
| 1804 | ;; (goto-char (point-min)) | ||
| 1805 | ;; (forward-line (1- top))) | ||
| 1806 | (unless (or (eq (point-min) (point-max)) ; no unfinished items | ||
| 1807 | (when (re-search-forward (concat "^\\[" | ||
| 1808 | (regexp-quote todos-done-string)) | ||
| 1809 | nil t) | ||
| 1810 | (forward-line -1) | ||
| 1811 | (bobp))) ; there are done items but no unfinished items | ||
| 1812 | (let* ((num-items (1+ (car (todos-count-items-in-category)))) | ||
| 1813 | (priority (string-to-number (read-from-minibuffer | ||
| 1814 | (format "Set item priority (1-%d): " | ||
| 1815 | num-items)))) | ||
| 1816 | prompt) | ||
| 1817 | (while (cond ((not (integerp priority)) | ||
| 1818 | (setq prompt "Priority must be an integer.\n")) | ||
| 1819 | ((< priority 1) | ||
| 1820 | (setq prompt "Priority cannot be higher than 1.\n")) | ||
| 1821 | ((> priority num-items) | ||
| 1822 | (setq prompt (format "Priority cannot be lower than %d.\n" | ||
| 1823 | num-items)))) | ||
| 1824 | (setq priority | ||
| 1825 | (string-to-number (read-from-minibuffer | ||
| 1826 | (concat prompt | ||
| 1827 | (format "Set item priority (1-%d): " | ||
| 1828 | num-items)))))) | ||
| 1829 | (goto-char (point-min)) | ||
| 1830 | (todos-forward-item (1- priority)))) | ||
| 1831 | (todos-insert-with-overlays new-item)) | ||
| 1832 | |||
| 1833 | (defun todos-jump-to-category-noninteractively (cat) | ||
| 1834 | (let ((bufname (buffer-name))) | ||
| 1835 | (cond ((string= bufname todos-categories-buffer) | ||
| 1836 | (switch-to-buffer (file-name-nondirectory todos-file-do))) | ||
| 1837 | ((string= bufname todos-archived-categories-buffer) | ||
| 1838 | ;; Is pop-to-buffer better for this case? | ||
| 1839 | (switch-to-buffer (file-name-nondirectory todos-archive-file)))) | ||
| 1840 | (kill-buffer bufname)) | ||
| 1841 | (widen) | ||
| 1842 | (goto-char (point-min)) | ||
| 1843 | (setq todos-category-number (- (length todos-categories) | ||
| 1844 | (length (member cat todos-categories)))) | ||
| 1845 | (todos-category-select)) | ||
| 1846 | |||
| 1847 | (defun todos-insert-with-overlays (item) | ||
| 1848 | "" ;FIXME add docstring | ||
| 1849 | ;; FIXME: breaks without narrowing, e.g. todos-item-done | ||
| 1850 | ;; (unless (and (bolp) (eolp)) (goto-char (todos-item-start))) | ||
| 1851 | (insert item "\n") | ||
| 1852 | (todos-backward-item) | ||
| 1853 | (if todos-number-prefix | ||
| 1854 | (todos-update-numbered-prefix) | ||
| 1855 | (todos-prefix-overlays))) | ||
| 1856 | |||
| 1857 | ;; (defun todos-more-important-p (line) | ||
| 1858 | ;; "Ask whether entry is more important than the one at LINE." | ||
| 1859 | ;; (unless (equal todos-previous-line line) | ||
| 1860 | ;; (setq todos-previous-line line) | ||
| 1861 | ;; (goto-char (point-min)) | ||
| 1862 | ;; (forward-line (1- todos-previous-line)) | ||
| 1863 | ;; (let ((item (todos-item-string-start))) | ||
| 1864 | ;; (setq todos-previous-answer | ||
| 1865 | ;; (y-or-n-p (concat "More important than '" item "'? "))))) | ||
| 1866 | ;; todos-previous-answer) | ||
| 1867 | |||
| 1213 | (defun todos-line-string () | 1868 | (defun todos-line-string () |
| 1214 | "Return current line in buffer as a string." | 1869 | "Return current line in buffer as a string." |
| 1215 | (buffer-substring (line-beginning-position) (line-end-position))) | 1870 | (buffer-substring (line-beginning-position) (line-end-position))) |
| @@ -1223,51 +1878,112 @@ Number of entries for each category is given by `todos-print-priorities'." | |||
| 1223 | item)) | 1878 | item)) |
| 1224 | 1879 | ||
| 1225 | (defun todos-item-start () | 1880 | (defun todos-item-start () |
| 1226 | "Return point at start of current TODO list item." | 1881 | "Move to start of current TODO list item and return its position." |
| 1227 | (save-excursion | 1882 | (unless (or (looking-at "^$") ; last item or between done and not done |
| 1228 | (if (re-search-backward (concat (regexp-quote todos-item-end) "\n") nil t) | 1883 | (looking-at (regexp-quote todos-category-beg))) ; for todos-count-items |
| 1229 | (forward-line) | 1884 | (goto-char (line-beginning-position)) |
| 1230 | (goto-char (point-min))) | 1885 | (while (not (looking-at (concat "^\\(\\[\\(" (regexp-quote todos-done-string) |
| 1231 | ;; for widened buffer in todos-toggle-diary-inclusion | 1886 | "\\)?\\)?" todos-date-pattern))) |
| 1232 | ;; (while (looking-at | 1887 | (forward-line -1))) |
| 1233 | ;; (concat "^" (regexp-opt (list todos-category-sep todos-category-beg | 1888 | (point)) |
| 1234 | ;; todos-category-end)))) | ||
| 1235 | ;; (forward-line)) | ||
| 1236 | (point))) | ||
| 1237 | 1889 | ||
| 1238 | (defun todos-item-end () | 1890 | (defun todos-item-end () |
| 1239 | "Return point at end of current TODO list item." | 1891 | "Move to end of current TODO list item and return its position." |
| 1240 | (if (todos-check-overlay 'invisible) | 1892 | (unless (looking-at "^$") ; last item or between done and not done |
| 1241 | (search-backward todos-item-end) | 1893 | (todos-forward-item) |
| 1242 | (when (not (and (bolp) (eobp))) | 1894 | (backward-char)) |
| 1243 | (save-excursion | 1895 | (point)) |
| 1244 | (re-search-forward (concat "\\(" (regexp-quote todos-item-end) "\\)\n")) | ||
| 1245 | (match-beginning 1))))) | ||
| 1246 | 1896 | ||
| 1247 | (defun todos-remove-item () | 1897 | (defun todos-remove-item () |
| 1248 | "Delete the current entry from the TODO list." | 1898 | "Delete the current entry from the TODO list." |
| 1249 | (let ((beg (todos-item-start)) | 1899 | (let* ((end (progn (todos-forward-item) (point))) |
| 1250 | (end (save-excursion | 1900 | (beg (progn (todos-backward-item) (point))) |
| 1251 | (unless (todos-check-overlay 'invisible) (goto-char (todos-item-end))) | 1901 | (ov-start (car (overlays-in beg beg)))) |
| 1252 | (line-end-position))) | 1902 | (when ov-start |
| 1253 | ov-start ov-end) | 1903 | ;; don't cache numbers, since they can be popped out of order in |
| 1254 | (goto-char (todos-item-start)) | 1904 | ;; todos-prefix-overlays |
| 1255 | ;; (setq ov-start (car (overlays-in (1- (point)) (1+ (point))))) | 1905 | (unless todos-number-prefix |
| 1256 | (setq ov-start (car (overlays-in (point) (point)))) | 1906 | (push ov-start todos-item-start-overlays)) |
| 1257 | (push ov-start todos-item-start-overlays) | 1907 | (delete-overlay ov-start)) |
| 1258 | (delete-overlay ov-start) | 1908 | (delete-region beg end))) |
| 1259 | (goto-char (todos-item-end)) | ||
| 1260 | ;; (setq ov-end (car (overlays-in (1- (point)) (1+ (point))))) | ||
| 1261 | ;; FIXME | ||
| 1262 | (setq ov-end (car (overlays-in (point) (point)))) | ||
| 1263 | (push ov-end todos-item-end-overlays) | ||
| 1264 | (delete-overlay ov-end) | ||
| 1265 | (delete-region (todos-item-start) (1+ end)))) | ||
| 1266 | 1909 | ||
| 1267 | (defun todos-item-string () | 1910 | (defun todos-item-string () |
| 1268 | "Return current TODO list entry as a string." | 1911 | "Return current TODO list entry as a string." |
| 1269 | (buffer-substring (todos-item-start) (todos-item-end))) | 1912 | (buffer-substring (todos-item-start) (todos-item-end))) |
| 1270 | 1913 | ||
| 1914 | (defun todos-done-item-p () | ||
| 1915 | "" ;FIXME | ||
| 1916 | (save-excursion | ||
| 1917 | (todos-item-start) | ||
| 1918 | (looking-at (concat "^\\[" (regexp-quote todos-done-string))))) | ||
| 1919 | |||
| 1920 | (defun todos-count-items-in-category () | ||
| 1921 | "Return number of not done and done items in current category." | ||
| 1922 | (save-excursion | ||
| 1923 | (let ((not-done 0) | ||
| 1924 | (done 0) | ||
| 1925 | (beg (point-min)) | ||
| 1926 | end) | ||
| 1927 | (save-restriction | ||
| 1928 | (widen) | ||
| 1929 | (re-search-forward (concat "^" (regexp-quote todos-category-beg)) nil t) | ||
| 1930 | (setq end (or (match-beginning 0) (point-max))) | ||
| 1931 | (goto-char beg) | ||
| 1932 | (while (> end (point)) | ||
| 1933 | (if (todos-done-item-p) | ||
| 1934 | (setq done (1+ done)) | ||
| 1935 | (setq not-done (1+ not-done))) | ||
| 1936 | (todos-forward-item) | ||
| 1937 | (when (and (not (> (point) end)) | ||
| 1938 | (looking-at "^$") | ||
| 1939 | (not (eobp))) | ||
| 1940 | ;; point is between done and not done items | ||
| 1941 | (setq not-done (1- not-done)))) | ||
| 1942 | (cons not-done done))))) | ||
| 1943 | |||
| 1944 | ;; FIXME: rename, since *-alist is by convention a variable name | ||
| 1945 | (defun todos-categories-alist () | ||
| 1946 | "Return alist of categories and some of their properties. | ||
| 1947 | The properties are at least the numbers of the unfinished and | ||
| 1948 | done items in the category." | ||
| 1949 | (let (todos-categories-alist) | ||
| 1950 | (save-excursion | ||
| 1951 | (save-restriction | ||
| 1952 | (widen) | ||
| 1953 | (goto-char (point-min)) | ||
| 1954 | (let ((not-done 0) | ||
| 1955 | (done 0) | ||
| 1956 | category beg end) | ||
| 1957 | (while (not (eobp)) | ||
| 1958 | (cond ((looking-at (concat (regexp-quote todos-category-beg) | ||
| 1959 | "\\(.*\\)\n")) | ||
| 1960 | (setq not-done 0 done 0) | ||
| 1961 | (push (list (match-string-no-properties 1) (cons not-done done)) | ||
| 1962 | todos-categories-alist)) | ||
| 1963 | ((looking-at (concat "^\\[" (regexp-quote todos-done-string))) | ||
| 1964 | (setq done (1+ done)) | ||
| 1965 | (setcdr (cadr (car todos-categories-alist)) done)) | ||
| 1966 | ((looking-at (concat "^\\[?" todos-date-pattern)) | ||
| 1967 | (setq not-done (1+ not-done)) | ||
| 1968 | (setcar (cadr (car todos-categories-alist)) not-done))) | ||
| 1969 | (forward-line))))) | ||
| 1970 | todos-categories-alist)) | ||
| 1971 | |||
| 1972 | (defun todos-count-all-items () | ||
| 1973 | "" | ||
| 1974 | (let ((unfinished 0) | ||
| 1975 | (done 0)) | ||
| 1976 | (dolist (l (todos-categories-alist)) | ||
| 1977 | (setq unfinished (+ unfinished (car (cadr l))) | ||
| 1978 | done (+ done (cdr (cadr l))))) | ||
| 1979 | (cons unfinished done))) | ||
| 1980 | |||
| 1981 | (defun todos-longest-category-name-length () | ||
| 1982 | "" | ||
| 1983 | (let ((longest 0)) | ||
| 1984 | (dolist (c (todos-categories-alist) longest) | ||
| 1985 | (setq longest (max longest (length (car c))))))) | ||
| 1986 | |||
| 1271 | (defun todos-string-count-lines (string) | 1987 | (defun todos-string-count-lines (string) |
| 1272 | "Return the number of lines STRING spans." | 1988 | "Return the number of lines STRING spans." |
| 1273 | (length (split-string string "\n"))) | 1989 | (length (split-string string "\n"))) |
| @@ -1295,7 +2011,21 @@ Number of entries for each category is given by `todos-print-priorities'." | |||
| 1295 | (setq todos-categories categories) | 2011 | (setq todos-categories categories) |
| 1296 | category))) | 2012 | category))) |
| 1297 | 2013 | ||
| 2014 | (defun todos-categories-list (buf) | ||
| 2015 | "Return a list of the Todo mode categories in buffer BUF." | ||
| 2016 | (let (categories) | ||
| 2017 | (with-current-buffer buf | ||
| 2018 | (save-excursion | ||
| 2019 | (save-restriction | ||
| 2020 | (widen) | ||
| 2021 | (goto-char (point-max)) | ||
| 2022 | (while (re-search-backward (concat "^" (regexp-quote todos-category-beg) | ||
| 2023 | "\\(.*\\)\n") nil t) | ||
| 2024 | (push (match-string-no-properties 1) categories))))) | ||
| 2025 | categories)) | ||
| 2026 | |||
| 1298 | ;; --------------------------------------------------------------------------- | 2027 | ;; --------------------------------------------------------------------------- |
| 2028 | ;;; Mode setup | ||
| 1299 | 2029 | ||
| 1300 | (easy-menu-define todos-menu todos-mode-map "Todo Menu" | 2030 | (easy-menu-define todos-menu todos-mode-map "Todo Menu" |
| 1301 | '("Todo" | 2031 | '("Todo" |
| @@ -1335,59 +2065,95 @@ Number of entries for each category is given by `todos-print-priorities'." | |||
| 1335 | (setq mode-name "TODOS") | 2065 | (setq mode-name "TODOS") |
| 1336 | (use-local-map todos-mode-map) | 2066 | (use-local-map todos-mode-map) |
| 1337 | (easy-menu-add todos-menu) | 2067 | (easy-menu-add todos-menu) |
| 2068 | (when todos-wrap-lines (funcall todos-line-wrapping-function)) | ||
| 1338 | (make-local-variable 'font-lock-defaults) | 2069 | (make-local-variable 'font-lock-defaults) |
| 1339 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | 2070 | (setq font-lock-defaults '(todos-font-lock-keywords t)) |
| 1340 | (make-local-variable 'word-wrap) | ||
| 1341 | (setq word-wrap t) | ||
| 1342 | (make-local-variable 'wrap-prefix) | ||
| 1343 | (setq wrap-prefix (make-string (+ 5 (length todos-prefix)) 32)) | ||
| 1344 | (unless (member '(continuation) fringe-indicator-alist) | ||
| 1345 | (push '(continuation) fringe-indicator-alist)) | ||
| 1346 | (make-local-variable 'hl-line-range-function) | 2071 | (make-local-variable 'hl-line-range-function) |
| 1347 | (setq hl-line-range-function | 2072 | (setq hl-line-range-function |
| 1348 | (lambda() (when (todos-item-end) | 2073 | (lambda() (when (todos-item-end) |
| 1349 | (cons (todos-item-start) (todos-item-end))))) | 2074 | (cons (todos-item-start) (todos-item-end))))) |
| 1350 | ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t) | 2075 | ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t) |
| 2076 | (add-to-invisibility-spec 'todos) | ||
| 2077 | ;; FIXME: use this and let-bind in editing commands? | ||
| 2078 | (setq buffer-read-only t) | ||
| 1351 | (run-mode-hooks 'todos-mode-hook)) | 2079 | (run-mode-hooks 'todos-mode-hook)) |
| 1352 | 2080 | ||
| 1353 | ;; (defvar date) | 2081 | (defun todos-archive-mode () |
| 1354 | ;; (defvar entry) | 2082 | "Major mode for archived Todos categories. |
| 1355 | 2083 | ||
| 1356 | ;; ;; t-c should be used from diary code, which requires calendar. | 2084 | \\{todos-mode-map}" |
| 1357 | ;; (declare-function calendar-current-date "calendar" nil) | 2085 | (interactive) |
| 1358 | 2086 | (kill-all-local-variables) | |
| 1359 | ;; ;; Read about this function in the setup instructions above! | 2087 | (setq major-mode 'todos-archive-mode) |
| 1360 | ;; ;;;###autoload | 2088 | (setq mode-name "TODOS Arch") |
| 1361 | ;; (defun todos-cp () | 2089 | (use-local-map todos-archive-mode-map) |
| 1362 | ;; "Make a diary entry appear only in the current date's diary." | 2090 | ;; (easy-menu-add todos-menu) |
| 1363 | ;; (if (equal (calendar-current-date) date) | 2091 | (when todos-wrap-lines (funcall todos-line-wrapping-function)) |
| 1364 | ;; entry)) | 2092 | (make-local-variable 'font-lock-defaults) |
| 2093 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | ||
| 2094 | (make-local-variable 'hl-line-range-function) | ||
| 2095 | (setq hl-line-range-function | ||
| 2096 | (lambda() (when (todos-item-end) | ||
| 2097 | (cons (todos-item-start) (todos-item-end))))) | ||
| 2098 | ;; (add-hook 'post-command-hook 'todos-show-paren-hack nil t) | ||
| 2099 | (add-to-invisibility-spec 'todos) | ||
| 2100 | (run-mode-hooks 'todos-mode-hook)) | ||
| 1365 | 2101 | ||
| 1366 | (define-derived-mode todos-edit-mode text-mode "TODO Edit" | 2102 | (defun todos-edit-mode () |
| 1367 | "Major mode for editing items in the TODO list. | 2103 | "Major mode for editing items in the TODO list. |
| 1368 | 2104 | ||
| 1369 | \\{todos-edit-mode-map}") | 2105 | \\{todos-edit-mode-map}" |
| 2106 | (interactive) | ||
| 2107 | (setq major-mode 'todos-edit-mode) | ||
| 2108 | (setq mode-name "TODOS Edit") | ||
| 2109 | (use-local-map todos-edit-mode-map) | ||
| 2110 | (make-local-variable 'font-lock-defaults) | ||
| 2111 | (setq font-lock-defaults '(todos-font-lock-keywords t)) | ||
| 2112 | (when todos-wrap-lines (funcall todos-line-wrapping-function))) | ||
| 2113 | |||
| 2114 | (defun todos-save () | ||
| 2115 | "Save the TODO list." | ||
| 2116 | (interactive) | ||
| 2117 | (save-excursion | ||
| 2118 | (save-restriction | ||
| 2119 | (save-buffer))) | ||
| 2120 | ;; (if todos-save-top-priorities-too (todos-save-top-priorities))) | ||
| 2121 | ) | ||
| 2122 | |||
| 2123 | (defun todos-quit () | ||
| 2124 | "Done with TODO list for now." | ||
| 2125 | (interactive) | ||
| 2126 | (widen) | ||
| 2127 | (todos-save) | ||
| 2128 | (message "") | ||
| 2129 | (bury-buffer)) | ||
| 1370 | 2130 | ||
| 1371 | ;;;###autoload | 2131 | ;;;###autoload |
| 1372 | (defun todos-show () | 2132 | (defun todos-show () |
| 1373 | "Show TODO list." | 2133 | "Show TODO list." |
| 1374 | (interactive) | 2134 | (interactive) |
| 1375 | ;; Call todos-initial-setup only if there is neither a Todo file nor | 2135 | ;; Make this a no-op if called interactively in narrowed Todos mode, since |
| 1376 | ;; a corresponding unsaved buffer. | 2136 | ;; it is in that case redundant, but in particular to work around the bug of |
| 1377 | (if (or (file-exists-p todos-file-do) | 2137 | ;; item prefix reduplication with show-paren-mode enabled. |
| 1378 | (let* ((buf (get-buffer (file-name-nondirectory todos-file-do))) | 2138 | (unless (and (called-interactively-p) |
| 1379 | (bufname (buffer-file-name buf))) | 2139 | (eq major-mode 'todos-mode) |
| 1380 | (equal (expand-file-name todos-file-do) bufname))) | 2140 | (< (- ( point-max) (point-min)) (buffer-size))) |
| 1381 | (find-file todos-file-do) | 2141 | ;; Call todos-initial-setup only if there is neither a Todo file nor |
| 1382 | (todos-initial-setup)) | 2142 | ;; a corresponding unsaved buffer. |
| 1383 | (unless (eq major-mode 'todos-mode) (todos-mode)) | 2143 | (if (or (file-exists-p todos-file-do) |
| 1384 | (unless todos-categories | 2144 | (let* ((buf (get-buffer (file-name-nondirectory todos-file-do))) |
| 1385 | (setq todos-categories (todos-list-categories))) | 2145 | (bufname (buffer-file-name buf))) |
| 1386 | ;; (beginning-of-line) | 2146 | (equal (expand-file-name todos-file-do) bufname))) |
| 1387 | (save-excursion | 2147 | (find-file todos-file-do) |
| 1388 | (todos-category-select) | 2148 | (todos-initial-setup)) |
| 1389 | ;; (todos-show-paren-hack) | 2149 | (unless (eq major-mode 'todos-mode) (todos-mode)) |
| 1390 | )) | 2150 | (unless todos-categories |
| 2151 | (setq todos-categories (todos-categories-list (buffer-name)))) | ||
| 2152 | ;; (beginning-of-line) | ||
| 2153 | (save-excursion | ||
| 2154 | (todos-category-select) | ||
| 2155 | ;; (todos-show-paren-hack) | ||
| 2156 | ))) | ||
| 1391 | 2157 | ||
| 1392 | (defun todos-initial-setup () | 2158 | (defun todos-initial-setup () |
| 1393 | "Set up things to work properly in TODO mode." | 2159 | "Set up things to work properly in TODO mode." |