emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Max Mikhanosha <max.mikhanosha@gmail.com>
To: emacs-orgmode@gnu.org
Subject: PATCH: [PATCH] Allow bulk agenda actions to take log notes
Date: Sun, 10 Jul 2022 12:40:15 -0400	[thread overview]
Message-ID: <CAGAC3hDA1SbciS_r+ik6O+QnnmiLA6L2MRS60KAUj6LLjR_DVg@mail.gmail.com> (raw)


[-- Attachment #1.1: Type: text/plain, Size: 320 bytes --]

Currently org-agenda-bulk-action is completely broken if anything tries to
take a log note during the action, this patch fixes it by storing log note
setup variables in a list, and then taking one log note, and duplicating it
over all affected items.

Please CC me when responding as I'm not subscribed.

Regards,
  Max

[-- Attachment #1.2: Type: text/html, Size: 410 bytes --]

[-- Attachment #2: 0001-Allow-bulk-agenda-actions-to-take-log-notes-a-note-f.patch --]
[-- Type: application/octet-stream, Size: 12787 bytes --]

From a6a08d4a0970b5671e18979d828113fdb67acea8 Mon Sep 17 00:00:00 2001
From: Max Mikhanosha <max.mikhanosha@gmail.com>
Date: Sun, 10 Jul 2022 17:28:34 +0100
Subject: [PATCH] Allow bulk agenda actions to take log notes, a note for bulk
 action is copied to all affected items

  * org-agenda.el (arg-agenda-bulk-action): Save (org-log-note-setup)
    variables in a list until bulk actions are done, then take one log
    note, and apply it to all entries that had called, pass arg to
    custom bulk functions

  * org.el (defvar org-bulk-log-note-list): new variable used to store
    log note setup variables for multiple entries.
    (org--with-log-note-vars): new macro
    (org-store-log-note): is now a wrapper that loops over org-bulk-log-note-list
    if needed, actual code moved to (org--store-log-note)
    (org--store-log-note): new function for storing a single log note
---
 lisp/org-agenda.el |  75 +++++++++++++++++++--------
 lisp/org.el        | 123 +++++++++++++++++++++++++++++++--------------
 2 files changed, 138 insertions(+), 60 deletions(-)

diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el
index 745af50f9..d59a6572b 100644
--- a/lisp/org-agenda.el
+++ b/lisp/org-agenda.el
@@ -11123,8 +11123,12 @@ (defun org-agenda-bulk-action (&optional arg)
 
 	(?f
 	 (setq cmd
-	       (intern
-		(completing-read "Function: " obarray #'fboundp t nil nil))))
+               ;; Make sure to pass ARG to custom bulk functions
+               (let ((fun
+	              (intern
+		       (completing-read "Function: " obarray #'fboundp t nil nil))))
+                 (lambda ()
+                   (if arg (funcall fun arg) (funcall fun))))))
 
 	(action
          (setq cmd
@@ -11149,27 +11153,54 @@ (defun org-agenda-bulk-action (&optional arg)
 
       ;; Now loop over all markers and apply CMD.
       (let ((processed 0)
-	    (skipped 0))
+	    (skipped 0)
+            ;; need to capture bulk agenda buffer because commands changing TODO
+            ;; state and logging it invisibly change current buffer only restoring
+            ;; it in the post-command hook, which made subsequent bulk commands
+            ;; operate on the wrong buffer
+            (bulk-agenda-buffer (current-buffer))
+            (org-bulk-log-note-list nil))
 	(dolist (e entries)
-	  (let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e)))
-	    (if (not pos)
-		(progn (message "Skipping removed entry at %s" e)
-		       (cl-incf skipped))
-	      (goto-char pos)
-	      (let (org-loop-over-headlines-in-active-region) (funcall cmd))
-	      ;; `post-command-hook' is not run yet.  We make sure any
-	      ;; pending log note is processed.
-	      (when org-log-setup (org-add-log-note))
-	      (cl-incf processed))))
-	(when redo-at-end (org-agenda-redo))
-	(unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all))
-	(message "Acted on %d entries%s%s"
-		 processed
-		 (if (= skipped 0)
-		     ""
-		   (format ", skipped %d (disappeared before their turn)"
-			   skipped))
-		 (if (not org-agenda-persistent-marks) "" " (kept marked)"))))))
+          (with-current-buffer bulk-agenda-buffer
+	    (let ((pos (text-property-any (point-min) (point-max) 'org-hd-marker e)))
+	      (if (not pos)
+		  (progn (message "Skipping removed entry at %s" e)
+		         (cl-incf skipped))
+	        (goto-char pos)
+	        (let (org-loop-over-headlines-in-active-region) (funcall cmd))
+                ;; `post-command-hook' is not run yet.  We make sure any
+                ;; pending log note is processed.
+	        (when org-log-setup
+                  (cond ((eq org-log-note-how 'note)
+                         ;; Saving everything we need to take a note on the entry
+                         ;; operated on by the bulk command for after the bulk processing
+                         ;; had finished
+                         (push (list (copy-marker org-log-note-marker)
+                                     org-log-note-purpose
+	                             org-log-note-state
+	                             org-log-note-previous-state
+	                             org-log-note-how
+	                             org-log-note-extra
+	                             org-log-note-effective-time)
+                               org-bulk-log-note-list)
+                         (setq org-log-setup nil))
+                        ;; non-interactive note can be saved immediately
+                        (t (org-add-log-note))))
+	        (cl-incf processed)))))
+        (when org-bulk-log-note-list
+          ;; Now we can safely take a note for entries affected by bulk action
+          (setq org-log-setup t)
+          (org-add-log-note))
+        (with-current-buffer bulk-agenda-buffer 
+	  (when redo-at-end (org-agenda-redo)) 
+	  (unless org-agenda-persistent-marks (org-agenda-bulk-unmark-all)) 
+	  (message "Acted on %d entries%s%s"
+		   processed
+		   (if (= skipped 0)
+		       ""
+		     (format ", skipped %d (disappeared before their turn)"
+			     skipped))
+		   (if (not org-agenda-persistent-marks) "" " (kept marked)")))))))
 
 (defun org-agenda-capture (&optional with-time)
   "Call `org-capture' with the date at point.
diff --git a/lisp/org.el b/lisp/org.el
index 3d4de5b4f..eb5ffc38b 100644
--- a/lisp/org.el
+++ b/lisp/org.el
@@ -9628,6 +9628,7 @@ (defvar org-log-note-purpose)
 (defvar org-log-note-how nil)
 (defvar org-log-note-extra)
 (defvar org-log-setup nil)
+(defvar org-bulk-log-note-list nil)
 (defun org-auto-repeat-maybe (done-word)
   "Check if the current headline contains a repeated time-stamp.
 
@@ -10172,6 +10173,19 @@ (defun org-skip-over-state-notes ()
 	(goto-char (or (org-list-get-next-item (point) struct prevs)
 		       (org-list-get-item-end (point) struct)))))))
 
+(defmacro org--with-log-note-vars (vars &rest body)
+  (declare (indent 1) (debug body))
+  `(cl-destructuring-bind (org-log-note-marker
+                           org-log-note-purpose
+                           org-log-note-state
+                           org-log-note-previous-state
+                           org-log-note-how
+                           org-log-note-extra
+                           org-log-note-effective-time)
+
+       ,vars
+     ,@body))
+
 (defun org-add-log-note (&optional _purpose)
   "Pop up a window for taking a note, and add this note later."
   (remove-hook 'post-command-hook 'org-add-log-note)
@@ -10179,44 +10193,62 @@ (defun org-add-log-note (&optional _purpose)
   (setq org-log-note-window-configuration (current-window-configuration))
   (delete-other-windows)
   (move-marker org-log-note-return-to (point))
-  (pop-to-buffer-same-window (marker-buffer org-log-note-marker))
-  (goto-char org-log-note-marker)
+  (let ((note-marker
+         (or (caar org-bulk-log-note-list) ;; for bulk log note we show first entry affected
+             org-log-note-marker)))
+    (pop-to-buffer-same-window (marker-buffer note-marker))
+    (goto-char org-log-note-marker))
   (org-switch-to-buffer-other-window "*Org Note*")
   (erase-buffer)
   (if (memq org-log-note-how '(time state))
       (org-store-log-note)
     (let ((org-inhibit-startup t)) (org-mode))
-    (insert (format "# Insert note for %s.
-# Finish with C-c C-c, or cancel with C-c C-k.\n\n"
-		    (cl-case org-log-note-purpose
-		      (clock-out "stopped clock")
-		      (done  "closed todo item")
-		      (reschedule "rescheduling")
-		      (delschedule "no longer scheduled")
-		      (redeadline "changing deadline")
-		      (deldeadline "removing deadline")
-		      (refile "refiling")
-		      (note "this entry")
-		      (state
-		       (format "state change from \"%s\" to \"%s\""
-			       (or org-log-note-previous-state "")
-			       (or org-log-note-state "")))
-		      (t (error "This should not happen")))))
-    (when org-log-note-extra (insert org-log-note-extra))
-    (setq-local org-finish-function 'org-store-log-note)
-    (run-hooks 'org-log-buffer-setup-hook)))
+    ;; Global org-agenda-bulk-action is always NIL, its only
+    ;; bound to something if we are called from `org-agenda-bulk-action'.
+    ;; We copy it in the local variable in the note buffer, this
+    ;; allows two log note taking buffers to exists in the same time
+    (setq-local org-bulk-log-note-list org-bulk-log-note-list)
+    (cl-flet ((format-purpose ()
+                              (cl-case org-log-note-purpose
+                                (clock-out "stopped clock")
+                                (done  "closed todo item")
+                                (reschedule "rescheduling")
+                                (delschedule "no longer scheduled")
+                                (redeadline "changing deadline")
+                                (deldeadline "removing deadline")
+                                (refile "refiling")
+                                (note "this entry")
+                                (state
+                                 (format "state change from \"%s\" to \"%s\""
+                                         (or org-log-note-previous-state "")
+                                         (or org-log-note-state "")))
+                                (t (error "This should not happen")))))
+      (cond ((null org-bulk-log-note-list)
+             (insert (format "# Insert note for %s.
+# Finish with C-c C-c, or cancel with C-c C-k.\n\n" (format-purpose)))
+             (when org-log-note-extra (insert org-log-note-extra)))
+            ;; bulk change log note. Top of the log note buffer will show one line for every
+            ;; affected bulk command entry.
+            (t (let (extras)
+                 (dolist (elem org-bulk-log-note-list)
+                   (org--with-log-note-vars elem
+                     (insert (format "# Insert note for %s.\n" (format-purpose)))
+                     (when (and org-log-note-extra
+                                (not (equal org-log-note-extra (car extras))))
+                       (push org-log-note-extra extras))))
+                 (insert "# Finish with C-c C-c, or cancel with C-c C-k.\n\n")
+                 (let ((first t))
+                   (dolist (extra extras)
+                     (unless first (insert "\n"))
+                     (insert extra)
+                     (setq first nil))))))
+      (setq-local org-finish-function 'org-store-log-note)
+      (run-hooks 'org-log-buffer-setup-hook))))
 
 (defvar org-note-abort nil) ; dynamically scoped
-(defun org-store-log-note ()
-  "Finish taking a log note, and insert it to where it belongs."
-  (let ((txt (prog1 (buffer-string)
-	       (kill-buffer)))
-	(note (cdr (assq org-log-note-purpose org-log-note-headings)))
-	lines)
-    (while (string-match "\\`# .*\n[ \t\n]*" txt)
-      (setq txt (replace-match "" t t txt)))
-    (when (string-match "\\s-+\\'" txt)
-      (setq txt (replace-match "" t t txt)))
+(defun org--store-log-note (txt)
+  (let ((note (cdr (assq org-log-note-purpose org-log-note-headings)))
+        lines)
     (setq lines (and (not (equal "" txt)) (org-split-string txt "\n")))
     (when (org-string-nw-p note)
       (setq note
@@ -10285,13 +10317,28 @@ (defun org-store-log-note ()
 	       (indent-line-to ind)
 	       (insert-and-inherit line)))
 	   (message "Note stored")
-	   (org-back-to-heading t))))))
-  ;; Don't add undo information when called from `org-agenda-todo'.
-  (set-window-configuration org-log-note-window-configuration)
-  (with-current-buffer (marker-buffer org-log-note-return-to)
-    (goto-char org-log-note-return-to))
-  (move-marker org-log-note-return-to nil)
-  (when org-log-post-message (message "%s" org-log-post-message)))
+	   (org-back-to-heading t)))))))
+
+(defun org-store-log-note ()
+  "Finish taking a log note, and insert it to where it belongs."
+  (let ((bulk-note-list org-bulk-log-note-list)
+        (txt (prog1 (buffer-string)
+               (kill-buffer))))
+    (while (string-match "\\`# .*\n[ \t\n]*" txt)
+      (setq txt (replace-match "" t t txt)))
+    (when (string-match "\\s-+\\'" txt)
+      (setq txt (replace-match "" t t txt)))
+    (cond ((null bulk-note-list)
+           (org--store-log-note txt))
+          (t (dolist (elem bulk-note-list)
+               (org--with-log-note-vars elem
+                 (org--store-log-note txt)
+                 (move-marker org-log-note-marker nil)))))
+    (set-window-configuration org-log-note-window-configuration)
+    (with-current-buffer (marker-buffer org-log-note-return-to)
+      (goto-char org-log-note-return-to))
+    (move-marker org-log-note-return-to nil)
+    (when org-log-post-message (message "%s" org-log-post-message))))
 
 (defun org-remove-empty-drawer-at (pos)
   "Remove an empty drawer at position POS.
-- 
2.37.0.windows.1


             reply	other threads:[~2022-07-10 18:33 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-07-10 16:40 Max Mikhanosha [this message]
2022-07-17  9:38 ` Ihor Radchenko

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=CAGAC3hDA1SbciS_r+ik6O+QnnmiLA6L2MRS60KAUj6LLjR_DVg@mail.gmail.com \
    --to=max.mikhanosha@gmail.com \
    --cc=emacs-orgmode@gnu.org \
    --subject='Re: PATCH: [PATCH] Allow bulk agenda actions to take log notes' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

Code repositories for project(s) associated with this inbox:

	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).