* PATCH: [PATCH] Allow bulk agenda actions to take log notes
@ 2022-07-10 16:40 Max Mikhanosha
2022-07-17 9:38 ` Ihor Radchenko
0 siblings, 1 reply; 2+ messages in thread
From: Max Mikhanosha @ 2022-07-10 16:40 UTC (permalink / raw)
To: emacs-orgmode
[-- 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
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: PATCH: [PATCH] Allow bulk agenda actions to take log notes
2022-07-10 16:40 PATCH: [PATCH] Allow bulk agenda actions to take log notes Max Mikhanosha
@ 2022-07-17 9:38 ` Ihor Radchenko
0 siblings, 0 replies; 2+ messages in thread
From: Ihor Radchenko @ 2022-07-17 9:38 UTC (permalink / raw)
To: Max Mikhanosha; +Cc: emacs-orgmode
Max Mikhanosha <max.mikhanosha@gmail.com> writes:
> 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.
Thanks for the patch!
I tried to test the patch and ran into an issue using the following
recipe:
1. Create /tmp/bug.org with the following contents:
#+TODO: TODO(t) | DONE(d@)
* TODO this is test
* TODO this is test
* TODO this is test
* TODO this is test
* TODO this is test
* TODO this is test
* TODO this is test
* TODO this is test
2. Open Emacs from Org git repo folder containing the patch applied onto
the latest main branch:
make clean; make autoloads; emacs-29-vcs -Q -L ./lisp /tmp/bug.org
3. Open agenda via M-x org-agenda <RET> < t
4. Move point to one of the TODO entries and change the toto state to
DONE: t DONE <RET>
5. The log window will pop. Enter "something" C-c C-c
6. Mark several TODO entries in the agenda
7. Try to bulk-mark them DONE: B t DONE <RET>
8. Observe no window popup, empty notes, and some entries not
being marked as done in the agenda buffer.
Best,
Ihor
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2022-07-17 9:38 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2022-07-10 16:40 PATCH: [PATCH] Allow bulk agenda actions to take log notes Max Mikhanosha
2022-07-17 9:38 ` Ihor Radchenko
Code repositories for project(s) associated with this public 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).