Commit | Line | Data |
---|---|---|
86fbb8ca CD |
1 | ;;; org-capture.el --- Fast note taking in Org-mode |
2 | ||
4d789d84 | 3 | ;; Copyright (C) 2010, 2011 Free Software Foundation, Inc. |
86fbb8ca CD |
4 | |
5 | ;; Author: Carsten Dominik <carsten at orgmode dot org> | |
6 | ;; Keywords: outlines, hypermedia, calendar, wp | |
7 | ;; Homepage: http://orgmode.org | |
acedf35c | 8 | ;; Version: 7.4 |
86fbb8ca CD |
9 | ;; |
10 | ;; This file is part of GNU Emacs. | |
11 | ;; | |
12 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
13 | ;; it under the terms of the GNU General Public License as published by | |
14 | ;; the Free Software Foundation, either version 3 of the License, or | |
15 | ;; (at your option) any later version. | |
16 | ||
17 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
18 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
19 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
20 | ;; GNU General Public License for more details. | |
21 | ||
22 | ;; You should have received a copy of the GNU General Public License | |
23 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
24 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | |
25 | ;; | |
26 | ;;; Commentary: | |
27 | ||
28 | ;; This file contains an alternative implementation of the same functionality | |
29 | ;; that is also provided by org-remember.el. The implementation is more | |
30 | ;; streamlined, can produce more target types (e.g. plain list items or | |
31 | ;; table lines). Also, it does not use a temporary buffer for editing | |
32 | ;; the captured entry - instead it uses an indirect buffer that visits | |
33 | ;; the new entry already in the target buffer (this was an idea by Samuel | |
34 | ;; Wales). John Wiegley's excellent `remember.el' is not needed for this | |
35 | ;; implementation, even though we borrow heavily from its ideas. | |
36 | ||
37 | ;; This implementation heavily draws on ideas by James TD Smith and | |
38 | ;; Samuel Wales, and, of cause, uses John Wiegley's remember.el as inspiration. | |
39 | ||
40 | ;;; TODO | |
41 | ||
42 | ;; - find a clever way to not always insert an annotation maybe a | |
43 | ;; predicate function that can check for conditions for %a to be | |
44 | ;; used. This could be one of the properties. | |
45 | ||
46 | ;; - Should there be plist members that arrange for properties to be | |
47 | ;; asked for, like James proposed in his RFC? | |
48 | ||
49 | ;;; Code: | |
50 | ||
51 | (eval-when-compile | |
52 | (require 'cl)) | |
53 | (require 'org) | |
54 | (require 'org-mks) | |
55 | ||
56 | (declare-function org-datetree-find-date-create "org-datetree" | |
57 | (DATE &optional KEEP-RESTRICTION)) | |
58 | (declare-function org-table-get-specials "org-table" ()) | |
59 | (declare-function org-table-goto-line "org-table" (N)) | |
60 | (defvar org-remember-default-headline) | |
61 | (defvar org-remember-templates) | |
62 | (defvar org-table-hlines) | |
63 | ||
64 | (defvar org-capture-clock-was-started nil | |
65 | "Internal flag, noting if the clock was started.") | |
66 | ||
67 | (defvar org-capture-last-stored-marker (make-marker) | |
68 | "Marker pointing to the entry most recently stored with `org-capture'.") | |
69 | ||
70 | ;; The following variable is scoped dynamically by org-protocol | |
71 | ;; to indicate that the link properties have already been stored | |
72 | (defvar org-capture-link-is-already-stored nil) | |
73 | ||
74 | (defgroup org-capture nil | |
75 | "Options concerning capturing new entries." | |
76 | :tag "Org Capture" | |
77 | :group 'org) | |
78 | ||
79 | (defcustom org-capture-templates nil | |
80 | "Templates for the creation of new entries. | |
81 | ||
82 | Each entry is a list with the following items: | |
83 | ||
84 | keys The keys that will select the template, as a string, characters | |
85 | only, for example \"a\" for a template to be selected with a | |
86 | single key, or \"bt\" for selection with two keys. When using | |
87 | several keys, keys using the same prefix key must be together | |
88 | in the list and preceded by a 2-element entry explaining the | |
89 | prefix key, for example | |
90 | ||
91 | (\"b\" \"Templates for marking stuff to buy\") | |
92 | ||
93 | The \"C\" key is used by default for quick access to the | |
94 | customization of the template variable. But if you want to use | |
95 | that key for a template, you can. | |
96 | ||
97 | description A short string describing the template, will be shown during | |
98 | selection. | |
99 | ||
100 | type The type of entry. Valid types are: | |
101 | entry an Org-mode node, with a headline. Will be | |
102 | filed as the child of the target entry or as | |
103 | a top-level entry. | |
104 | item a plain list item, will be placed in the | |
105 | first plain list at the target | |
106 | location. | |
107 | checkitem a checkbox item. This differs from the | |
108 | plain list item only is so far as it uses a | |
109 | different default template. | |
110 | table-line a new line in the first table at target location. | |
111 | plain text to be inserted as it is. | |
112 | ||
113 | target Specification of where the captured item should be placed. | |
114 | In Org-mode files, targets usually define a node. Entries will | |
115 | become children of this node, other types will be added to the | |
116 | table or list in the body of this node. | |
117 | ||
118 | Valid values are: | |
119 | ||
120 | (file \"path/to/file\") | |
121 | Text will be placed at the beginning or end of that file | |
122 | ||
123 | (id \"id of existing org entry\") | |
124 | File as child of this entry, or in the body of the entry | |
125 | ||
126 | (file+headline \"path/to/file\" \"node headline\") | |
127 | Fast configuration if the target heading is unique in the file | |
128 | ||
129 | (file+olp \"path/to/file\" \"Level 1 heading\" \"Level 2\" ...) | |
130 | For non-unique headings, the full path is safer | |
131 | ||
132 | (file+regexp \"path/to/file\" \"regexp to find location\") | |
133 | File to the entry matching regexp | |
134 | ||
135 | (file+datetree \"path/to/file\") | |
acedf35c CD |
136 | Will create a heading in a date tree for today's date |
137 | ||
138 | (file+datetree+prompt \"path/to/file\") | |
139 | Will create a heading in a date tree, promts for date | |
86fbb8ca CD |
140 | |
141 | (file+function \"path/to/file\" function-finding-location) | |
142 | A function to find the right location in the file | |
143 | ||
144 | (clock) | |
145 | File to the entry that is currently being clocked | |
146 | ||
147 | (function function-finding-location) | |
148 | Most general way, write your own function to find both | |
149 | file and location | |
150 | ||
151 | template The template for creating the capture item. If you leave this | |
152 | empty, an appropriate default template will be used. See below | |
153 | for more details. Instead of a string, this may also be one of | |
154 | ||
155 | (file \"/path/to/template-file\") | |
156 | (function function-returning-the-template) | |
157 | ||
158 | in order to get a template from a file, or dynamically | |
159 | from a function. | |
160 | ||
161 | The rest of the entry is a property list of additional options. Recognized | |
162 | properties are: | |
163 | ||
164 | :prepend Normally newly captured information will be appended at | |
165 | the target location (last child, last table line, | |
166 | last list item...). Setting this property will | |
167 | change that. | |
168 | ||
169 | :immediate-finish When set, do not offer to edit the information, just | |
170 | file it away immediately. This makes sense if the | |
171 | template only needs information that can be added | |
172 | automatically. | |
173 | ||
174 | :empty-lines Set this to the number of lines the should be inserted | |
175 | before and after the new item. Default 0, only common | |
176 | other value is 1. | |
177 | ||
178 | :clock-in Start the clock in this item. | |
179 | ||
180 | :clock-resume Start the interrupted clock when finishing the capture. | |
181 | ||
182 | :unnarrowed Do not narrow the target buffer, simply show the | |
183 | full buffer. Default is to narrow it so that you | |
184 | only see the new stuff. | |
185 | ||
186 | :table-line-pos Specification of the location in the table where the | |
187 | new line should be inserted. It looks like \"II-3\" | |
188 | which means that the new line should become the third | |
189 | line before the second horizontal separator line. | |
190 | ||
afe98dfa CD |
191 | :kill-buffer If the target file was not yet visited by a buffer when |
192 | capture was invoked, kill the buffer again after capture | |
193 | is finalized. | |
194 | ||
86fbb8ca CD |
195 | The template defines the text to be inserted. Often this is an org-mode |
196 | entry (so the first line should start with a star) that will be filed as a | |
197 | child of the target headline. It can also be freely formatted text. | |
198 | Furthermore, the following %-escapes will be replaced with content: | |
199 | ||
200 | %^{prompt} prompt the user for a string and replace this sequence with it. | |
201 | A default value and a completion table ca be specified like this: | |
202 | %^{prompt|default|completion2|completion3|...} | |
203 | %t time stamp, date only | |
204 | %T time stamp with date and time | |
205 | %u, %U like the above, but inactive time stamps | |
206 | %^t like %t, but prompt for date. Similarly %^T, %^u, %^U. | |
207 | You may define a prompt like %^{Please specify birthday | |
208 | %n user name (taken from `user-full-name') | |
209 | %a annotation, normally the link created with `org-store-link' | |
210 | %i initial content, copied from the active region. If %i is | |
211 | indented, the entire inserted text will be indented as well. | |
212 | %c current kill ring head | |
213 | %x content of the X clipboard | |
214 | %^C interactive selection of which kill or clip to use | |
215 | %^L like %^C, but insert as link | |
216 | %k title of currently clocked task | |
217 | %K link to currently clocked task | |
218 | %^g prompt for tags, with completion on tags in target file | |
219 | %^G prompt for tags, with completion on all tags in all agenda files | |
220 | %^{prop}p prompt the user for a value for property `prop' | |
221 | %:keyword specific information for certain link types, see below | |
222 | %[pathname] insert the contents of the file given by `pathname' | |
223 | %(sexp) evaluate elisp `(sexp)' and replace with the result | |
224 | ||
225 | %? After completing the template, position cursor here. | |
226 | ||
227 | Apart from these general escapes, you can access information specific to the | |
228 | link type that is created. For example, calling `org-capture' in emails | |
229 | or gnus will record the author and the subject of the message, which you | |
afe98dfa | 230 | can access with \"%:from\" and \"%:subject\", respectively. Here is a |
86fbb8ca CD |
231 | complete list of what is recorded for each link type. |
232 | ||
afe98dfa CD |
233 | Link type | Available information |
234 | ------------------------+------------------------------------------------------ | |
235 | bbdb | %:type %:name %:company | |
236 | vm, wl, mh, mew, rmail | %:type %:subject %:message-id | |
237 | | %:from %:fromname %:fromaddress | |
238 | | %:to %:toname %:toaddress | |
239 | | %:fromto (either \"to NAME\" or \"from NAME\") | |
240 | | %:date | |
241 | | %:date-timestamp (as active timestamp) | |
242 | | %:date-timestamp-inactive (as inactive timestamp) | |
243 | gnus | %:group, for messages also all email fields | |
244 | w3, w3m | %:type %:url | |
245 | info | %:type %:file %:node | |
246 | calendar | %:type %:date" | |
86fbb8ca CD |
247 | :group 'org-capture |
248 | :type | |
249 | '(repeat | |
250 | (choice :value ("" "" entry (file "~/org/notes.org") "") | |
251 | (list :tag "Multikey description" | |
252 | (string :tag "Keys ") | |
253 | (string :tag "Description")) | |
254 | (list :tag "Template entry" | |
255 | (string :tag "Keys ") | |
256 | (string :tag "Description ") | |
257 | (choice :tag "Capture Type " :value entry | |
258 | (const :tag "Org entry" entry) | |
259 | (const :tag "Plain list item" item) | |
260 | (const :tag "Checkbox item" checkitem) | |
261 | (const :tag "Plain text" plain) | |
262 | (const :tag "Table line" table-line)) | |
263 | (choice :tag "Target location" | |
264 | (list :tag "File" | |
265 | (const :format "" file) | |
266 | (file :tag " File")) | |
267 | (list :tag "ID" | |
268 | (const :format "" id) | |
269 | (string :tag " ID")) | |
270 | (list :tag "File & Headline" | |
271 | (const :format "" file+headline) | |
272 | (file :tag " File ") | |
273 | (string :tag " Headline")) | |
274 | (list :tag "File & Outline path" | |
275 | (const :format "" file+olp) | |
276 | (file :tag " File ") | |
277 | (repeat :tag "Outline path" :inline t | |
278 | (string :tag "Headline"))) | |
279 | (list :tag "File & Regexp" | |
280 | (const :format "" file+regexp) | |
281 | (file :tag " File ") | |
282 | (regexp :tag " Regexp")) | |
283 | (list :tag "File & Date tree" | |
284 | (const :format "" file+datetree) | |
285 | (file :tag " File")) | |
acedf35c CD |
286 | (list :tag "File & Date tree, prompt for date" |
287 | (const :format "" file+datetree+prompt) | |
288 | (file :tag " File")) | |
86fbb8ca CD |
289 | (list :tag "File & function" |
290 | (const :format "" file+function) | |
291 | (file :tag " File ") | |
292 | (sexp :tag " Function")) | |
293 | (list :tag "Current clocking task" | |
294 | (const :format "" clock)) | |
295 | (list :tag "Function" | |
296 | (const :format "" function) | |
297 | (sexp :tag " Function"))) | |
298 | (choice :tag "Template" | |
299 | (string) | |
300 | (list :tag "File" | |
301 | (const :format "" file) | |
302 | (file :tag "Template file")) | |
303 | (list :tag "Function" | |
304 | (const :format "" function) | |
afe98dfa | 305 | (function :tag "Template function"))) |
86fbb8ca CD |
306 | (plist :inline t |
307 | ;; Give the most common options as checkboxes | |
308 | :options (((const :format "%v " :prepend) (const t)) | |
309 | ((const :format "%v " :immediate-finish) (const t)) | |
310 | ((const :format "%v " :empty-lines) (const 1)) | |
311 | ((const :format "%v " :clock-in) (const t)) | |
312 | ((const :format "%v " :clock-resume) (const t)) | |
afe98dfa CD |
313 | ((const :format "%v " :unnarrowed) (const t)) |
314 | ((const :format "%v " :kill-buffer) (const t)))))))) | |
86fbb8ca CD |
315 | |
316 | (defcustom org-capture-before-finalize-hook nil | |
317 | "Hook that is run right before a remember process is finalized. | |
318 | The remember buffer is still current when this hook runs." | |
319 | :group 'org-capture | |
320 | :type 'hook) | |
321 | ||
acedf35c CD |
322 | (defcustom org-capture-after-finalize-hook nil |
323 | "Hook that is run right after a capture process is finalized. | |
324 | Suitable for window cleanup" | |
325 | :group 'org-capture | |
326 | :type 'hook) | |
327 | ||
86fbb8ca CD |
328 | ;;; The property list for keeping information about the capture process |
329 | ||
330 | (defvar org-capture-plist nil | |
331 | "Plist for the current capture process, global, to avoid having to pass it.") | |
332 | (defvar org-capture-current-plist nil | |
333 | "Local variable holding the plist in a capture buffer. | |
334 | This is used to store the plist for use when finishing a capture process. | |
335 | Another such process might have changed the global variable by then.") | |
336 | ||
337 | (defun org-capture-put (&rest stuff) | |
338 | (while stuff | |
339 | (setq org-capture-plist (plist-put org-capture-plist | |
340 | (pop stuff) (pop stuff))))) | |
341 | (defun org-capture-get (prop &optional local) | |
342 | (plist-get (if local org-capture-current-plist org-capture-plist) prop)) | |
343 | ||
344 | (defun org-capture-member (prop) | |
345 | (plist-get org-capture-plist prop)) | |
346 | ||
347 | ;;; The minor mode | |
348 | ||
349 | (defvar org-capture-mode-map (make-sparse-keymap) | |
350 | "Keymap for `org-capture-mode', a minor mode. | |
351 | Use this map to set additional keybindings for when Org-mode is used | |
352 | for a Remember buffer.") | |
353 | ||
354 | (defvar org-capture-mode-hook nil | |
355 | "Hook for the minor `org-capture-mode'.") | |
356 | ||
357 | (define-minor-mode org-capture-mode | |
358 | "Minor mode for special key bindings in a remember buffer." | |
359 | nil " Rem" org-capture-mode-map | |
360 | (org-set-local | |
361 | 'header-line-format | |
4d789d84 | 362 | "Capture buffer. Finish `C-c C-c', refile `C-c C-w', abort `C-c C-k'.")) |
86fbb8ca CD |
363 | (define-key org-capture-mode-map "\C-c\C-c" 'org-capture-finalize) |
364 | (define-key org-capture-mode-map "\C-c\C-k" 'org-capture-kill) | |
365 | (define-key org-capture-mode-map "\C-c\C-w" 'org-capture-refile) | |
366 | ||
367 | ;;; The main commands | |
368 | ||
369 | ;;;###autoload | |
370 | (defun org-capture (&optional goto keys) | |
371 | "Capture something. | |
372 | \\<org-capture-mode-map> | |
373 | This will let you select a template from `org-capture-templates', and then | |
374 | file the newly captured information. The text is immediately inserted | |
375 | at the target location, and an indirect buffer is shown where you can | |
376 | edit it. Pressing \\[org-capture-finalize] brings you back to the previous state | |
377 | of Emacs, so that you can continue your work. | |
378 | ||
379 | When called interactively with a \\[universal-argument] prefix argument GOTO, don't capture | |
380 | anything, just go to the file/headline where the selected template | |
381 | stores its notes. With a double prefix argument \ | |
382 | \\[universal-argument] \\[universal-argument], go to the last note | |
383 | stored. | |
384 | ||
385 | When called with a `C-0' (zero) prefix, insert a template at point. | |
386 | ||
387 | Lisp programs can set KEYS to a string associated with a template in | |
388 | `org-capture-templates'. In this case, interactive selection will be | |
389 | bypassed." | |
390 | (interactive "P") | |
391 | (cond | |
392 | ((equal goto '(4)) (org-capture-goto-target)) | |
393 | ((equal goto '(16)) (org-capture-goto-last-stored)) | |
394 | (t | |
395 | ;; FIXME: Are these needed? | |
396 | (let* ((orig-buf (current-buffer)) | |
397 | (annotation (if (and (boundp 'org-capture-link-is-already-stored) | |
398 | org-capture-link-is-already-stored) | |
399 | (plist-get org-store-link-plist :annotation) | |
400 | (org-store-link nil))) | |
401 | (initial (and (org-region-active-p) | |
402 | (buffer-substring (point) (mark)))) | |
403 | (entry (org-capture-select-template keys))) | |
afe98dfa CD |
404 | (when (stringp initial) |
405 | (remove-text-properties 0 (length initial) '(read-only t) initial)) | |
406 | (when (stringp annotation) | |
407 | (remove-text-properties 0 (length annotation) | |
408 | '(read-only t) annotation)) | |
86fbb8ca CD |
409 | (cond |
410 | ((equal entry "C") | |
411 | (customize-variable 'org-capture-templates)) | |
412 | ((equal entry "q") | |
413 | (error "Abort")) | |
414 | (t | |
415 | (org-capture-set-plist entry) | |
416 | (org-capture-get-template) | |
417 | (org-capture-put :original-buffer orig-buf :annotation annotation | |
418 | :initial initial) | |
419 | (org-capture-put :default-time | |
420 | (or org-overriding-default-time | |
421 | (org-current-time))) | |
422 | (org-capture-set-target-location) | |
423 | (condition-case error | |
424 | (org-capture-put :template (org-capture-fill-template)) | |
425 | ((error quit) | |
426 | (if (get-buffer "*Capture*") (kill-buffer "*Capture*")) | |
427 | (error "Capture abort: %s" error))) | |
428 | ||
429 | (if (equal goto 0) | |
430 | ;;insert at point | |
431 | (org-capture-insert-template-here) | |
432 | (condition-case error | |
433 | (org-capture-place-template) | |
434 | ((error quit) | |
435 | (if (and (buffer-base-buffer (current-buffer)) | |
436 | (string-match "\\`CAPTURE-" (buffer-name))) | |
437 | (kill-buffer (current-buffer))) | |
438 | (set-window-configuration (org-capture-get :return-to-wconf)) | |
439 | (error "Capture template `%s': %s" | |
440 | (org-capture-get :key) | |
441 | (nth 1 error)))) | |
442 | (if (org-capture-get :immediate-finish) | |
443 | (org-capture-finalize) | |
444 | (if (and (org-mode-p) | |
445 | (org-capture-get :clock-in)) | |
446 | (condition-case nil | |
447 | (progn | |
448 | (if (org-clock-is-active) | |
449 | (org-capture-put :interrupted-clock | |
450 | (copy-marker org-clock-marker))) | |
451 | (org-clock-in) | |
452 | (org-set-local 'org-capture-clock-was-started t)) | |
453 | (error | |
454 | "Could not start the clock in this capture buffer"))))))))))) | |
455 | ||
456 | ||
457 | (defun org-capture-get-template () | |
458 | "Get the template from a file or a function if necessary." | |
459 | (let ((txt (org-capture-get :template)) file) | |
460 | (cond | |
461 | ((and (listp txt) (eq (car txt) 'file)) | |
462 | (if (file-exists-p | |
463 | (setq file (expand-file-name (nth 1 txt) org-directory))) | |
464 | (setq txt (org-file-contents file)) | |
465 | (setq txt (format "* Template file %s not found" (nth 1 txt))))) | |
466 | ((and (listp txt) (eq (car txt) 'function)) | |
467 | (if (fboundp (nth 1 txt)) | |
468 | (setq txt (funcall (nth 1 txt))) | |
469 | (setq txt (format "* Template function %s not found" (nth 1 txt))))) | |
470 | ((not txt) (setq txt "")) | |
471 | ((stringp txt)) | |
472 | (t (setq txt "* Invalid capture template"))) | |
473 | (org-capture-put :template txt))) | |
474 | ||
acedf35c CD |
475 | (defun org-capture-finalize (&optional stay-with-capture) |
476 | "Finalize the capture process. | |
477 | With prefix argument STAY-WITH-CAPTURE, jump to the location of the | |
478 | captured item after finalizing." | |
479 | (interactive "P") | |
86fbb8ca CD |
480 | (unless (and org-capture-mode |
481 | (buffer-base-buffer (current-buffer))) | |
482 | (error "This does not seem to be a capture buffer for Org-mode")) | |
483 | ||
484 | ;; Did we start the clock in this capture buffer? | |
485 | (when (and org-capture-clock-was-started | |
486 | org-clock-marker (marker-buffer org-clock-marker) | |
487 | (equal (marker-buffer org-clock-marker) (buffer-base-buffer)) | |
488 | (> org-clock-marker (point-min)) | |
489 | (< org-clock-marker (point-max))) | |
490 | ;; Looks like the clock we started is still running. Clock out. | |
491 | (let (org-log-note-clock-out) (org-clock-out)) | |
492 | (when (and (org-capture-get :clock-resume 'local) | |
493 | (markerp (org-capture-get :interrupted-clock 'local)) | |
494 | (buffer-live-p (marker-buffer | |
495 | (org-capture-get :interrupted-clock 'local)))) | |
afe98dfa CD |
496 | (let ((clock-in-task (org-capture-get :interrupted-clock 'local))) |
497 | (org-with-point-at clock-in-task | |
498 | (org-clock-in))) | |
86fbb8ca CD |
499 | (message "Interrupted clock has been resumed"))) |
500 | ||
501 | (let ((beg (point-min)) | |
502 | (end (point-max)) | |
503 | (abort-note nil)) | |
504 | (widen) | |
505 | ||
506 | (if org-note-abort | |
507 | (let ((m1 (org-capture-get :begin-marker 'local)) | |
508 | (m2 (org-capture-get :end-marker 'local))) | |
509 | (if (and m1 m2 (= m1 beg) (= m2 end)) | |
510 | (progn | |
511 | (setq abort-note 'clean) | |
512 | (kill-region m1 m2)) | |
513 | (setq abort-note 'dirty))) | |
514 | ||
515 | ;; Make sure that the empty lines after are correct | |
516 | (when (and (> (point-max) end) ; indeed, the buffer was still narrowed | |
517 | (member (org-capture-get :type 'local) | |
518 | '(entry item checkitem plain))) | |
519 | (save-excursion | |
520 | (goto-char end) | |
521 | (or (bolp) (newline)) | |
522 | (org-capture-empty-lines-after | |
523 | (or (org-capture-get :empty-lines 'local) 0)))) | |
524 | ;; Postprocessing: Update Statistics cookies, do the sorting | |
525 | (when (org-mode-p) | |
526 | (save-excursion | |
527 | (when (ignore-errors (org-back-to-heading)) | |
528 | (org-update-parent-todo-statistics) | |
529 | (org-update-checkbox-count))) | |
530 | ;; FIXME Here we should do the sorting | |
531 | ;; If we have added a table line, maybe recompute? | |
532 | (when (and (eq (org-capture-get :type 'local) 'table-line) | |
533 | (org-at-table-p)) | |
534 | (if (org-table-get-stored-formulas) | |
535 | (org-table-recalculate 'all) ;; FIXME: Should we iterate??? | |
536 | (org-table-align))) | |
537 | ) | |
538 | ;; Store this place as the last one where we stored something | |
539 | ;; Do the marking in the base buffer, so that it makes sense after | |
540 | ;; the indirect buffer has been killed. | |
541 | (org-capture-bookmark-last-stored-position) | |
542 | ||
543 | ;; Run the hook | |
544 | (run-hooks 'org-capture-before-finalize-hook) | |
545 | ) | |
546 | ||
547 | ;; Kill the indirect buffer | |
548 | (save-buffer) | |
afe98dfa CD |
549 | (let ((return-wconf (org-capture-get :return-to-wconf 'local)) |
550 | (new-buffer (org-capture-get :new-buffer 'local)) | |
551 | (kill-buffer (org-capture-get :kill-buffer 'local)) | |
552 | (base-buffer (buffer-base-buffer (current-buffer)))) | |
553 | ||
554 | ;; Kill the indiret buffer | |
86fbb8ca | 555 | (kill-buffer (current-buffer)) |
afe98dfa CD |
556 | |
557 | ;; Kill the target buffer if that is desired | |
558 | (when (and base-buffer new-buffer kill-buffer) | |
559 | (with-current-buffer base-buffer (save-buffer)) | |
560 | (kill-buffer base-buffer)) | |
561 | ||
86fbb8ca CD |
562 | ;; Restore the window configuration before capture |
563 | (set-window-configuration return-wconf)) | |
acedf35c CD |
564 | |
565 | (run-hooks 'org-capture-after-finalize-hook) | |
566 | ;; Special cases | |
567 | (cond | |
568 | (abort-note | |
86fbb8ca CD |
569 | (cond |
570 | ((equal abort-note 'clean) | |
afe98dfa | 571 | (message "Capture process aborted and target buffer cleaned up")) |
86fbb8ca | 572 | ((equal abort-note 'dirty) |
acedf35c CD |
573 | (error "Capture process aborted, but target buffer could not be cleaned up correctly")))) |
574 | (stay-with-capture | |
575 | (org-capture-goto-last-stored))) | |
576 | ;; Return if we did store something | |
577 | (not abort-note))) | |
86fbb8ca CD |
578 | |
579 | (defun org-capture-refile () | |
580 | "Finalize the current capture and then refile the entry. | |
581 | Refiling is done from the base buffer, because the indirect buffer is then | |
acedf35c | 582 | already gone. Any prefix argument will be passed to the refile comand." |
86fbb8ca CD |
583 | (interactive) |
584 | (unless (eq (org-capture-get :type 'local) 'entry) | |
585 | (error | |
586 | "Refiling from a capture buffer makes only sense for `entry'-type templates")) | |
587 | (let ((pos (point)) | |
588 | (base (buffer-base-buffer (current-buffer))) | |
589 | (org-refile-for-capture t)) | |
590 | (org-capture-finalize) | |
591 | (save-window-excursion | |
592 | (with-current-buffer (or base (current-buffer)) | |
593 | (save-excursion | |
594 | (save-restriction | |
595 | (widen) | |
596 | (goto-char pos) | |
597 | (call-interactively 'org-refile))))))) | |
598 | ||
599 | (defun org-capture-kill () | |
600 | "Abort the current capture process." | |
601 | (interactive) | |
602 | ;; FIXME: This does not do the right thing, we need to remove the new stuff | |
603 | ;; By hand it is easy: undo, then kill the buffer | |
604 | (let ((org-note-abort t) (org-capture-before-finalize-hook nil)) | |
605 | (org-capture-finalize))) | |
606 | ||
607 | (defun org-capture-goto-last-stored () | |
608 | "Go to the location where the last remember note was stored." | |
609 | (interactive) | |
610 | (org-goto-marker-or-bmk org-capture-last-stored-marker | |
611 | "org-capture-last-stored") | |
612 | (message "This is the last note stored by a capture process")) | |
613 | ||
614 | ;;; Supporting functions for handling the process | |
615 | ||
616 | (defun org-capture-set-target-location (&optional target) | |
617 | "Find target buffer and position and store then in the property list." | |
618 | (let ((target-entry-p t)) | |
619 | (setq target (or target (org-capture-get :target))) | |
620 | (save-excursion | |
621 | (cond | |
622 | ((eq (car target) 'file) | |
623 | (set-buffer (org-capture-target-buffer (nth 1 target))) | |
624 | (setq target-entry-p nil)) | |
625 | ||
626 | ((eq (car target) 'id) | |
627 | (let ((loc (org-id-find (nth 1 target)))) | |
628 | (if (not loc) | |
629 | (error "Cannot find target ID \"%s\"" (nth 1 target)) | |
630 | (set-buffer (org-capture-target-buffer (car loc))) | |
631 | (goto-char (cdr loc))))) | |
632 | ||
633 | ((eq (car target) 'file+headline) | |
634 | (set-buffer (org-capture-target-buffer (nth 1 target))) | |
635 | (let ((hd (nth 2 target))) | |
636 | (goto-char (point-min)) | |
afe98dfa CD |
637 | (unless (org-mode-p) |
638 | (error "Target buffer for file+headline should be in Org mode")) | |
86fbb8ca CD |
639 | (if (re-search-forward |
640 | (format org-complex-heading-regexp-format (regexp-quote hd)) | |
641 | nil t) | |
642 | (goto-char (point-at-bol)) | |
643 | (goto-char (point-max)) | |
644 | (or (bolp) (insert "\n")) | |
645 | (insert "* " hd "\n") | |
646 | (beginning-of-line 0)))) | |
647 | ||
648 | ((eq (car target) 'file+olp) | |
649 | (let ((m (org-find-olp (cdr target)))) | |
650 | (set-buffer (marker-buffer m)) | |
651 | (goto-char m))) | |
652 | ||
653 | ((eq (car target) 'file+regexp) | |
654 | (set-buffer (org-capture-target-buffer (nth 1 target))) | |
655 | (goto-char (point-min)) | |
656 | (if (re-search-forward (nth 2 target) nil t) | |
657 | (progn | |
658 | (goto-char (if (org-capture-get :prepend) | |
659 | (match-beginning 0) (match-end 0))) | |
660 | (org-capture-put :exact-position (point)) | |
661 | (setq target-entry-p (and (org-mode-p) (org-at-heading-p)))) | |
662 | (error "No match for target regexp in file %s" (nth 1 target)))) | |
663 | ||
acedf35c | 664 | ((memq (car target) '(file+datetree file+datetree+prompt)) |
86fbb8ca CD |
665 | (require 'org-datetree) |
666 | (set-buffer (org-capture-target-buffer (nth 1 target))) | |
667 | ;; Make a date tree entry, with the current date (or yesterday, | |
668 | ;; if we are extending dates for a couple of hours) | |
669 | (org-datetree-find-date-create | |
670 | (calendar-gregorian-from-absolute | |
acedf35c CD |
671 | (cond |
672 | ||
673 | (org-overriding-default-time | |
674 | ;; use the overriding default time | |
675 | (time-to-days org-overriding-default-time)) | |
86fbb8ca | 676 | |
acedf35c CD |
677 | ((eq (car target) 'file+datetree+prompt) |
678 | ;; prompt for date | |
679 | (time-to-days (org-read-date | |
680 | nil t nil "Date for tree entry:" | |
681 | (days-to-time (org-today))))) | |
682 | (t | |
683 | ;; current date, possible corrected for late night workers | |
684 | (org-today)))))) | |
685 | ||
86fbb8ca CD |
686 | ((eq (car target) 'file+function) |
687 | (set-buffer (org-capture-target-buffer (nth 1 target))) | |
688 | (funcall (nth 2 target)) | |
689 | (org-capture-put :exact-position (point)) | |
690 | (setq target-entry-p (and (org-mode-p) (org-at-heading-p)))) | |
691 | ||
692 | ((eq (car target) 'function) | |
693 | (funcall (nth 1 target)) | |
694 | (org-capture-put :exact-position (point)) | |
695 | (setq target-entry-p (and (org-mode-p) (org-at-heading-p)))) | |
696 | ||
697 | ((eq (car target) 'clock) | |
698 | (if (and (markerp org-clock-hd-marker) | |
699 | (marker-buffer org-clock-hd-marker)) | |
700 | (progn (set-buffer (marker-buffer org-clock-hd-marker)) | |
701 | (goto-char org-clock-hd-marker)) | |
702 | (error "No running clock that could be used as capture target"))) | |
703 | ||
704 | (t (error "Invalid capture target specification"))) | |
705 | ||
706 | (org-capture-put :buffer (current-buffer) :pos (point) | |
707 | :target-entry-p target-entry-p)))) | |
708 | ||
709 | (defun org-capture-target-buffer (file) | |
710 | "Get a buffer for FILE." | |
afe98dfa CD |
711 | (setq file (or (org-string-nw-p file) |
712 | org-default-notes-file | |
713 | (error "No notes file specified, and no default available"))) | |
86fbb8ca | 714 | (or (org-find-base-buffer-visiting file) |
afe98dfa CD |
715 | (progn (org-capture-put :new-buffer t) |
716 | (find-file-noselect (expand-file-name file org-directory))))) | |
86fbb8ca CD |
717 | |
718 | (defun org-capture-steal-local-variables (buffer) | |
719 | "Install Org-mode local variables." | |
720 | (mapc (lambda (v) | |
721 | (ignore-errors (org-set-local (car v) (cdr v)))) | |
722 | (buffer-local-variables buffer))) | |
723 | ||
724 | (defun org-capture-place-template () | |
725 | "Insert the template at the target location, and display the buffer." | |
726 | (org-capture-put :return-to-wconf (current-window-configuration)) | |
727 | (delete-other-windows) | |
728 | (org-switch-to-buffer-other-window | |
729 | (org-capture-get-indirect-buffer (org-capture-get :buffer) "CAPTURE")) | |
afe98dfa | 730 | (widen) |
86fbb8ca CD |
731 | (show-all) |
732 | (goto-char (org-capture-get :pos)) | |
733 | (org-set-local 'org-capture-target-marker | |
734 | (move-marker (make-marker) (point))) | |
735 | (let* ((template (org-capture-get :template)) | |
736 | (type (org-capture-get :type))) | |
737 | (case type | |
738 | ((nil entry) (org-capture-place-entry)) | |
739 | (table-line (org-capture-place-table-line)) | |
740 | (plain (org-capture-place-plain-text)) | |
afe98dfa CD |
741 | (item (org-capture-place-item)) |
742 | (checkitem (org-capture-place-item)))) | |
86fbb8ca CD |
743 | (org-capture-mode 1) |
744 | (org-set-local 'org-capture-current-plist org-capture-plist)) | |
745 | ||
746 | (defun org-capture-place-entry () | |
747 | "Place the template as a new Org entry." | |
748 | (let* ((txt (org-capture-get :template)) | |
749 | (reversed (org-capture-get :prepend)) | |
750 | (target-entry-p (org-capture-get :target-entry-p)) | |
751 | level beg end file) | |
752 | ||
753 | (cond | |
754 | ((org-capture-get :exact-position) | |
755 | (goto-char (org-capture-get :exact-position))) | |
756 | ((not target-entry-p) | |
757 | ;; Insert as top-level entry, either at beginning or at end of file | |
758 | (setq level 1) | |
759 | (if reversed | |
760 | (progn (goto-char (point-min)) | |
afe98dfa CD |
761 | (or (org-at-heading-p) |
762 | (outline-next-heading))) | |
86fbb8ca CD |
763 | (goto-char (point-max)) |
764 | (or (bolp) (insert "\n")))) | |
765 | (t | |
766 | ;; Insert as a child of the current entry | |
767 | (and (looking-at "\\*+") | |
768 | (setq level (- (match-end 0) (match-beginning 0)))) | |
769 | (setq level (org-get-valid-level (or level 1) 1)) | |
770 | (if reversed | |
771 | (progn | |
772 | (outline-next-heading) | |
773 | (or (bolp) (insert "\n"))) | |
774 | (org-end-of-subtree t t) | |
775 | (or (bolp) (insert "\n"))))) | |
776 | (org-capture-empty-lines-before) | |
777 | (setq beg (point)) | |
778 | (org-paste-subtree level txt 'for-yank) | |
779 | (org-capture-empty-lines-after 1) | |
780 | (org-capture-position-for-last-stored beg) | |
781 | (outline-next-heading) | |
782 | (setq end (point)) | |
783 | (org-capture-mark-kill-region beg (1- end)) | |
784 | (org-capture-narrow beg (1- end)) | |
afe98dfa | 785 | (goto-char beg) |
86fbb8ca CD |
786 | (if (re-search-forward "%\\?" end t) (replace-match "")))) |
787 | ||
788 | (defun org-capture-place-item () | |
789 | "Place the template as a new plain list item." | |
790 | (let* ((txt (org-capture-get :template)) | |
791 | (target-entry-p (org-capture-get :target-entry-p)) | |
792 | (ind 0) | |
793 | beg end) | |
794 | (cond | |
795 | ((org-capture-get :exact-position) | |
796 | (goto-char (org-capture-get :exact-position))) | |
797 | ((not target-entry-p) | |
798 | ;; Insert as top-level entry, either at beginning or at end of file | |
799 | (setq beg (point-min) end (point-max))) | |
800 | (t | |
801 | (setq beg (1+ (point-at-eol)) | |
802 | end (save-excursion (outline-next-heading) (point))))) | |
803 | (if (org-capture-get :prepend) | |
804 | (progn | |
805 | (goto-char beg) | |
afe98dfa | 806 | (if (org-search-forward-unenclosed org-item-beginning-re end t) |
86fbb8ca CD |
807 | (progn |
808 | (goto-char (match-beginning 0)) | |
809 | (setq ind (org-get-indentation))) | |
810 | (goto-char end) | |
811 | (setq ind 0))) | |
812 | (goto-char end) | |
afe98dfa | 813 | (if (org-search-backward-unenclosed org-item-beginning-re beg t) |
86fbb8ca CD |
814 | (progn |
815 | (setq ind (org-get-indentation)) | |
816 | (org-end-of-item)) | |
817 | (setq ind 0))) | |
818 | ;; Remove common indentation | |
819 | (setq txt (org-remove-indentation txt)) | |
820 | ;; Make sure this is indeed an item | |
821 | (unless (string-match (concat "\\`" (org-item-re)) txt) | |
822 | (setq txt (concat "- " | |
823 | (mapconcat 'identity (split-string txt "\n") | |
824 | "\n ")))) | |
825 | ;; Set the correct indentation, depending on context | |
826 | (setq ind (make-string ind ?\ )) | |
827 | (setq txt (concat ind | |
828 | (mapconcat 'identity (split-string txt "\n") | |
829 | (concat "\n" ind)) | |
830 | "\n")) | |
831 | ;; Insert, with surrounding empty lines | |
832 | (org-capture-empty-lines-before) | |
833 | (setq beg (point)) | |
834 | (insert txt) | |
835 | (or (bolp) (insert "\n")) | |
836 | (org-capture-empty-lines-after 1) | |
837 | (org-capture-position-for-last-stored beg) | |
838 | (forward-char 1) | |
839 | (setq end (point)) | |
840 | (org-capture-mark-kill-region beg (1- end)) | |
841 | (org-capture-narrow beg (1- end)) | |
842 | (if (re-search-forward "%\\?" end t) (replace-match "")))) | |
843 | ||
844 | (defun org-capture-place-table-line () | |
845 | "Place the template as a table line." | |
846 | (require 'org-table) | |
847 | (let* ((txt (org-capture-get :template)) | |
848 | (target-entry-p (org-capture-get :target-entry-p)) | |
849 | (table-line-pos (org-capture-get :table-line-pos)) | |
850 | ind beg end) | |
851 | (cond | |
852 | ((org-capture-get :exact-position) | |
853 | (goto-char (org-capture-get :exact-position))) | |
854 | ((not target-entry-p) | |
855 | ;; Table is not necessarily under a heading | |
856 | (setq beg (point-min) end (point-max))) | |
857 | (t | |
858 | ;; WE are at a heading, limit search to the body | |
859 | (setq beg (1+ (point-at-eol)) | |
860 | end (save-excursion (outline-next-heading) (point))))) | |
861 | (if (re-search-forward org-table-dataline-regexp end t) | |
862 | (let ((b (org-table-begin)) (e (org-table-end))) | |
863 | (goto-char e) | |
864 | (if (looking-at "[ \t]*#\\+TBLFM:") | |
865 | (forward-line 1)) | |
866 | (narrow-to-region b (point))) | |
867 | (goto-char end) | |
868 | (insert "\n| |\n|----|\n| |\n") | |
869 | (narrow-to-region (1+ end) (point))) | |
870 | ;; We are narrowed to the table, or to an empty line if there was no table | |
871 | ||
872 | ;; Check if the template is good | |
873 | (if (not (string-match org-table-dataline-regexp txt)) | |
874 | (setq txt "| %?Bad template |\n")) | |
875 | (cond | |
876 | ((and table-line-pos | |
877 | (string-match "\\(I+\\)\\([-+][0-9]\\)" table-line-pos)) | |
878 | ;; we have a complex line specification | |
879 | (goto-char (point-min)) | |
880 | (let ((nh (- (match-end 1) (match-beginning 1))) | |
881 | (delta (string-to-number (match-string 2 table-line-pos))) | |
882 | ll) | |
883 | ;; The user wants a special position in the table | |
884 | (org-table-get-specials) | |
885 | (setq ll (ignore-errors (aref org-table-hlines nh))) | |
886 | (unless ll (error "Invalid table line specification \"%s\"" | |
887 | table-line-pos)) | |
888 | (setq ll (+ ll delta (if (< delta 0) 0 -1))) | |
889 | (org-goto-line ll) | |
890 | (org-table-insert-row 'below) | |
891 | (beginning-of-line 1) | |
892 | (delete-region (point) (1+ (point-at-eol))) | |
893 | (setq beg (point)) | |
894 | (insert txt) | |
895 | (setq end (point)))) | |
896 | ((org-capture-get :prepend) | |
897 | (goto-char (point-min)) | |
898 | (re-search-forward org-table-hline-regexp nil t) | |
899 | (beginning-of-line 1) | |
900 | (re-search-forward org-table-dataline-regexp nil t) | |
901 | (beginning-of-line 1) | |
902 | (setq beg (point)) | |
903 | (org-table-insert-row) | |
904 | (beginning-of-line 1) | |
905 | (delete-region (point) (1+ (point-at-eol))) | |
906 | (insert txt) | |
907 | (setq end (point))) | |
908 | (t | |
909 | (goto-char (point-max)) | |
910 | (re-search-backward org-table-dataline-regexp nil t) | |
911 | (beginning-of-line 1) | |
912 | (org-table-insert-row 'below) | |
913 | (beginning-of-line 1) | |
914 | (delete-region (point) (1+ (point-at-eol))) | |
915 | (setq beg (point)) | |
916 | (insert txt) | |
917 | (setq end (point)))) | |
918 | (goto-char beg) | |
919 | (org-capture-position-for-last-stored 'table-line) | |
920 | (if (re-search-forward "%\\?" end t) (replace-match "")) | |
921 | (org-table-align))) | |
922 | ||
923 | (defun org-capture-place-plain-text () | |
924 | "Place the template plainly." | |
925 | (let* ((txt (org-capture-get :template)) | |
926 | beg end) | |
927 | (goto-char (cond | |
928 | ((org-capture-get :exact-position)) | |
929 | ((org-capture-get :prepend) (point-min)) | |
930 | (t (point-max)))) | |
931 | (or (bolp) (newline)) | |
932 | (org-capture-empty-lines-before) | |
933 | (setq beg (point)) | |
934 | (insert txt) | |
935 | (org-capture-empty-lines-after 1) | |
936 | (org-capture-position-for-last-stored beg) | |
937 | (setq end (point)) | |
938 | (org-capture-mark-kill-region beg (1- end)) | |
939 | (org-capture-narrow beg (1- end)) | |
940 | (if (re-search-forward "%\\?" end t) (replace-match "")))) | |
941 | ||
942 | (defun org-capture-mark-kill-region (beg end) | |
943 | "Mark the region that will have to be killed when aborting capture." | |
944 | (let ((m1 (move-marker (make-marker) beg)) | |
945 | (m2 (move-marker (make-marker) end))) | |
946 | (org-capture-put :begin-marker m1) | |
947 | (org-capture-put :end-marker m2))) | |
948 | ||
949 | (defun org-capture-position-for-last-stored (where) | |
950 | "Memorize the position that should later become the position of last capture." | |
951 | (cond | |
952 | ((integerp where) | |
953 | (org-capture-put :position-for-last-stored | |
954 | (move-marker (make-marker) where | |
955 | (or (buffer-base-buffer (current-buffer)) | |
956 | (current-buffer))))) | |
957 | ((eq where 'table-line) | |
958 | (org-capture-put :position-for-last-stored | |
959 | (list 'table-line | |
960 | (org-table-current-dline)))) | |
961 | (t (error "This should not happen")))) | |
962 | ||
963 | (defun org-capture-bookmark-last-stored-position () | |
964 | "Bookmark the last-captured position." | |
965 | (let* ((where (org-capture-get :position-for-last-stored 'local)) | |
966 | (pos (cond | |
967 | ((markerp where) | |
968 | (prog1 (marker-position where) | |
969 | (move-marker where nil))) | |
970 | ((and (listp where) (eq (car where) 'table-line)) | |
971 | (if (org-at-table-p) | |
972 | (save-excursion | |
973 | (org-table-goto-line (nth 1 where)) | |
974 | (point-at-bol)) | |
975 | (point)))))) | |
976 | (with-current-buffer (buffer-base-buffer (current-buffer)) | |
977 | (save-excursion | |
978 | (save-restriction | |
979 | (widen) | |
980 | (goto-char pos) | |
981 | (bookmark-set "org-capture-last-stored") | |
982 | (move-marker org-capture-last-stored-marker (point))))))) | |
983 | ||
984 | (defun org-capture-narrow (beg end) | |
985 | "Narrow, unless configuration says not to narrow." | |
986 | (unless (org-capture-get :unnarrowed) | |
987 | (narrow-to-region beg end) | |
988 | (goto-char beg))) | |
989 | ||
990 | (defun org-capture-empty-lines-before (&optional n) | |
991 | "Arrange for the correct number of empty lines before the insertion point. | |
992 | Point will be after the empty lines, so insertion can directly be done." | |
993 | (setq n (or n (org-capture-get :empty-lines) 0)) | |
994 | (let ((pos (point))) | |
995 | (org-back-over-empty-lines) | |
996 | (delete-region (point) pos) | |
afe98dfa | 997 | (if (> n 0) (newline n)))) |
86fbb8ca CD |
998 | |
999 | (defun org-capture-empty-lines-after (&optional n) | |
1000 | "Arrange for the correct number of empty lines after the inserted string. | |
1001 | Point will remain at the first line after the inserted text." | |
1002 | (setq n (or n (org-capture-get :empty-lines) 0)) | |
1003 | (org-back-over-empty-lines) | |
1004 | (while (looking-at "[ \t]*\n") (replace-match "")) | |
1005 | (let ((pos (point))) | |
afe98dfa | 1006 | (if (> n 0) (newline n)) |
86fbb8ca CD |
1007 | (goto-char pos))) |
1008 | ||
1009 | (defvar org-clock-marker) ; Defined in org.el | |
1010 | ;;;###autoload | |
1011 | (defun org-capture-insert-template-here () | |
1012 | (let* ((template (org-capture-get :template)) | |
1013 | (type (org-capture-get :type)) | |
1014 | beg end pp) | |
1015 | (or (bolp) (newline)) | |
1016 | (setq beg (point)) | |
1017 | (cond | |
1018 | ((and (eq type 'entry) (org-mode-p)) | |
1019 | (org-paste-subtree nil template t)) | |
1020 | ((and (memq type '(item checkitem)) | |
1021 | (org-mode-p) | |
1022 | (save-excursion (skip-chars-backward " \t\n") | |
1023 | (setq pp (point)) | |
1024 | (org-in-item-p))) | |
1025 | (goto-char pp) | |
1026 | (org-insert-item) | |
1027 | (skip-chars-backward " ") | |
1028 | (skip-chars-backward "-+*0123456789).") | |
1029 | (delete-region (point) (point-at-eol)) | |
1030 | (setq beg (point)) | |
1031 | (org-remove-indentation template) | |
1032 | (insert template) | |
1033 | (org-capture-empty-lines-after) | |
1034 | (goto-char beg) | |
afe98dfa | 1035 | (org-list-repair) |
86fbb8ca CD |
1036 | (org-end-of-item) |
1037 | (setq end (point))) | |
1038 | (t (insert template))) | |
1039 | (setq end (point)) | |
1040 | (goto-char beg) | |
1041 | (if (re-search-forward "%\\?" end t) | |
1042 | (replace-match "")))) | |
1043 | ||
1044 | (defun org-capture-set-plist (entry) | |
1045 | "Initialize the property list from the template definition." | |
1046 | (setq org-capture-plist (copy-sequence (nthcdr 5 entry))) | |
1047 | (org-capture-put :key (car entry) :description (nth 1 entry) | |
1048 | :target (nth 3 entry)) | |
1049 | (let ((txt (nth 4 entry)) (type (or (nth 2 entry) 'entry))) | |
1050 | (when (or (not txt) (and (stringp txt) (not (string-match "\\S-" txt)))) | |
1051 | ;; The template may be empty or omitted for special types. | |
1052 | ;; Here we insert the default templates for such cases. | |
1053 | (cond | |
1054 | ((eq type 'item) (setq txt "- %?")) | |
1055 | ((eq type 'checkitem) (setq txt "- [ ] %?")) | |
1056 | ((eq type 'table-line) (setq txt "| %? |")) | |
1057 | ((member type '(nil entry)) (setq txt "* %?\n %a")))) | |
1058 | (org-capture-put :template txt :type type))) | |
1059 | ||
1060 | (defun org-capture-goto-target (&optional template-key) | |
1061 | "Go to the target location of a capture template. | |
1062 | The user is queried for the template." | |
1063 | (interactive) | |
1064 | (let* (org-select-template-temp-major-mode | |
1065 | (entry (org-capture-select-template template-key))) | |
1066 | (unless entry | |
1067 | (error "No capture template selected")) | |
1068 | (org-capture-set-plist entry) | |
1069 | (org-capture-set-target-location) | |
1070 | (switch-to-buffer (org-capture-get :buffer)) | |
1071 | (goto-char (org-capture-get :pos)))) | |
1072 | ||
1073 | (defun org-capture-get-indirect-buffer (&optional buffer prefix) | |
1074 | "Make an indirect buffer for a capture process. | |
1075 | Use PREFIX as a prefix for the name of the indirect buffer." | |
1076 | (setq buffer (or buffer (current-buffer))) | |
1077 | (let ((n 1) (base (buffer-name buffer)) bname) | |
1078 | (setq bname (concat prefix "-" base)) | |
1079 | (while (buffer-live-p (get-buffer bname)) | |
1080 | (setq bname (concat prefix "-" (number-to-string (incf n)) "-" base))) | |
1081 | (condition-case nil | |
1082 | (make-indirect-buffer buffer bname 'clone) | |
1083 | (error (make-indirect-buffer buffer bname))))) | |
1084 | ||
1085 | ||
1086 | ;;; The template code | |
1087 | ||
1088 | (defun org-capture-select-template (&optional keys) | |
1089 | "Select a capture template. | |
1090 | Lisp programs can force the template by setting KEYS to a string." | |
afe98dfa CD |
1091 | (if org-capture-templates |
1092 | (if keys | |
1093 | (or (assoc keys org-capture-templates) | |
1094 | (error "No capture template referred to by \"%s\" keys" keys)) | |
1095 | (if (= 1 (length org-capture-templates)) | |
1096 | (car org-capture-templates) | |
1097 | (org-mks org-capture-templates | |
1098 | "Select a capture template\n=========================" | |
1099 | "Template key: " | |
1100 | '(("C" "Customize org-capture-templates") | |
1101 | ("q" "Abort"))))) | |
1102 | ;; Use an arbitrary default template | |
1103 | '("t" "Task" entry (file+headline "" "Tasks") "* TODO %?\n %u\n %a"))) | |
86fbb8ca CD |
1104 | |
1105 | (defun org-capture-fill-template (&optional template initial annotation) | |
1106 | "Fill a template and return the filled template as a string. | |
1107 | The template may still contain \"%?\" for cursor positioning." | |
1108 | (setq template (or template (org-capture-get :template))) | |
1109 | (when (stringp initial) | |
1110 | (setq initial (org-no-properties initial)) | |
1111 | (remove-text-properties 0 (length initial) '(read-only t) initial)) | |
1112 | (let* ((buffer (org-capture-get :buffer)) | |
1113 | (file (buffer-file-name (or (buffer-base-buffer buffer) buffer))) | |
1114 | (ct (org-capture-get :default-time)) | |
1115 | (dct (decode-time ct)) | |
1116 | (ct1 | |
1117 | (if (< (nth 2 dct) org-extend-today-until) | |
1118 | (encode-time 0 59 23 (1- (nth 3 dct)) (nth 4 dct) (nth 5 dct)) | |
1119 | ct)) | |
1120 | (plist-p (if org-store-link-plist t nil)) | |
1121 | (v-c (and (> (length kill-ring) 0) (current-kill 0))) | |
1122 | (v-x (or (org-get-x-clipboard 'PRIMARY) | |
1123 | (org-get-x-clipboard 'CLIPBOARD) | |
1124 | (org-get-x-clipboard 'SECONDARY))) | |
1125 | (v-t (format-time-string (car org-time-stamp-formats) ct)) | |
1126 | (v-T (format-time-string (cdr org-time-stamp-formats) ct)) | |
1127 | (v-u (concat "[" (substring v-t 1 -1) "]")) | |
1128 | (v-U (concat "[" (substring v-T 1 -1) "]")) | |
1129 | ;; `initial' and `annotation' might habe been passed. | |
1130 | ;; But if the property list has them, we prefer those values | |
1131 | (v-i (or (plist-get org-store-link-plist :initial) | |
1132 | initial | |
1133 | (org-capture-get :initial) | |
1134 | "")) | |
1135 | (v-a (or (plist-get org-store-link-plist :annotation) | |
1136 | annotation | |
1137 | (org-capture-get :annotation) | |
1138 | "")) | |
1139 | ;; Is the link empty? Then we do not want it... | |
1140 | (v-a (if (equal v-a "[[]]") "" v-a)) | |
1141 | (clipboards (remove nil (list v-i | |
1142 | (org-get-x-clipboard 'PRIMARY) | |
1143 | (org-get-x-clipboard 'CLIPBOARD) | |
1144 | (org-get-x-clipboard 'SECONDARY) | |
1145 | v-c))) | |
1146 | (v-A (if (and v-a | |
1147 | (string-match | |
1148 | "\\[\\(\\[.*?\\]\\)\\(\\[.*?\\]\\)?\\]" v-a)) | |
1149 | (replace-match "[\\1[%^{Link description}]]" nil nil v-a) | |
1150 | v-a)) | |
1151 | (v-n user-full-name) | |
1152 | (v-k (if (marker-buffer org-clock-marker) | |
1153 | (org-substring-no-properties org-clock-heading))) | |
1154 | (v-K (if (marker-buffer org-clock-marker) | |
1155 | (org-make-link-string | |
1156 | (buffer-file-name (marker-buffer org-clock-marker)) | |
1157 | org-clock-heading))) | |
1158 | v-I | |
1159 | (org-startup-folded nil) | |
1160 | (org-inhibit-startup t) | |
1161 | org-time-was-given org-end-time-was-given x | |
1162 | prompt completions char time pos default histvar) | |
1163 | ||
1164 | (setq org-store-link-plist | |
1165 | (plist-put org-store-link-plist :annotation v-a) | |
1166 | org-store-link-plist | |
1167 | (plist-put org-store-link-plist :initial v-i)) | |
afe98dfa | 1168 | (setq initial v-i) |
86fbb8ca CD |
1169 | |
1170 | (unless template (setq template "") (message "No template") (ding) | |
1171 | (sit-for 1)) | |
1172 | (save-window-excursion | |
1173 | (delete-other-windows) | |
1174 | (switch-to-buffer (get-buffer-create "*Capture*")) | |
1175 | (erase-buffer) | |
1176 | (insert template) | |
1177 | (goto-char (point-min)) | |
1178 | (org-capture-steal-local-variables buffer) | |
1179 | (setq buffer-file-name nil) | |
1180 | ||
1181 | ;; %[] Insert contents of a file. | |
1182 | (goto-char (point-min)) | |
1183 | (while (re-search-forward "%\\[\\(.+\\)\\]" nil t) | |
1184 | (unless (org-capture-escaped-%) | |
1185 | (let ((start (match-beginning 0)) | |
1186 | (end (match-end 0)) | |
1187 | (filename (expand-file-name (match-string 1)))) | |
1188 | (goto-char start) | |
1189 | (delete-region start end) | |
1190 | (condition-case error | |
1191 | (insert-file-contents filename) | |
1192 | (error (insert (format "%%![Couldn't insert %s: %s]" | |
1193 | filename error))))))) | |
1194 | ;; %() embedded elisp | |
1195 | (goto-char (point-min)) | |
1196 | (while (re-search-forward "%\\((.+)\\)" nil t) | |
1197 | (unless (org-capture-escaped-%) | |
1198 | (goto-char (match-beginning 0)) | |
1199 | (let ((template-start (point))) | |
1200 | (forward-char 1) | |
1201 | (let ((result | |
1202 | (condition-case error | |
1203 | (eval (read (current-buffer))) | |
1204 | (error (format "%%![Error: %s]" error))))) | |
1205 | (delete-region template-start (point)) | |
1206 | (insert result))))) | |
1207 | ||
1208 | ;; Simple %-escapes | |
afe98dfa | 1209 | (goto-char (point-min)) |
86fbb8ca CD |
1210 | (while (re-search-forward "%\\([tTuUaiAcxkKI]\\)" nil t) |
1211 | (unless (org-capture-escaped-%) | |
1212 | (when (and initial (equal (match-string 0) "%i")) | |
1213 | (save-match-data | |
1214 | (let* ((lead (buffer-substring | |
1215 | (point-at-bol) (match-beginning 0)))) | |
1216 | (setq v-i (mapconcat 'identity | |
1217 | (org-split-string initial "\n") | |
1218 | (concat "\n" lead)))))) | |
1219 | (replace-match | |
1220 | (or (eval (intern (concat "v-" (match-string 1)))) "") | |
1221 | t t))) | |
1222 | ||
1223 | ;; From the property list | |
1224 | (when plist-p | |
1225 | (goto-char (point-min)) | |
1226 | (while (re-search-forward "%\\(:[-a-zA-Z]+\\)" nil t) | |
1227 | (unless (org-capture-escaped-%) | |
1228 | (and (setq x (or (plist-get org-store-link-plist | |
1229 | (intern (match-string 1))) "")) | |
1230 | (replace-match x t t))))) | |
1231 | ||
1232 | ;; Turn on org-mode in temp buffer, set local variables | |
1233 | ;; This is to support completion in interactive prompts | |
1234 | (let ((org-inhibit-startup t)) (org-mode)) | |
1235 | ;; Interactive template entries | |
1236 | (goto-char (point-min)) | |
1237 | (while (re-search-forward "%^\\({\\([^}]*\\)}\\)?\\([gGtTuUCLp]\\)?" | |
1238 | nil t) | |
1239 | (unless (org-capture-escaped-%) | |
1240 | (setq char (if (match-end 3) (match-string 3)) | |
1241 | prompt (if (match-end 2) (match-string 2))) | |
1242 | (goto-char (match-beginning 0)) | |
1243 | (replace-match "") | |
1244 | (setq completions nil default nil) | |
1245 | (when prompt | |
1246 | (setq completions (org-split-string prompt "|") | |
1247 | prompt (pop completions) | |
1248 | default (car completions) | |
1249 | histvar (intern (concat | |
1250 | "org-capture-template-prompt-history::" | |
1251 | (or prompt ""))) | |
1252 | completions (mapcar 'list completions))) | |
afe98dfa | 1253 | (unless (boundp histvar) (set histvar nil)) |
86fbb8ca CD |
1254 | (cond |
1255 | ((member char '("G" "g")) | |
1256 | (let* ((org-last-tags-completion-table | |
1257 | (org-global-tags-completion-table | |
1258 | (if (equal char "G") | |
1259 | (org-agenda-files) | |
1260 | (and file (list file))))) | |
1261 | (org-add-colon-after-tag-completion t) | |
1262 | (ins (org-icompleting-read | |
1263 | (if prompt (concat prompt ": ") "Tags: ") | |
1264 | 'org-tags-completion-function nil nil nil | |
1265 | 'org-tags-history))) | |
1266 | (setq ins (mapconcat 'identity | |
1267 | (org-split-string | |
afe98dfa | 1268 | ins (org-re "[^[:alnum:]_@#%]+")) |
86fbb8ca CD |
1269 | ":")) |
1270 | (when (string-match "\\S-" ins) | |
1271 | (or (equal (char-before) ?:) (insert ":")) | |
1272 | (insert ins) | |
afe98dfa CD |
1273 | (or (equal (char-after) ?:) (insert ":")) |
1274 | (and (org-on-heading-p) (org-set-tags nil 'align))))) | |
86fbb8ca CD |
1275 | ((equal char "C") |
1276 | (cond ((= (length clipboards) 1) (insert (car clipboards))) | |
1277 | ((> (length clipboards) 1) | |
1278 | (insert (read-string "Clipboard/kill value: " | |
1279 | (car clipboards) '(clipboards . 1) | |
1280 | (car clipboards)))))) | |
1281 | ((equal char "L") | |
1282 | (cond ((= (length clipboards) 1) | |
1283 | (org-insert-link 0 (car clipboards))) | |
1284 | ((> (length clipboards) 1) | |
1285 | (org-insert-link 0 (read-string "Clipboard/kill value: " | |
1286 | (car clipboards) | |
1287 | '(clipboards . 1) | |
1288 | (car clipboards)))))) | |
1289 | ((equal char "p") | |
1290 | (let* | |
1291 | ((prop (org-substring-no-properties prompt)) | |
1292 | (pall (concat prop "_ALL")) | |
1293 | (allowed | |
1294 | (with-current-buffer | |
1295 | (get-buffer (file-name-nondirectory file)) | |
1296 | (or (cdr (assoc pall org-file-properties)) | |
1297 | (cdr (assoc pall org-global-properties)) | |
1298 | (cdr (assoc pall org-global-properties-fixed))))) | |
1299 | (existing (with-current-buffer | |
1300 | (get-buffer (file-name-nondirectory file)) | |
1301 | (mapcar 'list (org-property-values prop)))) | |
1302 | (propprompt (concat "Value for " prop ": ")) | |
1303 | (val (if allowed | |
1304 | (org-completing-read | |
1305 | propprompt | |
1306 | (mapcar 'list (org-split-string allowed | |
1307 | "[ \t]+")) | |
1308 | nil 'req-match) | |
1309 | (org-completing-read-no-i propprompt | |
1310 | existing nil nil | |
1311 | "" nil "")))) | |
1312 | (org-set-property prop val))) | |
1313 | (char | |
1314 | ;; These are the date/time related ones | |
1315 | (setq org-time-was-given (equal (upcase char) char)) | |
1316 | (setq time (org-read-date (equal (upcase char) char) t nil | |
1317 | prompt)) | |
1318 | (if (equal (upcase char) char) (setq org-time-was-given t)) | |
1319 | (org-insert-time-stamp time org-time-was-given | |
1320 | (member char '("u" "U")) | |
1321 | nil nil (list org-end-time-was-given))) | |
1322 | (t | |
1323 | (let (org-completion-use-ido) | |
1324 | (insert (org-completing-read-no-i | |
1325 | (concat (if prompt prompt "Enter string") | |
1326 | (if default (concat " [" default "]")) | |
1327 | ": ") | |
1328 | completions nil nil nil histvar default))))))) | |
1329 | ;; Make sure there are no empty lines before the text, and that | |
1330 | ;; it ends with a newline character | |
1331 | (goto-char (point-min)) | |
1332 | (while (looking-at "[ \t]*\n") (replace-match "")) | |
1333 | (if (re-search-forward "[ \t\n]*\\'" nil t) (replace-match "\n")) | |
1334 | ;; Return the expanded tempate and kill the temporary buffer | |
1335 | (untabify (point-min) (point-max)) | |
1336 | (set-buffer-modified-p nil) | |
1337 | (prog1 (buffer-string) (kill-buffer (current-buffer)))))) | |
1338 | ||
1339 | (defun org-capture-escaped-% () | |
1340 | "Check if % was escaped - if yes, unescape it now." | |
1341 | (if (equal (char-before (match-beginning 0)) ?\\) | |
1342 | (progn | |
1343 | (delete-region (1- (match-beginning 0)) (match-beginning 0)) | |
1344 | t) | |
1345 | nil)) | |
1346 | ||
1347 | ;;;###autoload | |
1348 | (defun org-capture-import-remember-templates () | |
1349 | "Set org-capture-templates to be similar to `org-remember-templates'." | |
1350 | (interactive) | |
1351 | (when (and (yes-or-no-p | |
1352 | "Import old remember templates into org-capture-templates? ") | |
1353 | (yes-or-no-p | |
1354 | "Note that this will remove any templates currently defined in `org-capture-templates'. Do you still want to go ahead? ")) | |
1355 | (require 'org-remember) | |
1356 | (setq org-capture-templates | |
1357 | (mapcar | |
1358 | (lambda (entry) | |
1359 | (let ((desc (car entry)) | |
1360 | (key (char-to-string (nth 1 entry))) | |
1361 | (template (nth 2 entry)) | |
1362 | (file (or (nth 3 entry) org-default-notes-file)) | |
1363 | (position (or (nth 4 entry) org-remember-default-headline)) | |
1364 | (type 'entry) | |
1365 | (prepend org-reverse-note-order) | |
1366 | immediate target) | |
1367 | (cond | |
1368 | ((member position '(top bottom)) | |
1369 | (setq target (list 'file file) | |
1370 | prepend (eq position 'top))) | |
1371 | ((eq position 'date-tree) | |
1372 | (setq target (list 'file+datetree file) | |
1373 | prepend nil)) | |
1374 | (t (setq target (list 'file+headline file position)))) | |
1375 | ||
1376 | (when (string-match "%!" template) | |
1377 | (setq template (replace-match "" t t template) | |
1378 | immediate t)) | |
1379 | ||
1380 | (append (list key desc type target template) | |
1381 | (if prepend '(:prepend t)) | |
1382 | (if immediate '(:immediate-finish t))))) | |
1383 | ||
1384 | org-remember-templates)))) | |
1385 | ||
1386 | (provide 'org-capture) | |
1387 | ||
1388 | ;; arch-tag: 986bf41b-8ada-4e28-bf20-e8388a7205a0 | |
1389 | ||
1390 | ;;; org-capture.el ends here |