Commit | Line | Data |
---|---|---|
0bd48b37 CD |
1 | ;;; org-footnote.el --- Footnote support in Org and elsewhere |
2 | ;; | |
114f9c96 | 3 | ;; Copyright (C) 2009, 2010 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 | |
ed21c5c8 | 8 | ;; Version: 6.35i |
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 | ||
54a0dee5 | 41 | (declare-function org-in-commented-line "org" ()) |
0bd48b37 CD |
42 | (declare-function org-in-regexp "org" (re &optional nlines visually)) |
43 | (declare-function org-mark-ring-push "org" (&optional pos buffer)) | |
44 | (declare-function outline-next-heading "outline") | |
45 | (declare-function org-trim "org" (s)) | |
46 | (declare-function org-show-context "org" (&optional key)) | |
47 | (declare-function org-back-to-heading "org" (&optional invisible-ok)) | |
48 | (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) | |
8bfe682a | 49 | (declare-function org-in-verbatim-emphasis "org" ()) |
ed21c5c8 | 50 | (declare-function org-inside-latex-macro-p "org" ()) |
c8d0cf5c | 51 | (defvar org-odd-levels-only) ;; defined in org.el |
0bd48b37 CD |
52 | |
53 | (defconst org-footnote-re | |
54 | (concat "[^][\n]" ; to make sure it is not at the beginning of a line | |
55 | "\\[" | |
56 | "\\(?:" | |
57 | "\\([0-9]+\\)" | |
58 | "\\|" | |
59 | (org-re "\\(fn:\\([-_[:word:]]+?\\)?\\)\\(?::\\([^\]]*?\\)\\)?") | |
60 | "\\)" | |
61 | "\\]") | |
62 | "Regular expression for matching footnotes.") | |
63 | ||
c8d0cf5c | 64 | (defconst org-footnote-definition-re |
0bd48b37 CD |
65 | (org-re "^\\(\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]\\)") |
66 | "Regular expression matching the definition of a footnote.") | |
67 | ||
68 | (defcustom org-footnote-section "Footnotes" | |
69 | "Outline heading containing footnote definitions before export. | |
70 | This can be nil, to place footnotes locally at the end of the current | |
71 | outline node. If can also be the name of a special outline heading | |
72 | under which footnotes should be put. | |
73 | This variable defines the place where Org puts the definition | |
74 | automatically, i.e. when creating the footnote, and when sorting the notes. | |
75 | However, by hand you may place definitions *anywhere*. | |
76 | If this is a string, during export, all subtrees starting with this | |
77 | heading will be removed after extracting footnote definitions." | |
78 | :group 'org-footnotes | |
79 | :type '(choice | |
8bfe682a | 80 | (string :tag "Collect footnotes under heading") |
0bd48b37 CD |
81 | (const :tag "Define footnotes locally" nil))) |
82 | ||
83 | (defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:" | |
84 | "Tag marking the beginning of footnote section. | |
85 | The Org-mode footnote engine can be used in arbitrary text files as well | |
86 | as in Org-mode. Outside Org-mode, new footnotes are always placed at | |
87 | the end of the file. When you normalize the notes, any line containing | |
88 | only this tag will be removed, a new one will be inserted at the end | |
89 | of the file, followed by the collected and normalized footnotes." | |
90 | :group 'org-footnotes | |
91 | :type 'string) | |
92 | ||
93 | (defcustom org-footnote-define-inline nil | |
ed21c5c8 | 94 | "Non-nil means define footnotes inline, at reference location. |
0bd48b37 CD |
95 | When nil, footnotes will be defined in a special section near |
96 | the end of the document. When t, the [fn:label:definition] notation | |
97 | will be used to define the footnote at the reference position." | |
98 | :group 'org-footnote | |
99 | :type 'boolean) | |
100 | ||
101 | (defcustom org-footnote-auto-label t | |
ed21c5c8 | 102 | "Non-nil means define automatically new labels for footnotes. |
0bd48b37 CD |
103 | Possible values are: |
104 | ||
105 | nil prompt the user for each label | |
106 | t create unique labels of the form [fn:1], [fn:2], ... | |
107 | confirm like t, but let the user edit the created value. In particular, | |
108 | the label can be removed from the minibuffer, to create | |
109 | an anonymous footnote. | |
110 | plain Automatically create plain number labels like [1]" | |
111 | :group 'org-footnote | |
112 | :type '(choice | |
8bfe682a | 113 | (const :tag "Prompt for label" nil) |
0bd48b37 CD |
114 | (const :tag "Create automatic [fn:N]" t) |
115 | (const :tag "Offer automatic [fn:N] for editing" confirm) | |
116 | (const :tag "Create automatic [N]" plain))) | |
117 | ||
c8d0cf5c | 118 | (defcustom org-footnote-auto-adjust nil |
ed21c5c8 | 119 | "Non-nil means automatically adjust footnotes after insert/delete. |
c8d0cf5c CD |
120 | When this is t, after each insertion or deletion of a footnote, |
121 | simple fn:N footnotes will be renumbered, and all footnotes will be sorted. | |
122 | If you want to have just sorting or just renumbering, set this variable | |
123 | to `sort' or `renumber'. | |
124 | ||
125 | The main values of this variable can be set with in-buffer options: | |
126 | ||
127 | #+STARTUP: fnadjust | |
128 | #+STARTUP: nofnadjust" | |
129 | :group 'org-footnote | |
130 | :type '(choice | |
131 | (const :tag "Renumber" renumber) | |
132 | (const :tag "Sort" sort) | |
133 | (const :tag "Renumber and Sort" t))) | |
134 | ||
0bd48b37 | 135 | (defcustom org-footnote-fill-after-inline-note-extraction nil |
ed21c5c8 | 136 | "Non-nil means fill paragraphs after extracting footnotes. |
0bd48b37 CD |
137 | When extracting inline footnotes, the lengths of lines can change a lot. |
138 | When this option is set, paragraphs from which an inline footnote has been | |
139 | extracted will be filled again." | |
140 | :group 'org-footnote | |
141 | :type 'boolean) | |
142 | ||
143 | (defun org-footnote-at-reference-p () | |
144 | "Is the cursor at a footnote reference? | |
145 | If yes, return the beginning position, the label, and the definition, if local." | |
146 | (when (org-in-regexp org-footnote-re 15) | |
147 | (list (match-beginning 0) | |
576e334d | 148 | (or (match-string 1) |
0bd48b37 CD |
149 | (if (equal (match-string 2) "fn:") nil (match-string 2))) |
150 | (match-string 4)))) | |
151 | ||
152 | (defun org-footnote-at-definition-p () | |
153 | "Is the cursor at a footnote definition. | |
154 | This matches only pure definitions like [1] or [fn:name] at the beginning | |
155 | of a line. It does not a references like [fn:name:definition], where the | |
156 | footnote text is included and defined locally. | |
576e334d | 157 | The return value will be nil if not at a footnote definition, and a list |
0bd48b37 CD |
158 | with start and label of the footnote if there is a definition at point." |
159 | (save-excursion | |
160 | (end-of-line 1) | |
161 | (let ((lim (save-excursion (re-search-backward "^\\*+ \\|^[ \t]*$" nil t)))) | |
162 | (when (re-search-backward org-footnote-definition-re lim t) | |
163 | (list (match-beginning 0) (match-string 2)))))) | |
164 | ||
165 | (defun org-footnote-goto-definition (label) | |
166 | "Find the definition of the footnote with label LABEL." | |
167 | (interactive "sLabel: ") | |
168 | (org-mark-ring-push) | |
169 | (setq label (org-footnote-normalize-label label)) | |
170 | (let ((re (format "^\\[%s\\]\\|.\\[%s:" label label)) | |
171 | pos) | |
172 | (save-excursion | |
173 | (setq pos (or (re-search-forward re nil t) | |
174 | (and (goto-char (point-min)) | |
175 | (re-search-forward re nil t)) | |
176 | (and (progn (widen) t) | |
177 | (goto-char (point-min)) | |
178 | (re-search-forward re nil t))))) | |
179 | (if (not pos) | |
180 | (error "Cannot find definition of footnote %s" label) | |
181 | (goto-char pos) | |
182 | (org-show-context 'link-search) | |
183 | (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")))) | |
184 | ||
185 | (defun org-footnote-goto-next-reference (label) | |
ed21c5c8 | 186 | "Find the next reference of the footnote with label LABEL." |
0bd48b37 CD |
187 | (interactive "sLabel: ") |
188 | (org-mark-ring-push) | |
189 | (setq label (org-footnote-normalize-label label)) | |
190 | (let ((re (format ".\\[%s[]:]" label)) | |
191 | (p0 (point)) pos) | |
192 | (save-excursion | |
193 | (setq pos (or (re-search-forward re nil t) | |
194 | (and (goto-char (point-min)) | |
195 | (re-search-forward re nil t)) | |
196 | (and (progn (widen) t) | |
197 | (goto-char p0) | |
198 | (re-search-forward re nil t)) | |
199 | (and (goto-char (point-min)) | |
200 | (re-search-forward re nil t))))) | |
201 | (if pos | |
202 | (progn | |
203 | (goto-char pos) | |
204 | (org-show-context 'link-search)) | |
205 | (error "Cannot find reference of footnote %s" label)))) | |
206 | ||
207 | (defun org-footnote-normalize-label (label) | |
208 | (if (numberp label) (setq label (number-to-string label))) | |
209 | (if (not (string-match "^[0-9]+$\\|^$\\|^fn:" label)) | |
210 | (setq label (concat "fn:" label))) | |
211 | label) | |
212 | ||
213 | (defun org-footnote-all-labels () | |
214 | "Return list with all defined foot labels used in the buffer." | |
215 | (let (rtn l) | |
216 | (save-excursion | |
217 | (save-restriction | |
218 | (widen) | |
219 | (goto-char (point-min)) | |
220 | (while (re-search-forward org-footnote-definition-re nil t) | |
221 | (setq l (org-match-string-no-properties 2)) | |
222 | (and l (add-to-list 'rtn l))) | |
223 | (goto-char (point-min)) | |
224 | (while (re-search-forward org-footnote-re nil t) | |
225 | (setq l (or (org-match-string-no-properties 1) | |
226 | (org-match-string-no-properties 2))) | |
227 | (and l (not (equal l "fn:")) (add-to-list 'rtn l))))) | |
228 | rtn)) | |
229 | ||
230 | (defun org-footnote-unique-label (&optional current) | |
231 | "Return a new unique footnote label. | |
232 | The returns the firsts fn:N labels that is currently not used." | |
233 | (unless current (setq current (org-footnote-all-labels))) | |
234 | (let ((fmt (if (eq org-footnote-auto-label 'plain) "%d" "fn:%d")) | |
235 | (cnt 1)) | |
236 | (while (member (format fmt cnt) current) | |
237 | (incf cnt)) | |
238 | (format fmt cnt))) | |
239 | ||
240 | (defvar org-footnote-label-history nil | |
241 | "History of footnote labels entered in current buffer.") | |
242 | (make-variable-buffer-local 'org-footnote-label-history) | |
243 | ||
244 | (defun org-footnote-new () | |
245 | "Insert a new footnote. | |
246 | This command prompts for a label. If this is a label referencing an | |
247 | existing label, only insert the label. If the footnote label is empty | |
248 | or new, let the user edit the definition of the footnote." | |
249 | (interactive) | |
250 | (let* ((labels (org-footnote-all-labels)) | |
251 | (propose (org-footnote-unique-label labels)) | |
252 | (label | |
253 | (if (member org-footnote-auto-label '(t plain)) | |
254 | propose | |
255 | (completing-read | |
256 | "Label (leave empty for anonymous): " | |
257 | (mapcar 'list labels) nil nil | |
258 | (if (eq org-footnote-auto-label 'confirm) propose nil) | |
259 | 'org-footnote-label-history)))) | |
260 | (setq label (org-footnote-normalize-label label)) | |
261 | (cond | |
262 | ((equal label "") | |
263 | (insert "[fn:: ]") | |
264 | (backward-char 1)) | |
265 | ((member label labels) | |
266 | (insert "[" label "]") | |
267 | (message "New reference to existing note")) | |
268 | (org-footnote-define-inline | |
269 | (insert "[" label ": ]") | |
c8d0cf5c CD |
270 | (backward-char 1) |
271 | (org-footnote-auto-adjust-maybe)) | |
0bd48b37 CD |
272 | (t |
273 | (insert "[" label "]") | |
c8d0cf5c CD |
274 | (org-footnote-create-definition label) |
275 | (org-footnote-auto-adjust-maybe))))) | |
0bd48b37 CD |
276 | |
277 | (defun org-footnote-create-definition (label) | |
278 | "Start the definition of a footnote with label LABEL." | |
279 | (interactive "sLabel: ") | |
280 | (setq label (org-footnote-normalize-label label)) | |
65c439fd | 281 | (let (re) |
0bd48b37 CD |
282 | (cond |
283 | ((org-mode-p) | |
284 | (if (not org-footnote-section) | |
285 | ;; No section, put footnote into the current outline node | |
286 | nil | |
287 | ;; Try to find or make the special node | |
288 | (setq re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$")) | |
289 | (unless (or (re-search-forward re nil t) | |
290 | (and (progn (widen) t) | |
291 | (re-search-forward re nil t))) | |
292 | (goto-char (point-max)) | |
293 | (insert "\n\n* " org-footnote-section "\n"))) | |
294 | ;; Now go to the end of this entry and insert there. | |
8d642074 CD |
295 | (org-footnote-goto-local-insertion-point) |
296 | (org-show-context 'link-search)) | |
0bd48b37 CD |
297 | (t |
298 | (setq re (concat "^" org-footnote-tag-for-non-org-mode-files "[ \t]*$")) | |
299 | (unless (re-search-forward re nil t) | |
300 | (goto-char (point-max)) | |
301 | (skip-chars-backward " \t\r\n") | |
302 | (insert "\n\n") | |
303 | (delete-region (point) (point-max)) | |
304 | (insert org-footnote-tag-for-non-org-mode-files "\n")) | |
305 | (goto-char (point-max)) | |
306 | (skip-chars-backward " \t\r\n"))) | |
307 | (insert "\n\n") | |
308 | (insert "[" label "] ") | |
309 | (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))) | |
310 | ||
311 | ;;;###autoload | |
312 | (defun org-footnote-action (&optional special) | |
313 | "Do the right thing for footnotes. | |
9c55bbd2 | 314 | When at a footnote reference, jump to the definition. When at a definition, |
8bfe682a | 315 | jump to the references. When neither at definition or reference, |
0bd48b37 CD |
316 | create a new footnote, interactively. |
317 | With prefix arg SPECIAL, offer additional commands in a menu." | |
318 | (interactive "P") | |
319 | (let (tmp c) | |
320 | (cond | |
321 | (special | |
c8d0cf5c | 322 | (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete") |
0bd48b37 CD |
323 | (setq c (read-char-exclusive)) |
324 | (cond | |
325 | ((equal c ?s) | |
326 | (org-footnote-normalize 'sort)) | |
c8d0cf5c CD |
327 | ((equal c ?r) |
328 | (org-footnote-renumber-fn:N)) | |
329 | ((equal c ?S) | |
330 | (org-footnote-renumber-fn:N) | |
331 | (org-footnote-normalize 'sort)) | |
0bd48b37 CD |
332 | ((equal c ?n) |
333 | (org-footnote-normalize)) | |
334 | ((equal c ?d) | |
335 | (org-footnote-delete)) | |
336 | (t (error "No such footnote command %c" c)))) | |
337 | ((setq tmp (org-footnote-at-reference-p)) | |
338 | (if (nth 1 tmp) | |
339 | (org-footnote-goto-definition (nth 1 tmp)) | |
340 | (goto-char (match-beginning 4)))) | |
341 | ((setq tmp (org-footnote-at-definition-p)) | |
342 | (org-footnote-goto-next-reference (nth 1 tmp))) | |
343 | (t (org-footnote-new))))) | |
344 | ||
345 | ;;;###autoload | |
346 | (defun org-footnote-normalize (&optional sort-only for-preprocessor) | |
347 | "Collect the footnotes in various formats and normalize them. | |
c8d0cf5c | 348 | This finds the different sorts of footnotes allowed in Org, and |
0bd48b37 CD |
349 | normalizes them to the usual [N] format that is understood by the |
350 | Org-mode exporters. | |
351 | When SORT-ONLY is set, only sort the footnote definitions into the | |
352 | referenced sequence." | |
353 | ;; This is based on Paul's function, but rewritten. | |
c8d0cf5c CD |
354 | (let* ((limit-level |
355 | (and (boundp 'org-inlinetask-min-level) | |
356 | org-inlinetask-min-level | |
357 | (1- org-inlinetask-min-level))) | |
358 | (nstars (and limit-level | |
359 | (if org-odd-levels-only | |
360 | (and limit-level (1- (* limit-level 2))) | |
361 | limit-level))) | |
362 | (outline-regexp | |
363 | (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ "))) | |
364 | (count 0) | |
365 | ref def idef ref-table beg beg1 marker a before ins-point) | |
0bd48b37 CD |
366 | (save-excursion |
367 | ;; Now find footnote references, and extract the definitions | |
368 | (goto-char (point-min)) | |
369 | (while (re-search-forward org-footnote-re nil t) | |
ed21c5c8 CD |
370 | (unless (or (org-in-commented-line) (org-in-verbatim-emphasis) |
371 | (org-inside-latex-macro-p)) | |
54a0dee5 CD |
372 | (org-if-unprotected |
373 | (setq def (match-string 4) | |
374 | idef def | |
375 | ref (or (match-string 1) (match-string 2)) | |
376 | before (char-to-string (char-after (match-beginning 0)))) | |
377 | (if (equal ref "fn:") (setq ref nil)) | |
378 | (if (and ref (setq a (assoc ref ref-table))) | |
379 | (progn | |
380 | (setq marker (nth 1 a)) | |
381 | (unless (nth 2 a) (setf (caddr a) def))) | |
382 | (setq marker (number-to-string (incf count)))) | |
383 | (save-match-data | |
384 | (if def | |
385 | (setq def (org-trim def)) | |
386 | (save-excursion | |
387 | (goto-char (point-min)) | |
388 | (if (not (re-search-forward (concat "^\\[" (regexp-quote ref) | |
389 | "\\]") nil t)) | |
390 | (setq def nil) | |
391 | (setq beg (match-beginning 0)) | |
392 | (setq beg1 (match-end 0)) | |
393 | (re-search-forward | |
394 | (org-re "^[ \t]*$\\|^\\*+ \\|^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]") | |
395 | nil 'move) | |
396 | (setq def (buffer-substring beg1 (or (match-beginning 0) | |
397 | (point-max)))) | |
398 | (goto-char beg) | |
399 | (skip-chars-backward " \t\n\t") | |
400 | (delete-region (1+ (point)) (match-beginning 0)))))) | |
401 | (unless sort-only | |
ed21c5c8 | 402 | (replace-match (concat before "[" marker "]") t t) |
54a0dee5 CD |
403 | (and idef |
404 | org-footnote-fill-after-inline-note-extraction | |
405 | (fill-paragraph))) | |
406 | (if (not a) (push (list ref marker def (if idef t nil)) | |
407 | ref-table))))) | |
ed21c5c8 | 408 | |
0bd48b37 CD |
409 | ;; First find and remove the footnote section |
410 | (goto-char (point-min)) | |
411 | (cond | |
412 | ((org-mode-p) | |
413 | (if (and org-footnote-section | |
414 | (re-search-forward | |
415 | (concat "^\\*[ \t]+" (regexp-quote org-footnote-section) | |
416 | "[ \t]*$") | |
417 | nil t)) | |
418 | (if (or for-preprocessor (not org-footnote-section)) | |
419 | (replace-match "") | |
420 | (org-back-to-heading t) | |
421 | (forward-line 1) | |
422 | (setq ins-point (point)) | |
423 | (delete-region (point) (org-end-of-subtree t))) | |
424 | (goto-char (point-max)) | |
425 | (unless for-preprocessor | |
426 | (when org-footnote-section | |
427 | (or (bolp) (insert "\n")) | |
428 | (insert "* " org-footnote-section "\n") | |
429 | (setq ins-point (point)))))) | |
430 | (t | |
c8d0cf5c | 431 | (if (re-search-forward |
0bd48b37 CD |
432 | (concat "^" |
433 | (regexp-quote org-footnote-tag-for-non-org-mode-files) | |
434 | "[ \t]*$") | |
435 | nil t) | |
436 | (replace-match "")) | |
437 | (goto-char (point-max)) | |
438 | (skip-chars-backward " \t\n\r") | |
439 | (delete-region (point) (point-max)) | |
440 | (insert "\n\n" org-footnote-tag-for-non-org-mode-files "\n") | |
441 | (setq ins-point (point)))) | |
c8d0cf5c | 442 | |
0bd48b37 CD |
443 | ;; Insert the footnotes again |
444 | (goto-char (or ins-point (point-max))) | |
445 | (setq ref-table (reverse ref-table)) | |
446 | (when sort-only | |
c8d0cf5c | 447 | ;; remove anonymous and inline footnotes from the list |
0bd48b37 CD |
448 | (setq ref-table |
449 | (delq nil (mapcar | |
450 | (lambda (x) (and (car x) | |
451 | (not (equal (car x) "fn:")) | |
c8d0cf5c | 452 | (not (nth 3 x)) |
0bd48b37 CD |
453 | x)) |
454 | ref-table)))) | |
455 | ;; Make sure each footnote has a description, or an error message. | |
456 | (setq ref-table | |
457 | (mapcar | |
458 | (lambda (x) | |
459 | (if (not (nth 2 x)) | |
460 | (setcar (cddr x) | |
461 | (format "FOOTNOTE DEFINITION NOT FOUND: %s" (car x))) | |
462 | (setcar (cddr x) (org-trim (nth 2 x)))) | |
463 | x) | |
464 | ref-table)) | |
465 | ||
466 | (if (or (not (org-mode-p)) ; not an Org file | |
467 | org-footnote-section ; we do not use a footnote section | |
468 | (not sort-only) ; this is normalization | |
469 | for-preprocessor) ; the is the preprocessor | |
470 | ;; Insert the footnotes together in one place | |
471 | (progn | |
472 | (setq def | |
473 | (mapconcat | |
474 | (lambda (x) | |
475 | (format "[%s] %s" (nth (if sort-only 0 1) x) | |
476 | (org-trim (nth 2 x)))) | |
477 | ref-table "\n\n")) | |
478 | (if ref-table (insert "\n" def "\n\n"))) | |
479 | ;; Insert each footnote near the first reference | |
480 | ;; Happens only in Org files with no special footnote section, | |
481 | ;; and only when doing sorting | |
482 | (mapc 'org-insert-footnote-reference-near-definition | |
483 | ref-table))))) | |
484 | ||
485 | (defun org-insert-footnote-reference-near-definition (entry) | |
486 | "Find first reference of footnote ENTRY and insert the definition there. | |
487 | ENTRY is (fn-label num-mark definition)." | |
488 | (when (car entry) | |
65c439fd CD |
489 | (goto-char (point-min)) |
490 | (when (re-search-forward (format ".\\[%s[]:]" (regexp-quote (car entry))) | |
491 | nil t) | |
492 | (org-footnote-goto-local-insertion-point) | |
493 | (insert (format "\n\n[%s] %s" (car entry) (nth 2 entry)))))) | |
0bd48b37 CD |
494 | |
495 | (defun org-footnote-goto-local-insertion-point () | |
496 | "Find insertion point for footnote, just before next outline heading." | |
c8d0cf5c | 497 | (org-with-limited-levels (outline-next-heading)) |
0bd48b37 CD |
498 | (or (bolp) (newline)) |
499 | (beginning-of-line 0) | |
500 | (while (and (not (bobp)) (= (char-after) ?#)) | |
501 | (beginning-of-line 0)) | |
c8d0cf5c | 502 | (if (looking-at "[ \t]*#\\+TBLFM:") (beginning-of-line 2)) |
0bd48b37 CD |
503 | (end-of-line 1) |
504 | (skip-chars-backward "\n\r\t ")) | |
505 | ||
506 | (defun org-footnote-delete (&optional label) | |
507 | "Delete the footnote at point. | |
508 | This will remove the definition (even multiple definitions if they exist) | |
509 | and all references of a footnote label." | |
510 | (catch 'done | |
511 | (let (x label l beg def-re (nref 0) (ndef 0)) | |
512 | (unless label | |
513 | (when (setq x (org-footnote-at-reference-p)) | |
514 | (setq label (nth 1 x)) | |
515 | (when (or (not label) (equal "fn:" label)) | |
516 | (delete-region (1+ (match-beginning 0)) (match-end 0)) | |
517 | (message "Anonymous footnote removed") | |
518 | (throw 'done t))) | |
519 | (when (and (not label) (setq x (org-footnote-at-definition-p))) | |
520 | (setq label (nth 1 x))) | |
521 | (unless label (error "Don't know which footnote to remove"))) | |
522 | (save-excursion | |
523 | (save-restriction | |
524 | (goto-char (point-min)) | |
525 | (while (re-search-forward org-footnote-re nil t) | |
526 | (setq l (or (match-string 1) (match-string 2))) | |
527 | (when (equal l label) | |
528 | (delete-region (1+ (match-beginning 0)) (match-end 0)) | |
529 | (incf nref))) | |
530 | (goto-char (point-min)) | |
531 | (setq def-re (concat "^\\[" (regexp-quote label) "\\]")) | |
532 | (while (re-search-forward def-re nil t) | |
533 | (setq beg (match-beginning 0)) | |
534 | (if (re-search-forward "^\\[\\|^[ \t]*$\\|^\\*+ " nil t) | |
535 | (goto-char (match-beginning 0)) | |
536 | (goto-char (point-max))) | |
537 | (delete-region beg (point)) | |
538 | (incf ndef)))) | |
c8d0cf5c | 539 | (org-footnote-auto-adjust-maybe) |
0bd48b37 CD |
540 | (message "%d definition(s) of and %d reference(s) of footnote %s removed" |
541 | ndef nref label)))) | |
542 | ||
c8d0cf5c CD |
543 | (defun org-footnote-renumber-fn:N () |
544 | "Renumber the simple footnotes like fn:17 into a sequence in the document." | |
545 | (interactive) | |
546 | (let (map i (n 0)) | |
547 | (save-excursion | |
548 | (save-restriction | |
549 | (widen) | |
550 | (goto-char (point-min)) | |
551 | (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t) | |
552 | (setq i (string-to-number (match-string 1))) | |
553 | (when (and (string-match "\\S-" (buffer-substring | |
554 | (point-at-bol) (match-beginning 0))) | |
555 | (not (assq i map))) | |
556 | (push (cons i (number-to-string (incf n))) map))) | |
557 | (goto-char (point-min)) | |
558 | (while (re-search-forward "\\(\\[fn:\\)\\([0-9]+\\)\\([]:]\\)" nil t) | |
559 | (replace-match (concat "\\1" (cdr (assq (string-to-number (match-string 2)) map)) "\\3"))))))) | |
560 | ||
561 | (defun org-footnote-auto-adjust-maybe () | |
562 | "Renumber and/or sort footnotes according to user settings." | |
563 | (when (memq org-footnote-auto-adjust '(t renumber)) | |
564 | (org-footnote-renumber-fn:N)) | |
565 | (when (memq org-footnote-auto-adjust '(t sort)) | |
566 | (let ((label (nth 1 (org-footnote-at-definition-p)))) | |
567 | (org-footnote-normalize 'sort) | |
568 | (when label | |
569 | (goto-char (point-min)) | |
570 | (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]") | |
571 | nil t) | |
572 | (progn (insert " ") | |
573 | (just-one-space))))))) | |
574 | ||
0bd48b37 CD |
575 | (provide 'org-footnote) |
576 | ||
577 | ;; arch-tag: 1b5954df-fb5d-4da5-8709-78d944dbfc37 | |
578 | ||
579 | ;;; org-footnote.el ends here |