Browse Source

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.
Nicolas Goaziou 11 months ago
parent
commit
c37c6bda3c
1 changed files with 94 additions and 86 deletions
  1. 94 86
      lisp/org-footnote.el

+ 94 - 86
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.