diff options
Diffstat (limited to 'lisp/org/org-footnote.el')
-rw-r--r-- | lisp/org/org-footnote.el | 611 |
1 files changed, 310 insertions, 301 deletions
diff --git a/lisp/org/org-footnote.el b/lisp/org/org-footnote.el index f8963184654..0fe382819de 100644 --- a/lisp/org/org-footnote.el +++ b/lisp/org/org-footnote.el @@ -47,18 +47,16 @@ (declare-function org-end-of-subtree "org" (&optional invisible-ok to-heading)) (declare-function org-fill-paragraph "org" (&optional justify region)) (declare-function org-in-block-p "org" (names)) -(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 &optional keep-lead)) (declare-function outline-next-heading "outline") (defvar electric-indent-mode) (defvar org-blank-before-new-entry) ; defined in org.el -(defvar org-bracket-link-regexp) ; defined in org.el +(defvar org-link-bracket-re) ; defined in org.el (defvar org-complex-heading-regexp) ; defined in org.el (defvar org-odd-levels-only) ; defined in org.el (defvar org-outline-regexp) ; defined in org.el @@ -116,7 +114,8 @@ you will need to run the following command after the change: (org-element-cache-reset 'all))) :type '(choice (string :tag "Collect footnotes under heading") - (const :tag "Define footnotes locally" nil))) + (const :tag "Define footnotes locally" nil)) + :safe #'string-or-null-p) (defcustom org-footnote-define-inline nil "Non-nil means define footnotes inline, at reference location. @@ -124,7 +123,8 @@ When nil, footnotes will be defined in a special section near the end of the document. When t, the [fn:label:definition] notation will be used to define the footnote at the reference position." :group 'org-footnote - :type 'boolean) + :type 'boolean + :safe #'booleanp) (defcustom org-footnote-auto-label t "Non-nil means define automatically new labels for footnotes. @@ -141,7 +141,8 @@ random Automatically generate a unique, random label." (const :tag "Prompt for label" nil) (const :tag "Create automatic [fn:N]" t) (const :tag "Offer automatic [fn:N] for editing" confirm) - (const :tag "Create a random label" random))) + (const :tag "Create a random label" random)) + :safe #'symbolp) (defcustom org-footnote-auto-adjust nil "Non-nil means automatically adjust footnotes after insert/delete. @@ -159,7 +160,8 @@ The main values of this variable can be set with in-buffer options: (const :tag "No adjustment" nil) (const :tag "Renumber" renumber) (const :tag "Sort" sort) - (const :tag "Renumber and Sort" t))) + (const :tag "Renumber and Sort" t)) + :safe #'symbolp) (defcustom org-footnote-fill-after-inline-note-extraction nil "Non-nil means fill paragraphs after extracting footnotes. @@ -167,7 +169,8 @@ When extracting inline footnotes, the lengths of lines can change a lot. When this option is set, paragraphs from which an inline footnote has been extracted will be filled again." :group 'org-footnote - :type 'boolean) + :type 'boolean + :safe #'booleanp) ;;;; Predicates @@ -186,76 +189,53 @@ extracted will be filled again." (org-in-block-p org-footnote-forbidden-blocks))))) (defun org-footnote-at-reference-p () - "Is the cursor at a footnote reference? - + "Non-nil if point is at a footnote reference. If so, return a list containing its label, beginning and ending -positions, and the definition, when inlined." - (when (and (org-footnote-in-valid-context-p) - (or (looking-at org-footnote-re) - (org-in-regexp org-footnote-re) - (save-excursion (re-search-backward org-footnote-re nil t))) - (/= (match-beginning 0) (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)))) - ;; Point is really at a reference if it's located before true - ;; ending of the footnote. - (when (and end - (< (point) end) - ;; Verify match isn't a part of a link. - (not (save-excursion - (goto-char beg) - (let ((linkp - (save-match-data - (org-in-regexp org-bracket-link-regexp)))) - (and linkp (< (point) (cdr linkp)))))) - ;; Verify point doesn't belong to a LaTeX macro. - (not (org-inside-latex-macro-p))) - (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))))))))) +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))))))))) (defun org-footnote-at-definition-p () - "Is point within a footnote definition? + "Non-nil if point is within a footnote definition. -This matches only pure definitions like [1] or [fn:name] at the +This matches only pure definitions like [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 +The return value is nil if not at a footnote definition, and a list with label, start, end and definition of the footnote otherwise." - (when (save-excursion (beginning-of-line) (org-footnote-in-valid-context-p)) - (save-excursion - (end-of-line) - ;; Footnotes definitions are separated by new headlines, 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) - (let ((label (match-string-no-properties 1)) - (beg (match-beginning 0)) - (beg-def (match-end 0)) - (end (if (progn - (end-of-line) - (re-search-forward - (concat org-outline-regexp-bol "\\|" - org-footnote-definition-re "\\|" - "^\\([ \t]*\n\\)\\{2,\\}") nil 'move)) - (match-beginning 0) - (point)))) - (list label beg end - (org-trim (buffer-substring-no-properties beg-def end))))))))) + (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))))) ;;;; Internal functions @@ -313,23 +293,23 @@ otherwise." (defun org-footnote--clear-footnote-section () "Remove all footnote sections in buffer and create a new one. -New section is created at the end of the buffer, before any file -local variable definition. Leave point within the new section." +New section is created at the end of the buffer. Leave point +within the new section." (when org-footnote-section (goto-char (point-min)) - (let ((regexp - (format "^\\*+ +%s[ \t]*$" - (regexp-quote org-footnote-section)))) + (let ((regexp (format "^\\*+ +%s[ \t]*$" + (regexp-quote org-footnote-section)))) (while (re-search-forward regexp nil t) (delete-region (match-beginning 0) - (progn (org-end-of-subtree t t) - (if (not (eobp)) (point) - (org-footnote--goto-local-insertion-point) - (skip-chars-forward " \t\n") - (if (eobp) (point) (line-beginning-position))))))) + (org-end-of-subtree t t)))) (goto-char (point-max)) - (org-footnote--goto-local-insertion-point) + ;; 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)) (when (and (cdr (assq 'heading org-blank-before-new-entry)) (zerop (save-excursion (org-back-over-empty-lines)))) (insert "\n")) @@ -448,14 +428,8 @@ while collecting them." "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 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 (bobp) (forward-line)) (unless (bolp) (insert "\n"))) @@ -470,16 +444,15 @@ the buffer position bounding the search. Return value is a list like those provided by `org-footnote-at-reference-p'. If no footnote is found, return nil." - (save-excursion - (let* ((label-fmt (if label (format "\\[fn:%s[]:]" label) org-footnote-re))) - (catch 'exit - (while t - (unless (funcall (if backward #'re-search-backward #'re-search-forward) - label-fmt limit t) - (throw 'exit nil)) + (let ((label-regexp (if label (format "\\[fn:%s[]:]" label) org-footnote-re))) + (catch :exit + (save-excursion + (while (funcall (if backward #'re-search-backward #'re-search-forward) + label-regexp limit t) (unless backward (backward-char)) - (let ((ref (org-footnote-at-reference-p))) - (when ref (throw 'exit ref)))))))) + (pcase (org-footnote-at-reference-p) + (`nil nil) + (reference (throw :exit reference)))))))) (defun org-footnote-next-reference-or-definition (limit) "Move point to next footnote reference or definition. @@ -488,8 +461,10 @@ LIMIT is the buffer position bounding the search. Return value is a list like those provided by `org-footnote-at-reference-p' or `org-footnote-at-definition-p'. -If no footnote is found, return nil." - (let* (ref (origin (point))) +If no footnote is found, return nil. + +This function is meant to be used for fontification only." + (let ((origin (point))) (catch 'exit (while t (unless (re-search-forward org-footnote-re limit t) @@ -499,15 +474,56 @@ If no footnote is found, return nil." ;; the closing square bracket. (backward-char) (cond - ((setq ref (org-footnote-at-reference-p)) - (throw 'exit ref)) + ((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 + (org-in-regexp org-link-bracket-re)))) + (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)))))))))) ;; Definition: also grab the last square bracket, matched in ;; `org-footnote-re' for non-inline footnotes. - ((save-match-data (org-footnote-at-definition-p)) - (let ((end (match-end 0))) - (throw 'exit - (list nil (match-beginning 0) - (if (eq (char-before end) ?\]) end (1+ end))))))))))) + ((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. + (let ((end (match-end 0)) + (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)))))) (defun org-footnote-goto-definition (label &optional location) "Move point to the definition of the footnote LABEL. @@ -528,7 +544,7 @@ value if point was successfully moved." (user-error "Definition is outside narrowed part of buffer"))) (org-mark-ring-push) (goto-char def-start) - (looking-at (format "\\[fn:%s[]:] ?" (regexp-quote label))) + (looking-at (format "\\[fn:%s[]:]" (regexp-quote label))) (goto-char (match-end 0)) (org-show-context 'link-search) (when (derived-mode-p 'org-mode) @@ -540,21 +556,23 @@ value if point was successfully moved." (defun org-footnote-goto-previous-reference (label) "Find the first closest (to point) reference of footnote with label LABEL." (interactive "sLabel: ") - (org-mark-ring-push) - (let ((label (org-footnote-normalize-label label)) - ref) - (save-excursion - (setq ref (or (org-footnote-get-next-reference label t) - (org-footnote-get-next-reference label) - (save-restriction - (widen) - (or - (org-footnote-get-next-reference label t) - (org-footnote-get-next-reference label)))))) - (if (not ref) - (error "Cannot find reference of footnote %s" label) - (goto-char (nth 1 ref)) - (org-show-context 'link-search)))) + (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))) ;;;; Getters @@ -676,21 +694,22 @@ Return buffer position at the beginning of the definition. This function doesn't move point." (let ((label (org-footnote-normalize-label label)) electric-indent-mode) ; Prevent wrong indentation. - (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 (org-footnote--clear-footnote-section))) - (when (zerop (org-back-over-empty-lines)) (insert "\n")) - (insert "[fn:" label "] \n") - (line-beginning-position 0)))) + (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)) + (forward-line) + (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))))) (defun org-footnote-delete-references (label) "Delete every reference to footnote LABEL. @@ -733,31 +752,32 @@ and all references of a footnote label. If LABEL is non-nil, delete that footnote instead." (catch 'done - (let* ((nref 0) (ndef 0) x - ;; 1. Determine LABEL of footnote at point. - (label (cond - ;; LABEL is provided as argument. - (label) - ;; Footnote reference at point. If the footnote is - ;; anonymous, delete it and exit instead. - ((setq x (org-footnote-at-reference-p)) - (or (car x) - (progn - (delete-region (nth 1 x) (nth 2 x)) - (message "Anonymous footnote removed") - (throw 'done t)))) - ;; Footnote definition at point. - ((setq x (org-footnote-at-definition-p)) - (car x)) - (t (error "Don't know which footnote to remove"))))) - ;; 2. Now that LABEL is non-nil, find every reference and every - ;; definition, and delete them. - (setq nref (org-footnote-delete-references label) - ndef (org-footnote-delete-definitions label)) - ;; 3. Verify consistency of footnotes and notify user. - (org-footnote-auto-adjust-maybe) - (message "%d definition(s) of and %d reference(s) of footnote %s removed" - ndef nref label)))) + (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))))) ;;;; Sorting, Renumbering, Normalizing @@ -765,28 +785,25 @@ If LABEL is non-nil, delete that footnote instead." (defun org-footnote-renumber-fn:N () "Order numbered footnotes into a sequence in the document." (interactive) - (let ((references (org-footnote--collect-references))) - (unwind-protect - (let* ((c 0) - (references (cl-remove-if-not - (lambda (r) (string-match-p "\\`[0-9]+\\'" (car r))) - 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)))) - (dolist (r references) (set-marker (nth 1 r) nil))))) + (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))))) (defun org-footnote-sort () "Rearrange footnote definitions in the current buffer. @@ -795,129 +812,121 @@ 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))) - (unwind-protect - (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. - (let ((unreferenced - (cl-remove-if (lambda (d) (member (car d) inserted)) - definitions))) - (dolist (d unreferenced) (insert "\n" (cdr d) "\n")))))) - ;; Clear dangling markers in the buffer. - (dolist (r references) (set-marker (nth 1 r) nil))))) + (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"))))))))) (defun org-footnote-normalize () "Turn every footnote in buffer into a numbered one." (interactive) - (let ((references (org-footnote--collect-references 'anonymous))) - (unwind-protect - (let ((n 0) - (translations nil) - (definitions nil)) - (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. - (let ((unreferenced - (cl-remove-if (lambda (d) (member (car d) inserted)) - definitions))) - (dolist (d unreferenced) - (insert "\n" - (replace-regexp-in-string - org-footnote-definition-re - (format "[fn:%d]" (cl-incf n)) - (cdr d)) - "\n")))))) - ;; Clear dangling markers. - (dolist (r references) (set-marker (nth 1 r) nil))))) + (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")))))))) (defun org-footnote-auto-adjust-maybe () "Renumber and/or sort footnotes according to user settings." |