(struct frame): New field face_alist.
[bpt/emacs.git] / src / textprop.c
1 /* Interface code for dealing with text properties.
2 Copyright (C) 1993 Free Software Foundation, Inc.
3
4 This file is part of GNU Emacs.
5
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 1, or (at your option)
9 any later version.
10
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.
15
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. */
19
20 #include "config.h"
21 #include "lisp.h"
22 #include "intervals.h"
23 #include "buffer.h"
24 \f
25
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.
29
30 set_properties needs to deal with the interval property cache.
31
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 neccessary for the system to remain consistent. This requirement
36 is enforced by the subrs installing properties onto the intervals. */
37
38 /* The rest of the file is within this conditional */
39 #ifdef USE_TEXT_PROPERTIES
40 \f
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 Qmodification_hooks;
47 Lisp_Object Qcategory;
48 Lisp_Object Qlocal_map;
49
50 /* Visual properties text (including strings) may have. */
51 Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple;
52 Lisp_Object Qinvisible, Qread_only;
53 \f
54 /* Extract the interval at the position pointed to by BEGIN from
55 OBJECT, a string or buffer. Additionally, check that the positions
56 pointed to by BEGIN and END are within the bounds of OBJECT, and
57 reverse them if *BEGIN is greater than *END. The objects pointed
58 to by BEGIN and END may be integers or markers; if the latter, they
59 are coerced to integers.
60
61 When OBJECT is a string, we increment *BEGIN and *END
62 to make them origin-one.
63
64 Note that buffer points don't correspond to interval indices.
65 For example, point-max is 1 greater than the index of the last
66 character. This difference is handled in the caller, which uses
67 the validated points to determine a length, and operates on that.
68 Exceptions are Ftext_properties_at, Fnext_property_change, and
69 Fprevious_property_change which call this function with BEGIN == END.
70 Handle this case specially.
71
72 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
73 create an interval tree for OBJECT if one doesn't exist, provided
74 the object actually contains text. In the current design, if there
75 is no text, there can be no text properties. */
76
77 #define soft 0
78 #define hard 1
79
80 static INTERVAL
81 validate_interval_range (object, begin, end, force)
82 Lisp_Object object, *begin, *end;
83 int force;
84 {
85 register INTERVAL i;
86 int searchpos;
87
88 CHECK_STRING_OR_BUFFER (object, 0);
89 CHECK_NUMBER_COERCE_MARKER (*begin, 0);
90 CHECK_NUMBER_COERCE_MARKER (*end, 0);
91
92 /* If we are asked for a point, but from a subr which operates
93 on a range, then return nothing. */
94 if (*begin == *end && begin != end)
95 return NULL_INTERVAL;
96
97 if (XINT (*begin) > XINT (*end))
98 {
99 Lisp_Object n;
100 n = *begin;
101 *begin = *end;
102 *end = n;
103 }
104
105 if (XTYPE (object) == Lisp_Buffer)
106 {
107 register struct buffer *b = XBUFFER (object);
108
109 if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end)
110 && XINT (*end) <= BUF_ZV (b)))
111 args_out_of_range (*begin, *end);
112 i = b->intervals;
113
114 /* If there's no text, there are no properties. */
115 if (BUF_BEGV (b) == BUF_ZV (b))
116 return NULL_INTERVAL;
117
118 searchpos = XINT (*begin);
119 if (searchpos == BUF_Z (b))
120 searchpos--;
121 #if 0
122 /* Special case for point-max: return the interval for the
123 last character. */
124 if (*begin == *end && *begin == BUF_Z (b))
125 *begin -= 1;
126 #endif
127 }
128 else
129 {
130 register struct Lisp_String *s = XSTRING (object);
131
132 if (! (0 <= XINT (*begin) && XINT (*begin) <= XINT (*end)
133 && XINT (*end) <= s->size))
134 args_out_of_range (*begin, *end);
135 /* User-level Positions in strings start with 0,
136 but the interval code always wants positions starting with 1. */
137 XFASTINT (*begin) += 1;
138 XFASTINT (*end) += 1;
139 i = s->intervals;
140
141 if (s->size == 0)
142 return NULL_INTERVAL;
143
144 searchpos = XINT (*begin);
145 if (searchpos > s->size)
146 searchpos--;
147 }
148
149 if (NULL_INTERVAL_P (i))
150 return (force ? create_root_interval (object) : i);
151
152 return find_interval (i, searchpos);
153 }
154
155 /* Validate LIST as a property list. If LIST is not a list, then
156 make one consisting of (LIST nil). Otherwise, verify that LIST
157 is even numbered and thus suitable as a plist. */
158
159 static Lisp_Object
160 validate_plist (list)
161 {
162 if (NILP (list))
163 return Qnil;
164
165 if (CONSP (list))
166 {
167 register int i;
168 register Lisp_Object tail;
169 for (i = 0, tail = list; !NILP (tail); i++)
170 tail = Fcdr (tail);
171 if (i & 1)
172 error ("Odd length text property list");
173 return list;
174 }
175
176 return Fcons (list, Fcons (Qnil, Qnil));
177 }
178
179 /* Return nonzero if interval I has all the properties,
180 with the same values, of list PLIST. */
181
182 static int
183 interval_has_all_properties (plist, i)
184 Lisp_Object plist;
185 INTERVAL i;
186 {
187 register Lisp_Object tail1, tail2, sym1, sym2;
188 register int found;
189
190 /* Go through each element of PLIST. */
191 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
192 {
193 sym1 = Fcar (tail1);
194 found = 0;
195
196 /* Go through I's plist, looking for sym1 */
197 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
198 if (EQ (sym1, Fcar (tail2)))
199 {
200 /* Found the same property on both lists. If the
201 values are unequal, return zero. */
202 if (! EQ (Fequal (Fcar (Fcdr (tail1)), Fcar (Fcdr (tail2))),
203 Qt))
204 return 0;
205
206 /* Property has same value on both lists; go to next one. */
207 found = 1;
208 break;
209 }
210
211 if (! found)
212 return 0;
213 }
214
215 return 1;
216 }
217
218 /* Return nonzero if the plist of interval I has any of the
219 properties of PLIST, regardless of their values. */
220
221 static INLINE int
222 interval_has_some_properties (plist, i)
223 Lisp_Object plist;
224 INTERVAL i;
225 {
226 register Lisp_Object tail1, tail2, sym;
227
228 /* Go through each element of PLIST. */
229 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
230 {
231 sym = Fcar (tail1);
232
233 /* Go through i's plist, looking for tail1 */
234 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
235 if (EQ (sym, Fcar (tail2)))
236 return 1;
237 }
238
239 return 0;
240 }
241 \f
242 /* Set the properties of INTERVAL to PROPERTIES,
243 and record undo info for the previous values.
244 OBJECT is the string or buffer that INTERVAL belongs to. */
245
246 static void
247 set_properties (properties, interval, object)
248 Lisp_Object properties, object;
249 INTERVAL interval;
250 {
251 Lisp_Object oldprops;
252 oldprops = interval->plist;
253
254 /* Record undo for old properties. */
255 while (XTYPE (oldprops) == Lisp_Cons)
256 {
257 Lisp_Object sym;
258 sym = Fcar (oldprops);
259 record_property_change (interval->position, LENGTH (interval),
260 sym, Fcar_safe (Fcdr (oldprops)),
261 object);
262
263 oldprops = Fcdr_safe (Fcdr (oldprops));
264 }
265
266 /* Store new properties. */
267 interval->plist = Fcopy_sequence (properties);
268 }
269
270 /* Add the properties of PLIST to the interval I, or set
271 the value of I's property to the value of the property on PLIST
272 if they are different.
273
274 OBJECT should be the string or buffer the interval is in.
275
276 Return nonzero if this changes I (i.e., if any members of PLIST
277 are actually added to I's plist) */
278
279 static int
280 add_properties (plist, i, object)
281 Lisp_Object plist;
282 INTERVAL i;
283 Lisp_Object object;
284 {
285 register Lisp_Object tail1, tail2, sym1, val1;
286 register int changed = 0;
287 register int found;
288
289 /* Go through each element of PLIST. */
290 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
291 {
292 sym1 = Fcar (tail1);
293 val1 = Fcar (Fcdr (tail1));
294 found = 0;
295
296 /* Go through I's plist, looking for sym1 */
297 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
298 if (EQ (sym1, Fcar (tail2)))
299 {
300 register Lisp_Object this_cdr = Fcdr (tail2);
301
302 /* Found the property. Now check its value. */
303 found = 1;
304
305 /* The properties have the same value on both lists.
306 Continue to the next property. */
307 if (!NILP (Fequal (val1, Fcar (this_cdr))))
308 break;
309
310 /* Record this change in the buffer, for undo purposes. */
311 if (XTYPE (object) == Lisp_Buffer)
312 {
313 record_property_change (i->position, LENGTH (i),
314 sym1, Fcar (this_cdr), object);
315 modify_region (make_number (i->position),
316 make_number (i->position + LENGTH (i)));
317 }
318
319 /* I's property has a different value -- change it */
320 Fsetcar (this_cdr, val1);
321 changed++;
322 break;
323 }
324
325 if (! found)
326 {
327 /* Record this change in the buffer, for undo purposes. */
328 if (XTYPE (object) == Lisp_Buffer)
329 {
330 record_property_change (i->position, LENGTH (i),
331 sym1, Qnil, object);
332 modify_region (make_number (i->position),
333 make_number (i->position + LENGTH (i)));
334 }
335 i->plist = Fcons (sym1, Fcons (val1, i->plist));
336 changed++;
337 }
338 }
339
340 return changed;
341 }
342
343 /* For any members of PLIST which are properties of I, remove them
344 from I's plist.
345 OBJECT is the string or buffer containing I. */
346
347 static int
348 remove_properties (plist, i, object)
349 Lisp_Object plist;
350 INTERVAL i;
351 Lisp_Object object;
352 {
353 register Lisp_Object tail1, tail2, sym;
354 register Lisp_Object current_plist = i->plist;
355 register int changed = 0;
356
357 /* Go through each element of plist. */
358 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
359 {
360 sym = Fcar (tail1);
361
362 /* First, remove the symbol if its at the head of the list */
363 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
364 {
365 if (XTYPE (object) == Lisp_Buffer)
366 {
367 record_property_change (i->position, LENGTH (i),
368 sym, Fcar (Fcdr (current_plist)),
369 object);
370 modify_region (make_number (i->position),
371 make_number (i->position + LENGTH (i)));
372 }
373
374 current_plist = Fcdr (Fcdr (current_plist));
375 changed++;
376 }
377
378 /* Go through i's plist, looking for sym */
379 tail2 = current_plist;
380 while (! NILP (tail2))
381 {
382 register Lisp_Object this = Fcdr (Fcdr (tail2));
383 if (EQ (sym, Fcar (this)))
384 {
385 if (XTYPE (object) == Lisp_Buffer)
386 {
387 record_property_change (i->position, LENGTH (i),
388 sym, Fcar (Fcdr (this)), object);
389 modify_region (make_number (i->position),
390 make_number (i->position + LENGTH (i)));
391 }
392
393 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
394 changed++;
395 }
396 tail2 = this;
397 }
398 }
399
400 if (changed)
401 i->plist = current_plist;
402 return changed;
403 }
404
405 #if 0
406 /* Remove all properties from interval I. Return non-zero
407 if this changes the interval. */
408
409 static INLINE int
410 erase_properties (i)
411 INTERVAL i;
412 {
413 if (NILP (i->plist))
414 return 0;
415
416 i->plist = Qnil;
417 return 1;
418 }
419 #endif
420 \f
421 DEFUN ("text-properties-at", Ftext_properties_at,
422 Stext_properties_at, 1, 2, 0,
423 "Return the list of properties held by the character at POSITION\n\
424 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
425 defaults to the current buffer.\n\
426 If POSITION is at the end of OBJECT, the value is nil.")
427 (pos, object)
428 Lisp_Object pos, object;
429 {
430 register INTERVAL i;
431
432 if (NILP (object))
433 XSET (object, Lisp_Buffer, current_buffer);
434
435 i = validate_interval_range (object, &pos, &pos, soft);
436 if (NULL_INTERVAL_P (i))
437 return Qnil;
438 /* If POS is at the end of the interval,
439 it means it's the end of OBJECT.
440 There are no properties at the very end,
441 since no character follows. */
442 if (XINT (pos) == LENGTH (i) + i->position)
443 return Qnil;
444
445 return i->plist;
446 }
447
448 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
449 "Return the value of position POS's property PROP, in OBJECT.\n\
450 OBJECT is optional and defaults to the current buffer.\n\
451 If POSITION is at the end of OBJECT, the value is nil.")
452 (pos, prop, object)
453 Lisp_Object pos, object;
454 register Lisp_Object prop;
455 {
456 register INTERVAL i;
457 register Lisp_Object tail;
458
459 if (NILP (object))
460 XSET (object, Lisp_Buffer, current_buffer);
461 i = validate_interval_range (object, &pos, &pos, soft);
462 if (NULL_INTERVAL_P (i))
463 return Qnil;
464
465 /* If POS is at the end of the interval,
466 it means it's the end of OBJECT.
467 There are no properties at the very end,
468 since no character follows. */
469 if (XINT (pos) == LENGTH (i) + i->position)
470 return Qnil;
471
472 return textget (i->plist, prop);
473 }
474
475 DEFUN ("next-property-change", Fnext_property_change,
476 Snext_property_change, 1, 2, 0,
477 "Return the position of next property change.\n\
478 Scans characters forward from POS in OBJECT till it finds\n\
479 a change in some text property, then returns the position of the change.\n\
480 The optional second argument OBJECT is the string or buffer to scan.\n\
481 Return nil if the property is constant all the way to the end of OBJECT.\n\
482 If the value is non-nil, it is a position greater than POS, never equal.")
483 (pos, object)
484 Lisp_Object pos, object;
485 {
486 register INTERVAL i, next;
487
488 if (NILP (object))
489 XSET (object, Lisp_Buffer, current_buffer);
490
491 i = validate_interval_range (object, &pos, &pos, soft);
492 if (NULL_INTERVAL_P (i))
493 return Qnil;
494
495 next = next_interval (i);
496 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
497 next = next_interval (next);
498
499 if (NULL_INTERVAL_P (next))
500 return Qnil;
501
502 return next->position - (XTYPE (object) == Lisp_String);
503 ;
504 }
505
506 DEFUN ("next-single-property-change", Fnext_single_property_change,
507 Snext_single_property_change, 1, 3, 0,
508 "Return the position of next property change for a specific property.\n\
509 Scans characters forward from POS till it finds\n\
510 a change in the PROP property, then returns the position of the change.\n\
511 The optional third argument OBJECT is the string or buffer to scan.\n\
512 Return nil if the property is constant all the way to the end of OBJECT.\n\
513 If the value is non-nil, it is a position greater than POS, never equal.")
514 (pos, prop, object)
515 Lisp_Object pos, prop, object;
516 {
517 register INTERVAL i, next;
518 register Lisp_Object here_val;
519
520 if (NILP (object))
521 XSET (object, Lisp_Buffer, current_buffer);
522
523 i = validate_interval_range (object, &pos, &pos, soft);
524 if (NULL_INTERVAL_P (i))
525 return Qnil;
526
527 here_val = textget (prop, i->plist);
528 next = next_interval (i);
529 while (! NULL_INTERVAL_P (next) && EQ (here_val, textget (prop, next->plist)))
530 next = next_interval (next);
531
532 if (NULL_INTERVAL_P (next))
533 return Qnil;
534
535 return next->position - (XTYPE (object) == Lisp_String);
536 }
537
538 DEFUN ("previous-property-change", Fprevious_property_change,
539 Sprevious_property_change, 1, 2, 0,
540 "Return the position of previous property change.\n\
541 Scans characters backwards from POS in OBJECT till it finds\n\
542 a change in some text property, then returns the position of the change.\n\
543 The optional second argument OBJECT is the string or buffer to scan.\n\
544 Return nil if the property is constant all the way to the start of OBJECT.\n\
545 If the value is non-nil, it is a position less than POS, never equal.")
546 (pos, object)
547 Lisp_Object pos, object;
548 {
549 register INTERVAL i, previous;
550
551 if (NILP (object))
552 XSET (object, Lisp_Buffer, current_buffer);
553
554 i = validate_interval_range (object, &pos, &pos, soft);
555 if (NULL_INTERVAL_P (i))
556 return Qnil;
557
558 previous = previous_interval (i);
559 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i))
560 previous = previous_interval (previous);
561 if (NULL_INTERVAL_P (previous))
562 return Qnil;
563
564 return (previous->position + LENGTH (previous) - 1
565 - (XTYPE (object) == Lisp_String));
566 }
567
568 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
569 Sprevious_single_property_change, 2, 3, 0,
570 "Return the position of previous property change for a specific property.\n\
571 Scans characters backward from POS till it finds\n\
572 a change in the PROP property, then returns the position of the change.\n\
573 The optional third argument OBJECT is the string or buffer to scan.\n\
574 Return nil if the property is constant all the way to the start of OBJECT.\n\
575 If the value is non-nil, it is a position less than POS, never equal.")
576 (pos, prop, object)
577 Lisp_Object pos, prop, object;
578 {
579 register INTERVAL i, previous;
580 register Lisp_Object here_val;
581
582 if (NILP (object))
583 XSET (object, Lisp_Buffer, current_buffer);
584
585 i = validate_interval_range (object, &pos, &pos, soft);
586 if (NULL_INTERVAL_P (i))
587 return Qnil;
588
589 here_val = textget (prop, i->plist);
590 previous = previous_interval (i);
591 while (! NULL_INTERVAL_P (previous)
592 && EQ (here_val, textget (prop, previous->plist)))
593 previous = previous_interval (previous);
594 if (NULL_INTERVAL_P (previous))
595 return Qnil;
596
597 return (previous->position + LENGTH (previous) - 1
598 - (XTYPE (object) == Lisp_String));
599 }
600
601 DEFUN ("add-text-properties", Fadd_text_properties,
602 Sadd_text_properties, 3, 4, 0,
603 "Add properties to the text from START to END.\n\
604 The third argument PROPS is a property list\n\
605 specifying the property values to add.\n\
606 The optional fourth argument, OBJECT,\n\
607 is the string or buffer containing the text.\n\
608 Return t if any property value actually changed, nil otherwise.")
609 (start, end, properties, object)
610 Lisp_Object start, end, properties, object;
611 {
612 register INTERVAL i, unchanged;
613 register int s, len, modified = 0;
614
615 properties = validate_plist (properties);
616 if (NILP (properties))
617 return Qnil;
618
619 if (NILP (object))
620 XSET (object, Lisp_Buffer, current_buffer);
621
622 i = validate_interval_range (object, &start, &end, hard);
623 if (NULL_INTERVAL_P (i))
624 return Qnil;
625
626 s = XINT (start);
627 len = XINT (end) - s;
628
629 /* If we're not starting on an interval boundary, we have to
630 split this interval. */
631 if (i->position != s)
632 {
633 /* If this interval already has the properties, we can
634 skip it. */
635 if (interval_has_all_properties (properties, i))
636 {
637 int got = (LENGTH (i) - (s - i->position));
638 if (got >= len)
639 return Qnil;
640 len -= got;
641 }
642 else
643 {
644 unchanged = i;
645 i = split_interval_right (unchanged, s - unchanged->position + 1);
646 copy_properties (unchanged, i);
647 if (LENGTH (i) > len)
648 {
649 i = split_interval_left (i, len + 1);
650 copy_properties (unchanged, i);
651 add_properties (properties, i, object);
652 return Qt;
653 }
654
655 add_properties (properties, i, object);
656 modified = 1;
657 len -= LENGTH (i);
658 i = next_interval (i);
659 }
660 }
661
662 /* We are at the beginning of an interval, with len to scan */
663 for (;;)
664 {
665 if (i == 0)
666 abort ();
667
668 if (LENGTH (i) >= len)
669 {
670 if (interval_has_all_properties (properties, i))
671 return modified ? Qt : Qnil;
672
673 if (LENGTH (i) == len)
674 {
675 add_properties (properties, i, object);
676 return Qt;
677 }
678
679 /* i doesn't have the properties, and goes past the change limit */
680 unchanged = i;
681 i = split_interval_left (unchanged, len + 1);
682 copy_properties (unchanged, i);
683 add_properties (properties, i, object);
684 return Qt;
685 }
686
687 len -= LENGTH (i);
688 modified += add_properties (properties, i, object);
689 i = next_interval (i);
690 }
691 }
692
693 DEFUN ("put-text-property", Fput_text_property,
694 Sput_text_property, 4, 5, 0,
695 "Set one property of the text from START to END.\n\
696 The third and fourth arguments PROP and VALUE\n\
697 specify the property to add.\n\
698 The optional fifth argument, OBJECT,\n\
699 is the string or buffer containing the text.")
700 (start, end, prop, value, object)
701 Lisp_Object start, end, prop, value, object;
702 {
703 Fadd_text_properties (start, end,
704 Fcons (prop, Fcons (value, Qnil)),
705 object);
706 return Qnil;
707 }
708
709 DEFUN ("set-text-properties", Fset_text_properties,
710 Sset_text_properties, 3, 4, 0,
711 "Completely replace properties of text from START to END.\n\
712 The third argument PROPS is the new property list.\n\
713 The optional fourth argument, OBJECT,\n\
714 is the string or buffer containing the text.")
715 (start, end, props, object)
716 Lisp_Object start, end, props, object;
717 {
718 register INTERVAL i, unchanged;
719 register INTERVAL prev_changed = NULL_INTERVAL;
720 register int s, len;
721
722 props = validate_plist (props);
723 if (NILP (props))
724 return Qnil;
725
726 if (NILP (object))
727 XSET (object, Lisp_Buffer, current_buffer);
728
729 i = validate_interval_range (object, &start, &end, hard);
730 if (NULL_INTERVAL_P (i))
731 return Qnil;
732
733 s = XINT (start);
734 len = XINT (end) - s;
735
736 if (i->position != s)
737 {
738 unchanged = i;
739 i = split_interval_right (unchanged, s - unchanged->position + 1);
740 set_properties (props, i, object);
741
742 if (LENGTH (i) > len)
743 {
744 i = split_interval_right (i, len);
745 copy_properties (unchanged, i);
746 return Qt;
747 }
748
749 if (LENGTH (i) == len)
750 return Qt;
751
752 prev_changed = i;
753 len -= LENGTH (i);
754 i = next_interval (i);
755 }
756
757 /* We are starting at the beginning of an interval, I */
758 while (len > 0)
759 {
760 if (i == 0)
761 abort ();
762
763 if (LENGTH (i) >= len)
764 {
765 if (LENGTH (i) > len)
766 i = split_interval_left (i, len + 1);
767
768 if (NULL_INTERVAL_P (prev_changed))
769 set_properties (props, i, object);
770 else
771 merge_interval_left (i);
772 return Qt;
773 }
774
775 len -= LENGTH (i);
776 if (NULL_INTERVAL_P (prev_changed))
777 {
778 set_properties (props, i, object);
779 prev_changed = i;
780 }
781 else
782 prev_changed = i = merge_interval_left (i);
783
784 i = next_interval (i);
785 }
786
787 return Qt;
788 }
789
790 DEFUN ("remove-text-properties", Fremove_text_properties,
791 Sremove_text_properties, 3, 4, 0,
792 "Remove some properties from text from START to END.\n\
793 The third argument PROPS is a property list\n\
794 whose property names specify the properties to remove.\n\
795 \(The values stored in PROPS are ignored.)\n\
796 The optional fourth argument, OBJECT,\n\
797 is the string or buffer containing the text.\n\
798 Return t if any property was actually removed, nil otherwise.")
799 (start, end, props, object)
800 Lisp_Object start, end, props, object;
801 {
802 register INTERVAL i, unchanged;
803 register int s, len, modified = 0;
804
805 if (NILP (object))
806 XSET (object, Lisp_Buffer, current_buffer);
807
808 i = validate_interval_range (object, &start, &end, soft);
809 if (NULL_INTERVAL_P (i))
810 return Qnil;
811
812 s = XINT (start);
813 len = XINT (end) - s;
814
815 if (i->position != s)
816 {
817 /* No properties on this first interval -- return if
818 it covers the entire region. */
819 if (! interval_has_some_properties (props, i))
820 {
821 int got = (LENGTH (i) - (s - i->position));
822 if (got >= len)
823 return Qnil;
824 len -= got;
825 }
826 /* Remove the properties from this interval. If it's short
827 enough, return, splitting it if it's too short. */
828 else
829 {
830 unchanged = i;
831 i = split_interval_right (unchanged, s - unchanged->position + 1);
832 copy_properties (unchanged, i);
833 if (LENGTH (i) > len)
834 {
835 i = split_interval_left (i, len + 1);
836 copy_properties (unchanged, i);
837 remove_properties (props, i, object);
838 return Qt;
839 }
840
841 remove_properties (props, i, object);
842 modified = 1;
843 len -= LENGTH (i);
844 i = next_interval (i);
845 }
846 }
847
848 /* We are at the beginning of an interval, with len to scan */
849 for (;;)
850 {
851 if (i == 0)
852 abort ();
853
854 if (LENGTH (i) >= len)
855 {
856 if (! interval_has_some_properties (props, i))
857 return modified ? Qt : Qnil;
858
859 if (LENGTH (i) == len)
860 {
861 remove_properties (props, i, object);
862 return Qt;
863 }
864
865 /* i has the properties, and goes past the change limit */
866 unchanged = split_interval_right (i, len + 1);
867 copy_properties (unchanged, i);
868 remove_properties (props, i, object);
869 return Qt;
870 }
871
872 len -= LENGTH (i);
873 modified += remove_properties (props, i, object);
874 i = next_interval (i);
875 }
876 }
877
878 #if 0 /* You can use set-text-properties for this. */
879
880 DEFUN ("erase-text-properties", Ferase_text_properties,
881 Serase_text_properties, 2, 3, 0,
882 "Remove all properties from the text from START to END.\n\
883 The optional third argument, OBJECT,\n\
884 is the string or buffer containing the text.")
885 (start, end, object)
886 Lisp_Object start, end, object;
887 {
888 register INTERVAL i;
889 register INTERVAL prev_changed = NULL_INTERVAL;
890 register int s, len, modified;
891
892 if (NILP (object))
893 XSET (object, Lisp_Buffer, current_buffer);
894
895 i = validate_interval_range (object, &start, &end, soft);
896 if (NULL_INTERVAL_P (i))
897 return Qnil;
898
899 s = XINT (start);
900 len = XINT (end) - s;
901
902 if (i->position != s)
903 {
904 register int got;
905 register INTERVAL unchanged = i;
906
907 /* If there are properties here, then this text will be modified. */
908 if (! NILP (i->plist))
909 {
910 i = split_interval_right (unchanged, s - unchanged->position + 1);
911 i->plist = Qnil;
912 modified++;
913
914 if (LENGTH (i) > len)
915 {
916 i = split_interval_right (i, len + 1);
917 copy_properties (unchanged, i);
918 return Qt;
919 }
920
921 if (LENGTH (i) == len)
922 return Qt;
923
924 got = LENGTH (i);
925 }
926 /* If the text of I is without any properties, and contains
927 LEN or more characters, then we may return without changing
928 anything.*/
929 else if (LENGTH (i) - (s - i->position) <= len)
930 return Qnil;
931 /* The amount of text to change extends past I, so just note
932 how much we've gotten. */
933 else
934 got = LENGTH (i) - (s - i->position);
935
936 len -= got;
937 prev_changed = i;
938 i = next_interval (i);
939 }
940
941 /* We are starting at the beginning of an interval, I. */
942 while (len > 0)
943 {
944 if (LENGTH (i) >= len)
945 {
946 /* If I has no properties, simply merge it if possible. */
947 if (NILP (i->plist))
948 {
949 if (! NULL_INTERVAL_P (prev_changed))
950 merge_interval_left (i);
951
952 return modified ? Qt : Qnil;
953 }
954
955 if (LENGTH (i) > len)
956 i = split_interval_left (i, len + 1);
957 if (! NULL_INTERVAL_P (prev_changed))
958 merge_interval_left (i);
959 else
960 i->plist = Qnil;
961
962 return Qt;
963 }
964
965 /* Here if we still need to erase past the end of I */
966 len -= LENGTH (i);
967 if (NULL_INTERVAL_P (prev_changed))
968 {
969 modified += erase_properties (i);
970 prev_changed = i;
971 }
972 else
973 {
974 modified += ! NILP (i->plist);
975 /* Merging I will give it the properties of PREV_CHANGED. */
976 prev_changed = i = merge_interval_left (i);
977 }
978
979 i = next_interval (i);
980 }
981
982 return modified ? Qt : Qnil;
983 }
984 #endif /* 0 */
985
986 void
987 syms_of_textprop ()
988 {
989 DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold,
990 "Threshold for rebalancing interval trees, expressed as the\n\
991 percentage by which the left interval tree should not differ from the right.");
992 interval_balance_threshold = 8;
993
994 /* Common attributes one might give text */
995
996 staticpro (&Qforeground);
997 Qforeground = intern ("foreground");
998 staticpro (&Qbackground);
999 Qbackground = intern ("background");
1000 staticpro (&Qfont);
1001 Qfont = intern ("font");
1002 staticpro (&Qstipple);
1003 Qstipple = intern ("stipple");
1004 staticpro (&Qunderline);
1005 Qunderline = intern ("underline");
1006 staticpro (&Qread_only);
1007 Qread_only = intern ("read-only");
1008 staticpro (&Qinvisible);
1009 Qinvisible = intern ("invisible");
1010 staticpro (&Qcategory);
1011 Qcategory = intern ("category");
1012 staticpro (&Qlocal_map);
1013 Qlocal_map = intern ("local-map");
1014
1015 /* Properties that text might use to specify certain actions */
1016
1017 staticpro (&Qmouse_left);
1018 Qmouse_left = intern ("mouse-left");
1019 staticpro (&Qmouse_entered);
1020 Qmouse_entered = intern ("mouse-entered");
1021 staticpro (&Qpoint_left);
1022 Qpoint_left = intern ("point-left");
1023 staticpro (&Qpoint_entered);
1024 Qpoint_entered = intern ("point-entered");
1025 staticpro (&Qmodification_hooks);
1026 Qmodification_hooks = intern ("modification-hooks");
1027
1028 defsubr (&Stext_properties_at);
1029 defsubr (&Sget_text_property);
1030 defsubr (&Snext_property_change);
1031 defsubr (&Snext_single_property_change);
1032 defsubr (&Sprevious_property_change);
1033 defsubr (&Sprevious_single_property_change);
1034 defsubr (&Sadd_text_properties);
1035 defsubr (&Sput_text_property);
1036 defsubr (&Sset_text_properties);
1037 defsubr (&Sremove_text_properties);
1038 /* defsubr (&Serase_text_properties); */
1039 }
1040
1041 #else
1042
1043 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1044
1045 #endif /* USE_TEXT_PROPERTIES */