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 3 years 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
   - ~:refile-targets~ :: Temporarily set ~org-refile-targets~ to the
     value of this property.
     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
 **** Template expansion
 :PROPERTIES:
 :PROPERTIES:
 :DESCRIPTION: Filling in information about time and context.
 :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
 ~org-refile-targets~ will be temporarily bound to the value of this
 template option.
 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=
 *** New startup options =#+startup: show<n>levels=
 
 
 These startup options complement the existing =overview=, =content=,
 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.
  :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
 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
 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
 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)))
 	(format "* Template function %S not found" f)))
      (_ "* Invalid capture template"))))
      (_ "* 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)
 (defun org-capture-finalize (&optional stay-with-capture)
   "Finalize the capture process.
   "Finalize the capture process.
 With prefix argument STAY-WITH-CAPTURE, jump to the location of the
 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)))
 	       (buffer-base-buffer (current-buffer)))
     (error "This does not seem to be a capture buffer for Org mode"))
     (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)
   (run-hooks 'org-capture-prepare-finalize-hook)
 
 
   ;; Update `org-capture-plist' with the buffer-local value.  Since
   ;; Update `org-capture-plist' with the buffer-local value.  Since
@@ -821,6 +848,7 @@ captured item after finalizing."
       ;; the indirect buffer has been killed.
       ;; the indirect buffer has been killed.
       (org-capture-store-last-position)
       (org-capture-store-last-position)
 
 
+      (org-capture--run-template-functions :before-finalize 'local)
       ;; Run the hook
       ;; Run the hook
       (run-hooks 'org-capture-before-finalize-hook))
       (run-hooks 'org-capture-before-finalize-hook))
 
 
@@ -869,6 +897,9 @@ captured item after finalizing."
       ;; Restore the window configuration before capture
       ;; Restore the window configuration before capture
       (set-window-configuration return-wconf))
       (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)
     (run-hooks 'org-capture-after-finalize-hook)
     ;; Special cases
     ;; Special cases
     (cond
     (cond
@@ -1145,6 +1176,7 @@ may have been stored before."
     (`item (org-capture-place-item))
     (`item (org-capture-place-item))
     (`checkitem (org-capture-place-item)))
     (`checkitem (org-capture-place-item)))
   (setq-local org-capture-current-plist org-capture-plist)
   (setq-local org-capture-current-plist org-capture-plist)
+  (org-capture--run-template-functions :hook 'local)
   (org-capture-mode 1))
   (org-capture-mode 1))
 
 
 (defun org-capture-place-entry ()
 (defun org-capture-place-entry ()

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

@@ -754,5 +754,48 @@
 	      (org-capture nil "t")
 	      (org-capture nil "t")
 	      (buffer-string))))))
 	      (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)
 (provide 'test-org-capture)
 ;;; test-org-capture.el ends here
 ;;; test-org-capture.el ends here