aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorStephen Berman2017-07-07 17:48:14 +0200
committerStephen Berman2017-07-07 17:48:14 +0200
commit264dd81d7bf14d39737677af11e1cd3d618ad887 (patch)
tree5a07b1e5b3842787d7997d20c6abb4c967da8597
parent1cf6b1579976227346284033c3e333e53226a350 (diff)
downloademacs-264dd81d7bf14d39737677af11e1cd3d618ad887.tar.gz
emacs-264dd81d7bf14d39737677af11e1cd3d618ad887.zip
todo-mode.el: Fix handling of hidden item headers (bug#27609)
* lisp/calendar/todo-mode.el (todo--item-headers-hidden): New variable. (todo-toggle-item-header): Use it. Make this command a noop if the file has no items. (todo-move-item, todo-item-done): Instead of concatenating the items to move into one string, make a list of them to facilitate handling hidden headers. Adjust insertion accordingly. (todo-archive-done-item): Handle hidden headers in archive file. (todo-unarchive-items): Handle hidden headers in todo file. (todo-backward-item): Use todo--item-headers-hidden and handle moving backward work when item date-time headers are hidden. (todo-remove-item): Delete date-time header overlay. (todo-get-overlay, todo-insert-with-overlays): Make them work with hidden date-time headers. (todo-modes-set-2): Make todo--item-headers-hidden buffer local.
-rw-r--r--lisp/calendar/todo-mode.el185
1 files changed, 124 insertions, 61 deletions
diff --git a/lisp/calendar/todo-mode.el b/lisp/calendar/todo-mode.el
index eb8d3d65eb5..235eb83e85b 100644
--- a/lisp/calendar/todo-mode.el
+++ b/lisp/calendar/todo-mode.el
@@ -1034,29 +1034,41 @@ empty line above the done items separator."
1034 (hl-line-mode -1) 1034 (hl-line-mode -1)
1035 (hl-line-mode 1)))) 1035 (hl-line-mode 1))))
1036 1036
1037(defvar todo--item-headers-hidden nil
1038 "Non-nil if item date-time headers in current buffer are hidden.")
1039
1037(defun todo-toggle-item-header () 1040(defun todo-toggle-item-header ()
1038 "Hide or show item date-time headers in the current file. 1041 "Hide or show item date-time headers in the current file.
1039With done items, this hides only the done date-time string, not 1042With done items, this hides only the done date-time string, not
1040the the original date-time string." 1043the the original date-time string."
1041 (interactive) 1044 (interactive)
1042 (save-excursion 1045 (unless (catch 'nonempty
1043 (save-restriction 1046 (dolist (type '(todo done))
1044 (goto-char (point-min)) 1047 (dolist (c todo-categories)
1045 (let ((ov (todo-get-overlay 'header))) 1048 (let ((count (todo-get-count type (car c))))
1046 (if ov 1049 (unless (zerop count)
1047 (remove-overlays 1 (1+ (buffer-size)) 'todo 'header) 1050 (throw 'nonempty t))))))
1048 (widen) 1051 (user-error "This file has no items"))
1049 (goto-char (point-min)) 1052 (if todo--item-headers-hidden
1050 (while (not (eobp)) 1053 (progn
1051 (when (re-search-forward 1054 (remove-overlays 1 (1+ (buffer-size)) 'todo 'header)
1052 (concat todo-item-start 1055 (setq todo--item-headers-hidden nil))
1053 "\\( " diary-time-regexp "\\)?" 1056 (save-excursion
1054 (regexp-quote todo-nondiary-end) "? ") 1057 (save-restriction
1055 nil t) 1058 (widen)
1056 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t)) 1059 (goto-char (point-min))
1057 (overlay-put ov 'todo 'header) 1060 (let (ov)
1058 (overlay-put ov 'display "")) 1061 (while (not (eobp))
1059 (todo-forward-item))))))) 1062 (when (re-search-forward
1063 (concat todo-item-start
1064 "\\( " diary-time-regexp "\\)?"
1065 (regexp-quote todo-nondiary-end) "? ")
1066 nil t)
1067 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
1068 (overlay-put ov 'todo 'header)
1069 (overlay-put ov 'display ""))
1070 (forward-line)))
1071 (setq todo--item-headers-hidden t)))))
1060 1072
1061;; ----------------------------------------------------------------------------- 1073;; -----------------------------------------------------------------------------
1062;;; File and category editing 1074;;; File and category editing
@@ -2673,7 +2685,7 @@ section in the category moved to."
2673 (num todo-category-number) 2685 (num todo-category-number)
2674 (item (todo-item-string)) 2686 (item (todo-item-string))
2675 (diary-item (todo-diary-item-p)) 2687 (diary-item (todo-diary-item-p))
2676 (done-item (and (todo-done-item-p) (concat item "\n"))) 2688 (done-item (and (todo-done-item-p) item))
2677 (omark (save-excursion (todo-item-start) (point-marker))) 2689 (omark (save-excursion (todo-item-start) (point-marker)))
2678 (todo 0) 2690 (todo 0)
2679 (diary 0) 2691 (diary 0)
@@ -2703,43 +2715,51 @@ section in the category moved to."
2703 (while (not (eobp)) 2715 (while (not (eobp))
2704 (when (todo-marked-item-p) 2716 (when (todo-marked-item-p)
2705 (if (todo-done-item-p) 2717 (if (todo-done-item-p)
2706 (setq done-items (concat done-items 2718 (progn
2707 (todo-item-string) "\n") 2719 (push (todo-item-string) done-items)
2708 done (1+ done)) 2720 (setq done (1+ done)))
2709 (setq todo-items (concat todo-items 2721 (push (todo-item-string) todo-items)
2710 (todo-item-string) "\n") 2722 (setq todo (1+ todo))
2711 todo (1+ todo))
2712 (when (todo-diary-item-p) 2723 (when (todo-diary-item-p)
2713 (setq diary (1+ diary))))) 2724 (setq diary (1+ diary)))))
2714 (todo-forward-item)) 2725 (todo-forward-item))
2715 ;; Chop off last newline of multiple todo item string, 2726 (setq todo-items (nreverse todo-items))
2716 ;; since it will be reinserted when setting priority 2727 (setq done-items (nreverse done-items)))
2717 ;; (but with done items priority is not set, so keep
2718 ;; last newline).
2719 (and todo-items
2720 (setq todo-items (substring todo-items 0 -1))))
2721 (if (todo-done-item-p) 2728 (if (todo-done-item-p)
2722 (setq done 1) 2729 (progn
2723 (setq todo 1) 2730 (push done-item done-items)
2731 (setq done 1))
2732 (push item todo-items)
2733 (setq todo 1)
2724 (when (todo-diary-item-p) (setq diary 1)))) 2734 (when (todo-diary-item-p) (setq diary 1))))
2725 (set-window-buffer (selected-window) 2735 (set-window-buffer (selected-window)
2726 (set-buffer (find-file-noselect file2 'nowarn))) 2736 (set-buffer (find-file-noselect file2 'nowarn)))
2727 (unwind-protect 2737 (unwind-protect
2728 (progn 2738 (let (here)
2729 (when (or todo-items (and item (not done-item))) 2739 (when todo-items
2730 (todo-set-item-priority (or todo-items item) cat2 t)) 2740 (todo-set-item-priority (pop todo-items) cat2 t)
2741 (setq here (point))
2742 (while todo-items
2743 (todo-forward-item)
2744 (todo-insert-with-overlays (pop todo-items))))
2731 ;; Move done items en bloc to top of done items section. 2745 ;; Move done items en bloc to top of done items section.
2732 (when (or done-items done-item) 2746 (when done-items
2733 (todo-category-number cat2) 2747 (todo-category-number cat2)
2734 (widen) 2748 (widen)
2735 (goto-char (point-min)) 2749 (goto-char (point-min))
2736 (re-search-forward 2750 (re-search-forward
2737 (concat "^" (regexp-quote (concat todo-category-beg cat2)) 2751 (concat "^" (regexp-quote (concat todo-category-beg cat2)) "$")
2738 "$") nil t) 2752 nil t)
2739 (re-search-forward 2753 (re-search-forward
2740 (concat "^" (regexp-quote todo-category-done)) nil t) 2754 (concat "^" (regexp-quote todo-category-done)) nil t)
2741 (forward-line) 2755 (forward-line)
2742 (insert (or done-items done-item))) 2756 (unless here (setq here (point)))
2757 (while done-items
2758 (todo-insert-with-overlays (pop done-items))
2759 (todo-forward-item)))
2760 ;; If only done items were moved, move point to the top
2761 ;; one, otherwise, move point to the top moved todo item.
2762 (goto-char here)
2743 (setq moved t)) 2763 (setq moved t))
2744 (cond 2764 (cond
2745 ;; Move succeeded, so remove item from starting category, 2765 ;; Move succeeded, so remove item from starting category,
@@ -2787,7 +2807,7 @@ section in the category moved to."
2787 (set-window-buffer (selected-window) 2807 (set-window-buffer (selected-window)
2788 (set-buffer (find-file-noselect file2 'nowarn))) 2808 (set-buffer (find-file-noselect file2 'nowarn)))
2789 (setq todo-category-number (todo-category-number cat2)) 2809 (setq todo-category-number (todo-category-number cat2))
2790 (let ((todo-show-with-done (or done-items done-item))) 2810 (let ((todo-show-with-done (> done 0)))
2791 (todo-category-select)) 2811 (todo-category-select))
2792 (goto-char nmark) 2812 (goto-char nmark)
2793 ;; If item is moved to end of (just first?) category, make 2813 ;; If item is moved to end of (just first?) category, make
@@ -2836,12 +2856,13 @@ visible."
2836 (goto-char (point-min)) 2856 (goto-char (point-min))
2837 (re-search-forward todo-done-string-start nil t))) 2857 (re-search-forward todo-done-string-start nil t)))
2838 (buffer-read-only nil) 2858 (buffer-read-only nil)
2839 item done-item 2859 header item done-items
2840 (opoint (point))) 2860 (opoint (point)))
2841 ;; Don't add empty comment to done item. 2861 ;; Don't add empty comment to done item.
2842 (setq comment (unless (zerop (length comment)) 2862 (setq comment (unless (zerop (length comment))
2843 (concat " [" todo-comment-string ": " comment "]"))) 2863 (concat " [" todo-comment-string ": " comment "]")))
2844 (and marked (goto-char (point-min))) 2864 (and marked (goto-char (point-min)))
2865 (setq header (todo-get-overlay 'header))
2845 (catch 'done 2866 (catch 'done
2846 ;; Stop looping when we hit the empty line below the last 2867 ;; Stop looping when we hit the empty line below the last
2847 ;; todo item (this is eobp if only done items are hidden). 2868 ;; todo item (this is eobp if only done items are hidden).
@@ -2849,17 +2870,15 @@ visible."
2849 (if (or (not marked) (and marked (todo-marked-item-p))) 2870 (if (or (not marked) (and marked (todo-marked-item-p)))
2850 (progn 2871 (progn
2851 (setq item (todo-item-string)) 2872 (setq item (todo-item-string))
2852 (setq done-item (concat done-item done-prefix item 2873 (push (concat done-prefix item comment) done-items)
2853 comment (and marked "\n")))
2854 (setq item-count (1+ item-count)) 2874 (setq item-count (1+ item-count))
2855 (when (todo-diary-item-p) 2875 (when (todo-diary-item-p)
2856 (setq diary-count (1+ diary-count))) 2876 (setq diary-count (1+ diary-count)))
2857 (todo-remove-item) 2877 (todo-remove-item)
2858 (unless marked (throw 'done nil))) 2878 (unless marked (throw 'done nil)))
2859 (todo-forward-item)))) 2879 (todo-forward-item))))
2880 (setq done-items (nreverse done-items))
2860 (when marked 2881 (when marked
2861 ;; Chop off last newline of done item string.
2862 (setq done-item (substring done-item 0 -1))
2863 (setq todo-categories-with-marks 2882 (setq todo-categories-with-marks
2864 (assq-delete-all cat todo-categories-with-marks))) 2883 (assq-delete-all cat todo-categories-with-marks)))
2865 (save-excursion 2884 (save-excursion
@@ -2868,7 +2887,17 @@ visible."
2868 (concat "^" (regexp-quote todo-category-done)) nil t) 2887 (concat "^" (regexp-quote todo-category-done)) nil t)
2869 (forward-char) 2888 (forward-char)
2870 (when show-done (setq opoint (point))) 2889 (when show-done (setq opoint (point)))
2871 (insert done-item "\n")) 2890 (while done-items
2891 (insert (pop done-items) "\n")
2892 (when header (let ((copy (copy-overlay header)))
2893 (re-search-backward
2894 (concat todo-item-start
2895 "\\( " diary-time-regexp "\\)?"
2896 (regexp-quote todo-nondiary-end) "? ")
2897 nil t)
2898 (move-overlay copy (match-beginning 0) (match-end 0)))
2899 (todo-item-end)
2900 (forward-char))))
2872 (todo-update-count 'todo (- item-count)) 2901 (todo-update-count 'todo (- item-count))
2873 (todo-update-count 'done item-count) 2902 (todo-update-count 'done item-count)
2874 (todo-update-count 'diary (- diary-count)) 2903 (todo-update-count 'diary (- diary-count))
@@ -3095,7 +3124,9 @@ this category does not exist in the archive, it is created."
3095 (throw 'end (message "Only done items can be archived")) 3124 (throw 'end (message "Only done items can be archived"))
3096 (with-current-buffer archive 3125 (with-current-buffer archive
3097 (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode)) 3126 (unless (derived-mode-p 'todo-archive-mode) (todo-archive-mode))
3098 (let (buffer-read-only) 3127 (let ((headers-hidden todo--item-headers-hidden)
3128 buffer-read-only)
3129 (if headers-hidden (todo-toggle-item-header))
3099 (widen) 3130 (widen)
3100 (goto-char (point-min)) 3131 (goto-char (point-min))
3101 (if (and (re-search-forward 3132 (if (and (re-search-forward
@@ -3121,7 +3152,8 @@ this category does not exist in the archive, it is created."
3121 (unless (nth 7 (file-attributes afile)) 3152 (unless (nth 7 (file-attributes afile))
3122 (write-region nil nil afile t t) 3153 (write-region nil nil afile t t)
3123 (setq todo-archives (funcall todo-files-function t)) 3154 (setq todo-archives (funcall todo-files-function t))
3124 (todo-archive-mode)))) 3155 (todo-archive-mode))
3156 (if headers-hidden (todo-toggle-item-header))))
3125 (with-current-buffer tbuf 3157 (with-current-buffer tbuf
3126 (cond 3158 (cond
3127 (all 3159 (all
@@ -3200,7 +3232,9 @@ the only category in the archive, the archive file is deleted."
3200 (todo-forward-item)))) 3232 (todo-forward-item))))
3201 ;; Restore items to top of category's done section and update counts. 3233 ;; Restore items to top of category's done section and update counts.
3202 (with-current-buffer tbuf 3234 (with-current-buffer tbuf
3203 (let (buffer-read-only newcat) 3235 (let ((headers-hidden todo--item-headers-hidden)
3236 buffer-read-only newcat)
3237 (if headers-hidden (todo-toggle-item-header))
3204 (widen) 3238 (widen)
3205 (goto-char (point-min)) 3239 (goto-char (point-min))
3206 ;; Find the corresponding todo category, or if there isn't 3240 ;; Find the corresponding todo category, or if there isn't
@@ -3224,6 +3258,7 @@ the only category in the archive, the archive file is deleted."
3224 (todo-update-count 'done 1 cat) 3258 (todo-update-count 'done 1 cat)
3225 (unless newcat ; Newly added category has no archive. 3259 (unless newcat ; Newly added category has no archive.
3226 (todo-update-count 'archived -1 cat)))) 3260 (todo-update-count 'archived -1 cat))))
3261 (if headers-hidden (todo-toggle-item-header))
3227 (todo-update-categories-sexp))) 3262 (todo-update-categories-sexp)))
3228 ;; Delete restored items from archive. 3263 ;; Delete restored items from archive.
3229 (when marked 3264 (when marked
@@ -5156,7 +5191,17 @@ empty line above the done items separator."
5156 (let* ((done (todo-done-item-p))) 5191 (let* ((done (todo-done-item-p)))
5157 (todo-item-start) 5192 (todo-item-start)
5158 (unless (bobp) 5193 (unless (bobp)
5159 (re-search-backward todo-item-start nil t (or count 1))) 5194 (re-search-backward (concat todo-item-start
5195 "\\( " diary-time-regexp "\\)?"
5196 (regexp-quote todo-nondiary-end) "? ")
5197 nil t (or count 1))
5198 ;; If the item date-time header is hidden, the display engine
5199 ;; moves point to the next earlier displayable position, which
5200 ;; is the end of the next item above, so we move it to the start
5201 ;; of the current item's text (that's what the display engine
5202 ;; does with todo-forward-item in this case.)
5203 ;; FIXME: would it be better to use cursor-sensor-functions?
5204 (when todo--item-headers-hidden (goto-char (match-end 0))))
5160 ;; Unless this is a regexp filtered items buffer (which can contain 5205 ;; Unless this is a regexp filtered items buffer (which can contain
5161 ;; intermixed todo and done items), if points advances by one from a 5206 ;; intermixed todo and done items), if points advances by one from a
5162 ;; done to a todo item, go back to the space above 5207 ;; done to a todo item, go back to the space above
@@ -5172,10 +5217,12 @@ empty line above the done items separator."
5172 5217
5173(defun todo-remove-item () 5218(defun todo-remove-item ()
5174 "Internal function called in editing, deleting or moving items." 5219 "Internal function called in editing, deleting or moving items."
5175 (let* ((end (progn (todo-item-end) (1+ (point)))) 5220 (let ((end (progn (todo-item-end) (1+ (point))))
5176 (beg (todo-item-start)) 5221 (beg (todo-item-start))
5177 (ov (todo-get-overlay 'prefix))) 5222 ovs)
5178 (when ov (delete-overlay ov)) 5223 (push (todo-get-overlay 'prefix) ovs)
5224 (push (todo-get-overlay 'header) ovs)
5225 (dolist (ov ovs) (when ov (delete-overlay ov)))
5179 (delete-region beg end))) 5226 (delete-region beg end)))
5180 5227
5181(defun todo-diary-item-p () 5228(defun todo-diary-item-p ()
@@ -5309,6 +5356,11 @@ marked) not done todo items."
5309 5356
5310(defun todo-get-overlay (val) 5357(defun todo-get-overlay (val)
5311 "Return the overlay at point whose `todo' property has value VAL." 5358 "Return the overlay at point whose `todo' property has value VAL."
5359 ;; When headers are hidden, the display engine makes item's start
5360 ;; inaccessible to commands, so go there here, if necessary, in
5361 ;; order to check for prefix and header overlays.
5362 (when (memq val '(prefix header))
5363 (unless (looking-at todo-item-start) (todo-item-start)))
5312 ;; Use overlays-in to find prefix overlays and check over two 5364 ;; Use overlays-in to find prefix overlays and check over two
5313 ;; positions to find done separator overlay. 5365 ;; positions to find done separator overlay.
5314 (let ((ovs (overlays-in (point) (1+ (point)))) 5366 (let ((ovs (overlays-in (point) (1+ (point))))
@@ -5333,16 +5385,26 @@ In that case, return the item's prefix overlay."
5333 (when marked ov))) 5385 (when marked ov)))
5334 5386
5335(defun todo-insert-with-overlays (item) 5387(defun todo-insert-with-overlays (item)
5336 "Insert ITEM at point and update prefix/priority number overlays." 5388 "Insert ITEM at point and update prefix and header overlays."
5337 (todo-item-start) 5389 (todo-item-start)
5338 ;; Insertion pushes item down but not its prefix overlay. When the
5339 ;; overlay includes a mark, this would now mark the inserted ITEM,
5340 ;; so move it to the pushed down item.
5341 (let ((ov (todo-get-overlay 'prefix)) 5390 (let ((ov (todo-get-overlay 'prefix))
5342 (marked (todo-marked-item-p))) 5391 (marked (todo-marked-item-p)))
5343 (insert item "\n") 5392 (insert item "\n")
5344 (when marked (move-overlay ov (point) (point)))) 5393 ;; Insertion pushes item down but not its prefix overlay. When
5345 (todo-backward-item) 5394 ;; the overlay includes a mark, this would now mark the inserted
5395 ;; ITEM, so move it to the pushed down item.
5396 (when marked (move-overlay ov (point) (point)))
5397 (todo-backward-item)
5398 ;; With hidden headers, todo-backward-item puts point on first
5399 ;; visible character after header, so we have to search backward.
5400 (when todo--item-headers-hidden
5401 (re-search-backward (concat todo-item-start
5402 "\\( " diary-time-regexp "\\)?"
5403 (regexp-quote todo-nondiary-end) "? ")
5404 nil t)
5405 (setq ov (make-overlay (match-beginning 0) (match-end 0) nil t))
5406 (overlay-put ov 'todo 'header)
5407 (overlay-put ov 'display "")))
5346 (todo-prefix-overlays)) 5408 (todo-prefix-overlays))
5347 5409
5348(defun todo-prefix-overlays () 5410(defun todo-prefix-overlays ()
@@ -6607,6 +6669,7 @@ Added to `window-configuration-change-hook' in Todo mode."
6607 "Make some settings that apply to multiple Todo modes." 6669 "Make some settings that apply to multiple Todo modes."
6608 (add-to-invisibility-spec 'todo) 6670 (add-to-invisibility-spec 'todo)
6609 (setq buffer-read-only t) 6671 (setq buffer-read-only t)
6672 (setq-local todo--item-headers-hidden nil)
6610 (setq-local desktop-save-buffer 'todo-desktop-save-buffer) 6673 (setq-local desktop-save-buffer 'todo-desktop-save-buffer)
6611 (setq-local hl-line-range-function 'todo-hl-line-range)) 6674 (setq-local hl-line-range-function 'todo-hl-line-range))
6612 6675