summaryrefslogtreecommitdiff
path: root/lisp/org/org-capture.el
diff options
context:
space:
mode:
authorRasmus <rasmus@gmx.us>2017-06-21 13:20:20 +0200
committerRasmus <rasmus@gmx.us>2017-06-22 11:54:18 +0200
commit5cecd275820df825c51bf9a27fcc7e35f30ff273 (patch)
treeb3f72e63953613d565e6d5a35bec97f158eb603c /lisp/org/org-capture.el
parent386a3da920482b8cb3e962fb944d135c8a770e26 (diff)
downloademacs-5cecd275820df825c51bf9a27fcc7e35f30ff273.tar.gz
Update Org to v9.0.9
Please see etc/ORG-NEWS for details.
Diffstat (limited to 'lisp/org/org-capture.el')
-rw-r--r--lisp/org/org-capture.el1117
1 files changed, 592 insertions, 525 deletions
diff --git a/lisp/org/org-capture.el b/lisp/org/org-capture.el
index b302113f3e8..63e23cc118b 100644
--- a/lisp/org/org-capture.el
+++ b/lisp/org/org-capture.el
@@ -1,4 +1,4 @@
-;;; org-capture.el --- Fast note taking in Org-mode
+;;; org-capture.el --- Fast note taking in Org -*- lexical-binding: t; -*-
;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
@@ -47,23 +47,22 @@
;;; Code:
-(eval-when-compile
- (require 'cl))
+(require 'cl-lib)
(require 'org)
+(declare-function org-at-encrypted-entry-p "org-crypt" ())
(declare-function org-datetree-find-date-create "org-datetree"
(date &optional keep-restriction))
-(declare-function org-table-get-specials "org-table" ())
-(declare-function org-table-goto-line "org-table" (N))
-(declare-function org-pop-to-buffer-same-window "org-compat"
- (&optional buffer-or-name norecord label))
-(declare-function org-at-encrypted-entry-p "org-crypt" ())
-(declare-function org-encrypt-entry "org-crypt" ())
(declare-function org-decrypt-entry "org-crypt" ())
+(declare-function org-encrypt-entry "org-crypt" ())
+(declare-function org-table-analyze "org-table" ())
+(declare-function org-table-goto-line "org-table" (N))
+(defvar org-end-time-was-given)
(defvar org-remember-default-headline)
(defvar org-remember-templates)
(defvar org-table-hlines)
+(defvar org-table-current-begin-pos)
(defvar dired-buffers)
(defvar org-capture-clock-was-started nil
@@ -76,6 +75,9 @@
;; to indicate that the link properties have already been stored
(defvar org-capture-link-is-already-stored nil)
+(defvar org-capture-is-refiling nil
+ "Non-nil when capture process is refiling an entry.")
+
(defgroup org-capture nil
"Options concerning capturing new entries."
:tag "Org Capture"
@@ -103,9 +105,9 @@ description A short string describing the template, will be shown during
selection.
type The type of entry. Valid types are:
- entry an Org-mode node, with a headline. Will be
- filed as the child of the target entry or as
- a top-level entry.
+ entry an Org node, with a headline. Will be filed
+ as the child of the target entry or as a
+ top-level entry.
item a plain list item, will be placed in the
first plain list at the target
location.
@@ -116,21 +118,22 @@ type The type of entry. Valid types are:
plain text to be inserted as it is.
target Specification of where the captured item should be placed.
- In Org-mode files, targets usually define a node. Entries will
+ In Org files, targets usually define a node. Entries will
become children of this node, other types will be added to the
table or list in the body of this node.
Most target specifications contain a file name. If that file
name is the empty string, it defaults to `org-default-notes-file'.
A file can also be given as a variable, function, or Emacs Lisp
- form.
+ form. When an absolute path is not specified for a
+ target, it is taken as relative to `org-directory'.
Valid values are:
(file \"path/to/file\")
Text will be placed at the beginning or end of that file
- (id \"id of existing org entry\")
+ (id \"id of existing Org entry\")
File as child of this entry, or in the body of the entry
(file+headline \"path/to/file\" \"node headline\")
@@ -148,6 +151,12 @@ target Specification of where the captured item should be placed.
(file+datetree+prompt \"path/to/file\")
Will create a heading in a date tree, prompts for date
+ (file+weektree \"path/to/file\")
+ Will create a heading in a week tree for today's date
+
+ (file+weektree+prompt \"path/to/file\")
+ Will create a heading in a week tree, prompts for date
+
(file+function \"path/to/file\" function-finding-location)
A function to find the right location in the file
@@ -155,8 +164,8 @@ target Specification of where the captured item should be placed.
File to the entry that is currently being clocked
(function function-finding-location)
- Most general way, write your own function to find both
- file and location
+ Most general way: write your own function which both visits
+ the file and moves point to the right location
template The template for creating the capture item. If you leave this
empty, an appropriate default template will be used. See below
@@ -218,15 +227,20 @@ properties are:
is finalized.
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
freely formatted text. Furthermore, the following %-escapes will
-be replaced with content and expanded in this order:
+be replaced with content and expanded:
- %[pathname] Insert the contents of the file given by `pathname'.
+ %[pathname] Insert the contents of the file given by
+ `pathname'. These placeholders are expanded at the very
+ beginning of the process so they can be used to extend the
+ current template.
%(sexp) Evaluate elisp `(sexp)' and replace it with the results.
- For convenience, %:keyword (see below) placeholders within
- the expression will be expanded prior to this.
+ Only placeholders pre-existing within the template, or
+ introduced with %[pathname] are expanded this way. Since this
+ happens after expanding non-interactive %-escapes, those can
+ be used to fill the expression.
%<...> The result of format-time-string on the ... format specification.
%t Time stamp, date only.
%T Time stamp with date and time.
@@ -255,8 +269,8 @@ be replaced with content and expanded in this order:
A default value and a completion table ca be specified like this:
%^{prompt|default|completion2|completion3|...}.
%? After completing the template, position cursor here.
- %\\n Insert the text entered at the nth %^{prompt}, where `n' is
- a number, starting from 1.
+ %\\1 ... %\\N Insert the text entered at the nth %^{prompt}, where N
+ is a number, starting from 1.
Apart from these general escapes, you can access information specific to
the link type that is created. For example, calling `org-capture' in emails
@@ -274,13 +288,21 @@ gnus | %:from %:fromname %:fromaddress
| %:date %:date-timestamp (as active timestamp)
| %:date-timestamp-inactive (as inactive timestamp)
gnus | %:group, for messages also all email fields
-w3, w3m | %:type %:url
+eww, w3, w3m | %:type %:url
info | %:type %:file %:node
-calendar | %:type %:date"
+calendar | %:type %:date
+
+When you need to insert a literal percent sign in the template,
+you can escape ambiguous cases with a backward slash, e.g., \\%i."
:group 'org-capture
:version "24.1"
:type
- '(repeat
+ (let ((file-variants '(choice :tag "Filename "
+ (file :tag "Literal")
+ (function :tag "Function")
+ (variable :tag "Variable")
+ (sexp :tag "Form"))))
+ `(repeat
(choice :value ("" "" entry (file "~/org/notes.org") "")
(list :tag "Multikey description"
(string :tag "Keys ")
@@ -297,39 +319,45 @@ calendar | %:type %:date"
(choice :tag "Target location"
(list :tag "File"
(const :format "" file)
- (file :tag " File"))
+ ,file-variants)
(list :tag "ID"
(const :format "" id)
(string :tag " ID"))
(list :tag "File & Headline"
(const :format "" file+headline)
- (file :tag " File ")
+ ,file-variants
(string :tag " Headline"))
(list :tag "File & Outline path"
(const :format "" file+olp)
- (file :tag " File ")
+ ,file-variants
(repeat :tag "Outline path" :inline t
(string :tag "Headline")))
(list :tag "File & Regexp"
(const :format "" file+regexp)
- (file :tag " File ")
+ ,file-variants
(regexp :tag " Regexp"))
(list :tag "File & Date tree"
(const :format "" file+datetree)
- (file :tag " File"))
+ ,file-variants)
(list :tag "File & Date tree, prompt for date"
(const :format "" file+datetree+prompt)
- (file :tag " File"))
+ ,file-variants)
+ (list :tag "File & Week tree"
+ (const :format "" file+weektree)
+ ,file-variants)
+ (list :tag "File & Week tree, prompt for date"
+ (const :format "" file+weektree+prompt)
+ ,file-variants)
(list :tag "File & function"
(const :format "" file+function)
- (file :tag " File ")
+ ,file-variants
(sexp :tag " Function"))
(list :tag "Current clocking task"
(const :format "" clock))
(list :tag "Function"
(const :format "" function)
(sexp :tag " Function")))
- (choice :tag "Template"
+ (choice :tag "Template "
(string)
(list :tag "File"
(const :format "" file)
@@ -350,7 +378,7 @@ calendar | %:type %:date"
((const :format "%v " :clock-resume) (const t))
((const :format "%v " :unnarrowed) (const t))
((const :format "%v " :table-line-pos) (const t))
- ((const :format "%v " :kill-buffer) (const t))))))))
+ ((const :format "%v " :kill-buffer) (const t)))))))))
(defcustom org-capture-before-finalize-hook nil
"Hook that is run right before a capture process is finalized.
@@ -421,7 +449,7 @@ to avoid conflicts with other active capture processes."
(defvar org-capture-mode-map (make-sparse-keymap)
"Keymap for `org-capture-mode', a minor mode.
-Use this map to set additional keybindings for when Org-mode is used
+Use this map to set additional keybindings for when Org mode is used
for a capture buffer.")
(defvar org-capture-mode-hook nil
@@ -432,10 +460,12 @@ for a capture buffer.")
Turning on this mode runs the normal hook `org-capture-mode-hook'."
nil " Rem" org-capture-mode-map
- (org-set-local
- 'header-line-format
+ (setq-local
+ header-line-format
(substitute-command-keys
- "Capture buffer. Finish `C-c C-c', refile `C-c C-w', abort `C-c C-k'.")))
+ "\\<org-capture-mode-map>Capture buffer. Finish \
+`\\[org-capture-finalize]', refile `\\[org-capture-refile]', \
+abort `\\[org-capture-kill]'.")))
(define-key org-capture-mode-map "\C-c\C-c" 'org-capture-finalize)
(define-key org-capture-mode-map "\C-c\C-k" 'org-capture-kill)
(define-key org-capture-mode-map "\C-c\C-w" 'org-capture-refile)
@@ -460,7 +490,7 @@ For example, if you have a capture template \"c\" and you want
this template to be accessible only from `message-mode' buffers,
use this:
- ((\"c\" ((in-mode . \"message-mode\"))))
+ \\='((\"c\" ((in-mode . \"message-mode\"))))
Here are the available contexts definitions:
@@ -478,7 +508,7 @@ accessible if there is at least one valid check.
You can also bind a key to another agenda custom command
depending on contextual rules.
- ((\"c\" \"d\" ((in-mode . \"message-mode\"))))
+ \\='((\"c\" \"d\" ((in-mode . \"message-mode\"))))
Here it means: in `message-mode buffers', use \"c\" as the
key for the capture template otherwise associated with \"d\".
@@ -504,7 +534,8 @@ to avoid duplicates.)"
(defcustom org-capture-use-agenda-date nil
"Non-nil means use the date at point when capturing from agendas.
-When nil, you can still capture using the date at point with \\[org-agenda-capture]."
+When nil, you can still capture using the date at point with
+`\\[org-agenda-capture]'."
:group 'org-capture
:version "24.3"
:type 'boolean)
@@ -513,17 +544,20 @@ When nil, you can still capture using the date at point with \\[org-agenda-captu
(defun org-capture (&optional goto keys)
"Capture something.
\\<org-capture-mode-map>
-This will let you select a template from `org-capture-templates', and then
-file the newly captured information. The text is immediately inserted
-at the target location, and an indirect buffer is shown where you can
-edit it. Pressing \\[org-capture-finalize] brings you back to the previous state
-of Emacs, so that you can continue your work.
-
-When called interactively with a \\[universal-argument] prefix argument GOTO, don't capture
-anything, just go to the file/headline where the selected template
-stores its notes. With a double prefix argument \
-\\[universal-argument] \\[universal-argument], go to the last note
-stored.
+This will let you select a template from `org-capture-templates', and
+then file the newly captured information. The text is immediately
+inserted at the target location, and an indirect buffer is shown where
+you can edit it. Pressing `\\[org-capture-finalize]' brings you back to the \
+previous
+state of Emacs, so that you can continue your work.
+
+When called interactively with a `\\[universal-argument]' prefix argument \
+GOTO, don't
+capture anything, just go to the file/headline where the selected
+template stores its notes.
+
+With a `\\[universal-argument] \\[universal-argument]' prefix argument, go to \
+the last note stored.
When called with a `C-0' (zero) prefix, insert a template at point.
@@ -564,7 +598,7 @@ of the day at point (if any) or the current HH:MM time."
((equal entry "C")
(customize-variable 'org-capture-templates))
((equal entry "q")
- (error "Abort"))
+ (user-error "Abort"))
(t
(org-capture-set-plist entry)
(org-capture-get-template)
@@ -596,10 +630,10 @@ of the day at point (if any) or the current HH:MM time."
(org-capture-insert-template-here)
(condition-case error
(org-capture-place-template
- (equal (car (org-capture-get :target)) 'function))
+ (eq (car (org-capture-get :target)) 'function))
((error quit)
(if (and (buffer-base-buffer (current-buffer))
- (string-match "\\`CAPTURE-" (buffer-name)))
+ (string-prefix-p "CAPTURE-" (buffer-name)))
(kill-buffer (current-buffer)))
(set-window-configuration (org-capture-get :return-to-wconf))
(error "Capture template `%s': %s"
@@ -613,7 +647,7 @@ of the day at point (if any) or the current HH:MM time."
(org-capture-put :interrupted-clock
(copy-marker org-clock-marker)))
(org-clock-in)
- (org-set-local 'org-capture-clock-was-started t))
+ (setq-local org-capture-clock-was-started t))
(error
"Could not start the clock in this capture buffer")))
(if (org-capture-get :immediate-finish)
@@ -646,7 +680,7 @@ captured item after finalizing."
(setq stay-with-capture t))
(unless (and org-capture-mode
(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"))
(run-hooks 'org-capture-prepare-finalize-hook)
@@ -682,23 +716,13 @@ captured item after finalizing."
(m2 (org-capture-get :end-marker 'local)))
(if (and m1 m2 (= m1 beg) (= m2 end))
(progn
- (setq m2 (if (cdr (assoc 'heading org-blank-before-new-entry))
+ (setq m2 (if (cdr (assq 'heading org-blank-before-new-entry))
m2 (1+ m2))
m2 (if (< (point-max) m2) (point-max) m2))
(setq abort-note 'clean)
(kill-region m1 m2))
(setq abort-note 'dirty)))
- ;; Make sure that the empty lines after are correct
- (when (and (> (point-max) end) ; indeed, the buffer was still narrowed
- (member (org-capture-get :type 'local)
- '(entry item checkitem plain)))
- (save-excursion
- (goto-char end)
- (or (bolp) (newline))
- (org-capture-empty-lines-after
- (or (org-capture-get :empty-lines-after 'local)
- (org-capture-get :empty-lines 'local) 0))))
;; Postprocessing: Update Statistics cookies, do the sorting
(when (derived-mode-p 'org-mode)
(save-excursion
@@ -715,8 +739,7 @@ captured item after finalizing."
;; Store this place as the last one where we stored something
;; Do the marking in the base buffer, so that it makes sense after
;; the indirect buffer has been killed.
- (when org-capture-bookmark
- (org-capture-bookmark-last-stored-position))
+ (org-capture-store-last-position)
;; Run the hook
(run-hooks 'org-capture-before-finalize-hook))
@@ -770,11 +793,12 @@ captured item after finalizing."
;; Special cases
(cond
(abort-note
- (cond
- ((equal abort-note 'clean)
- (message "Capture process aborted and target buffer cleaned up"))
- ((equal abort-note 'dirty)
- (error "Capture process aborted, but target buffer could not be cleaned up correctly"))))
+ (cl-case abort-note
+ (clean
+ (message "Capture process aborted and target buffer cleaned up"))
+ (dirty
+ (error "Capture process aborted, but target buffer could not be \
+cleaned up correctly"))))
(stay-with-capture
(org-capture-goto-last-stored)))
;; Return if we did store something
@@ -786,19 +810,28 @@ Refiling is done from the base buffer, because the indirect buffer is then
already gone. Any prefix argument will be passed to the refile command."
(interactive)
(unless (eq (org-capture-get :type 'local) 'entry)
- (error
- "Refiling from a capture buffer makes only sense for `entry'-type templates"))
- (let ((pos (point))
- (base (buffer-base-buffer (current-buffer)))
- (org-refile-for-capture t))
- (save-window-excursion
- (with-current-buffer (or base (current-buffer))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char pos)
- (call-interactively 'org-refile)))))
- (org-capture-finalize)))
+ (user-error "Refiling from a capture buffer makes only sense \
+for `entry'-type templates"))
+ (let* ((base (or (buffer-base-buffer) (current-buffer)))
+ (pos (make-marker))
+ (org-capture-is-refiling t)
+ (kill-buffer (org-capture-get :kill-buffer 'local)))
+ ;; Since `org-capture-finalize' may alter buffer contents (e.g.,
+ ;; empty lines) around entry, use a marker to refer to the
+ ;; headline to be refiled. Place the marker in the base buffer,
+ ;; as the current indirect one is going to be killed.
+ (set-marker pos (save-excursion (org-back-to-heading t) (point)) base)
+ (org-capture-put :kill-buffer nil)
+ (unwind-protect
+ (progn
+ (org-capture-finalize)
+ (save-window-excursion
+ (with-current-buffer base
+ (org-with-wide-buffer
+ (goto-char pos)
+ (call-interactively 'org-refile))))
+ (when kill-buffer (kill-buffer base)))
+ (set-marker pos nil))))
(defun org-capture-kill ()
"Abort the current capture process."
@@ -813,7 +846,8 @@ already gone. Any prefix argument will be passed to the refile command."
"Go to the location where the last capture note was stored."
(interactive)
(org-goto-marker-or-bmk org-capture-last-stored-marker
- "org-capture-last-stored")
+ (plist-get org-bookmark-names-plist
+ :last-capture))
(message "This is the last note stored by a capture process"))
;;; Supporting functions for handling the process
@@ -823,7 +857,7 @@ already gone. Any prefix argument will be passed to the refile command."
(org-capture-put
:initial-target-region
;; Check if the buffer is currently narrowed
- (when (/= (buffer-size) (- (point-max) (point-min)))
+ (when (org-buffer-narrowed-p)
(cons (point-min) (point-max))))
;; store the current point
(org-capture-put :initial-target-position (point)))
@@ -853,14 +887,14 @@ Store them in the capture property list."
((eq (car target) 'file+headline)
(set-buffer (org-capture-target-buffer (nth 1 target)))
+ (unless (derived-mode-p 'org-mode)
+ (error
+ "Target buffer \"%s\" for file+headline should be in Org mode"
+ (current-buffer)))
(org-capture-put-target-region-and-position)
(widen)
(let ((hd (nth 2 target)))
(goto-char (point-min))
- (unless (derived-mode-p 'org-mode)
- (error
- "Target buffer \"%s\" for file+headline should be in Org mode"
- (current-buffer)))
(if (re-search-forward
(format org-complex-heading-regexp-format (regexp-quote hd))
nil t)
@@ -892,21 +926,29 @@ Store them in the capture property list."
(setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))
(error "No match for target regexp in file %s" (nth 1 target))))
- ((memq (car target) '(file+datetree file+datetree+prompt))
+ ((memq (car target) '(file+datetree file+datetree+prompt file+weektree file+weektree+prompt))
(require 'org-datetree)
(set-buffer (org-capture-target-buffer (nth 1 target)))
+ (unless (derived-mode-p 'org-mode)
+ (error "Target buffer \"%s\" for %s should be in Org mode"
+ (current-buffer)
+ (car target)))
(org-capture-put-target-region-and-position)
(widen)
- ;; Make a date tree entry, with the current date (or yesterday,
- ;; if we are extending dates for a couple of hours)
- (org-datetree-find-date-create
+ ;; Make a date/week tree entry, with the current date (or
+ ;; yesterday, if we are extending dates for a couple of hours)
+ (funcall
+ (cond
+ ((memq (car target) '(file+weektree file+weektree+prompt))
+ #'org-datetree-find-iso-week-create)
+ (t #'org-datetree-find-date-create))
(calendar-gregorian-from-absolute
(cond
(org-overriding-default-time
;; use the overriding default time
(time-to-days org-overriding-default-time))
- ((eq (car target) 'file+datetree+prompt)
+ ((memq (car target) '(file+datetree+prompt file+weektree+prompt))
;; prompt for date
(let ((prompt-time (org-read-date
nil t nil "Date for tree entry:"
@@ -917,7 +959,9 @@ Store them in the capture property list."
(not org-time-was-given))
(not (= (time-to-days prompt-time) (org-today))))
;; Use 00:00 when no time is given for another date than today?
- (apply 'encode-time (append '(0 0 0) (cdddr (decode-time prompt-time)))))
+ (apply #'encode-time
+ (append '(0 0 0)
+ (cl-cdddr (decode-time prompt-time)))))
((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)" org-read-date-final-answer)
;; Replace any time range by its start
(apply 'encode-time
@@ -964,31 +1008,31 @@ Store them in the capture property list."
:decrypted decrypted-hl-pos))))
(defun org-capture-expand-file (file)
- "Expand functions and symbols for FILE.
+ "Expand functions, symbols and file names for FILE.
When FILE is a function, call it. When it is a form, evaluate
-it. When it is a variable, retrieve the value. Return whatever we get."
+it. When it is a variable, retrieve the value. When it is
+a string, treat it as a file name, possibly expanding it
+according to `org-directory', and return it. If it is the empty
+string, however, return `org-default-notes-file'. In any other
+case, raise an error."
(cond
- ((org-string-nw-p file) file)
+ ((equal file "") org-default-notes-file)
+ ((stringp file) (expand-file-name file org-directory))
((functionp file) (funcall file))
((and (symbolp file) (boundp file)) (symbol-value file))
- ((and file (consp file)) (eval file))
+ ((consp file) (eval file))
(t file)))
(defun org-capture-target-buffer (file)
- "Get a buffer for FILE."
- (setq file (org-capture-expand-file file))
- (setq file (or (org-string-nw-p file)
- org-default-notes-file
- (error "No notes file specified, and no default available")))
- (or (org-find-base-buffer-visiting file)
- (progn (org-capture-put :new-buffer t)
- (find-file-noselect (expand-file-name file org-directory)))))
-
-(defun org-capture-steal-local-variables (buffer)
- "Install Org-mode local variables of BUFFER."
- (mapc (lambda (v)
- (ignore-errors (org-set-local (car v) (cdr v))))
- (buffer-local-variables buffer)))
+ "Get a buffer for FILE.
+FILE is a generalized file location, as handled by
+`org-capture-expand-file'."
+ (let ((file (or (org-string-nw-p (org-capture-expand-file file))
+ org-default-notes-file
+ (error "No notes file specified, and no default available"))))
+ (or (org-find-base-buffer-visiting file)
+ (progn (org-capture-put :new-buffer t)
+ (find-file-noselect file)))))
(defun org-capture-place-template (&optional inhibit-wconf-store)
"Insert the template at the target location, and display the buffer.
@@ -1000,65 +1044,52 @@ may have been stored before."
(org-switch-to-buffer-other-window
(org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE"))
(widen)
- (show-all)
+ (outline-show-all)
(goto-char (org-capture-get :pos))
- (org-set-local 'org-capture-target-marker
- (point-marker))
- (org-set-local 'outline-level 'org-outline-level)
- (let* ((template (org-capture-get :template))
- (type (org-capture-get :type)))
- (case type
- ((nil entry) (org-capture-place-entry))
- (table-line (org-capture-place-table-line))
- (plain (org-capture-place-plain-text))
- (item (org-capture-place-item))
- (checkitem (org-capture-place-item))))
+ (setq-local outline-level 'org-outline-level)
+ (pcase (org-capture-get :type)
+ ((or `nil `entry) (org-capture-place-entry))
+ (`table-line (org-capture-place-table-line))
+ (`plain (org-capture-place-plain-text))
+ (`item (org-capture-place-item))
+ (`checkitem (org-capture-place-item)))
(org-capture-mode 1)
- (org-set-local 'org-capture-current-plist org-capture-plist))
+ (setq-local org-capture-current-plist org-capture-plist))
(defun org-capture-place-entry ()
"Place the template as a new Org entry."
- (let* ((txt (org-capture-get :template))
- (reversed (org-capture-get :prepend))
- (target-entry-p (org-capture-get :target-entry-p))
- level beg end file)
-
- (cond
- ((org-capture-get :exact-position)
+ (let ((reversed? (org-capture-get :prepend))
+ level)
+ (when (org-capture-get :exact-position)
(goto-char (org-capture-get :exact-position)))
- ((not target-entry-p)
- ;; Insert as top-level entry, either at beginning or at end of file
- (setq level 1)
- (if reversed
- (progn (goto-char (point-min))
- (or (org-at-heading-p)
- (outline-next-heading)))
- (goto-char (point-max))
- (or (bolp) (insert "\n"))))
- (t
- ;; Insert as a child of the current entry
- (and (looking-at "\\*+")
- (setq level (- (match-end 0) (match-beginning 0))))
- (setq level (org-get-valid-level (or level 1) 1))
- (if reversed
- (progn
- (outline-next-heading)
- (or (bolp) (insert "\n")))
- (org-end-of-subtree t nil)
- (or (bolp) (insert "\n")))))
+ (cond
+ ;; Insert as a child of the current entry.
+ ((org-capture-get :target-entry-p)
+ (setq level (org-get-valid-level
+ (if (org-at-heading-p) (org-outline-level) 1)
+ 1))
+ (if reversed? (outline-next-heading) (org-end-of-subtree t)))
+ ;; Insert as a top-level entry at the beginning of the file.
+ (reversed?
+ (goto-char (point-min))
+ (unless (org-at-heading-p) (outline-next-heading)))
+ ;; Otherwise, insert as a top-level entry at the end of the file.
+ (t (goto-char (point-max))))
+ (unless (bolp) (insert "\n"))
(org-capture-empty-lines-before)
- (setq beg (point))
- (org-capture-verify-tree txt)
- (org-paste-subtree level txt 'for-yank)
- (org-capture-empty-lines-after 1)
- (org-capture-position-for-last-stored beg)
- (outline-next-heading)
- (setq end (point))
- (org-capture-mark-kill-region beg (1- end))
- (org-capture-narrow beg (1- end))
- (if (or (re-search-backward "%\\?" beg t)
- (re-search-forward "%\\?" end t))
- (replace-match ""))))
+ (let ((beg (point))
+ (template (org-capture-get :template)))
+ (org-capture-verify-tree template)
+ (org-paste-subtree level template 'for-yank)
+ (org-capture-empty-lines-after)
+ (org-capture-position-for-last-stored beg)
+ (unless (org-at-heading-p) (outline-next-heading))
+ (let ((end (point)))
+ (org-capture-mark-kill-region beg end)
+ (org-capture-narrow beg end)
+ (when (or (re-search-backward "%\\?" beg t)
+ (re-search-forward "%\\?" end t))
+ (replace-match ""))))))
(defun org-capture-place-item ()
"Place the template as a new plain list item."
@@ -1075,21 +1106,18 @@ may have been stored before."
(t
(setq beg (1+ (point-at-eol))
end (save-excursion (outline-next-heading) (point)))))
+ (setq ind nil)
(if (org-capture-get :prepend)
(progn
(goto-char beg)
- (if (org-list-search-forward (org-item-beginning-re) end t)
- (progn
- (goto-char (match-beginning 0))
- (setq ind (org-get-indentation)))
- (goto-char end)
- (setq ind 0)))
+ (when (org-list-search-forward (org-item-beginning-re) end t)
+ (goto-char (match-beginning 0))
+ (setq ind (org-get-indentation))))
(goto-char end)
- (if (org-list-search-backward (org-item-beginning-re) beg t)
- (progn
- (setq ind (org-get-indentation))
- (org-end-of-item))
- (setq ind 0))))
+ (when (org-list-search-backward (org-item-beginning-re) beg t)
+ (setq ind (org-get-indentation))
+ (org-end-of-item)))
+ (unless ind (goto-char end)))
;; Remove common indentation
(setq txt (org-remove-indentation txt))
;; Make sure this is indeed an item
@@ -1097,18 +1125,23 @@ may have been stored before."
(setq txt (concat "- "
(mapconcat 'identity (split-string txt "\n")
"\n "))))
+ ;; Prepare surrounding empty lines.
+ (org-capture-empty-lines-before)
+ (setq beg (point))
+ (unless (eolp) (save-excursion (insert "\n")))
+ (unless ind
+ (org-indent-line)
+ (setq ind (org-get-indentation))
+ (delete-region beg (point)))
;; Set the correct indentation, depending on context
(setq ind (make-string ind ?\ ))
(setq txt (concat ind
(mapconcat 'identity (split-string txt "\n")
(concat "\n" ind))
"\n"))
- ;; Insert, with surrounding empty lines
- (org-capture-empty-lines-before)
- (setq beg (point))
+ ;; Insert item.
(insert txt)
- (or (bolp) (insert "\n"))
- (org-capture-empty-lines-after 1)
+ (org-capture-empty-lines-after)
(org-capture-position-for-last-stored beg)
(forward-char 1)
(setq end (point))
@@ -1124,7 +1157,7 @@ may have been stored before."
(let* ((txt (org-capture-get :template))
(target-entry-p (org-capture-get :target-entry-p))
(table-line-pos (org-capture-get :table-line-pos))
- ind beg end)
+ beg end)
(cond
((org-capture-get :exact-position)
(goto-char (org-capture-get :exact-position)))
@@ -1149,21 +1182,24 @@ may have been stored before."
;; Check if the template is good
(if (not (string-match org-table-dataline-regexp txt))
(setq txt "| %?Bad template |\n"))
+ (if (functionp table-line-pos)
+ (setq table-line-pos (funcall table-line-pos))
+ (setq table-line-pos (eval table-line-pos)))
(cond
((and table-line-pos
(string-match "\\(I+\\)\\([-+][0-9]\\)" table-line-pos))
- ;; we have a complex line specification
(goto-char (point-min))
- (let ((nh (- (match-end 1) (match-beginning 1)))
- (delta (string-to-number (match-string 2 table-line-pos)))
- ll)
+ ;; we have a complex line specification
+ (let ((ll (ignore-errors
+ (save-match-data (org-table-analyze))
+ (aref org-table-hlines
+ (- (match-end 1) (match-beginning 1)))))
+ (delta (string-to-number (match-string 2 table-line-pos))))
;; The user wants a special position in the table
- (org-table-get-specials)
- (setq ll (ignore-errors (aref org-table-hlines nh)))
- (unless ll (error "Invalid table line specification \"%s\""
- table-line-pos))
- (setq ll (+ ll delta (if (< delta 0) 0 -1)))
- (org-goto-line ll)
+ (unless ll
+ (error "Invalid table line specification \"%s\"" table-line-pos))
+ (goto-char org-table-current-begin-pos)
+ (forward-line (+ ll delta (if (< delta 0) 0 -1)))
(org-table-insert-row 'below)
(beginning-of-line 1)
(delete-region (point) (1+ (point-at-eol)))
@@ -1216,7 +1252,7 @@ Of course, if exact position has been required, just put it there."
;; we should place the text into this entry
(if (org-capture-get :prepend)
;; Skip meta data and drawers
- (org-end-of-meta-data-and-drawers)
+ (org-end-of-meta-data t)
;; go to ent of the entry text, before the next headline
(outline-next-heading)))
(t
@@ -1226,7 +1262,7 @@ Of course, if exact position has been required, just put it there."
(org-capture-empty-lines-before)
(setq beg (point))
(insert txt)
- (org-capture-empty-lines-after 1)
+ (org-capture-empty-lines-after)
(org-capture-position-for-last-stored beg)
(setq end (point))
(org-capture-mark-kill-region beg (1- end))
@@ -1256,8 +1292,8 @@ Of course, if exact position has been required, just put it there."
(org-table-current-dline))))
(t (error "This should not happen"))))
-(defun org-capture-bookmark-last-stored-position ()
- "Bookmark the last-captured position."
+(defun org-capture-store-last-position ()
+ "Store the last-captured position."
(let* ((where (org-capture-get :position-for-last-stored 'local))
(pos (cond
((markerp where)
@@ -1270,16 +1306,11 @@ Of course, if exact position has been required, just put it there."
(point-at-bol))
(point))))))
(with-current-buffer (buffer-base-buffer (current-buffer))
- (save-excursion
- (save-restriction
- (widen)
- (goto-char pos)
- (let ((bookmark-name (plist-get org-bookmark-names-plist
- :last-capture)))
- (when bookmark-name
- (with-demoted-errors
- (bookmark-set bookmark-name))))
- (move-marker org-capture-last-stored-marker (point)))))))
+ (org-with-point-at pos
+ (when org-capture-bookmark
+ (let ((bookmark (plist-get org-bookmark-names-plist :last-capture)))
+ (when bookmark (with-demoted-errors (bookmark-set bookmark)))))
+ (move-marker org-capture-last-stored-marker (point))))))
(defun org-capture-narrow (beg end)
"Narrow, unless configuration says not to narrow."
@@ -1315,7 +1346,7 @@ Point will remain at the first line after the inserted text."
(let* ((template (org-capture-get :template))
(type (org-capture-get :type))
beg end pp)
- (or (bolp) (newline))
+ (unless (bolp) (insert "\n"))
(setq beg (point))
(cond
((and (eq type 'entry) (derived-mode-p 'org-mode))
@@ -1337,13 +1368,16 @@ Point will remain at the first line after the inserted text."
(org-capture-empty-lines-after)
(goto-char beg)
(org-list-repair)
- (org-end-of-item)
- (setq end (point)))
- (t (insert template)))
+ (org-end-of-item))
+ (t
+ (insert template)
+ (org-capture-empty-lines-after)
+ (skip-chars-forward " \t\n")
+ (unless (eobp) (beginning-of-line))))
(setq end (point))
(goto-char beg)
- (if (re-search-forward "%\\?" end t)
- (replace-match ""))))
+ (when (re-search-forward "%\\?" end t)
+ (replace-match ""))))
(defun org-capture-set-plist (entry)
"Initialize the property list from the template definition."
@@ -1365,13 +1399,11 @@ Point will remain at the first line after the inserted text."
"Go to the target location of a capture template.
The user is queried for the template."
(interactive)
- (let* (org-select-template-temp-major-mode
- (entry (org-capture-select-template template-key)))
- (unless entry
- (error "No capture template selected"))
+ (let ((entry (org-capture-select-template template-key)))
+ (unless entry (error "No capture template selected"))
(org-capture-set-plist entry)
(org-capture-set-target-location)
- (org-pop-to-buffer-same-window (org-capture-get :buffer))
+ (pop-to-buffer-same-window (org-capture-get :buffer))
(goto-char (org-capture-get :pos))))
(defun org-capture-get-indirect-buffer (&optional buffer prefix)
@@ -1381,7 +1413,7 @@ Use PREFIX as a prefix for the name of the indirect buffer."
(let ((n 1) (base (buffer-name buffer)) bname)
(setq bname (concat prefix "-" base))
(while (buffer-live-p (get-buffer bname))
- (setq bname (concat prefix "-" (number-to-string (incf n)) "-" base)))
+ (setq bname (concat prefix "-" (number-to-string (cl-incf n)) "-" base)))
(condition-case nil
(make-indirect-buffer buffer bname 'clone)
(error
@@ -1396,6 +1428,7 @@ Use PREFIX as a prefix for the name of the indirect buffer."
(defun org-mks (table title &optional prompt specials)
"Select a member of an alist with multiple keys.
+
TABLE is the alist which should contain entries where the car is a string.
There should be two types of entries.
@@ -1403,7 +1436,7 @@ There should be two types of entries.
This indicates that `a' is a prefix key for multi-letter selection, and
that there are entries following with keys like \"ab\", \"ax\"...
-2. Selectable members must have more than two elements, with the first
+2. Select-able members must have more than two elements, with the first
being the string of keys that lead to selecting it, and the second a
short description string of the item.
@@ -1414,84 +1447,72 @@ When you press a prefix key, the commands (and maybe further prefixes)
under this key will be shown and offered for selection.
TITLE will be placed over the selection in the temporary buffer,
-PROMPT will be used when prompting for a key. SPECIAL is an alist with
-also (\"key\" \"description\") entries. When one of these is selection,
-only the bare key is returned."
- (setq prompt (or prompt "Select: "))
- (let (tbl orig-table dkey ddesc des-keys allowed-keys
- current prefix rtn re pressed buffer (inhibit-quit t))
- (save-window-excursion
- (setq buffer (org-switch-to-buffer-other-window "*Org Select*"))
- (setq orig-table table)
- (catch 'exit
- (while t
- (erase-buffer)
- (insert title "\n\n")
- (setq tbl table
- des-keys nil
- allowed-keys nil
- cursor-type nil)
- (setq prefix (if current (concat current " ") ""))
- (while tbl
- (cond
- ((and (= 2 (length (car tbl))) (= (length (caar tbl)) 1))
- ;; This is a description on this level
- (setq dkey (caar tbl) ddesc (cadar tbl))
- (pop tbl)
- (push dkey des-keys)
- (push dkey allowed-keys)
- (insert prefix "[" dkey "]" "..." " " ddesc "..." "\n")
- ;; Skip keys which are below this prefix
- (setq re (concat "\\`" (regexp-quote dkey)))
- (let (case-fold-search)
- (while (and tbl (string-match re (caar tbl))) (pop tbl))))
- ((= 2 (length (car tbl)))
- ;; Not yet a usable description, skip it
- )
- (t
- ;; usable entry on this level
- (insert prefix "[" (caar tbl) "]" " " (nth 1 (car tbl)) "\n")
- (push (caar tbl) allowed-keys)
- (pop tbl))))
- (when specials
- (insert "-------------------------------------------------------------------------------\n")
- (let ((sp specials))
- (while sp
- (insert (format "[%s] %s\n"
- (caar sp) (nth 1 (car sp))))
- (push (caar sp) allowed-keys)
- (pop sp))))
- (push "\C-g" allowed-keys)
- (goto-char (point-min))
- (if (not (pos-visible-in-window-p (point-max)))
- (org-fit-window-to-buffer))
- (message prompt)
- (setq pressed (char-to-string (read-char-exclusive)))
- (while (not (member pressed allowed-keys))
- (message "Invalid key `%s'" pressed) (sit-for 1)
- (message prompt)
- (setq pressed (char-to-string (read-char-exclusive))))
- (when (equal pressed "\C-g")
- (kill-buffer buffer)
- (error "Abort"))
- (when (and (not (assoc pressed table))
- (not (member pressed des-keys))
- (assoc pressed specials))
- (throw 'exit (setq rtn pressed)))
- (unless (member pressed des-keys)
- (throw 'exit (setq rtn (rassoc (cdr (assoc pressed table))
- orig-table))))
- (setq current (concat current pressed))
- (setq table (mapcar
- (lambda (x)
- (if (and (> (length (car x)) 1)
- (equal (substring (car x) 0 1) pressed))
- (cons (substring (car x) 1) (cdr x))
- nil))
- table))
- (setq table (remove nil table)))))
- (when buffer (kill-buffer buffer))
- rtn))
+PROMPT will be used when prompting for a key. SPECIAL is an
+alist with (\"key\" \"description\") entries. When one of these
+is selected, only the bare key is returned."
+ (save-window-excursion
+ (let ((inhibit-quit t)
+ (buffer (org-switch-to-buffer-other-window "*Org Select*"))
+ (prompt (or prompt "Select: "))
+ current)
+ (unwind-protect
+ (catch 'exit
+ (while t
+ (erase-buffer)
+ (insert title "\n\n")
+ (let ((des-keys nil)
+ (allowed-keys '("\C-g"))
+ (cursor-type nil))
+ ;; Populate allowed keys and descriptions keys
+ ;; available with CURRENT selector.
+ (let ((re (format "\\`%s\\(.\\)\\'"
+ (if current (regexp-quote current) "")))
+ (prefix (if current (concat current " ") "")))
+ (dolist (entry table)
+ (pcase entry
+ ;; Description.
+ (`(,(and key (pred (string-match re))) ,desc)
+ (let ((k (match-string 1 key)))
+ (push k des-keys)
+ (push k allowed-keys)
+ (insert prefix "[" k "]" "..." " " desc "..." "\n")))
+ ;; Usable entry.
+ (`(,(and key (pred (string-match re))) ,desc . ,_)
+ (let ((k (match-string 1 key)))
+ (insert prefix "[" k "]" " " desc "\n")
+ (push k allowed-keys)))
+ (_ nil))))
+ ;; Insert special entries, if any.
+ (when specials
+ (insert "----------------------------------------------------\
+---------------------------\n")
+ (pcase-dolist (`(,key ,description) specials)
+ (insert (format "[%s] %s\n" key description))
+ (push key allowed-keys)))
+ ;; Display UI and let user select an entry or
+ ;; a sub-level prefix.
+ (goto-char (point-min))
+ (unless (pos-visible-in-window-p (point-max))
+ (org-fit-window-to-buffer))
+ (message prompt)
+ (let ((pressed (char-to-string (read-char-exclusive))))
+ (while (not (member pressed allowed-keys))
+ (message "Invalid key `%s'" pressed) (sit-for 1)
+ (message prompt)
+ (setq pressed (char-to-string (read-char-exclusive))))
+ (setq current (concat current pressed))
+ (cond
+ ((equal pressed "\C-g") (user-error "Abort"))
+ ;; Selection is a prefix: open a new menu.
+ ((member pressed des-keys))
+ ;; Selection matches an association: return it.
+ ((let ((entry (assoc current table)))
+ (and entry (throw 'exit entry))))
+ ;; Selection matches a special entry: return the
+ ;; selection prefix.
+ ((assoc current specials) (throw 'exit current))
+ (t (error "No entry available")))))))
+ (when buffer (kill-buffer buffer))))))
;;; The template code
(defun org-capture-select-template (&optional keys)
@@ -1511,46 +1532,41 @@ Lisp programs can force the template by setting KEYS to a string."
'(("C" "Customize org-capture-templates")
("q" "Abort"))))))
+(defvar org-capture--clipboards nil
+ "List various clipboards values.")
+
(defun org-capture-fill-template (&optional template initial annotation)
"Fill a template and return the filled template as a string.
The template may still contain \"%?\" for cursor positioning."
- (setq template (or template (org-capture-get :template)))
- (when (stringp initial)
- (setq initial (org-no-properties initial)))
- (let* ((buffer (org-capture-get :buffer))
+ (let* ((template (or template (org-capture-get :template)))
+ (buffer (org-capture-get :buffer))
(file (buffer-file-name (or (buffer-base-buffer buffer) buffer)))
- (ct (org-capture-get :default-time))
- (dct (decode-time ct))
- (ct1
- (if (< (nth 2 dct) org-extend-today-until)
- (encode-time 0 59 23 (1- (nth 3 dct)) (nth 4 dct) (nth 5 dct))
- ct))
- (plist-p (if org-store-link-plist t nil))
- (v-c (and (> (length kill-ring) 0) (current-kill 0)))
+ (time (let* ((c (or (org-capture-get :default-time) (current-time)))
+ (d (decode-time c)))
+ (if (< (nth 2 d) org-extend-today-until)
+ (encode-time 0 59 23 (1- (nth 3 d)) (nth 4 d) (nth 5 d))
+ c)))
+ (v-t (format-time-string (org-time-stamp-format nil) time))
+ (v-T (format-time-string (org-time-stamp-format t) time))
+ (v-u (format-time-string (org-time-stamp-format nil t) time))
+ (v-U (format-time-string (org-time-stamp-format t t) time))
+ (v-c (and kill-ring (current-kill 0)))
(v-x (or (org-get-x-clipboard 'PRIMARY)
(org-get-x-clipboard 'CLIPBOARD)
(org-get-x-clipboard 'SECONDARY)))
- (v-t (format-time-string (car org-time-stamp-formats) ct1))
- (v-T (format-time-string (cdr org-time-stamp-formats) ct1))
- (v-u (concat "[" (substring v-t 1 -1) "]"))
- (v-U (concat "[" (substring v-T 1 -1) "]"))
- ;; `initial' and `annotation' might habe been passed.
- ;; But if the property list has them, we prefer those values
+ ;; `initial' and `annotation' might have been passed. But if
+ ;; the property list has them, we prefer those values.
(v-i (or (plist-get org-store-link-plist :initial)
- initial
+ (and (stringp initial) (org-no-properties initial))
(org-capture-get :initial)
""))
- (v-a (or (plist-get org-store-link-plist :annotation)
- annotation
- (org-capture-get :annotation)
- ""))
- ;; Is the link empty? Then we do not want it...
- (v-a (if (equal v-a "[[]]") "" v-a))
- (clipboards (remove nil (list v-i
- (org-get-x-clipboard 'PRIMARY)
- (org-get-x-clipboard 'CLIPBOARD)
- (org-get-x-clipboard 'SECONDARY)
- v-c)))
+ (v-a
+ (let ((a (or (plist-get org-store-link-plist :annotation)
+ annotation
+ (org-capture-get :annotation)
+ "")))
+ ;; Is the link empty? Then we do not want it...
+ (if (equal a "[[]]") "" a)))
(l-re "\\[\\[\\(.*?\\)\\]\\(\\[.*?\\]\\)?\\]")
(v-A (if (and v-a (string-match l-re v-a))
(replace-match "[[\\1][%^{Link description}]]" nil nil v-a)
@@ -1559,202 +1575,260 @@ The template may still contain \"%?\" for cursor positioning."
(replace-match "\\1" nil nil v-a)
v-a))
(v-n user-full-name)
- (v-k (if (marker-buffer org-clock-marker)
- (org-no-properties org-clock-heading)))
+ (v-k (and (marker-buffer org-clock-marker)
+ (org-no-properties org-clock-heading)))
(v-K (if (marker-buffer org-clock-marker)
(org-make-link-string
(buffer-file-name (marker-buffer org-clock-marker))
org-clock-heading)))
(v-f (or (org-capture-get :original-file-nondirectory) ""))
(v-F (or (org-capture-get :original-file) ""))
- v-I
- (org-startup-folded nil)
- (org-inhibit-startup t)
- org-time-was-given org-end-time-was-given x
- prompt completions char time pos default histvar strings)
-
- (setq org-store-link-plist
- (plist-put org-store-link-plist :annotation v-a)
- org-store-link-plist
- (plist-put org-store-link-plist :initial v-i))
- (setq initial v-i)
-
- (unless template (setq template "") (message "No template") (ding)
- (sit-for 1))
+ (org-capture--clipboards
+ (delq nil
+ (list v-i
+ (org-get-x-clipboard 'PRIMARY)
+ (org-get-x-clipboard 'CLIPBOARD)
+ (org-get-x-clipboard 'SECONDARY)
+ v-c))))
+
+ (setq org-store-link-plist (plist-put org-store-link-plist :annotation v-a))
+ (setq org-store-link-plist (plist-put org-store-link-plist :initial v-i))
+
+ (unless template
+ (setq template "")
+ (message "no template") (ding)
+ (sit-for 1))
(save-window-excursion
- (delete-other-windows)
- (org-pop-to-buffer-same-window (get-buffer-create "*Capture*"))
+ (org-switch-to-buffer-other-window (get-buffer-create "*Capture*"))
(erase-buffer)
+ (setq buffer-file-name nil)
+ (setq mark-active nil)
(insert template)
(goto-char (point-min))
- (org-capture-steal-local-variables buffer)
- (setq buffer-file-name nil mark-active nil)
- ;; %[] Insert contents of a file.
- (goto-char (point-min))
- (while (re-search-forward "%\\[\\(.+\\)\\]" nil t)
- (unless (org-capture-escaped-%)
- (let ((start (match-beginning 0))
- (end (match-end 0))
- (filename (expand-file-name (match-string 1))))
- (goto-char start)
- (delete-region start end)
- (condition-case error
- (insert-file-contents filename)
- (error (insert (format "%%![Could not insert %s: %s]"
- filename error)))))))
- ;; %() embedded elisp
- (org-capture-expand-embedded-elisp)
+ ;; %[] insert contents of a file.
+ (save-excursion
+ (while (re-search-forward "%\\[\\(.+\\)\\]" nil t)
+ (let ((filename (expand-file-name (match-string 1)))
+ (beg (copy-marker (match-beginning 0)))
+ (end (copy-marker (match-end 0))))
+ (unless (org-capture-escaped-%)
+ (delete-region beg end)
+ (set-marker beg nil)
+ (set-marker end nil)
+ (condition-case error
+ (insert-file-contents filename)
+ (error
+ (insert (format "%%![couldn not insert %s: %s]"
+ filename
+ error))))))))
- ;; The current time
- (goto-char (point-min))
- (while (re-search-forward "%<\\([^>\n]+\\)>" nil t)
- (replace-match (format-time-string (match-string 1)) t t))
+ ;; Mark %() embedded elisp for later evaluation.
+ (org-capture-expand-embedded-elisp 'mark)
- ;; Simple %-escapes
- (goto-char (point-min))
- (while (re-search-forward "%\\([tTuUaliAcxkKInfF]\\)" nil t)
- (unless (org-capture-escaped-%)
- (when (and initial (equal (match-string 0) "%i"))
- (save-match-data
- (let* ((lead (buffer-substring
- (point-at-bol) (match-beginning 0))))
- (setq v-i (mapconcat 'identity
- (org-split-string initial "\n")
- (concat "\n" lead))))))
- (replace-match (or (eval (intern (concat "v-" (match-string 1)))) "")
- t t)))
-
- ;; From the property list
- (when plist-p
- (goto-char (point-min))
- (while (re-search-forward "%\\(:[-a-zA-Z]+\\)" nil t)
- (unless (org-capture-escaped-%)
- (and (setq x (or (plist-get org-store-link-plist
- (intern (match-string 1))) ""))
- (replace-match x t t)))))
-
- ;; Turn on org-mode in temp buffer, set local variables
- ;; This is to support completion in interactive prompts
+ ;; Expand non-interactive templates.
+ (let ((regexp "%\\(:[-a-za-z]+\\|<\\([^>\n]+\\)>\\|[aAcfFikKlntTuUx]\\)"))
+ (save-excursion
+ (while (re-search-forward regexp nil t)
+ ;; `org-capture-escaped-%' may modify buffer and cripple
+ ;; match-data. Use markers instead. Ditto for other
+ ;; templates.
+ (let ((pos (copy-marker (match-beginning 0)))
+ (end (copy-marker (match-end 0)))
+ (value (match-string 1))
+ (time-string (match-string 2)))
+ (unless (org-capture-escaped-%)
+ (delete-region pos end)
+ (set-marker pos nil)
+ (set-marker end nil)
+ (let* ((inside-sexp? (org-capture-inside-embedded-elisp-p))
+ (replacement
+ (pcase (string-to-char value)
+ (?< (format-time-string time-string))
+ (?:
+ (or (plist-get org-store-link-plist (intern value))
+ ""))
+ (?i
+ (if inside-sexp? v-i
+ ;; Outside embedded Lisp, repeat leading
+ ;; characters before initial place holder
+ ;; every line.
+ (let ((lead (buffer-substring-no-properties
+ (line-beginning-position) (point))))
+ (replace-regexp-in-string "\n\\(.\\)"
+ (concat lead "\\1")
+ v-i nil nil 1))))
+ (?a v-a)
+ (?A v-A)
+ (?c v-c)
+ (?f v-f)
+ (?F v-F)
+ (?k v-k)
+ (?K v-K)
+ (?l v-l)
+ (?n v-n)
+ (?t v-t)
+ (?T v-T)
+ (?u v-u)
+ (?U v-U)
+ (?x v-x))))
+ (insert
+ (if inside-sexp?
+ ;; Escape sensitive characters.
+ (replace-regexp-in-string "[\\\"]" "\\\\\\&" replacement)
+ replacement))))))))
+
+ ;; Expand %() embedded Elisp. Limit to Sexp originally marked.
+ (org-capture-expand-embedded-elisp)
+
+ ;; Expand interactive templates. This is the last step so that
+ ;; template is mostly expanded when prompting happens. Turn on
+ ;; Org mode and set local variables. This is to support
+ ;; completion in interactive prompts.
(let ((org-inhibit-startup t)) (org-mode))
- ;; Interactive template entries
- (goto-char (point-min))
- (while (re-search-forward "%^\\({\\([^}]*\\)}\\)?\\([gGtTuUCLp]\\)?" nil t)
- (unless (org-capture-escaped-%)
- (setq char (if (match-end 3) (match-string-no-properties 3))
- prompt (if (match-end 2) (match-string-no-properties 2)))
- (goto-char (match-beginning 0))
- (replace-match "")
- (setq completions nil default nil)
- (when prompt
- (setq completions (org-split-string prompt "|")
- prompt (pop completions)
- default (car completions)
- histvar (intern (concat
- "org-capture-template-prompt-history::"
- (or prompt "")))
- completions (mapcar 'list completions)))
- (unless (boundp histvar) (set histvar nil))
- (cond
- ((member char '("G" "g"))
- (let* ((org-last-tags-completion-table
- (org-global-tags-completion-table
- (if (equal char "G")
- (org-agenda-files)
- (and file (list file)))))
- (org-add-colon-after-tag-completion t)
- (ins (org-icompleting-read
- (if prompt (concat prompt ": ") "Tags: ")
- 'org-tags-completion-function nil nil nil
- 'org-tags-history)))
- (setq ins (mapconcat 'identity
- (org-split-string
- ins (org-re "[^[:alnum:]_@#%]+"))
- ":"))
- (when (string-match "\\S-" ins)
- (or (equal (char-before) ?:) (insert ":"))
- (insert ins)
- (or (equal (char-after) ?:) (insert ":"))
- (and (org-at-heading-p)
- (let ((org-ignore-region t))
- (org-set-tags nil 'align))))))
- ((equal char "C")
- (cond ((= (length clipboards) 1) (insert (car clipboards)))
- ((> (length clipboards) 1)
- (insert (read-string "Clipboard/kill value: "
- (car clipboards) '(clipboards . 1)
- (car clipboards))))))
- ((equal char "L")
- (cond ((= (length clipboards) 1)
- (org-insert-link 0 (car clipboards)))
- ((> (length clipboards) 1)
- (org-insert-link 0 (read-string "Clipboard/kill value: "
- (car clipboards)
- '(clipboards . 1)
- (car clipboards))))))
- ((equal char "p")
- (org-set-property (org-no-properties prompt) nil))
- (char
- ;; These are the date/time related ones
- (setq org-time-was-given (equal (upcase char) char))
- (setq time (org-read-date (equal (upcase char) char) t nil
- prompt))
- (if (equal (upcase char) char) (setq org-time-was-given t))
- (org-insert-time-stamp time org-time-was-given
- (member char '("u" "U"))
- nil nil (list org-end-time-was-given)))
- (t
- (let (org-completion-use-ido)
- (push (org-completing-read-no-i
- (concat (if prompt prompt "Enter string")
- (if default (concat " [" default "]"))
- ": ")
- completions nil nil nil histvar default)
- strings)
- (insert (car strings)))))))
- ;; Replace %n escapes with nth %^{...} string
- (setq strings (nreverse strings))
- (goto-char (point-min))
- (while (re-search-forward "%\\\\\\([1-9][0-9]*\\)" nil t)
- (unless (org-capture-escaped-%)
- (replace-match
- (nth (1- (string-to-number (match-string 1))) strings)
- nil t)))
+ (org-clone-local-variables buffer "\\`org-")
+ (let (strings) ; Stores interactive answers.
+ (save-excursion
+ (let ((regexp "%\\^\\(?:{\\([^}]*\\)}\\)?\\([CgGLptTuU]\\)?"))
+ (while (re-search-forward regexp nil t)
+ (let* ((items (and (match-end 1)
+ (save-match-data
+ (split-string (match-string-no-properties 1)
+ "|"))))
+ (key (match-string 2))
+ (beg (copy-marker (match-beginning 0)))
+ (end (copy-marker (match-end 0)))
+ (prompt (nth 0 items))
+ (default (nth 1 items))
+ (completions (nthcdr 2 items)))
+ (unless (org-capture-escaped-%)
+ (delete-region beg end)
+ (set-marker beg nil)
+ (set-marker end nil)
+ (pcase key
+ ((or "G" "g")
+ (let* ((org-last-tags-completion-table
+ (org-global-tags-completion-table
+ (cond ((equal key "G") (org-agenda-files))
+ (file (list file))
+ (t nil))))
+ (org-add-colon-after-tag-completion t)
+ (ins (mapconcat
+ #'identity
+ (org-split-string
+ (completing-read
+ (if prompt (concat prompt ": ") "Tags: ")
+ 'org-tags-completion-function nil nil nil
+ 'org-tags-history)
+ "[^[:alnum:]_@#%]+")
+ ":")))
+ (when (org-string-nw-p ins)
+ (unless (eq (char-before) ?:) (insert ":"))
+ (insert ins)
+ (unless (eq (char-after) ?:) (insert ":"))
+ (and (org-at-heading-p)
+ (let ((org-ignore-region t))
+ (org-set-tags nil 'align))))))
+ ((or "C" "L")
+ (let ((insert-fun (if (equal key "C") #'insert
+ (lambda (s) (org-insert-link 0 s)))))
+ (pcase org-capture--clipboards
+ (`nil nil)
+ (`(,value) (funcall insert-fun value))
+ (`(,first-value . ,_)
+ (funcall insert-fun
+ (read-string "Clipboard/kill value: "
+ first-value
+ 'org-capture--clipboards
+ first-value)))
+ (_ (error "Invalid `org-capture--clipboards' value: %S"
+ org-capture--clipboards)))))
+ ("p" (org-set-property prompt nil))
+ ((guard key)
+ ;; These are the date/time related ones.
+ (let* ((upcase? (equal (upcase key) key))
+ (org-time-was-given upcase?)
+ (org-end-time-was-given)
+ (time (org-read-date upcase? t nil prompt)))
+ (org-insert-time-stamp
+ time org-time-was-given
+ (member key '("u" "U"))
+ nil nil (list org-end-time-was-given))))
+ (_
+ (push (org-completing-read
+ (concat (or prompt "Enter string")
+ (and default (format " [%s]" default))
+ ": ")
+ completions nil nil nil nil default)
+ strings)
+ (insert (car strings)))))))))
+
+ ;; Replace %n escapes with nth %^{...} string.
+ (setq strings (nreverse strings))
+ (save-excursion
+ (while (re-search-forward "%\\\\\\([1-9][0-9]*\\)" nil t)
+ (unless (org-capture-escaped-%)
+ (replace-match
+ (nth (1- (string-to-number (match-string 1))) strings)
+ nil t)))))
+
;; Make sure there are no empty lines before the text, and that
- ;; it ends with a newline character
- (goto-char (point-min))
- (while (looking-at "[ \t]*\n") (replace-match ""))
- (if (re-search-forward "[ \t\n]*\\'" nil t) (replace-match "\n"))
- ;; Return the expanded template and kill the temporary buffer
+ ;; it ends with a newline character.
+ (skip-chars-forward " \t\n")
+ (delete-region (point-min) (line-beginning-position))
+ (goto-char (point-max))
+ (skip-chars-backward " \t\n")
+ (delete-region (point) (point-max))
+ (insert "\n")
+
+ ;; Return the expanded template and kill the capture buffer.
(untabify (point-min) (point-max))
(set-buffer-modified-p nil)
- (prog1 (buffer-string) (kill-buffer (current-buffer))))))
+ (prog1 (buffer-substring-no-properties (point-min) (point-max))
+ (kill-buffer (current-buffer))))))
(defun org-capture-escaped-% ()
- "Check if % was escaped - if yes, unescape it now."
- (if (equal (char-before (match-beginning 0)) ?\\)
- (progn
- (delete-region (1- (match-beginning 0)) (match-beginning 0))
- t)
- nil))
-
-(defun org-capture-expand-embedded-elisp ()
- "Evaluate embedded elisp %(sexp) and replace with the result."
- (goto-char (point-min))
- (while (re-search-forward "%(" nil t)
- (unless (org-capture-escaped-%)
- (goto-char (match-beginning 0))
- (let ((template-start (point)))
- (forward-char 1)
- (let* ((sexp (read (current-buffer)))
- (result (org-eval
- (org-capture--expand-keyword-in-embedded-elisp sexp))))
- (delete-region template-start (point))
- (when result
- (if (stringp result)
- (insert result)
- (error "Capture template sexp `%s' must evaluate to string or nil"
- sexp))))))))
+ "Non-nil if % was escaped.
+If yes, unescape it now. Assume match-data contains the
+placeholder to check."
+ (save-excursion
+ (goto-char (match-beginning 0))
+ (let ((n (abs (skip-chars-backward "\\\\"))))
+ (delete-char (/ (1+ n) 2))
+ (= (% n 2) 1))))
+
+(defun org-capture-expand-embedded-elisp (&optional mark)
+ "Evaluate embedded elisp %(sexp) and replace with the result.
+When optional MARK argument is non-nil, mark Sexp with a text
+property (`org-embedded-elisp') for later evaluation. Only
+marked Sexp are evaluated when this argument is nil."
+ (save-excursion
+ (goto-char (point-min))
+ (while (re-search-forward "%(" nil t)
+ (cond
+ ((get-text-property (match-beginning 0) 'org-embedded-elisp)
+ (goto-char (match-beginning 0))
+ (let ((template-start (point)))
+ (forward-char 1)
+ (let* ((sexp (read (current-buffer)))
+ (result (org-eval
+ (org-capture--expand-keyword-in-embedded-elisp
+ sexp))))
+ (delete-region template-start (point))
+ (cond
+ ((not result) nil)
+ ((stringp result) (insert result))
+ (t (error
+ "Capture template sexp `%s' must evaluate to string or nil"
+ sexp))))))
+ ((not mark) nil)
+ ;; Only mark valid and non-escaped sexp.
+ ((org-capture-escaped-%) nil)
+ (t
+ (let ((end (with-syntax-table emacs-lisp-mode-syntax-table
+ (ignore-errors (scan-sexps (1- (point)) 1)))))
+ (when end
+ (put-text-property (- (point) 2) end 'org-embedded-elisp t))))))))
(defun org-capture--expand-keyword-in-embedded-elisp (attr)
"Recursively replace capture link keywords in ATTR sexp.
@@ -1771,20 +1845,10 @@ Such keywords are prefixed with \"%:\". See
(t attr)))
(defun org-capture-inside-embedded-elisp-p ()
- "Return non-nil if point is inside of embedded elisp %(sexp)."
- (let (beg end)
- (with-syntax-table emacs-lisp-mode-syntax-table
- (save-excursion
- ;; `looking-at' and `search-backward' below do not match the "%(" if
- ;; point is in its middle
- (when (equal (char-before) ?%)
- (backward-char))
- (save-match-data
- (when (or (looking-at "%(") (search-backward "%(" nil t))
- (setq beg (point))
- (setq end (progn (forward-char) (forward-sexp) (1- (point)))))))
- (when (and beg end)
- (and (<= (point) end) (>= (point) beg))))))
+ "Non-nil if point is inside of embedded elisp %(sexp).
+Assume sexps have been marked with
+`org-capture-expand-embedded-elisp' beforehand."
+ (get-text-property (point) 'org-embedded-elisp))
;;;###autoload
(defun org-capture-import-remember-templates ()
@@ -1828,6 +1892,9 @@ Such keywords are prefixed with \"%:\". See
(if jump-to-captured '(:jump-to-captured t)))))
org-remember-templates))))
+;;; The function was made obsolete by commit 65399674d5 of
+;;; 2013-02-22. This make-obsolete call was added 2016-09-01.
+(make-obsolete 'org-capture-import-remember-templates "use the `org-capture-templates' variable instead." "Org 9.0")
(provide 'org-capture)