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

From: Roi Martin <[email-masked]>
Date: Thu, 19 Jun 2025 19:45:57 +0200

Eli Zaretskii <[email-masked]> writes:

> I have a problem with the documentation of the functions and commands
> we had before, which can now be overridden by the new
> fill-region-as-paragraph-function.  The documentation was basically
> left unaltered as to what filling does, with just a single sentence,
> at the end of a very long doc string, saying that
> fill-region-as-paragraph-function could change the behavior.  But the
> truth is that such a function can completely change the result and
> invalidate the description in the doc string; in particular, the
> function which implements semantic line-feeds does exactly that.
>
> So at the very least, the doc strings should:
>
>  . mention the fill-region-as-paragraph-function early on
>  . say that the rest of the description is about the default function,
>    mentioning its symbol
>
> I also think some minimal addition to the user manual is in order, to
> the effect that the manual describes the default filling style, while
> the major mode or the user could change it via
> fill-region-as-paragraph-function.

Please, find attached a new version of the patch with the following
changes:

- In the user manual, I added a reference to
  `fill-region-as-paragraph-function'.  I also made explicit that the
  `fill-region-as-paragraph' behavior described by the manual
  corresponds to the default implementation.
- In the Emacs Lisp reference, I made explicit that the behavior
  described for `fill-region-as-paragraph' corresponds to the default
  implementation.
- Moved the original docstring of `fill-region-as-paragraph' to
  `fill-region-as-paragraph-default' given that it describes its
  specific behavior.
- I used the docstring of `fill-region-as-paragraph' to describe the
  general purpose of the command and the expected arguments.  I also
  moved the mention to `fill-region-as-paragraph-function' to the
  beginning of the docstring.
- I noticed that, after my previous patches, `fill-region-as-paragraph'
  was not a command anymore.  Now it is a command again.

        Roi

>From 71483d1d149970b2d52c1244244512ec5ae4e023 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.
* doc/emacs/text.texi (Fill Commands): Reference
`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/emacs/text.texi                           | 11 ++--
 doc/lispref/text.texi                         | 15 ++++-
 lisp/textmodes/fill.el                        | 62 +++++++++++++++----
 .../textmodes/fill-resources/fill-region.erts | 23 +++++++
 test/lisp/textmodes/fill-tests.el             | 11 ++++
 5 files changed, 104 insertions(+), 18 deletions(-)
 create mode 100644 test/lisp/textmodes/fill-resources/fill-region.erts

diff --git a/doc/emacs/text.texi b/doc/emacs/text.texi
index 5635171f5cfe..2708b2e93121 100644
--- a/doc/emacs/text.texi
+++ b/doc/emacs/text.texi
@@ -600,12 +600,15 @@ Fill Commands
 the region.
 
 @findex fill-region-as-paragraph
+@vindex fill-region-as-paragraph-function
   @kbd{M-q} and @code{fill-region} use the usual Emacs criteria for
 finding paragraph boundaries (@pxref{Paragraphs}).  For more control,
-you can use @kbd{M-x fill-region-as-paragraph}, which refills
-everything between point and mark as a single paragraph.  This command
-deletes any blank lines within the region, so separate blocks of text
-end up combined into one block.
+you can use @kbd{M-x fill-region-as-paragraph}, which refills everything
+between point and mark as a single paragraph.  The behavior of this
+command is controlled by the variable
+@code{fill-region-as-paragraph-function}.  By default, it deletes any
+blank lines within the region, so separate blocks of text end up
+combined into one block.
 
 @cindex justification
   A numeric argument to @kbd{M-q} tells it to @dfn{justify} the text
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 75b2b1c3d60e..9ab06b6d613c 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -1737,7 +1737,12 @@ Filling
 
 @deffn Command fill-region-as-paragraph start end &optional justify nosqueeze squeeze-after
 This command considers a region of text as a single paragraph and fills
-it.  If the region was made up of many paragraphs, the blank lines
+it.  The behavior of this command is controlled by the variable
+@code{fill-region-as-paragraph-function}, with the default
+implementation being @code{fill-region-as-paragraph-default}, which is
+described in detail below.
+
+If the region was made up of many paragraphs, the blank lines
 between paragraphs are removed.  This function justifies as well as
 filling when @var{justify} is non-@code{nil}.
 
@@ -1841,6 +1846,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
+should accept the arguments defined by @code{fill-region-as-paragraph}
+and 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..c552add7f8f0 100644
--- a/lisp/textmodes/fill.el
+++ b/lisp/textmodes/fill.el
@@ -644,8 +644,8 @@ 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
@@ -797,6 +797,39 @@ 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 should accept the arguments defined by `fill-region-as-paragraph' and
+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.
+The behavior of this command is controlled by the variable
+`fill-region-as-paragraph-function', with the default implementation
+being `fill-region-as-paragraph-default'.
+
+The arguments FROM and TO define the boundaries of the region.
+
+The optional third argument JUSTIFY, when called interactively with a
+prefix arg, is assigned the value `full'.
+When called from Lisp, 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.
+
+It returns the `fill-prefix' used for filling."
+  (interactive (progn
+		 (barf-if-buffer-read-only)
+		 (list (region-beginning) (region-end)
+		       (if current-prefix-arg 'full))))
+  (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 +1091,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 +1654,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