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: ") | |
d3517077 BG |
489 | (let ((label (org-footnote-normalize-label label)) |
490 | electric-indent-mode) ;; Prevent wrong indentation | |
0bd48b37 | 491 | (cond |
e66ba1df | 492 | ;; In an Org file. |
8223b1d2 | 493 | ((derived-mode-p 'org-mode) |
e66ba1df BG |
494 | ;; If `org-footnote-section' is defined, find it, or create it |
495 | ;; at the end of the buffer. | |
3ab2c837 BG |
496 | (when org-footnote-section |
497 | (goto-char (point-min)) | |
498 | (let ((re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$"))) | |
499 | (unless (or (re-search-forward re nil t) | |
500 | (and (progn (widen) t) | |
501 | (re-search-forward re nil t))) | |
e66ba1df BG |
502 | (goto-char (point-max)) |
503 | (skip-chars-backward " \t\r\n") | |
504 | (unless (bolp) (newline)) | |
505 | ;; Insert new section. Separate it from the previous one | |
506 | ;; with a blank line, unless `org-blank-before-new-entry' | |
507 | ;; explicitly says no. | |
508 | (when (and (cdr (assq 'heading org-blank-before-new-entry)) | |
509 | (zerop (save-excursion (org-back-over-empty-lines)))) | |
510 | (insert "\n")) | |
511 | (insert "* " org-footnote-section "\n")))) | |
512 | ;; Move to the end of this entry (which may be | |
513 | ;; `org-footnote-section' or the current one). | |
8d642074 CD |
514 | (org-footnote-goto-local-insertion-point) |
515 | (org-show-context 'link-search)) | |
0bd48b37 | 516 | (t |
3ab2c837 | 517 | ;; In a non-Org file. Search for footnote tag, or create it if |
e66ba1df | 518 | ;; specified (at the end of buffer, or before signature if in |
3ab2c837 | 519 | ;; Message mode). Set point after any definition already there. |
e66ba1df BG |
520 | (let ((tag (and org-footnote-tag-for-non-org-mode-files |
521 | (concat "^" (regexp-quote | |
522 | org-footnote-tag-for-non-org-mode-files) | |
523 | "[ \t]*$"))) | |
524 | (max (if (and (derived-mode-p 'message-mode) | |
525 | (goto-char (point-max)) | |
526 | (re-search-backward | |
527 | message-signature-separator nil t)) | |
528 | (progn | |
529 | ;; Ensure one blank line separates last | |
530 | ;; footnote from signature. | |
531 | (beginning-of-line) | |
532 | (open-line 2) | |
533 | (point-marker)) | |
534 | (point-max-marker)))) | |
535 | (set-marker-insertion-type max t) | |
3ab2c837 | 536 | (goto-char max) |
e66ba1df BG |
537 | ;; Check if the footnote tag is defined but missing. In this |
538 | ;; case, insert it, before any footnote or one blank line | |
539 | ;; after any previous text. | |
540 | (when (and tag (not (re-search-backward tag nil t))) | |
afe98dfa | 541 | (skip-chars-backward " \t\r\n") |
e66ba1df BG |
542 | (while (re-search-backward org-footnote-definition-re nil t)) |
543 | (unless (bolp) (newline 2)) | |
544 | (insert org-footnote-tag-for-non-org-mode-files "\n\n")) | |
545 | ;; Remove superfluous white space and clear marker. | |
546 | (goto-char max) | |
547 | (skip-chars-backward " \t\r\n") | |
548 | (delete-region (point) max) | |
549 | (unless (bolp) (newline)) | |
3ab2c837 | 550 | (set-marker max nil)))) |
e66ba1df | 551 | ;; Insert footnote label. |
153ae947 BG |
552 | (when (zerop (org-back-over-empty-lines)) (newline)) |
553 | (insert "[" label "] \n") | |
554 | (backward-char) | |
e66ba1df BG |
555 | ;; Only notify user about next possible action when in an Org |
556 | ;; buffer, as the bindings may have different meanings otherwise. | |
8223b1d2 | 557 | (when (derived-mode-p 'org-mode) |
e66ba1df BG |
558 | (message |
559 | "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")))) | |
0bd48b37 CD |
560 | |
561 | ;;;###autoload | |
562 | (defun org-footnote-action (&optional special) | |
563 | "Do the right thing for footnotes. | |
3ab2c837 BG |
564 | |
565 | When at a footnote reference, jump to the definition. | |
566 | ||
567 | When at a definition, jump to the references if they exist, offer | |
568 | to create them otherwise. | |
569 | ||
570 | When neither at definition or reference, create a new footnote, | |
571 | interactively. | |
572 | ||
0bd48b37 CD |
573 | With prefix arg SPECIAL, offer additional commands in a menu." |
574 | (interactive "P") | |
575 | (let (tmp c) | |
576 | (cond | |
577 | (special | |
c8d0cf5c | 578 | (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete") |
0bd48b37 CD |
579 | (setq c (read-char-exclusive)) |
580 | (cond | |
3ab2c837 BG |
581 | ((eq c ?s) (org-footnote-normalize 'sort)) |
582 | ((eq c ?r) (org-footnote-renumber-fn:N)) | |
583 | ((eq c ?S) | |
c8d0cf5c CD |
584 | (org-footnote-renumber-fn:N) |
585 | (org-footnote-normalize 'sort)) | |
3ab2c837 BG |
586 | ((eq c ?n) (org-footnote-normalize)) |
587 | ((eq c ?d) (org-footnote-delete)) | |
0bd48b37 CD |
588 | (t (error "No such footnote command %c" c)))) |
589 | ((setq tmp (org-footnote-at-reference-p)) | |
3ab2c837 BG |
590 | (cond |
591 | ;; Anonymous footnote: move point at the beginning of its | |
592 | ;; definition. | |
593 | ((not (car tmp)) | |
594 | (goto-char (nth 1 tmp)) | |
595 | (forward-char 5)) | |
596 | ;; A definition exists: move to it. | |
597 | ((ignore-errors (org-footnote-goto-definition (car tmp)))) | |
598 | ;; No definition exists: offer to create it. | |
599 | ((yes-or-no-p (format "No definition for %s. Create one? " (car tmp))) | |
600 | (org-footnote-create-definition (car tmp))))) | |
0bd48b37 | 601 | ((setq tmp (org-footnote-at-definition-p)) |
3ab2c837 | 602 | (org-footnote-goto-previous-reference (car tmp))) |
0bd48b37 CD |
603 | (t (org-footnote-new))))) |
604 | ||
3ab2c837 BG |
605 | (defvar org-footnote-insert-pos-for-preprocessor 'point-max |
606 | "See `org-footnote-normalize'.") | |
607 | ||
6a24cbb1 CY |
608 | (defvar org-export-footnotes-seen) ; silence byte-compiler |
609 | (defvar org-export-footnotes-data) ; silence byte-compiler | |
3ab2c837 | 610 | |
0bd48b37 | 611 | ;;;###autoload |
3ab2c837 | 612 | (defun org-footnote-normalize (&optional sort-only export-props) |
0bd48b37 | 613 | "Collect the footnotes in various formats and normalize them. |
3ab2c837 | 614 | |
c8d0cf5c | 615 | This finds the different sorts of footnotes allowed in Org, and |
0bd48b37 CD |
616 | normalizes them to the usual [N] format that is understood by the |
617 | Org-mode exporters. | |
3ab2c837 | 618 | |
0bd48b37 | 619 | When SORT-ONLY is set, only sort the footnote definitions into the |
3ab2c837 BG |
620 | referenced sequence. |
621 | ||
622 | If Org is amidst an export process, EXPORT-PROPS will hold the | |
623 | export properties of the buffer. | |
624 | ||
625 | When EXPORT-PROPS is non-nil, the default action is to insert | |
e66ba1df BG |
626 | normalized footnotes towards the end of the pre-processing |
627 | buffer. Some exporters (docbook, odt...) expect footnote | |
628 | definitions to be available before any references to them. Such | |
629 | exporters can let bind `org-footnote-insert-pos-for-preprocessor' | |
630 | to symbol `point-min' to achieve the desired behaviour. | |
3ab2c837 BG |
631 | |
632 | Additional note on `org-footnote-insert-pos-for-preprocessor': | |
633 | 1. This variable has not effect when FOR-PREPROCESSOR is nil. | |
634 | 2. This variable (potentially) obviates the need for extra scan | |
635 | of pre-processor buffer as witnessed in | |
636 | `org-export-docbook-get-footnotes'." | |
0bd48b37 | 637 | ;; This is based on Paul's function, but rewritten. |
3ab2c837 BG |
638 | ;; |
639 | ;; Re-create `org-with-limited-levels', but not limited to Org | |
640 | ;; buffers. | |
c8d0cf5c CD |
641 | (let* ((limit-level |
642 | (and (boundp 'org-inlinetask-min-level) | |
643 | org-inlinetask-min-level | |
644 | (1- org-inlinetask-min-level))) | |
645 | (nstars (and limit-level | |
646 | (if org-odd-levels-only | |
647 | (and limit-level (1- (* limit-level 2))) | |
648 | limit-level))) | |
3ab2c837 | 649 | (org-outline-regexp |
c8d0cf5c | 650 | (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ "))) |
3ab2c837 BG |
651 | ;; Determine the highest marker used so far. |
652 | (ref-table (when export-props org-export-footnotes-seen)) | |
653 | (count (if (and export-props ref-table) | |
654 | (apply 'max (mapcar (lambda (e) (nth 1 e)) ref-table)) | |
655 | 0)) | |
656 | ins-point ref) | |
657 | (save-excursion | |
658 | ;; 1. Find every footnote reference, extract the definition, and | |
659 | ;; collect that data in REF-TABLE. If SORT-ONLY is nil, also | |
660 | ;; normalize references. | |
0bd48b37 | 661 | (goto-char (point-min)) |
3ab2c837 BG |
662 | (while (setq ref (org-footnote-get-next-reference)) |
663 | (let* ((lbl (car ref)) | |
153ae947 | 664 | (pos (nth 1 ref)) |
3ab2c837 BG |
665 | ;; When footnote isn't anonymous, check if it's label |
666 | ;; (REF) is already stored in REF-TABLE. In that case, | |
667 | ;; extract number used to identify it (MARKER). If | |
668 | ;; footnote is unknown, increment the global counter | |
669 | ;; (COUNT) to create an unused identifier. | |
670 | (a (and lbl (assoc lbl ref-table))) | |
671 | (marker (or (nth 1 a) (incf count))) | |
672 | ;; Is the reference inline or pointing to an inline | |
673 | ;; footnote? | |
674 | (inlinep (or (stringp (nth 3 ref)) (nth 3 a)))) | |
675 | ;; Replace footnote reference with [MARKER]. Maybe fill | |
676 | ;; paragraph once done. If SORT-ONLY is non-nil, only move | |
677 | ;; to the end of reference found to avoid matching it twice. | |
678 | ;; If EXPORT-PROPS isn't nil, also add `org-footnote' | |
679 | ;; property to it, so it can be easily recognized by | |
680 | ;; exporters. | |
153ae947 | 681 | (if sort-only (goto-char (nth 2 ref)) |
3ab2c837 BG |
682 | (delete-region (nth 1 ref) (nth 2 ref)) |
683 | (goto-char (nth 1 ref)) | |
684 | (let ((new-ref (format "[%d]" marker))) | |
685 | (when export-props (org-add-props new-ref '(org-footnote t))) | |
686 | (insert new-ref)) | |
687 | (and inlinep | |
688 | org-footnote-fill-after-inline-note-extraction | |
689 | (org-fill-paragraph))) | |
e66ba1df | 690 | ;; Add label (REF), identifier (MARKER), definition (DEF) |
153ae947 BG |
691 | ;; type (INLINEP) and position (POS) to REF-TABLE if data |
692 | ;; was unknown. | |
3ab2c837 BG |
693 | (unless a |
694 | (let ((def (or (nth 3 ref) ; inline | |
695 | (and export-props | |
696 | (cdr (assoc lbl org-export-footnotes-data))) | |
697 | (nth 3 (org-footnote-get-definition lbl))))) | |
698 | (push (list lbl marker | |
699 | ;; When exporting, each definition goes | |
700 | ;; through `org-export-preprocess-string' so | |
701 | ;; it is ready to insert in the | |
702 | ;; backend-specific buffer. | |
e66ba1df | 703 | (if (and export-props def) |
3ab2c837 BG |
704 | (let ((parameters |
705 | (org-combine-plists | |
706 | export-props | |
707 | '(:todo-keywords t :tags t :priority t)))) | |
801a68c8 | 708 | (apply #'org-export-preprocess-string def parameters)) |
3ab2c837 | 709 | def) |
153ae947 BG |
710 | ;; Reference beginning position is a marker |
711 | ;; to preserve it during further buffer | |
712 | ;; modifications. | |
713 | inlinep (copy-marker pos)) ref-table))))) | |
3ab2c837 BG |
714 | ;; 2. Find and remove the footnote section, if any. Also |
715 | ;; determine where footnotes shall be inserted (INS-POINT). | |
0bd48b37 | 716 | (cond |
8223b1d2 | 717 | ((and org-footnote-section (derived-mode-p 'org-mode)) |
153ae947 BG |
718 | (goto-char (point-min)) |
719 | (if (re-search-forward | |
2f885dca BG |
720 | (concat "^\\*[ \t]+" (regexp-quote org-footnote-section) |
721 | "[ \t]*$") nil t) | |
153ae947 BG |
722 | (delete-region (match-beginning 0) (org-end-of-subtree t t))) |
723 | ;; A new footnote section is inserted by default at the end of | |
724 | ;; the buffer. | |
e66ba1df | 725 | (goto-char (point-max)) |
153ae947 BG |
726 | (skip-chars-backward " \r\t\n") |
727 | (forward-line) | |
e66ba1df | 728 | (unless (bolp) (newline))) |
153ae947 BG |
729 | ;; No footnote section set: Footnotes will be added at the end |
730 | ;; of the section containing their first reference. | |
2f885dca BG |
731 | ;; Nevertheless, in an export situation, set insertion point to |
732 | ;; `point-max' by default. | |
8223b1d2 | 733 | ((derived-mode-p 'org-mode) |
2f885dca BG |
734 | (when export-props |
735 | (goto-char (point-max)) | |
736 | (skip-chars-backward " \r\t\n") | |
737 | (forward-line) | |
738 | (delete-region (point) (point-max)))) | |
0bd48b37 | 739 | (t |
e66ba1df BG |
740 | ;; Remove any left-over tag in the buffer, if one is set up. |
741 | (when org-footnote-tag-for-non-org-mode-files | |
742 | (let ((tag (concat "^" (regexp-quote | |
743 | org-footnote-tag-for-non-org-mode-files) | |
744 | "[ \t]*$"))) | |
153ae947 | 745 | (goto-char (point-min)) |
e66ba1df BG |
746 | (while (re-search-forward tag nil t) |
747 | (replace-match "") | |
748 | (delete-region (point) (progn (forward-line) (point)))))) | |
749 | ;; In Message mode, ensure footnotes are inserted before the | |
3ab2c837 | 750 | ;; signature. |
e66ba1df BG |
751 | (if (and (derived-mode-p 'message-mode) |
752 | (goto-char (point-max)) | |
753 | (re-search-backward message-signature-separator nil t)) | |
754 | (beginning-of-line) | |
755 | (goto-char (point-max))))) | |
756 | ;; During export, `org-footnote-insert-pos-for-preprocessor' has | |
757 | ;; precedence over previously found position. | |
758 | (setq ins-point | |
759 | (copy-marker | |
760 | (if (and export-props | |
761 | (eq org-footnote-insert-pos-for-preprocessor 'point-min)) | |
762 | (point-min) | |
763 | (point)))) | |
3ab2c837 | 764 | ;; 3. Clean-up REF-TABLE. |
0bd48b37 | 765 | (setq ref-table |
3ab2c837 BG |
766 | (delq nil |
767 | (mapcar | |
0bd48b37 | 768 | (lambda (x) |
3ab2c837 BG |
769 | (cond |
770 | ;; When only sorting, ignore inline footnotes. | |
153ae947 BG |
771 | ;; Also clear position marker. |
772 | ((and sort-only (nth 3 x)) | |
773 | (set-marker (nth 4 x) nil) nil) | |
3ab2c837 BG |
774 | ;; No definition available: provide one. |
775 | ((not (nth 2 x)) | |
153ae947 BG |
776 | (append |
777 | (list (car x) (nth 1 x) | |
778 | (format "DEFINITION NOT FOUND: %s" (car x))) | |
779 | (nthcdr 3 x))) | |
3ab2c837 BG |
780 | (t x))) |
781 | ref-table))) | |
782 | (setq ref-table (nreverse ref-table)) | |
e66ba1df | 783 | ;; 4. Remove left-over definitions in the buffer. |
153ae947 BG |
784 | (mapc (lambda (x) |
785 | (unless (nth 3 x) (org-footnote-delete-definitions (car x)))) | |
e66ba1df BG |
786 | ref-table) |
787 | ;; 5. Insert the footnotes again in the buffer, at the | |
3ab2c837 | 788 | ;; appropriate spot. |
e66ba1df | 789 | (goto-char ins-point) |
3ab2c837 BG |
790 | (cond |
791 | ;; No footnote: exit. | |
792 | ((not ref-table)) | |
793 | ;; Cases when footnotes should be inserted in one place. | |
8223b1d2 | 794 | ((or (not (derived-mode-p 'org-mode)) |
3ab2c837 | 795 | org-footnote-section |
153ae947 | 796 | export-props) |
e66ba1df BG |
797 | ;; Insert again the section title, if any. Ensure that title, |
798 | ;; or the subsequent footnotes, will be separated by a blank | |
799 | ;; lines from the rest of the document. In an Org buffer, | |
800 | ;; separate section with a blank line, unless explicitly | |
801 | ;; stated in `org-blank-before-new-entry'. | |
3ab2c837 | 802 | (cond |
8223b1d2 | 803 | ((not (derived-mode-p 'org-mode)) |
e66ba1df BG |
804 | (skip-chars-backward " \t\n\r") |
805 | (delete-region (point) ins-point) | |
806 | (unless (bolp) (newline)) | |
e66ba1df BG |
807 | (when org-footnote-tag-for-non-org-mode-files |
808 | (insert "\n" org-footnote-tag-for-non-org-mode-files "\n"))) | |
3ab2c837 | 809 | ((and org-footnote-section (not export-props)) |
e66ba1df BG |
810 | (when (and (cdr (assq 'heading org-blank-before-new-entry)) |
811 | (zerop (save-excursion (org-back-over-empty-lines)))) | |
812 | (insert "\n")) | |
3ab2c837 | 813 | (insert "* " org-footnote-section "\n"))) |
e66ba1df BG |
814 | (set-marker ins-point nil) |
815 | ;; Insert the footnotes, separated by a blank line. | |
153ae947 BG |
816 | (insert |
817 | (mapconcat | |
818 | (lambda (x) | |
819 | ;; Clean markers. | |
820 | (set-marker (nth 4 x) nil) | |
821 | (format "\n[%s] %s" (nth (if sort-only 0 1) x) (nth 2 x))) | |
822 | ref-table "\n")) | |
823 | (unless (eobp) (insert "\n\n")) | |
3ab2c837 BG |
824 | ;; When exporting, add newly inserted markers along with their |
825 | ;; associated definition to `org-export-footnotes-seen'. | |
153ae947 BG |
826 | (when export-props (setq org-export-footnotes-seen ref-table))) |
827 | ;; Each footnote definition has to be inserted at the end of | |
828 | ;; the section where its first reference belongs. | |
829 | (t | |
830 | (mapc | |
831 | (lambda (x) | |
832 | (let ((pos (nth 4 x))) | |
833 | (goto-char pos) | |
834 | ;; Clean marker. | |
835 | (set-marker pos nil)) | |
836 | (org-footnote-goto-local-insertion-point) | |
837 | (insert (format "\n[%s] %s\n" | |
838 | (if sort-only (car x) (nth 1 x)) | |
839 | (nth 2 x)))) | |
840 | ref-table)))))) | |
0bd48b37 CD |
841 | |
842 | (defun org-footnote-goto-local-insertion-point () | |
843 | "Find insertion point for footnote, just before next outline heading." | |
c8d0cf5c | 844 | (org-with-limited-levels (outline-next-heading)) |
0bd48b37 CD |
845 | (or (bolp) (newline)) |
846 | (beginning-of-line 0) | |
847 | (while (and (not (bobp)) (= (char-after) ?#)) | |
848 | (beginning-of-line 0)) | |
8223b1d2 | 849 | (if (let ((case-fold-search t)) (looking-at "[ \t]*#\\+tblfm:")) (beginning-of-line 2)) |
0bd48b37 | 850 | (end-of-line 1) |
afe98dfa CD |
851 | (skip-chars-backward "\n\r\t ") |
852 | (forward-line)) | |
0bd48b37 | 853 | |
3ab2c837 BG |
854 | (defun org-footnote-delete-references (label) |
855 | "Delete every reference to footnote LABEL. | |
856 | Return the number of footnotes removed." | |
857 | (save-excursion | |
858 | (goto-char (point-min)) | |
859 | (let (ref (nref 0)) | |
860 | (while (setq ref (org-footnote-get-next-reference label)) | |
861 | (goto-char (nth 1 ref)) | |
862 | (delete-region (nth 1 ref) (nth 2 ref)) | |
863 | (incf nref)) | |
864 | nref))) | |
865 | ||
866 | (defun org-footnote-delete-definitions (label) | |
867 | "Delete every definition of the footnote LABEL. | |
868 | Return the number of footnotes removed." | |
869 | (save-excursion | |
870 | (goto-char (point-min)) | |
871 | (let ((def-re (concat "^\\[" (regexp-quote label) "\\]")) | |
872 | (ndef 0)) | |
873 | (while (re-search-forward def-re nil t) | |
874 | (let ((full-def (org-footnote-at-definition-p))) | |
153ae947 | 875 | (when full-def |
8223b1d2 BG |
876 | ;; Remove the footnote, and all blank lines before it. |
877 | (goto-char (nth 1 full-def)) | |
878 | (skip-chars-backward " \r\t\n") | |
879 | (unless (bolp) (forward-line)) | |
880 | (delete-region (point) (nth 2 full-def)) | |
153ae947 | 881 | (incf ndef)))) |
3ab2c837 BG |
882 | ndef))) |
883 | ||
0bd48b37 CD |
884 | (defun org-footnote-delete (&optional label) |
885 | "Delete the footnote at point. | |
886 | This will remove the definition (even multiple definitions if they exist) | |
3ab2c837 BG |
887 | and all references of a footnote label. |
888 | ||
889 | If LABEL is non-nil, delete that footnote instead." | |
0bd48b37 | 890 | (catch 'done |
3ab2c837 BG |
891 | (let* ((nref 0) (ndef 0) x |
892 | ;; 1. Determine LABEL of footnote at point. | |
893 | (label (cond | |
894 | ;; LABEL is provided as argument. | |
895 | (label) | |
8223b1d2 | 896 | ;; Footnote reference at point. If the footnote is |
3ab2c837 BG |
897 | ;; anonymous, delete it and exit instead. |
898 | ((setq x (org-footnote-at-reference-p)) | |
899 | (or (car x) | |
900 | (progn | |
901 | (delete-region (nth 1 x) (nth 2 x)) | |
902 | (message "Anonymous footnote removed") | |
903 | (throw 'done t)))) | |
904 | ;; Footnote definition at point. | |
905 | ((setq x (org-footnote-at-definition-p)) | |
906 | (car x)) | |
907 | (t (error "Don't know which footnote to remove"))))) | |
908 | ;; 2. Now that LABEL is non-nil, find every reference and every | |
909 | ;; definition, and delete them. | |
910 | (setq nref (org-footnote-delete-references label) | |
911 | ndef (org-footnote-delete-definitions label)) | |
912 | ;; 3. Verify consistency of footnotes and notify user. | |
c8d0cf5c | 913 | (org-footnote-auto-adjust-maybe) |
0bd48b37 CD |
914 | (message "%d definition(s) of and %d reference(s) of footnote %s removed" |
915 | ndef nref label)))) | |
916 | ||
c8d0cf5c CD |
917 | (defun org-footnote-renumber-fn:N () |
918 | "Renumber the simple footnotes like fn:17 into a sequence in the document." | |
919 | (interactive) | |
e66ba1df BG |
920 | (let (map (n 0)) |
921 | (org-with-wide-buffer | |
922 | (goto-char (point-min)) | |
923 | (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t) | |
924 | (save-excursion | |
925 | (goto-char (match-beginning 0)) | |
926 | ;; Ensure match is a footnote reference or definition. | |
927 | (when (save-match-data (if (bolp) | |
928 | (org-footnote-at-definition-p) | |
929 | (org-footnote-at-reference-p))) | |
930 | (let ((new-val (or (cdr (assoc (match-string 1) map)) | |
931 | (number-to-string (incf n))))) | |
932 | (unless (assoc (match-string 1) map) | |
933 | (push (cons (match-string 1) new-val) map)) | |
934 | (replace-match new-val nil nil nil 1)))))))) | |
c8d0cf5c CD |
935 | |
936 | (defun org-footnote-auto-adjust-maybe () | |
937 | "Renumber and/or sort footnotes according to user settings." | |
938 | (when (memq org-footnote-auto-adjust '(t renumber)) | |
939 | (org-footnote-renumber-fn:N)) | |
940 | (when (memq org-footnote-auto-adjust '(t sort)) | |
3ab2c837 | 941 | (let ((label (car (org-footnote-at-definition-p)))) |
c8d0cf5c CD |
942 | (org-footnote-normalize 'sort) |
943 | (when label | |
944 | (goto-char (point-min)) | |
945 | (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]") | |
946 | nil t) | |
947 | (progn (insert " ") | |
948 | (just-one-space))))))) | |
949 | ||
0bd48b37 CD |
950 | (provide 'org-footnote) |
951 | ||
bdebdb64 BG |
952 | ;; Local variables: |
953 | ;; generated-autoload-file: "org-loaddefs.el" | |
954 | ;; End: | |
955 | ||
0bd48b37 | 956 | ;;; org-footnote.el ends here |