* 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.
(cherry picked from commit
b36fd07560fd12c5e819e808a6f0eb9579f77c25)
#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,
{
INTERVAL intervals = buffer_intervals (buf);
INTERVAL i;
+ ptrdiff_t p;
Lisp_Object hooks;
Lisp_Object prev_mod_hooks;
Lisp_Object mod_hooks;
}
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)
{
}
}
- 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);
(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