summaryrefslogtreecommitdiff
path: root/lisp/org-footnote.el
diff options
context:
space:
mode:
Diffstat (limited to 'lisp/org-footnote.el')
-rw-r--r--lisp/org-footnote.el951
1 files changed, 951 insertions, 0 deletions
diff --git a/lisp/org-footnote.el b/lisp/org-footnote.el
new file mode 100644
index 0000000..3aaa44b
--- /dev/null
+++ b/lisp/org-footnote.el
@@ -0,0 +1,951 @@
+;;; org-footnote.el --- Footnote support in Org and elsewhere
+;;
+;; Copyright (C) 2009-2012 Free Software Foundation, Inc.
+;;
+;; Author: Carsten Dominik <carsten at orgmode dot org>
+;; Keywords: outlines, hypermedia, calendar, wp
+;; Homepage: http://orgmode.org
+;;
+;; This file is part of GNU Emacs.
+;;
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;;; Commentary:
+
+;; This file contains the code dealing with footnotes in Org-mode.
+;; The code can also be used in arbitrary text modes to provide
+;; footnotes. Compared to Steven L Baur's footnote.el it provides
+;; better support for resuming editing. It is less configurable than
+;; Steve's code, though.
+
+;;; Code:
+
+(eval-when-compile
+ (require 'cl))
+(require 'org-macs)
+(require 'org-compat)
+
+(declare-function message-point-in-header-p "message" ())
+(declare-function org-back-over-empty-lines "org" ())
+(declare-function org-back-to-heading "org" (&optional invisible-ok))
+(declare-function org-combine-plists "org" (&rest plists))
+(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
+(declare-function org-export-preprocess-string "org-exp"
+ (string &rest parameters))
+(declare-function org-fill-paragraph "org" (&optional justify))
+(declare-function org-icompleting-read "org" (&rest args))
+(declare-function org-id-uuid "org-id" ())
+(declare-function org-in-block-p "org" (names))
+(declare-function org-in-commented-line "org" ())
+(declare-function org-in-indented-comment-line "org" ())
+(declare-function org-in-regexp "org" (re &optional nlines visually))
+(declare-function org-in-verbatim-emphasis "org" ())
+(declare-function org-inside-LaTeX-fragment-p "org" ())
+(declare-function org-inside-latex-macro-p "org" ())
+(declare-function org-mark-ring-push "org" (&optional pos buffer))
+(declare-function org-show-context "org" (&optional key))
+(declare-function org-trim "org" (s))
+(declare-function org-skip-whitespace "org" ())
+(declare-function outline-next-heading "outline")
+(declare-function org-skip-whitespace "org" ())
+
+(defvar org-outline-regexp-bol) ; defined in org.el
+(defvar org-odd-levels-only) ; defined in org.el
+(defvar org-bracket-link-regexp) ; defined in org.el
+(defvar message-cite-prefix-regexp) ; defined in message.el
+(defvar message-signature-separator) ; defined in message.el
+
+(defconst org-footnote-re
+ ;; Only [1]-like footnotes are closed in this regexp, as footnotes
+ ;; from other types might contain square brackets (i.e. links) in
+ ;; their definition.
+ ;;
+ ;; `org-re' is used for regexp compatibility with XEmacs.
+ (concat "\\[\\(?:"
+ ;; Match inline footnotes.
+ (org-re "fn:\\([-_[:word:]]+\\)?:\\|")
+ ;; Match other footnotes.
+ "\\(?:\\([0-9]+\\)\\]\\)\\|"
+ (org-re "\\(fn:[-_[:word:]]+\\)")
+ "\\)")
+ "Regular expression for matching footnotes.")
+
+(defconst org-footnote-definition-re
+ (org-re "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]")
+ "Regular expression matching the definition of a footnote.")
+
+(defconst org-footnote-forbidden-blocks
+ '("ascii" "beamer" "comment" "docbook" "example" "html" "latex" "odt" "src")
+ "Names of blocks where footnotes are not allowed.")
+
+(defgroup org-footnote nil
+ "Footnotes in Org-mode."
+ :tag "Org Footnote"
+ :group 'org)
+
+(defcustom org-footnote-section "Footnotes"
+ "Outline heading containing footnote definitions before export.
+This can be nil, to place footnotes locally at the end of the current
+outline node. If can also be the name of a special outline heading
+under which footnotes should be put.
+This variable defines the place where Org puts the definition
+automatically, i.e. when creating the footnote, and when sorting the notes.
+However, by hand you may place definitions *anywhere*.
+If this is a string, during export, all subtrees starting with this
+heading will be removed after extracting footnote definitions."
+ :group 'org-footnote
+ :type '(choice
+ (string :tag "Collect footnotes under heading")
+ (const :tag "Define footnotes locally" nil)))
+
+(defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:"
+ "Tag marking the beginning of footnote section.
+The Org footnote engine can be used in arbitrary text files as well
+as in Org-mode. Outside Org mode, new footnotes are always placed at
+the end of the file. When you normalize the notes, any line containing
+only this tag will be removed, a new one will be inserted at the end
+of the file, followed by the collected and normalized footnotes.
+
+If you don't want any tag in such buffers, set this variable to nil."
+ :group 'org-footnote
+ :type '(choice
+ (string :tag "Collect footnotes under tag")
+ (const :tag "Don't use a tag" nil)))
+
+(defcustom org-footnote-define-inline nil
+ "Non-nil means define footnotes inline, at reference location.
+When nil, footnotes will be defined in a special section near
+the end of the document. When t, the [fn:label:definition] notation
+will be used to define the footnote at the reference position."
+ :group 'org-footnote
+ :type 'boolean)
+
+(defcustom org-footnote-auto-label t
+ "Non-nil means define automatically new labels for footnotes.
+Possible values are:
+
+nil prompt the user for each label
+t create unique labels of the form [fn:1], [fn:2], ...
+confirm like t, but let the user edit the created value. In particular,
+ the label can be removed from the minibuffer, to create
+ an anonymous footnote.
+random Automatically generate a unique, random label.
+plain Automatically create plain number labels like [1]"
+ :group 'org-footnote
+ :type '(choice
+ (const :tag "Prompt for label" nil)
+ (const :tag "Create automatic [fn:N]" t)
+ (const :tag "Offer automatic [fn:N] for editing" confirm)
+ (const :tag "Create a random label" random)
+ (const :tag "Create automatic [N]" plain)))
+
+(defcustom org-footnote-auto-adjust nil
+ "Non-nil means automatically adjust footnotes after insert/delete.
+When this is t, after each insertion or deletion of a footnote,
+simple fn:N footnotes will be renumbered, and all footnotes will be sorted.
+If you want to have just sorting or just renumbering, set this variable
+to `sort' or `renumber'.
+
+The main values of this variable can be set with in-buffer options:
+
+#+STARTUP: fnadjust
+#+STARTUP: nofnadjust"
+ :group 'org-footnote
+ :type '(choice
+ (const :tag "Renumber" renumber)
+ (const :tag "Sort" sort)
+ (const :tag "Renumber and Sort" t)))
+
+(defcustom org-footnote-fill-after-inline-note-extraction nil
+ "Non-nil means fill paragraphs after extracting footnotes.
+When extracting inline footnotes, the lengths of lines can change a lot.
+When this option is set, paragraphs from which an inline footnote has been
+extracted will be filled again."
+ :group 'org-footnote
+ :type 'boolean)
+
+(defun org-footnote-in-valid-context-p ()
+ "Is point in a context where footnotes are allowed?"
+ (save-match-data
+ (not (or (org-in-commented-line)
+ (org-in-indented-comment-line)
+ (org-inside-LaTeX-fragment-p)
+ ;; Avoid protected environments (LaTeX export)
+ (get-text-property (point) 'org-protected)
+ ;; Avoid literal example.
+ (org-in-verbatim-emphasis)
+ (save-excursion
+ (beginning-of-line)
+ (looking-at "[ \t]*:[ \t]+"))
+ ;; Avoid cited text and headers in message-mode.
+ (and (derived-mode-p 'message-mode)
+ (or (save-excursion
+ (beginning-of-line)
+ (looking-at message-cite-prefix-regexp))
+ (message-point-in-header-p)))
+ ;; Avoid forbidden blocks.
+ (org-in-block-p org-footnote-forbidden-blocks)))))
+
+(defun org-footnote-at-reference-p ()
+ "Is the cursor at a footnote reference?
+
+If so, return a list containing its label, beginning and ending
+positions, and the definition, when inlined."
+ (when (and (org-footnote-in-valid-context-p)
+ (or (looking-at org-footnote-re)
+ (org-in-regexp org-footnote-re)
+ (save-excursion (re-search-backward org-footnote-re nil t)))
+ (/= (match-beginning 0) (point-at-bol)))
+ (let* ((beg (match-beginning 0))
+ (label (or (org-match-string-no-properties 2)
+ (org-match-string-no-properties 3)
+ ;; Anonymous footnotes don't have labels
+ (and (match-string 1)
+ (concat "fn:" (org-match-string-no-properties 1)))))
+ ;; Inline footnotes don't end at (match-end 0) as
+ ;; `org-footnote-re' stops just after the second colon.
+ ;; Find the real ending with `scan-sexps', so Org doesn't
+ ;; get fooled by unrelated closing square brackets.
+ (end (ignore-errors (scan-sexps beg 1))))
+ ;; Point is really at a reference if it's located before true
+ ;; ending of the footnote.
+ (when (and end (< (point) end)
+ ;; Verify match isn't a part of a link.
+ (not (save-excursion
+ (goto-char beg)
+ (let ((linkp
+ (save-match-data
+ (org-in-regexp org-bracket-link-regexp))))
+ (and linkp (< (point) (cdr linkp))))))
+ ;; Verify point doesn't belong to a LaTeX macro.
+ ;; Beware though, when two footnotes are side by
+ ;; side, once the first one is changed into LaTeX,
+ ;; the second one might then be considered as an
+ ;; optional argument of the command. Thus, check
+ ;; the `org-protected' property of that command.
+ (or (not (org-inside-latex-macro-p))
+ (get-text-property (1- beg) 'org-protected)))
+ (list label beg end
+ ;; Definition: ensure this is an inline footnote first.
+ (and (or (not label) (match-string 1))
+ (org-trim (buffer-substring-no-properties
+ (match-end 0) (1- end)))))))))
+
+(defun org-footnote-at-definition-p ()
+ "Is point within a footnote definition?
+
+This matches only pure definitions like [1] or [fn:name] at the
+beginning of a line. It does not match references like
+\[fn:name:definition], where the footnote text is included and
+defined locally.
+
+The return value will be nil if not at a footnote definition, and
+a list with label, start, end and definition of the footnote
+otherwise."
+ (when (save-excursion (beginning-of-line) (org-footnote-in-valid-context-p))
+ (save-excursion
+ (end-of-line)
+ ;; Footnotes definitions are separated by new headlines or blank
+ ;; lines.
+ (let ((lim (save-excursion (re-search-backward
+ (concat org-outline-regexp-bol
+ "\\|^[ \t]*$") nil t))))
+ (when (re-search-backward org-footnote-definition-re lim t)
+ (let ((label (org-match-string-no-properties 1))
+ (beg (match-beginning 0))
+ (beg-def (match-end 0))
+ ;; In message-mode, do not search after signature.
+ (end (let ((bound (and (derived-mode-p 'message-mode)
+ (save-excursion
+ (goto-char (point-max))
+ (re-search-backward
+ message-signature-separator nil t)))))
+ (if (progn
+ (end-of-line)
+ (re-search-forward
+ (concat org-outline-regexp-bol "\\|"
+ org-footnote-definition-re "\\|"
+ "^[ \t]*$") bound 'move))
+ (match-beginning 0)
+ (point)))))
+ (list label beg end
+ (org-trim (buffer-substring-no-properties beg-def end)))))))))
+
+(defun org-footnote-get-next-reference (&optional label backward limit)
+ "Return complete reference of the next footnote.
+
+If LABEL is provided, get the next reference of that footnote. If
+BACKWARD is non-nil, find previous reference instead. LIMIT is
+the buffer position bounding the search.
+
+Return value is a list like those provided by `org-footnote-at-reference-p'.
+If no footnote is found, return nil."
+ (save-excursion
+ (let* ((label-fmt (if label (format "\\[%s[]:]" label) org-footnote-re)))
+ (catch 'exit
+ (while t
+ (unless (funcall (if backward #'re-search-backward #'re-search-forward)
+ label-fmt limit t)
+ (throw 'exit nil))
+ (unless backward (backward-char))
+ (let ((ref (org-footnote-at-reference-p)))
+ (when ref (throw 'exit ref))))))))
+
+(defun org-footnote-next-reference-or-definition (limit)
+ "Move point to next footnote reference or definition.
+
+LIMIT is the buffer position bounding the search.
+
+Return value is a list like those provided by
+`org-footnote-at-reference-p' or `org-footnote-at-definition-p'.
+If no footnote is found, return nil."
+ (let* (ref (origin (point)))
+ (catch 'exit
+ (while t
+ (unless (re-search-forward org-footnote-re limit t)
+ (goto-char origin)
+ (throw 'exit nil))
+ ;; Beware: with [1]-like footnotes point will be just after
+ ;; the closing square bracket.
+ (backward-char)
+ (cond
+ ((setq ref (org-footnote-at-reference-p))
+ (throw 'exit ref))
+ ;; Definition: also grab the last square bracket, only
+ ;; matched in `org-footnote-re' for [1]-like footnotes.
+ ((save-match-data (org-footnote-at-definition-p))
+ (let ((end (match-end 0)))
+ (throw 'exit
+ (list nil (match-beginning 0)
+ (if (eq (char-before end) 93) end (1+ end)))))))))))
+
+(defun org-footnote-get-definition (label)
+ "Return label, boundaries and definition of the footnote LABEL."
+ (let* ((label (regexp-quote (org-footnote-normalize-label label)))
+ (re (format "^\\[%s\\]\\|.\\[%s:" label label))
+ pos)
+ (save-excursion
+ (save-restriction
+ (when (or (re-search-forward re nil t)
+ (and (goto-char (point-min))
+ (re-search-forward re nil t))
+ (and (progn (widen) t)
+ (goto-char (point-min))
+ (re-search-forward re nil t)))
+ (let ((refp (org-footnote-at-reference-p)))
+ (cond
+ ((and (nth 3 refp) refp))
+ ((org-footnote-at-definition-p)))))))))
+
+(defun org-footnote-goto-definition (label)
+ "Move point to the definition of the footnote LABEL.
+Return a non-nil value when a definition has been found."
+ (interactive "sLabel: ")
+ (org-mark-ring-push)
+ (let ((def (org-footnote-get-definition label)))
+ (if (not def)
+ (error "Cannot find definition of footnote %s" label)
+ (goto-char (nth 1 def))
+ (looking-at (format "\\[%s\\]\\|\\[%s:" label label))
+ (goto-char (match-end 0))
+ (org-show-context 'link-search)
+ (when (derived-mode-p 'org-mode)
+ (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))
+ t)))
+
+(defun org-footnote-goto-previous-reference (label)
+ "Find the first closest (to point) reference of footnote with label LABEL."
+ (interactive "sLabel: ")
+ (org-mark-ring-push)
+ (let* ((label (org-footnote-normalize-label label)) ref)
+ (save-excursion
+ (setq ref (or (org-footnote-get-next-reference label t)
+ (org-footnote-get-next-reference label)
+ (save-restriction
+ (widen)
+ (or
+ (org-footnote-get-next-reference label t)
+ (org-footnote-get-next-reference label))))))
+ (if (not ref)
+ (error "Cannot find reference of footnote %s" label)
+ (goto-char (nth 1 ref))
+ (org-show-context 'link-search))))
+
+(defun org-footnote-normalize-label (label)
+ "Return LABEL as an appropriate string."
+ (cond
+ ((numberp label) (number-to-string label))
+ ((equal "" label) nil)
+ ((not (string-match "^[0-9]+$\\|^fn:" label))
+ (concat "fn:" label))
+ (t label)))
+
+(defun org-footnote-all-labels (&optional with-defs)
+ "Return list with all defined foot labels used in the buffer.
+
+If WITH-DEFS is non-nil, also associate the definition to each
+label. The function will then return an alist whose key is label
+and value definition."
+ (let* (rtn
+ (push-to-rtn
+ (function
+ ;; Depending on WITH-DEFS, store label or (label . def) of
+ ;; footnote reference/definition given as argument in RTN.
+ (lambda (el)
+ (let ((lbl (car el)))
+ (push (if with-defs (cons lbl (nth 3 el)) lbl) rtn))))))
+ (save-excursion
+ (save-restriction
+ (widen)
+ ;; Find all labels found in definitions.
+ (goto-char (point-min))
+ (let (def)
+ (while (re-search-forward org-footnote-definition-re nil t)
+ (when (setq def (org-footnote-at-definition-p))
+ (funcall push-to-rtn def))))
+ ;; Find all labels found in references.
+ (goto-char (point-min))
+ (let (ref)
+ (while (setq ref (org-footnote-get-next-reference))
+ (goto-char (nth 2 ref))
+ (and (car ref) ; ignore anonymous footnotes
+ (not (funcall (if with-defs #'assoc #'member) (car ref) rtn))
+ (funcall push-to-rtn ref))))))
+ rtn))
+
+(defun org-footnote-unique-label (&optional current)
+ "Return a new unique footnote label.
+
+The function returns the first \"fn:N\" or \"N\" label that is
+currently not used.
+
+Optional argument CURRENT is the list of labels active in the
+buffer."
+ (unless current (setq current (org-footnote-all-labels)))
+ (let ((fmt (if (eq org-footnote-auto-label 'plain) "%d" "fn:%d"))
+ (cnt 1))
+ (while (member (format fmt cnt) current)
+ (incf cnt))
+ (format fmt cnt)))
+
+(defun org-footnote-new ()
+ "Insert a new footnote.
+This command prompts for a label. If this is a label referencing an
+existing label, only insert the label. If the footnote label is empty
+or new, let the user edit the definition of the footnote."
+ (interactive)
+ (unless (org-footnote-in-valid-context-p)
+ (error "Cannot insert a footnote here"))
+ (let* ((lbls (and (not (equal org-footnote-auto-label 'random))
+ (org-footnote-all-labels)))
+ (propose (and (not (equal org-footnote-auto-label 'random))
+ (org-footnote-unique-label lbls)))
+ (label
+ (org-footnote-normalize-label
+ (cond
+ ((member org-footnote-auto-label '(t plain))
+ propose)
+ ((equal org-footnote-auto-label 'random)
+ (require 'org-id)
+ (substring (org-id-uuid) 0 8))
+ (t
+ (org-icompleting-read
+ "Label (leave empty for anonymous): "
+ (mapcar 'list lbls) nil nil
+ (if (eq org-footnote-auto-label 'confirm) propose nil)))))))
+ (cond
+ ((bolp) (error "Cannot create a footnote reference at left margin"))
+ ((not label)
+ (insert "[fn:: ]")
+ (backward-char 1))
+ ((member label lbls)
+ (insert "[" label "]")
+ (message "New reference to existing note"))
+ (org-footnote-define-inline
+ (insert "[" label ": ]")
+ (backward-char 1)
+ (org-footnote-auto-adjust-maybe))
+ (t
+ (insert "[" label "]")
+ (org-footnote-create-definition label)
+ (org-footnote-auto-adjust-maybe)))))
+
+(defvar org-blank-before-new-entry) ; silence byte-compiler
+(defun org-footnote-create-definition (label)
+ "Start the definition of a footnote with label LABEL."
+ (interactive "sLabel: ")
+ (let ((label (org-footnote-normalize-label label)))
+ (cond
+ ;; In an Org file.
+ ((derived-mode-p 'org-mode)
+ ;; If `org-footnote-section' is defined, find it, or create it
+ ;; at the end of the buffer.
+ (when org-footnote-section
+ (goto-char (point-min))
+ (let ((re (concat "^\\*+[ \t]+" org-footnote-section "[ \t]*$")))
+ (unless (or (re-search-forward re nil t)
+ (and (progn (widen) t)
+ (re-search-forward re nil t)))
+ (goto-char (point-max))
+ (skip-chars-backward " \t\r\n")
+ (unless (bolp) (newline))
+ ;; Insert new section. Separate it from the previous one
+ ;; with a blank line, unless `org-blank-before-new-entry'
+ ;; explicitly says no.
+ (when (and (cdr (assq 'heading org-blank-before-new-entry))
+ (zerop (save-excursion (org-back-over-empty-lines))))
+ (insert "\n"))
+ (insert "* " org-footnote-section "\n"))))
+ ;; Move to the end of this entry (which may be
+ ;; `org-footnote-section' or the current one).
+ (org-footnote-goto-local-insertion-point)
+ (org-show-context 'link-search))
+ (t
+ ;; In a non-Org file. Search for footnote tag, or create it if
+ ;; specified (at the end of buffer, or before signature if in
+ ;; Message mode). Set point after any definition already there.
+ (let ((tag (and org-footnote-tag-for-non-org-mode-files
+ (concat "^" (regexp-quote
+ org-footnote-tag-for-non-org-mode-files)
+ "[ \t]*$")))
+ (max (if (and (derived-mode-p 'message-mode)
+ (goto-char (point-max))
+ (re-search-backward
+ message-signature-separator nil t))
+ (progn
+ ;; Ensure one blank line separates last
+ ;; footnote from signature.
+ (beginning-of-line)
+ (open-line 2)
+ (point-marker))
+ (point-max-marker))))
+ (set-marker-insertion-type max t)
+ (goto-char max)
+ ;; Check if the footnote tag is defined but missing. In this
+ ;; case, insert it, before any footnote or one blank line
+ ;; after any previous text.
+ (when (and tag (not (re-search-backward tag nil t)))
+ (skip-chars-backward " \t\r\n")
+ (while (re-search-backward org-footnote-definition-re nil t))
+ (unless (bolp) (newline 2))
+ (insert org-footnote-tag-for-non-org-mode-files "\n\n"))
+ ;; Remove superfluous white space and clear marker.
+ (goto-char max)
+ (skip-chars-backward " \t\r\n")
+ (delete-region (point) max)
+ (unless (bolp) (newline))
+ (set-marker max nil))))
+ ;; Insert footnote label.
+ (when (zerop (org-back-over-empty-lines)) (newline))
+ (insert "[" label "] \n")
+ (backward-char)
+ ;; Only notify user about next possible action when in an Org
+ ;; buffer, as the bindings may have different meanings otherwise.
+ (when (derived-mode-p 'org-mode)
+ (message
+ "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))))
+
+;;;###autoload
+(defun org-footnote-action (&optional special)
+ "Do the right thing for footnotes.
+
+When at a footnote reference, jump to the definition.
+
+When at a definition, jump to the references if they exist, offer
+to create them otherwise.
+
+When neither at definition or reference, create a new footnote,
+interactively.
+
+With prefix arg SPECIAL, offer additional commands in a menu."
+ (interactive "P")
+ (let (tmp c)
+ (cond
+ (special
+ (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s |->[n]umeric | [d]elete")
+ (setq c (read-char-exclusive))
+ (cond
+ ((eq c ?s) (org-footnote-normalize 'sort))
+ ((eq c ?r) (org-footnote-renumber-fn:N))
+ ((eq c ?S)
+ (org-footnote-renumber-fn:N)
+ (org-footnote-normalize 'sort))
+ ((eq c ?n) (org-footnote-normalize))
+ ((eq c ?d) (org-footnote-delete))
+ (t (error "No such footnote command %c" c))))
+ ((setq tmp (org-footnote-at-reference-p))
+ (cond
+ ;; Anonymous footnote: move point at the beginning of its
+ ;; definition.
+ ((not (car tmp))
+ (goto-char (nth 1 tmp))
+ (forward-char 5))
+ ;; A definition exists: move to it.
+ ((ignore-errors (org-footnote-goto-definition (car tmp))))
+ ;; No definition exists: offer to create it.
+ ((yes-or-no-p (format "No definition for %s. Create one? " (car tmp)))
+ (org-footnote-create-definition (car tmp)))))
+ ((setq tmp (org-footnote-at-definition-p))
+ (org-footnote-goto-previous-reference (car tmp)))
+ (t (org-footnote-new)))))
+
+(defvar org-footnote-insert-pos-for-preprocessor 'point-max
+ "See `org-footnote-normalize'.")
+
+(defvar org-export-footnotes-seen) ; silence byte-compiler
+(defvar org-export-footnotes-data) ; silence byte-compiler
+
+;;;###autoload
+(defun org-footnote-normalize (&optional sort-only export-props)
+ "Collect the footnotes in various formats and normalize them.
+
+This finds the different sorts of footnotes allowed in Org, and
+normalizes them to the usual [N] format that is understood by the
+Org-mode exporters.
+
+When SORT-ONLY is set, only sort the footnote definitions into the
+referenced sequence.
+
+If Org is amidst an export process, EXPORT-PROPS will hold the
+export properties of the buffer.
+
+When EXPORT-PROPS is non-nil, the default action is to insert
+normalized footnotes towards the end of the pre-processing
+buffer. Some exporters (docbook, odt...) expect footnote
+definitions to be available before any references to them. Such
+exporters can let bind `org-footnote-insert-pos-for-preprocessor'
+to symbol `point-min' to achieve the desired behaviour.
+
+Additional note on `org-footnote-insert-pos-for-preprocessor':
+1. This variable has not effect when FOR-PREPROCESSOR is nil.
+2. This variable (potentially) obviates the need for extra scan
+ of pre-processor buffer as witnessed in
+ `org-export-docbook-get-footnotes'."
+ ;; This is based on Paul's function, but rewritten.
+ ;;
+ ;; Re-create `org-with-limited-levels', but not limited to Org
+ ;; buffers.
+ (let* ((limit-level
+ (and (boundp 'org-inlinetask-min-level)
+ org-inlinetask-min-level
+ (1- org-inlinetask-min-level)))
+ (nstars (and limit-level
+ (if org-odd-levels-only
+ (and limit-level (1- (* limit-level 2)))
+ limit-level)))
+ (org-outline-regexp
+ (concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ ")))
+ ;; Determine the highest marker used so far.
+ (ref-table (when export-props org-export-footnotes-seen))
+ (count (if (and export-props ref-table)
+ (apply 'max (mapcar (lambda (e) (nth 1 e)) ref-table))
+ 0))
+ ins-point ref)
+ (save-excursion
+ ;; 1. Find every footnote reference, extract the definition, and
+ ;; collect that data in REF-TABLE. If SORT-ONLY is nil, also
+ ;; normalize references.
+ (goto-char (point-min))
+ (while (setq ref (org-footnote-get-next-reference))
+ (let* ((lbl (car ref))
+ (pos (nth 1 ref))
+ ;; When footnote isn't anonymous, check if it's label
+ ;; (REF) is already stored in REF-TABLE. In that case,
+ ;; extract number used to identify it (MARKER). If
+ ;; footnote is unknown, increment the global counter
+ ;; (COUNT) to create an unused identifier.
+ (a (and lbl (assoc lbl ref-table)))
+ (marker (or (nth 1 a) (incf count)))
+ ;; Is the reference inline or pointing to an inline
+ ;; footnote?
+ (inlinep (or (stringp (nth 3 ref)) (nth 3 a))))
+ ;; Replace footnote reference with [MARKER]. Maybe fill
+ ;; paragraph once done. If SORT-ONLY is non-nil, only move
+ ;; to the end of reference found to avoid matching it twice.
+ ;; If EXPORT-PROPS isn't nil, also add `org-footnote'
+ ;; property to it, so it can be easily recognized by
+ ;; exporters.
+ (if sort-only (goto-char (nth 2 ref))
+ (delete-region (nth 1 ref) (nth 2 ref))
+ (goto-char (nth 1 ref))
+ (let ((new-ref (format "[%d]" marker)))
+ (when export-props (org-add-props new-ref '(org-footnote t)))
+ (insert new-ref))
+ (and inlinep
+ org-footnote-fill-after-inline-note-extraction
+ (org-fill-paragraph)))
+ ;; Add label (REF), identifier (MARKER), definition (DEF)
+ ;; type (INLINEP) and position (POS) to REF-TABLE if data
+ ;; was unknown.
+ (unless a
+ (let ((def (or (nth 3 ref) ; inline
+ (and export-props
+ (cdr (assoc lbl org-export-footnotes-data)))
+ (nth 3 (org-footnote-get-definition lbl)))))
+ (push (list lbl marker
+ ;; When exporting, each definition goes
+ ;; through `org-export-preprocess-string' so
+ ;; it is ready to insert in the
+ ;; backend-specific buffer.
+ (if (and export-props def)
+ (let ((parameters
+ (org-combine-plists
+ export-props
+ '(:todo-keywords t :tags t :priority t))))
+ (apply #'org-export-preprocess-string def parameters))
+ def)
+ ;; Reference beginning position is a marker
+ ;; to preserve it during further buffer
+ ;; modifications.
+ inlinep (copy-marker pos)) ref-table)))))
+ ;; 2. Find and remove the footnote section, if any. Also
+ ;; determine where footnotes shall be inserted (INS-POINT).
+ (cond
+ ((and org-footnote-section (derived-mode-p 'org-mode))
+ (goto-char (point-min))
+ (if (re-search-forward
+ (concat "^\\*[ \t]+" (regexp-quote org-footnote-section)
+ "[ \t]*$") nil t)
+ (delete-region (match-beginning 0) (org-end-of-subtree t t)))
+ ;; A new footnote section is inserted by default at the end of
+ ;; the buffer.
+ (goto-char (point-max))
+ (skip-chars-backward " \r\t\n")
+ (forward-line)
+ (unless (bolp) (newline)))
+ ;; No footnote section set: Footnotes will be added at the end
+ ;; of the section containing their first reference.
+ ;; Nevertheless, in an export situation, set insertion point to
+ ;; `point-max' by default.
+ ((derived-mode-p 'org-mode)
+ (when export-props
+ (goto-char (point-max))
+ (skip-chars-backward " \r\t\n")
+ (forward-line)
+ (delete-region (point) (point-max))))
+ (t
+ ;; Remove any left-over tag in the buffer, if one is set up.
+ (when org-footnote-tag-for-non-org-mode-files
+ (let ((tag (concat "^" (regexp-quote
+ org-footnote-tag-for-non-org-mode-files)
+ "[ \t]*$")))
+ (goto-char (point-min))
+ (while (re-search-forward tag nil t)
+ (replace-match "")
+ (delete-region (point) (progn (forward-line) (point))))))
+ ;; In Message mode, ensure footnotes are inserted before the
+ ;; signature.
+ (if (and (derived-mode-p 'message-mode)
+ (goto-char (point-max))
+ (re-search-backward message-signature-separator nil t))
+ (beginning-of-line)
+ (goto-char (point-max)))))
+ ;; During export, `org-footnote-insert-pos-for-preprocessor' has
+ ;; precedence over previously found position.
+ (setq ins-point
+ (copy-marker
+ (if (and export-props
+ (eq org-footnote-insert-pos-for-preprocessor 'point-min))
+ (point-min)
+ (point))))
+ ;; 3. Clean-up REF-TABLE.
+ (setq ref-table
+ (delq nil
+ (mapcar
+ (lambda (x)
+ (cond
+ ;; When only sorting, ignore inline footnotes.
+ ;; Also clear position marker.
+ ((and sort-only (nth 3 x))
+ (set-marker (nth 4 x) nil) nil)
+ ;; No definition available: provide one.
+ ((not (nth 2 x))
+ (append
+ (list (car x) (nth 1 x)
+ (format "DEFINITION NOT FOUND: %s" (car x)))
+ (nthcdr 3 x)))
+ (t x)))
+ ref-table)))
+ (setq ref-table (nreverse ref-table))
+ ;; 4. Remove left-over definitions in the buffer.
+ (mapc (lambda (x)
+ (unless (nth 3 x) (org-footnote-delete-definitions (car x))))
+ ref-table)
+ ;; 5. Insert the footnotes again in the buffer, at the
+ ;; appropriate spot.
+ (goto-char ins-point)
+ (cond
+ ;; No footnote: exit.
+ ((not ref-table))
+ ;; Cases when footnotes should be inserted in one place.
+ ((or (not (derived-mode-p 'org-mode))
+ org-footnote-section
+ export-props)
+ ;; Insert again the section title, if any. Ensure that title,
+ ;; or the subsequent footnotes, will be separated by a blank
+ ;; lines from the rest of the document. In an Org buffer,
+ ;; separate section with a blank line, unless explicitly
+ ;; stated in `org-blank-before-new-entry'.
+ (cond
+ ((not (derived-mode-p 'org-mode))
+ (skip-chars-backward " \t\n\r")
+ (delete-region (point) ins-point)
+ (unless (bolp) (newline))
+ (when org-footnote-tag-for-non-org-mode-files
+ (insert "\n" org-footnote-tag-for-non-org-mode-files "\n")))
+ ((and org-footnote-section (not export-props))
+ (when (and (cdr (assq 'heading org-blank-before-new-entry))
+ (zerop (save-excursion (org-back-over-empty-lines))))
+ (insert "\n"))
+ (insert "* " org-footnote-section "\n")))
+ (set-marker ins-point nil)
+ ;; Insert the footnotes, separated by a blank line.
+ (insert
+ (mapconcat
+ (lambda (x)
+ ;; Clean markers.
+ (set-marker (nth 4 x) nil)
+ (format "\n[%s] %s" (nth (if sort-only 0 1) x) (nth 2 x)))
+ ref-table "\n"))
+ (unless (eobp) (insert "\n\n"))
+ ;; When exporting, add newly inserted markers along with their
+ ;; associated definition to `org-export-footnotes-seen'.
+ (when export-props (setq org-export-footnotes-seen ref-table)))
+ ;; Each footnote definition has to be inserted at the end of
+ ;; the section where its first reference belongs.
+ (t
+ (mapc
+ (lambda (x)
+ (let ((pos (nth 4 x)))
+ (goto-char pos)
+ ;; Clean marker.
+ (set-marker pos nil))
+ (org-footnote-goto-local-insertion-point)
+ (insert (format "\n[%s] %s\n"
+ (if sort-only (car x) (nth 1 x))
+ (nth 2 x))))
+ ref-table))))))
+
+(defun org-footnote-goto-local-insertion-point ()
+ "Find insertion point for footnote, just before next outline heading."
+ (org-with-limited-levels (outline-next-heading))
+ (or (bolp) (newline))
+ (beginning-of-line 0)
+ (while (and (not (bobp)) (= (char-after) ?#))
+ (beginning-of-line 0))
+ (if (let ((case-fold-search t)) (looking-at "[ \t]*#\\+tblfm:")) (beginning-of-line 2))
+ (end-of-line 1)
+ (skip-chars-backward "\n\r\t ")
+ (forward-line))
+
+(defun org-footnote-delete-references (label)
+ "Delete every reference to footnote LABEL.
+Return the number of footnotes removed."
+ (save-excursion
+ (goto-char (point-min))
+ (let (ref (nref 0))
+ (while (setq ref (org-footnote-get-next-reference label))
+ (goto-char (nth 1 ref))
+ (delete-region (nth 1 ref) (nth 2 ref))
+ (incf nref))
+ nref)))
+
+(defun org-footnote-delete-definitions (label)
+ "Delete every definition of the footnote LABEL.
+Return the number of footnotes removed."
+ (save-excursion
+ (goto-char (point-min))
+ (let ((def-re (concat "^\\[" (regexp-quote label) "\\]"))
+ (ndef 0))
+ (while (re-search-forward def-re nil t)
+ (let ((full-def (org-footnote-at-definition-p)))
+ (when full-def
+ ;; Remove the footnote, and all blank lines before it.
+ (goto-char (nth 1 full-def))
+ (skip-chars-backward " \r\t\n")
+ (unless (bolp) (forward-line))
+ (delete-region (point) (nth 2 full-def))
+ (incf ndef))))
+ ndef)))
+
+(defun org-footnote-delete (&optional label)
+ "Delete the footnote at point.
+This will remove the definition (even multiple definitions if they exist)
+and all references of a footnote label.
+
+If LABEL is non-nil, delete that footnote instead."
+ (catch 'done
+ (let* ((nref 0) (ndef 0) x
+ ;; 1. Determine LABEL of footnote at point.
+ (label (cond
+ ;; LABEL is provided as argument.
+ (label)
+ ;; Footnote reference at point. If the footnote is
+ ;; anonymous, delete it and exit instead.
+ ((setq x (org-footnote-at-reference-p))
+ (or (car x)
+ (progn
+ (delete-region (nth 1 x) (nth 2 x))
+ (message "Anonymous footnote removed")
+ (throw 'done t))))
+ ;; Footnote definition at point.
+ ((setq x (org-footnote-at-definition-p))
+ (car x))
+ (t (error "Don't know which footnote to remove")))))
+ ;; 2. Now that LABEL is non-nil, find every reference and every
+ ;; definition, and delete them.
+ (setq nref (org-footnote-delete-references label)
+ ndef (org-footnote-delete-definitions label))
+ ;; 3. Verify consistency of footnotes and notify user.
+ (org-footnote-auto-adjust-maybe)
+ (message "%d definition(s) of and %d reference(s) of footnote %s removed"
+ ndef nref label))))
+
+(defun org-footnote-renumber-fn:N ()
+ "Renumber the simple footnotes like fn:17 into a sequence in the document."
+ (interactive)
+ (let (map (n 0))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t)
+ (save-excursion
+ (goto-char (match-beginning 0))
+ ;; Ensure match is a footnote reference or definition.
+ (when (save-match-data (if (bolp)
+ (org-footnote-at-definition-p)
+ (org-footnote-at-reference-p)))
+ (let ((new-val (or (cdr (assoc (match-string 1) map))
+ (number-to-string (incf n)))))
+ (unless (assoc (match-string 1) map)
+ (push (cons (match-string 1) new-val) map))
+ (replace-match new-val nil nil nil 1))))))))
+
+(defun org-footnote-auto-adjust-maybe ()
+ "Renumber and/or sort footnotes according to user settings."
+ (when (memq org-footnote-auto-adjust '(t renumber))
+ (org-footnote-renumber-fn:N))
+ (when (memq org-footnote-auto-adjust '(t sort))
+ (let ((label (car (org-footnote-at-definition-p))))
+ (org-footnote-normalize 'sort)
+ (when label
+ (goto-char (point-min))
+ (and (re-search-forward (concat "^\\[" (regexp-quote label) "\\]")
+ nil t)
+ (progn (insert " ")
+ (just-one-space)))))))
+
+(provide 'org-footnote)
+
+;;; org-footnote.el ends here