;;; org-capture.el --- Fast note taking in Org-mode
-;; Copyright (C) 2010-2011 Free Software Foundation, Inc.
+;; Copyright (C) 2010-2016 Free Software Foundation, Inc.
;; Author: Carsten Dominik <carsten at orgmode dot org>
;; Keywords: outlines, hypermedia, calendar, wp
;; Homepage: http://orgmode.org
-;; Version: 7.7
;;
;; This file is part of GNU Emacs.
;;
;;
;;; Commentary:
-;; This file contains an alternative implementation of the same functionality
-;; that is also provided by org-remember.el. The implementation is more
+;; This file contains an alternative implementation of the functionality
+;; that used to be provided by org-remember.el. The implementation is more
;; streamlined, can produce more target types (e.g. plain list items or
;; table lines). Also, it does not use a temporary buffer for editing
;; the captured entry - instead it uses an indirect buffer that visits
;; the new entry already in the target buffer (this was an idea by Samuel
-;; Wales). John Wiegley's excellent `remember.el' is not needed for this
-;; implementation, even though we borrow heavily from its ideas.
+;; Wales). John Wiegley's excellent `remember.el' is not needed anymore
+;; for this implementation, even though we borrow heavily from its ideas.
;; This implementation heavily draws on ideas by James TD Smith and
;; Samuel Wales, and, of cause, uses John Wiegley's remember.el as inspiration.
(eval-when-compile
(require 'cl))
(require 'org)
-(require 'org-mks)
(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" ())
(defvar org-remember-default-headline)
(defvar org-remember-templates)
selection.
type The type of entry. Valid types are:
- entry an Org-mode node, with a headline. Will be
+ entry an Org-mode 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
template only needs information that can be added
automatically.
+ :jump-to-captured When set, jump to the captured entry when finished.
+
:empty-lines Set this to the number of lines the should be inserted
before and after the new item. Default 0, only common
other value is 1.
+ :empty-lines-before Set this to the number of lines the should be inserted
+ before the new item. Overrides :empty-lines for the
+ number lines inserted before.
+
+ :empty-lines-after Set this to the number of lines the should be inserted
+ after the new item. Overrides :empty-lines for the
+ number of lines inserted after.
+
:clock-in Start the clock in this item.
:clock-keep Keep the clock running when filing the captured entry.
:clock-resume Start the interrupted clock when finishing the capture.
Note that :clock-keep has precedence over :clock-resume.
- When setting both to `t', the current clock will run and
+ When setting both to t, the current clock will run and
the previous one will not be resumed.
:unnarrowed Do not narrow the target buffer, simply show the
freely formatted text. Furthermore, the following %-escapes will
be replaced with content and expanded in this order:
- %[pathname] insert the contents of the file given by `pathname'.
- %(sexp) evaluate elisp `(sexp)' and replace with the result.
- %<...> the result of format-time-string on the ... format specification.
- %t time stamp, date only.
- %T time stamp with date and time.
- %u, %U like the above, but inactive time stamps.
- %a annotation, normally the link created with `org-store-link'.
- %i initial content, copied from the active region. If %i is
+ %[pathname] Insert the contents of the file given by `pathname'.
+ %(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.
+ %<...> The result of format-time-string on the ... format specification.
+ %t Time stamp, date only.
+ %T Time stamp with date and time.
+ %u, %U Like the above, but inactive time stamps.
+ %i Initial content, copied from the active region. If %i is
indented, the entire inserted text will be indented as well.
- %A like %a, but prompt for the description part.
- %c current kill ring head.
- %x content of the X clipboard.
- %k title of currently clocked task.
- %K link to currently clocked task.
- %n user name (taken from `user-full-name').
- %f file visited by current buffer when org-capture was called.
- %F full path of the file or directory visited by current buffer.
- %:keyword specific information for certain link types, see below.
- %^g prompt for tags, with completion on tags in target file.
- %^G prompt for tags, with completion on all tags in all agenda files.
- %^t like %t, but prompt for date. Similarly %^T, %^u, %^U.
- You may define a prompt like %^{Please specify birthday.
- %^C interactive selection of which kill or clip to use.
- %^L like %^C, but insert as link.
- %^{prop}p prompt the user for a value for property `prop'.
- %^{prompt} prompt the user for a string and replace this sequence with it.
+ %a Annotation, normally the link created with `org-store-link'.
+ %A Like %a, but prompt for the description part.
+ %l Like %a, but only insert the literal link.
+ %c Current kill ring head.
+ %x Content of the X clipboard.
+ %k Title of currently clocked task.
+ %K Link to currently clocked task.
+ %n User name (taken from the variable `user-full-name').
+ %f File visited by current buffer when org-capture was called.
+ %F Full path of the file or directory visited by current buffer.
+ %:keyword Specific information for certain link types, see below.
+ %^g Prompt for tags, with completion on tags in target file.
+ %^G Prompt for tags, with completion on all tags in all agenda files.
+ %^t Like %t, but prompt for date. Similarly %^T, %^u, %^U.
+ You may define a prompt like: %^{Please specify birthday}t
+ %^C Interactive selection of which kill or clip to use.
+ %^L Like %^C, but insert as link.
+ %^{prop}p Prompt the user for a value for property `prop'.
+ %^{prompt} Prompt the user for a string and replace this sequence with it.
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.
-Apart from these general escapes, you can access information specific to the
-link type that is created. For example, calling `org-capture' in emails
-or gnus will record the author and the subject of the message, which you
+Apart from these general escapes, you can access information specific to
+the link type that is created. For example, calling `org-capture' in emails
+or in Gnus will record the author and the subject of the message, which you
can access with \"%:from\" and \"%:subject\", respectively. Here is a
complete list of what is recorded for each link type.
Link type | Available information
------------------------+------------------------------------------------------
bbdb | %:type %:name %:company
-vm, wl, mh, mew, rmail | %:type %:subject %:message-id
- | %:from %:fromname %:fromaddress
+vm, wl, mh, mew, rmail, | %:type %:subject %:message-id
+gnus | %:from %:fromname %:fromaddress
| %:to %:toname %:toaddress
| %:fromto (either \"to NAME\" or \"from NAME\")
- | %:date
- | %:date-timestamp (as active timestamp)
+ | %:date %:date-timestamp (as active timestamp)
| %:date-timestamp-inactive (as inactive timestamp)
gnus | %:group, for messages also all email fields
w3, w3m | %:type %:url
info | %:type %:file %:node
calendar | %:type %:date"
:group 'org-capture
+ :version "24.1"
:type
'(repeat
(choice :value ("" "" entry (file "~/org/notes.org") "")
- (list :tag "Multikey description"
- (string :tag "Keys ")
- (string :tag "Description"))
- (list :tag "Template entry"
- (string :tag "Keys ")
- (string :tag "Description ")
- (choice :tag "Capture Type " :value entry
- (const :tag "Org entry" entry)
- (const :tag "Plain list item" item)
- (const :tag "Checkbox item" checkitem)
- (const :tag "Plain text" plain)
- (const :tag "Table line" table-line))
- (choice :tag "Target location"
- (list :tag "File"
- (const :format "" file)
- (file :tag " File"))
- (list :tag "ID"
- (const :format "" id)
- (string :tag " ID"))
- (list :tag "File & Headline"
- (const :format "" file+headline)
- (file :tag " File ")
- (string :tag " Headline"))
- (list :tag "File & Outline path"
- (const :format "" file+olp)
- (file :tag " File ")
- (repeat :tag "Outline path" :inline t
- (string :tag "Headline")))
- (list :tag "File & Regexp"
- (const :format "" file+regexp)
- (file :tag " File ")
- (regexp :tag " Regexp"))
- (list :tag "File & Date tree"
- (const :format "" file+datetree)
- (file :tag " File"))
- (list :tag "File & Date tree, prompt for date"
- (const :format "" file+datetree+prompt)
- (file :tag " File"))
- (list :tag "File & function"
- (const :format "" file+function)
- (file :tag " File ")
- (sexp :tag " Function"))
- (list :tag "Current clocking task"
- (const :format "" clock))
- (list :tag "Function"
- (const :format "" function)
- (sexp :tag " Function")))
- (choice :tag "Template"
- (string)
- (list :tag "File"
- (const :format "" file)
- (file :tag "Template file"))
- (list :tag "Function"
- (const :format "" function)
- (function :tag "Template function")))
- (plist :inline t
- ;; Give the most common options as checkboxes
- :options (((const :format "%v " :prepend) (const t))
- ((const :format "%v " :immediate-finish) (const t))
- ((const :format "%v " :empty-lines) (const 1))
- ((const :format "%v " :clock-in) (const t))
- ((const :format "%v " :clock-keep) (const t))
- ((const :format "%v " :clock-resume) (const t))
- ((const :format "%v " :unnarrowed) (const t))
- ((const :format "%v " :kill-buffer) (const t))))))))
+ (list :tag "Multikey description"
+ (string :tag "Keys ")
+ (string :tag "Description"))
+ (list :tag "Template entry"
+ (string :tag "Keys ")
+ (string :tag "Description ")
+ (choice :tag "Capture Type " :value entry
+ (const :tag "Org entry" entry)
+ (const :tag "Plain list item" item)
+ (const :tag "Checkbox item" checkitem)
+ (const :tag "Plain text" plain)
+ (const :tag "Table line" table-line))
+ (choice :tag "Target location"
+ (list :tag "File"
+ (const :format "" file)
+ (file :tag " File"))
+ (list :tag "ID"
+ (const :format "" id)
+ (string :tag " ID"))
+ (list :tag "File & Headline"
+ (const :format "" file+headline)
+ (file :tag " File ")
+ (string :tag " Headline"))
+ (list :tag "File & Outline path"
+ (const :format "" file+olp)
+ (file :tag " File ")
+ (repeat :tag "Outline path" :inline t
+ (string :tag "Headline")))
+ (list :tag "File & Regexp"
+ (const :format "" file+regexp)
+ (file :tag " File ")
+ (regexp :tag " Regexp"))
+ (list :tag "File & Date tree"
+ (const :format "" file+datetree)
+ (file :tag " File"))
+ (list :tag "File & Date tree, prompt for date"
+ (const :format "" file+datetree+prompt)
+ (file :tag " File"))
+ (list :tag "File & function"
+ (const :format "" file+function)
+ (file :tag " File ")
+ (sexp :tag " Function"))
+ (list :tag "Current clocking task"
+ (const :format "" clock))
+ (list :tag "Function"
+ (const :format "" function)
+ (sexp :tag " Function")))
+ (choice :tag "Template"
+ (string)
+ (list :tag "File"
+ (const :format "" file)
+ (file :tag "Template file"))
+ (list :tag "Function"
+ (const :format "" function)
+ (function :tag "Template function")))
+ (plist :inline t
+ ;; Give the most common options as checkboxes
+ :options (((const :format "%v " :prepend) (const t))
+ ((const :format "%v " :immediate-finish) (const t))
+ ((const :format "%v " :jump-to-captured) (const t))
+ ((const :format "%v " :empty-lines) (const 1))
+ ((const :format "%v " :empty-lines-before) (const 1))
+ ((const :format "%v " :empty-lines-after) (const 1))
+ ((const :format "%v " :clock-in) (const t))
+ ((const :format "%v " :clock-keep) (const t))
+ ((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))))))))
(defcustom org-capture-before-finalize-hook nil
"Hook that is run right before a capture process is finalized.
-The capture buffer is still current when this hook runs."
+The capture buffer is still current when this hook runs and it is
+widened to the entire buffer."
:group 'org-capture
+ :version "24.1"
:type 'hook)
(defcustom org-capture-after-finalize-hook nil
"Hook that is run right after a capture process is finalized.
- Suitable for window cleanup"
+Suitable for window cleanup."
:group 'org-capture
+ :version "24.1"
:type 'hook)
+(defcustom org-capture-prepare-finalize-hook nil
+ "Hook that is run before the finalization starts.
+The capture buffer is current and still narrowed."
+ :group 'org-capture
+ :version "24.1"
+ :type 'hook)
+
+(defcustom org-capture-bookmark t
+ "When non-nil, add a bookmark pointing at the last stored
+position when capturing."
+ :group 'org-capture
+ :version "24.3"
+ :type 'boolean)
+
;;; The property list for keeping information about the capture process
(defvar org-capture-plist nil
(plist-get (if local org-capture-current-plist org-capture-plist) prop))
(defun org-capture-member (prop &optional local)
- "Is PROP a preperty in `org-capture-plist'.
+ "Is PROP a property in `org-capture-plist'.
When LOCAL is set, use the local variable `org-capture-current-plist',
this is necessary after initialization of the capture process,
to avoid conflicts with other active capture processes."
"Hook for the minor `org-capture-mode'.")
(define-minor-mode org-capture-mode
- "Minor mode for special key bindings in a capture buffer."
+ "Minor mode for special key bindings in 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
- "Capture buffer. Finish `C-c C-c', refile `C-c C-w', abort `C-c C-k'.")
- (run-hooks 'org-capture-mode-hook))
+ (substitute-command-keys
+ "Capture buffer. Finish `C-c C-c', refile `C-c C-w', abort `C-c C-k'.")))
(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)
;;; The main commands
+(defvar org-capture-initial nil)
+(defvar org-capture-entry nil)
+
+;;;###autoload
+(defun org-capture-string (string &optional keys)
+ "Capture STRING with the template selected by KEYS."
+ (interactive "sInitial text: \n")
+ (let ((org-capture-initial string)
+ (org-capture-entry (org-capture-select-template keys)))
+ (org-capture)))
+
+(defcustom org-capture-templates-contexts nil
+ "Alist of capture templates and valid contexts.
+
+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\"))))
+
+Here are the available contexts definitions:
+
+ in-file: command displayed only in matching files
+ in-mode: command displayed only in matching modes
+ not-in-file: command not displayed in matching files
+ not-in-mode: command not displayed in matching modes
+ in-buffer: command displayed only in matching buffers
+not-in-buffer: command not displayed in matching buffers
+ [function]: a custom function taking no argument
+
+If you define several checks, the agenda command will be
+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\"))))
+
+Here it means: in `message-mode buffers', use \"c\" as the
+key for the capture template otherwise associated with \"d\".
+\(The template originally associated with \"d\" is not displayed
+to avoid duplicates.)"
+ :version "24.3"
+ :group 'org-capture
+ :type '(repeat (list :tag "Rule"
+ (string :tag " Capture key")
+ (string :tag "Replace by template")
+ (repeat :tag "Available when"
+ (choice
+ (cons :tag "Condition"
+ (choice
+ (const :tag "In file" in-file)
+ (const :tag "Not in file" not-in-file)
+ (const :tag "In buffer" in-buffer)
+ (const :tag "Not in buffer" not-in-buffer)
+ (const :tag "In mode" in-mode)
+ (const :tag "Not in mode" not-in-mode))
+ (regexp))
+ (function :tag "Custom function"))))))
+
+(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]."
+ :group 'org-capture
+ :version "24.3"
+ :type 'boolean)
+
;;;###autoload
(defun org-capture (&optional goto keys)
"Capture something.
When called with a `C-0' (zero) prefix, insert a template at point.
-Lisp programs can set KEYS to a string associated with a template in
-`org-capture-templates'. In this case, interactive selection will be
-bypassed."
+ELisp programs can set KEYS to a string associated with a template
+in `org-capture-templates'. In this case, interactive selection
+will be bypassed.
+
+If `org-capture-use-agenda-date' is non-nil, capturing from the
+agenda will use the date at point as the default date. Then, a
+`C-1' prefix will tell the capture process to use the HH:MM time
+of the day at point (if any) or the current HH:MM time."
(interactive "P")
+ (when (and org-capture-use-agenda-date
+ (eq major-mode 'org-agenda-mode))
+ (setq org-overriding-default-time
+ (org-get-cursor-date (equal goto 1))))
(cond
((equal goto '(4)) (org-capture-goto-target))
((equal goto '(16)) (org-capture-goto-last-stored))
org-capture-link-is-already-stored)
(plist-get org-store-link-plist :annotation)
(ignore-errors (org-store-link nil))))
- (initial (and (org-region-active-p)
- (buffer-substring (point) (mark))))
- (entry (org-capture-select-template keys)))
+ (entry (or org-capture-entry (org-capture-select-template keys)))
+ initial)
+ (setq initial (or org-capture-initial
+ (and (org-region-active-p)
+ (buffer-substring (point) (mark)))))
(when (stringp initial)
(remove-text-properties 0 (length initial) '(read-only t) initial))
(when (stringp annotation)
(file-name-nondirectory
(buffer-file-name orig-buf)))
:annotation annotation
- :initial initial)
- (org-capture-put :default-time
+ :initial initial
+ :return-to-wconf (current-window-configuration)
+ :default-time
(or org-overriding-default-time
(org-current-time)))
(org-capture-set-target-location)
;;insert at point
(org-capture-insert-template-here)
(condition-case error
- (org-capture-place-template)
+ (org-capture-place-template
+ (equal (car (org-capture-get :target)) 'function))
((error quit)
(if (and (buffer-base-buffer (current-buffer))
(string-match "\\`CAPTURE-" (buffer-name)))
(error "Capture template `%s': %s"
(org-capture-get :key)
(nth 1 error))))
- (if (and (org-mode-p)
+ (if (and (derived-mode-p 'org-mode)
(org-capture-get :clock-in))
(condition-case nil
(progn
(error
"Could not start the clock in this capture buffer")))
(if (org-capture-get :immediate-finish)
- (org-capture-finalize nil)))))))))
+ (org-capture-finalize)))))))))
(defun org-capture-get-template ()
"Get the template from a file or a function if necessary."
With prefix argument STAY-WITH-CAPTURE, jump to the location of the
captured item after finalizing."
(interactive "P")
+ (when (org-capture-get :jump-to-captured)
+ (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"))
+ (run-hooks 'org-capture-prepare-finalize-hook)
+
;; Did we start the clock in this capture buffer?
(when (and org-capture-clock-was-started
org-clock-marker (marker-buffer org-clock-marker)
(goto-char end)
(or (bolp) (newline))
(org-capture-empty-lines-after
- (or (org-capture-get :empty-lines 'local) 0))))
+ (or (org-capture-get :empty-lines-after 'local)
+ (org-capture-get :empty-lines 'local) 0))))
;; Postprocessing: Update Statistics cookies, do the sorting
- (when (org-mode-p)
+ (when (derived-mode-p 'org-mode)
(save-excursion
(when (ignore-errors (org-back-to-heading))
(org-update-parent-todo-statistics)
;; 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.
- (org-capture-bookmark-last-stored-position)
+ (when org-capture-bookmark
+ (org-capture-bookmark-last-stored-position))
;; Run the hook
(run-hooks 'org-capture-before-finalize-hook))
+ (when (org-capture-get :decrypted)
+ (save-excursion
+ (goto-char (org-capture-get :decrypted))
+ (org-encrypt-entry)))
+
;; Kill the indirect buffer
(save-buffer)
(let ((return-wconf (org-capture-get :return-to-wconf 'local))
(pos (org-capture-get :initial-target-position))
(ipt (org-capture-get :insertion-point))
(size (org-capture-get :captured-entry-size)))
- (when reg
+ (if (not reg)
+ (widen)
(cond ((< ipt (car reg))
;; insertion point is before the narrowed region
(narrow-to-region (+ size (car reg)) (+ size (cdr reg))))
(let ((pos (point))
(base (buffer-base-buffer (current-buffer)))
(org-refile-for-capture t))
- (org-capture-finalize)
(save-window-excursion
(with-current-buffer (or base (current-buffer))
(save-excursion
(save-restriction
(widen)
(goto-char pos)
- (call-interactively 'org-refile)))))))
+ (call-interactively 'org-refile)))))
+ (org-capture-finalize)))
(defun org-capture-kill ()
"Abort the current capture process."
(interactive)
- ;; FIXME: This does not do the right thing, we need to remove the new stuff
- ;; By hand it is easy: undo, then kill the buffer
+ ;; FIXME: This does not do the right thing, we need to remove the
+ ;; new stuff by hand it is easy: undo, then kill the buffer
(let ((org-note-abort t)
(org-capture-before-finalize-hook nil))
(org-capture-finalize)))
;; store the current point
(org-capture-put :initial-target-position (point)))
+(defvar org-time-was-given) ; dynamically scoped parameter
(defun org-capture-set-target-location (&optional target)
- "Find target buffer and position and store then in the property list."
- (let ((target-entry-p t))
+ "Find TARGET buffer and position.
+Store them in the capture property list."
+ (let ((target-entry-p t) decrypted-hl-pos)
(setq target (or target (org-capture-get :target)))
(save-excursion
(cond
(widen)
(let ((hd (nth 2 target)))
(goto-char (point-min))
- (unless (org-mode-p)
+ (unless (derived-mode-p 'org-mode)
(error
"Target buffer \"%s\" for file+headline should be in Org mode"
(current-buffer)))
(goto-char (if (org-capture-get :prepend)
(match-beginning 0) (match-end 0)))
(org-capture-put :exact-position (point))
- (setq target-entry-p (and (org-mode-p) (org-at-heading-p))))
+ (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))
(let ((prompt-time (org-read-date
nil t nil "Date for tree entry:"
(current-time))))
- (org-capture-put :prompt-time prompt-time)
+ (org-capture-put
+ :default-time
+ (cond ((and (or (not (boundp 'org-time-was-given))
+ (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)))))
+ ((string-match "\\([^ ]+\\)--?[^ ]+[ ]+\\(.*\\)" org-read-date-final-answer)
+ ;; Replace any time range by its start
+ (apply 'encode-time
+ (org-read-date-analyze
+ (replace-match "\\1 \\2" nil nil org-read-date-final-answer)
+ prompt-time (decode-time prompt-time))))
+ (t prompt-time)))
(time-to-days prompt-time)))
(t
- ;; current date, possible corrected for late night workers
+ ;; current date, possibly corrected for late night workers
(org-today))))))
((eq (car target) 'file+function)
(widen)
(funcall (nth 2 target))
(org-capture-put :exact-position (point))
- (setq target-entry-p (and (org-mode-p) (org-at-heading-p))))
+ (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))
((eq (car target) 'function)
(funcall (nth 1 target))
(org-capture-put :exact-position (point))
- (setq target-entry-p (and (org-mode-p) (org-at-heading-p))))
+ (setq target-entry-p (and (derived-mode-p 'org-mode) (org-at-heading-p))))
((eq (car target) 'clock)
(if (and (markerp org-clock-hd-marker)
(t (error "Invalid capture target specification")))
+ (when (and (featurep 'org-crypt) (org-at-encrypted-entry-p))
+ (org-decrypt-entry)
+ (setq decrypted-hl-pos
+ (save-excursion (and (org-back-to-heading t) (point)))))
+
(org-capture-put :buffer (current-buffer) :pos (point)
- :target-entry-p target-entry-p))))
+ :target-entry-p target-entry-p
+ :decrypted decrypted-hl-pos))))
(defun org-capture-expand-file (file)
"Expand functions and symbols for FILE.
(find-file-noselect (expand-file-name file org-directory)))))
(defun org-capture-steal-local-variables (buffer)
- "Install Org-mode local variables."
+ "Install Org-mode local variables of BUFFER."
(mapc (lambda (v)
(ignore-errors (org-set-local (car v) (cdr v))))
(buffer-local-variables buffer)))
-(defun org-capture-place-template ()
- "Insert the template at the target location, and display the buffer."
- (org-capture-put :return-to-wconf (current-window-configuration))
+(defun org-capture-place-template (&optional inhibit-wconf-store)
+ "Insert the template at the target location, and display the buffer.
+When `inhibit-wconf-store', don't store the window configuration, as it
+may have been stored before."
+ (unless inhibit-wconf-store
+ (org-capture-put :return-to-wconf (current-window-configuration)))
(delete-other-windows)
(org-switch-to-buffer-other-window
(org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE"))
(show-all)
(goto-char (org-capture-get :pos))
(org-set-local 'org-capture-target-marker
- (move-marker (make-marker) (point)))
+ (point-marker))
+ (org-set-local 'outline-level 'org-outline-level)
(let* ((template (org-capture-get :template))
(type (org-capture-get :type)))
(case type
(progn
(outline-next-heading)
(or (bolp) (insert "\n")))
- (org-end-of-subtree t t)
+ (org-end-of-subtree t nil)
(or (bolp) (insert "\n")))))
(org-capture-empty-lines-before)
(setq beg (point))
(setq end (point))
(org-capture-mark-kill-region beg (1- end))
(org-capture-narrow beg (1- end))
- (goto-char beg)
- (if (re-search-forward "%\\?" end t) (replace-match ""))))
+ (if (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."
(target-entry-p (org-capture-get :target-entry-p))
(ind 0)
beg end)
- (cond
- ((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 beg (point-min) end (point-max)))
- (t
- (setq beg (1+ (point-at-eol))
- end (save-excursion (outline-next-heading) (point)))))
- (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)))
- (goto-char end)
- (if (org-list-search-backward (org-item-beginning-re) beg t)
+ (if (org-capture-get :exact-position)
+ (goto-char (org-capture-get :exact-position))
+ (cond
+ ((not target-entry-p)
+ ;; Insert as top-level entry, either at beginning or at end of file
+ (setq beg (point-min) end (point-max)))
+ (t
+ (setq beg (1+ (point-at-eol))
+ end (save-excursion (outline-next-heading) (point)))))
+ (if (org-capture-get :prepend)
(progn
- (setq ind (org-get-indentation))
- (org-end-of-item))
- (setq ind 0)))
+ (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)))
+ (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))))
;; Remove common indentation
(setq txt (org-remove-indentation txt))
;; Make sure this is indeed an item
(setq end (point))
(org-capture-mark-kill-region beg (1- end))
(org-capture-narrow beg (1- end))
- (if (re-search-forward "%\\?" end t) (replace-match ""))))
+ (if (or (re-search-backward "%\\?" beg t)
+ (re-search-forward "%\\?" end t))
+ (replace-match ""))))
(defun org-capture-place-table-line ()
"Place the template as a table line."
(setq beg (1+ (point-at-eol))
end (save-excursion (outline-next-heading) (point)))))
(if (re-search-forward org-table-dataline-regexp end t)
- (let ((b (org-table-begin)) (e (org-table-end)))
+ (let ((b (org-table-begin)) (e (org-table-end)) (case-fold-search t))
(goto-char e)
- (if (looking-at "[ \t]*#\\+TBLFM:")
+ (if (looking-at "[ \t]*#\\+tblfm:")
(forward-line 1))
(narrow-to-region b (point)))
(goto-char end)
(setq end (point))))
(goto-char beg)
(org-capture-position-for-last-stored 'table-line)
- (if (re-search-forward "%\\?" end t) (replace-match ""))
+ (if (or (re-search-backward "%\\?" beg t)
+ (re-search-forward "%\\?" end t))
+ (replace-match ""))
(org-table-align)))
(defun org-capture-place-plain-text ()
(setq end (point))
(org-capture-mark-kill-region beg (1- end))
(org-capture-narrow beg (1- end))
- (if (re-search-forward "%\\?" end t) (replace-match ""))))
+ (if (or (re-search-backward "%\\?" beg t)
+ (re-search-forward "%\\?" end t))
+ (replace-match ""))))
(defun org-capture-mark-kill-region (beg end)
"Mark the region that will have to be killed when aborting capture."
(save-restriction
(widen)
(goto-char pos)
- (bookmark-set "org-capture-last-stored")
+ (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)))))))
(defun org-capture-narrow (beg end)
(goto-char beg)))
(defun org-capture-empty-lines-before (&optional n)
- "Arrange for the correct number of empty lines before the insertion point.
+ "Set the correct number of empty lines before the insertion point.
Point will be after the empty lines, so insertion can directly be done."
- (setq n (or n (org-capture-get :empty-lines) 0))
+ (setq n (or n (org-capture-get :empty-lines-before)
+ (org-capture-get :empty-lines) 0))
(let ((pos (point)))
(org-back-over-empty-lines)
(delete-region (point) pos)
(if (> n 0) (newline n))))
(defun org-capture-empty-lines-after (&optional n)
- "Arrange for the correct number of empty lines after the inserted string.
+ "Set the correct number of empty lines after the inserted string.
Point will remain at the first line after the inserted text."
- (setq n (or n (org-capture-get :empty-lines) 0))
+ (setq n (or n (org-capture-get :empty-lines-after)
+ (org-capture-get :empty-lines) 0))
(org-back-over-empty-lines)
(while (looking-at "[ \t]*\n") (replace-match ""))
(let ((pos (point)))
(goto-char pos)))
(defvar org-clock-marker) ; Defined in org.el
-;;;###autoload
+
(defun org-capture-insert-template-here ()
+ "Insert the capture template at point."
(let* ((template (org-capture-get :template))
(type (org-capture-get :type))
beg end pp)
(or (bolp) (newline))
(setq beg (point))
(cond
- ((and (eq type 'entry) (org-mode-p))
+ ((and (eq type 'entry) (derived-mode-p 'org-mode))
(org-capture-verify-tree (org-capture-get :template))
(org-paste-subtree nil template t))
((and (memq type '(item checkitem))
- (org-mode-p)
+ (derived-mode-p 'org-mode)
(save-excursion (skip-chars-backward " \t\n")
(setq pp (point))
(org-in-item-p)))
(error "No capture template selected"))
(org-capture-set-plist entry)
(org-capture-set-target-location)
- (switch-to-buffer (org-capture-get :buffer))
+ (org-pop-to-buffer-same-window (org-capture-get :buffer))
(goto-char (org-capture-get :pos))))
(defun org-capture-get-indirect-buffer (&optional buffer prefix)
(setq bname (concat prefix "-" (number-to-string (incf n)) "-" base)))
(condition-case nil
(make-indirect-buffer buffer bname 'clone)
- (error (make-indirect-buffer buffer bname)))))
-
+ (error
+ (let ((buf (make-indirect-buffer buffer bname)))
+ (with-current-buffer buf (org-mode))
+ buf)))))
(defun org-capture-verify-tree (tree)
- "Throw error if TREE is not a valid tree"
+ "Throw error if TREE is not a valid tree."
(unless (org-kill-is-subtree-p tree)
(error "Template is not a valid Org entry or tree")))
-;;; The template code
+(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.
+
+1. prefix descriptions like (\"a\" \"Description\")
+ 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
+ being the string of keys that lead to selecting it, and the second a
+ short description string of the item.
+
+The command will then make a temporary buffer listing all entries
+that can be selected with a single key, and all the single key
+prefixes. When you press the key for a single-letter entry, it is selected.
+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))
+;;; The template code
(defun org-capture-select-template (&optional keys)
"Select a capture template.
Lisp programs can force the template by setting KEYS to a string."
(let ((org-capture-templates
- (or org-capture-templates
+ (or (org-contextualize-keys
+ org-capture-templates org-capture-templates-contexts)
'(("t" "Task" entry (file+headline "" "Tasks")
"* TODO %?\n %u\n %a")))))
(if keys
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))
- (remove-text-properties 0 (length initial) '(read-only t) initial))
+ (setq initial (org-no-properties initial)))
(let* ((buffer (org-capture-get :buffer))
(file (buffer-file-name (or (buffer-base-buffer buffer) buffer)))
(ct (org-capture-get :default-time))
(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) ct))
- (v-T (format-time-string (cdr org-time-stamp-formats) ct))
+ (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.
(org-get-x-clipboard 'CLIPBOARD)
(org-get-x-clipboard 'SECONDARY)
v-c)))
- (v-A (if (and v-a
- (string-match
- "\\[\\(\\[.*?\\]\\)\\(\\[.*?\\]\\)?\\]" v-a))
- (replace-match "[\\1[%^{Link description}]]" nil nil v-a)
+ (l-re "\\[\\[\\(.*?\\)\\]\\(\\[.*?\\]\\)?\\]")
+ (v-A (if (and v-a (string-match l-re v-a))
+ (replace-match "[[\\1][%^{Link description}]]" nil nil v-a)
+ v-a))
+ (v-l (if (and v-a (string-match l-re v-a))
+ (replace-match "\\1" nil nil v-a)
v-a))
(v-n user-full-name)
(v-k (if (marker-buffer org-clock-marker)
- (org-substring-no-properties org-clock-heading)))
+ (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-startup-folded nil)
(org-inhibit-startup t)
org-time-was-given org-end-time-was-given x
- prompt completions char time pos default histvar)
+ prompt completions char time pos default histvar strings)
(setq org-store-link-plist
(plist-put org-store-link-plist :annotation v-a)
(sit-for 1))
(save-window-excursion
(delete-other-windows)
- (switch-to-buffer (get-buffer-create "*Capture*"))
+ (org-pop-to-buffer-same-window (get-buffer-create "*Capture*"))
(erase-buffer)
(insert template)
(goto-char (point-min))
(org-capture-steal-local-variables buffer)
- (setq buffer-file-name nil)
+ (setq buffer-file-name nil mark-active nil)
;; %[] Insert contents of a file.
(goto-char (point-min))
(delete-region start end)
(condition-case error
(insert-file-contents filename)
- (error (insert (format "%%![Couldn't insert %s: %s]"
+ (error (insert (format "%%![Could not insert %s: %s]"
filename error)))))))
;; %() embedded elisp
- (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 ((result (org-eval (read (current-buffer)))))
- (delete-region template-start (point))
- (insert result)))))
+ (org-capture-expand-embedded-elisp)
;; The current time
(goto-char (point-min))
;; Simple %-escapes
(goto-char (point-min))
- (while (re-search-forward "%\\([tTuUaiAcxkKInfF]\\)" nil t)
+ (while (re-search-forward "%\\([tTuUaliAcxkKInfF]\\)" nil t)
(unless (org-capture-escaped-%)
(when (and initial (equal (match-string 0) "%i"))
(save-match-data
(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)))
+ (replace-match (or (eval (intern (concat "v-" (match-string 1)))) "")
+ t t)))
;; From the property list
(when plist-p
(let ((org-inhibit-startup t)) (org-mode))
;; Interactive template entries
(goto-char (point-min))
- (while (re-search-forward "%^\\({\\([^}]*\\)}\\)?\\([gGtTuUCLp]\\)?"
- nil t)
+ (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)))
(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-on-heading-p) (org-set-tags nil 'align)))))
+ (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)
'(clipboards . 1)
(car clipboards))))))
((equal char "p")
- (org-set-property (org-substring-no-properties prompt) nil))
+ (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))
nil nil (list org-end-time-was-given)))
(t
(let (org-completion-use-ido)
- (insert (org-completing-read-no-i
- (concat (if prompt prompt "Enter string")
- (if default (concat " [" default "]"))
- ": ")
- completions nil nil nil histvar default)))))))
+ (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)))
;; 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 tempate and kill the temporary buffer
+ ;; Return the expanded template and kill the temporary buffer
(untabify (point-min) (point-max))
(set-buffer-modified-p nil)
(prog1 (buffer-string) (kill-buffer (current-buffer))))))
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))))))))
+
+(defun org-capture--expand-keyword-in-embedded-elisp (attr)
+ "Recursively replace capture link keywords in ATTR sexp.
+Such keywords are prefixed with \"%:\". See
+`org-capture-template' for more information."
+ (cond ((consp attr)
+ (mapcar 'org-capture--expand-keyword-in-embedded-elisp attr))
+ ((symbolp attr)
+ (let* ((attr-symbol (symbol-name attr))
+ (key (and (string-match "%\\(:.*\\)" attr-symbol)
+ (intern (match-string 1 attr-symbol)))))
+ (or (plist-get org-store-link-plist key)
+ attr)))
+ (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))))))
+
;;;###autoload
(defun org-capture-import-remember-templates ()
- "Set org-capture-templates to be similar to `org-remember-templates'."
+ "Set `org-capture-templates' to be similar to `org-remember-templates'."
(interactive)
(when (and (yes-or-no-p
"Import old remember templates into org-capture-templates? ")
(position (or (nth 4 entry) org-remember-default-headline))
(type 'entry)
(prepend org-reverse-note-order)
- immediate target)
+ immediate target jump-to-captured)
(cond
((member position '(top bottom))
(setq target (list 'file file)
(setq template (replace-match "" t t template)
immediate t))
+ (when (string-match "%&" template)
+ (setq jump-to-captured t))
+
(append (list key desc type target template)
(if prepend '(:prepend t))
- (if immediate '(:immediate-finish t)))))
+ (if immediate '(:immediate-finish t))
+ (if jump-to-captured '(:jump-to-captured t)))))
org-remember-templates))))
(provide 'org-capture)
-
-
;;; org-capture.el ends here