Browse Source

org-footnote: parse footnotes references with more accuracy

* lisp/org-footnote.el (org-footnote-re): don't end an inline footnote
  at unrelated closing square brackets.
  (org-footnote-at-reference-p): improve accuracy of the function to
  determine if point is at a reference and to extract definition of an
  inline footnote.
  (org-footnote-all-labels, org-footnote-action, org-footnote-delete,
  org-footnote-auto-adjust-maybe): make use of previous function.
Nicolas Goaziou 14 years ago
parent
commit
62fe76b5dc
1 changed files with 128 additions and 64 deletions
  1. 128 64
      lisp/org-footnote.el

+ 128 - 64
lisp/org-footnote.el

@@ -53,14 +53,15 @@
 (defvar message-signature-separator) ;; defined in message.el
 (defvar message-signature-separator) ;; defined in message.el
 
 
 (defconst org-footnote-re
 (defconst org-footnote-re
-  (concat "[^][\n]"   ; to make sure it is not at the beginning of a line
-	  "\\["
-	  "\\(?:"
-	  "\\([0-9]+\\)"
-	  "\\|"
-	  (org-re "\\(fn:\\([-_[:word:]]+?\\)?\\)\\(?::\\([^\]]*?\\)\\)?")
-	  "\\)"
-	  "\\]")
+  ;; Footnotes ain't closed in this regexp, as their definition might
+  ;; contain square brackets \(i.e. links\).
+  ;;
+  ;; `org-re' is used for regexp compatibility with XEmacs.
+  (org-re (concat "\\[\\(?:"
+		  ;; Match inline footnotes.
+		  "fn:\\([-_[:word:]]+\\)?:\\|"
+		  ;; Match other footnotes.
+		  "\\([0-9]+\\)\\|\\(fn:[-_[:word:]]+\\)\\)"))
   "Regular expression for matching footnotes.")
   "Regular expression for matching footnotes.")
 
 
 (defconst org-footnote-definition-re
 (defconst org-footnote-definition-re
@@ -151,43 +152,92 @@ extracted will be filled again."
 
 
 (defun org-footnote-at-reference-p ()
 (defun org-footnote-at-reference-p ()
   "Is the cursor at a footnote reference?
   "Is the cursor at a footnote reference?
-If yes, return the beginning position, the label, and the definition, if local."
-  (when (org-in-regexp org-footnote-re 15)
-    (list (match-beginning 0)
-	  (or (match-string 1)
-	      (if (equal (match-string 2) "fn:") nil (match-string 2)))
-	  (match-string 4))))
+
+If so, return an list containing its label, beginning and ending
+positions, and the definition, if local."
+  (when (and (not (or (org-in-commented-line)
+		      (org-in-verbatim-emphasis)))
+	     (or (org-in-regexp org-footnote-re)
+		 (save-excursion (re-search-backward org-footnote-re nil t)))
+	     ;; A footnote reference cannot start at bol.
+	     (/= (match-beginning 0) (point-at-bol)))
+    (let* ((beg (match-beginning 0))
+	   (label (or (match-string 2) (match-string 3)
+		      ;; Anonymous footnotes don't have labels
+		      (and (match-string 1) (concat "fn:" (match-string 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 and isn't within a LaTeX macro. About
+      ;; that case, some special attention should be paid. Indeed,
+      ;; when two footnotes are side by side, once the first one is
+      ;; changed into LaTeX, the second one might then be considered
+      ;; as an optional argument of the command. To prevent that, we
+      ;; have a look at the `org-protected' property of that LaTeX
+      ;; command.
+      (when (and end (< (point) end)
+		 (or (not (org-inside-latex-macro-p))
+		     (and (get-text-property (1- beg) 'org-protected)
+			  (not (get-text-property beg 'org-protected)))))
+	(list label beg end
+	      ;; Definition: ensure this is an inline footnote first.
+	      (and (or (not label) (match-string 1))
+		   (org-trim (buffer-substring (match-end 0) (1- end)))))))))
 
 
 (defun org-footnote-at-definition-p ()
 (defun org-footnote-at-definition-p ()
-  "Is the cursor at a footnote definition.
+  "Is the cursor at a footnote definition?
+
 This matches only pure definitions like [1] or [fn:name] at the beginning
 This matches only pure definitions like [1] or [fn:name] at the beginning
 of a line.  It does not match references like [fn:name:definition], where the
 of a line.  It does not match references like [fn:name:definition], where the
 footnote text is included and defined locally.
 footnote text is included and defined locally.
-The return value will be nil if not at a footnote definition, and a list
-with start and label of the footnote if there is a definition at point."
+
+The return value will be nil if not at a footnote definition, and a list with
+label, start, end and definition of the footnote otherwise."
   (save-excursion
   (save-excursion
-    (end-of-line 1)
+    (end-of-line)
     (let ((lim (save-excursion (re-search-backward "^\\*+ \\|^[ \t]*$" nil t))))
     (let ((lim (save-excursion (re-search-backward "^\\*+ \\|^[ \t]*$" nil t))))
       (when (re-search-backward org-footnote-definition-re lim t)
       (when (re-search-backward org-footnote-definition-re lim t)
-	(list (match-beginning 0) (match-string 2))))))
+	(end-of-line)
+	(list (match-string 2)
+	      (match-beginning 0)
+	      (save-match-data
+		(or (and (re-search-forward
+			  (org-re "^[ \t]*$\\|^\\*+ \\|^\\[\\([0-9]+\\|fn:[-_[:word:]]+\\)\\]")
+			  nil t)
+			 (progn (skip-chars-forward " \t\n") (point-at-bol)))
+		    (point-max)))
+	      (org-trim (buffer-substring (match-end 0) (point))))))))
+
+(defun org-footnote-get-definition (label)
+  "Return label, boundaries and definition of the footnote LABEL."
+  (let* ((label (regexp-quote (org-footnote-normalize-label label)))
+	 (re (format "^\\[%s\\]\\|.\\[%s:" label label))
+	 pos)
+    (save-excursion
+      (when (or (re-search-forward re nil t)
+		(and (goto-char (point-min))
+		     (re-search-forward re nil t))
+		(and (progn (widen) t)
+		     (goto-char (point-min))
+		     (re-search-forward re nil t)))
+	(let ((refp (org-footnote-at-reference-p)))
+	  (cond
+	   ((and (nth 3 refp) refp))
+	   ((org-footnote-at-definition-p))))))))
 
 
 (defun org-footnote-goto-definition (label)
 (defun org-footnote-goto-definition (label)
-  "Find the definition of the footnote with label LABEL."
+  "Move point to the definition of the footnote LABEL."
   (interactive "sLabel: ")
   (interactive "sLabel: ")
   (org-mark-ring-push)
   (org-mark-ring-push)
-  (setq label (org-footnote-normalize-label label))
-  (let ((re (format "^\\[%s\\]\\|.\\[%s:" label label))
-	pos)
-    (save-excursion
-      (setq pos (or (re-search-forward re nil t)
-		    (and (goto-char (point-min))
-			 (re-search-forward re nil t))
-		    (and (progn (widen) t)
-			 (goto-char (point-min))
-			 (re-search-forward re nil t)))))
-    (if (not pos)
+  (let ((def (org-footnote-get-definition label)))
+    (if (not def)
 	(error "Cannot find definition of footnote %s" label)
 	(error "Cannot find definition of footnote %s" label)
-      (goto-char pos)
+      (goto-char (nth 1 def))
+      (looking-at (format "^\\[%s\\]\\|.\\[%s:" label label))
+      (goto-char (match-end 0))
       (org-show-context 'link-search)
       (org-show-context 'link-search)
       (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))))
       (message "Edit definition and go back with `C-c &' or, if unique, with `C-c C-c'."))))
 
 
@@ -231,9 +281,11 @@ with start and label of the footnote if there is a definition at point."
 	  (and l (add-to-list 'rtn l)))
 	  (and l (add-to-list 'rtn l)))
 	(goto-char (point-min))
 	(goto-char (point-min))
 	(while (re-search-forward org-footnote-re nil t)
 	(while (re-search-forward org-footnote-re nil t)
-	  (setq l (or (org-match-string-no-properties 1)
-		      (org-match-string-no-properties 2)))
-	  (and l (not (equal l "fn:")) (add-to-list 'rtn l)))))
+	  (backward-char)
+	  (let* ((full-ref (org-footnote-at-reference-p))
+		 (l (car full-ref)))
+	    ;; Skip anonymous footnotes.
+	    (and l (add-to-list 'rtn l))))))
     rtn))
     rtn))
 
 
 (defun org-footnote-unique-label (&optional current)
 (defun org-footnote-unique-label (&optional current)
@@ -355,11 +407,12 @@ With prefix arg SPECIAL, offer additional commands in a menu."
 	(org-footnote-delete))
 	(org-footnote-delete))
        (t (error "No such footnote command %c" c))))
        (t (error "No such footnote command %c" c))))
      ((setq tmp (org-footnote-at-reference-p))
      ((setq tmp (org-footnote-at-reference-p))
-      (if (nth 1 tmp)
-	  (org-footnote-goto-definition (nth 1 tmp))
-	(goto-char (match-beginning 4))))
+      (if (car tmp)
+	  (org-footnote-goto-definition (car tmp))
+	(goto-char (nth 1 tmp))
+	(forward-char 5)))
      ((setq tmp (org-footnote-at-definition-p))
      ((setq tmp (org-footnote-at-definition-p))
-      (org-footnote-goto-previous-reference (nth 1 tmp)))
+      (org-footnote-goto-previous-reference (car tmp)))
      (t (org-footnote-new)))))
      (t (org-footnote-new)))))
 
 
 (defvar org-footnote-insert-pos-for-preprocessor 'point-max
 (defvar org-footnote-insert-pos-for-preprocessor 'point-max
@@ -548,36 +601,47 @@ ENTRY is (fn-label num-mark definition)."
 (defun org-footnote-delete (&optional label)
 (defun org-footnote-delete (&optional label)
   "Delete the footnote at point.
   "Delete the footnote at point.
 This will remove the definition (even multiple definitions if they exist)
 This will remove the definition (even multiple definitions if they exist)
-and all references of a footnote label."
+and all references of a footnote label.
+
+If LABEL is non-nil, delete that footnote instead."
   (catch 'done
   (catch 'done
-    (let (x label l beg def-re (nref 0) (ndef 0))
-      (unless label
-	(when (setq x (org-footnote-at-reference-p))
-	  (setq label (nth 1 x))
-	  (when (or (not label) (equal "fn:" label))
-	    (delete-region (1+ (match-beginning 0)) (match-end 0))
-	    (message "Anonymous footnote removed")
-	    (throw 'done t)))
-	(when (and (not label) (setq x (org-footnote-at-definition-p)))
-	  (setq label (nth 1 x)))
-	(unless label (error "Don't know which footnote to remove")))
+    (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 to it and
+      ;;    delete it. Increase counter NREF in the process.
       (save-excursion
       (save-excursion
 	(save-restriction
 	(save-restriction
 	  (goto-char (point-min))
 	  (goto-char (point-min))
 	  (while (re-search-forward org-footnote-re nil t)
 	  (while (re-search-forward org-footnote-re nil t)
-	    (setq l (or (match-string 1) (match-string 2)))
-	    (when (equal l label)
-	      (delete-region (1+ (match-beginning 0)) (match-end 0))
-	      (incf nref)))
+	    (backward-char)
+	    (let* ((full-ref (org-footnote-at-reference-p))
+		   (l (car full-ref)))
+	      (when (equal l label)
+		(delete-region (nth 1 full-ref) (nth 2 full-ref))
+		(incf nref))))
+	  ;; 3. Find every definition of footnote LABEL and delete it.
+	  ;;    Increase counter NDEF in the process.
 	  (goto-char (point-min))
 	  (goto-char (point-min))
-	  (setq def-re (concat "^\\[" (regexp-quote label) "\\]"))
-	  (while (re-search-forward def-re nil t)
-	    (setq beg (match-beginning 0))
-	    (if (re-search-forward "^\\[\\|^[ \t]*$\\|^\\*+ " nil t)
-		(goto-char (match-beginning 0))
-	      (goto-char (point-max)))
-	    (delete-region beg (point))
-	    (incf ndef))))
+	  (let ((def-re (concat "^\\[" (regexp-quote label) "\\]")) beg)
+	    (while (re-search-forward def-re nil t)
+	      (let ((full-def (org-footnote-at-definition-p)))
+		(delete-region (nth 1 full-def) (nth 2 full-def)))
+	      (incf ndef)))))
       (org-footnote-auto-adjust-maybe)
       (org-footnote-auto-adjust-maybe)
       (message "%d definition(s) of and %d reference(s) of footnote %s removed"
       (message "%d definition(s) of and %d reference(s) of footnote %s removed"
 	       ndef nref label))))
 	       ndef nref label))))
@@ -605,7 +669,7 @@ and all references of a footnote label."
   (when (memq org-footnote-auto-adjust '(t renumber))
   (when (memq org-footnote-auto-adjust '(t renumber))
     (org-footnote-renumber-fn:N))
     (org-footnote-renumber-fn:N))
   (when (memq org-footnote-auto-adjust '(t sort))
   (when (memq org-footnote-auto-adjust '(t sort))
-    (let ((label (nth 1 (org-footnote-at-definition-p))))
+    (let ((label (car (org-footnote-at-definition-p))))
       (org-footnote-normalize 'sort)
       (org-footnote-normalize 'sort)
       (when label
       (when label
 	(goto-char (point-min))
 	(goto-char (point-min))