Browse Source

org-capture: Add template hook properties

* lisp/org-capture.el (org-capture-templates): Document template hook properties.
(org-capture-finalize): Execute :prepare/:before/:after-finalize functions.
(org-capture-place-template): Execute :hook functions.

* doc/org-manual.org: Document template hook properties.

* etc/ORG-NEWS: Add news entry for template hook properties.

* testing/lisp/test-org-capture.el: Add tests for template hook properties.
Nicholas Vollmer 1 year ago
parent
commit
7f3a6cf6e7
4 changed files with 102 additions and 0 deletions
  1. 20 0
      doc/org-manual.org
  2. 7 0
      etc/ORG-NEWS
  3. 32 0
      lisp/org-capture.el
  4. 43 0
      testing/lisp/test-org-capture.el

+ 20 - 0
doc/org-manual.org

@@ -7929,6 +7929,26 @@ Now lets look at the elements of a template definition.  Each entry in
   - ~:refile-targets~ :: Temporarily set ~org-refile-targets~ to the
     value of this property.
 
+  - ~:hook~ ::
+
+    A nullary function or list of nullary functions run before
+    ~org-capture-mode-hook~ when the template is selected.
+
+ - ~:prepare-finalize~ ::
+
+    A nullary function or list of nullary functions run before
+    ~org-capture-prepare-finalize-hook~ when the template is selected.
+
+ - ~:before-finalize~ ::
+
+    A nullary function or list of nullary functions run before
+    ~org-capture-before-finalize-hook~ when the template is selected.
+
+ - ~:after-finalize~ ::
+
+    A nullary function or list of nullary functions run before
+    ~org-capture-after-finalize-hook~ when the template is selected.
+
 **** Template expansion
 :PROPERTIES:
 :DESCRIPTION: Filling in information about time and context.

+ 7 - 0
etc/ORG-NEWS

@@ -663,6 +663,13 @@ When exiting capture mode via ~org-capture-refile~, the variable
 ~org-refile-targets~ will be temporarily bound to the value of this
 template option.
 
+*** Add Capture template hook properties
+
+Capture templates can now attach template specific hooks via the
+following properties: ~:hook~, ~:prepare-finalize~,
+~:before-finalize~, ~:after-finalize~.  These nullary functions run
+prior to their global counterparts for the selected template.
+
 *** New startup options =#+startup: show<n>levels=
 
 These startup options complement the existing =overview=, =content=,

+ 32 - 0
lisp/org-capture.el

@@ -297,6 +297,21 @@ properties are:
 
  :no-save            Do not save the target file after finishing the capture.
 
+ :hook               A nullary function or list of nullary functions run before
+                     `org-capture-mode-hook' when the template is selected.
+
+ :prepare-finalize   A nullary function or list of nullary functions run before
+                     `org-capture-prepare-finalize-hook'
+                     when the template is selected.
+
+ :before-finalize    A nullary function or list of nullary functions run before
+                     `org-capture-before-finalize-hook'
+                     when the template is selected.
+
+ :after-finalize     A nullary function or list of nullary functions run before
+                     `org-capture-after-finalize-hook'
+                     when the template is selected.
+
 The template defines the text to be inserted.  Often this is an
 Org mode entry (so the first line should start with a star) that
 will be filed as a child of the target headline.  It can also be
@@ -741,6 +756,17 @@ of the day at point (if any) or the current HH:MM time."
 	(format "* Template function %S not found" f)))
      (_ "* Invalid capture template"))))
 
+(defun org-capture--run-template-functions (keyword &optional local)
+  "Run funcitons associated with KEYWORD on template's plist.
+For valid values of KEYWORD see `org-capture-templates'.
+If LOCAL is non-nil use the buffer-local value of `org-capture-plist'."
+  ;; Used in place of `run-hooks' because these functions have no associated symbol.
+  ;; They are stored directly on `org-capture-plist'.
+  (let ((value (org-capture-get keyword local)))
+    (if (functionp value)
+        (funcall value)
+      (mapc #'funcall value))))
+
 (defun org-capture-finalize (&optional stay-with-capture)
   "Finalize the capture process.
 With prefix argument STAY-WITH-CAPTURE, jump to the location of the
@@ -752,6 +778,7 @@ captured item after finalizing."
 	       (buffer-base-buffer (current-buffer)))
     (error "This does not seem to be a capture buffer for Org mode"))
 
+  (org-capture--run-template-functions :prepare-finalize 'local)
   (run-hooks 'org-capture-prepare-finalize-hook)
 
   ;; Update `org-capture-plist' with the buffer-local value.  Since
@@ -821,6 +848,7 @@ captured item after finalizing."
       ;; the indirect buffer has been killed.
       (org-capture-store-last-position)
 
+      (org-capture--run-template-functions :before-finalize 'local)
       ;; Run the hook
       (run-hooks 'org-capture-before-finalize-hook))
 
@@ -869,6 +897,9 @@ captured item after finalizing."
       ;; Restore the window configuration before capture
       (set-window-configuration return-wconf))
 
+    ;; Do not use the local arg to `org-capture--run-template-functions' here.
+    ;; The buffer-local value has been stored on `org-capture-plist'.
+    (org-capture--run-template-functions :after-finalize)
     (run-hooks 'org-capture-after-finalize-hook)
     ;; Special cases
     (cond
@@ -1145,6 +1176,7 @@ may have been stored before."
     (`item (org-capture-place-item))
     (`checkitem (org-capture-place-item)))
   (setq-local org-capture-current-plist org-capture-plist)
+  (org-capture--run-template-functions :hook 'local)
   (org-capture-mode 1))
 
 (defun org-capture-place-entry ()

+ 43 - 0
testing/lisp/test-org-capture.el

@@ -754,5 +754,48 @@
 	      (org-capture nil "t")
 	      (buffer-string))))))
 
+(ert-deftest test-org-capture/template-specific-hooks ()
+  "Test template-specific hook execution."
+  ;; Runs each template hook prior to corresponding global hook
+  (should
+   (equal "hook\nglobal-hook\nprepare\nglobal-prepare
+before\nglobal-before\nafter\nglobal-after"
+          (org-test-with-temp-text-in-file ""
+            (let* ((file (buffer-file-name))
+                   (org-capture-mode-hook
+                    '((lambda () (insert "global-hook\n"))))
+                   (org-capture-prepare-finalize-hook
+                    '((lambda () (insert "global-prepare\n"))))
+                   (org-capture-before-finalize-hook
+                    '((lambda () (insert "global-before\n"))))
+                   (org-capture-after-finalize-hook
+                    '((lambda () (with-current-buffer
+                                     (org-capture-get :buffer)
+                                   (goto-char (point-max))
+                                   (insert "global-after")))))
+                   (org-capture-templates
+                    `(("t" "Test" plain (file ,file) ""
+                       :hook (lambda () (insert "hook\n"))
+                       :prepare-finalize (lambda () (insert "prepare\n"))
+                       :before-finalize (lambda () (insert "before\n"))
+                       :after-finalize (lambda () (with-current-buffer
+                                                      (org-capture-get :buffer)
+                                                    (goto-char (point-max))
+                                                    (insert "after\n")))
+                       :immediate-finish t))))
+              (org-capture nil "t")
+              (buffer-string)))))
+  ;; Accepts a list of nullary functions
+  (should
+   (equal "one\ntwo"
+          (org-test-with-temp-text-in-file ""
+            (let* ((file (buffer-file-name))
+                   (org-capture-templates
+                    `(("t" "Test" plain (file ,file) ""
+                       :hook ((lambda () (insert "one\n"))
+                              (lambda () (insert "two")))))))
+              (org-capture nil "t")
+              (buffer-string))))))
+
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here