summaryrefslogtreecommitdiff
path: root/lisp/org-footnote.el
diff options
context:
space:
mode:
authorSébastien Delafond <sdelafond@gmail.com>2015-08-25 12:27:35 +0200
committerSébastien Delafond <sdelafond@gmail.com>2015-08-25 12:27:35 +0200
commit1be13d57dc8357576a8285c6dadc03db9e3ed7b0 (patch)
treee35b32d4dbd60cb6cea09f3c0797cc8877352def /lisp/org-footnote.el
parent4dc4918d0d667f18f3d5e3dd71e6f117ddb8af8a (diff)
Imported Upstream version 8.3.1
Diffstat (limited to 'lisp/org-footnote.el')
-rw-r--r--lisp/org-footnote.el675
1 files changed, 383 insertions, 292 deletions
diff --git a/lisp/org-footnote.el b/lisp/org-footnote.el
index c8b8c2e..7396609 100644
--- a/lisp/org-footnote.el
+++ b/lisp/org-footnote.el
@@ -1,6 +1,6 @@
;;; org-footnote.el --- Footnote support in Org and elsewhere
;;
-;; Copyright (C) 2009-2014 Free Software Foundation, Inc.
+;; Copyright (C) 2009-2015 Free Software Foundation, Inc.
;;
;; Author: Carsten Dominik <carsten at orgmode dot org>
;; Keywords: outlines, hypermedia, calendar, wp
@@ -38,32 +38,39 @@
(require 'org-compat)
(declare-function message-point-in-header-p "message" ())
+(declare-function org-at-comment-p "org" ())
+(declare-function org-at-heading-p "org" (&optional ignored))
(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-edit-footnote-reference "org-src" ())
+(declare-function org-element-context "org-element" (&optional element))
+(declare-function org-element-property "org-element" (property element))
+(declare-function org-element-type "org-element" (element))
(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
(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" ())
+(declare-function org-trim "org" (s))
+(declare-function outline-next-heading "outline")
-(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
+(defvar org-bracket-link-regexp) ; defined in org.el
+(defvar org-complex-heading-regexp) ; defined in org.el
+(defvar org-element-all-elements) ; defined in org-element.el
+(defvar org-element-all-objects) ; defined in org-element.el
+(defvar org-odd-levels-only) ; defined in org.el
+(defvar org-outline-regexp-bol) ; defined in org.el
(defconst org-footnote-re
;; Only [1]-like footnotes are closed in this regexp, as footnotes
@@ -106,8 +113,18 @@ 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 ignored."
+this heading will be ignored.
+
+If you don't use the customize interface to change this variable,
+you will need to run the following command after the change:
+
+ \\[universal-argument] \\[org-element-cache-reset]"
:group 'org-footnote
+ :initialize 'custom-initialize-default
+ :set (lambda (var val)
+ (set var val)
+ (when (fboundp 'org-element-cache-reset)
+ (org-element-cache-reset 'all)))
:type '(choice
(string :tag "Collect footnotes under heading")
(const :tag "Define footnotes locally" nil)))
@@ -182,8 +199,7 @@ extracted will be filled again."
(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)
+ (not (or (org-at-comment-p)
(org-inside-LaTeX-fragment-p)
;; Avoid literal example.
(org-in-verbatim-emphasis)
@@ -330,36 +346,56 @@ If no footnote is found, return nil."
(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)
+ (re (format "^\\[%s\\]\\|.\\[%s:" label label)))
+ (org-with-wide-buffer
+ (goto-char (point-min))
+ (catch 'found
+ (while (re-search-forward re nil t)
+ (let* ((datum (progn (backward-char) (org-element-context)))
+ (type (org-element-type datum)))
+ (when (memq type '(footnote-definition footnote-reference))
+ (throw 'found
+ (list
+ label
+ (org-element-property :begin datum)
+ (org-element-property :end datum)
+ (let ((cbeg (org-element-property :contents-begin datum)))
+ (if (not cbeg) ""
+ (replace-regexp-in-string
+ "[ \t\n]*\\'"
+ ""
+ (buffer-substring-no-properties
+ cbeg
+ (org-element-property :contents-end datum))))))))))
+ nil))))
+
+(defun org-footnote-goto-definition (label &optional location)
"Move point to the definition of the footnote LABEL.
-Return a non-nil value when a definition has been found."
+
+LOCATION, when non-nil specifies the buffer position of the
+definition.
+
+Throw an error if there is no definition or if it cannot be
+reached from current narrowed part of buffer. Return a non-nil
+value if point was successfully moved."
(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)))
+ (let ((def-start (or location (nth 1 (org-footnote-get-definition label)))))
+ (cond
+ ((not def-start)
+ (user-error "Cannot find definition of footnote %s" label))
+ ((or (> def-start (point-max)) (< def-start (point-min)))
+ (user-error "Definition is outside narrowed part of buffer")))
+ (org-mark-ring-push)
+ (goto-char def-start)
+ (looking-at (format "\\[%s[]:]" label))
+ (goto-char (match-end 0))
+ (org-show-context 'link-search)
+ (when (derived-mode-p 'org-mode)
+ (message
+ (substitute-command-keys
+ "Edit definition and go back with `\\[org-mark-ring-goto]' or, if \
+unique, with `\\[org-ctrl-c-ctrl-c]'.")))
+ t))
(defun org-footnote-goto-previous-reference (label)
"Find the first closest (to point) reference of footnote with label LABEL."
@@ -436,79 +472,131 @@ buffer."
(incf cnt))
(format fmt cnt)))
+(defun org-footnote--allow-reference-p ()
+ "Non-nil when a footnote reference can be inserted at point."
+ ;; XXX: This is similar to `org-footnote-in-valid-context-p' but
+ ;; more accurate and usually faster, except in some corner cases.
+ ;; It may replace it after doing proper benchmarks as it would be
+ ;; used in fontification.
+ (unless (bolp)
+ (let* ((context (org-element-context))
+ (type (org-element-type context)))
+ (cond
+ ;; No footnote reference in attributes.
+ ((let ((post (org-element-property :post-affiliated context)))
+ (and post (< (point) post)))
+ nil)
+ ;; Paragraphs and blank lines at top of document are fine.
+ ((memq type '(nil paragraph)))
+ ;; So are contents of verse blocks.
+ ((eq type 'verse-block)
+ (and (>= (point) (org-element-property :contents-begin context))
+ (< (point) (org-element-property :contents-end context))))
+ ;; In an headline or inlinetask, point must be either on the
+ ;; heading itself or on the blank lines below.
+ ((memq type '(headline inlinetask))
+ (or (not (org-at-heading-p))
+ (and (save-excursion (beginning-of-line)
+ (and (let ((case-fold-search t))
+ (not (looking-at "\\*+ END[ \t]*$")))
+ (looking-at org-complex-heading-regexp)))
+ (match-beginning 4)
+ (>= (point) (match-beginning 4))
+ (or (not (match-beginning 5))
+ (< (point) (match-beginning 5))))))
+ ;; White spaces after an object or blank lines after an element
+ ;; are OK.
+ ((>= (point)
+ (save-excursion (goto-char (org-element-property :end context))
+ (skip-chars-backward " \r\t\n")
+ (if (memq type org-element-all-objects) (point)
+ (1+ (line-beginning-position 2))))))
+ ;; Other elements are invalid.
+ ((memq type org-element-all-elements) nil)
+ ;; Just before object is fine.
+ ((= (point) (org-element-property :begin context)))
+ ;; Within recursive object too, but not in a link.
+ ((eq type 'link) nil)
+ ((let ((cbeg (org-element-property :contents-begin context))
+ (cend (org-element-property :contents-end context)))
+ (and cbeg (>= (point) cbeg) (<= (point) cend))))))))
+
(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)))
+ (unless (org-footnote--allow-reference-p)
+ (user-error "Cannot insert a footnote here"))
+ (let* ((all (org-footnote-all-labels))
(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
+ (if (eq org-footnote-auto-label 'random)
+ (format "fn:%x" (random #x100000000))
+ (let ((propose (org-footnote-unique-label all)))
+ (if (memq org-footnote-auto-label '(t plain)) propose
+ (org-icompleting-read
+ "Label (leave empty for anonymous): "
+ (mapcar #'list all) nil nil
+ (and (eq org-footnote-auto-label 'confirm) propose))))))))
+ (cond ((not label)
+ (insert "[fn::]")
+ (backward-char 1))
+ ((member label all)
+ (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 "]")
+ (let ((l (copy-marker (org-footnote-create-definition label))))
+ (org-footnote-auto-adjust-maybe)
+ (or (ignore-errors (org-footnote-goto-definition label l))
+ ;; Since definition was created outside current
+ ;; scope, edit it remotely.
+ (progn (set-marker l nil)
+ (org-edit-footnote-reference))))))))
+
+(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: ")
+ "Start the definition of a footnote with label LABEL.
+Return buffer position at the beginning of the definition. In an
+Org buffer, this function doesn't move point."
(let ((label (org-footnote-normalize-label label))
- electric-indent-mode) ;; Prevent wrong indentation
+ electric-indent-mode) ; Prevent wrong indentation.
(cond
- ;; In an Org file.
+ ;; In an Org document.
((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))
+ (org-with-wide-buffer
+ (cond
+ ((not org-footnote-section)
+ (org-footnote--goto-local-insertion-point))
+ ((save-excursion
+ (goto-char (point-min))
+ (re-search-forward
+ (concat "^\\*+[ \t]+" (regexp-quote org-footnote-section) "[ \t]*$")
+ nil t))
+ (goto-char (match-end 0))
+ (forward-line)
+ (unless (bolp) (insert "\n")))
+ (t
+ (goto-char (point-max))
+ (unless (bolp) (insert "\n"))
+ ;; 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")))
+ (when (zerop (org-back-over-empty-lines)) (insert "\n"))
+ (insert "[" label "] \n")
+ (line-beginning-position 0)))
(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
@@ -543,16 +631,11 @@ or new, let the user edit the definition of the footnote."
(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'."))))
+ (set-marker max nil))
+ (when (zerop (org-back-over-empty-lines)) (insert "\n"))
+ (insert "[" label "] \n")
+ (backward-char)
+ (line-beginning-position)))))
;;;###autoload
(defun org-footnote-action (&optional special)
@@ -564,38 +647,47 @@ 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.
+interactively if possible.
-With prefix arg SPECIAL, offer additional commands in a menu."
+With prefix arg SPECIAL, or when no footnote can be created,
+offer additional commands in a menu."
(interactive "P")
- (let (tmp c)
+ (let* ((context (and (not special) (org-element-context)))
+ (type (org-element-type context)))
(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)))
+ ((eq type 'footnote-reference)
+ (let ((label (org-element-property :label context)))
+ (cond
+ ;; Anonymous footnote: move point at the beginning of its
+ ;; definition.
+ ((not label)
+ (goto-char (org-element-property :contents-begin context)))
+ ;; Check if a definition exists: then move to it.
+ ((let ((p (nth 1 (org-footnote-get-definition label))))
+ (when p (org-footnote-goto-definition label p))))
+ ;; No definition exists: offer to create it.
+ ((yes-or-no-p (format "No definition for %s. Create one? " label))
+ (let ((p (org-footnote-create-definition label)))
+ (or (ignore-errors (org-footnote-goto-definition label p))
+ ;; Since definition was created outside current scope,
+ ;; edit it remotely.
+ (org-edit-footnote-reference)))))))
+ ((eq type 'footnote-definition)
+ (org-footnote-goto-previous-reference
+ (org-element-property :label context)))
+ ((or special (not (org-footnote--allow-reference-p)))
+ (message "Footnotes: [s]ort | [r]enumber fn:N | [S]=r+s | \
+->[n]umeric | [d]elete")
+ (let ((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)))))
(t (org-footnote-new)))))
;;;###autoload
@@ -622,163 +714,162 @@ referenced sequence."
(concat "\\*" (if nstars (format "\\{1,%d\\} " nstars) "+ ")))
(count 0)
ins-point ref ref-table)
- (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 sort-only (goto-char (nth 2 ref))
- (delete-region (nth 1 ref) (nth 2 ref))
- (goto-char (nth 1 ref))
- (insert (format "[%d]" marker))
- (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 definition.
- (nth 3 (org-footnote-get-definition lbl)))))
- (push (list lbl marker 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.
- ((derived-mode-p 'org-mode))
- (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)))))
- (setq ins-point (point-marker))
- ;; 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)
- ;; 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'.
- (if (not (derived-mode-p 'org-mode))
- (progn (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")))
- (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")))
- ;; Each footnote definition has to be inserted at the end of
- ;; the section where its first reference belongs.
- (t
- (mapc
+ (org-with-wide-buffer
+ ;; 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 sort-only (goto-char (nth 2 ref))
+ (delete-region (nth 1 ref) (nth 2 ref))
+ (goto-char (nth 1 ref))
+ (insert (format "[%d]" marker))
+ (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 definition.
+ (nth 3 (org-footnote-get-definition lbl)))))
+ (push (list lbl marker 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.
+ ((derived-mode-p 'org-mode))
+ (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)))))
+ (setq ins-point (point-marker))
+ ;; 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.
+ (dolist (x ref-table)
+ (unless (nth 3 x) (org-footnote-delete-definitions (car x))))
+ ;; 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)
+ ;; 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'.
+ (if (not (derived-mode-p 'org-mode))
+ (progn (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")))
+ (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)
- (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."
+ ;; 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")))
+ ;; Each footnote definition has to be inserted at the end of the
+ ;; section where its first reference belongs.
+ (t
+ (dolist (x ref-table)
+ (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"
+ (nth (if sort-only 0 1) x)
+ (nth 2 x)))))))))
+
+(defun org-footnote--goto-local-insertion-point ()
+ "Find insertion point for footnote, just before next outline heading.
+Assume insertion point is within currently accessible part of the buffer."
(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))
+ ;; Skip file local variables. See `modify-file-local-variable'.
+ (when (eobp)
+ (let ((case-fold-search t))
+ (re-search-backward "^[ \t]*# +Local Variables:"
+ (max (- (point-max) 3000) (point-min))
+ t)))
+ (skip-chars-backward " \t\n")
+ (forward-line)
+ (unless (bolp) (insert "\n")))
(defun org-footnote-delete-references (label)
"Delete every reference to footnote LABEL.