summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolas Goaziou <mail@nicolasgoaziou.fr>2018-06-24 14:57:49 +0200
committerNicolas Goaziou <mail@nicolasgoaziou.fr>2018-06-24 14:57:49 +0200
commitc37c6bda3ca461044dd36e674883420246a42eba (patch)
treede464031f57f4881bc56817df47e3fb51a9ad666
parent627cb7f578e07547f3a3ea73ef1670ed39084a82 (diff)
downloadorg-mode-c37c6bda3ca461044dd36e674883420246a42eba.tar.gz
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.
-rw-r--r--lisp/org-footnote.el180
1 files changed, 94 insertions, 86 deletions
diff --git a/lisp/org-footnote.el b/lisp/org-footnote.el
index d57ffcd..de8f26f 100644
--- a/lisp/org-footnote.el
+++ b/lisp/org-footnote.el
@@ -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.