Subject: Re: bug#61400: Different filling for verbatim macros

From: Ikumi Keita <[email-masked]>
Date: Tue, 14 Feb 2023 15:46:53 +0900

Hi Arash,

>>>>> Arash Esbati <[email-masked]> writes:
> Do we really want this?  My gut feeling is to have `LaTeX-verbatim-p'
> returns t just before the "\verb|...|" etc., i.e., when cursor is over
> the backslash.

OK, then another candidate is the attached patch. This keeps the current
behavior of `LaTeX-verbatim-p' and introduces a new function
`LaTeX-verbatim-nobreak-p' as you suggested in your previous message.

However, I'm a bit reluctant.
1. If `LaTeX-verbatim-p' should return non-nil at just before
   "\verb", then it should also do so just before "\begin{verbatim}" for
   consistency, shouldn't it?
2. Suppose that you type "$" just before "\verb...". Then
   `TeX-insert-dollar' inserts only one "$" instead of "$$" under the
   current behavior of `LaTeX-verbatim-p', because `TeX-insert-dollar'
   uses `TeX-verbatim-p'. I think it's unexpected. 

> Thanks, LGTM.  One other complication we have to deal with is that \Verb
> from fancyvrb or \lstinline from listings can have an optional argument,
> e.g.:

>   \Verb[fontsize=\scriptsize]|foo bar|

Thanks, I incorporated it with slight modification in the attached patch.

>> +;; (defun LaTeX-current-verbatim-macro ()
>> +;;   "Return name of verbatim macro containing point, nil if none is present."
>> +;;   (let ((macro-boundaries (LaTeX-verbatim-macro-boundaries)))
>> +;;     (when macro-boundaries
>> +;;       (save-excursion
>> +;;         (goto-char (car macro-boundaries))
>> +;;         (forward-char (length TeX-esc))
>> +;;         (buffer-substring-no-properties
>> +;;          (point) (progn (skip-chars-forward "@A-Za-z*") (point)))))))

> Would it make sense to keep the above as a utility function?

This time I leave that function as is.

> And while we're at it: The docstring isn't correct any more, right?  I
> mean this part:

>     It doesn't regard the point just before \\verb... etc. belongs to
>     the verbatim macro.  This isn't symmetric with
>     `TeX-find-macro-boundaries'.

In my previous patch, it's correct because I removed the chunk
      (if (looking-at (concat (regexp-quote TeX-esc) verbatim-regexp))
          (forward-char 1)
from `LaTeX-verbatim-macro-boundaries'. :-)

(By the way, I noticed that `LaTeX-verbatim-p' raises error when "\verb"
is at the end of the buffer without delimiters and its contents "|...|".
In fact `LaTeX-verbatim-macro-boundaries' and
`LaTeX-current-verbatim-macro' raise the same error. The reason is that
`LaTeX-verbatim-macro-boundaries' presumes there is nonempty string
after the macro body. Should we cater for such corner case?)

Best,
Ikumi Keita
#StandWithUkraine #StopWarInUkraine

diff --git a/latex.el b/latex.el
index fb862b76..93c13dd8 100644
--- a/latex.el
+++ b/latex.el
@@ -3780,45 +3780,66 @@ values of the variable `LaTeX-verbatim-environments' as well.")
   (append LaTeX-verbatim-environments
           LaTeX-verbatim-environments-local))
 
-(defun LaTeX-verbatim-macro-boundaries ()
-  "Return boundaries of verbatim macro.
+(defun LaTeX-verbatim-macro-boundaries (&optional arg-only)
+  "Return boundaries of verbatim macro containing point.
 Boundaries are returned as a cons cell where the car is the macro
 start and the cdr the macro end.
 
-Only macros which enclose their arguments with special
-non-parenthetical delimiters, like \\verb+foo+, are recognized."
+If optional argument ARG-ONLY is non-nil, return the inner region
+of the macro argument as cons."
   (save-excursion
     (let ((orig (point))
-          (verbatim-regexp (regexp-opt (LaTeX-verbatim-macros-with-delims) t)))
+          (verbatim-regexp (regexp-opt
+                            (append (LaTeX-verbatim-macros-with-delims)
+                                    (LaTeX-verbatim-macros-with-braces))
+                            t)))
       ;; Search backwards for the macro start, unless we are facing one
-      (unless (looking-at (concat (regexp-quote TeX-esc) verbatim-regexp))
-        (catch 'found
-          (while (progn
-                   (skip-chars-backward (concat "^\n" (regexp-quote TeX-esc))
-                                        (line-beginning-position))
-                   (when (looking-at verbatim-regexp) (throw 'found nil))
-                   (or (bobp) (forward-char -1))
-                   (/= (point) (line-beginning-position))))))
+      (if (looking-at (concat (regexp-quote TeX-esc) verbatim-regexp))
+          (forward-char 1)
+        (while (progn
+                 (skip-chars-backward (concat "^" (regexp-quote TeX-esc))
+                                      (line-beginning-position))
+                 (if (or (bolp)
+                         (looking-at verbatim-regexp))
+                     ;; Terminate the loop.
+                     nil
+                   (forward-char -1)
+                   ;; Continue the loop.
+                   t))))
       ;; Search forward for the macro end, unless we failed to find a start
       (unless (bolp)
         (let* ((beg (1- (point)))
-               (macro-end (match-end 0))
+               (end (match-end 0))
                ;; XXX: Here we assume we are dealing with \verb which
                ;; expects the delimiter right behind the command.
                ;; However, \lstinline can also cope with whitespace as
                ;; well as an optional argument after the command.
-               (delimiter (buffer-substring-no-properties
-                           macro-end (1+ macro-end))))
+               ;; Besides, \lstinline or \Verb (from fancyvrb) also
+               ;; accept an optional argument which we have to
+               ;; encounter.  We assume that users don't write
+               ;; something like this '\Verb[foo[' and again the
+               ;; delimiter is directly after the ] closing the
+               ;; optional argument:
+               (delimiter (if (= (char-after end) ?\[)
+                              ;; Update `end'.
+                              (save-excursion (goto-char end)
+                                              (forward-list)
+                                              (setq end (point))
+                                              (string (following-char)))
+                            (buffer-substring-no-properties
+                             end (1+ end))))
           ;; Heuristic: If an opening brace is encountered, search for
           ;; both the opening and the closing brace as an end marker.
           ;; Like that the function should work for \verb|...| as well
           ;; as for \url{...}.
           (when (string= delimiter TeX-grop)
             (setq delimiter (concat delimiter TeX-grcl)))
-          (goto-char (1+ macro-end))
+          (goto-char (1+ end))
           (skip-chars-forward (concat "^" delimiter))
           (when (<= orig (point))
-            (cons beg (1+ (point)))))))))
+            (if arg-only
+                (cons (1+ end) (point))
+              (cons beg (1+ (point)))))))))))
 
 (defun LaTeX-current-verbatim-macro ()
   "Return name of verbatim macro containing point, nil if none is present."
@@ -3832,16 +3853,34 @@ non-parenthetical delimiters, like \\verb+foo+, are recognized."
 
 (defun LaTeX-verbatim-p (&optional pos)
   "Return non-nil if position POS is in a verbatim-like construct."
+  ;; This returns non-nil when POS is at just before "\verb...".
   (when pos (goto-char pos))
   (save-match-data
     (or (progn
           (syntax-propertize (point))
           (nth 3 (syntax-ppss)))
-        (member (LaTeX-current-verbatim-macro)
-                (LaTeX-verbatim-macros-with-delims))
-        (member (TeX-current-macro) (LaTeX-verbatim-macros-with-braces))
+        (LaTeX-verbatim-macro-boundaries)
         (member (LaTeX-current-environment) (LaTeX-verbatim-environments)))))
 
+(defun LaTeX-verbatim-nobreak-p ()
+  "Return non-nil if current position is in a verbatim-like construct.
+The macro body (\"\\verb\") and its delimiters, including optional
+argument if any, aren't considered as component of a verbatim-like construct."
+  ;; Intended for `fill-nobreak-predicate'.
+
+  ;; TODO: Factor out syntax propertize facility from font-latex.el
+  ;; and re-implement as major mode feature.
+  (if (eq TeX-install-font-lock 'font-latex-setup)
+      (progn
+        (syntax-propertize (point))
+        (nth 3 (syntax-ppss)))
+    ;; Fallback for users who stay away from font-latex.
+    (or
+     (let (region (LaTeX-verbatim-macro-boundaries t))
+       (and region
+            (<= (car region) (point) (cdr region))))
+     (member (LaTeX-current-environment) (LaTeX-verbatim-environments)))))
+
 
 ;;; Formatting
 
@@ -7960,7 +7999,7 @@ function would return non-nil and `(match-string 1)' would return
   ;; Cater for \verb|...| (and similar) contructs which should not be
   ;; broken.
   (add-to-list (make-local-variable 'fill-nobreak-predicate)
-               #'LaTeX-verbatim-p t)
+               #'LaTeX-verbatim-nobreak-p t)
 
   (or LaTeX-largest-level
       (setq LaTeX-largest-level (LaTeX-section-level "section")))