Subject: Re: bug#77544: (WIP) [PATCH] Prettify page separators.

From: Elijah Gabe Pérez <[email-masked]>
Date: Fri, 08 Aug 2025 23:09:03 -0600

Sorry for the delay, i tried to make the strike-through face property
support the same line styles as the underline, but i couldn't figure out
how, as it seems that strike-through is designed to support only 1
style.

My solution was to make a kind of hack to display the underline as
strike-through, and it worked.

Another solution was to reuse the code from display space type, for make
a new display property type (as suggested by Gerd), but I ended up
getting lost when I saw too many references to this in the source code,
so for now the hack was the only solution I could think of.

This should work well in both GUI and TUI frames.

In GUI, the line will be displayed as a double horizontal line (as
shown in the screenshot)

And in TUI, it will be displayed as a normal underline (or
strike-through if underline is not supported).

Optionally, I've added a new minor mode to enable this outside
whitespace-mode (an example of use in this would be in the NEWS file or
in *Compile-Log* buffers).

>From 751e8c3ab69fca962afcaa20e3b3fc23e5d6a2b7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?El=C3=ADas=20Gabriel=20P=C3=A9rez?= <[email-masked]>
Date: Fri, 4 Apr 2025 22:17:23 -0600
Subject: [PATCH] whitespace-mode: Prettify page delimiters characters. 
 (bug#77544)

This makes whitespace-mode display the page delimiter character
"^L" as a pretty horizontal line.

This also add a new minor mode to display these character
outside of whitespace-mode.

* lisp/whitespace.el (whitespace-style): Add page-delimiters
option.
(whitespace-page-delimiter): New face.
(whitespace--page-delimiters-keyword): New variable.
(whitespace-page-delimiters-mode): New minor mode.
(whitespace-style-face-p, whitespace-color-on): Rework for
display the page-delimiters.
---
 etc/NEWS           |  8 ++++++
 lisp/whitespace.el | 69 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 77 insertions(+)

diff --git a/etc/NEWS b/etc/NEWS
index e12a9dcb127..1bd901c2960 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1112,6 +1112,14 @@ slot.  Symbol macros are created for the accessor functions using
 If 'whitespace-style' includes 'missing-newline-at-eof' (which is the
 default), the 'whitespace-cleanup' function will now add the newline.
 
+---
+*** 'whitespace-mode' now can prettify page delimiters characters (^L).
+If 'page-delimiters' is set in 'whitespace-style', or the new minor mode
+'whitespace-page-delimiters-mode' is on, the page delimiters characters
+(^L) are displayed as a pretty horizontal line that spans the entire
+width of the window.  The new 'whitespace-page-delimiter' face, can be
+used for customize the appearence
+
 ** Bookmark
 
 ---
diff --git a/lisp/whitespace.el b/lisp/whitespace.el
index 58ba0db8c90..79afb8cf298 100644
--- a/lisp/whitespace.el
+++ b/lisp/whitespace.el
@@ -326,6 +326,11 @@ whitespace-style
                         This has effect only if `face' (see above)
                         is present in `whitespace-style'.
 
+  page-delimiters       visualize page delimiters characters (^L)
+                        as horizontal lines.
+                        This has effect only if `face' (see above)
+                        is present in `whitespace-style'.
+
    empty                visualize empty lines at beginning and/or
                         end of buffer via faces.
                         This has effect only if `face' (see above)
@@ -440,6 +445,7 @@ whitespace-style
               (const :tag "(Face) NEWLINEs" newline)
               (const :tag "(Face) Missing newlines at EOB"
                      missing-newline-at-eof)
+              (const :tag "(Face) Page delimiters" page-delimiters)
               (const :tag "(Face) Empty Lines At BOB And/Or EOB" empty)
               (const :tag "(Face) Indentation SPACEs" indentation::tab)
               (const :tag "(Face) Indentation TABs"
@@ -622,6 +628,14 @@ whitespace-space-after-tab
 
 See `whitespace-space-after-tab-regexp'.")
 
+(defface whitespace-page-delimiter
+  '((((supports :underline (:color foreground-color  :style double-line)))
+     :underline (:color foreground-color :style double-line)
+     :extend t :inherit shadow)
+    (((supports :strike-through t))
+     :strike-through t :extend t :inherit shadow)
+    (t :extend t :inherit shadow :inverse-video t))
+  "Face used to visualize page delimiter characters.")
 
 (defcustom whitespace-hspace-regexp
   "\\(\u00A0+\\)"
@@ -934,6 +948,29 @@ whitespace-action
 			  (const :tag "Abort On Bogus" abort-on-bogus)
 			  (const :tag "Warn If Read-Only" warn-if-read-only)))))
 
+(defvar whitespace--page-delimiters-keyword
+  `((,(concat page-delimiter "\n") 0
+     (prog1 nil
+       ;; HACK: Due :strike-through, doesn't support the same line
+       ;; styles as in :underline, for GUI frames raise the invisible ^L
+       ;; character to give the illusion of a centered line (unless
+       ;; whitespace-page-delimiter doesn't have the :underline attribute).
+       ;; In TUI, this is only displayed as an normal underline.
+       ;; FIXME: Rewrite this when :strike-through supports this.
+       (if (not (eq (internal-get-lisp-face-attribute
+                     'whitespace-page-delimiter :underline)
+                    'unspecified))
+           (progn
+             ;; :underline
+             (put-text-property (match-beginning 0) (1- (match-end 0))
+                                'display (propertize " " 'display '(raise 0.5)))
+             (put-text-property (1- (match-end 0)) (match-end 0) 'display '(raise 0.5))
+             (add-face-text-property (match-beginning 0) (match-end 0) '(:inherit whitespace-page-delimiter :height 0.5)))
+         ;; :strike-through
+         (put-text-property (match-beginning 0) (1- (match-end 0)) 'display " ")
+         (add-face-text-property (match-beginning 0) (match-end 0) 'whitespace-page-delimiter)))))
+  "Used to add page delimiters keywords to `whitespace-font-lock-keywords'.")
+
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;; User commands - Local mode
@@ -976,6 +1013,25 @@ whitespace-newline-mode
   ;; sync states (running a batch job)
   (setq whitespace-newline-mode whitespace-mode))
 
+;;;###autoload
+(define-minor-mode whitespace-page-delimiters-mode
+  "Display page delimiters characters as horizontal lines."
+  :group 'whitespace
+  (cond
+   ((and whitespace-mode (not (memq 'page-delimiters whitespace-style)))
+    (setq-local whitespace-style
+                (cons 'page-delimiters whitespace-style))
+    (whitespace-mode +1))
+   (whitespace-page-delimiters-mode
+    (font-lock-add-keywords nil whitespace--page-delimiters-keyword)
+    (unless (memq 'display font-lock-extra-managed-props)
+      (setq-local font-lock-extra-managed-props
+                  (cons 'display font-lock-extra-managed-props)))
+    (font-lock-flush))
+   (t
+    (font-lock-remove-keywords nil whitespace--page-delimiters-keyword)
+    (font-lock-flush))))
+
 
 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 ;;;; User commands - Global mode
@@ -1044,6 +1100,7 @@ whitespace-style-value-list
     tabs
     spaces
     trailing
+    page-delimiters
     lines
     lines-tail
     lines-char
@@ -1071,6 +1128,7 @@ whitespace-toggle-option-alist
   '((?f    . face)
     (?t    . tabs)
     (?s    . spaces)
+    (?p    . page-delimiters)
     (?r    . trailing)
     (?l    . lines)
     (?L    . lines-tail)
@@ -1156,6 +1214,7 @@ whitespace-toggle-options
    t	toggle TAB visualization
    s	toggle SPACE and HARD SPACE visualization
    r	toggle trailing blanks visualization
+   p	toggle page delimiters visualization
    l	toggle \"long lines\" visualization
    L	toggle \"long lines\" tail visualization
    n	toggle NEWLINE visualization
@@ -1186,6 +1245,7 @@ whitespace-toggle-options
    tabs			toggle TAB visualization
    spaces		toggle SPACE and HARD SPACE visualization
    trailing		toggle trailing blanks visualization
+   page-delimiters	toggle page delimiters visualization
    lines		toggle \"long lines\" visualization
    lines-tail		toggle \"long lines\" tail visualization
    newline		toggle NEWLINE visualization
@@ -1237,6 +1297,7 @@ global-whitespace-toggle-options
    t	toggle TAB visualization
    s	toggle SPACE and HARD SPACE visualization
    r	toggle trailing blanks visualization
+   p	toggle page delimiters visualization
    l	toggle \"long lines\" visualization
    L	toggle \"long lines\" tail visualization
    C-l	toggle \"long lines\" one character visualization
@@ -1268,6 +1329,7 @@ global-whitespace-toggle-options
    tabs			toggle TAB visualization
    spaces		toggle SPACE and HARD SPACE visualization
    trailing		toggle trailing blanks visualization
+   page-delimiters	toggle page delimiters visualization
    lines		toggle \"long lines\" visualization
    lines-tail		toggle \"long lines\" tail visualization
    lines-char		toggle \"long lines\" one character visualization
@@ -2035,6 +2097,7 @@ whitespace-style-face-p
 	   (memq 'lines-tail              whitespace-active-style)
 	   (memq 'lines-char              whitespace-active-style)
 	   (memq 'newline                 whitespace-active-style)
+           (memq 'page-delimiters         whitespace-active-style)
 	   (memq 'empty                   whitespace-active-style)
 	   (memq 'indentation             whitespace-active-style)
 	   (memq 'indentation::tab        whitespace-active-style)
@@ -2108,6 +2171,12 @@ whitespace-color-on
                 ;; first overflowing character
                 ((memq 'lines-char whitespace-active-style) 3))
               whitespace-line prepend)))
+       ,@(when (memq 'page-delimiters whitespace-active-style)
+           (unless (memq 'display font-lock-extra-managed-props)
+             (setq-local font-lock-extra-managed-props
+                         (cons 'display font-lock-extra-managed-props)))
+           ;; Show page delimiters characters
+           whitespace--page-delimiters-keyword)
        ,@(when (or (memq 'space-before-tab whitespace-active-style)
                    (memq 'space-before-tab::tab whitespace-active-style)
                    (memq 'space-before-tab::space whitespace-active-style))
-- 
2.50.1

-- 
- E.G via Gnus and Org.