diff options
| author | Stephen Berman | 2012-12-12 20:53:49 +0100 |
|---|---|---|
| committer | Stephen Berman | 2012-12-12 20:53:49 +0100 |
| commit | d16da867a96deafbfc9920e3f15970a8fe48161d (patch) | |
| tree | 5b73cd5c0494574334106a399b0f6e741ea7b135 | |
| parent | bbf95285a036398ed993e6051249f094b1c4d354 (diff) | |
| download | emacs-d16da867a96deafbfc9920e3f15970a8fe48161d.tar.gz emacs-d16da867a96deafbfc9920e3f15970a8fe48161d.zip | |
* calendar/todos.el: Extend and improve handling of item editing,
especially of date/time header.
(todos-month-name-array, todos-month-abbrev-array): New defconsts.
(todos-date-pattern): Use explicitly numbered groups.
(todos-read-date): Optionally read and return just one of the date
string components year, month or monthname, day.
(todos-key-bindings): Add bindings for new item header editing
commands.
(todos-edit-item): Exclude date/time header from minibuffer by
default; include it by passing a prefix argument.
(todos-edit-item-header-1): New function containing the guts of
the commands for editing item date/time headers.
(todos-edit-item-header): Use it. Condition editing of time
string on value of `todos-always-add-time-string'.
(todos-edit-item-date-from-calendar): Use todos-edit-item-header-1.
(todos-edit-item-date-to-today): Rename from
todos-edit-item-date-is-today and use todos-edit-item-header-1.
(todos-edit-item-date): Remove.
(todos-edit-item-date-day-name, todos-edit-item-date-year)
(todos-edit-item-date-month, todos-edit-item-date-day): New commands.
| -rw-r--r-- | lisp/ChangeLog | 23 | ||||
| -rw-r--r-- | lisp/calendar/todos.el | 484 |
2 files changed, 374 insertions, 133 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 98df5d3fe92..bde403fe0d5 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog | |||
| @@ -1,3 +1,26 @@ | |||
| 1 | 2012-12-12 Stephen Berman <stephen.berman@gmx.net> | ||
| 2 | |||
| 3 | * calendar/todos.el: Extend and improve handling of item editing, | ||
| 4 | especially of date/time header. | ||
| 5 | (todos-month-name-array, todos-month-abbrev-array): New defconsts. | ||
| 6 | (todos-date-pattern): Use explicitly numbered groups. | ||
| 7 | (todos-read-date): Optionally read and return just one of the date | ||
| 8 | string components year, month or monthname, day. | ||
| 9 | (todos-key-bindings): Add bindings for new item header editing | ||
| 10 | commands. | ||
| 11 | (todos-edit-item): Exclude date/time header from minibuffer by | ||
| 12 | default; include it by passing a prefix argument. | ||
| 13 | (todos-edit-item-header-1): New function containing the guts of | ||
| 14 | the commands for editing item date/time headers. | ||
| 15 | (todos-edit-item-header): Use it. Condition editing of time | ||
| 16 | string on value of `todos-always-add-time-string'. | ||
| 17 | (todos-edit-item-date-from-calendar): Use todos-edit-item-header-1. | ||
| 18 | (todos-edit-item-date-to-today): Rename from | ||
| 19 | todos-edit-item-date-is-today and use todos-edit-item-header-1. | ||
| 20 | (todos-edit-item-date): Remove. | ||
| 21 | (todos-edit-item-date-day-name, todos-edit-item-date-year) | ||
| 22 | (todos-edit-item-date-month, todos-edit-item-date-day): New commands. | ||
| 23 | |||
| 1 | 2012-12-02 Stephen Berman <stephen.berman@gmx.net> | 24 | 2012-12-02 Stephen Berman <stephen.berman@gmx.net> |
| 2 | 25 | ||
| 3 | * calendar/todos.el (todos-show): Fix a comment. | 26 | * calendar/todos.el (todos-show): Fix a comment. |
diff --git a/lisp/calendar/todos.el b/lisp/calendar/todos.el index 70f144d8004..c7f2d5b8dee 100644 --- a/lisp/calendar/todos.el +++ b/lisp/calendar/todos.el | |||
| @@ -1336,19 +1336,32 @@ it.") | |||
| 1336 | 1336 | ||
| 1337 | ;;; Global variables and helper functions for items | 1337 | ;;; Global variables and helper functions for items |
| 1338 | 1338 | ||
| 1339 | (defconst todos-month-name-array | ||
| 1340 | (vconcat calendar-month-name-array (vector "*")) | ||
| 1341 | "Array of month names, in order. | ||
| 1342 | The final element is \"*\", indicating an unspecified month.") | ||
| 1343 | |||
| 1344 | (defconst todos-month-abbrev-array | ||
| 1345 | (vconcat calendar-month-abbrev-array (vector "*")) | ||
| 1346 | "Array of abbreviated month names, in order. | ||
| 1347 | The final element is \"*\", indicating an unspecified month.") | ||
| 1348 | |||
| 1339 | (defconst todos-date-pattern | 1349 | (defconst todos-date-pattern |
| 1340 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) | 1350 | (let ((dayname (diary-name-pattern calendar-day-name-array nil t))) |
| 1341 | (concat "\\(?:" dayname "\\|" | 1351 | (concat "\\(?5:" dayname "\\|" |
| 1342 | (let ((dayname) | 1352 | (let ((dayname) |
| 1343 | ;; FIXME: how to choose between abbreviated and unabbreviated | 1353 | ;; FIXME: how to choose between abbreviated and unabbreviated |
| 1344 | ;; month name? | 1354 | ;; month name? |
| 1345 | (monthname (format "\\(?:%s\\|\\*\\)" | 1355 | ;; (monthname (format "\\(?6:%s\\|\\*\\)" |
| 1346 | (diary-name-pattern | 1356 | ;; (diary-name-pattern |
| 1347 | calendar-month-name-array | 1357 | ;; calendar-month-name-array |
| 1348 | calendar-month-abbrev-array t))) | 1358 | ;; calendar-month-abbrev-array))) |
| 1349 | (month "\\(?:[0-9]+\\|\\*\\)") | 1359 | (monthname (format "\\(?6:%s\\)" (diary-name-pattern |
| 1350 | (day "\\(?:[0-9]+\\|\\*\\)") | 1360 | todos-month-name-array |
| 1351 | (year "-?\\(?:[0-9]+\\|\\*\\)")) | 1361 | todos-month-abbrev-array))) |
| 1362 | (month "\\(?7:[0-9]+\\|\\*\\)") | ||
| 1363 | (day "\\(?8:[0-9]+\\|\\*\\)") | ||
| 1364 | (year "-?\\(?9:[0-9]+\\|\\*\\)")) | ||
| 1352 | (mapconcat 'eval calendar-date-display-form "")) | 1365 | (mapconcat 'eval calendar-date-display-form "")) |
| 1353 | "\\)")) | 1366 | "\\)")) |
| 1354 | "Regular expression matching a Todos date header.") | 1367 | "Regular expression matching a Todos date header.") |
| @@ -1637,57 +1650,87 @@ TYPE can be either a file or a category" | |||
| 1637 | name)) | 1650 | name)) |
| 1638 | 1651 | ||
| 1639 | ;; Adapted from calendar-read-date and calendar-date-string. | 1652 | ;; Adapted from calendar-read-date and calendar-date-string. |
| 1640 | (defun todos-read-date () | 1653 | (defun todos-read-date (&optional arg mo yr) |
| 1641 | "Prompt for Gregorian date and return it in the current format. | 1654 | "Prompt for Gregorian date and return it in the current format. |
| 1642 | Also accepts `*' as an unspecified month, day, or year." | 1655 | |
| 1643 | (let* ((year (let (x) | 1656 | With non-nil ARG, prompt for and return only the date component |
| 1644 | (while (if (numberp x) (< x 0) (not (eq x '*))) | 1657 | specified by ARG, which can be one of these symbols: |
| 1645 | (setq x (read-from-minibuffer | 1658 | `month' (prompt for name, return name or number according to |
| 1646 | "Year (>0 or RET for this year or * for any year): " | 1659 | value of `calendar-date-display-form'), `day' of month, or |
| 1647 | nil nil t nil (number-to-string | 1660 | `year'. The value of each of these components can be `*', |
| 1648 | (calendar-extract-year | 1661 | indicating an unspecified month, day, or year. |
| 1649 | (calendar-current-date)))))) | 1662 | |
| 1650 | x)) | 1663 | When ARG is `day', non-nil arguments MO and YR determine the |
| 1651 | (month-array (vconcat calendar-month-name-array (vector "*"))) | 1664 | number of the last the day of the month." |
| 1652 | (abbrevs (vconcat calendar-month-abbrev-array (vector "*"))) | 1665 | (let (year monthname month day |
| 1653 | (completion-ignore-case todos-completion-ignore-case) | 1666 | dayname) ; Needed by calendar-date-display-form. |
| 1654 | (monthname (completing-read | 1667 | ;; FIXME: year can be omitted from Diary |
| 1655 | "Month name (RET for current month, * for any month): " | 1668 | (when (or (not arg) (eq arg 'year)) |
| 1656 | (mapcar 'list (append month-array nil)) | 1669 | (while (if (natnump year) (< year 1) (not (eq year '*))) |
| 1657 | nil t nil nil | 1670 | (setq year (read-from-minibuffer |
| 1658 | (calendar-month-name (calendar-extract-month | 1671 | "Year (>0 or RET for this year or * for any year): " |
| 1659 | (calendar-current-date)) t))) | 1672 | nil nil t nil (number-to-string |
| 1660 | (month (cdr (assoc-string | 1673 | (calendar-extract-year |
| 1661 | monthname (calendar-make-alist month-array nil nil | 1674 | (calendar-current-date))))))) |
| 1662 | abbrevs)))) | 1675 | (when (or (not arg) (eq arg 'month)) |
| 1663 | (last (if (= month 13) | 1676 | (let* ((marray todos-month-name-array) |
| 1664 | ;; Use longest possible month for checking day number | 1677 | (mlist (append marray nil)) |
| 1665 | ;; input. Does Calendar do anything special when * is | 1678 | (mabarray todos-month-abbrev-array) |
| 1666 | ;; currently a shorter month? | 1679 | (mablist (append mabarray nil)) |
| 1667 | 31 | 1680 | (completion-ignore-case todos-completion-ignore-case)) |
| 1668 | (let ((yr (if (eq year '*) | 1681 | (setq monthname (completing-read |
| 1669 | ;; Use a leap year to allow Feb. 29. | 1682 | "Month name (RET for current month, * for any month): " |
| 1670 | 2012 | 1683 | ;; (mapcar 'list (append marray nil)) |
| 1671 | year))) | 1684 | mlist nil t nil nil |
| 1672 | (calendar-last-day-of-month month yr)))) | 1685 | (calendar-month-name (calendar-extract-month |
| 1673 | (day (let (x) | 1686 | (calendar-current-date)) t)) |
| 1674 | (while (if (numberp x) (or (< x 0) (< last x)) (not (eq x '*))) | 1687 | ;; month (cdr (assoc-string |
| 1675 | (setq x (read-from-minibuffer | 1688 | ;; monthname (calendar-make-alist marray nil nil |
| 1676 | (format | 1689 | ;; abbrevs)))))) |
| 1677 | "Day (1-%d or RET for today or * for any day): " | 1690 | month (1+ (- (length mlist) |
| 1678 | last) nil nil t nil (number-to-string | 1691 | (length (or (member monthname mlist) |
| 1679 | (calendar-extract-day | 1692 | (member monthname mablist)))))) |
| 1680 | (calendar-current-date)))))) | 1693 | ;; FIXME: We follow diary-insert-entry in using abbreviated |
| 1681 | x)) | 1694 | ;; month name (and no day name) in date string. Should this |
| 1682 | dayname) ; Needed by calendar-date-display-form. | 1695 | ;; be customizable? |
| 1683 | (setq year (if (eq year '*) (symbol-name '*) (number-to-string year))) | 1696 | (setq monthname (aref mabarray (1- month))))) |
| 1684 | (setq day (if (eq day '*) (symbol-name '*) (number-to-string day))) | 1697 | (when (or (not arg) (eq arg 'day)) |
| 1685 | ;; FIXME: make abbreviation customizable | 1698 | (let ((last (let ((mm (or month mo)) |
| 1686 | (setq monthname | 1699 | (yy (or year yr))) |
| 1687 | (or (and (= month 13) "*") | 1700 | ;; If month is unspecified, use a month with 31 |
| 1688 | (calendar-month-name (calendar-extract-month (list month day year)) | 1701 | ;; days for checking day of month input. Does |
| 1689 | t))) | 1702 | ;; Calendar do anything special when * is |
| 1690 | (mapconcat 'eval calendar-date-display-form ""))) | 1703 | ;; currently a shorter month? |
| 1704 | (if (= mm 13) (setq mm 1)) | ||
| 1705 | ;; If year is unspecified, use a leap year to | ||
| 1706 | ;; allow Feb. 29. | ||
| 1707 | (if (eq year '*) (setq yy 2012)) | ||
| 1708 | (calendar-last-day-of-month mm yy)))) | ||
| 1709 | (while (if (natnump day) (or (< day 1) (> day last)) (not (eq day '*))) | ||
| 1710 | (setq day (read-from-minibuffer | ||
| 1711 | (format "Day (1-%d or RET for today or * for any day): " | ||
| 1712 | last) | ||
| 1713 | nil nil t nil (number-to-string | ||
| 1714 | (calendar-extract-day | ||
| 1715 | (calendar-current-date)))))))) | ||
| 1716 | ;; Stringify read values (monthname is already a string). | ||
| 1717 | (and year (setq year (if (eq year '*) | ||
| 1718 | (symbol-name '*) | ||
| 1719 | (number-to-string year)))) | ||
| 1720 | (and day (setq day (if (eq day '*) | ||
| 1721 | (symbol-name '*) | ||
| 1722 | (number-to-string day)))) | ||
| 1723 | (and month (setq month (if (eq month '*) | ||
| 1724 | (symbol-name '*) | ||
| 1725 | (number-to-string month)))) | ||
| 1726 | (if arg | ||
| 1727 | (cond ((eq arg 'year) year) | ||
| 1728 | ((eq arg 'day) day) | ||
| 1729 | ((eq arg 'month) | ||
| 1730 | (if (memq 'month calendar-date-display-form) | ||
| 1731 | month | ||
| 1732 | monthname))) | ||
| 1733 | (mapconcat 'eval calendar-date-display-form "")))) | ||
| 1691 | 1734 | ||
| 1692 | (defun todos-read-dayname () | 1735 | (defun todos-read-dayname () |
| 1693 | "Choose name of a day of the week with completion and return it." | 1736 | "Choose name of a day of the week with completion and return it." |
| @@ -1742,6 +1785,7 @@ the empty string (i.e., no time string)." | |||
| 1742 | :notify (lambda (widget &rest ignore) | 1785 | :notify (lambda (widget &rest ignore) |
| 1743 | (setq todos-multiple-filter-files 'quit) | 1786 | (setq todos-multiple-filter-files 'quit) |
| 1744 | (quit-window t) | 1787 | (quit-window t) |
| 1788 | ;; FIXME: use (throw 'exit nil) ? | ||
| 1745 | (exit-recursive-edit)) | 1789 | (exit-recursive-edit)) |
| 1746 | "Cancel") | 1790 | "Cancel") |
| 1747 | (widget-insert " ") | 1791 | (widget-insert " ") |
| @@ -1754,6 +1798,7 @@ the empty string (i.e., no time string)." | |||
| 1754 | (widget-value | 1798 | (widget-value |
| 1755 | todos-multiple-filter-files-widget))) | 1799 | todos-multiple-filter-files-widget))) |
| 1756 | (quit-window t) | 1800 | (quit-window t) |
| 1801 | ;; FIXME: use (throw 'exit nil) ? | ||
| 1757 | (exit-recursive-edit)) | 1802 | (exit-recursive-edit)) |
| 1758 | "Apply") | 1803 | "Apply") |
| 1759 | (use-local-map widget-keymap) | 1804 | (use-local-map widget-keymap) |
| @@ -2407,7 +2452,7 @@ which is the value of the user option | |||
| 2407 | (defvar todos-key-bindings | 2452 | (defvar todos-key-bindings |
| 2408 | `( | 2453 | `( |
| 2409 | ;; display | 2454 | ;; display |
| 2410 | ("Cd" . todos-display-categories) ;FIXME: Cs todos-show-categories? | 2455 | ("Cd" . todos-display-categories) ;FIXME: Fc todos-file-categories? |
| 2411 | ("H" . todos-highlight-item) | 2456 | ("H" . todos-highlight-item) |
| 2412 | ("N" . todos-hide-show-item-numbering) | 2457 | ("N" . todos-hide-show-item-numbering) |
| 2413 | ("D" . todos-hide-show-date-time) | 2458 | ("D" . todos-hide-show-date-time) |
| @@ -2420,7 +2465,7 @@ which is the value of the user option | |||
| 2420 | ("V" . todos-show-done-only) | 2465 | ("V" . todos-show-done-only) |
| 2421 | ("As" . todos-show-archive) | 2466 | ("As" . todos-show-archive) |
| 2422 | ("Ac" . todos-choose-archive) | 2467 | ("Ac" . todos-choose-archive) |
| 2423 | ("Y" . todos-diary-items) | 2468 | ;; ("Y" . todos-diary-items) |
| 2424 | ("Fe" . todos-edit-multiline) | 2469 | ("Fe" . todos-edit-multiline) |
| 2425 | ("Fh" . todos-highlight-item) | 2470 | ("Fh" . todos-highlight-item) |
| 2426 | ("Fn" . todos-hide-show-item-numbering) | 2471 | ("Fn" . todos-hide-show-item-numbering) |
| @@ -2453,9 +2498,12 @@ which is the value of the user option | |||
| 2453 | ("ee" . todos-edit-item) | 2498 | ("ee" . todos-edit-item) |
| 2454 | ("em" . todos-edit-multiline-item) | 2499 | ("em" . todos-edit-multiline-item) |
| 2455 | ("eh" . todos-edit-item-header) | 2500 | ("eh" . todos-edit-item-header) |
| 2456 | ("edd" . todos-edit-item-date) | ||
| 2457 | ("edc" . todos-edit-item-date-from-calendar) | 2501 | ("edc" . todos-edit-item-date-from-calendar) |
| 2458 | ("edt" . todos-edit-item-date-is-today) | 2502 | ("edt" . todos-edit-item-date-to-today) |
| 2503 | ("edn" . todos-edit-item-date-day-name) | ||
| 2504 | ("edy" . todos-edit-item-date-year) | ||
| 2505 | ("edm" . todos-edit-item-date-month) | ||
| 2506 | ("edd" . todos-edit-item-date-day) | ||
| 2459 | ("et" . todos-edit-item-time) | 2507 | ("et" . todos-edit-item-time) |
| 2460 | ("eyy" . todos-edit-item-diary-inclusion) | 2508 | ("eyy" . todos-edit-item-diary-inclusion) |
| 2461 | ;; ("" . todos-edit-category-diary-inclusion) | 2509 | ;; ("" . todos-edit-category-diary-inclusion) |
| @@ -4209,7 +4257,12 @@ the priority is not given by HERE but by prompting." | |||
| 4209 | (string-match todos-date-pattern date-type)) | 4257 | (string-match todos-date-pattern date-type)) |
| 4210 | (setq todos-date-from-calendar date-type) | 4258 | (setq todos-date-from-calendar date-type) |
| 4211 | (todos-set-date-from-calendar)) | 4259 | (todos-set-date-from-calendar)) |
| 4212 | (t (calendar-date-string (calendar-current-date) t t)))) | 4260 | (t |
| 4261 | ;; FIXME: We follow diary-insert-entry in | ||
| 4262 | ;; hardcoding abbreviated month name and no | ||
| 4263 | ;; day name in date string. Should this be | ||
| 4264 | ;; customizable? | ||
| 4265 | (calendar-date-string (calendar-current-date) t t)))) | ||
| 4213 | (time-string (or (and time (todos-read-time)) | 4266 | (time-string (or (and time (todos-read-time)) |
| 4214 | (and todos-always-add-time-string | 4267 | (and todos-always-add-time-string |
| 4215 | (substring (current-time-string) 11 16))))) | 4268 | (substring (current-time-string) 11 16))))) |
| @@ -4285,7 +4338,7 @@ the priority is not given by HERE but by prompting." | |||
| 4285 | (let (calendar-view-diary-initially-flag) | 4338 | (let (calendar-view-diary-initially-flag) |
| 4286 | (calendar)) | 4339 | (calendar)) |
| 4287 | ;; *Calendar* is now current buffer. | 4340 | ;; *Calendar* is now current buffer. |
| 4288 | (local-set-key (kbd "RET") 'exit-recursive-edit) | 4341 | (local-set-key (kbd "RET") 'exit-recursive-edit) ; FIXME: (throw 'exit nil)? |
| 4289 | (message "Put cursor on a date and type <return> to set it.") | 4342 | (message "Put cursor on a date and type <return> to set it.") |
| 4290 | ;; FIXME: is there a better way than recursive-edit? | 4343 | ;; FIXME: is there a better way than recursive-edit? |
| 4291 | (recursive-edit) | 4344 | (recursive-edit) |
| @@ -4344,13 +4397,18 @@ the item at point." | |||
| 4344 | (todos-prefix-overlays))) | 4397 | (todos-prefix-overlays))) |
| 4345 | (if ov (delete-overlay ov))))) | 4398 | (if ov (delete-overlay ov))))) |
| 4346 | 4399 | ||
| 4347 | (defun todos-edit-item () | 4400 | (defun todos-edit-item (&optional arg) |
| 4348 | "Edit the Todo item at point. | 4401 | "Edit the Todo item at point. |
| 4402 | |||
| 4403 | With non-nil prefix argument ARG, include the item's date/time | ||
| 4404 | header, making it also editable; otherwise, include only the item | ||
| 4405 | content. | ||
| 4406 | |||
| 4349 | If the item consists of only one logical line, edit it in the | 4407 | If the item consists of only one logical line, edit it in the |
| 4350 | minibuffer; otherwise, edit it in Todos Edit mode." | 4408 | minibuffer; otherwise, edit it in Todos Edit mode." |
| 4351 | (interactive) | 4409 | (interactive "P") |
| 4352 | (when (todos-item-string) | 4410 | (when (todos-item-string) |
| 4353 | (let* ((buffer-read-only) | 4411 | (let* ((opoint (point)) |
| 4354 | (start (todos-item-start)) | 4412 | (start (todos-item-start)) |
| 4355 | (item-beg (progn | 4413 | (item-beg (progn |
| 4356 | (re-search-forward | 4414 | (re-search-forward |
| @@ -4359,16 +4417,22 @@ minibuffer; otherwise, edit it in Todos Edit mode." | |||
| 4359 | (regexp-quote todos-nondiary-end) "?") | 4417 | (regexp-quote todos-nondiary-end) "?") |
| 4360 | (line-end-position) t) | 4418 | (line-end-position) t) |
| 4361 | (1+ (- (point) start)))) | 4419 | (1+ (- (point) start)))) |
| 4362 | (item (todos-item-string)) | 4420 | (header (substring (todos-item-string) 0 item-beg)) |
| 4421 | (item (if arg (todos-item-string) | ||
| 4422 | (substring (todos-item-string) item-beg))) | ||
| 4363 | (multiline (> (length (split-string item "\n")) 1)) | 4423 | (multiline (> (length (split-string item "\n")) 1)) |
| 4364 | (opoint (point))) | 4424 | (buffer-read-only nil)) |
| 4365 | (if multiline | 4425 | (if multiline |
| 4366 | (todos-edit-multiline t) | 4426 | (todos-edit-multiline-item) |
| 4367 | (let ((new (read-string "Edit: " (cons item item-beg)))) | 4427 | (let ((new (concat (if arg "" header) |
| 4368 | (while (not (string-match | 4428 | (read-string "Edit: " (if arg |
| 4369 | (concat todos-date-string-start todos-date-pattern) new)) | 4429 | (cons item item-beg) |
| 4370 | (setq new (read-from-minibuffer | 4430 | (cons item 0)))))) |
| 4371 | "Item must start with a date: " new))) | 4431 | (when arg |
| 4432 | (while (not (string-match (concat todos-date-string-start | ||
| 4433 | todos-date-pattern) new)) | ||
| 4434 | (setq new (read-from-minibuffer | ||
| 4435 | "Item must start with a date: " new)))) | ||
| 4372 | ;; Indent newlines inserted by C-q C-j if nonspace char follows. | 4436 | ;; Indent newlines inserted by C-q C-j if nonspace char follows. |
| 4373 | (setq new (replace-regexp-in-string | 4437 | (setq new (replace-regexp-in-string |
| 4374 | "\\(\n\\)[^[:blank:]]" | 4438 | "\\(\n\\)[^[:blank:]]" |
| @@ -4422,91 +4486,245 @@ in the number or names of categories." | |||
| 4422 | ;; In case next buffer is not the one holding todos-current-todos-file. | 4486 | ;; In case next buffer is not the one holding todos-current-todos-file. |
| 4423 | (todos-show)) | 4487 | (todos-show)) |
| 4424 | 4488 | ||
| 4425 | (defun todos-edit-item-header (&optional what) | 4489 | (defun todos-edit-item-header-1 (what &optional inc) |
| 4426 | "Edit date/time header of at least one item. | 4490 | "Underlying function to edit items' date/time headers. |
| 4427 | 4491 | ||
| 4428 | Interactively, ask whether to edit year, month and day or day of | 4492 | The argument WHAT (passed by invoking commands) specifies what |
| 4429 | the week, as well as time. If there are marked items, apply the | 4493 | part of the header to edit; possible values are these symbols: |
| 4430 | changes to all of these; otherwise, edit just the item at point. | 4494 | `date', to edit the year, month, and day of the date string; |
| 4431 | 4495 | `time', to edit just the time string; `calendar', to select the | |
| 4432 | Non-interactively, argument WHAT specifies whether to set the | 4496 | date from the Calendar; `today', to set the date to today's date; |
| 4433 | date from the Calendar or to today, or whether to edit only the | 4497 | `dayname', to set the date string to the name of a day or to |
| 4434 | date or day, or only the time." | 4498 | change the day name; and `year', `month' or `day', to edit only |
| 4435 | (interactive) | 4499 | these respective parts of the date string (`day' is the number of |
| 4500 | the given day of the month, and `month' is either the name of the | ||
| 4501 | given month or its number, depending on the value of | ||
| 4502 | `calendar-date-display-form'). | ||
| 4503 | |||
| 4504 | The optional argument INC is a positive or negative integer | ||
| 4505 | \(passed by invoking commands as a numerical prefix argument) | ||
| 4506 | that in conjunction with the WHAT values `year', `month' or | ||
| 4507 | `day', increments or decrements the specified date string | ||
| 4508 | component by the specified number of suitable units, i.e., years, | ||
| 4509 | months, or days, with automatic adjustment of the other date | ||
| 4510 | string components as necessary. | ||
| 4511 | |||
| 4512 | If there are marked items, apply the same edit to all of these; | ||
| 4513 | otherwise, edit just the item at point." | ||
| 4436 | (let* ((cat (todos-current-category)) | 4514 | (let* ((cat (todos-current-category)) |
| 4437 | (marked (assoc cat todos-categories-with-marks)) | 4515 | (marked (assoc cat todos-categories-with-marks)) |
| 4438 | (first t) ; Match only first of marked items. | 4516 | (first t) |
| 4439 | (todos-date-from-calendar t) | 4517 | (todos-date-from-calendar t) |
| 4440 | ndate ntime nheader) | 4518 | (buffer-read-only nil) |
| 4519 | ndate ntime year monthname month day | ||
| 4520 | dayname) ; Needed by calendar-date-display-form. | ||
| 4441 | (save-excursion | 4521 | (save-excursion |
| 4442 | (or (and marked (goto-char (point-min))) (todos-item-start)) | 4522 | (or (and marked (goto-char (point-min))) (todos-item-start)) |
| 4443 | (catch 'stop | 4523 | (catch 'end |
| 4444 | (while (not (eobp)) | 4524 | (while (not (eobp)) |
| 4445 | (and marked | 4525 | (and marked |
| 4446 | (while (not (todos-marked-item-p)) | 4526 | (while (not (todos-marked-item-p)) |
| 4447 | (todos-forward-item) | 4527 | (todos-forward-item) |
| 4448 | (and (eobp) (throw 'stop nil)))) | 4528 | (and (eobp) (throw 'end nil)))) |
| 4449 | (re-search-forward (concat todos-date-string-start "\\(?1:" | 4529 | (re-search-forward (concat todos-date-string-start "\\(?1:" |
| 4450 | todos-date-pattern | 4530 | todos-date-pattern |
| 4451 | "\\)\\(?2: " diary-time-regexp "\\)?") | 4531 | "\\)\\(?2: " diary-time-regexp "\\)?" |
| 4532 | (regexp-quote todos-nondiary-end) "?") | ||
| 4452 | (line-end-position) t) | 4533 | (line-end-position) t) |
| 4453 | (let* ((odate (match-string-no-properties 1)) | 4534 | (let* ((odate (match-string-no-properties 1)) |
| 4454 | (otime (match-string-no-properties 2)) | 4535 | (otime (match-string-no-properties 2)) |
| 4455 | (buffer-read-only)) | 4536 | (omonthname (match-string-no-properties 6)) |
| 4456 | (cond ((eq what 'today) | 4537 | (omonth (match-string-no-properties 7)) |
| 4457 | (progn | 4538 | (oday (match-string-no-properties 8)) |
| 4458 | (setq ndate (calendar-date-string | 4539 | (oyear (match-string-no-properties 9)) |
| 4459 | (calendar-current-date) t t)) | 4540 | (tmn-array todos-month-name-array) |
| 4460 | (replace-match ndate nil nil nil 1))) | 4541 | (mlist (append tmn-array nil)) |
| 4461 | ((eq what 'calendar) | 4542 | (tma-array todos-month-abbrev-array) |
| 4462 | (setq ndate (save-match-data (todos-set-date-from-calendar))) | 4543 | (mablist (append tma-array nil)) |
| 4463 | (replace-match ndate nil nil nil 1)) | 4544 | (yy (and oyear (unless (string= oyear "*") |
| 4464 | (t | 4545 | (string-to-number oyear)))) |
| 4465 | (unless (eq what 'timeonly) | 4546 | (mm (or (and omonth (unless (string= omonth "*") |
| 4466 | (when first | 4547 | (string-to-number omonth))) |
| 4467 | (setq ndate (if (save-match-data | 4548 | (1+ (- (length mlist) |
| 4468 | (string-match "[0-9]+" odate)) | 4549 | (length (or (member omonthname mlist) |
| 4469 | (if (y-or-n-p "Change date? ") | 4550 | (member omonthname mablist))))))) |
| 4470 | (todos-read-date) | 4551 | (dd (and oday (unless (string= oday "*") |
| 4471 | (todos-read-dayname)) | 4552 | (string-to-number oday))))) |
| 4472 | (if (y-or-n-p "Change day? ") | 4553 | ;; If there are marked items, use only the first to set |
| 4473 | (todos-read-dayname) | 4554 | ;; header changes, and apply these to all marked items. |
| 4474 | (todos-read-date))))) | 4555 | (when first |
| 4475 | (replace-match ndate nil nil nil 1)) | 4556 | (cond |
| 4476 | (unless (eq what 'dateonly) | 4557 | ((eq what 'date) |
| 4477 | (when first | 4558 | (setq ndate (todos-read-date))) |
| 4478 | (setq ntime (save-match-data (todos-read-time))) | 4559 | ((eq what 'calendar) |
| 4479 | (when (< 0 (length ntime)) | 4560 | (setq ndate (save-match-data (todos-set-date-from-calendar)))) |
| 4480 | (setq ntime (concat " " ntime)))) | 4561 | ((eq what 'today) |
| 4481 | (if otime | 4562 | (setq ndate (calendar-date-string (calendar-current-date) t t))) |
| 4482 | (replace-match ntime nil nil nil 2) | 4563 | ((eq what 'dayname) |
| 4483 | (goto-char (match-end 1)) | 4564 | (setq ndate (todos-read-dayname))) |
| 4484 | (insert ntime))))) | 4565 | ((eq what 'time) |
| 4566 | (setq ntime (save-match-data (todos-read-time))) | ||
| 4567 | (when (> (length ntime) 0) | ||
| 4568 | (setq ntime (concat " " ntime)))) | ||
| 4569 | ;; When date string consists only of a day name, | ||
| 4570 | ;; passing other date components is a NOP. | ||
| 4571 | ((and (memq what '(year month day)) | ||
| 4572 | (not (or oyear omonth oday)))) | ||
| 4573 | ((eq what 'year) | ||
| 4574 | (setq day oday | ||
| 4575 | monthname omonthname | ||
| 4576 | month omonth | ||
| 4577 | year (cond ((not current-prefix-arg) | ||
| 4578 | (todos-read-date 'year)) | ||
| 4579 | ((string= oyear "*") | ||
| 4580 | (error "Cannot increment *")) | ||
| 4581 | (t ; FIXME: handle negative years | ||
| 4582 | (number-to-string (+ yy inc)))))) | ||
| 4583 | ((eq what 'month) | ||
| 4584 | (setf day oday | ||
| 4585 | year oyear | ||
| 4586 | (if (memq 'month calendar-date-display-form) | ||
| 4587 | month | ||
| 4588 | monthname) | ||
| 4589 | (cond ((not current-prefix-arg) | ||
| 4590 | (todos-read-date 'month)) | ||
| 4591 | ((or (string= omonth "*") (= mm 13)) | ||
| 4592 | (error "Cannot increment *")) | ||
| 4593 | (t | ||
| 4594 | (let ((mminc (+ mm inc))) | ||
| 4595 | ;; Increment or decrement month by INC | ||
| 4596 | ;; modulo 12. | ||
| 4597 | (setq mm (% mminc 12)) | ||
| 4598 | ;; If result is 0, make month December. | ||
| 4599 | (setq mm (if (= mm 0) 12 (abs mm))) | ||
| 4600 | ;; Adjust year if necessary. | ||
| 4601 | (setq year (or (and (cond ((> mminc 12) | ||
| 4602 | (+ yy (/ mminc 12))) | ||
| 4603 | ((< mminc 1) | ||
| 4604 | (- yy (/ mminc 12) 1)) | ||
| 4605 | (t yy)) | ||
| 4606 | (number-to-string yy)) | ||
| 4607 | oyear))) | ||
| 4608 | ;; Return the changed numerical month as | ||
| 4609 | ;; a string or the corresponding month name. | ||
| 4610 | (if omonth | ||
| 4611 | (number-to-string mm) | ||
| 4612 | (aref tma-array (1- mm)))))) | ||
| 4613 | (let ((yy (string-to-number year)) ; 0 if year is "*". | ||
| 4614 | ;; When mm is 13 (corresponding to "*" as value | ||
| 4615 | ;; of month), this raises an args-out-of-range | ||
| 4616 | ;; error in calendar-last-day-of-month, so use 1 | ||
| 4617 | ;; (corresponding to January) to get 31 days. | ||
| 4618 | (mm (if (= mm 13) 1 mm))) | ||
| 4619 | (if (> (string-to-number day) | ||
| 4620 | (calendar-last-day-of-month mm yy)) | ||
| 4621 | (error "%s %s does not have %s days" | ||
| 4622 | (aref tmn-array (1- mm)) | ||
| 4623 | (if (= mm 2) yy "") day)))) | ||
| 4624 | ((eq what 'day) | ||
| 4625 | (setq year oyear | ||
| 4626 | month omonth | ||
| 4627 | monthname omonthname | ||
| 4628 | day (cond | ||
| 4629 | ((not current-prefix-arg) | ||
| 4630 | (todos-read-date 'day mm oyear)) | ||
| 4631 | ((string= oday "*") | ||
| 4632 | (error "Cannot increment *")) | ||
| 4633 | ((or (string= omonth "*") (string= omonthname "*")) | ||
| 4634 | (setq dd (+ dd inc)) | ||
| 4635 | (if (> dd 31) | ||
| 4636 | (error "A month cannot have more than 31 days") | ||
| 4637 | (number-to-string dd))) | ||
| 4638 | ;; Increment or decrement day by INC, | ||
| 4639 | ;; adjusting month and year if necessary | ||
| 4640 | ;; (if year is "*" assume current year to | ||
| 4641 | ;; calculate adjustment). | ||
| 4642 | (t | ||
| 4643 | (let* ((yy (or yy (calendar-extract-year | ||
| 4644 | (calendar-current-date)))) | ||
| 4645 | (date (calendar-gregorian-from-absolute | ||
| 4646 | (+ (calendar-absolute-from-gregorian | ||
| 4647 | (list mm dd yy)) inc))) | ||
| 4648 | (adjmm (nth 0 date))) | ||
| 4649 | ;; Set year and month(name) to adjusted values. | ||
| 4650 | (unless (string= year "*") | ||
| 4651 | (setq year (number-to-string (nth 2 date)))) | ||
| 4652 | (if month | ||
| 4653 | (setq month (number-to-string adjmm)) | ||
| 4654 | (setq monthname (aref tma-array (1- adjmm)))) | ||
| 4655 | ;; Return changed numerical day as a string. | ||
| 4656 | (number-to-string (nth 1 date))))))))) | ||
| 4657 | ;; If new year, month or day date string components were | ||
| 4658 | ;; calculated, rebuild the whole date string from them. | ||
| 4659 | (when (memq what '(year month day)) | ||
| 4660 | (if (or oyear omonth omonthname oday) | ||
| 4661 | (setq ndate (mapconcat 'eval calendar-date-display-form "")) | ||
| 4662 | (message "Cannot edit date component of empty date string"))) | ||
| 4663 | (when ndate (replace-match ndate nil nil nil 1)) | ||
| 4664 | ;; Add new time string to the header, if it was supplied. | ||
| 4665 | (when ntime | ||
| 4666 | (if otime | ||
| 4667 | (replace-match ntime nil nil nil 2) | ||
| 4668 | (goto-char (match-end 1)) | ||
| 4669 | (insert ntime))) | ||
| 4485 | (setq todos-date-from-calendar nil) | 4670 | (setq todos-date-from-calendar nil) |
| 4486 | (setq first nil)) | 4671 | (setq first nil)) |
| 4672 | ;; Apply the changes to the first marked item header to the | ||
| 4673 | ;; remaining marked items. If there are no marked items, | ||
| 4674 | ;; we're finished. | ||
| 4487 | (if marked | 4675 | (if marked |
| 4488 | (todos-forward-item) | 4676 | (todos-forward-item) |
| 4489 | (goto-char (point-max)))))))) | 4677 | (goto-char (point-max)))))))) |
| 4490 | 4678 | ||
| 4491 | (defun todos-edit-item-date () | 4679 | (defun todos-edit-item-header () |
| 4492 | "Prompt for and apply changes to current item's date." | 4680 | "Interactively edit at least the date of item's date/time header. |
| 4681 | If user option `todos-always-add-time-string' is non-nil, also | ||
| 4682 | edit item's time string." | ||
| 4683 | (interactive) | ||
| 4684 | (todos-edit-item-header-1 'date) | ||
| 4685 | (when todos-always-add-time-string | ||
| 4686 | (todos-edit-item-time))) | ||
| 4687 | |||
| 4688 | (defun todos-edit-item-time () | ||
| 4689 | "Interactively edit the time string of item's date/time header." | ||
| 4493 | (interactive) | 4690 | (interactive) |
| 4494 | (todos-edit-item-header 'dateonly)) | 4691 | (todos-edit-item-header-1 'time)) |
| 4495 | 4692 | ||
| 4496 | (defun todos-edit-item-date-from-calendar () | 4693 | (defun todos-edit-item-date-from-calendar () |
| 4497 | "Prompt for changes to current item's date and apply from Calendar." | 4694 | "Interactively edit item's date using the Calendar." |
| 4498 | (interactive) | 4695 | (interactive) |
| 4499 | (todos-edit-item-header 'calendar)) | 4696 | (todos-edit-item-header-1 'calendar)) |
| 4500 | 4697 | ||
| 4501 | (defun todos-edit-item-date-is-today () | 4698 | (defun todos-edit-item-date-to-today () |
| 4502 | "Set item date to today's date." | 4699 | "Set item's date to today's date." |
| 4503 | (interactive) | 4700 | (interactive) |
| 4504 | (todos-edit-item-header 'today)) | 4701 | (todos-edit-item-header-1 'today)) |
| 4505 | 4702 | ||
| 4506 | (defun todos-edit-item-time () | 4703 | (defun todos-edit-item-date-day-name () |
| 4507 | "Prompt For and apply changes to current item's time." | 4704 | "Replace item's date with the name of a day of the week." |
| 4508 | (interactive) | 4705 | (interactive) |
| 4509 | (todos-edit-item-header 'timeonly)) | 4706 | (todos-edit-item-header-1 'dayname)) |
| 4707 | |||
| 4708 | (defun todos-edit-item-date-year (&optional inc) | ||
| 4709 | "Interactively edit the year of item's date string. | ||
| 4710 | With prefix argument INC a positive or negative integer, | ||
| 4711 | increment or decrement the year by INC." | ||
| 4712 | (interactive "p") | ||
| 4713 | (todos-edit-item-header-1 'year inc)) | ||
| 4714 | |||
| 4715 | (defun todos-edit-item-date-month (&optional inc) | ||
| 4716 | "Interactively edit the month of item's date string. | ||
| 4717 | With prefix argument INC a positive or negative integer, | ||
| 4718 | increment or decrement the month by INC." | ||
| 4719 | (interactive "p") | ||
| 4720 | (todos-edit-item-header-1 'month inc)) | ||
| 4721 | |||
| 4722 | (defun todos-edit-item-date-day (&optional inc) | ||
| 4723 | "Interactively edit the day of the month of item's date string. | ||
| 4724 | With prefix argument INC a positive or negative integer, | ||
| 4725 | increment or decrement the day by INC." | ||
| 4726 | (interactive "p") | ||
| 4727 | (todos-edit-item-header-1 'day inc)) | ||
| 4510 | 4728 | ||
| 4511 | (defun todos-edit-item-diary-inclusion () | 4729 | (defun todos-edit-item-diary-inclusion () |
| 4512 | "Change diary status of one or more todo items in this category. | 4730 | "Change diary status of one or more todo items in this category. |