From d5d0d48c4f2e606440957c74bdcf0337a7025a8b Mon Sep 17 00:00:00 2001 From: Roi Martin 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) (cherry picked from commit 99ec286d7803fa59f770013a2135f23918510103) --- 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 2950618f35f..09fa846be32 100644 --- a/doc/emacs/text.texi +++ b/doc/emacs/text.texi @@ -600,12 +600,15 @@ can also call @kbd{M-x fill-region} to specifically fill the text in 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 0b3698240f1..dff979c92c5 100644 --- a/doc/lispref/text.texi +++ b/doc/lispref/text.texi @@ -1739,7 +1739,12 @@ described above. @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}. @@ -1843,6 +1848,14 @@ paragraphs actually moved. The default value of this variable is 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 1583ba4df05..bb10890141d 100644 --- a/lisp/textmodes/fill.el +++ b/lisp/textmodes/fill.el @@ -647,8 +647,8 @@ The break position will be always after LINEBEG and generally before point." (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 @@ -800,6 +800,39 @@ space does not end a sentence, so don't break a line there." ;; 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 @@ -1061,7 +1094,10 @@ if variable `use-hard-newlines' is on). 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) @@ -1621,19 +1657,19 @@ For more details about semantic linefeeds, see `fill-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 00000000000..95e08248309 --- /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 8fbd9919bad..40095fc9941 100644 --- a/test/lisp/textmodes/fill-tests.el +++ b/test/lisp/textmodes/fill-tests.el @@ -122,6 +122,17 @@ eius. Foo"))) ;; 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.39.5