X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/3ab2c837b302b01fff610f7b83050ab7e703477c..0e963201d03d9229bb8ac4323291d2b0119526ed:/lisp/org/org-footnote.el diff --git a/lisp/org/org-footnote.el b/lisp/org/org-footnote.el index 9a92bd5db1..a25afee97f 100644 --- a/lisp/org/org-footnote.el +++ b/lisp/org/org-footnote.el @@ -1,11 +1,10 @@ ;;; org-footnote.el --- Footnote support in Org and elsewhere ;; -;; Copyright (C) 2009, 2010 Free Software Foundation, Inc. +;; Copyright (C) 2009-2016 Free Software Foundation, Inc. ;; ;; Author: Carsten Dominik ;; Keywords: outlines, hypermedia, calendar, wp ;; Homepage: http://orgmode.org -;; Version: 7.7 ;; ;; This file is part of GNU Emacs. ;; @@ -38,28 +37,33 @@ (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-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-block-p "org" (names)) -(declare-function org-mark-ring-push "org" (&optional pos buffer)) -(declare-function outline-next-heading "outline") -(declare-function org-trim "org" (s)) -(declare-function org-show-context "org" (&optional key)) -(declare-function org-back-to-heading "org" (&optional invisible-ok)) -(declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (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-id-uuid "org" ()) -(declare-function org-fill-paragraph "org" (&optional justify)) -(declare-function org-export-preprocess-string "org-exp" - (string &rest parameters)) +(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-signature-separator) ;; defined in message.el +(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 @@ -67,21 +71,21 @@ ;; their definition. ;; ;; `org-re' is used for regexp compatibility with XEmacs. - (org-re (concat "\\[\\(?:" - ;; Match inline footnotes. - "fn:\\([-_[:word:]]+\\)?:\\|" - ;; Match other footnotes. - "\\(?:\\([0-9]+\\)\\]\\)\\|" - "\\(fn:[-_[:word:]]+\\)" - "\\)")) + (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:]]+\\)\\]\\)") + (org-re "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]") "Regular expression matching the definition of a footnote.") -(defvar org-footnote-forbidden-blocks '("example" "verse" "src" "ascii" "beamer" - "docbook" "html" "latex" "odt") +(defconst org-footnote-forbidden-blocks + '("ascii" "beamer" "comment" "example" "html" "latex" "odt" "src") "Names of blocks where footnotes are not allowed.") (defgroup org-footnote nil @@ -90,15 +94,19 @@ :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. + "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. + 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." +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 ignored." :group 'org-footnote :type '(choice (string :tag "Collect footnotes under heading") @@ -106,13 +114,17 @@ heading will be removed after extracting footnote definitions." (defcustom org-footnote-tag-for-non-org-mode-files "Footnotes:" "Tag marking the beginning of footnote section. -The Org-mode 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 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." +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 'string) + :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. @@ -126,13 +138,13 @@ will be used to define the footnote at the reference position." "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 +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 an anonymous footnote. random Automatically generate a unique, random label. -plain Automatically create plain number labels like [1]" +plain Automatically create plain number labels like [1]." :group 'org-footnote :type '(choice (const :tag "Prompt for label" nil) @@ -154,6 +166,7 @@ The main values of this variable can be set with in-buffer options: #+STARTUP: nofnadjust" :group 'org-footnote :type '(choice + (const :tag "No adjustment" nil) (const :tag "Renumber" renumber) (const :tag "Sort" sort) (const :tag "Renumber and Sort" t))) @@ -171,8 +184,9 @@ extracted will be filled again." (save-match-data (not (or (org-in-commented-line) (org-in-indented-comment-line) - (org-in-verbatim-emphasis) + (org-inside-LaTeX-fragment-p) ;; Avoid literal example. + (org-in-verbatim-emphasis) (save-excursion (beginning-of-line) (looking-at "[ \t]*:[ \t]+")) @@ -194,13 +208,13 @@ positions, and the definition, when inlined." (or (looking-at org-footnote-re) (org-in-regexp org-footnote-re) (save-excursion (re-search-backward org-footnote-re nil t))) - ;; Only inline footnotes can start at bol. - (or (eq (char-before (match-end 0)) 58) - (/= (match-beginning 0) (point-at-bol)))) + (/= (match-beginning 0) (point-at-bol))) (let* ((beg (match-beginning 0)) - (label (or (match-string 2) (match-string 3) + (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:" (match-string 1))))) + (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 @@ -217,55 +231,53 @@ positions, and the definition, when inlined." (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)) - (and (get-text-property (1- beg) 'org-protected) - (not (get-text-property beg 'org-protected))))) + (not (org-inside-latex-macro-p))) (list label beg end ;; Definition: ensure this is an inline footnote first. (and (or (not label) (match-string 1)) - (org-trim (buffer-substring (match-end 0) (1- end))))))))) + (org-trim (buffer-substring-no-properties + (match-end 0) (1- end))))))))) (defun org-footnote-at-definition-p () - "Is the cursor at a footnote definition? + "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. +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 (org-footnote-in-valid-context-p) +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) - (let ((lim (save-excursion (re-search-backward - (concat org-outline-regexp-bol - "\\|^[ \t]*$") nil t)))) + ;; Footnotes definitions are separated by new headlines, another + ;; footnote definition or 2 blank lines. + (let ((lim (save-excursion + (re-search-backward + (concat org-outline-regexp-bol + "\\|^\\([ \t]*\n\\)\\{2,\\}") nil t)))) (when (re-search-backward org-footnote-definition-re lim t) - (end-of-line) - (list (match-string 2) - (match-beginning 0) - (save-match-data - ;; In a message, limit search to signature. - (let ((bound (and (derived-mode-p 'message-mode) - (save-excursion - (goto-char (point-max)) - (re-search-backward - message-signature-separator nil t))))) - (or (and (re-search-forward - (org-re - (concat "^[ \t]*$" "\\|" - org-outline-regexp-bol - "\\|" - "^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]")) - bound 'move) - (progn (skip-chars-forward " \t\n") (point-at-bol))) - (point)))) - (org-trim (buffer-substring (match-end 0) (point))))))))) + (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]*\n\\)\\{2,\\}") 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. @@ -295,10 +307,11 @@ 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) + (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. @@ -320,19 +333,21 @@ If no footnote is found, return nil." (re (format "^\\[%s\\]\\|.\\[%s:" label label)) pos) (save-excursion - (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)))))))) + (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." + "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))) @@ -342,7 +357,9 @@ If no footnote is found, return nil." (looking-at (format "\\[%s\\]\\|\\[%s:" label label)) (goto-char (match-end 0)) (org-show-context 'link-search) - (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'.")))) + (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." @@ -406,7 +423,12 @@ and value definition." (defun org-footnote-unique-label (&optional current) "Return a new unique footnote label. -The returns the firsts fn:N labels that is currently not used." + +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)) @@ -414,21 +436,18 @@ The returns the firsts fn:N labels that is currently not used." (incf cnt)) (format fmt cnt))) -(defvar org-footnote-label-history nil - "History of footnote labels entered in current buffer.") -(make-variable-buffer-local 'org-footnote-label-history) - (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 (and (not (bolp)) (org-footnote-in-valid-context-p)) + (unless (org-footnote-in-valid-context-p) (error "Cannot insert a footnote here")) - (let* ((labels (and (not (equal org-footnote-auto-label 'random)) - (org-footnote-all-labels))) - (propose (org-footnote-unique-label labels)) + (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 @@ -438,16 +457,16 @@ or new, let the user edit the definition of the footnote." (require 'org-id) (substring (org-id-uuid) 0 8)) (t - (completing-read + (org-icompleting-read "Label (leave empty for anonymous): " - (mapcar 'list labels) nil nil - (if (eq org-footnote-auto-label 'confirm) propose nil) - 'org-footnote-label-history)))))) + (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 labels) + ((member label lbls) (insert "[" label "]") (message "New reference to existing note")) (org-footnote-define-inline @@ -459,51 +478,81 @@ or new, let the user edit the definition of the footnote." (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))) + (let ((label (org-footnote-normalize-label label)) + electric-indent-mode) ;; Prevent wrong indentation (cond - ((org-mode-p) - ;; No section, put footnote into the current outline node Try to - ;; find or make the special node + ;; 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)) - (insert "\n\n* " org-footnote-section "\n")))) - ;; Now go to the end of this entry and insert there. + (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 - ;; necessary (at the end of buffer, or before a signature if in + ;; specified (at the end of buffer, or before signature if in ;; Message mode). Set point after any definition already there. - (let ((tag (concat "^" org-footnote-tag-for-non-org-mode-files "[ \t]*$")) - (max (save-excursion - (if (and (derived-mode-p 'message-mode) - (re-search-forward - message-signature-separator nil t)) - (copy-marker (point-at-bol) t) - (copy-marker (point-max) t))))) + (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) - (unless (re-search-backward tag nil t) + ;; 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") - (delete-region (point) max) - (insert "\n\n" org-footnote-tag-for-non-org-mode-files "\n")) - ;; Skip existing footnotes. - (while (re-search-forward org-footnote-definition-re max t)) - (let ((def (org-footnote-at-definition-p))) - (when def (goto-char (nth 2 def)))) + (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, position point and notify user. - (unless (bolp) (insert "\n")) - (insert "\n[" label "] \n") + ;; Insert footnote label. + (when (zerop (org-back-over-empty-lines)) (newline)) + (insert "[" label "] \n") (backward-char) - (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))) + ;; 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) @@ -549,38 +598,15 @@ With prefix arg SPECIAL, offer additional commands in a menu." (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 nil) ; silence byte-compiler -(defvar org-export-footnotes-data nil) ; silence byte-compiler - ;;;###autoload -(defun org-footnote-normalize (&optional sort-only export-props) +(defun org-footnote-normalize (&optional sort-only) "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. +normalizes them to the usual [N] format. 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 like docbook, odt, etc. expect that footnote -definitions 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'." +referenced sequence." ;; This is based on Paul's function, but rewritten. ;; ;; Re-create `org-with-limited-levels', but not limited to Org @@ -590,17 +616,12 @@ Additional note on `org-footnote-insert-pos-for-preprocessor': 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))) + (if org-odd-levels-only (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) + (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 @@ -608,6 +629,7 @@ Additional note on `org-footnote-insert-pos-for-preprocessor': (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 @@ -621,77 +643,60 @@ Additional note on `org-footnote-insert-pos-for-preprocessor': ;; 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)) + (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)) + (insert (format "[%d]" marker)) (and inlinep org-footnote-fill-after-inline-note-extraction (org-fill-paragraph))) - ;; Add label (REF), identifier (MARKER) and definition (DEF) - ;; to REF-TABLE if data was unknown. + ;; 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))) + (let ((def (or (nth 3 ref) ; Inline definition. (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 export-props - (let ((parameters - (org-combine-plists - export-props - '(:todo-keywords t :tags t :priority t)))) - (org-export-preprocess-string def parameters)) - def) - inlinep) ref-table))) - ;; Remove definition of non-inlined footnotes. - (unless inlinep (org-footnote-delete-definitions 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). - (goto-char (point-min)) (cond - ((org-mode-p) - (if (and org-footnote-section - (re-search-forward - (concat "^\\*[ \t]+" (regexp-quote org-footnote-section) - "[ \t]*$") - nil t)) - (progn - (setq ins-point (match-beginning 0)) - (delete-region (match-beginning 0) (org-end-of-subtree t))) - (setq ins-point (point-max)))) + ((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 - (when (re-search-forward - (concat "^" - (regexp-quote org-footnote-tag-for-non-org-mode-files) - "[ \t]*$") - nil t) - (replace-match "")) - ;; In message-mode, ensure footnotes are inserted before the + ;; 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. - (let ((pt-max - (or (and (derived-mode-p 'message-mode) - (save-excursion - (goto-char (point-max)) - (re-search-backward - message-signature-separator nil t) - (1- (point)))) - (point-max)))) - (goto-char pt-max) - (skip-chars-backward " \t\n\r") - (forward-line) - (delete-region (point) pt-max)) - (setq ins-point (point)))) + (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 @@ -699,64 +704,69 @@ Additional note on `org-footnote-insert-pos-for-preprocessor': (lambda (x) (cond ;; When only sorting, ignore inline footnotes. - ((and sort-only (nth 3 x)) nil) + ;; 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 (butlast x 2) - (list (format "DEFINITION NOT FOUND: %s" (car x)) - (nth 3 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. Insert the footnotes again in the buffer, at the + ;; 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 (or - (and export-props - (eq org-footnote-insert-pos-for-preprocessor 'point-min) - (point-min)) - ins-point - (point-max))) + (goto-char ins-point) (cond ;; No footnote: exit. ((not ref-table)) ;; Cases when footnotes should be inserted in one place. - ((or (not (org-mode-p)) - org-footnote-section - (not sort-only)) - ;; Insert again the section title. - (cond - ((not (org-mode-p)) - (insert "\n\n" org-footnote-tag-for-non-org-mode-files "\n")) - ((and org-footnote-section (not export-props)) - (or (bolp) (insert "\n")) - (insert "* " org-footnote-section "\n"))) - ;; Insert the footnotes. - (insert "\n" - (mapconcat (lambda (x) (format "[%s] %s" - (nth (if sort-only 0 1) x) (nth 2 x))) - ref-table "\n\n") - "\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))) - ;; Else, insert each definition at the end of the section - ;; containing their first reference. Happens only in Org files - ;; with no special footnote section, and only when doing - ;; sorting. - (t (mapc 'org-insert-footnote-reference-near-definition - ref-table)))))) - -(defun org-insert-footnote-reference-near-definition (entry) - "Find first reference of footnote ENTRY and insert the definition there. -ENTRY is (fn-label num-mark definition)." - (when (car entry) - (goto-char (point-min)) - (let ((ref (org-footnote-get-next-reference (car entry)))) - (when ref - (goto-char (nth 2 ref)) - (org-footnote-goto-local-insertion-point) - (insert (format "\n[%s] %s\n" (car entry) (nth 2 entry))))))) + ((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 + (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." @@ -765,7 +775,7 @@ ENTRY is (fn-label num-mark definition)." (beginning-of-line 0) (while (and (not (bobp)) (= (char-after) ?#)) (beginning-of-line 0)) - (if (looking-at "[ \t]*#\\+TBLFM:") (beginning-of-line 2)) + (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)) @@ -791,8 +801,13 @@ Return the number of footnotes removed." (ndef 0)) (while (re-search-forward def-re nil t) (let ((full-def (org-footnote-at-definition-p))) - (delete-region (nth 1 full-def) (nth 2 full-def))) - (incf ndef)) + (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) @@ -807,7 +822,7 @@ If LABEL is non-nil, delete that footnote instead." (label (cond ;; LABEL is provided as argument. (label) - ;; Footnote reference at point. If the footnote is + ;; Footnote reference at point. If the footnote is ;; anonymous, delete it and exit instead. ((setq x (org-footnote-at-reference-p)) (or (car x) @@ -831,20 +846,21 @@ If LABEL is non-nil, delete that footnote instead." (defun org-footnote-renumber-fn:N () "Renumber the simple footnotes like fn:17 into a sequence in the document." (interactive) - (let (map i (n 0)) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (while (re-search-forward "\\[fn:\\([0-9]+\\)[]:]" nil t) - (setq i (string-to-number (match-string 1))) - (when (and (string-match "\\S-" (buffer-substring - (point-at-bol) (match-beginning 0))) - (not (assq i map))) - (push (cons i (number-to-string (incf n))) map))) - (goto-char (point-min)) - (while (re-search-forward "\\(\\[fn:\\)\\([0-9]+\\)\\([]:]\\)" nil t) - (replace-match (concat "\\1" (cdr (assq (string-to-number (match-string 2)) map)) "\\3"))))))) + (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." @@ -862,6 +878,8 @@ If LABEL is non-nil, delete that footnote instead." (provide 'org-footnote) -;; arch-tag: 1b5954df-fb5d-4da5-8709-78d944dbfc37 +;; Local variables: +;; generated-autoload-file: "org-loaddefs.el" +;; End: ;;; org-footnote.el ends here