org-footnote: Fix footnote predicates

* lisp/org-footnote.el (org-footnote-at-reference-p):
(org-footnote-at-definition-p): Rewrite using Elements library.
(org-footnote-get-next-reference): Use `org-footnote-at-reference-p'.
(org-footnote-next-reference-or-definition): Use old definition for
`org-footnote-at-definition-p' and `org-footnote-at-definition-p'.

This patch aims at reducing the calls to the inaccurate
`org-footnote-in-valid-context-p' function.  However,
`org-footnote-next-reference-or-definition' still uses it because the
function is for fontification only, so 1. accuracy matters less
2. Elements has a slower worse case scenario.
This commit is contained in:
Nicolas Goaziou 2018-06-24 14:57:49 +02:00
parent 627cb7f578
commit c37c6bda3c
1 changed files with 94 additions and 86 deletions

View File

@ -189,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
@ -467,27 +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."
(let ((label-fmt (if label (format "\\[fn:%s[]:]" label) org-footnote-re)))
(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-fmt limit t)
label-regexp limit t)
(unless backward (backward-char))
(let ((reference (org-element-context)))
(when (eq 'footnote-reference (org-element-type reference))
(throw :exit
(list
(org-element-property :label reference)
(org-element-property :begin reference)
(save-excursion
(goto-char (org-element-property :end reference))
(skip-chars-backward " \t")
(point))
(and (eq 'inline (org-element-property :type reference))
(buffer-substring-no-properties
(org-element-property :contents-begin reference)
(org-element-property :contents-end
reference))))))))))))
(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.
@ -496,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)
@ -507,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-bracket-link-regexp))))
(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-beginning 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.