Subject: bug#78816: [PATCH] Add variable `fill-region-as-paragraph-function'

From: Roi Martin <[email-masked]>
Date: Wed, 18 Jun 2025 01:34:26 +0200

Stefan Monnier <[email-masked]> writes:

> I think I'd leave this alone and instead do
>
>     (defun fill-region-as-paragraph (&rest args)
>       (apply fill-region-as-paragraph-function args))
>
> and rename `fill-region-as-paragraph` as `fill-region-as-paragraph-default`.

Done.  However, I kept the parameters and the doc string in
`fill-region-as-paragraph', so almost nothing changed from the user
perspective.

Thanks for the review!  I've attached a new version of the patch.

       Roi

>From bda4f9cb256ca85f39298cf29f20ee5cbcb846fb Mon Sep 17 00:00:00 2001
From: Roi Martin <[email-masked]>
Date: Tue, 17 Jun 2025 10:45:13 +0200
Subject: [PATCH] Add variable `fill-region-as-paragraph-function'

Add the variable `fill-region-as-paragraph-function' to provide a way to
override how functions like `fill-region' fill text.

* doc/lispref/text.texi (Filling): Document
`fill-region-as-paragraph-function' variable.
* lisp/textmodes/fill.el (fill-region-as-paragraph-function): Add
variable.
(fill-region-as-paragraph): Convert into
`fill-region-as-paragraph-function' wrapper.
(fill-region-as-paragraph-default): Rename old
`fill-region-as-paragraph' function.
(fill-region-as-paragraph-semlf): Update calls to
`fill-region-as-paragraph-default'.
(fill-region): Add reference to `fill-region-as-paragraph-function' in
doc string.
* test/lisp/textmodes/fill-tests.el (fill-test-fill-region): Add test
case for the `fill-region' function.
* test/lisp/textmodes/fill-resources/fill-region.erts: Add test
data.  (Bug#78816)
---
 doc/lispref/text.texi                         |  8 ++
 lisp/textmodes/fill.el                        | 94 +++++++++++--------
 .../textmodes/fill-resources/fill-region.erts | 23 +++++
 test/lisp/textmodes/fill-tests.el             | 11 +++
 4 files changed, 98 insertions(+), 38 deletions(-)
 create mode 100644 test/lisp/textmodes/fill-resources/fill-region.erts

diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 75b2b1c3d60e..ed13b73971f3 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -1841,6 +1841,14 @@ Filling
 Manual}.
 @end defvar
 
+@defvar fill-region-as-paragraph-function
+This variable provides a way to override how functions like
+@code{fill-region} fill text.  Its value should be a function, which is
+called with the arguments defined by @code{fill-region-as-paragraph}.
+It should return the fill prefix used for filling.  The default value of
+this variable is @code{fill-region-as-paragraph-default}.
+@end defvar
+
 @defvar use-hard-newlines
 If this variable is non-@code{nil}, the filling functions do not delete
 newlines that have the @code{hard} text property.  These hard
diff --git a/lisp/textmodes/fill.el b/lisp/textmodes/fill.el
index 386a88bda3a8..e0d2bd2e1d37 100644
--- a/lisp/textmodes/fill.el
+++ b/lisp/textmodes/fill.el
@@ -644,34 +644,10 @@ fill-indent-to-left-margin
     (indent-line-to (current-left-margin))
     (put-text-property beg (point) 'face 'default)))
 
-(defun fill-region-as-paragraph (from to &optional justify
-				      nosqueeze squeeze-after)
+(defun fill-region-as-paragraph-default (from to &optional justify
+				              nosqueeze squeeze-after)
   "Fill the region as if it were a single paragraph.
-This command removes any paragraph breaks in the region and
-extra newlines at the end, and indents and fills lines between the
-margins given by the `current-left-margin' and `current-fill-column'
-functions.  (In most cases, the variable `fill-column' controls the
-width.)  It leaves point at the beginning of the line following the
-region.
-
-Normally, the command performs justification according to
-the `current-justification' function, but with a prefix arg, it
-does full justification instead.
-
-When called from Lisp, optional third arg JUSTIFY can specify any
-type of justification; see `default-justification' for the possible
-values.
-Optional fourth arg NOSQUEEZE non-nil means not to make spaces
-between words canonical before filling.
-Fifth arg SQUEEZE-AFTER, if non-nil, should be a buffer position; it
-means canonicalize spaces only starting from that position.
-See `canonically-space-region' for the meaning of canonicalization
-of spaces.
-
-Return the `fill-prefix' used for filling.
-
-If `sentence-end-double-space' is non-nil, then period followed by one
-space does not end a sentence, so don't break a line there."
+This is the default implementation of `fill-region-as-paragraph'."
   (interactive (progn
 		 (barf-if-buffer-read-only)
 		 (list (region-beginning) (region-end)
@@ -797,6 +773,45 @@ fill-region-as-paragraph
       ;; Return the fill-prefix we used
       fill-prefix)))
 
+(defvar fill-region-as-paragraph-function #'fill-region-as-paragraph-default
+  "Function to fill the region as if it were a single paragraph.
+It is called with the arguments defined by `fill-region-as-paragraph'.
+It should return the `fill-prefix' used for filling.")
+
+(defun fill-region-as-paragraph (from to &optional justify
+				      nosqueeze squeeze-after)
+  "Fill the region as if it were a single paragraph.
+This command removes any paragraph breaks in the region and
+extra newlines at the end, and indents and fills lines between the
+margins given by the `current-left-margin' and `current-fill-column'
+functions.  (In most cases, the variable `fill-column' controls the
+width.)  It leaves point at the beginning of the line following the
+region.
+
+Normally, the command performs justification according to
+the `current-justification' function, but with a prefix arg, it
+does full justification instead.
+
+When called from Lisp, optional third arg JUSTIFY can specify any
+type of justification; see `default-justification' for the possible
+values.
+Optional fourth arg NOSQUEEZE non-nil means not to make spaces
+between words canonical before filling.
+Fifth arg SQUEEZE-AFTER, if non-nil, should be a buffer position; it
+means canonicalize spaces only starting from that position.
+See `canonically-space-region' for the meaning of canonicalization
+of spaces.
+
+Return the `fill-prefix' used for filling.
+
+If `sentence-end-double-space' is non-nil, then period followed by one
+space does not end a sentence, so don't break a line there.
+
+The variable `fill-region-as-paragraph-function' can be used to override
+how text is filled."
+  (funcall fill-region-as-paragraph-function
+           from to justify nosqueeze squeeze-after))
+
 (defsubst skip-line-prefix (prefix)
   "If point is inside the string PREFIX at the beginning of line, move past it."
   (when (and prefix
@@ -1058,7 +1073,10 @@ fill-region
 Return the `fill-prefix' used for filling the last paragraph.
 
 If `sentence-end-double-space' is non-nil, then period followed by one
-space does not end a sentence, so don't break a line there."
+space does not end a sentence, so don't break a line there.
+
+The variable `fill-region-as-paragraph-function' can be used to override
+how paragraphs are filled."
   (interactive (progn
 		 (barf-if-buffer-read-only)
 		 (list (region-beginning) (region-end)
@@ -1618,19 +1636,19 @@ fill-region-as-paragraph-semlf
     (with-restriction (line-beginning-position) to
       (let ((fill-column (point-max)))
         (setq pfx (or (save-excursion
-                        (fill-region-as-paragraph (point)
-                                                  (point-max)
-                                                  nil
-                                                  nosqueeze
-                                                  squeeze-after))
+                        (fill-region-as-paragraph-default (point)
+                                                          (point-max)
+                                                          nil
+                                                          nosqueeze
+                                                          squeeze-after))
                       "")))
       (while (not (eobp))
         (let ((fill-prefix pfx))
-	  (fill-region-as-paragraph (point)
-				    (progn (forward-sentence) (point))
-				    justify
-                                    nosqueeze
-                                    squeeze-after))
+	  (fill-region-as-paragraph-default (point)
+				            (progn (forward-sentence) (point))
+				            justify
+                                            nosqueeze
+                                            squeeze-after))
         (when (and (> (point) (line-beginning-position))
 		   (< (point) (line-end-position)))
 	  (delete-horizontal-space)
diff --git a/test/lisp/textmodes/fill-resources/fill-region.erts b/test/lisp/textmodes/fill-resources/fill-region.erts
new file mode 100644
index 000000000000..95e08248309f
--- /dev/null
+++ b/test/lisp/textmodes/fill-resources/fill-region.erts
@@ -0,0 +1,23 @@
+Point-Char: |
+
+Name: fill region
+
+=-=
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+|Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+=-=
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
+eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor.  Incididunt ut labore et dolore magna aliqua.
+=-=-=
diff --git a/test/lisp/textmodes/fill-tests.el b/test/lisp/textmodes/fill-tests.el
index 8fbd9919bad0..40095fc99417 100644
--- a/test/lisp/textmodes/fill-tests.el
+++ b/test/lisp/textmodes/fill-tests.el
@@ -122,6 +122,17 @@ test-fill-haskell
   ;; w
 ")))
 
+(ert-deftest fill-test-fill-region ()
+  "Test the `fill-region' function."
+  (ert-test-erts-file (ert-resource-file "fill-region.erts")
+                      (lambda ()
+			(fill-region
+                         (point)
+                         (progn
+                           (goto-char (point-max))
+                           (forward-line -1)
+                           (beginning-of-line)
+                           (point))))))
 
 (ert-deftest fill-test-fill-region-as-paragraph-semlf ()
   "Test the `fill-region-as-paragraph-semlf' function."
-- 
2.49.0