Subject: Re: bug#66771: [PATCH] Throw out fold spec after a single failure

From: Tony Zorman <[email-masked]>
Date: Fri, 15 Dec 2023 06:56:17 +0100

Hi Ikumi,

On Fri, Nov 10 2023 21:10, Ikumi Keita wrote:
> I'll wait for your feedback.

I've heard back from the FSF: everything is signed now, and we should be
good to go! Attached you can find the patch again, rebased to include
commit 2a959a1d43b3f38cf4845b071679a7978e36d95a.

Thank you for your patience!
Tony

>From c0840aab5e791488bae7cdae1607710993ccb13e Mon Sep 17 00:00:00 2001
From: Tony Zorman <[email-masked]>
Date: Thu, 26 Oct 2023 21:26:27 +0200
Subject: [PATCH] Throw out fold spec after a single failure
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* tex-fold.el (TeX-fold-expand-spec): So far, TeX-fold-expand-spec did
not abort its expansion of the current spec if *any* of the expansions
succeeded.  This can lead to some unexpected behaviour.  For example,
given

  (setopt TeX-fold-math-spec-list
          '(("[1] of {1}||{1}" ("cite"))))

expanding `\cite{this}' would result in the overlay displaying
`… of this' instead of just `this', because one of the arguments
(the mandatory one) succeeded.  This is probably not what the user
intended.  Instead, in order for the spec to be chosen, *all
arguments should succeed.
---
 tex-fold.el | 56 ++++++++++++++++++++++++-----------------------------
 1 file changed, 25 insertions(+), 31 deletions(-)

diff --git a/tex-fold.el b/tex-fold.el
index c6e9f1ca..d4dacc16 100644
--- a/tex-fold.el
+++ b/tex-fold.el
@@ -741,37 +741,31 @@ Return non-nil if a removal happened, nil otherwise."
   "Expand instances of {<num>}, [<num>], <<num>>, and (<num>).
 Replace them with the respective macro argument."
   (let ((spec-list (split-string spec "||"))
-        (delims '((?\{ . ?\}) (?\[ . ?\]) (?< . ?>) (?\( . ?\))))
-        index success)
-    (catch 'success
-      ;; Iterate over alternatives.
-      (dolist (elt spec-list)
-        (setq spec elt
-              index nil)
-        ;; Find and expand every placeholder.
-        (while (and (string-match "\\([[{<(]\\)\\([1-9]\\)\\([]}>)]\\)"
-                                  elt index)
-                    ;; Does the closing delim match the opening one?
-                    (string-equal
-                     (match-string 3 elt)
-                     (char-to-string
-                      (cdr (assq (string-to-char (match-string 1 elt))
-                                 delims)))))
-          (setq index (match-end 0))
-          (let ((arg (car (save-match-data
-                            ;; Get the argument.
-                            (TeX-fold-macro-nth-arg
-                             (string-to-number (match-string 2 elt))
-                             ov-start ov-end
-                             (assoc (string-to-char (match-string 1 elt))
-                                    delims))))))
-            (when arg (setq success t))
-            ;; Replace the placeholder in the string.
-            (setq elt (replace-match (or arg TeX-fold-ellipsis) nil t elt)
-                  index (+ index (- (length elt) (length spec)))
-                  spec elt)))
-        (when success (throw 'success nil))))
-    spec))
+        (delims '((?\{ . ?\}) (?\[ . ?\]) (?< . ?>) (?\( . ?\)))))
+    (cl-labels
+        ((expand (spec &optional index)
+           ;; If there is something to replace and the closing delimiter
+           ;; matches the opening one…
+           (if-let (((string-match "\\([[{<(]\\)\\([1-9]\\)\\([]}>)]\\)"
+                                   elt index))
+                    (open (string-to-char (match-string 1 spec)))
+                    (num (string-to-number (match-string 2 spec)))
+                    (close (string-to-char (match-string 3 spec)))
+                    ((equal close (cdr (assoc open delims)))))
+               ;; … then replace it and move on.  Otherwise, it must have been
+               ;; a spurious spec, so abort.
+               (when-let ((arg (car (save-match-data
+                                      (TeX-fold-macro-nth-arg
+                                       num ov-start ov-end (assoc open delims)))))
+                          (spec* (replace-match arg nil t spec)))
+                 (expand spec*
+                         (+ (match-end 0) (- (length spec*) (length spec)))))
+             ;; Nothing to replace: return the (completed) spec.
+             spec)))
+      (or (cl-loop for elt in spec-list
+                   do (when-let (expanded (expand elt))
+                        (cl-return expanded)))
+          TeX-fold-ellipsis))))
 
 (defun TeX-fold-hide-item (ov)
   "Hide a single macro or environment.
-- 
2.42.0

-- 
Tony Zorman | https://tony-zorman.com/