From 62fe76b5dcf04d42adbbb6ca9280fe618c424569 Mon Sep 17 00:00:00 2001 From: Nicolas Goaziou Date: Fri, 29 Apr 2011 15:46:25 +0200 Subject: [PATCH] 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. --- lisp/org-footnote.el | 192 ++++++++++++++++++++++++++++--------------- 1 file changed, 128 insertions(+), 64 deletions(-) diff --git a/lisp/org-footnote.el b/lisp/org-footnote.el index c9739241a..059e97f92 100644 --- a/lisp/org-footnote.el +++ b/lisp/org-footnote.el @@ -53,14 +53,15 @@ (defvar message-signature-separator) ;; defined in message.el (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.") (defconst org-footnote-definition-re @@ -151,43 +152,92 @@ extracted will be filled again." (defun org-footnote-at-reference-p () "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 () - "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 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 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 - (end-of-line 1) + (end-of-line) (let ((lim (save-excursion (re-search-backward "^\\*+ \\|^[ \t]*$" nil 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) - "Find the definition of the footnote with label LABEL." + "Move point to the definition of the footnote LABEL." (interactive "sLabel: ") (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) - (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) (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))) (goto-char (point-min)) (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)) (defun org-footnote-unique-label (&optional current) @@ -355,11 +407,12 @@ With prefix arg SPECIAL, offer additional commands in a menu." (org-footnote-delete)) (t (error "No such footnote command %c" c)))) ((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)) - (org-footnote-goto-previous-reference (nth 1 tmp))) + (org-footnote-goto-previous-reference (car tmp))) (t (org-footnote-new))))) (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) "Delete the footnote at point. 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 - (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-restriction (goto-char (point-min)) (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)) - (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) (message "%d definition(s) of and %d reference(s) of footnote %s removed" ndef nref label)))) @@ -605,7 +669,7 @@ and all references of a footnote label." (when (memq org-footnote-auto-adjust '(t renumber)) (org-footnote-renumber-fn:N)) (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) (when label (goto-char (point-min))