Commit | Line | Data |
---|---|---|
0bd48b37 CD |
1 | ;;; org-footnote.el --- Footnote support in Org and elsewhere |
2 | ;; | |
cbd20947 | 3 | ;; Copyright (C) 2009-2011 Free Software Foundation, Inc. |
0bd48b37 CD |
4 | ;; |
5 | ;; Author: Carsten Dominik <carsten at orgmode dot org> | |
6 | ;; Keywords: outlines, hypermedia, calendar, wp | |
7 | ;; Homepage: http://orgmode.org | |
3ab2c837 | 8 | ;; Version: 7.7 |
0bd48b37 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 the code dealing with footnotes in Org-mode. | |
29 | ;; The code can also be used in arbitrary text modes to provide | |
30 | ;; footnotes. Compared to Steven L Baur's footnote.el it provides | |
31 | ;; better support for resuming editing. It is less configurable than | |
32 | ;; Steve's code, though. | |
33 | ||
34 | ;;; Code: | |
35 | ||
36 | (eval-when-compile | |
37 | (require 'cl)) | |
38 | (require 'org-macs) | |
39 | (require 'org-compat) | |
40 | ||
3ab2c837 | 41 | (declare-function org-combine-plists "org" (&rest plists)) |
54a0dee5 | 42 | (declare-function org-in-commented-line "org" ()) |
3ab2c837 | 43 | (declare-function org-in-indented-comment-line "org" ()) |
0bd48b37 | 44 | (declare-function org-in-regexp "org" (re &optional nlines visually)) |
3ab2c837 | 45 | (declare-function org-in-block-p "org" (names)) |
0bd48b37 CD |
46 | (declare-function org-mark-ring-push "org" (&optional pos buffer)) |
47 | (declare-function outline-next-heading "outline") | |
48 | (declare-function org-trim "org" (s)) | |
49 | (declare-function org-show-context "org" (&optional key)) | |
50 | (declare-function org-back-to-heading "org" (&optional invisible-ok)) | |
51 | (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) | |
8bfe682a | 52 | (declare-function org-in-verbatim-emphasis "org" ()) |
ed21c5c8 | 53 | (declare-function org-inside-latex-macro-p "org" ()) |
3ab2c837 BG |
54 | (declare-function org-id-uuid "org" ()) |
55 | (declare-function org-fill-paragraph "org" (&optional justify)) | |
56 | (declare-function org-export-preprocess-string "org-exp" | |
57 | (string &rest parameters)) | |
58 | ||
59 | (defvar org-outline-regexp-bol) ; defined in org.el | |
c8d0cf5c | 60 | (defvar org-odd-levels-only) ;; defined in org.el |
3ab2c837 | 61 | (defvar org-bracket-link-regexp) ; defined in org.el |
afe98dfa | 62 | (defvar message-signature-separator) ;; defined in message.el |
0bd48b37 CD |
63 | |
64 | (defconst org-footnote-re | |
3ab2c837 BG |
65 | ;; Only [1]-like footnotes are closed in this regexp, as footnotes |
66 | ;; from other types might contain square brackets (i.e. links) in | |
67 | ;; their definition. | |
68 | ;; | |
69 | ;; `org-re' is used for regexp compatibility with XEmacs. | |
70 | (org-re (concat "\\[\\(?:" | |
71 | ;; Match inline footnotes. | |
72 | "fn:\\([-_[:word:]]+\\)?:\\|" | |
73 | ;; Match other footnotes. | |
74 | "\\(?:\\([0-9]+\\)\\]\\)\\|" | |
75 | "\\(fn:[-_[:word:]]+\\)" | |
76 | "\\)")) | |
0bd48b37 CD |
77 | "Regular expression for matching footnotes.") |
78 | ||
c8d0cf5c | 79 | (defconst org-footnote-definition-re |
0bd48b37 CD |
80 | (org-re "^\\(\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]\\)") |
81 | "Regular expression matching the definition of a footnote.") | |
82 | ||
3ab2c837 BG |
83 | (defvar org-footnote-forbidden-blocks '("example" "verse" "src" "ascii" "beamer" |
84 | "docbook" "html" "latex" "odt") | |
85 | "Names of blocks where footnotes are not allowed.") | |
86 | ||
86fbb8ca CD |
87 | (defgroup org-footnote nil |
88 | "Footnotes in Org-mode." | |
89 | :tag "Org Footnote" | |
90 | :group 'org) | |
91 | ||
0bd48b37 CD |
92 | (defcustom org-footnote-section "Footnotes" |
93 | "Outline heading containing footnote definitions before export. | |
94 | This can be nil, to place footnotes locally at the end of the current | |
95 | outline node. If can also be the name of a special outline heading | |
96 | under which footnotes should be put. | |
97 | This variable defines the place where Org puts the definition | |
98 | automatically, i.e. when creating the footnote, and when sorting the notes. | |
99 | However, by hand you may place definitions *anywhere*. | |
100 | If this is a string, during export, all subtrees starting with this | |
101 | heading will be removed after extracting footnote definitions." | |
86fbb8ca | 102 | :group 'org-footnote |
0bd48b37 | 103 | :type '(choice |
8bfe682a | 104 | (string :tag "Collect footnotes under heading") |
0bd48b37 CD |
105 | (const :tag "Define footnotes locally" nil))) |
106 | ||
107 | (defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:" | |
108 | "Tag marking the beginning of footnote section. | |
109 | The Org-mode footnote engine can be used in arbitrary text files as well | |
110 | as in Org-mode. Outside Org-mode, new footnotes are always placed at | |
111 | the end of the file. When you normalize the notes, any line containing | |
112 | only this tag will be removed, a new one will be inserted at the end | |
113 | of the file, followed by the collected and normalized footnotes." | |
86fbb8ca | 114 | :group 'org-footnote |
0bd48b37 CD |
115 | :type 'string) |
116 | ||
117 | (defcustom org-footnote-define-inline nil | |
ed21c5c8 | 118 | "Non-nil means define footnotes inline, at reference location. |
0bd48b37 CD |
119 | When nil, footnotes will be defined in a special section near |
120 | the end of the document. When t, the [fn:label:definition] notation | |
121 | will be used to define the footnote at the reference position." | |
122 | :group 'org-footnote | |
123 | :type 'boolean) | |
124 | ||
125 | (defcustom org-footnote-auto-label t | |
ed21c5c8 | 126 | "Non-nil means define automatically new labels for footnotes. |
0bd48b37 CD |
127 | Possible values are: |
128 | ||
129 | nil prompt the user for each label | |
130 | t create unique labels of the form [fn:1], [fn:2], ... | |
131 | confirm like t, but let the user edit the created value. In particular, | |
132 | the label can be removed from the minibuffer, to create | |
133 | an anonymous footnote. | |
3ab2c837 | 134 | random Automatically generate a unique, random label. |
0bd48b37 CD |
135 | plain Automatically create plain number labels like [1]" |
136 | :group 'org-footnote | |
137 | :type '(choice | |
8bfe682a | 138 | (const :tag "Prompt for label" nil) |
0bd48b37 CD |
139 | (const :tag "Create automatic [fn:N]" t) |
140 | (const :tag "Offer automatic [fn:N] for editing" confirm) | |
3ab2c837 | 141 | (const :tag "Create a random label" random) |
0bd48b37 CD |
142 | (const :tag "Create automatic [N]" plain))) |
143 | ||
c8d0cf5c | 144 | (defcustom org-footnote-auto-adjust nil |
ed21c5c8 | 145 | "Non-nil means automatically adjust footnotes after insert/delete. |
c8d0cf5c CD |
146 | When this is t, after each insertion or deletion of a footnote, |
147 | simple fn:N footnotes will be renumbered, and all footnotes will be sorted. | |
148 | If you want to have just sorting or just renumbering, set this variable | |
149 | to `sort' or `renumber'. | |
150 | ||
151 | The main values of this variable can be set with in-buffer options: | |
152 | ||
153 | #+STARTUP: fnadjust | |
154 | #+STARTUP: nofnadjust" | |
155 | :group 'org-footnote | |
156 | :type '(choice | |
157 | (const :tag "Renumber" renumber) | |
158 | (const :tag "Sort" sort) | |
159 | (const :tag "Renumber and Sort" t))) | |
160 | ||
0bd48b37 | 161 | (defcustom org-footnote-fill-after-inline-note-extraction nil |
ed21c5c8 | 162 | "Non-nil means fill paragraphs after extracting footnotes. |
0bd48b37 CD |
163 | When extracting inline footnotes, the lengths of lines can change a lot. |
164 | When this option is set, paragraphs from which an inline footnote has been | |
165 | extracted will be filled again." | |
166 | :group 'org-footnote | |
167 | :type 'boolean) | |
168 | ||
3ab2c837 BG |
169 | (defun org-footnote-in-valid-context-p () |
170 | "Is point in a context where footnotes are allowed?" | |
171 | (save-match-data | |
172 | (not (or (org-in-commented-line) | |
173 | (org-in-indented-comment-line) | |
174 | (org-in-verbatim-emphasis) | |
175 | ;; Avoid literal example. | |
176 | (save-excursion | |
177 | (beginning-of-line) | |
178 | (looking-at "[ \t]*:[ \t]+")) | |
179 | ;; Avoid cited text and headers in message-mode. | |
180 | (and (derived-mode-p 'message-mode) | |
181 | (or (save-excursion | |
182 | (beginning-of-line) | |
183 | (looking-at message-cite-prefix-regexp)) | |
184 | (message-point-in-header-p))) | |
185 | ;; Avoid forbidden blocks. | |
186 | (org-in-block-p org-footnote-forbidden-blocks))))) | |
187 | ||
0bd48b37 CD |
188 | (defun org-footnote-at-reference-p () |
189 | "Is the cursor at a footnote reference? | |
3ab2c837 BG |
190 | |
191 | If so, return a list containing its label, beginning and ending | |
192 | positions, and the definition, when inlined." | |
193 | (when (and (org-footnote-in-valid-context-p) | |
194 | (or (looking-at org-footnote-re) | |
195 | (org-in-regexp org-footnote-re) | |
196 | (save-excursion (re-search-backward org-footnote-re nil t))) | |
197 | ;; Only inline footnotes can start at bol. | |
198 | (or (eq (char-before (match-end 0)) 58) | |
199 | (/= (match-beginning 0) (point-at-bol)))) | |
200 | (let* ((beg (match-beginning 0)) | |
201 | (label (or (match-string 2) (match-string 3) | |
202 | ;; Anonymous footnotes don't have labels | |
203 | (and (match-string 1) (concat "fn:" (match-string 1))))) | |
204 | ;; Inline footnotes don't end at (match-end 0) as | |
205 | ;; `org-footnote-re' stops just after the second colon. | |
206 | ;; Find the real ending with `scan-sexps', so Org doesn't | |
207 | ;; get fooled by unrelated closing square brackets. | |
208 | (end (ignore-errors (scan-sexps beg 1)))) | |
209 | ;; Point is really at a reference if it's located before true | |
210 | ;; ending of the footnote. | |
211 | (when (and end (< (point) end) | |
212 | ;; Verify match isn't a part of a link. | |
213 | (not (save-excursion | |
214 | (goto-char beg) | |
215 | (let ((linkp | |
216 | (save-match-data | |
217 | (org-in-regexp org-bracket-link-regexp)))) | |
218 | (and linkp (< (point) (cdr linkp)))))) | |
219 | ;; Verify point doesn't belong to a LaTeX macro. | |
220 | ;; Beware though, when two footnotes are side by | |
221 | ;; side, once the first one is changed into LaTeX, | |
222 | ;; the second one might then be considered as an | |
223 | ;; optional argument of the command. Thus, check | |
224 | ;; the `org-protected' property of that command. | |
225 | (or (not (org-inside-latex-macro-p)) | |
226 | (and (get-text-property (1- beg) 'org-protected) | |
227 | (not (get-text-property beg 'org-protected))))) | |
228 | (list label beg end | |
229 | ;; Definition: ensure this is an inline footnote first. | |
230 | (and (or (not label) (match-string 1)) | |
231 | (org-trim (buffer-substring (match-end 0) (1- end))))))))) | |
0bd48b37 CD |
232 | |
233 | (defun org-footnote-at-definition-p () | |
3ab2c837 BG |
234 | "Is the cursor at a footnote definition? |
235 | ||
0bd48b37 | 236 | This matches only pure definitions like [1] or [fn:name] at the beginning |
3ab2c837 | 237 | of a line. It does not match references like [fn:name:definition], where the |
0bd48b37 | 238 | footnote text is included and defined locally. |
3ab2c837 BG |
239 | |
240 | The return value will be nil if not at a footnote definition, and a list with | |
241 | label, start, end and definition of the footnote otherwise." | |
242 | (when (org-footnote-in-valid-context-p) | |
243 | (save-excursion | |
244 | (end-of-line) | |
245 | (let ((lim (save-excursion (re-search-backward | |
246 | (concat org-outline-regexp-bol | |
247 | "\\|^[ \t]*$") nil t)))) | |
248 | (when (re-search-backward org-footnote-definition-re lim t) | |
249 | (end-of-line) | |
250 | (list (match-string 2) | |
251 | (match-beginning 0) | |
252 | (save-match-data | |
253 | ;; In a message, limit search to signature. | |
254 | (let ((bound (and (derived-mode-p 'message-mode) | |
255 | (save-excursion | |
256 | (goto-char (point-max)) | |
257 | (re-search-backward | |
258 | message-signature-separator nil t))))) | |
259 | (or (and (re-search-forward | |
260 | (org-re | |
261 | (concat "^[ \t]*$" "\\|" | |
262 | org-outline-regexp-bol | |
263 | "\\|" | |
264 | "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]")) | |
265 | bound 'move) | |
266 | (progn (skip-chars-forward " \t\n") (point-at-bol))) | |
267 | (point)))) | |
268 | (org-trim (buffer-substring (match-end 0) (point))))))))) | |
269 | ||
270 | (defun org-footnote-get-next-reference (&optional label backward limit) | |
271 | "Return complete reference of the next footnote. | |
272 | ||
273 | If LABEL is provided, get the next reference of that footnote. If | |
274 | BACKWARD is non-nil, find previous reference instead. LIMIT is | |
275 | the buffer position bounding the search. | |
276 | ||
277 | Return value is a list like those provided by `org-footnote-at-reference-p'. | |
278 | If no footnote is found, return nil." | |
0bd48b37 | 279 | (save-excursion |
3ab2c837 BG |
280 | (let* ((label-fmt (if label (format "\\[%s[]:]" label) org-footnote-re))) |
281 | (catch 'exit | |
282 | (while t | |
283 | (unless (funcall (if backward #'re-search-backward #'re-search-forward) | |
284 | label-fmt limit t) | |
285 | (throw 'exit nil)) | |
286 | (unless backward (backward-char)) | |
287 | (let ((ref (org-footnote-at-reference-p))) | |
288 | (when ref (throw 'exit ref)))))))) | |
289 | ||
290 | (defun org-footnote-next-reference-or-definition (limit) | |
291 | "Move point to next footnote reference or definition. | |
292 | ||
293 | LIMIT is the buffer position bounding the search. | |
294 | ||
295 | Return value is a list like those provided by | |
296 | `org-footnote-at-reference-p' or `org-footnote-at-definition-p'. | |
297 | If no footnote is found, return nil." | |
298 | (let* (ref) | |
299 | (catch 'exit | |
300 | (while t | |
301 | (unless (re-search-forward org-footnote-re limit t) | |
302 | (throw 'exit nil)) | |
303 | ;; Beware: with [1]-like footnotes point will be just after | |
304 | ;; the closing square bracket. | |
305 | (backward-char) | |
306 | (cond | |
307 | ((setq ref (org-footnote-at-reference-p)) | |
308 | (throw 'exit ref)) | |
309 | ;; Definition: also grab the last square bracket, only | |
310 | ;; matched in `org-footnote-re' for [1]-like footnotes. | |
311 | ((save-match-data (org-footnote-at-definition-p)) | |
312 | (let ((end (match-end 0))) | |
313 | (throw 'exit | |
314 | (list nil (match-beginning 0) | |
315 | (if (eq (char-before end) 93) end (1+ end))))))))))) | |
316 | ||
317 | (defun org-footnote-get-definition (label) | |
318 | "Return label, boundaries and definition of the footnote LABEL." | |
319 | (let* ((label (regexp-quote (org-footnote-normalize-label label))) | |
320 | (re (format "^\\[%s\\]\\|.\\[%s:" label label)) | |
321 | pos) | |
322 | (save-excursion | |
323 | (when (or (re-search-forward re nil t) | |
324 | (and (goto-char (point-min)) | |
325 | (re-search-forward re nil t)) | |
326 | (and (progn (widen) t) | |
327 | (goto-char (point-min)) | |
328 | (re-search-forward re nil t))) | |
329 | (let ((refp (org-footnote-at-reference-p))) | |
330 | (cond | |
331 | ((and (nth 3 refp) refp)) | |
332 | ((org-footnote-at-definition-p)))))))) | |
0bd48b37 CD |
333 | |
334 | (defun org-footnote-goto-definition (label) | |
3ab2c837 | 335 | "Move point to the definition of the footnote LABEL." |
0bd48b37 CD |
336 | (interactive "sLabel: ") |
337 | (org-mark-ring-push) | |
3ab2c837 BG |
338 | (let ((def (org-footnote-get-definition label))) |
339 | (if (not def) | |
0bd48b37 | 340 | (error "Cannot find definition of footnote %s" label) |
3ab2c837 BG |
341 | (goto-char (nth 1 def)) |
342 | (looking-at (format "\\[%s\\]\\|\\[%s:" label label)) | |
343 | (goto-char (match-end 0)) | |
0bd48b37 CD |
344 | (org-show-context 'link-search) |
345 | (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")))) | |
346 | ||
86fbb8ca | 347 | (defun org-footnote-goto-previous-reference (label) |
afe98dfa | 348 | "Find the first closest (to point) reference of footnote with label LABEL." |
0bd48b37 CD |
349 | (interactive "sLabel: ") |
350 | (org-mark-ring-push) | |
3ab2c837 | 351 | (let* ((label (org-footnote-normalize-label label)) ref) |
0bd48b37 | 352 | (save-excursion |
3ab2c837 BG |
353 | (setq ref (or (org-footnote-get-next-reference label t) |
354 | (org-footnote-get-next-reference label) | |
355 | (save-restriction | |
356 | (widen) | |
357 | (or | |
358 | (org-footnote-get-next-reference label t) | |
359 | (org-footnote-get-next-reference label)))))) | |
360 | (if (not ref) | |
361 | (error "Cannot find reference of footnote %s" label) | |
362 | (goto-char (nth 1 ref)) | |
363 | (org-show-context 'link-search)))) | |
0bd48b37 CD |
364 | |
365 | (defun org-footnote-normalize-label (label) | |
3ab2c837 BG |
366 | "Return LABEL as an appropriate string." |
367 | (cond | |
368 | ((numberp label) (number-to-string label)) | |
369 | ((equal "" label) nil) | |
370 | ((not (string-match "^[0-9]+$\\|^fn:" label)) | |
371 | (concat "fn:" label)) | |
372 | (t label))) | |
373 | ||
374 | (defun org-footnote-all-labels (&optional with-defs) | |
375 | "Return list with all defined foot labels used in the buffer. | |
376 | ||
377 | If WITH-DEFS is non-nil, also associate the definition to each | |
378 | label. The function will then return an alist whose key is label | |
379 | and value definition." | |
380 | (let* (rtn | |
381 | (push-to-rtn | |
382 | (function | |
383 | ;; Depending on WITH-DEFS, store label or (label . def) of | |
384 | ;; footnote reference/definition given as argument in RTN. | |
385 | (lambda (el) | |
386 | (let ((lbl (car el))) | |
387 | (push (if with-defs (cons lbl (nth 3 el)) lbl) rtn)))))) | |
0bd48b37 CD |
388 | (save-excursion |
389 | (save-restriction | |
390 | (widen) | |
3ab2c837 | 391 | ;; Find all labels found in definitions. |
0bd48b37 | 392 | (goto-char (point-min)) |
3ab2c837 BG |
393 | (let (def) |
394 | (while (re-search-forward org-footnote-definition-re nil t) | |
395 | (when (setq def (org-footnote-at-definition-p)) | |
396 | (funcall push-to-rtn def)))) | |
397 | ;; Find all labels found in references. | |
0bd48b37 | 398 | (goto-char (point-min)) |
3ab2c837 BG |
399 | (let (ref) |
400 | (while (setq ref (org-footnote-get-next-reference)) | |
401 | (goto-char (nth 2 ref)) | |
402 | (and (car ref) ; ignore anonymous footnotes | |
403 | (not (funcall (if with-defs #'assoc #'member) (car ref) rtn)) | |
404 | (funcall push-to-rtn ref)))))) | |
0bd48b37 CD |
405 | rtn)) |
406 | ||
407 | (defun org-footnote-unique-label (&optional current) | |
408 | "Return a new unique footnote label. | |
409 | The returns the firsts fn:N labels that is currently not used." | |
410 | (unless current (setq current (org-footnote-all-labels))) | |
411 | (let ((fmt (if (eq org-footnote-auto-label 'plain) "%d" "fn:%d")) | |
412 | (cnt 1)) | |
413 | (while (member (format fmt cnt) current) | |
414 | (incf cnt)) | |
415 | (format fmt cnt))) | |
416 | ||
417 | (defvar org-footnote-label-history nil | |
418 | "History of footnote labels entered in current buffer.") | |
419 | (make-variable-buffer-local 'org-footnote-label-history) | |
420 | ||
421 | (defun org-footnote-new () | |
422 | "Insert a new footnote. | |
423 | This command prompts for a label. If this is a label referencing an | |
424 | existing label, only insert the label. If the footnote label is empty | |
425 | or new, let the user edit the definition of the footnote." | |
426 | (interactive) | |
3ab2c837 BG |
427 | (unless (and (not (bolp)) (org-footnote-in-valid-context-p)) |
428 | (error "Cannot insert a footnote here")) | |
429 | (let* ((labels (and (not (equal org-footnote-auto-label 'random)) | |
430 | (org-footnote-all-labels))) | |
0bd48b37 CD |
431 | (propose (org-footnote-unique-label labels)) |
432 | (label | |
3ab2c837 BG |
433 | (org-footnote-normalize-label |
434 | (cond | |
435 | ((member org-footnote-auto-label '(t plain)) | |
436 | propose) | |
437 | ((equal org-footnote-auto-label 'random) | |
438 | (require 'org-id) | |
439 | (substring (org-id-uuid) 0 8)) | |
440 | (t | |
441 | (completing-read | |
442 | "Label (leave empty for anonymous): " | |
443 | (mapcar 'list labels) nil nil | |
444 | (if (eq org-footnote-auto-label 'confirm) propose nil) | |
445 | 'org-footnote-label-history)))))) | |
0bd48b37 | 446 | (cond |
3ab2c837 | 447 | ((not label) |
0bd48b37 CD |
448 | (insert "[fn:: ]") |
449 | (backward-char 1)) | |
450 | ((member label labels) | |
451 | (insert "[" label "]") | |
452 | (message "New reference to existing note")) | |
453 | (org-footnote-define-inline | |
454 | (insert "[" label ": ]") | |
c8d0cf5c CD |
455 | (backward-char 1) |
456 | (org-footnote-auto-adjust-maybe)) | |
0bd48b37 CD |
457 | (t |
458 | (insert "[" label "]") | |
c8d0cf5c CD |
459 | (org-footnote-create-definition label) |
460 | (org-footnote-auto-adjust-maybe))))) | |
0bd48b37 CD |
461 | |
462 | (defun org-footnote-create-definition (label) | |
463 | "Start the definition of a footnote with label LABEL." | |
464 | (interactive "sLabel: ") | |
3ab2c837 | 465 | (let ((label (org-footnote-normalize-label label))) |
0bd48b37 CD |
466 | (cond |
467 | ((org-mode-p) | |
3ab2c837 BG |
468 | ;; No section, put footnote into the current outline node Try to |
469 | ;; find or make the special node | |
470 | (when org-footnote-section | |
471 | (goto-char (point-min)) | |
472 | (let ((re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$"))) | |
473 | (unless (or (re-search-forward re nil t) | |
474 | (and (progn (widen) t) | |
475 | (re-search-forward re nil t))) | |
0bd48b37 | 476 | (goto-char (point-max)) |
3ab2c837 | 477 | (insert "\n\n* " org-footnote-section "\n")))) |
0bd48b37 | 478 | ;; Now go to the end of this entry and insert there. |
8d642074 CD |
479 | (org-footnote-goto-local-insertion-point) |
480 | (org-show-context 'link-search)) | |
0bd48b37 | 481 | (t |
3ab2c837 BG |
482 | ;; In a non-Org file. Search for footnote tag, or create it if |
483 | ;; necessary (at the end of buffer, or before a signature if in | |
484 | ;; Message mode). Set point after any definition already there. | |
485 | (let ((tag (concat "^" org-footnote-tag-for-non-org-mode-files "[ \t]*$")) | |
486 | (max (save-excursion | |
487 | (if (and (derived-mode-p 'message-mode) | |
488 | (re-search-forward | |
489 | message-signature-separator nil t)) | |
490 | (copy-marker (point-at-bol) t) | |
491 | (copy-marker (point-max) t))))) | |
492 | (goto-char max) | |
493 | (unless (re-search-backward tag nil t) | |
afe98dfa CD |
494 | (skip-chars-backward " \t\r\n") |
495 | (delete-region (point) max) | |
3ab2c837 BG |
496 | (insert "\n\n" org-footnote-tag-for-non-org-mode-files "\n")) |
497 | ;; Skip existing footnotes. | |
498 | (while (re-search-forward org-footnote-definition-re max t)) | |
499 | (let ((def (org-footnote-at-definition-p))) | |
500 | (when def (goto-char (nth 2 def)))) | |
501 | (set-marker max nil)))) | |
502 | ;; Insert footnote label, position point and notify user. | |
503 | (unless (bolp) (insert "\n")) | |
504 | (insert "\n[" label "] \n") | |
505 | (backward-char) | |
0bd48b37 CD |
506 | (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))) |
507 | ||
508 | ;;;###autoload | |
509 | (defun org-footnote-action (&optional special) | |
510 | "Do the right thing for footnotes. | |
3ab2c837 BG |
511 | |
512 | When at a footnote reference, jump to the definition. | |
513 | ||
514 | When at a definition, jump to the references if they exist, offer | |
515 | to create them otherwise. | |
516 | ||
517 | When neither at definition or reference, create a new footnote, | |
518 | interactively. | |
519 | ||
0bd48b37 CD |
520 | With prefix arg SPECIAL, offer additional commands in a menu." |
521 | (interactive "P") | |
522 | (let (tmp c) | |
523 | (cond | |
524 | (special | |
c8d0cf5c | 525 | (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete") |
0bd48b37 CD |
526 | (setq c (read-char-exclusive)) |
527 | (cond | |
3ab2c837 BG |
528 | ((eq c ?s) (org-footnote-normalize 'sort)) |
529 | ((eq c ?r) (org-footnote-renumber-fn:N)) | |
530 | ((eq c ?S) | |
c8d0cf5c CD |
531 | (org-footnote-renumber-fn:N) |
532 | (org-footnote-normalize 'sort)) | |
3ab2c837 BG |
533 | ((eq c ?n) (org-footnote-normalize)) |
534 | ((eq c ?d) (org-footnote-delete)) | |
0bd48b37 CD |
535 | (t (error "No such footnote command %c" c)))) |
536 | ((setq tmp (org-footnote-at-reference-p)) | |
3ab2c837 BG |
537 | (cond |
538 | ;; Anonymous footnote: move point at the beginning of its | |
539 | ;; definition. | |
540 | ((not (car tmp)) | |
541 | (goto-char (nth 1 tmp)) | |
542 | (forward-char 5)) | |
543 | ;; A definition exists: move to it. | |
544 | ((ignore-errors (org-footnote-goto-definition (car tmp)))) | |
545 | ;; No definition exists: offer to create it. | |
546 | ((yes-or-no-p (format "No definition for %s. Create one? " (car tmp))) | |
547 | (org-footnote-create-definition (car tmp))))) | |
0bd48b37 | 548 | ((setq tmp (org-footnote-at-definition-p)) |
3ab2c837 | 549 | (org-footnote-goto-previous-reference (car tmp))) |
0bd48b37 CD |
550 | (t (org-footnote-new))))) |
551 | ||
3ab2c837 BG |
552 | (defvar org-footnote-insert-pos-for-preprocessor 'point-max |
553 | "See `org-footnote-normalize'.") | |
554 | ||
555 | (defvar org-export-footnotes-seen nil) ; silence byte-compiler | |
556 | (defvar org-export-footnotes-data nil) ; silence byte-compiler | |
557 | ||
0bd48b37 | 558 | ;;;###autoload |
3ab2c837 | 559 | (defun org-footnote-normalize (&optional sort-only export-props) |
0bd48b37 | 560 | "Collect the footnotes in various formats and normalize them. |
3ab2c837 | 561 | |
c8d0cf5c | 562 | This finds the different sorts of footnotes allowed in Org, and |
0bd48b37 CD |
563 | normalizes them to the usual [N] format that is understood by the |
564 | Org-mode exporters. | |
3ab2c837 | 565 | |
0bd48b37 | 566 | When SORT-ONLY is set, only sort the footnote definitions into the |
3ab2c837 BG |
567 | referenced sequence. |
568 | ||
569 | If Org is amidst an export process, EXPORT-PROPS will hold the | |
570 | export properties of the buffer. | |
571 | ||
572 | When EXPORT-PROPS is non-nil, the default action is to insert | |
573 | normalized footnotes towards the end of the pre-processing buffer. | |
574 | Some exporters like docbook, odt, etc. expect that footnote | |
575 | definitions be available before any references to them. Such | |
576 | exporters can let bind `org-footnote-insert-pos-for-preprocessor' to | |
91af3942 | 577 | symbol 'point-min to achieve the desired behavior. |
3ab2c837 BG |
578 | |
579 | Additional note on `org-footnote-insert-pos-for-preprocessor': | |
580 | 1. This variable has not effect when FOR-PREPROCESSOR is nil. | |
581 | 2. This variable (potentially) obviates the need for extra scan | |
582 | of pre-processor buffer as witnessed in | |
583 | `org-export-docbook-get-footnotes'." | |
0bd48b37 | 584 | ;; This is based on Paul's function, but rewritten. |
3ab2c837 BG |
585 | ;; |
586 | ;; Re-create `org-with-limited-levels', but not limited to Org | |
587 | ;; buffers. | |
c8d0cf5c CD |
588 | (let* ((limit-level |
589 | (and (boundp 'org-inlinetask-min-level) | |
590 | org-inlinetask-min-level | |
591 | (1- org-inlinetask-min-level))) | |
592 | (nstars (and limit-level | |
593 | (if org-odd-levels-only | |
594 | (and limit-level (1- (* limit-level 2))) | |
595 | limit-level))) | |
3ab2c837 | 596 | (org-outline-regexp |
c8d0cf5c | 597 | (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ "))) |
3ab2c837 BG |
598 | ;; Determine the highest marker used so far. |
599 | (ref-table (when export-props org-export-footnotes-seen)) | |
600 | (count (if (and export-props ref-table) | |
601 | (apply 'max (mapcar (lambda (e) (nth 1 e)) ref-table)) | |
602 | 0)) | |
603 | ins-point ref) | |
604 | (save-excursion | |
605 | ;; 1. Find every footnote reference, extract the definition, and | |
606 | ;; collect that data in REF-TABLE. If SORT-ONLY is nil, also | |
607 | ;; normalize references. | |
0bd48b37 | 608 | (goto-char (point-min)) |
3ab2c837 BG |
609 | (while (setq ref (org-footnote-get-next-reference)) |
610 | (let* ((lbl (car ref)) | |
611 | ;; When footnote isn't anonymous, check if it's label | |
612 | ;; (REF) is already stored in REF-TABLE. In that case, | |
613 | ;; extract number used to identify it (MARKER). If | |
614 | ;; footnote is unknown, increment the global counter | |
615 | ;; (COUNT) to create an unused identifier. | |
616 | (a (and lbl (assoc lbl ref-table))) | |
617 | (marker (or (nth 1 a) (incf count))) | |
618 | ;; Is the reference inline or pointing to an inline | |
619 | ;; footnote? | |
620 | (inlinep (or (stringp (nth 3 ref)) (nth 3 a)))) | |
621 | ;; Replace footnote reference with [MARKER]. Maybe fill | |
622 | ;; paragraph once done. If SORT-ONLY is non-nil, only move | |
623 | ;; to the end of reference found to avoid matching it twice. | |
624 | ;; If EXPORT-PROPS isn't nil, also add `org-footnote' | |
625 | ;; property to it, so it can be easily recognized by | |
626 | ;; exporters. | |
627 | (if sort-only | |
628 | (goto-char (nth 2 ref)) | |
629 | (delete-region (nth 1 ref) (nth 2 ref)) | |
630 | (goto-char (nth 1 ref)) | |
631 | (let ((new-ref (format "[%d]" marker))) | |
632 | (when export-props (org-add-props new-ref '(org-footnote t))) | |
633 | (insert new-ref)) | |
634 | (and inlinep | |
635 | org-footnote-fill-after-inline-note-extraction | |
636 | (org-fill-paragraph))) | |
637 | ;; Add label (REF), identifier (MARKER) and definition (DEF) | |
638 | ;; to REF-TABLE if data was unknown. | |
639 | (unless a | |
640 | (let ((def (or (nth 3 ref) ; inline | |
641 | (and export-props | |
642 | (cdr (assoc lbl org-export-footnotes-data))) | |
643 | (nth 3 (org-footnote-get-definition lbl))))) | |
644 | (push (list lbl marker | |
645 | ;; When exporting, each definition goes | |
646 | ;; through `org-export-preprocess-string' so | |
647 | ;; it is ready to insert in the | |
648 | ;; backend-specific buffer. | |
649 | (if export-props | |
650 | (let ((parameters | |
651 | (org-combine-plists | |
652 | export-props | |
653 | '(:todo-keywords t :tags t :priority t)))) | |
654 | (org-export-preprocess-string def parameters)) | |
655 | def) | |
656 | inlinep) ref-table))) | |
657 | ;; Remove definition of non-inlined footnotes. | |
658 | (unless inlinep (org-footnote-delete-definitions lbl)))) | |
659 | ;; 2. Find and remove the footnote section, if any. Also | |
660 | ;; determine where footnotes shall be inserted (INS-POINT). | |
0bd48b37 CD |
661 | (goto-char (point-min)) |
662 | (cond | |
663 | ((org-mode-p) | |
664 | (if (and org-footnote-section | |
665 | (re-search-forward | |
666 | (concat "^\\*[ \t]+" (regexp-quote org-footnote-section) | |
667 | "[ \t]*$") | |
668 | nil t)) | |
3ab2c837 BG |
669 | (progn |
670 | (setq ins-point (match-beginning 0)) | |
671 | (delete-region (match-beginning 0) (org-end-of-subtree t))) | |
672 | (setq ins-point (point-max)))) | |
0bd48b37 | 673 | (t |
3ab2c837 BG |
674 | (when (re-search-forward |
675 | (concat "^" | |
676 | (regexp-quote org-footnote-tag-for-non-org-mode-files) | |
677 | "[ \t]*$") | |
678 | nil t) | |
679 | (replace-match "")) | |
680 | ;; In message-mode, ensure footnotes are inserted before the | |
681 | ;; signature. | |
682 | (let ((pt-max | |
683 | (or (and (derived-mode-p 'message-mode) | |
684 | (save-excursion | |
685 | (goto-char (point-max)) | |
686 | (re-search-backward | |
687 | message-signature-separator nil t) | |
688 | (1- (point)))) | |
689 | (point-max)))) | |
690 | (goto-char pt-max) | |
691 | (skip-chars-backward " \t\n\r") | |
692 | (forward-line) | |
693 | (delete-region (point) pt-max)) | |
0bd48b37 | 694 | (setq ins-point (point)))) |
3ab2c837 | 695 | ;; 3. Clean-up REF-TABLE. |
0bd48b37 | 696 | (setq ref-table |
3ab2c837 BG |
697 | (delq nil |
698 | (mapcar | |
0bd48b37 | 699 | (lambda (x) |
3ab2c837 BG |
700 | (cond |
701 | ;; When only sorting, ignore inline footnotes. | |
702 | ((and sort-only (nth 3 x)) nil) | |
703 | ;; No definition available: provide one. | |
704 | ((not (nth 2 x)) | |
705 | (append (butlast x 2) | |
706 | (list (format "DEFINITION NOT FOUND: %s" (car x)) | |
707 | (nth 3 x)))) | |
708 | (t x))) | |
709 | ref-table))) | |
710 | (setq ref-table (nreverse ref-table)) | |
711 | ;; 4. Insert the footnotes again in the buffer, at the | |
712 | ;; appropriate spot. | |
713 | (goto-char (or | |
714 | (and export-props | |
715 | (eq org-footnote-insert-pos-for-preprocessor 'point-min) | |
716 | (point-min)) | |
717 | ins-point | |
718 | (point-max))) | |
719 | (cond | |
720 | ;; No footnote: exit. | |
721 | ((not ref-table)) | |
722 | ;; Cases when footnotes should be inserted in one place. | |
723 | ((or (not (org-mode-p)) | |
724 | org-footnote-section | |
725 | (not sort-only)) | |
726 | ;; Insert again the section title. | |
727 | (cond | |
728 | ((not (org-mode-p)) | |
729 | (insert "\n\n" org-footnote-tag-for-non-org-mode-files "\n")) | |
730 | ((and org-footnote-section (not export-props)) | |
731 | (or (bolp) (insert "\n")) | |
732 | (insert "* " org-footnote-section "\n"))) | |
733 | ;; Insert the footnotes. | |
734 | (insert "\n" | |
735 | (mapconcat (lambda (x) (format "[%s] %s" | |
736 | (nth (if sort-only 0 1) x) (nth 2 x))) | |
737 | ref-table "\n\n") | |
738 | "\n\n") | |
739 | ;; When exporting, add newly inserted markers along with their | |
740 | ;; associated definition to `org-export-footnotes-seen'. | |
741 | (when export-props | |
742 | (setq org-export-footnotes-seen ref-table))) | |
743 | ;; Else, insert each definition at the end of the section | |
744 | ;; containing their first reference. Happens only in Org files | |
745 | ;; with no special footnote section, and only when doing | |
746 | ;; sorting. | |
747 | (t (mapc 'org-insert-footnote-reference-near-definition | |
748 | ref-table)))))) | |
0bd48b37 CD |
749 | |
750 | (defun org-insert-footnote-reference-near-definition (entry) | |
751 | "Find first reference of footnote ENTRY and insert the definition there. | |
752 | ENTRY is (fn-label num-mark definition)." | |
753 | (when (car entry) | |
65c439fd | 754 | (goto-char (point-min)) |
3ab2c837 BG |
755 | (let ((ref (org-footnote-get-next-reference (car entry)))) |
756 | (when ref | |
757 | (goto-char (nth 2 ref)) | |
758 | (org-footnote-goto-local-insertion-point) | |
759 | (insert (format "\n[%s] %s\n" (car entry) (nth 2 entry))))))) | |
0bd48b37 CD |
760 | |
761 | (defun org-footnote-goto-local-insertion-point () | |
762 | "Find insertion point for footnote, just before next outline heading." | |
c8d0cf5c | 763 | (org-with-limited-levels (outline-next-heading)) |
0bd48b37 CD |
764 | (or (bolp) (newline)) |
765 | (beginning-of-line 0) | |
766 | (while (and (not (bobp)) (= (char-after) ?#)) | |
767 | (beginning-of-line 0)) | |
c8d0cf5c | 768 | (if (looking-at "[ \t]*#\\+TBLFM:") (beginning-of-line 2)) |
0bd48b37 | 769 | (end-of-line 1) |
afe98dfa CD |
770 | (skip-chars-backward "\n\r\t ") |
771 | (forward-line)) | |
0bd48b37 | 772 | |
3ab2c837 BG |
773 | (defun org-footnote-delete-references (label) |
774 | "Delete every reference to footnote LABEL. | |
775 | Return the number of footnotes removed." | |
776 | (save-excursion | |
777 | (goto-char (point-min)) | |
778 | (let (ref (nref 0)) | |
779 | (while (setq ref (org-footnote-get-next-reference label)) | |
780 | (goto-char (nth 1 ref)) | |
781 | (delete-region (nth 1 ref) (nth 2 ref)) | |
782 | (incf nref)) | |
783 | nref))) | |
784 | ||
785 | (defun org-footnote-delete-definitions (label) | |
786 | "Delete every definition of the footnote LABEL. | |
787 | Return the number of footnotes removed." | |
788 | (save-excursion | |
789 | (goto-char (point-min)) | |
790 | (let ((def-re (concat "^\\[" (regexp-quote label) "\\]")) | |
791 | (ndef 0)) | |
792 | (while (re-search-forward def-re nil t) | |
793 | (let ((full-def (org-footnote-at-definition-p))) | |
794 | (delete-region (nth 1 full-def) (nth 2 full-def))) | |
795 | (incf ndef)) | |
796 | ndef))) | |
797 | ||
0bd48b37 CD |
798 | (defun org-footnote-delete (&optional label) |
799 | "Delete the footnote at point. | |
800 | This will remove the definition (even multiple definitions if they exist) | |
3ab2c837 BG |
801 | and all references of a footnote label. |
802 | ||
803 | If LABEL is non-nil, delete that footnote instead." | |
0bd48b37 | 804 | (catch 'done |
3ab2c837 BG |
805 | (let* ((nref 0) (ndef 0) x |
806 | ;; 1. Determine LABEL of footnote at point. | |
807 | (label (cond | |
808 | ;; LABEL is provided as argument. | |
809 | (label) | |
810 | ;; Footnote reference at point. If the footnote is | |
811 | ;; anonymous, delete it and exit instead. | |
812 | ((setq x (org-footnote-at-reference-p)) | |
813 | (or (car x) | |
814 | (progn | |
815 | (delete-region (nth 1 x) (nth 2 x)) | |
816 | (message "Anonymous footnote removed") | |
817 | (throw 'done t)))) | |
818 | ;; Footnote definition at point. | |
819 | ((setq x (org-footnote-at-definition-p)) | |
820 | (car x)) | |
821 | (t (error "Don't know which footnote to remove"))))) | |
822 | ;; 2. Now that LABEL is non-nil, find every reference and every | |
823 | ;; definition, and delete them. | |
824 | (setq nref (org-footnote-delete-references label) | |
825 | ndef (org-footnote-delete-definitions label)) | |
826 | ;; 3. Verify consistency of footnotes and notify user. | |
c8d0cf5c | 827 | (org-footnote-auto-adjust-maybe) |
0bd48b37 CD |
828 | (message "%d definition(s) of and %d reference(s) of footnote %s removed" |
829 | ndef nref label)))) | |
830 | ||
c8d0cf5c CD |
831 | (defun org-footnote-renumber-fn:N () |
832 | "Renumber the simple footnotes like fn:17 into a sequence in the document." | |
833 | (interactive) | |
834 | (let (map i (n 0)) | |
835 | (save-excursion | |
836 | (save-restriction | |
837 | (widen) | |
838 | (goto-char (point-min)) | |
839 | (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t) | |
840 | (setq i (string-to-number (match-string 1))) | |
841 | (when (and (string-match "\\S-" (buffer-substring | |
842 | (point-at-bol) (match-beginning 0))) | |
843 | (not (assq i map))) | |
844 | (push (cons i (number-to-string (incf n))) map))) | |
845 | (goto-char (point-min)) | |
846 | (while (re-search-forward "\\(\\[fn:\\)\\([0-9]+\\)\\([]:]\\)" nil t) | |
847 | (replace-match (concat "\\1" (cdr (assq (string-to-number (match-string 2)) map)) "\\3"))))))) | |
848 | ||
849 | (defun org-footnote-auto-adjust-maybe () | |
850 | "Renumber and/or sort footnotes according to user settings." | |
851 | (when (memq org-footnote-auto-adjust '(t renumber)) | |
852 | (org-footnote-renumber-fn:N)) | |
853 | (when (memq org-footnote-auto-adjust '(t sort)) | |
3ab2c837 | 854 | (let ((label (car (org-footnote-at-definition-p)))) |
c8d0cf5c CD |
855 | (org-footnote-normalize 'sort) |
856 | (when label | |
857 | (goto-char (point-min)) | |
858 | (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]") | |
859 | nil t) | |
860 | (progn (insert " ") | |
861 | (just-one-space))))))) | |
862 | ||
0bd48b37 CD |
863 | (provide 'org-footnote) |
864 | ||
5b409b39 | 865 | |
0bd48b37 CD |
866 | |
867 | ;;; org-footnote.el ends here |