1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-26 07:11:34 -08:00

Clean up text properties in 'visual-wrap-prefix-mode'

Before refontifying a region, remove any text properties we care about
so that we don't end up with stray properties.  Additionally, make sure
to remove all the properties when deactivating the mode.

* lisp/emacs-lisp/subr-x.el (add-remove--display-text-property): New
function, extracted from...
(add-display-text-property): ... here.
(remove-display-text-property): New function.

* lisp/visual-wrap.el (visual-wrap--remove-properties): New function...
(visual-wrap-prefix-function, visual-wrap-prefix-mode): ... call it.

* test/lisp/emacs-lisp/subr-x-tests.el
(subr-x-test-remove-display-text-property): New test.

* test/lisp/visual-wrap-tests.el
(visual-wrap-tests/wrap-prefix-stickiness, visual-wrap-tests/cleanup):
New tests.

* doc/lispref/display.texi (Display Property): Document
'remove-display-text-property'.

* etc/NEWS: Announce 'remove-display-text-property' (bug#76018).
This commit is contained in:
Jim Porter 2025-05-28 09:44:34 -07:00
parent 24e6cd4233
commit 90c0c9a01e
6 changed files with 174 additions and 23 deletions

View file

@ -5302,6 +5302,44 @@ specification.
If omitted, @var{object} defaults to the current buffer.
@end defun
@defun remove-display-text-property start end spec &optional object
Remove the display specification @var{spec} from the text from
@var{start} to @var{end}. @var{spec} is the @sc{car} of the display
specification to remove, e.g.@: @code{height} or @code{'(margin nil)}.
If any text in the region has any other @code{display} properties, those
properties are retained. For instance:
@lisp
@group
(add-display-text-property 1 8 'raise 0.5)
(add-display-text-property 4 8 'height 2.0)
(remove-display-text-property 2 6 'raise)
@end group
@end lisp
After doing this, the text will have the following @code{display}
properties:
@itemize @bullet
@item
The region from 1 to 2, only @code{raise}
@item
The region from 2 to 4, no properties
@item
The region from 4 to 6, only @code{height}
@item
The region from 6 to 8, both @code{raise} and @code{height}
@end itemize
@var{object} is either a string or a buffer to remove the specification
from. If omitted, @var{object} defaults to the current buffer.
@end defun
@cindex display property, unsafe evaluation
@cindex security, and display specifications
Some of the display specifications allow inclusion of Lisp forms,

View file

@ -2413,6 +2413,12 @@ This 'display' property was previously supported only as text property.
Now overlays can also have this property, with the same effect for the
text "covered" by the overlay.
+++
** New function 'remove-display-text-property'.
This function removes a display property from the specified region of
text, preserving any other display properties already set for that
region.
+++
** New macro 'cond*'.
The new macro 'cond*' is an alternative to 'cond' and 'pcase'.

View file

@ -416,28 +416,25 @@ indivisible unit."
(setq start (1+ start))))
(nreverse result)))
;;;###autoload
(defun add-display-text-property (start end spec value &optional object)
"Add the display specification (SPEC VALUE) to the text from START to END.
If any text in the region has a non-nil `display' property, the existing
display specifications are retained.
OBJECT is either a string or a buffer to add the specification to.
If omitted, OBJECT defaults to the current buffer."
(defun add-remove--display-text-property (start end spec value
&optional object remove)
(let ((sub-start start)
(sub-end 0)
(limit (if (stringp object)
(min (length object) end)
(min end (point-max))))
disp)
(while (< sub-end end)
(setq sub-end (next-single-property-change sub-start 'display object
(if (stringp object)
(min (length object) end)
(min end (point-max)))))
limit))
(if (not (setq disp (get-text-property sub-start 'display object)))
;; No old properties in this range.
(unless remove
(put-text-property sub-start sub-end 'display (list spec value)
object)
object))
;; We have old properties.
(let (type)
(let ((changed nil)
type)
;; Make disp into a list.
(setq disp
(cond
@ -460,14 +457,41 @@ If omitted, OBJECT defaults to the current buffer."
;; regions of text.
(setq disp (if (eq type 'list)
(remove old disp)
(delete old disp))))
(setq disp (cons (list spec value) disp))
(delete old disp))
changed t))
(unless remove
(setq disp (cons (list spec value) disp)
changed t))
(when changed
(if (not disp)
(remove-text-properties sub-start sub-end '(display nil) object)
(when (eq type 'vector)
(setq disp (seq-into disp 'vector)))
;; Finally update the range.
(put-text-property sub-start sub-end 'display disp object)))
(put-text-property sub-start sub-end 'display disp object)))))
(setq sub-start sub-end))))
;;;###autoload
(defun add-display-text-property (start end spec value &optional object)
"Add the display specification (SPEC VALUE) to the text from START to END.
If any text in the region has a non-nil `display' property, the existing
display specifications are retained.
OBJECT is either a string or a buffer to add the specification to.
If omitted, OBJECT defaults to the current buffer."
(add-remove--display-text-property start end spec value object))
;;;###autoload
(defun remove-display-text-property (start end spec &optional object)
"Remove the display specification SPEC from the text from START to END.
SPEC is the car of the display specification to remove, e.g. `height'.
If any text in the region has other display specifications, those specs
are retained.
OBJECT is either a string or a buffer to remove the specification from.
If omitted, OBJECT defaults to the current buffer."
(add-remove--display-text-property start end spec nil object 'remove))
;;;###autoload
(defun read-process-name (prompt)
"Query the user for a process and return the process object."

View file

@ -226,6 +226,14 @@ by `visual-wrap-extra-indent'."
(propertize prefix 'face face)
prefix)))
(defun visual-wrap--remove-properties (start end)
"Remove visual wrapping text properties from START to END."
;; Remove `min-width' from any prefixes we detected.
(remove-display-text-property start end 'min-width)
;; Remove `wrap-prefix' related properties from any lines with
;; prefixes we detected.
(remove-text-properties start end '(wrap-prefix nil)))
(defun visual-wrap-prefix-function (beg end)
"Indent the region between BEG and END with visual filling."
;; Any change at the beginning of a line might change its wrap
@ -238,6 +246,7 @@ by `visual-wrap-extra-indent'."
(goto-char beg)
(forward-line 0)
(setq beg (point))
(visual-wrap--remove-properties beg end)
(while (< (point) end)
;; Check if the display property at the end of this line is "safe".
(if (visual-wrap--display-property-safe-p
@ -283,7 +292,7 @@ To enable this minor mode across all buffers, enable
(with-silent-modifications
(save-restriction
(widen)
(remove-text-properties (point-min) (point-max) '(wrap-prefix nil))))))
(visual-wrap--remove-properties (point-min) (point-max))))))
;;;###autoload
(define-globalized-minor-mode global-visual-wrap-prefix-mode

View file

@ -740,6 +740,44 @@
4 8 (display ((raise 0.5) (height 2.0)))
8 12 (display (raise 0.5)))))))
(ert-deftest subr-x-test-remove-display-text-property ()
(with-temp-buffer
(insert "Foo bar zot gazonk")
(add-display-text-property 4 12 'height 2.0)
(add-display-text-property 2 8 'raise 0.5)
(remove-display-text-property 6 10 'height)
(should (equal-including-properties
(buffer-string)
#("Foo bar zot gazonk"
1 3 (display (raise 0.5))
3 5 (display ((raise 0.5) (height 2.0)))
5 7 (display ((raise 0.5)))
9 11 (display (height 2.0))))))
(with-temp-buffer
(insert "Foo bar zot gazonk")
(put-text-property 4 12 'display [(height 2.0)])
(add-display-text-property 2 8 'raise 0.5)
(remove-display-text-property 6 10 'height)
(should (equal-including-properties
(buffer-string)
#("Foo bar zot gazonk"
1 3 (display (raise 0.5))
3 5 (display [(raise 0.5) (height 2.0)])
5 7 (display [(raise 0.5)])
9 11 (display [(height 2.0)])))))
(with-temp-buffer
(should (equal-including-properties
(let ((str (copy-sequence "Foo bar zot gazonk")))
(add-display-text-property 3 11 'height 2.0 str)
(add-display-text-property 1 7 'raise 0.5 str)
(remove-display-text-property 5 9 'height str)
str)
#("Foo bar zot gazonk"
1 3 (display (raise 0.5))
3 5 (display ((raise 0.5) (height 2.0)))
5 7 (display ((raise 0.5)))
9 11 (display (height 2.0)))))))
(ert-deftest subr-x-named-let ()
(let ((funs ()))
(named-let loop

View file

@ -1,6 +1,6 @@
;;; visual-wrap-tests.el --- Tests for `visual-wrap-prefix-mode' -*- lexical-binding: t; -*-
;; Copyright (C) 2024 Free Software Foundation, Inc.
;; Copyright (C) 2024-2025 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
@ -19,7 +19,7 @@
;;; Commentary:
;; Tets for `visual-wrap-prefix-mode'.
;; Tests for `visual-wrap-prefix-mode'.
;;; Code:
@ -117,4 +117,40 @@ should *not* add wrapping properties to either block."
0 4 (display ((image :type bmp)))
4 8 (display ((image :type bmp) (height 1.5))))))))
(ert-deftest visual-wrap-tests/wrap-prefix-stickiness ()
"Test that `wrap-prefix' doesn't persist across multiple lines when typing.
See bug#76018."
(with-temp-buffer
(insert "* this zoo contains goats")
(visual-wrap-prefix-function (point-min) (point-max))
(should (equal-including-properties
(buffer-string)
#("* this zoo contains goats"
0 2 ( wrap-prefix (space :align-to (2 . width))
display (min-width ((2 . width))))
2 25 ( wrap-prefix (space :align-to (2 . width))))))
(let ((start (point)))
(insert-and-inherit "\n\nit also contains pandas")
(visual-wrap-prefix-function start (point-max)))
(should (equal-including-properties
(buffer-string)
#("* this zoo contains goats\n\nit also contains pandas"
0 2 ( wrap-prefix (space :align-to (2 . width))
display (min-width ((2 . width))))
2 25 ( wrap-prefix (space :align-to (2 . width))))))))
(ert-deftest visual-wrap-tests/cleanup ()
"Test that deactivating `visual-wrap-prefix-mode' cleans up text properties."
(with-temp-buffer
(insert "* hello\n* hi")
(visual-wrap-prefix-function (point-min) (point-max))
;; Make sure we've added the visual-wrapping properties.
(should (equal (text-properties-at (point-min))
'( wrap-prefix (space :align-to (2 . width))
display (min-width ((2 . width))))))
(visual-wrap-prefix-mode -1)
(should (equal-including-properties
(buffer-string)
"* hello\n* hi"))))
;; visual-wrap-tests.el ends here