1 /* Interface code for dealing with text properties.
2 Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
4 This file is part of GNU Emacs.
6 GNU Emacs is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Emacs is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Emacs; see the file COPYING. If not, write to
18 the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
22 #include "intervals.h"
27 #define NULL (void *)0
30 /* Test for membership, allowing for t (actually any non-cons) to mean the
33 #define TMEM(sym, set) (CONSP (set) ? ! NILP (Fmemq (sym, set)) : ! NILP (set))
36 /* NOTES: previous- and next- property change will have to skip
37 zero-length intervals if they are implemented. This could be done
38 inside next_interval and previous_interval.
40 set_properties needs to deal with the interval property cache.
42 It is assumed that for any interval plist, a property appears
43 only once on the list. Although some code i.e., remove_properties,
44 handles the more general case, the uniqueness of properties is
45 necessary for the system to remain consistent. This requirement
46 is enforced by the subrs installing properties onto the intervals. */
48 /* The rest of the file is within this conditional */
49 #ifdef USE_TEXT_PROPERTIES
52 Lisp_Object Qmouse_left
;
53 Lisp_Object Qmouse_entered
;
54 Lisp_Object Qpoint_left
;
55 Lisp_Object Qpoint_entered
;
56 Lisp_Object Qcategory
;
57 Lisp_Object Qlocal_map
;
59 /* Visual properties text (including strings) may have. */
60 Lisp_Object Qforeground
, Qbackground
, Qfont
, Qunderline
, Qstipple
;
61 Lisp_Object Qinvisible
, Qread_only
, Qintangible
;
63 /* Sticky properties */
64 Lisp_Object Qfront_sticky
, Qrear_nonsticky
;
66 /* If o1 is a cons whose cdr is a cons, return non-zero and set o2 to
67 the o1's cdr. Otherwise, return zero. This is handy for
69 #define PLIST_ELT_P(o1, o2) (CONSP (o1) && ((o2)=XCONS (o1)->cdr, CONSP (o2)))
71 Lisp_Object Vinhibit_point_motion_hooks
;
72 Lisp_Object Vdefault_text_properties
;
74 /* verify_interval_modification saves insertion hooks here
75 to be run later by report_interval_modification. */
76 Lisp_Object interval_insert_behind_hooks
;
77 Lisp_Object interval_insert_in_front_hooks
;
79 /* Extract the interval at the position pointed to by BEGIN from
80 OBJECT, a string or buffer. Additionally, check that the positions
81 pointed to by BEGIN and END are within the bounds of OBJECT, and
82 reverse them if *BEGIN is greater than *END. The objects pointed
83 to by BEGIN and END may be integers or markers; if the latter, they
84 are coerced to integers.
86 When OBJECT is a string, we increment *BEGIN and *END
87 to make them origin-one.
89 Note that buffer points don't correspond to interval indices.
90 For example, point-max is 1 greater than the index of the last
91 character. This difference is handled in the caller, which uses
92 the validated points to determine a length, and operates on that.
93 Exceptions are Ftext_properties_at, Fnext_property_change, and
94 Fprevious_property_change which call this function with BEGIN == END.
95 Handle this case specially.
97 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
98 create an interval tree for OBJECT if one doesn't exist, provided
99 the object actually contains text. In the current design, if there
100 is no text, there can be no text properties. */
106 validate_interval_range (object
, begin
, end
, force
)
107 Lisp_Object object
, *begin
, *end
;
113 CHECK_STRING_OR_BUFFER (object
, 0);
114 CHECK_NUMBER_COERCE_MARKER (*begin
, 0);
115 CHECK_NUMBER_COERCE_MARKER (*end
, 0);
117 /* If we are asked for a point, but from a subr which operates
118 on a range, then return nothing. */
119 if (EQ (*begin
, *end
) && begin
!= end
)
120 return NULL_INTERVAL
;
122 if (XINT (*begin
) > XINT (*end
))
130 if (BUFFERP (object
))
132 register struct buffer
*b
= XBUFFER (object
);
134 if (!(BUF_BEGV (b
) <= XINT (*begin
) && XINT (*begin
) <= XINT (*end
)
135 && XINT (*end
) <= BUF_ZV (b
)))
136 args_out_of_range (*begin
, *end
);
137 i
= BUF_INTERVALS (b
);
139 /* If there's no text, there are no properties. */
140 if (BUF_BEGV (b
) == BUF_ZV (b
))
141 return NULL_INTERVAL
;
143 searchpos
= XINT (*begin
);
147 register struct Lisp_String
*s
= XSTRING (object
);
149 if (! (0 <= XINT (*begin
) && XINT (*begin
) <= XINT (*end
)
150 && XINT (*end
) <= s
->size
))
151 args_out_of_range (*begin
, *end
);
152 /* User-level Positions in strings start with 0,
153 but the interval code always wants positions starting with 1. */
154 XSETFASTINT (*begin
, XFASTINT (*begin
) + 1);
156 XSETFASTINT (*end
, XFASTINT (*end
) + 1);
160 return NULL_INTERVAL
;
162 searchpos
= XINT (*begin
);
165 if (NULL_INTERVAL_P (i
))
166 return (force
? create_root_interval (object
) : i
);
168 return find_interval (i
, searchpos
);
171 /* Validate LIST as a property list. If LIST is not a list, then
172 make one consisting of (LIST nil). Otherwise, verify that LIST
173 is even numbered and thus suitable as a plist. */
176 validate_plist (list
)
185 register Lisp_Object tail
;
186 for (i
= 0, tail
= list
; !NILP (tail
); i
++)
192 error ("Odd length text property list");
196 return Fcons (list
, Fcons (Qnil
, Qnil
));
199 /* Return nonzero if interval I has all the properties,
200 with the same values, of list PLIST. */
203 interval_has_all_properties (plist
, i
)
207 register Lisp_Object tail1
, tail2
, sym1
, sym2
;
210 /* Go through each element of PLIST. */
211 for (tail1
= plist
; ! NILP (tail1
); tail1
= Fcdr (Fcdr (tail1
)))
216 /* Go through I's plist, looking for sym1 */
217 for (tail2
= i
->plist
; ! NILP (tail2
); tail2
= Fcdr (Fcdr (tail2
)))
218 if (EQ (sym1
, Fcar (tail2
)))
220 /* Found the same property on both lists. If the
221 values are unequal, return zero. */
222 if (! EQ (Fcar (Fcdr (tail1
)), Fcar (Fcdr (tail2
))))
225 /* Property has same value on both lists; go to next one. */
237 /* Return nonzero if the plist of interval I has any of the
238 properties of PLIST, regardless of their values. */
241 interval_has_some_properties (plist
, i
)
245 register Lisp_Object tail1
, tail2
, sym
;
247 /* Go through each element of PLIST. */
248 for (tail1
= plist
; ! NILP (tail1
); tail1
= Fcdr (Fcdr (tail1
)))
252 /* Go through i's plist, looking for tail1 */
253 for (tail2
= i
->plist
; ! NILP (tail2
); tail2
= Fcdr (Fcdr (tail2
)))
254 if (EQ (sym
, Fcar (tail2
)))
261 /* Changing the plists of individual intervals. */
263 /* Return the value of PROP in property-list PLIST, or Qunbound if it
266 property_value (plist
, prop
)
267 Lisp_Object plist
, prop
;
271 while (PLIST_ELT_P (plist
, value
))
272 if (EQ (XCONS (plist
)->car
, prop
))
273 return XCONS (value
)->car
;
275 plist
= XCONS (value
)->cdr
;
280 /* Set the properties of INTERVAL to PROPERTIES,
281 and record undo info for the previous values.
282 OBJECT is the string or buffer that INTERVAL belongs to. */
285 set_properties (properties
, interval
, object
)
286 Lisp_Object properties
, object
;
289 Lisp_Object sym
, value
;
291 if (BUFFERP (object
))
293 /* For each property in the old plist which is missing from PROPERTIES,
294 or has a different value in PROPERTIES, make an undo record. */
295 for (sym
= interval
->plist
;
296 PLIST_ELT_P (sym
, value
);
297 sym
= XCONS (value
)->cdr
)
298 if (! EQ (property_value (properties
, XCONS (sym
)->car
),
301 modify_region (XBUFFER (object
),
302 make_number (interval
->position
),
303 make_number (interval
->position
+ LENGTH (interval
)));
304 record_property_change (interval
->position
, LENGTH (interval
),
305 XCONS (sym
)->car
, XCONS (value
)->car
,
307 signal_after_change (interval
->position
, LENGTH (interval
),
311 /* For each new property that has no value at all in the old plist,
312 make an undo record binding it to nil, so it will be removed. */
313 for (sym
= properties
;
314 PLIST_ELT_P (sym
, value
);
315 sym
= XCONS (value
)->cdr
)
316 if (EQ (property_value (interval
->plist
, XCONS (sym
)->car
), Qunbound
))
318 modify_region (XBUFFER (object
),
319 make_number (interval
->position
),
320 make_number (interval
->position
+ LENGTH (interval
)));
321 record_property_change (interval
->position
, LENGTH (interval
),
322 XCONS (sym
)->car
, Qnil
,
324 signal_after_change (interval
->position
, LENGTH (interval
),
329 /* Store new properties. */
330 interval
->plist
= Fcopy_sequence (properties
);
333 /* Add the properties of PLIST to the interval I, or set
334 the value of I's property to the value of the property on PLIST
335 if they are different.
337 OBJECT should be the string or buffer the interval is in.
339 Return nonzero if this changes I (i.e., if any members of PLIST
340 are actually added to I's plist) */
343 add_properties (plist
, i
, object
)
348 Lisp_Object tail1
, tail2
, sym1
, val1
;
349 register int changed
= 0;
351 struct gcpro gcpro1
, gcpro2
, gcpro3
;
356 /* No need to protect OBJECT, because we can GC only in the case
357 where it is a buffer, and live buffers are always protected.
358 I and its plist are also protected, via OBJECT. */
359 GCPRO3 (tail1
, sym1
, val1
);
361 /* Go through each element of PLIST. */
362 for (tail1
= plist
; ! NILP (tail1
); tail1
= Fcdr (Fcdr (tail1
)))
365 val1
= Fcar (Fcdr (tail1
));
368 /* Go through I's plist, looking for sym1 */
369 for (tail2
= i
->plist
; ! NILP (tail2
); tail2
= Fcdr (Fcdr (tail2
)))
370 if (EQ (sym1
, Fcar (tail2
)))
372 /* No need to gcpro, because tail2 protects this
373 and it must be a cons cell (we get an error otherwise). */
374 register Lisp_Object this_cdr
;
376 this_cdr
= Fcdr (tail2
);
377 /* Found the property. Now check its value. */
380 /* The properties have the same value on both lists.
381 Continue to the next property. */
382 if (EQ (val1
, Fcar (this_cdr
)))
385 /* Record this change in the buffer, for undo purposes. */
386 if (BUFFERP (object
))
388 modify_region (XBUFFER (object
),
389 make_number (i
->position
),
390 make_number (i
->position
+ LENGTH (i
)));
391 record_property_change (i
->position
, LENGTH (i
),
392 sym1
, Fcar (this_cdr
), object
);
393 signal_after_change (i
->position
, LENGTH (i
), LENGTH (i
));
396 /* I's property has a different value -- change it */
397 Fsetcar (this_cdr
, val1
);
404 /* Record this change in the buffer, for undo purposes. */
405 if (BUFFERP (object
))
407 modify_region (XBUFFER (object
),
408 make_number (i
->position
),
409 make_number (i
->position
+ LENGTH (i
)));
410 record_property_change (i
->position
, LENGTH (i
),
412 signal_after_change (i
->position
, LENGTH (i
), LENGTH (i
));
414 i
->plist
= Fcons (sym1
, Fcons (val1
, i
->plist
));
424 /* For any members of PLIST which are properties of I, remove them
426 OBJECT is the string or buffer containing I. */
429 remove_properties (plist
, i
, object
)
434 register Lisp_Object tail1
, tail2
, sym
, current_plist
;
435 register int changed
= 0;
437 current_plist
= i
->plist
;
438 /* Go through each element of plist. */
439 for (tail1
= plist
; ! NILP (tail1
); tail1
= Fcdr (Fcdr (tail1
)))
443 /* First, remove the symbol if its at the head of the list */
444 while (! NILP (current_plist
) && EQ (sym
, Fcar (current_plist
)))
446 if (BUFFERP (object
))
448 modify_region (XBUFFER (object
),
449 make_number (i
->position
),
450 make_number (i
->position
+ LENGTH (i
)));
451 record_property_change (i
->position
, LENGTH (i
),
452 sym
, Fcar (Fcdr (current_plist
)),
454 signal_after_change (i
->position
, LENGTH (i
), LENGTH (i
));
457 current_plist
= Fcdr (Fcdr (current_plist
));
461 /* Go through i's plist, looking for sym */
462 tail2
= current_plist
;
463 while (! NILP (tail2
))
465 register Lisp_Object
this;
466 this = Fcdr (Fcdr (tail2
));
467 if (EQ (sym
, Fcar (this)))
469 if (BUFFERP (object
))
471 modify_region (XBUFFER (object
),
472 make_number (i
->position
),
473 make_number (i
->position
+ LENGTH (i
)));
474 record_property_change (i
->position
, LENGTH (i
),
475 sym
, Fcar (Fcdr (this)), object
);
476 signal_after_change (i
->position
, LENGTH (i
), LENGTH (i
));
479 Fsetcdr (Fcdr (tail2
), Fcdr (Fcdr (this)));
487 i
->plist
= current_plist
;
492 /* Remove all properties from interval I. Return non-zero
493 if this changes the interval. */
507 DEFUN ("text-properties-at", Ftext_properties_at
,
508 Stext_properties_at
, 1, 2, 0,
509 "Return the list of properties held by the character at POSITION\n\
510 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
511 defaults to the current buffer.\n\
512 If POSITION is at the end of OBJECT, the value is nil.")
514 Lisp_Object pos
, object
;
519 XSETBUFFER (object
, current_buffer
);
521 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
522 if (NULL_INTERVAL_P (i
))
524 /* If POS is at the end of the interval,
525 it means it's the end of OBJECT.
526 There are no properties at the very end,
527 since no character follows. */
528 if (XINT (pos
) == LENGTH (i
) + i
->position
)
534 DEFUN ("get-text-property", Fget_text_property
, Sget_text_property
, 2, 3, 0,
535 "Return the value of position POS's property PROP, in OBJECT.\n\
536 OBJECT is optional and defaults to the current buffer.\n\
537 If POSITION is at the end of OBJECT, the value is nil.")
539 Lisp_Object pos
, object
;
542 return textget (Ftext_properties_at (pos
, object
), prop
);
545 DEFUN ("get-char-property", Fget_char_property
, Sget_char_property
, 2, 3, 0,
546 "Return the value of position POS's property PROP, in OBJECT.\n\
547 OBJECT is optional and defaults to the current buffer.\n\
548 If POS is at the end of OBJECT, the value is nil.\n\
549 If OBJECT is a buffer, then overlay properties are considered as well as\n\
551 If OBJECT is a window, then that window's buffer is used, but window-specific\n\
552 overlays are considered only if they are associated with OBJECT.")
554 Lisp_Object pos
, object
;
555 register Lisp_Object prop
;
557 struct window
*w
= 0;
559 CHECK_NUMBER_COERCE_MARKER (pos
, 0);
562 XSETBUFFER (object
, current_buffer
);
564 if (WINDOWP (object
))
566 w
= XWINDOW (object
);
569 if (BUFFERP (object
))
571 int posn
= XINT (pos
);
573 Lisp_Object
*overlay_vec
, tem
;
576 struct buffer
*obuf
= current_buffer
;
578 set_buffer_temp (XBUFFER (object
));
580 /* First try with room for 40 overlays. */
582 overlay_vec
= (Lisp_Object
*) alloca (len
* sizeof (Lisp_Object
));
584 noverlays
= overlays_at (posn
, 0, &overlay_vec
, &len
,
585 &next_overlay
, NULL
);
587 /* If there are more than 40,
588 make enough space for all, and try again. */
592 overlay_vec
= (Lisp_Object
*) alloca (len
* sizeof (Lisp_Object
));
593 noverlays
= overlays_at (posn
, 0, &overlay_vec
, &len
,
594 &next_overlay
, NULL
);
596 noverlays
= sort_overlays (overlay_vec
, noverlays
, w
);
598 set_buffer_temp (obuf
);
600 /* Now check the overlays in order of decreasing priority. */
601 while (--noverlays
>= 0)
603 tem
= Foverlay_get (overlay_vec
[noverlays
], prop
);
608 /* Not a buffer, or no appropriate overlay, so fall through to the
610 return (Fget_text_property (pos
, prop
, object
));
613 DEFUN ("next-property-change", Fnext_property_change
,
614 Snext_property_change
, 1, 3, 0,
615 "Return the position of next property change.\n\
616 Scans characters forward from POS in OBJECT till it finds\n\
617 a change in some text property, then returns the position of the change.\n\
618 The optional second argument OBJECT is the string or buffer to scan.\n\
619 Return nil if the property is constant all the way to the end of OBJECT.\n\
620 If the value is non-nil, it is a position greater than POS, never equal.\n\n\
621 If the optional third argument LIMIT is non-nil, don't search\n\
622 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
624 Lisp_Object pos
, object
, limit
;
626 register INTERVAL i
, next
;
629 XSETBUFFER (object
, current_buffer
);
631 if (! NILP (limit
) && ! EQ (limit
, Qt
))
632 CHECK_NUMBER_COERCE_MARKER (limit
, 0);
634 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
636 /* If LIMIT is t, return start of next interval--don't
637 bother checking further intervals. */
640 if (NULL_INTERVAL_P (i
))
643 next
= next_interval (i
);
645 if (NULL_INTERVAL_P (next
))
646 XSETFASTINT (pos
, (STRINGP (object
)
647 ? XSTRING (object
)->size
648 : BUF_ZV (XBUFFER (object
))));
650 XSETFASTINT (pos
, next
->position
- (STRINGP (object
)));
654 if (NULL_INTERVAL_P (i
))
657 next
= next_interval (i
);
659 while (! NULL_INTERVAL_P (next
) && intervals_equal (i
, next
)
660 && (NILP (limit
) || next
->position
< XFASTINT (limit
)))
661 next
= next_interval (next
);
663 if (NULL_INTERVAL_P (next
))
665 if (! NILP (limit
) && !(next
->position
< XFASTINT (limit
)))
668 XSETFASTINT (pos
, next
->position
- (STRINGP (object
)));
672 /* Return 1 if there's a change in some property between BEG and END. */
675 property_change_between_p (beg
, end
)
678 register INTERVAL i
, next
;
679 Lisp_Object object
, pos
;
681 XSETBUFFER (object
, current_buffer
);
682 XSETFASTINT (pos
, beg
);
684 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
685 if (NULL_INTERVAL_P (i
))
688 next
= next_interval (i
);
689 while (! NULL_INTERVAL_P (next
) && intervals_equal (i
, next
))
691 next
= next_interval (next
);
692 if (NULL_INTERVAL_P (next
))
694 if (next
->position
>= end
)
698 if (NULL_INTERVAL_P (next
))
704 DEFUN ("next-single-property-change", Fnext_single_property_change
,
705 Snext_single_property_change
, 2, 4, 0,
706 "Return the position of next property change for a specific property.\n\
707 Scans characters forward from POS till it finds\n\
708 a change in the PROP property, then returns the position of the change.\n\
709 The optional third argument OBJECT is the string or buffer to scan.\n\
710 The property values are compared with `eq'.\n\
711 Return nil if the property is constant all the way to the end of OBJECT.\n\
712 If the value is non-nil, it is a position greater than POS, never equal.\n\n\
713 If the optional fourth argument LIMIT is non-nil, don't search\n\
714 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
715 (pos
, prop
, object
, limit
)
716 Lisp_Object pos
, prop
, object
, limit
;
718 register INTERVAL i
, next
;
719 register Lisp_Object here_val
;
722 XSETBUFFER (object
, current_buffer
);
725 CHECK_NUMBER_COERCE_MARKER (limit
, 0);
727 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
728 if (NULL_INTERVAL_P (i
))
731 here_val
= textget (i
->plist
, prop
);
732 next
= next_interval (i
);
733 while (! NULL_INTERVAL_P (next
)
734 && EQ (here_val
, textget (next
->plist
, prop
))
735 && (NILP (limit
) || next
->position
< XFASTINT (limit
)))
736 next
= next_interval (next
);
738 if (NULL_INTERVAL_P (next
))
740 if (! NILP (limit
) && !(next
->position
< XFASTINT (limit
)))
743 XSETFASTINT (pos
, next
->position
- (STRINGP (object
)));
747 DEFUN ("previous-property-change", Fprevious_property_change
,
748 Sprevious_property_change
, 1, 3, 0,
749 "Return the position of previous property change.\n\
750 Scans characters backwards from POS in OBJECT till it finds\n\
751 a change in some text property, then returns the position of the change.\n\
752 The optional second argument OBJECT is the string or buffer to scan.\n\
753 Return nil if the property is constant all the way to the start of OBJECT.\n\
754 If the value is non-nil, it is a position less than POS, never equal.\n\n\
755 If the optional third argument LIMIT is non-nil, don't search\n\
756 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
758 Lisp_Object pos
, object
, limit
;
760 register INTERVAL i
, previous
;
763 XSETBUFFER (object
, current_buffer
);
766 CHECK_NUMBER_COERCE_MARKER (limit
, 0);
768 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
769 if (NULL_INTERVAL_P (i
))
772 /* Start with the interval containing the char before point. */
773 if (i
->position
== XFASTINT (pos
))
774 i
= previous_interval (i
);
776 previous
= previous_interval (i
);
777 while (! NULL_INTERVAL_P (previous
) && intervals_equal (previous
, i
)
779 || previous
->position
+ LENGTH (previous
) > XFASTINT (limit
)))
780 previous
= previous_interval (previous
);
781 if (NULL_INTERVAL_P (previous
))
784 && !(previous
->position
+ LENGTH (previous
) > XFASTINT (limit
)))
787 XSETFASTINT (pos
, (previous
->position
+ LENGTH (previous
)
788 - (STRINGP (object
))));
792 DEFUN ("previous-single-property-change", Fprevious_single_property_change
,
793 Sprevious_single_property_change
, 2, 4, 0,
794 "Return the position of previous property change for a specific property.\n\
795 Scans characters backward from POS till it finds\n\
796 a change in the PROP property, then returns the position of the change.\n\
797 The optional third argument OBJECT is the string or buffer to scan.\n\
798 The property values are compared with `eq'.\n\
799 Return nil if the property is constant all the way to the start of OBJECT.\n\
800 If the value is non-nil, it is a position less than POS, never equal.\n\n\
801 If the optional fourth argument LIMIT is non-nil, don't search\n\
802 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
803 (pos
, prop
, object
, limit
)
804 Lisp_Object pos
, prop
, object
, limit
;
806 register INTERVAL i
, previous
;
807 register Lisp_Object here_val
;
810 XSETBUFFER (object
, current_buffer
);
813 CHECK_NUMBER_COERCE_MARKER (limit
, 0);
815 i
= validate_interval_range (object
, &pos
, &pos
, soft
);
817 /* Start with the interval containing the char before point. */
818 if (! NULL_INTERVAL_P (i
) && i
->position
== XFASTINT (pos
))
819 i
= previous_interval (i
);
821 if (NULL_INTERVAL_P (i
))
824 here_val
= textget (i
->plist
, prop
);
825 previous
= previous_interval (i
);
826 while (! NULL_INTERVAL_P (previous
)
827 && EQ (here_val
, textget (previous
->plist
, prop
))
829 || previous
->position
+ LENGTH (previous
) > XFASTINT (limit
)))
830 previous
= previous_interval (previous
);
831 if (NULL_INTERVAL_P (previous
))
834 && !(previous
->position
+ LENGTH (previous
) > XFASTINT (limit
)))
837 XSETFASTINT (pos
, (previous
->position
+ LENGTH (previous
)
838 - (STRINGP (object
))));
842 /* Callers note, this can GC when OBJECT is a buffer (or nil). */
844 DEFUN ("add-text-properties", Fadd_text_properties
,
845 Sadd_text_properties
, 3, 4, 0,
846 "Add properties to the text from START to END.\n\
847 The third argument PROPS is a property list\n\
848 specifying the property values to add.\n\
849 The optional fourth argument, OBJECT,\n\
850 is the string or buffer containing the text.\n\
851 Return t if any property value actually changed, nil otherwise.")
852 (start
, end
, properties
, object
)
853 Lisp_Object start
, end
, properties
, object
;
855 register INTERVAL i
, unchanged
;
856 register int s
, len
, modified
= 0;
859 properties
= validate_plist (properties
);
860 if (NILP (properties
))
864 XSETBUFFER (object
, current_buffer
);
866 i
= validate_interval_range (object
, &start
, &end
, hard
);
867 if (NULL_INTERVAL_P (i
))
871 len
= XINT (end
) - s
;
873 /* No need to protect OBJECT, because we GC only if it's a buffer,
874 and live buffers are always protected. */
877 /* If we're not starting on an interval boundary, we have to
878 split this interval. */
879 if (i
->position
!= s
)
881 /* If this interval already has the properties, we can
883 if (interval_has_all_properties (properties
, i
))
885 int got
= (LENGTH (i
) - (s
- i
->position
));
889 i
= next_interval (i
);
894 i
= split_interval_right (unchanged
, s
- unchanged
->position
);
895 copy_properties (unchanged
, i
);
899 /* We are at the beginning of interval I, with LEN chars to scan. */
905 if (LENGTH (i
) >= len
)
907 /* We can UNGCPRO safely here, because there will be just
908 one more chance to gc, in the next call to add_properties,
909 and after that we will not need PROPERTIES or OBJECT again. */
912 if (interval_has_all_properties (properties
, i
))
913 return modified
? Qt
: Qnil
;
915 if (LENGTH (i
) == len
)
917 add_properties (properties
, i
, object
);
921 /* i doesn't have the properties, and goes past the change limit */
923 i
= split_interval_left (unchanged
, len
);
924 copy_properties (unchanged
, i
);
925 add_properties (properties
, i
, object
);
930 modified
+= add_properties (properties
, i
, object
);
931 i
= next_interval (i
);
935 /* Callers note, this can GC when OBJECT is a buffer (or nil). */
937 DEFUN ("put-text-property", Fput_text_property
,
938 Sput_text_property
, 4, 5, 0,
939 "Set one property of the text from START to END.\n\
940 The third and fourth arguments PROP and VALUE\n\
941 specify the property to add.\n\
942 The optional fifth argument, OBJECT,\n\
943 is the string or buffer containing the text.")
944 (start
, end
, prop
, value
, object
)
945 Lisp_Object start
, end
, prop
, value
, object
;
947 Fadd_text_properties (start
, end
,
948 Fcons (prop
, Fcons (value
, Qnil
)),
953 DEFUN ("set-text-properties", Fset_text_properties
,
954 Sset_text_properties
, 3, 4, 0,
955 "Completely replace properties of text from START to END.\n\
956 The third argument PROPS is the new property list.\n\
957 The optional fourth argument, OBJECT,\n\
958 is the string or buffer containing the text.")
959 (start
, end
, props
, object
)
960 Lisp_Object start
, end
, props
, object
;
962 register INTERVAL i
, unchanged
;
963 register INTERVAL prev_changed
= NULL_INTERVAL
;
965 Lisp_Object ostart
, oend
;
970 props
= validate_plist (props
);
973 XSETBUFFER (object
, current_buffer
);
975 /* If we want no properties for a whole string,
976 get rid of its intervals. */
977 if (NILP (props
) && STRINGP (object
)
978 && XFASTINT (start
) == 0
979 && XFASTINT (end
) == XSTRING (object
)->size
)
981 XSTRING (object
)->intervals
= 0;
985 i
= validate_interval_range (object
, &start
, &end
, soft
);
987 if (NULL_INTERVAL_P (i
))
989 /* If buffer has no props, and we want none, return now. */
993 /* Restore the original START and END values
994 because validate_interval_range increments them for strings. */
998 i
= validate_interval_range (object
, &start
, &end
, hard
);
999 /* This can return if start == end. */
1000 if (NULL_INTERVAL_P (i
))
1005 len
= XINT (end
) - s
;
1007 if (i
->position
!= s
)
1010 i
= split_interval_right (unchanged
, s
- unchanged
->position
);
1012 if (LENGTH (i
) > len
)
1014 copy_properties (unchanged
, i
);
1015 i
= split_interval_left (i
, len
);
1016 set_properties (props
, i
, object
);
1020 set_properties (props
, i
, object
);
1022 if (LENGTH (i
) == len
)
1027 i
= next_interval (i
);
1030 /* We are starting at the beginning of an interval, I */
1036 if (LENGTH (i
) >= len
)
1038 if (LENGTH (i
) > len
)
1039 i
= split_interval_left (i
, len
);
1041 /* We have to call set_properties even if we are going to
1042 merge the intervals, so as to make the undo records
1043 and cause redisplay to happen. */
1044 set_properties (props
, i
, object
);
1045 if (!NULL_INTERVAL_P (prev_changed
))
1046 merge_interval_left (i
);
1052 /* We have to call set_properties even if we are going to
1053 merge the intervals, so as to make the undo records
1054 and cause redisplay to happen. */
1055 set_properties (props
, i
, object
);
1056 if (NULL_INTERVAL_P (prev_changed
))
1059 prev_changed
= i
= merge_interval_left (i
);
1061 i
= next_interval (i
);
1067 DEFUN ("remove-text-properties", Fremove_text_properties
,
1068 Sremove_text_properties
, 3, 4, 0,
1069 "Remove some properties from text from START to END.\n\
1070 The third argument PROPS is a property list\n\
1071 whose property names specify the properties to remove.\n\
1072 \(The values stored in PROPS are ignored.)\n\
1073 The optional fourth argument, OBJECT,\n\
1074 is the string or buffer containing the text.\n\
1075 Return t if any property was actually removed, nil otherwise.")
1076 (start
, end
, props
, object
)
1077 Lisp_Object start
, end
, props
, object
;
1079 register INTERVAL i
, unchanged
;
1080 register int s
, len
, modified
= 0;
1083 XSETBUFFER (object
, current_buffer
);
1085 i
= validate_interval_range (object
, &start
, &end
, soft
);
1086 if (NULL_INTERVAL_P (i
))
1090 len
= XINT (end
) - s
;
1092 if (i
->position
!= s
)
1094 /* No properties on this first interval -- return if
1095 it covers the entire region. */
1096 if (! interval_has_some_properties (props
, i
))
1098 int got
= (LENGTH (i
) - (s
- i
->position
));
1102 i
= next_interval (i
);
1104 /* Split away the beginning of this interval; what we don't
1109 i
= split_interval_right (unchanged
, s
- unchanged
->position
);
1110 copy_properties (unchanged
, i
);
1114 /* We are at the beginning of an interval, with len to scan */
1120 if (LENGTH (i
) >= len
)
1122 if (! interval_has_some_properties (props
, i
))
1123 return modified
? Qt
: Qnil
;
1125 if (LENGTH (i
) == len
)
1127 remove_properties (props
, i
, object
);
1131 /* i has the properties, and goes past the change limit */
1133 i
= split_interval_left (i
, len
);
1134 copy_properties (unchanged
, i
);
1135 remove_properties (props
, i
, object
);
1140 modified
+= remove_properties (props
, i
, object
);
1141 i
= next_interval (i
);
1145 DEFUN ("text-property-any", Ftext_property_any
,
1146 Stext_property_any
, 4, 5, 0,
1147 "Check text from START to END to see if PROP is ever `eq' to VALUE.\n\
1148 If so, return the position of the first character whose PROP is `eq'\n\
1149 to VALUE. Otherwise return nil.\n\
1150 The optional fifth argument, OBJECT, is the string or buffer\n\
1151 containing the text.")
1152 (start
, end
, prop
, value
, object
)
1153 Lisp_Object start
, end
, prop
, value
, object
;
1155 register INTERVAL i
;
1156 register int e
, pos
;
1159 XSETBUFFER (object
, current_buffer
);
1160 i
= validate_interval_range (object
, &start
, &end
, soft
);
1161 if (NULL_INTERVAL_P (i
))
1162 return (!NILP (value
) || EQ (start
, end
) ? Qnil
: start
);
1165 while (! NULL_INTERVAL_P (i
))
1167 if (i
->position
>= e
)
1169 if (EQ (textget (i
->plist
, prop
), value
))
1172 if (pos
< XINT (start
))
1174 return make_number (pos
- (STRINGP (object
)));
1176 i
= next_interval (i
);
1181 DEFUN ("text-property-not-all", Ftext_property_not_all
,
1182 Stext_property_not_all
, 4, 5, 0,
1183 "Check text from START to END to see if PROP is ever not `eq' to VALUE.\n\
1184 If so, return the position of the first character whose PROP is not\n\
1185 `eq' to VALUE. Otherwise, return nil.\n\
1186 The optional fifth argument, OBJECT, is the string or buffer\n\
1187 containing the text.")
1188 (start
, end
, prop
, value
, object
)
1189 Lisp_Object start
, end
, prop
, value
, object
;
1191 register INTERVAL i
;
1195 XSETBUFFER (object
, current_buffer
);
1196 i
= validate_interval_range (object
, &start
, &end
, soft
);
1197 if (NULL_INTERVAL_P (i
))
1198 return (NILP (value
) || EQ (start
, end
)) ? Qnil
: start
;
1202 while (! NULL_INTERVAL_P (i
))
1204 if (i
->position
>= e
)
1206 if (! EQ (textget (i
->plist
, prop
), value
))
1208 if (i
->position
> s
)
1210 return make_number (s
- (STRINGP (object
)));
1212 i
= next_interval (i
);
1217 #if 0 /* You can use set-text-properties for this. */
1219 DEFUN ("erase-text-properties", Ferase_text_properties
,
1220 Serase_text_properties
, 2, 3, 0,
1221 "Remove all properties from the text from START to END.\n\
1222 The optional third argument, OBJECT,\n\
1223 is the string or buffer containing the text.")
1224 (start
, end
, object
)
1225 Lisp_Object start
, end
, object
;
1227 register INTERVAL i
;
1228 register INTERVAL prev_changed
= NULL_INTERVAL
;
1229 register int s
, len
, modified
;
1232 XSETBUFFER (object
, current_buffer
);
1234 i
= validate_interval_range (object
, &start
, &end
, soft
);
1235 if (NULL_INTERVAL_P (i
))
1239 len
= XINT (end
) - s
;
1241 if (i
->position
!= s
)
1244 register INTERVAL unchanged
= i
;
1246 /* If there are properties here, then this text will be modified. */
1247 if (! NILP (i
->plist
))
1249 i
= split_interval_right (unchanged
, s
- unchanged
->position
);
1253 if (LENGTH (i
) > len
)
1255 i
= split_interval_right (i
, len
);
1256 copy_properties (unchanged
, i
);
1260 if (LENGTH (i
) == len
)
1265 /* If the text of I is without any properties, and contains
1266 LEN or more characters, then we may return without changing
1268 else if (LENGTH (i
) - (s
- i
->position
) <= len
)
1270 /* The amount of text to change extends past I, so just note
1271 how much we've gotten. */
1273 got
= LENGTH (i
) - (s
- i
->position
);
1277 i
= next_interval (i
);
1280 /* We are starting at the beginning of an interval, I. */
1283 if (LENGTH (i
) >= len
)
1285 /* If I has no properties, simply merge it if possible. */
1286 if (NILP (i
->plist
))
1288 if (! NULL_INTERVAL_P (prev_changed
))
1289 merge_interval_left (i
);
1291 return modified
? Qt
: Qnil
;
1294 if (LENGTH (i
) > len
)
1295 i
= split_interval_left (i
, len
);
1296 if (! NULL_INTERVAL_P (prev_changed
))
1297 merge_interval_left (i
);
1304 /* Here if we still need to erase past the end of I */
1306 if (NULL_INTERVAL_P (prev_changed
))
1308 modified
+= erase_properties (i
);
1313 modified
+= ! NILP (i
->plist
);
1314 /* Merging I will give it the properties of PREV_CHANGED. */
1315 prev_changed
= i
= merge_interval_left (i
);
1318 i
= next_interval (i
);
1321 return modified
? Qt
: Qnil
;
1325 /* I don't think this is the right interface to export; how often do you
1326 want to do something like this, other than when you're copying objects
1329 I think it would be better to have a pair of functions, one which
1330 returns the text properties of a region as a list of ranges and
1331 plists, and another which applies such a list to another object. */
1333 /* Add properties from SRC to SRC of SRC, starting at POS in DEST.
1334 SRC and DEST may each refer to strings or buffers.
1335 Optional sixth argument PROP causes only that property to be copied.
1336 Properties are copied to DEST as if by `add-text-properties'.
1337 Return t if any property value actually changed, nil otherwise. */
1339 /* Note this can GC when DEST is a buffer. */
1342 copy_text_properties (start
, end
, src
, pos
, dest
, prop
)
1343 Lisp_Object start
, end
, src
, pos
, dest
, prop
;
1349 int s
, e
, e2
, p
, len
, modified
= 0;
1350 struct gcpro gcpro1
, gcpro2
;
1352 i
= validate_interval_range (src
, &start
, &end
, soft
);
1353 if (NULL_INTERVAL_P (i
))
1356 CHECK_NUMBER_COERCE_MARKER (pos
, 0);
1358 Lisp_Object dest_start
, dest_end
;
1361 XSETFASTINT (dest_end
, XINT (dest_start
) + (XINT (end
) - XINT (start
)));
1362 /* Apply this to a copy of pos; it will try to increment its arguments,
1363 which we don't want. */
1364 validate_interval_range (dest
, &dest_start
, &dest_end
, soft
);
1375 e2
= i
->position
+ LENGTH (i
);
1382 while (! NILP (plist
))
1384 if (EQ (Fcar (plist
), prop
))
1386 plist
= Fcons (prop
, Fcons (Fcar (Fcdr (plist
)), Qnil
));
1389 plist
= Fcdr (Fcdr (plist
));
1393 /* Must defer modifications to the interval tree in case src
1394 and dest refer to the same string or buffer. */
1395 stuff
= Fcons (Fcons (make_number (p
),
1396 Fcons (make_number (p
+ len
),
1397 Fcons (plist
, Qnil
))),
1401 i
= next_interval (i
);
1402 if (NULL_INTERVAL_P (i
))
1409 GCPRO2 (stuff
, dest
);
1411 while (! NILP (stuff
))
1414 res
= Fadd_text_properties (Fcar (res
), Fcar (Fcdr (res
)),
1415 Fcar (Fcdr (Fcdr (res
))), dest
);
1418 stuff
= Fcdr (stuff
);
1423 return modified
? Qt
: Qnil
;
1426 /* Call the modification hook functions in LIST, each with START and END. */
1429 call_mod_hooks (list
, start
, end
)
1430 Lisp_Object list
, start
, end
;
1432 struct gcpro gcpro1
;
1434 while (!NILP (list
))
1436 call2 (Fcar (list
), start
, end
);
1442 /* Check for read-only intervals and signal an error if we find one.
1443 Then check for any modification hooks in the range START up to
1444 (but not including) END. Create a list of all these hooks in
1445 lexicographic order, eliminating consecutive extra copies of the
1446 same hook. Then call those hooks in order, with START and END - 1
1450 verify_interval_modification (buf
, start
, end
)
1454 register INTERVAL intervals
= BUF_INTERVALS (buf
);
1455 register INTERVAL i
, prev
;
1457 register Lisp_Object prev_mod_hooks
;
1458 Lisp_Object mod_hooks
;
1459 struct gcpro gcpro1
;
1462 prev_mod_hooks
= Qnil
;
1465 interval_insert_behind_hooks
= Qnil
;
1466 interval_insert_in_front_hooks
= Qnil
;
1468 if (NULL_INTERVAL_P (intervals
))
1478 /* For an insert operation, check the two chars around the position. */
1482 Lisp_Object before
, after
;
1484 /* Set I to the interval containing the char after START,
1485 and PREV to the interval containing the char before START.
1486 Either one may be null. They may be equal. */
1487 i
= find_interval (intervals
, start
);
1489 if (start
== BUF_BEGV (buf
))
1491 else if (i
->position
== start
)
1492 prev
= previous_interval (i
);
1493 else if (i
->position
< start
)
1495 if (start
== BUF_ZV (buf
))
1498 /* If Vinhibit_read_only is set and is not a list, we can
1499 skip the read_only checks. */
1500 if (NILP (Vinhibit_read_only
) || CONSP (Vinhibit_read_only
))
1502 /* If I and PREV differ we need to check for the read-only
1503 property together with its stickiness. If either I or
1504 PREV are 0, this check is all we need.
1505 We have to take special care, since read-only may be
1506 indirectly defined via the category property. */
1509 if (! NULL_INTERVAL_P (i
))
1511 after
= textget (i
->plist
, Qread_only
);
1513 /* If interval I is read-only and read-only is
1514 front-sticky, inhibit insertion.
1515 Check for read-only as well as category. */
1517 && NILP (Fmemq (after
, Vinhibit_read_only
)))
1521 tem
= textget (i
->plist
, Qfront_sticky
);
1522 if (TMEM (Qread_only
, tem
)
1523 || (NILP (Fplist_get (i
->plist
, Qread_only
))
1524 && TMEM (Qcategory
, tem
)))
1525 error ("Attempt to insert within read-only text");
1529 if (! NULL_INTERVAL_P (prev
))
1531 before
= textget (prev
->plist
, Qread_only
);
1533 /* If interval PREV is read-only and read-only isn't
1534 rear-nonsticky, inhibit insertion.
1535 Check for read-only as well as category. */
1537 && NILP (Fmemq (before
, Vinhibit_read_only
)))
1541 tem
= textget (prev
->plist
, Qrear_nonsticky
);
1542 if (! TMEM (Qread_only
, tem
)
1543 && (! NILP (Fplist_get (prev
->plist
,Qread_only
))
1544 || ! TMEM (Qcategory
, tem
)))
1545 error ("Attempt to insert within read-only text");
1549 else if (! NULL_INTERVAL_P (i
))
1551 after
= textget (i
->plist
, Qread_only
);
1553 /* If interval I is read-only and read-only is
1554 front-sticky, inhibit insertion.
1555 Check for read-only as well as category. */
1556 if (! NILP (after
) && NILP (Fmemq (after
, Vinhibit_read_only
)))
1560 tem
= textget (i
->plist
, Qfront_sticky
);
1561 if (TMEM (Qread_only
, tem
)
1562 || (NILP (Fplist_get (i
->plist
, Qread_only
))
1563 && TMEM (Qcategory
, tem
)))
1564 error ("Attempt to insert within read-only text");
1566 tem
= textget (prev
->plist
, Qrear_nonsticky
);
1567 if (! TMEM (Qread_only
, tem
)
1568 && (! NILP (Fplist_get (prev
->plist
, Qread_only
))
1569 || ! TMEM (Qcategory
, tem
)))
1570 error ("Attempt to insert within read-only text");
1575 /* Run both insert hooks (just once if they're the same). */
1576 if (!NULL_INTERVAL_P (prev
))
1577 interval_insert_behind_hooks
1578 = textget (prev
->plist
, Qinsert_behind_hooks
);
1579 if (!NULL_INTERVAL_P (i
))
1580 interval_insert_in_front_hooks
1581 = textget (i
->plist
, Qinsert_in_front_hooks
);
1585 /* Loop over intervals on or next to START...END,
1586 collecting their hooks. */
1588 i
= find_interval (intervals
, start
);
1591 if (! INTERVAL_WRITABLE_P (i
))
1592 error ("Attempt to modify read-only text");
1594 mod_hooks
= textget (i
->plist
, Qmodification_hooks
);
1595 if (! NILP (mod_hooks
) && ! EQ (mod_hooks
, prev_mod_hooks
))
1597 hooks
= Fcons (mod_hooks
, hooks
);
1598 prev_mod_hooks
= mod_hooks
;
1601 i
= next_interval (i
);
1603 /* Keep going thru the interval containing the char before END. */
1604 while (! NULL_INTERVAL_P (i
) && i
->position
< end
);
1607 hooks
= Fnreverse (hooks
);
1608 while (! EQ (hooks
, Qnil
))
1610 call_mod_hooks (Fcar (hooks
), make_number (start
),
1612 hooks
= Fcdr (hooks
);
1618 /* Run the interval hooks for an insertion.
1619 verify_interval_modification chose which hooks to run;
1620 this function is called after the insertion happens
1621 so it can indicate the range of inserted text. */
1624 report_interval_modification (start
, end
)
1625 Lisp_Object start
, end
;
1627 if (! NILP (interval_insert_behind_hooks
))
1628 call_mod_hooks (interval_insert_behind_hooks
,
1629 make_number (start
), make_number (end
));
1630 if (! NILP (interval_insert_in_front_hooks
)
1631 && ! EQ (interval_insert_in_front_hooks
,
1632 interval_insert_behind_hooks
))
1633 call_mod_hooks (interval_insert_in_front_hooks
,
1634 make_number (start
), make_number (end
));
1640 DEFVAR_LISP ("default-text-properties", &Vdefault_text_properties
,
1641 "Property-list used as default values.\n\
1642 The value of a property in this list is seen as the value for every\n\
1643 character that does not have its own value for that property.");
1644 Vdefault_text_properties
= Qnil
;
1646 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks
,
1647 "If non-nil, don't run `point-left' and `point-entered' text properties.\n\
1648 This also inhibits the use of the `intangible' text property.");
1649 Vinhibit_point_motion_hooks
= Qnil
;
1651 staticpro (&interval_insert_behind_hooks
);
1652 staticpro (&interval_insert_in_front_hooks
);
1653 interval_insert_behind_hooks
= Qnil
;
1654 interval_insert_in_front_hooks
= Qnil
;
1657 /* Common attributes one might give text */
1659 staticpro (&Qforeground
);
1660 Qforeground
= intern ("foreground");
1661 staticpro (&Qbackground
);
1662 Qbackground
= intern ("background");
1664 Qfont
= intern ("font");
1665 staticpro (&Qstipple
);
1666 Qstipple
= intern ("stipple");
1667 staticpro (&Qunderline
);
1668 Qunderline
= intern ("underline");
1669 staticpro (&Qread_only
);
1670 Qread_only
= intern ("read-only");
1671 staticpro (&Qinvisible
);
1672 Qinvisible
= intern ("invisible");
1673 staticpro (&Qintangible
);
1674 Qintangible
= intern ("intangible");
1675 staticpro (&Qcategory
);
1676 Qcategory
= intern ("category");
1677 staticpro (&Qlocal_map
);
1678 Qlocal_map
= intern ("local-map");
1679 staticpro (&Qfront_sticky
);
1680 Qfront_sticky
= intern ("front-sticky");
1681 staticpro (&Qrear_nonsticky
);
1682 Qrear_nonsticky
= intern ("rear-nonsticky");
1684 /* Properties that text might use to specify certain actions */
1686 staticpro (&Qmouse_left
);
1687 Qmouse_left
= intern ("mouse-left");
1688 staticpro (&Qmouse_entered
);
1689 Qmouse_entered
= intern ("mouse-entered");
1690 staticpro (&Qpoint_left
);
1691 Qpoint_left
= intern ("point-left");
1692 staticpro (&Qpoint_entered
);
1693 Qpoint_entered
= intern ("point-entered");
1695 defsubr (&Stext_properties_at
);
1696 defsubr (&Sget_text_property
);
1697 defsubr (&Sget_char_property
);
1698 defsubr (&Snext_property_change
);
1699 defsubr (&Snext_single_property_change
);
1700 defsubr (&Sprevious_property_change
);
1701 defsubr (&Sprevious_single_property_change
);
1702 defsubr (&Sadd_text_properties
);
1703 defsubr (&Sput_text_property
);
1704 defsubr (&Sset_text_properties
);
1705 defsubr (&Sremove_text_properties
);
1706 defsubr (&Stext_property_any
);
1707 defsubr (&Stext_property_not_all
);
1708 /* defsubr (&Serase_text_properties); */
1709 /* defsubr (&Scopy_text_properties); */
1714 lose
-- this shouldn
't be compiled if USE_TEXT_PROPERTIES isn't defined
1716 #endif /* USE_TEXT_PROPERTIES */