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