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