2015-10-26 00:56:00 +00:00
|
|
|
|
;;; org-footnote.el --- Footnote support in Org -*- lexical-binding: t; -*-
|
2008-12-30 22:40:14 +00:00
|
|
|
|
;;
|
2021-01-01 19:55:31 +00:00
|
|
|
|
;; Copyright (C) 2009-2021 Free Software Foundation, Inc.
|
2008-12-30 22:40:14 +00:00
|
|
|
|
;;
|
|
|
|
|
;; Author: Carsten Dominik <carsten at orgmode dot org>
|
|
|
|
|
;; Keywords: outlines, hypermedia, calendar, wp
|
2018-01-16 16:22:00 +00:00
|
|
|
|
;; Homepage: https://orgmode.org
|
2008-12-30 22:40:14 +00:00
|
|
|
|
;;
|
|
|
|
|
;; 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
|
2017-09-13 22:52:52 +00:00
|
|
|
|
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
2008-12-30 22:40:14 +00:00
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
|
;;
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;; This file contains the code dealing with footnotes in Org mode.
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;;;; Declarations
|
|
|
|
|
|
|
|
|
|
(require 'cl-lib)
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(require 'org-macs)
|
|
|
|
|
(require 'org-compat)
|
|
|
|
|
|
2015-05-30 12:28:35 +00:00
|
|
|
|
(declare-function org-at-comment-p "org" ())
|
|
|
|
|
(declare-function org-at-heading-p "org" (&optional ignored))
|
2011-08-16 18:02:30 +00:00
|
|
|
|
(declare-function org-back-over-empty-lines "org" ())
|
2015-05-30 12:28:35 +00:00
|
|
|
|
(declare-function org-edit-footnote-reference "org-src" ())
|
2015-12-29 20:36:30 +00:00
|
|
|
|
(declare-function org-element-at-point "org-element" ())
|
2016-10-25 11:13:26 +00:00
|
|
|
|
(declare-function org-element-class "org-element" (datum &optional parent))
|
2014-06-01 12:19:25 +00:00
|
|
|
|
(declare-function org-element-context "org-element" (&optional element))
|
2015-12-29 20:36:30 +00:00
|
|
|
|
(declare-function org-element-lineage "org-element" (blob &optional types with-self))
|
2014-06-01 12:19:25 +00:00
|
|
|
|
(declare-function org-element-property "org-element" (property element))
|
|
|
|
|
(declare-function org-element-type "org-element" (element))
|
2011-08-16 18:02:30 +00:00
|
|
|
|
(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading))
|
2017-12-15 21:24:44 +00:00
|
|
|
|
(declare-function org-fill-paragraph "org" (&optional justify region))
|
2011-08-16 18:02:30 +00:00
|
|
|
|
(declare-function org-in-block-p "org" (names))
|
2009-10-29 15:34:33 +00:00
|
|
|
|
(declare-function org-in-verbatim-emphasis "org" ())
|
2011-09-04 14:45:39 +00:00
|
|
|
|
(declare-function org-inside-LaTeX-fragment-p "org" ())
|
2009-12-11 07:49:44 +00:00
|
|
|
|
(declare-function org-inside-latex-macro-p "org" ())
|
2011-08-16 18:02:30 +00:00
|
|
|
|
(declare-function org-mark-ring-push "org" (&optional pos buffer))
|
|
|
|
|
(declare-function org-show-context "org" (&optional key))
|
2015-05-30 12:28:35 +00:00
|
|
|
|
(declare-function outline-next-heading "outline")
|
2011-07-18 17:25:10 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(defvar electric-indent-mode)
|
|
|
|
|
(defvar org-blank-before-new-entry) ; defined in org.el
|
2018-11-26 23:04:41 +00:00
|
|
|
|
(defvar org-link-bracket-re) ; defined in org.el
|
2015-05-30 12:28:35 +00:00
|
|
|
|
(defvar org-complex-heading-regexp) ; defined in org.el
|
|
|
|
|
(defvar org-odd-levels-only) ; defined in org.el
|
2015-10-26 08:29:13 +00:00
|
|
|
|
(defvar org-outline-regexp) ; defined in org.el
|
2015-05-30 12:28:35 +00:00
|
|
|
|
(defvar org-outline-regexp-bol) ; defined in org.el
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Constants
|
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defconst org-footnote-re
|
2015-12-16 17:38:53 +00:00
|
|
|
|
"\\[fn:\\(?:\\(?1:[-_[:word:]]+\\)?\\(:\\)\\|\\(?1:[-_[:word:]]+\\)\\]\\)"
|
|
|
|
|
"Regular expression for matching footnotes.
|
|
|
|
|
Match group 1 contains footnote's label. It is nil for anonymous
|
|
|
|
|
footnotes. Match group 2 is non-nil only when footnote is
|
|
|
|
|
inline, i.e., it contains its own definition.")
|
|
|
|
|
|
|
|
|
|
(defconst org-footnote-definition-re "^\\[fn:\\([-_[:word:]]+\\)\\]"
|
|
|
|
|
"Regular expression matching the definition of a footnote.
|
|
|
|
|
Match group 1 contains definition's label.")
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2015-02-03 15:16:54 +00:00
|
|
|
|
(defconst org-footnote-forbidden-blocks '("comment" "example" "export" "src")
|
2011-07-10 08:33:25 +00:00
|
|
|
|
"Names of blocks where footnotes are not allowed.")
|
2011-07-08 13:48:07 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Customization
|
|
|
|
|
|
2010-04-27 06:11:03 +00:00
|
|
|
|
(defgroup org-footnote nil
|
2015-12-16 17:38:53 +00:00
|
|
|
|
"Footnotes in Org mode."
|
2010-04-27 06:11:03 +00:00
|
|
|
|
:tag "Org Footnote"
|
|
|
|
|
:group 'org)
|
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defcustom org-footnote-section "Footnotes"
|
2013-01-27 23:02:51 +00:00
|
|
|
|
"Outline heading containing footnote definitions.
|
|
|
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
2009-01-01 17:01:07 +00:00
|
|
|
|
This variable defines the place where Org puts the definition
|
2013-01-27 23:02:51 +00:00
|
|
|
|
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
|
org-element: Implement caching for dynamic parser
* lisp/org-element.el (org-element-use-cache, org-element--cache,
org-element--cache-sync-idle-time,
org-element--cache-merge-changes-threshold, org-element--cache-status,
org-element--cache-opening-line, org-element--cache-closing-line): New
variables.
(org-element-cache-reset, org-element--cache-pending-changes-p,
org-element--cache-push-change, org-element--cache-cancel-changes,
org-element--cache-get-key, org-element-cache-get,
org-element-cache-put, org-element--shift-positions,
org-element--cache-before-change, org-element--cache-record-change,
org-element--cache-sync): New functions.
(org-element-at-point, org-element-context): Use cache when possible.
* lisp/org.el (org-mode, org-set-modules): Reset cache.
* lisp/org-footnote.el (org-footnote-section): Reset cache.
* testing/lisp/test-org-element.el: Update tests.
This patch gives a boost to `org-element-at-point' and, to a lesser
extent, to `org-element-context'.
2013-10-27 10:09:17 +00:00
|
|
|
|
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:
|
|
|
|
|
|
2016-10-15 15:36:47 +00:00
|
|
|
|
`\\[universal-argument] \\[org-element-cache-reset]'"
|
2010-04-27 06:11:03 +00:00
|
|
|
|
:group 'org-footnote
|
2013-11-17 08:52:54 +00:00
|
|
|
|
:initialize 'custom-initialize-default
|
2013-11-05 19:47:29 +00:00
|
|
|
|
:set (lambda (var val)
|
|
|
|
|
(set var val)
|
|
|
|
|
(when (fboundp 'org-element-cache-reset)
|
|
|
|
|
(org-element-cache-reset 'all)))
|
2009-01-01 17:01:07 +00:00
|
|
|
|
:type '(choice
|
2009-11-12 12:39:29 +00:00
|
|
|
|
(string :tag "Collect footnotes under heading")
|
2018-05-27 16:33:44 +00:00
|
|
|
|
(const :tag "Define footnotes locally" nil))
|
2018-05-27 19:59:19 +00:00
|
|
|
|
:safe #'string-or-null-p)
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
|
|
|
|
(defcustom org-footnote-define-inline nil
|
2010-01-21 15:15:40 +00:00
|
|
|
|
"Non-nil means define footnotes inline, at reference location.
|
2008-12-30 22:40:14 +00:00
|
|
|
|
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
|
2018-05-27 16:33:44 +00:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:safe #'booleanp)
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2009-01-02 14:53:02 +00:00
|
|
|
|
(defcustom org-footnote-auto-label t
|
2010-01-21 15:15:40 +00:00
|
|
|
|
"Non-nil means define automatically new labels for footnotes.
|
2009-01-02 14:53:02 +00:00
|
|
|
|
Possible values are:
|
|
|
|
|
|
2013-04-08 16:08:03 +00:00
|
|
|
|
nil Prompt the user for each label.
|
|
|
|
|
t Create unique labels of the form [fn:1], [fn:2], etc.
|
|
|
|
|
confirm Like t, but let the user edit the created value.
|
|
|
|
|
The label can be removed from the minibuffer to create
|
2009-01-02 14:53:02 +00:00
|
|
|
|
an anonymous footnote.
|
2015-12-16 17:38:53 +00:00
|
|
|
|
random Automatically generate a unique, random label."
|
2009-01-02 14:53:02 +00:00
|
|
|
|
:group 'org-footnote
|
|
|
|
|
:type '(choice
|
2009-11-12 12:39:29 +00:00
|
|
|
|
(const :tag "Prompt for label" nil)
|
2009-01-02 14:53:02 +00:00
|
|
|
|
(const :tag "Create automatic [fn:N]" t)
|
|
|
|
|
(const :tag "Offer automatic [fn:N] for editing" confirm)
|
2018-05-27 16:33:44 +00:00
|
|
|
|
(const :tag "Create a random label" random))
|
2018-05-28 19:18:28 +00:00
|
|
|
|
:safe #'symbolp)
|
2009-01-02 14:53:02 +00:00
|
|
|
|
|
2009-07-03 15:56:47 +00:00
|
|
|
|
(defcustom org-footnote-auto-adjust nil
|
2010-01-21 15:15:40 +00:00
|
|
|
|
"Non-nil means automatically adjust footnotes after insert/delete.
|
2009-07-03 15:56:47 +00:00
|
|
|
|
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
|
2013-05-09 13:19:02 +00:00
|
|
|
|
(const :tag "No adjustment" nil)
|
2009-07-03 15:56:47 +00:00
|
|
|
|
(const :tag "Renumber" renumber)
|
|
|
|
|
(const :tag "Sort" sort)
|
2018-05-27 16:33:44 +00:00
|
|
|
|
(const :tag "Renumber and Sort" t))
|
2018-05-28 19:18:28 +00:00
|
|
|
|
:safe #'symbolp)
|
2009-07-03 15:56:47 +00:00
|
|
|
|
|
2009-01-03 08:03:04 +00:00
|
|
|
|
(defcustom org-footnote-fill-after-inline-note-extraction nil
|
2010-01-21 15:15:40 +00:00
|
|
|
|
"Non-nil means fill paragraphs after extracting footnotes.
|
2009-01-03 08:03:04 +00:00
|
|
|
|
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
|
2018-05-27 16:33:44 +00:00
|
|
|
|
:type 'boolean
|
|
|
|
|
:safe #'booleanp)
|
2009-01-03 08:03:04 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Predicates
|
|
|
|
|
|
2011-07-08 13:48:07 +00:00
|
|
|
|
(defun org-footnote-in-valid-context-p ()
|
|
|
|
|
"Is point in a context where footnotes are allowed?"
|
2011-07-19 10:51:23 +00:00
|
|
|
|
(save-match-data
|
2015-12-05 15:40:36 +00:00
|
|
|
|
(not (or (org-at-comment-p)
|
|
|
|
|
(org-inside-LaTeX-fragment-p)
|
|
|
|
|
;; Avoid literal example.
|
|
|
|
|
(org-in-verbatim-emphasis)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(beginning-of-line)
|
|
|
|
|
(looking-at "[ \t]*:[ \t]+"))
|
|
|
|
|
;; Avoid forbidden blocks.
|
|
|
|
|
(org-in-block-p org-footnote-forbidden-blocks)))))
|
2011-07-08 13:48:07 +00:00
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defun org-footnote-at-reference-p ()
|
2018-06-24 12:57:49 +00:00
|
|
|
|
"Non-nil if point is at a footnote reference.
|
2011-07-14 07:49:50 +00:00
|
|
|
|
If so, return a list containing its label, beginning and ending
|
2018-06-24 12:57:49 +00:00
|
|
|
|
positions, and the definition, when inline."
|
|
|
|
|
(let ((reference (org-element-context)))
|
|
|
|
|
(when (eq 'footnote-reference (org-element-type reference))
|
|
|
|
|
(let ((end (save-excursion
|
|
|
|
|
(goto-char (org-element-property :end reference))
|
|
|
|
|
(skip-chars-backward " \t")
|
|
|
|
|
(point))))
|
|
|
|
|
(when (< (point) end)
|
|
|
|
|
(list (org-element-property :label reference)
|
|
|
|
|
(org-element-property :begin reference)
|
|
|
|
|
end
|
|
|
|
|
(and (eq 'inline (org-element-property :type reference))
|
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
|
(org-element-property :contents-begin reference)
|
|
|
|
|
(org-element-property :contents-end
|
|
|
|
|
reference)))))))))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
|
|
|
|
(defun org-footnote-at-definition-p ()
|
2018-06-24 12:57:49 +00:00
|
|
|
|
"Non-nil if point is within a footnote definition.
|
2011-04-29 13:46:25 +00:00
|
|
|
|
|
2018-06-24 12:57:49 +00:00
|
|
|
|
This matches only pure definitions like [fn:name] at the
|
2012-02-02 08:08:28 +00:00
|
|
|
|
beginning of a line. It does not match references like
|
|
|
|
|
\[fn:name:definition], where the footnote text is included and
|
|
|
|
|
defined locally.
|
2011-04-29 13:46:25 +00:00
|
|
|
|
|
2018-06-24 12:57:49 +00:00
|
|
|
|
The return value is nil if not at a footnote definition, and
|
2012-02-02 08:08:28 +00:00
|
|
|
|
a list with label, start, end and definition of the footnote
|
|
|
|
|
otherwise."
|
2018-06-24 12:57:49 +00:00
|
|
|
|
(pcase (org-element-lineage (org-element-at-point) '(footnote-definition) t)
|
|
|
|
|
(`nil nil)
|
|
|
|
|
(definition
|
|
|
|
|
(let* ((label (org-element-property :label definition))
|
|
|
|
|
(begin (org-element-property :post-affiliated definition))
|
|
|
|
|
(end (save-excursion
|
|
|
|
|
(goto-char (org-element-property :end definition))
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(line-beginning-position 2)))
|
|
|
|
|
(contents-begin (org-element-property :contents-begin definition))
|
|
|
|
|
(contents-end (org-element-property :contents-end definition))
|
|
|
|
|
(contents
|
|
|
|
|
(if (not contents-begin) ""
|
|
|
|
|
(org-trim
|
|
|
|
|
(buffer-substring-no-properties contents-begin
|
|
|
|
|
contents-end)))))
|
|
|
|
|
(list label begin end contents)))))
|
2011-04-29 13:46:25 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Internal functions
|
|
|
|
|
|
|
|
|
|
(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))
|
2016-10-28 22:38:15 +00:00
|
|
|
|
(and (save-excursion
|
|
|
|
|
(beginning-of-line)
|
|
|
|
|
(and (let ((case-fold-search t))
|
|
|
|
|
(not (looking-at-p "\\*+ END[ \t]*$")))
|
|
|
|
|
(let ((case-fold-search nil))
|
|
|
|
|
(looking-at org-complex-heading-regexp))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(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")
|
2016-10-25 11:13:26 +00:00
|
|
|
|
(if (eq (org-element-class context) 'object) (point)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(1+ (line-beginning-position 2))))))
|
|
|
|
|
;; Other elements are invalid.
|
2016-10-25 11:13:26 +00:00
|
|
|
|
((eq (org-element-class context) 'element) nil)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;; 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--clear-footnote-section ()
|
|
|
|
|
"Remove all footnote sections in buffer and create a new one.
|
2017-12-18 14:50:51 +00:00
|
|
|
|
New section is created at the end of the buffer. Leave point
|
|
|
|
|
within the new section."
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(when org-footnote-section
|
|
|
|
|
(goto-char (point-min))
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(let ((regexp (format "^\\*+ +%s[ \t]*$"
|
|
|
|
|
(regexp-quote org-footnote-section))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(while (re-search-forward regexp nil t)
|
|
|
|
|
(delete-region
|
|
|
|
|
(match-beginning 0)
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(org-end-of-subtree t t))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(goto-char (point-max))
|
2017-12-18 14:50:51 +00:00
|
|
|
|
;; Clean-up blank lines at the end of the buffer.
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(unless (bobp)
|
|
|
|
|
(forward-line)
|
|
|
|
|
(when (eolp) (insert "\n")))
|
|
|
|
|
(delete-region (point) (point-max))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(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")))
|
|
|
|
|
|
|
|
|
|
(defun org-footnote--set-label (label)
|
|
|
|
|
"Set label of footnote at point to string LABEL.
|
|
|
|
|
Assume point is at the beginning of the reference or definition
|
|
|
|
|
to rename."
|
|
|
|
|
(forward-char 4)
|
|
|
|
|
(cond ((eq (char-after) ?:) (insert label))
|
|
|
|
|
((looking-at "\\([-_[:word:]]+\\)") (replace-match label nil nil nil 1))
|
|
|
|
|
(t nil)))
|
|
|
|
|
|
|
|
|
|
(defun org-footnote--collect-references (&optional anonymous)
|
2017-07-06 07:23:30 +00:00
|
|
|
|
"Collect all labeled footnote references in current buffer.
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
Return an alist where associations follow the pattern
|
|
|
|
|
|
2016-10-31 09:01:33 +00:00
|
|
|
|
(LABEL MARKER TOP-LEVEL SIZE)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
with
|
|
|
|
|
|
|
|
|
|
LABEL the label of the of the definition,
|
|
|
|
|
MARKER a marker pointing to its beginning,
|
2015-12-29 20:38:57 +00:00
|
|
|
|
TOP-LEVEL a boolean, nil when the footnote is contained within
|
2015-12-16 17:38:53 +00:00
|
|
|
|
another one,
|
|
|
|
|
SIZE the length of the inline definition, in characters,
|
|
|
|
|
or nil for non-inline references.
|
|
|
|
|
|
|
|
|
|
When optional ANONYMOUS is non-nil, also collect anonymous
|
|
|
|
|
references. In such cases, LABEL is nil.
|
|
|
|
|
|
|
|
|
|
References are sorted according to a deep-reading order."
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(goto-char (point-min))
|
2015-12-17 13:33:33 +00:00
|
|
|
|
(let ((regexp (if anonymous org-footnote-re "\\[fn:[-_[:word:]]+[]:]"))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
references nested)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(while (re-search-forward regexp nil t)
|
2015-12-17 13:33:33 +00:00
|
|
|
|
;; Ignore definitions.
|
|
|
|
|
(unless (and (eq (char-before) ?\])
|
|
|
|
|
(= (line-beginning-position) (match-beginning 0)))
|
|
|
|
|
;; Ensure point is within the reference before parsing it.
|
|
|
|
|
(backward-char)
|
|
|
|
|
(let ((object (org-element-context)))
|
|
|
|
|
(when (eq (org-element-type object) 'footnote-reference)
|
|
|
|
|
(let* ((label (org-element-property :label object))
|
|
|
|
|
(begin (org-element-property :begin object))
|
|
|
|
|
(size
|
|
|
|
|
(and (eq (org-element-property :type object) 'inline)
|
|
|
|
|
(- (org-element-property :contents-end object)
|
|
|
|
|
(org-element-property :contents-begin object)))))
|
|
|
|
|
(let ((d (org-element-lineage object '(footnote-definition))))
|
|
|
|
|
(push (list label (copy-marker begin) (not d) size)
|
|
|
|
|
references)
|
|
|
|
|
(when d
|
|
|
|
|
;; Nested references are stored in alist NESTED.
|
|
|
|
|
;; Associations there follow the pattern
|
|
|
|
|
;;
|
|
|
|
|
;; (DEFINITION-LABEL . REFERENCES)
|
|
|
|
|
(let* ((def-label (org-element-property :label d))
|
|
|
|
|
(labels (assoc def-label nested)))
|
|
|
|
|
(if labels (push label (cdr labels))
|
|
|
|
|
(push (list def-label label) nested)))))))))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;; Sort the list of references. Nested footnotes have priority
|
|
|
|
|
;; over top-level ones.
|
|
|
|
|
(letrec ((ordered nil)
|
|
|
|
|
(add-reference
|
|
|
|
|
(lambda (ref allow-nested)
|
|
|
|
|
(when (or allow-nested (nth 2 ref))
|
|
|
|
|
(push ref ordered)
|
|
|
|
|
(dolist (r (mapcar (lambda (l) (assoc l references))
|
|
|
|
|
(reverse
|
|
|
|
|
(cdr (assoc (nth 0 ref) nested)))))
|
|
|
|
|
(funcall add-reference r t))))))
|
|
|
|
|
(dolist (r (reverse references) (nreverse ordered))
|
|
|
|
|
(funcall add-reference r nil))))))
|
|
|
|
|
|
|
|
|
|
(defun org-footnote--collect-definitions (&optional delete)
|
|
|
|
|
"Collect all footnote definitions in current buffer.
|
|
|
|
|
|
|
|
|
|
Return an alist where associations follow the pattern
|
|
|
|
|
|
2016-10-31 09:01:33 +00:00
|
|
|
|
(LABEL . DEFINITION)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
with LABEL and DEFINITION being, respectively, the label and the
|
|
|
|
|
definition of the footnote, as strings.
|
|
|
|
|
|
|
|
|
|
When optional argument DELETE is non-nil, delete the definition
|
|
|
|
|
while collecting them."
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(let (definitions seen)
|
|
|
|
|
(while (re-search-forward org-footnote-definition-re nil t)
|
|
|
|
|
(backward-char)
|
|
|
|
|
(let ((element (org-element-at-point)))
|
|
|
|
|
(let ((label (org-element-property :label element)))
|
|
|
|
|
(when (and (eq (org-element-type element) 'footnote-definition)
|
|
|
|
|
(not (member label seen)))
|
|
|
|
|
(push label seen)
|
|
|
|
|
(let* ((beg (progn
|
|
|
|
|
(goto-char (org-element-property :begin element))
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(if (bobp) (point) (line-beginning-position 2))))
|
|
|
|
|
(end (progn
|
|
|
|
|
(goto-char (org-element-property :end element))
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(line-beginning-position 2)))
|
|
|
|
|
(def (org-trim (buffer-substring-no-properties beg end))))
|
|
|
|
|
(push (cons label def) definitions)
|
|
|
|
|
(when delete (delete-region beg end)))))))
|
|
|
|
|
definitions)))
|
|
|
|
|
|
|
|
|
|
(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))
|
|
|
|
|
(skip-chars-backward " \t\n")
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(unless (bobp) (forward-line))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(unless (bolp) (insert "\n")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
;;;; Navigation
|
|
|
|
|
|
2011-04-30 11:23:21 +00:00
|
|
|
|
(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."
|
2018-06-24 12:57:49 +00:00
|
|
|
|
(let ((label-regexp (if label (format "\\[fn:%s[]:]" label) org-footnote-re)))
|
2018-06-24 09:10:06 +00:00
|
|
|
|
(catch :exit
|
|
|
|
|
(save-excursion
|
|
|
|
|
(while (funcall (if backward #'re-search-backward #'re-search-forward)
|
2018-06-24 12:57:49 +00:00
|
|
|
|
label-regexp limit t)
|
2011-04-30 11:23:21 +00:00
|
|
|
|
(unless backward (backward-char))
|
2018-06-24 12:57:49 +00:00
|
|
|
|
(pcase (org-footnote-at-reference-p)
|
|
|
|
|
(`nil nil)
|
|
|
|
|
(reference (throw :exit reference))))))))
|
2011-05-02 09:34:06 +00:00
|
|
|
|
|
|
|
|
|
(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'.
|
2018-06-24 12:57:49 +00:00
|
|
|
|
If no footnote is found, return nil.
|
|
|
|
|
|
|
|
|
|
This function is meant to be used for fontification only."
|
|
|
|
|
(let ((origin (point)))
|
2011-05-02 09:34:06 +00:00
|
|
|
|
(catch 'exit
|
|
|
|
|
(while t
|
|
|
|
|
(unless (re-search-forward org-footnote-re limit t)
|
2011-07-29 19:06:38 +00:00
|
|
|
|
(goto-char origin)
|
2011-05-02 09:34:06 +00:00
|
|
|
|
(throw 'exit nil))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;; Beware: with non-inline footnotes point will be just after
|
2011-06-28 23:14:13 +00:00
|
|
|
|
;; the closing square bracket.
|
|
|
|
|
(backward-char)
|
2011-05-02 09:34:06 +00:00
|
|
|
|
(cond
|
2018-06-24 12:57:49 +00:00
|
|
|
|
((and (/= (match-beginning 0) (line-beginning-position))
|
|
|
|
|
(let* ((beg (match-beginning 0))
|
|
|
|
|
(label (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))))
|
|
|
|
|
(and end
|
|
|
|
|
;; Verify match isn't a part of a link.
|
|
|
|
|
(not (save-excursion
|
|
|
|
|
(goto-char beg)
|
|
|
|
|
(let ((linkp
|
|
|
|
|
(save-match-data
|
2018-11-26 23:04:41 +00:00
|
|
|
|
(org-in-regexp org-link-bracket-re))))
|
2018-06-24 12:57:49 +00:00
|
|
|
|
(and linkp (< (point) (cdr linkp))))))
|
|
|
|
|
;; Verify point doesn't belong to a LaTeX macro.
|
|
|
|
|
(not (org-inside-latex-macro-p))
|
|
|
|
|
(throw 'exit
|
|
|
|
|
(list label beg end
|
|
|
|
|
;; Definition: ensure this is an
|
|
|
|
|
;; inline footnote first.
|
|
|
|
|
(and (match-end 2)
|
|
|
|
|
(org-trim
|
|
|
|
|
(buffer-substring-no-properties
|
|
|
|
|
(match-end 0) (1- end))))))))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
;; Definition: also grab the last square bracket, matched in
|
|
|
|
|
;; `org-footnote-re' for non-inline footnotes.
|
2018-06-24 12:57:49 +00:00
|
|
|
|
((and (save-excursion
|
|
|
|
|
(beginning-of-line)
|
|
|
|
|
(save-match-data (org-footnote-in-valid-context-p)))
|
|
|
|
|
(save-excursion
|
|
|
|
|
(end-of-line)
|
|
|
|
|
;; Footnotes definitions are separated by new
|
|
|
|
|
;; headlines, another footnote definition or 2 blank
|
|
|
|
|
;; lines.
|
2018-06-24 17:47:41 +00:00
|
|
|
|
(let ((end (match-end 0))
|
2018-06-24 12:57:49 +00:00
|
|
|
|
(lim (save-excursion
|
|
|
|
|
(re-search-backward
|
|
|
|
|
(concat org-outline-regexp-bol
|
|
|
|
|
"\\|^\\([ \t]*\n\\)\\{2,\\}")
|
|
|
|
|
nil t))))
|
|
|
|
|
(and (re-search-backward org-footnote-definition-re lim t)
|
|
|
|
|
(throw 'exit
|
|
|
|
|
(list nil
|
|
|
|
|
(match-beginning 0)
|
|
|
|
|
(if (eq (char-before end) ?\]) end
|
|
|
|
|
(1+ end)))))))))
|
|
|
|
|
(t nil))))))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2015-05-06 21:15:42 +00:00
|
|
|
|
(defun org-footnote-goto-definition (label &optional location)
|
2011-09-29 13:30:46 +00:00
|
|
|
|
"Move point to the definition of the footnote LABEL.
|
2015-05-06 21:15:42 +00:00
|
|
|
|
|
|
|
|
|
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."
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(interactive "sLabel: ")
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(let* ((label (org-footnote-normalize-label label))
|
|
|
|
|
(def-start (or location (nth 1 (org-footnote-get-definition label)))))
|
2015-04-25 08:36:35 +00:00
|
|
|
|
(cond
|
2015-04-25 09:09:05 +00:00
|
|
|
|
((not def-start)
|
|
|
|
|
(user-error "Cannot find definition of footnote %s" label))
|
2015-05-06 21:15:42 +00:00
|
|
|
|
((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)
|
2018-08-30 21:59:01 +00:00
|
|
|
|
(looking-at (format "\\[fn:%s[]:]" (regexp-quote label)))
|
2015-05-06 21:15:42 +00:00
|
|
|
|
(goto-char (match-end 0))
|
|
|
|
|
(org-show-context 'link-search)
|
|
|
|
|
(when (derived-mode-p 'org-mode)
|
2016-03-11 17:30:20 +00:00
|
|
|
|
(message "%s" (substitute-command-keys
|
|
|
|
|
"Edit definition and go back with \
|
|
|
|
|
`\\[org-mark-ring-goto]' or, if unique, with `\\[org-ctrl-c-ctrl-c]'.")))
|
2015-05-06 21:15:42 +00:00
|
|
|
|
t))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2010-04-14 14:45:45 +00:00
|
|
|
|
(defun org-footnote-goto-previous-reference (label)
|
2010-10-24 12:40:54 +00:00
|
|
|
|
"Find the first closest (to point) reference of footnote with label LABEL."
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(interactive "sLabel: ")
|
2018-06-24 09:10:06 +00:00
|
|
|
|
(let* ((label (org-footnote-normalize-label label))
|
|
|
|
|
(reference
|
|
|
|
|
(save-excursion
|
|
|
|
|
(or (org-footnote-get-next-reference label t)
|
|
|
|
|
(org-footnote-get-next-reference label)
|
|
|
|
|
(and (buffer-narrowed-p)
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(or (org-footnote-get-next-reference label t)
|
|
|
|
|
(org-footnote-get-next-reference label)))))))
|
|
|
|
|
(start (nth 1 reference)))
|
|
|
|
|
(cond ((not reference)
|
|
|
|
|
(user-error "Cannot find reference of footnote %S" label))
|
|
|
|
|
((or (> start (point-max)) (< start (point-min)))
|
|
|
|
|
(user-error "Reference is outside narrowed part of buffer")))
|
|
|
|
|
(org-mark-ring-push)
|
|
|
|
|
(goto-char start)
|
|
|
|
|
(org-show-context 'link-search)))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Getters
|
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defun org-footnote-normalize-label (label)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
"Return LABEL without \"fn:\" prefix.
|
|
|
|
|
If LABEL is the empty string or constituted of white spaces only,
|
|
|
|
|
return nil instead."
|
2016-09-05 22:08:32 +00:00
|
|
|
|
(pcase (org-trim label)
|
|
|
|
|
("" nil)
|
|
|
|
|
((pred (string-prefix-p "fn:")) (substring label 3))
|
2016-09-07 13:56:38 +00:00
|
|
|
|
(_ label)))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
(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 "^\\[fn:%s\\]\\|.\\[fn:%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-all-labels ()
|
|
|
|
|
"List all defined footnote labels used throughout the buffer.
|
|
|
|
|
This function ignores narrowing, if any."
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(let (all)
|
|
|
|
|
(while (re-search-forward org-footnote-re nil t)
|
|
|
|
|
(backward-char)
|
|
|
|
|
(let ((context (org-element-context)))
|
|
|
|
|
(when (memq (org-element-type context)
|
|
|
|
|
'(footnote-definition footnote-reference))
|
|
|
|
|
(let ((label (org-element-property :label context)))
|
|
|
|
|
(when label (cl-pushnew label all :test #'equal))))))
|
|
|
|
|
all)))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2009-01-02 14:53:02 +00:00
|
|
|
|
(defun org-footnote-unique-label (&optional current)
|
|
|
|
|
"Return a new unique footnote label.
|
2011-08-23 19:26:51 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
The function returns the first numeric label currently unused.
|
2011-08-23 19:26:51 +00:00
|
|
|
|
|
|
|
|
|
Optional argument CURRENT is the list of labels active in the
|
|
|
|
|
buffer."
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(let ((current (or current (org-footnote-all-labels))))
|
|
|
|
|
(let ((count 1))
|
|
|
|
|
(while (member (number-to-string count) current)
|
2015-12-29 20:36:30 +00:00
|
|
|
|
(cl-incf count))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(number-to-string count))))
|
2009-01-02 14:53:02 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Adding, Deleting Footnotes
|
2015-02-15 20:30:29 +00:00
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(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)
|
2015-02-15 20:30:29 +00:00
|
|
|
|
(unless (org-footnote--allow-reference-p)
|
|
|
|
|
(user-error "Cannot insert a footnote here"))
|
|
|
|
|
(let* ((all (org-footnote-all-labels))
|
2014-05-31 09:26:37 +00:00
|
|
|
|
(label
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(if (eq org-footnote-auto-label 'random)
|
2018-08-23 03:45:47 +00:00
|
|
|
|
(format "%x" (abs (random)))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(org-footnote-normalize-label
|
2015-02-15 20:30:29 +00:00
|
|
|
|
(let ((propose (org-footnote-unique-label all)))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(if (eq org-footnote-auto-label t) propose
|
|
|
|
|
(completing-read
|
2015-02-15 20:30:29 +00:00
|
|
|
|
"Label (leave empty for anonymous): "
|
|
|
|
|
(mapcar #'list all) nil nil
|
|
|
|
|
(and (eq org-footnote-auto-label 'confirm) propose))))))))
|
|
|
|
|
(cond ((not label)
|
2015-05-06 21:23:54 +00:00
|
|
|
|
(insert "[fn::]")
|
2015-02-15 20:30:29 +00:00
|
|
|
|
(backward-char 1))
|
|
|
|
|
((member label all)
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(insert "[fn:" label "]")
|
2015-02-15 20:30:29 +00:00
|
|
|
|
(message "New reference to existing note"))
|
|
|
|
|
(org-footnote-define-inline
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(insert "[fn:" label ":]")
|
2015-02-15 20:30:29 +00:00
|
|
|
|
(backward-char 1)
|
|
|
|
|
(org-footnote-auto-adjust-maybe))
|
|
|
|
|
(t
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(insert "[fn:" label "]")
|
2015-08-14 14:17:18 +00:00
|
|
|
|
(let ((p (org-footnote-create-definition label)))
|
|
|
|
|
;; `org-footnote-goto-definition' needs to be called
|
|
|
|
|
;; after `org-footnote-auto-adjust-maybe'. Otherwise
|
|
|
|
|
;; both label and location of the definition are lost.
|
|
|
|
|
;; On the contrary, it needs to be called before
|
|
|
|
|
;; `org-edit-footnote-reference' so that the remote
|
|
|
|
|
;; editing buffer can display the correct label.
|
|
|
|
|
(if (ignore-errors (org-footnote-goto-definition label p))
|
|
|
|
|
(org-footnote-auto-adjust-maybe)
|
|
|
|
|
;; Definition was created outside current scope: edit
|
|
|
|
|
;; it remotely.
|
|
|
|
|
(org-footnote-auto-adjust-maybe)
|
|
|
|
|
(org-edit-footnote-reference)))))))
|
2015-05-06 21:23:54 +00:00
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defun org-footnote-create-definition (label)
|
2015-05-06 21:23:54 +00:00
|
|
|
|
"Start the definition of a footnote with label LABEL.
|
2015-12-20 10:37:28 +00:00
|
|
|
|
Return buffer position at the beginning of the definition. This
|
|
|
|
|
function doesn't move point."
|
2013-01-26 12:40:18 +00:00
|
|
|
|
(let ((label (org-footnote-normalize-label label))
|
2015-05-06 21:23:54 +00:00
|
|
|
|
electric-indent-mode) ; Prevent wrong indentation.
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(org-preserve-local-variables
|
|
|
|
|
(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))
|
2021-01-09 16:50:50 +00:00
|
|
|
|
(org-end-of-meta-data t)
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(unless (bolp) (insert "\n")))
|
|
|
|
|
(t (org-footnote--clear-footnote-section)))
|
|
|
|
|
(when (zerop (org-back-over-empty-lines)) (insert "\n"))
|
|
|
|
|
(insert "[fn:" label "] \n")
|
|
|
|
|
(line-beginning-position 0)))))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2011-04-30 11:23:21 +00:00
|
|
|
|
(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))
|
2015-12-29 20:36:30 +00:00
|
|
|
|
(cl-incf nref))
|
2011-04-30 11:23:21 +00:00
|
|
|
|
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))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(let ((def-re (format "^\\[fn:%s\\]" (regexp-quote label)))
|
2011-04-30 11:23:21 +00:00
|
|
|
|
(ndef 0))
|
|
|
|
|
(while (re-search-forward def-re nil t)
|
2017-06-09 07:43:49 +00:00
|
|
|
|
(pcase (org-footnote-at-definition-p)
|
|
|
|
|
(`(,_ ,start ,end ,_)
|
|
|
|
|
;; Remove the footnote, and all blank lines before it.
|
|
|
|
|
(delete-region (progn
|
|
|
|
|
(goto-char start)
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(if (bobp) (point) (line-beginning-position 2)))
|
|
|
|
|
(progn
|
|
|
|
|
(goto-char end)
|
|
|
|
|
(skip-chars-backward " \r\t\n")
|
|
|
|
|
(if (bobp) (point) (line-beginning-position 2))))
|
|
|
|
|
(cl-incf ndef))))
|
2011-04-30 11:23:21 +00:00
|
|
|
|
ndef)))
|
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(defun org-footnote-delete (&optional label)
|
|
|
|
|
"Delete the footnote at point.
|
|
|
|
|
This will remove the definition (even multiple definitions if they exist)
|
2011-04-29 13:46:25 +00:00
|
|
|
|
and all references of a footnote label.
|
|
|
|
|
|
|
|
|
|
If LABEL is non-nil, delete that footnote instead."
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(catch 'done
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(org-preserve-local-variables
|
|
|
|
|
(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)))))
|
2008-12-30 22:40:14 +00:00
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; Sorting, Renumbering, Normalizing
|
|
|
|
|
|
2009-07-01 08:37:03 +00:00
|
|
|
|
(defun org-footnote-renumber-fn:N ()
|
2015-12-16 17:38:53 +00:00
|
|
|
|
"Order numbered footnotes into a sequence in the document."
|
2009-07-01 08:37:03 +00:00
|
|
|
|
(interactive)
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(let* ((c 0)
|
|
|
|
|
(references (cl-remove-if-not
|
|
|
|
|
(lambda (r) (string-match-p "\\`[0-9]+\\'" (car r)))
|
|
|
|
|
(org-footnote--collect-references)))
|
|
|
|
|
(alist (mapcar (lambda (l) (cons l (number-to-string (cl-incf c))))
|
|
|
|
|
(delete-dups (mapcar #'car references)))))
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
;; Re-number references.
|
|
|
|
|
(dolist (ref references)
|
|
|
|
|
(goto-char (nth 1 ref))
|
|
|
|
|
(org-footnote--set-label (cdr (assoc (nth 0 ref) alist))))
|
|
|
|
|
;; Re-number definitions.
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(while (re-search-forward "^\\[fn:\\([0-9]+\\)\\]" nil t)
|
|
|
|
|
(replace-match (or (cdr (assoc (match-string 1) alist))
|
|
|
|
|
;; Un-referenced definitions get higher
|
|
|
|
|
;; numbers.
|
|
|
|
|
(number-to-string (cl-incf c)))
|
|
|
|
|
nil nil nil 1)))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
(defun org-footnote-sort ()
|
|
|
|
|
"Rearrange footnote definitions in the current buffer.
|
|
|
|
|
Sort footnote definitions so they match order of footnote
|
|
|
|
|
references. Also relocate definitions at the end of their
|
|
|
|
|
relative section or within a single footnote section, according
|
|
|
|
|
to `org-footnote-section'. Inline definitions are ignored."
|
|
|
|
|
(let ((references (org-footnote--collect-references)))
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(org-preserve-local-variables
|
|
|
|
|
(let ((definitions (org-footnote--collect-definitions 'delete)))
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
(org-footnote--clear-footnote-section)
|
|
|
|
|
;; Insert footnote definitions at the appropriate location,
|
|
|
|
|
;; separated by a blank line. Each definition is inserted
|
|
|
|
|
;; only once throughout the buffer.
|
|
|
|
|
(let (inserted)
|
|
|
|
|
(dolist (cell references)
|
|
|
|
|
(let ((label (car cell))
|
|
|
|
|
(nested (not (nth 2 cell)))
|
|
|
|
|
(inline (nth 3 cell)))
|
|
|
|
|
(unless (or (member label inserted) inline)
|
|
|
|
|
(push label inserted)
|
|
|
|
|
(unless (or org-footnote-section nested)
|
|
|
|
|
;; If `org-footnote-section' is non-nil, or
|
|
|
|
|
;; reference is nested, point is already at the
|
|
|
|
|
;; correct position. Otherwise, move at the
|
|
|
|
|
;; appropriate location within the section
|
|
|
|
|
;; containing the reference.
|
|
|
|
|
(goto-char (nth 1 cell))
|
|
|
|
|
(org-footnote--goto-local-insertion-point))
|
|
|
|
|
(insert "\n"
|
|
|
|
|
(or (cdr (assoc label definitions))
|
|
|
|
|
(format "[fn:%s] DEFINITION NOT FOUND." label))
|
|
|
|
|
"\n"))))
|
|
|
|
|
;; Insert un-referenced footnote definitions at the end.
|
|
|
|
|
(pcase-dolist (`(,label . ,definition) definitions)
|
|
|
|
|
(unless (member label inserted)
|
|
|
|
|
(insert "\n" definition "\n")))))))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
(defun org-footnote-normalize ()
|
|
|
|
|
"Turn every footnote in buffer into a numbered one."
|
|
|
|
|
(interactive)
|
2017-12-18 14:50:51 +00:00
|
|
|
|
(org-preserve-local-variables
|
|
|
|
|
(let ((n 0)
|
|
|
|
|
(translations nil)
|
|
|
|
|
(definitions nil)
|
|
|
|
|
(references (org-footnote--collect-references 'anonymous)))
|
|
|
|
|
(org-with-wide-buffer
|
|
|
|
|
;; Update label for reference. We need to do this before
|
|
|
|
|
;; clearing definitions in order to rename nested footnotes
|
|
|
|
|
;; before they are deleted.
|
|
|
|
|
(dolist (cell references)
|
|
|
|
|
(let* ((label (car cell))
|
|
|
|
|
(anonymous (not label))
|
|
|
|
|
(new
|
|
|
|
|
(cond
|
|
|
|
|
;; In order to differentiate anonymous references
|
|
|
|
|
;; from regular ones, set their labels to integers,
|
|
|
|
|
;; not strings.
|
|
|
|
|
(anonymous (setcar cell (cl-incf n)))
|
|
|
|
|
((cdr (assoc label translations)))
|
|
|
|
|
(t (let ((l (number-to-string (cl-incf n))))
|
|
|
|
|
(push (cons label l) translations)
|
|
|
|
|
l)))))
|
|
|
|
|
(goto-char (nth 1 cell)) ; Move to reference's start.
|
|
|
|
|
(org-footnote--set-label
|
|
|
|
|
(if anonymous (number-to-string new) new))
|
|
|
|
|
(let ((size (nth 3 cell)))
|
|
|
|
|
;; Transform inline footnotes into regular references and
|
|
|
|
|
;; retain their definition for later insertion as
|
|
|
|
|
;; a regular footnote definition.
|
|
|
|
|
(when size
|
|
|
|
|
(let ((def (concat
|
|
|
|
|
(format "[fn:%s] " new)
|
|
|
|
|
(org-trim
|
|
|
|
|
(substring
|
|
|
|
|
(delete-and-extract-region
|
|
|
|
|
(point) (+ (point) size 1))
|
|
|
|
|
1)))))
|
|
|
|
|
(push (cons (if anonymous new label) def) definitions)
|
|
|
|
|
(when org-footnote-fill-after-inline-note-extraction
|
|
|
|
|
(org-fill-paragraph)))))))
|
|
|
|
|
;; Collect definitions. Update labels according to ALIST.
|
|
|
|
|
(let ((definitions
|
|
|
|
|
(nconc definitions
|
|
|
|
|
(org-footnote--collect-definitions 'delete)))
|
|
|
|
|
(inserted))
|
|
|
|
|
(org-footnote--clear-footnote-section)
|
|
|
|
|
(dolist (cell references)
|
|
|
|
|
(let* ((label (car cell))
|
|
|
|
|
(anonymous (integerp label))
|
|
|
|
|
(pos (nth 1 cell)))
|
|
|
|
|
;; Move to appropriate location, if required. When there
|
|
|
|
|
;; is a footnote section or reference is nested, point is
|
|
|
|
|
;; already at the expected location.
|
|
|
|
|
(unless (or org-footnote-section (not (nth 2 cell)))
|
|
|
|
|
(goto-char pos)
|
|
|
|
|
(org-footnote--goto-local-insertion-point))
|
|
|
|
|
;; Insert new definition once label is updated.
|
|
|
|
|
(unless (member label inserted)
|
|
|
|
|
(push label inserted)
|
|
|
|
|
(let ((stored (cdr (assoc label definitions)))
|
|
|
|
|
;; Anonymous footnotes' label is already
|
|
|
|
|
;; up-to-date.
|
|
|
|
|
(new (if anonymous label
|
|
|
|
|
(cdr (assoc label translations)))))
|
|
|
|
|
(insert "\n"
|
|
|
|
|
(cond
|
|
|
|
|
((not stored)
|
|
|
|
|
(format "[fn:%s] DEFINITION NOT FOUND." new))
|
|
|
|
|
(anonymous stored)
|
|
|
|
|
(t
|
|
|
|
|
(replace-regexp-in-string
|
|
|
|
|
"\\`\\[fn:\\(.*?\\)\\]" new stored nil nil 1)))
|
|
|
|
|
"\n")))))
|
|
|
|
|
;; Insert un-referenced footnote definitions at the end.
|
|
|
|
|
(pcase-dolist (`(,label . ,definition) definitions)
|
|
|
|
|
(unless (member label inserted)
|
|
|
|
|
(insert "\n"
|
|
|
|
|
(replace-regexp-in-string org-footnote-definition-re
|
|
|
|
|
(format "[fn:%d]" (cl-incf n))
|
|
|
|
|
definition)
|
|
|
|
|
"\n"))))))))
|
2009-07-01 08:37:03 +00:00
|
|
|
|
|
2009-07-03 15:56:47 +00:00
|
|
|
|
(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))
|
2011-04-29 13:46:25 +00:00
|
|
|
|
(let ((label (car (org-footnote-at-definition-p))))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(org-footnote-sort)
|
2009-07-03 15:56:47 +00:00
|
|
|
|
(when label
|
|
|
|
|
(goto-char (point-min))
|
2015-12-16 17:38:53 +00:00
|
|
|
|
(and (re-search-forward (format "^\\[fn:%s\\]" (regexp-quote label))
|
2009-07-03 15:56:47 +00:00
|
|
|
|
nil t)
|
2009-08-03 15:30:30 +00:00
|
|
|
|
(progn (insert " ")
|
2009-07-03 15:56:47 +00:00
|
|
|
|
(just-one-space)))))))
|
|
|
|
|
|
2015-12-16 17:38:53 +00:00
|
|
|
|
|
|
|
|
|
;;;; End-user interface
|
|
|
|
|
|
|
|
|
|
;;;###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 if possible.
|
|
|
|
|
|
|
|
|
|
With prefix arg SPECIAL, or when no footnote can be created,
|
|
|
|
|
offer additional commands in a menu."
|
|
|
|
|
(interactive "P")
|
|
|
|
|
(let* ((context (and (not special) (org-element-context)))
|
|
|
|
|
(type (org-element-type context)))
|
|
|
|
|
(cond
|
|
|
|
|
;; On white space after element, insert a new footnote.
|
|
|
|
|
((and context
|
|
|
|
|
(> (point)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(goto-char (org-element-property :end context))
|
|
|
|
|
(skip-chars-backward " \t")
|
|
|
|
|
(point))))
|
|
|
|
|
(org-footnote-new))
|
|
|
|
|
((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]ormalize | \
|
|
|
|
|
\[d]elete")
|
|
|
|
|
(pcase (read-char-exclusive)
|
|
|
|
|
(?s (org-footnote-sort))
|
|
|
|
|
(?r (org-footnote-renumber-fn:N))
|
|
|
|
|
(?S (org-footnote-renumber-fn:N)
|
|
|
|
|
(org-footnote-sort))
|
|
|
|
|
(?n (org-footnote-normalize))
|
|
|
|
|
(?d (org-footnote-delete))
|
|
|
|
|
(char (error "No such footnote command %c" char))))
|
|
|
|
|
(t (org-footnote-new)))))
|
|
|
|
|
|
|
|
|
|
|
2008-12-30 22:40:14 +00:00
|
|
|
|
(provide 'org-footnote)
|
|
|
|
|
|
2012-10-02 06:50:46 +00:00
|
|
|
|
;; Local variables:
|
|
|
|
|
;; generated-autoload-file: "org-loaddefs.el"
|
|
|
|
|
;; End:
|
|
|
|
|
|
2009-01-03 08:03:04 +00:00
|
|
|
|
;;; org-footnote.el ends here
|