Commit 3fa9c9f7 authored by Stefan Monnier's avatar Stefan Monnier

* lisp/mail/footnote.el: Tweak markers convention

Instead of using markers that are sometimes before and sometimes after
the [...] and using `insert-before-markers` to make sure those that are
are before stay before, always place them before, and make them
"move after"so they stay with their [...] without the need for

(footnote--current-regexp): Add arg to match previous style.
Include the start/end "tags" in the regexp.  Adjust all callers.
(footnote--markers-alist): Change position of POINTERS.
(footnote--refresh-footnotes, footnote--renumber)
(footnote--make-hole, footnote-delete-footnote)
(footnote-back-to-message): Adjust accordingly, mostly by using
`looking-at` instead of `looking-back`.
(footnote--make-hole): Always return footnote nb to use.
(footnote-add-footnote): Simplify call accordingly.

* test/lisp/mail/footnote-tests.el: New file.
parent e10e314e
Pipeline #1532 failed with stage
in 90 minutes and 1 second
......@@ -165,8 +165,7 @@ left with the first character of footnote text."
Where FN is the footnote number, TEXT is a marker pointing to
the footnote's text, and POINTERS is a list of markers pointing
to the places from which the footnote is referenced.
TEXT points right *before* the [...] and POINTERS point right
*after* the [...].")
Both TEXT and POINTERS points right *before* the [...]")
(defvar footnote-mouse-highlight 'highlight
;; FIXME: This `highlight' property is not currently used.
......@@ -436,30 +435,26 @@ Conversion is done based upon the current selected style."
(nth 0 footnote-style-alist))))
(funcall (nth 1 alist) index)))
(defun footnote--current-regexp ()
(defun footnote--current-regexp (&optional index-regexp)
"Return the regexp of the index of the current style."
(let ((regexp (nth 2 (or (assq footnote-style footnote-style-alist)
(nth 0 footnote-style-alist)))))
(let ((regexp (or index-regexp
(nth 2 (or (assq footnote-style footnote-style-alist)
(nth 0 footnote-style-alist))))))
(regexp-quote footnote-start-tag) "\\("
;; Hack to avoid repetition of repetition.
;; FIXME: I'm not sure the added * makes sense at all; there is
;; always a single number within the footnote-{start,end}-tag pairs.
;; Worse, the code goes on and adds yet another + later on, in
;; footnote-refresh-footnotes, just in case. That makes even less sense.
;; Likely, both the * and the extra + should go away.
(if (string-match "[^\\]\\\\\\{2\\}*[*+?]\\'" regexp)
(substring regexp 0 -1)
"*\\)" (regexp-quote footnote-end-tag))))
(defun footnote--refresh-footnotes (&optional index-regexp)
"Redraw all footnotes.
You must call this or arrange to have this called after changing
footnote styles."
(let ((fn-regexp (concat
(regexp-quote footnote-start-tag)
"\\(" (or index-regexp (footnote--current-regexp)) "+\\)"
(regexp-quote footnote-end-tag))))
(let ((fn-regexp (footnote--current-regexp index-regexp)))
(pcase-dolist (`(,fn ,text . ,pointers) footnote--markers-alist)
;; Take care of the pointers first
......@@ -467,8 +462,7 @@ footnote styles."
(goto-char locn)
;; Try to handle the case where `footnote-start-tag' and
;; `footnote-end-tag' are the same string.
(when (looking-back fn-regexp
(when (looking-at fn-regexp)
......@@ -515,7 +509,7 @@ footnote styles."
(let ((string (concat footnote-start-tag
(footnote--index-to-string arg)
(if mousable
string 'footnote-number arg footnote-mouse-highlight t)
......@@ -524,13 +518,11 @@ footnote styles."
(defun footnote--renumber (to alist-elem)
"Renumber a single footnote."
(unless (equal to (car alist-elem)) ;Nothing to do.
(let* ((fn-regexp (concat (regexp-quote footnote-start-tag)
(regexp-quote footnote-end-tag))))
(let* ((fn-regexp (footnote--current-regexp)))
(setcar alist-elem to)
(dolist (posn (cddr alist-elem))
(goto-char posn)
(when (looking-back fn-regexp (line-beginning-position))
(when (looking-at fn-regexp)
(concat footnote-start-tag
......@@ -562,7 +554,7 @@ footnote styles."
"Insert a marker pointing to footnote ARG, at buffer location LOCN."
(let ((entry (assq arg footnote--markers-alist)))
(unless (cadr entry)
(let ((marker (copy-marker locn)))
(let ((marker (copy-marker locn t)))
(if entry
(setf (cadr entry) marker)
(push `(,arg ,marker) footnote--markers-alist)
......@@ -572,7 +564,7 @@ footnote styles."
(defun footnote--insert-pointer-marker (arg locn)
"Insert a marker pointing to footnote ARG, at buffer location LOCN."
(let ((entry (assq arg footnote--markers-alist))
(marker (copy-marker locn)))
(marker (copy-marker locn t)))
(if entry
(push marker (cddr entry))
(push `(,arg nil ,marker) footnote--markers-alist)
......@@ -601,8 +593,9 @@ Presumes we're within the footnote area already."
(defun footnote--insert-footnote (arg)
"Insert a footnote numbered ARG, at (point)."
(footnote--insert-pointer-marker arg (point))
(footnote--insert-numbered-footnote arg t)
(let ((old-point (point)))
(footnote--insert-numbered-footnote arg t)
(footnote--insert-pointer-marker arg old-point))
(if (footnote--goto-first)
......@@ -615,10 +608,7 @@ Presumes we're within the footnote area already."
(when (re-search-forward
(if footnote-spaced-footnotes
(concat "\n"
(regexp-quote footnote-start-tag)
(regexp-quote footnote-end-tag)))
(concat "\n" (footnote--current-regexp)))
nil t)
(unless (beginning-of-line) t))
......@@ -730,10 +720,12 @@ footnote area, returns `point-max'."
;;; User functions
(defun footnote--make-hole ()
"Make room in the alist for a new footnote at point.
Return the footnote number to use."
(let (rc)
(dolist (alist-elem footnote--markers-alist)
(when (< (point) (- (cl-caddr alist-elem) 3))
(when (<= (point) (cl-caddr alist-elem))
(unless rc
(setq rc (car alist-elem)))
......@@ -743,7 +735,8 @@ footnote area, returns `point-max'."
(1+ (car alist-elem))))
(footnote--renumber (1+ (car alist-elem))
(or rc
(1+ (or (caar (last footnote--markers-alist)) 0))))))
(defun footnote-add-footnote ()
"Add a numbered footnote.
......@@ -753,27 +746,17 @@ If the variable `footnote-narrow-to-footnotes-when-editing' is set,
the buffer is narrowed to the footnote body. The restriction is removed
by using `footnote-back-to-message'."
(interactive "*")
(let ((num
(if footnote--markers-alist
(let ((last (car (last footnote--markers-alist))))
(if (< (point) (cl-caddr last))
(1+ (car last))))
(let ((num (footnote--make-hole)))
(message "Adding footnote %d" num)
(footnote--insert-footnote num)
(insert-before-markers (make-string footnote-body-tag-spacing ? ))
(let ((opoint (point)))
(if footnote-spaced-footnotes
(when footnote-narrow-to-footnotes-when-editing
;; Emacs/XEmacs bug? save-excursion doesn't restore point when using
;; insert-before-markers.
(goto-char opoint))))
(insert (make-string footnote-body-tag-spacing ? ))
(if footnote-spaced-footnotes
(when footnote-narrow-to-footnotes-when-editing
(defun footnote-delete-footnote (&optional arg)
"Delete a numbered footnote.
......@@ -787,14 +770,11 @@ delete the footnote with that number."
(y-or-n-p (format "Really delete footnote %d?" arg))))
(let ((alist-elem (or (assq arg footnote--markers-alist)
(error "Can't delete footnote %d" arg)))
(fn-regexp (concat (regexp-quote footnote-start-tag)
(regexp-quote footnote-end-tag))))
(fn-regexp (footnote--current-regexp)))
(dolist (locn (cddr alist-elem))
(goto-char locn)
(when (looking-back fn-regexp
(when (looking-at fn-regexp)
(delete-region (match-beginning 0) (match-end 0)))))
(goto-char (cadr alist-elem))
......@@ -867,7 +847,9 @@ being set it is automatically widened."
(when note
(when footnote-narrow-to-footnotes-when-editing
(goto-char (cl-caddr (assq note footnote--markers-alist))))))
(goto-char (cl-caddr (assq note footnote--markers-alist)))
(when (looking-at (footnote--current-regexp))
(goto-char (match-end 0))))))
(defvar footnote-mode-map
(let ((map (make-sparse-keymap)))
;;; footnote-tests.el --- Tests for footnote-mode -*- lexical-binding: t; -*-
;; Copyright (C) 2019 Free Software Foundation, Inc.
;; Author: Stefan Monnier <>
;; Keywords:
;; This program 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.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <>.
;;; Commentary:
;;; Code:
(ert-deftest footnote-tests-same-place ()
(footnote-mode 1)
(insert "hello world")
(beginning-of-line) (forward-word)
(insert "footnote")
(should (equal (buffer-substring (point-min) (point))
(beginning-of-line) (forward-word)
(insert "other footnote")
(should (equal (buffer-substring (point-min) (point))
(should (equal (buffer-substring (point-min) (line-end-position))
"hello[1][2] world"))))
(provide 'footnote-tests)
;;; footnote-tests.el ends here
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment