1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-21 06:55:39 +00:00

Fix deletion of text holding `inhibit-read-only' properties

* src/intervals.h (INTERVAL_VISIBLE_P): Split into ...
(INTERVAL_GENERALLY_WRITABLE_P, INTERVAL_EXPRESSLY_WRITABLE_P):
... two new macros.

* src/textprop.c (verify_interval_modification): If the buffer
is read only, verify not that there is only a single exempting
interval spanning the area of a multiple-character operation,
but that every intervening interval in such an operation exempts
it from write restrictions, either by providing a read-only
property that appears in Vinhibit_read_only, or by providing an
inhibit-read-only property.

* test/src/textprop-tests.el (textprop-interval-immutability):
New test.
This commit is contained in:
Po Lu 2024-04-30 18:26:39 +08:00
parent aad80e1934
commit b36fd07560
3 changed files with 92 additions and 15 deletions

View File

@ -204,14 +204,21 @@ set_interval_plist (INTERVAL i, Lisp_Object plist)
#define INTERVAL_VISIBLE_P(i) \
(i && NILP (textget ((i)->plist, Qinvisible)))
/* Is this interval writable? Replace later with cache access. */
#define INTERVAL_WRITABLE_P(i) \
(NILP (textget ((i)->plist, Qread_only)) \
|| !NILP (textget ((i)->plist, Qinhibit_read_only)) \
|| ((CONSP (Vinhibit_read_only) \
? !NILP (Fmemq (textget ((i)->plist, Qread_only), \
Vinhibit_read_only)) \
: !NILP (Vinhibit_read_only))))
/* Is this interval writable by virtue of not being marked read-only, or
a general value of Vinhibit_read_only? Replace later with cache
access. */
#define INTERVAL_GENERALLY_WRITABLE_P(i, ro) \
(NILP (ro) || (!NILP (Vinhibit_read_only) \
&& !CONSP (Vinhibit_read_only)))
/* Is this interval writable by virtue of an explicit inhibit-read-only
property, or the specific presence of its Qread_only property in
Vinhibit_read_only? */
#define INTERVAL_EXPRESSLY_WRITABLE_P(i, ro) \
(!NILP (textget ((i)->plist, Qinhibit_read_only)) \
|| (!NILP (ro) \
&& CONSP (Vinhibit_read_only) \
&& !NILP (Fmemq ((ro), Vinhibit_read_only))))
/* Macros to tell whether insertions before or after this interval
should stick to it. Now we have Vtext_property_default_nonsticky,

View File

@ -2186,6 +2186,7 @@ verify_interval_modification (struct buffer *buf,
{
INTERVAL intervals = buffer_intervals (buf);
INTERVAL i;
ptrdiff_t p;
Lisp_Object hooks;
Lisp_Object prev_mod_hooks;
Lisp_Object mod_hooks;
@ -2314,14 +2315,30 @@ verify_interval_modification (struct buffer *buf,
}
else
{
bool buffer_read_only;
/* Loop over intervals on or next to START...END,
collecting their hooks. */
/* Extent of last writable interval. */
i = find_interval (intervals, start);
p = 0;
buffer_read_only = (!NILP (BVAR (current_buffer, read_only))
&& NILP (Vinhibit_read_only));
do
{
if (! INTERVAL_WRITABLE_P (i))
text_read_only (textget (i->plist, Qread_only));
bool implied, express;
Lisp_Object read_only;
read_only = textget ((i)->plist, Qread_only);
implied = INTERVAL_GENERALLY_WRITABLE_P (i, read_only);
express = INTERVAL_EXPRESSLY_WRITABLE_P (i, read_only);
if (!implied && !express)
text_read_only (read_only);
/* If this interval is only implicitly read only and the
buffer is read only as a whole, signal an error. */
else if (!express && buffer_read_only)
xsignal1 (Qbuffer_read_only, Fcurrent_buffer ());
if (!inhibit_modification_hooks)
{
@ -2333,16 +2350,18 @@ verify_interval_modification (struct buffer *buf,
}
}
if (i->position + LENGTH (i) < end
&& (!NILP (BVAR (current_buffer, read_only))
&& NILP (Vinhibit_read_only)))
xsignal1 (Qbuffer_read_only, Fcurrent_buffer ());
p = i->position + LENGTH (i);
i = next_interval (i);
}
/* Keep going thru the interval containing the char before END. */
while (i && i->position < end);
/* Should the buffer be read only while the last interval with an
`inhibit-read-only' property does not enclose the entire change
under consideration, signal error. */
if (p < end && buffer_read_only)
xsignal1 (Qbuffer_read_only, Fcurrent_buffer ());
if (!inhibit_modification_hooks)
{
hooks = Fnreverse (hooks);

View File

@ -68,5 +68,56 @@
(should (and (equal-including-properties (pop stack) string)
(null stack)))))
(ert-deftest textprop-interval-immutability ()
"Test modification of text with properties affecting mutability."
(let ((template (concat
(propertize "12345" 'inhibit-read-only t) ; 1-5
(propertize "67890" 'read-only 'abcdefg) ; 6-10
(propertize "ABCDE" 'inhibit-read-only t) ; 11-15
(propertize "FGHIJ" 'inhibit-read-only 'yes) ; 16-20
"KLMNO" ; 21-25
(propertize "PQRST" 'inhibit-read-only 't) ; 26-30
(propertize "UVWXYZ" 'read-only 'not-suppressed)))
inhibit-read-only)
(with-temp-buffer
(insert template)
(setq buffer-read-only t)
;; Delete an entire inhibit-read-only region.
(progn (should (equal (delete-and-extract-region 1 6)
"12345"))
(let ((inhibit-read-only t)) (erase-buffer)
(insert template)))
;; Delete multiple characters inside an inhibit-read-only section.
(progn (should (equal (delete-and-extract-region 2 5)
"234"))
(let ((inhibit-read-only t)) (erase-buffer)
(insert template)))
;; Attempt to delete characters across both an inhibit-read-only
;; and a read only region.
(setq buffer-read-only nil)
(should-error (delete-and-extract-region 4 7))
(setq inhibit-read-only '(abcdefg))
;; Attempt the same, but with the read-only property of the second
;; section suppressed.
(progn (should (equal (delete-and-extract-region 4 7) "456"))
(let ((inhibit-read-only t)) (erase-buffer)
(insert template)))
(setq buffer-read-only t)
;; Delete text across the suppressed read-only region and two
;; other inhibit-read-only regions each with distinct intervals.
(progn (should (equal (delete-and-extract-region 7 17)
"7890ABCDEF"))
(let ((inhibit-read-only t)) (erase-buffer)
(insert template)))
(setq inhibit-read-only nil)
;; Attempt to delete text spanning two inhibit-read-only sections
;; separated by immutable text.
(should-error (delete-and-extract-region 17 27))
(setq inhibit-read-only '(abcdefg))
;; Attempt to delete text from the start of an inhibit-read-only
;; section extending into protected text exempt from
;; `inhibit-read-only''s influence towards the end of the buffer.
(should-error (delete-and-extract-region 26 37)))))
(provide 'textprop-tests)
;;; textprop-tests.el ends here