(Fyes_or_no_p): Use Qyes_or_no_p_history.
[emacs.git] / src / textprop.c
blobb324217907e766b0b2cb70c3f9bf8a47096e556a
1 /* Interface code for dealing with text properties.
2 Copyright (C) 1993 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)
9 any later version.
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. */
20 #include "config.h"
21 #include "lisp.h"
22 #include "intervals.h"
23 #include "buffer.h"
26 /* NOTES: previous- and next- property change will have to skip
27 zero-length intervals if they are implemented. This could be done
28 inside next_interval and previous_interval.
30 set_properties needs to deal with the interval property cache.
32 It is assumed that for any interval plist, a property appears
33 only once on the list. Although some code i.e., remove_properties,
34 handles the more general case, the uniqueness of properties is
35 necessary for the system to remain consistent. This requirement
36 is enforced by the subrs installing properties onto the intervals. */
38 /* The rest of the file is within this conditional */
39 #ifdef USE_TEXT_PROPERTIES
41 /* Types of hooks. */
42 Lisp_Object Qmouse_left;
43 Lisp_Object Qmouse_entered;
44 Lisp_Object Qpoint_left;
45 Lisp_Object Qpoint_entered;
46 Lisp_Object Qcategory;
47 Lisp_Object Qlocal_map;
49 /* Visual properties text (including strings) may have. */
50 Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple;
51 Lisp_Object Qinvisible, Qread_only, Qhidden;
53 /* Sticky properties */
54 Lisp_Object Qfront_sticky, Qrear_nonsticky;
56 /* If o1 is a cons whose cdr is a cons, return non-zero and set o2 to
57 the o1's cdr. Otherwise, return zero. This is handy for
58 traversing plists. */
59 #define PLIST_ELT_P(o1, o2) (CONSP (o1) && CONSP ((o2) = XCONS (o1)->cdr))
61 Lisp_Object Vinhibit_point_motion_hooks;
64 /* Extract the interval at the position pointed to by BEGIN from
65 OBJECT, a string or buffer. Additionally, check that the positions
66 pointed to by BEGIN and END are within the bounds of OBJECT, and
67 reverse them if *BEGIN is greater than *END. The objects pointed
68 to by BEGIN and END may be integers or markers; if the latter, they
69 are coerced to integers.
71 When OBJECT is a string, we increment *BEGIN and *END
72 to make them origin-one.
74 Note that buffer points don't correspond to interval indices.
75 For example, point-max is 1 greater than the index of the last
76 character. This difference is handled in the caller, which uses
77 the validated points to determine a length, and operates on that.
78 Exceptions are Ftext_properties_at, Fnext_property_change, and
79 Fprevious_property_change which call this function with BEGIN == END.
80 Handle this case specially.
82 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
83 create an interval tree for OBJECT if one doesn't exist, provided
84 the object actually contains text. In the current design, if there
85 is no text, there can be no text properties. */
87 #define soft 0
88 #define hard 1
90 static INTERVAL
91 validate_interval_range (object, begin, end, force)
92 Lisp_Object object, *begin, *end;
93 int force;
95 register INTERVAL i;
96 int searchpos;
98 CHECK_STRING_OR_BUFFER (object, 0);
99 CHECK_NUMBER_COERCE_MARKER (*begin, 0);
100 CHECK_NUMBER_COERCE_MARKER (*end, 0);
102 /* If we are asked for a point, but from a subr which operates
103 on a range, then return nothing. */
104 if (*begin == *end && begin != end)
105 return NULL_INTERVAL;
107 if (XINT (*begin) > XINT (*end))
109 Lisp_Object n;
110 n = *begin;
111 *begin = *end;
112 *end = n;
115 if (XTYPE (object) == Lisp_Buffer)
117 register struct buffer *b = XBUFFER (object);
119 if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end)
120 && XINT (*end) <= BUF_ZV (b)))
121 args_out_of_range (*begin, *end);
122 i = b->intervals;
124 /* If there's no text, there are no properties. */
125 if (BUF_BEGV (b) == BUF_ZV (b))
126 return NULL_INTERVAL;
128 searchpos = XINT (*begin);
130 else
132 register struct Lisp_String *s = XSTRING (object);
134 if (! (0 <= XINT (*begin) && XINT (*begin) <= XINT (*end)
135 && XINT (*end) <= s->size))
136 args_out_of_range (*begin, *end);
137 /* User-level Positions in strings start with 0,
138 but the interval code always wants positions starting with 1. */
139 XFASTINT (*begin) += 1;
140 if (begin != end)
141 XFASTINT (*end) += 1;
142 i = s->intervals;
144 if (s->size == 0)
145 return NULL_INTERVAL;
147 searchpos = XINT (*begin);
150 if (NULL_INTERVAL_P (i))
151 return (force ? create_root_interval (object) : i);
153 return find_interval (i, searchpos);
156 /* Validate LIST as a property list. If LIST is not a list, then
157 make one consisting of (LIST nil). Otherwise, verify that LIST
158 is even numbered and thus suitable as a plist. */
160 static Lisp_Object
161 validate_plist (list)
163 if (NILP (list))
164 return Qnil;
166 if (CONSP (list))
168 register int i;
169 register Lisp_Object tail;
170 for (i = 0, tail = list; !NILP (tail); i++)
172 tail = Fcdr (tail);
173 QUIT;
175 if (i & 1)
176 error ("Odd length text property list");
177 return list;
180 return Fcons (list, Fcons (Qnil, Qnil));
183 /* Return nonzero if interval I has all the properties,
184 with the same values, of list PLIST. */
186 static int
187 interval_has_all_properties (plist, i)
188 Lisp_Object plist;
189 INTERVAL i;
191 register Lisp_Object tail1, tail2, sym1, sym2;
192 register int found;
194 /* Go through each element of PLIST. */
195 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
197 sym1 = Fcar (tail1);
198 found = 0;
200 /* Go through I's plist, looking for sym1 */
201 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
202 if (EQ (sym1, Fcar (tail2)))
204 /* Found the same property on both lists. If the
205 values are unequal, return zero. */
206 if (! EQ (Fcar (Fcdr (tail1)), Fcar (Fcdr (tail2))))
207 return 0;
209 /* Property has same value on both lists; go to next one. */
210 found = 1;
211 break;
214 if (! found)
215 return 0;
218 return 1;
221 /* Return nonzero if the plist of interval I has any of the
222 properties of PLIST, regardless of their values. */
224 static INLINE int
225 interval_has_some_properties (plist, i)
226 Lisp_Object plist;
227 INTERVAL i;
229 register Lisp_Object tail1, tail2, sym;
231 /* Go through each element of PLIST. */
232 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
234 sym = Fcar (tail1);
236 /* Go through i's plist, looking for tail1 */
237 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
238 if (EQ (sym, Fcar (tail2)))
239 return 1;
242 return 0;
245 /* Changing the plists of individual intervals. */
247 /* Return the value of PROP in property-list PLIST, or Qunbound if it
248 has none. */
249 static int
250 property_value (plist, prop)
252 Lisp_Object value;
254 while (PLIST_ELT_P (plist, value))
255 if (EQ (XCONS (plist)->car, prop))
256 return XCONS (value)->car;
257 else
258 plist = XCONS (value)->cdr;
260 return Qunbound;
263 /* Set the properties of INTERVAL to PROPERTIES,
264 and record undo info for the previous values.
265 OBJECT is the string or buffer that INTERVAL belongs to. */
267 static void
268 set_properties (properties, interval, object)
269 Lisp_Object properties, object;
270 INTERVAL interval;
272 Lisp_Object sym, value;
274 if (BUFFERP (object))
276 /* For each property in the old plist which is missing from PROPERTIES,
277 or has a different value in PROPERTIES, make an undo record. */
278 for (sym = interval->plist;
279 PLIST_ELT_P (sym, value);
280 sym = XCONS (value)->cdr)
281 if (! EQ (property_value (properties, XCONS (sym)->car),
282 XCONS (value)->car))
284 modify_region (XBUFFER (object),
285 make_number (interval->position),
286 make_number (interval->position + LENGTH (interval)));
287 record_property_change (interval->position, LENGTH (interval),
288 XCONS (sym)->car, XCONS (value)->car,
289 object);
292 /* For each new property that has no value at all in the old plist,
293 make an undo record binding it to nil, so it will be removed. */
294 for (sym = properties;
295 PLIST_ELT_P (sym, value);
296 sym = XCONS (value)->cdr)
297 if (EQ (property_value (interval->plist, XCONS (sym)->car), Qunbound))
299 modify_region (XBUFFER (object),
300 make_number (interval->position),
301 make_number (interval->position + LENGTH (interval)));
302 record_property_change (interval->position, LENGTH (interval),
303 XCONS (sym)->car, Qnil,
304 object);
308 /* Store new properties. */
309 interval->plist = Fcopy_sequence (properties);
312 /* Add the properties of PLIST to the interval I, or set
313 the value of I's property to the value of the property on PLIST
314 if they are different.
316 OBJECT should be the string or buffer the interval is in.
318 Return nonzero if this changes I (i.e., if any members of PLIST
319 are actually added to I's plist) */
321 static int
322 add_properties (plist, i, object)
323 Lisp_Object plist;
324 INTERVAL i;
325 Lisp_Object object;
327 register Lisp_Object tail1, tail2, sym1, val1;
328 register int changed = 0;
329 register int found;
331 /* Go through each element of PLIST. */
332 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
334 sym1 = Fcar (tail1);
335 val1 = Fcar (Fcdr (tail1));
336 found = 0;
338 /* Go through I's plist, looking for sym1 */
339 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
340 if (EQ (sym1, Fcar (tail2)))
342 register Lisp_Object this_cdr = Fcdr (tail2);
344 /* Found the property. Now check its value. */
345 found = 1;
347 /* The properties have the same value on both lists.
348 Continue to the next property. */
349 if (EQ (val1, Fcar (this_cdr)))
350 break;
352 /* Record this change in the buffer, for undo purposes. */
353 if (XTYPE (object) == Lisp_Buffer)
355 modify_region (XBUFFER (object),
356 make_number (i->position),
357 make_number (i->position + LENGTH (i)));
358 record_property_change (i->position, LENGTH (i),
359 sym1, Fcar (this_cdr), object);
362 /* I's property has a different value -- change it */
363 Fsetcar (this_cdr, val1);
364 changed++;
365 break;
368 if (! found)
370 /* Record this change in the buffer, for undo purposes. */
371 if (XTYPE (object) == Lisp_Buffer)
373 modify_region (XBUFFER (object),
374 make_number (i->position),
375 make_number (i->position + LENGTH (i)));
376 record_property_change (i->position, LENGTH (i),
377 sym1, Qnil, object);
379 i->plist = Fcons (sym1, Fcons (val1, i->plist));
380 changed++;
384 return changed;
387 /* For any members of PLIST which are properties of I, remove them
388 from I's plist.
389 OBJECT is the string or buffer containing I. */
391 static int
392 remove_properties (plist, i, object)
393 Lisp_Object plist;
394 INTERVAL i;
395 Lisp_Object object;
397 register Lisp_Object tail1, tail2, sym;
398 register Lisp_Object current_plist = i->plist;
399 register int changed = 0;
401 /* Go through each element of plist. */
402 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
404 sym = Fcar (tail1);
406 /* First, remove the symbol if its at the head of the list */
407 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
409 if (XTYPE (object) == Lisp_Buffer)
411 modify_region (XBUFFER (object),
412 make_number (i->position),
413 make_number (i->position + LENGTH (i)));
414 record_property_change (i->position, LENGTH (i),
415 sym, Fcar (Fcdr (current_plist)),
416 object);
419 current_plist = Fcdr (Fcdr (current_plist));
420 changed++;
423 /* Go through i's plist, looking for sym */
424 tail2 = current_plist;
425 while (! NILP (tail2))
427 register Lisp_Object this = Fcdr (Fcdr (tail2));
428 if (EQ (sym, Fcar (this)))
430 if (XTYPE (object) == Lisp_Buffer)
432 modify_region (XBUFFER (object),
433 make_number (i->position),
434 make_number (i->position + LENGTH (i)));
435 record_property_change (i->position, LENGTH (i),
436 sym, Fcar (Fcdr (this)), object);
439 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
440 changed++;
442 tail2 = this;
446 if (changed)
447 i->plist = current_plist;
448 return changed;
451 #if 0
452 /* Remove all properties from interval I. Return non-zero
453 if this changes the interval. */
455 static INLINE int
456 erase_properties (i)
457 INTERVAL i;
459 if (NILP (i->plist))
460 return 0;
462 i->plist = Qnil;
463 return 1;
465 #endif
467 DEFUN ("text-properties-at", Ftext_properties_at,
468 Stext_properties_at, 1, 2, 0,
469 "Return the list of properties held by the character at POSITION\n\
470 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
471 defaults to the current buffer.\n\
472 If POSITION is at the end of OBJECT, the value is nil.")
473 (pos, object)
474 Lisp_Object pos, object;
476 register INTERVAL i;
478 if (NILP (object))
479 XSET (object, Lisp_Buffer, current_buffer);
481 i = validate_interval_range (object, &pos, &pos, soft);
482 if (NULL_INTERVAL_P (i))
483 return Qnil;
484 /* If POS is at the end of the interval,
485 it means it's the end of OBJECT.
486 There are no properties at the very end,
487 since no character follows. */
488 if (XINT (pos) == LENGTH (i) + i->position)
489 return Qnil;
491 return i->plist;
494 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
495 "Return the value of position POS's property PROP, in OBJECT.\n\
496 OBJECT is optional and defaults to the current buffer.\n\
497 If POSITION is at the end of OBJECT, the value is nil.")
498 (pos, prop, object)
499 Lisp_Object pos, object;
500 register Lisp_Object prop;
502 register INTERVAL i;
503 register Lisp_Object tail;
505 if (NILP (object))
506 XSET (object, Lisp_Buffer, current_buffer);
507 i = validate_interval_range (object, &pos, &pos, soft);
508 if (NULL_INTERVAL_P (i))
509 return Qnil;
511 /* If POS is at the end of the interval,
512 it means it's the end of OBJECT.
513 There are no properties at the very end,
514 since no character follows. */
515 if (XINT (pos) == LENGTH (i) + i->position)
516 return Qnil;
518 return textget (i->plist, prop);
521 DEFUN ("next-property-change", Fnext_property_change,
522 Snext_property_change, 1, 2, 0,
523 "Return the position of next property change.\n\
524 Scans characters forward from POS in OBJECT till it finds\n\
525 a change in some text property, then returns the position of the change.\n\
526 The optional second argument OBJECT is the string or buffer to scan.\n\
527 Return nil if the property is constant all the way to the end of OBJECT.\n\
528 If the value is non-nil, it is a position greater than POS, never equal.")
529 (pos, object)
530 Lisp_Object pos, object;
532 register INTERVAL i, next;
534 if (NILP (object))
535 XSET (object, Lisp_Buffer, current_buffer);
537 i = validate_interval_range (object, &pos, &pos, soft);
538 if (NULL_INTERVAL_P (i))
539 return Qnil;
541 next = next_interval (i);
542 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
543 next = next_interval (next);
545 if (NULL_INTERVAL_P (next))
546 return Qnil;
548 return next->position - (XTYPE (object) == Lisp_String);
551 /* Return 1 if there's a change in some property between BEG and END. */
554 property_change_between_p (beg, end)
555 int beg, end;
557 register INTERVAL i, next;
558 Lisp_Object object, pos;
560 XSET (object, Lisp_Buffer, current_buffer);
561 XFASTINT (pos) = beg;
563 i = validate_interval_range (object, &pos, &pos, soft);
564 if (NULL_INTERVAL_P (i))
565 return 0;
567 next = next_interval (i);
568 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
570 next = next_interval (next);
571 if (next->position >= end)
572 return 0;
575 if (NULL_INTERVAL_P (next))
576 return 0;
578 return 1;
581 DEFUN ("next-single-property-change", Fnext_single_property_change,
582 Snext_single_property_change, 1, 3, 0,
583 "Return the position of next property change for a specific property.\n\
584 Scans characters forward from POS till it finds\n\
585 a change in the PROP property, then returns the position of the change.\n\
586 The optional third argument OBJECT is the string or buffer to scan.\n\
587 Return nil if the property is constant all the way to the end of OBJECT.\n\
588 If the value is non-nil, it is a position greater than POS, never equal.")
589 (pos, prop, object)
590 Lisp_Object pos, prop, object;
592 register INTERVAL i, next;
593 register Lisp_Object here_val;
595 if (NILP (object))
596 XSET (object, Lisp_Buffer, current_buffer);
598 i = validate_interval_range (object, &pos, &pos, soft);
599 if (NULL_INTERVAL_P (i))
600 return Qnil;
602 here_val = textget (i->plist, prop);
603 next = next_interval (i);
604 while (! NULL_INTERVAL_P (next)
605 && EQ (here_val, textget (next->plist, prop)))
606 next = next_interval (next);
608 if (NULL_INTERVAL_P (next))
609 return Qnil;
611 return next->position - (XTYPE (object) == Lisp_String);
614 DEFUN ("previous-property-change", Fprevious_property_change,
615 Sprevious_property_change, 1, 2, 0,
616 "Return the position of previous property change.\n\
617 Scans characters backwards from POS in OBJECT till it finds\n\
618 a change in some text property, then returns the position of the change.\n\
619 The optional second argument OBJECT is the string or buffer to scan.\n\
620 Return nil if the property is constant all the way to the start of OBJECT.\n\
621 If the value is non-nil, it is a position less than POS, never equal.")
622 (pos, object)
623 Lisp_Object pos, object;
625 register INTERVAL i, previous;
627 if (NILP (object))
628 XSET (object, Lisp_Buffer, current_buffer);
630 i = validate_interval_range (object, &pos, &pos, soft);
631 if (NULL_INTERVAL_P (i))
632 return Qnil;
634 previous = previous_interval (i);
635 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i))
636 previous = previous_interval (previous);
637 if (NULL_INTERVAL_P (previous))
638 return Qnil;
640 return (previous->position + LENGTH (previous) - 1
641 - (XTYPE (object) == Lisp_String));
644 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
645 Sprevious_single_property_change, 2, 3, 0,
646 "Return the position of previous property change for a specific property.\n\
647 Scans characters backward from POS till it finds\n\
648 a change in the PROP property, then returns the position of the change.\n\
649 The optional third argument OBJECT is the string or buffer to scan.\n\
650 Return nil if the property is constant all the way to the start of OBJECT.\n\
651 If the value is non-nil, it is a position less than POS, never equal.")
652 (pos, prop, object)
653 Lisp_Object pos, prop, object;
655 register INTERVAL i, previous;
656 register Lisp_Object here_val;
658 if (NILP (object))
659 XSET (object, Lisp_Buffer, current_buffer);
661 i = validate_interval_range (object, &pos, &pos, soft);
662 if (NULL_INTERVAL_P (i))
663 return Qnil;
665 here_val = textget (i->plist, prop);
666 previous = previous_interval (i);
667 while (! NULL_INTERVAL_P (previous)
668 && EQ (here_val, textget (previous->plist, prop)))
669 previous = previous_interval (previous);
670 if (NULL_INTERVAL_P (previous))
671 return Qnil;
673 return (previous->position + LENGTH (previous) - 1
674 - (XTYPE (object) == Lisp_String));
677 DEFUN ("add-text-properties", Fadd_text_properties,
678 Sadd_text_properties, 3, 4, 0,
679 "Add properties to the text from START to END.\n\
680 The third argument PROPS is a property list\n\
681 specifying the property values to add.\n\
682 The optional fourth argument, OBJECT,\n\
683 is the string or buffer containing the text.\n\
684 Return t if any property value actually changed, nil otherwise.")
685 (start, end, properties, object)
686 Lisp_Object start, end, properties, object;
688 register INTERVAL i, unchanged;
689 register int s, len, modified = 0;
691 properties = validate_plist (properties);
692 if (NILP (properties))
693 return Qnil;
695 if (NILP (object))
696 XSET (object, Lisp_Buffer, current_buffer);
698 i = validate_interval_range (object, &start, &end, hard);
699 if (NULL_INTERVAL_P (i))
700 return Qnil;
702 s = XINT (start);
703 len = XINT (end) - s;
705 /* If we're not starting on an interval boundary, we have to
706 split this interval. */
707 if (i->position != s)
709 /* If this interval already has the properties, we can
710 skip it. */
711 if (interval_has_all_properties (properties, i))
713 int got = (LENGTH (i) - (s - i->position));
714 if (got >= len)
715 return Qnil;
716 len -= got;
717 i = next_interval (i);
719 else
721 unchanged = i;
722 i = split_interval_right (unchanged, s - unchanged->position);
723 copy_properties (unchanged, i);
727 /* We are at the beginning of interval I, with LEN chars to scan. */
728 for (;;)
730 if (i == 0)
731 abort ();
733 if (LENGTH (i) >= len)
735 if (interval_has_all_properties (properties, i))
736 return modified ? Qt : Qnil;
738 if (LENGTH (i) == len)
740 add_properties (properties, i, object);
741 return Qt;
744 /* i doesn't have the properties, and goes past the change limit */
745 unchanged = i;
746 i = split_interval_left (unchanged, len);
747 copy_properties (unchanged, i);
748 add_properties (properties, i, object);
749 return Qt;
752 len -= LENGTH (i);
753 modified += add_properties (properties, i, object);
754 i = next_interval (i);
758 DEFUN ("put-text-property", Fput_text_property,
759 Sput_text_property, 4, 5, 0,
760 "Set one property of the text from START to END.\n\
761 The third and fourth arguments PROP and VALUE\n\
762 specify the property to add.\n\
763 The optional fifth argument, OBJECT,\n\
764 is the string or buffer containing the text.")
765 (start, end, prop, value, object)
766 Lisp_Object start, end, prop, value, object;
768 Fadd_text_properties (start, end,
769 Fcons (prop, Fcons (value, Qnil)),
770 object);
771 return Qnil;
774 DEFUN ("set-text-properties", Fset_text_properties,
775 Sset_text_properties, 3, 4, 0,
776 "Completely replace properties of text from START to END.\n\
777 The third argument PROPS is the new property list.\n\
778 The optional fourth argument, OBJECT,\n\
779 is the string or buffer containing the text.")
780 (start, end, props, object)
781 Lisp_Object start, end, props, object;
783 register INTERVAL i, unchanged;
784 register INTERVAL prev_changed = NULL_INTERVAL;
785 register int s, len;
787 props = validate_plist (props);
789 if (NILP (object))
790 XSET (object, Lisp_Buffer, current_buffer);
792 i = validate_interval_range (object, &start, &end, hard);
793 if (NULL_INTERVAL_P (i))
794 return Qnil;
796 s = XINT (start);
797 len = XINT (end) - s;
799 if (i->position != s)
801 unchanged = i;
802 i = split_interval_right (unchanged, s - unchanged->position);
804 if (LENGTH (i) > len)
806 copy_properties (unchanged, i);
807 i = split_interval_left (i, len);
808 set_properties (props, i, object);
809 return Qt;
812 set_properties (props, i, object);
814 if (LENGTH (i) == len)
815 return Qt;
817 prev_changed = i;
818 len -= LENGTH (i);
819 i = next_interval (i);
822 /* We are starting at the beginning of an interval, I */
823 while (len > 0)
825 if (i == 0)
826 abort ();
828 if (LENGTH (i) >= len)
830 if (LENGTH (i) > len)
831 i = split_interval_left (i, len);
833 if (NULL_INTERVAL_P (prev_changed))
834 set_properties (props, i, object);
835 else
836 merge_interval_left (i);
837 return Qt;
840 len -= LENGTH (i);
841 if (NULL_INTERVAL_P (prev_changed))
843 set_properties (props, i, object);
844 prev_changed = i;
846 else
847 prev_changed = i = merge_interval_left (i);
849 i = next_interval (i);
852 return Qt;
855 DEFUN ("remove-text-properties", Fremove_text_properties,
856 Sremove_text_properties, 3, 4, 0,
857 "Remove some properties from text from START to END.\n\
858 The third argument PROPS is a property list\n\
859 whose property names specify the properties to remove.\n\
860 \(The values stored in PROPS are ignored.)\n\
861 The optional fourth argument, OBJECT,\n\
862 is the string or buffer containing the text.\n\
863 Return t if any property was actually removed, nil otherwise.")
864 (start, end, props, object)
865 Lisp_Object start, end, props, object;
867 register INTERVAL i, unchanged;
868 register int s, len, modified = 0;
870 if (NILP (object))
871 XSET (object, Lisp_Buffer, current_buffer);
873 i = validate_interval_range (object, &start, &end, soft);
874 if (NULL_INTERVAL_P (i))
875 return Qnil;
877 s = XINT (start);
878 len = XINT (end) - s;
880 if (i->position != s)
882 /* No properties on this first interval -- return if
883 it covers the entire region. */
884 if (! interval_has_some_properties (props, i))
886 int got = (LENGTH (i) - (s - i->position));
887 if (got >= len)
888 return Qnil;
889 len -= got;
890 i = next_interval (i);
892 /* Split away the beginning of this interval; what we don't
893 want to modify. */
894 else
896 unchanged = i;
897 i = split_interval_right (unchanged, s - unchanged->position);
898 copy_properties (unchanged, i);
902 /* We are at the beginning of an interval, with len to scan */
903 for (;;)
905 if (i == 0)
906 abort ();
908 if (LENGTH (i) >= len)
910 if (! interval_has_some_properties (props, i))
911 return modified ? Qt : Qnil;
913 if (LENGTH (i) == len)
915 remove_properties (props, i, object);
916 return Qt;
919 /* i has the properties, and goes past the change limit */
920 unchanged = i;
921 i = split_interval_left (i, len);
922 copy_properties (unchanged, i);
923 remove_properties (props, i, object);
924 return Qt;
927 len -= LENGTH (i);
928 modified += remove_properties (props, i, object);
929 i = next_interval (i);
933 DEFUN ("text-property-any", Ftext_property_any,
934 Stext_property_any, 4, 5, 0,
935 "Check text from START to END to see if PROP is ever `eq' to VALUE.\n\
936 If so, return the position of the first character whose PROP is `eq'\n\
937 to VALUE. Otherwise return nil.\n\
938 The optional fifth argument, OBJECT, is the string or buffer\n\
939 containing the text.")
940 (start, end, prop, value, object)
941 Lisp_Object start, end, prop, value, object;
943 register INTERVAL i;
944 register int e, pos;
946 if (NILP (object))
947 XSET (object, Lisp_Buffer, current_buffer);
948 i = validate_interval_range (object, &start, &end, soft);
949 e = XINT (end);
951 while (! NULL_INTERVAL_P (i))
953 if (i->position >= e)
954 break;
955 if (EQ (textget (i->plist, prop), value))
957 pos = i->position;
958 if (pos < XINT (start))
959 pos = XINT (start);
960 return make_number (pos - (XTYPE (object) == Lisp_String));
962 i = next_interval (i);
964 return Qnil;
967 DEFUN ("text-property-not-all", Ftext_property_not_all,
968 Stext_property_not_all, 4, 5, 0,
969 "Check text from START to END to see if PROP is ever not `eq' to VALUE.\n\
970 If so, return the position of the first character whose PROP is not\n\
971 `eq' to VALUE. Otherwise, return nil.\n\
972 The optional fifth argument, OBJECT, is the string or buffer\n\
973 containing the text.")
974 (start, end, prop, value, object)
975 Lisp_Object start, end, prop, value, object;
977 register INTERVAL i;
978 register int s, e;
980 if (NILP (object))
981 XSET (object, Lisp_Buffer, current_buffer);
982 i = validate_interval_range (object, &start, &end, soft);
983 if (NULL_INTERVAL_P (i))
984 return (NILP (value) || EQ (start, end)) ? Qt : Qnil;
985 s = XINT (start);
986 e = XINT (end);
988 while (! NULL_INTERVAL_P (i))
990 if (i->position >= e)
991 break;
992 if (! EQ (textget (i->plist, prop), value))
994 if (i->position > s)
995 s = i->position;
996 return make_number (s - (XTYPE (object) == Lisp_String));
998 i = next_interval (i);
1000 return Qnil;
1003 #if 0 /* You can use set-text-properties for this. */
1005 DEFUN ("erase-text-properties", Ferase_text_properties,
1006 Serase_text_properties, 2, 3, 0,
1007 "Remove all properties from the text from START to END.\n\
1008 The optional third argument, OBJECT,\n\
1009 is the string or buffer containing the text.")
1010 (start, end, object)
1011 Lisp_Object start, end, object;
1013 register INTERVAL i;
1014 register INTERVAL prev_changed = NULL_INTERVAL;
1015 register int s, len, modified;
1017 if (NILP (object))
1018 XSET (object, Lisp_Buffer, current_buffer);
1020 i = validate_interval_range (object, &start, &end, soft);
1021 if (NULL_INTERVAL_P (i))
1022 return Qnil;
1024 s = XINT (start);
1025 len = XINT (end) - s;
1027 if (i->position != s)
1029 register int got;
1030 register INTERVAL unchanged = i;
1032 /* If there are properties here, then this text will be modified. */
1033 if (! NILP (i->plist))
1035 i = split_interval_right (unchanged, s - unchanged->position);
1036 i->plist = Qnil;
1037 modified++;
1039 if (LENGTH (i) > len)
1041 i = split_interval_right (i, len);
1042 copy_properties (unchanged, i);
1043 return Qt;
1046 if (LENGTH (i) == len)
1047 return Qt;
1049 got = LENGTH (i);
1051 /* If the text of I is without any properties, and contains
1052 LEN or more characters, then we may return without changing
1053 anything.*/
1054 else if (LENGTH (i) - (s - i->position) <= len)
1055 return Qnil;
1056 /* The amount of text to change extends past I, so just note
1057 how much we've gotten. */
1058 else
1059 got = LENGTH (i) - (s - i->position);
1061 len -= got;
1062 prev_changed = i;
1063 i = next_interval (i);
1066 /* We are starting at the beginning of an interval, I. */
1067 while (len > 0)
1069 if (LENGTH (i) >= len)
1071 /* If I has no properties, simply merge it if possible. */
1072 if (NILP (i->plist))
1074 if (! NULL_INTERVAL_P (prev_changed))
1075 merge_interval_left (i);
1077 return modified ? Qt : Qnil;
1080 if (LENGTH (i) > len)
1081 i = split_interval_left (i, len);
1082 if (! NULL_INTERVAL_P (prev_changed))
1083 merge_interval_left (i);
1084 else
1085 i->plist = Qnil;
1087 return Qt;
1090 /* Here if we still need to erase past the end of I */
1091 len -= LENGTH (i);
1092 if (NULL_INTERVAL_P (prev_changed))
1094 modified += erase_properties (i);
1095 prev_changed = i;
1097 else
1099 modified += ! NILP (i->plist);
1100 /* Merging I will give it the properties of PREV_CHANGED. */
1101 prev_changed = i = merge_interval_left (i);
1104 i = next_interval (i);
1107 return modified ? Qt : Qnil;
1109 #endif /* 0 */
1111 /* I don't think this is the right interface to export; how often do you
1112 want to do something like this, other than when you're copying objects
1113 around?
1115 I think it would be better to have a pair of functions, one which
1116 returns the text properties of a region as a list of ranges and
1117 plists, and another which applies such a list to another object. */
1119 /* DEFUN ("copy-text-properties", Fcopy_text_properties,
1120 Scopy_text_properties, 5, 6, 0,
1121 "Add properties from SRC-START to SRC-END of SRC at DEST-POS of DEST.\n\
1122 SRC and DEST may each refer to strings or buffers.\n\
1123 Optional sixth argument PROP causes only that property to be copied.\n\
1124 Properties are copied to DEST as if by `add-text-properties'.\n\
1125 Return t if any property value actually changed, nil otherwise.") */
1127 Lisp_Object
1128 copy_text_properties (start, end, src, pos, dest, prop)
1129 Lisp_Object start, end, src, pos, dest, prop;
1131 INTERVAL i;
1132 Lisp_Object res;
1133 Lisp_Object stuff;
1134 Lisp_Object plist;
1135 int s, e, e2, p, len, modified = 0;
1137 i = validate_interval_range (src, &start, &end, soft);
1138 if (NULL_INTERVAL_P (i))
1139 return Qnil;
1141 CHECK_NUMBER_COERCE_MARKER (pos, 0);
1143 Lisp_Object dest_start, dest_end;
1145 dest_start = pos;
1146 XFASTINT (dest_end) = XINT (dest_start) + (XINT (end) - XINT (start));
1147 /* Apply this to a copy of pos; it will try to increment its arguments,
1148 which we don't want. */
1149 validate_interval_range (dest, &dest_start, &dest_end, soft);
1152 s = XINT (start);
1153 e = XINT (end);
1154 p = XINT (pos);
1156 stuff = Qnil;
1158 while (s < e)
1160 e2 = i->position + LENGTH (i);
1161 if (e2 > e)
1162 e2 = e;
1163 len = e2 - s;
1165 plist = i->plist;
1166 if (! NILP (prop))
1167 while (! NILP (plist))
1169 if (EQ (Fcar (plist), prop))
1171 plist = Fcons (prop, Fcons (Fcar (Fcdr (plist)), Qnil));
1172 break;
1174 plist = Fcdr (Fcdr (plist));
1176 if (! NILP (plist))
1178 /* Must defer modifications to the interval tree in case src
1179 and dest refer to the same string or buffer. */
1180 stuff = Fcons (Fcons (make_number (p),
1181 Fcons (make_number (p + len),
1182 Fcons (plist, Qnil))),
1183 stuff);
1186 i = next_interval (i);
1187 if (NULL_INTERVAL_P (i))
1188 break;
1190 p += len;
1191 s = i->position;
1194 while (! NILP (stuff))
1196 res = Fcar (stuff);
1197 res = Fadd_text_properties (Fcar (res), Fcar (Fcdr (res)),
1198 Fcar (Fcdr (Fcdr (res))), dest);
1199 if (! NILP (res))
1200 modified++;
1201 stuff = Fcdr (stuff);
1204 return modified ? Qt : Qnil;
1207 void
1208 syms_of_textprop ()
1210 DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold,
1211 "Threshold for rebalancing interval trees, expressed as the\n\
1212 percentage by which the left interval tree should not differ from the right.");
1213 interval_balance_threshold = 8;
1215 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks,
1216 "If nonnil, don't call the text property values of\n\
1217 `point-left' and `point-entered'.");
1218 Vinhibit_point_motion_hooks = Qnil;
1220 /* Common attributes one might give text */
1222 staticpro (&Qforeground);
1223 Qforeground = intern ("foreground");
1224 staticpro (&Qbackground);
1225 Qbackground = intern ("background");
1226 staticpro (&Qfont);
1227 Qfont = intern ("font");
1228 staticpro (&Qstipple);
1229 Qstipple = intern ("stipple");
1230 staticpro (&Qunderline);
1231 Qunderline = intern ("underline");
1232 staticpro (&Qread_only);
1233 Qread_only = intern ("read-only");
1234 staticpro (&Qinvisible);
1235 Qinvisible = intern ("invisible");
1236 staticpro (&Qhidden);
1237 Qhidden = intern ("hidden");
1238 staticpro (&Qcategory);
1239 Qcategory = intern ("category");
1240 staticpro (&Qlocal_map);
1241 Qlocal_map = intern ("local-map");
1242 staticpro (&Qfront_sticky);
1243 Qfront_sticky = intern ("front-sticky");
1244 staticpro (&Qrear_nonsticky);
1245 Qrear_nonsticky = intern ("rear-nonsticky");
1247 /* Properties that text might use to specify certain actions */
1249 staticpro (&Qmouse_left);
1250 Qmouse_left = intern ("mouse-left");
1251 staticpro (&Qmouse_entered);
1252 Qmouse_entered = intern ("mouse-entered");
1253 staticpro (&Qpoint_left);
1254 Qpoint_left = intern ("point-left");
1255 staticpro (&Qpoint_entered);
1256 Qpoint_entered = intern ("point-entered");
1258 defsubr (&Stext_properties_at);
1259 defsubr (&Sget_text_property);
1260 defsubr (&Snext_property_change);
1261 defsubr (&Snext_single_property_change);
1262 defsubr (&Sprevious_property_change);
1263 defsubr (&Sprevious_single_property_change);
1264 defsubr (&Sadd_text_properties);
1265 defsubr (&Sput_text_property);
1266 defsubr (&Sset_text_properties);
1267 defsubr (&Sremove_text_properties);
1268 defsubr (&Stext_property_any);
1269 defsubr (&Stext_property_not_all);
1270 /* defsubr (&Serase_text_properties); */
1271 /* defsubr (&Scopy_text_properties); */
1274 #else
1276 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1278 #endif /* USE_TEXT_PROPERTIES */