* s/dgux.h: Move #definition of SYSTEM_MALLOC outside of
[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 (XBUFFER (object),
316 make_number (i->position),
317 make_number (i->position + LENGTH (i)));
318 }
319
320 /* I's property has a different value -- change it */
321 Fsetcar (this_cdr, val1);
322 changed++;
323 break;
324 }
325
326 if (! found)
327 {
328 /* Record this change in the buffer, for undo purposes. */
329 if (XTYPE (object) == Lisp_Buffer)
330 {
331 record_property_change (i->position, LENGTH (i),
332 sym1, Qnil, object);
333 modify_region (XBUFFER (object),
334 make_number (i->position),
335 make_number (i->position + LENGTH (i)));
336 }
337 i->plist = Fcons (sym1, Fcons (val1, i->plist));
338 changed++;
339 }
340 }
341
342 return changed;
343 }
344
345 /* For any members of PLIST which are properties of I, remove them
346 from I's plist.
347 OBJECT is the string or buffer containing I. */
348
349 static int
350 remove_properties (plist, i, object)
351 Lisp_Object plist;
352 INTERVAL i;
353 Lisp_Object object;
354 {
355 register Lisp_Object tail1, tail2, sym;
356 register Lisp_Object current_plist = i->plist;
357 register int changed = 0;
358
359 /* Go through each element of plist. */
360 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
361 {
362 sym = Fcar (tail1);
363
364 /* First, remove the symbol if its at the head of the list */
365 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
366 {
367 if (XTYPE (object) == Lisp_Buffer)
368 {
369 record_property_change (i->position, LENGTH (i),
370 sym, Fcar (Fcdr (current_plist)),
371 object);
372 modify_region (XBUFFER (object),
373 make_number (i->position),
374 make_number (i->position + LENGTH (i)));
375 }
376
377 current_plist = Fcdr (Fcdr (current_plist));
378 changed++;
379 }
380
381 /* Go through i's plist, looking for sym */
382 tail2 = current_plist;
383 while (! NILP (tail2))
384 {
385 register Lisp_Object this = Fcdr (Fcdr (tail2));
386 if (EQ (sym, Fcar (this)))
387 {
388 if (XTYPE (object) == Lisp_Buffer)
389 {
390 record_property_change (i->position, LENGTH (i),
391 sym, Fcar (Fcdr (this)), object);
392 modify_region (XBUFFER (object),
393 make_number (i->position),
394 make_number (i->position + LENGTH (i)));
395 }
396
397 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
398 changed++;
399 }
400 tail2 = this;
401 }
402 }
403
404 if (changed)
405 i->plist = current_plist;
406 return changed;
407 }
408
409 #if 0
410 /* Remove all properties from interval I. Return non-zero
411 if this changes the interval. */
412
413 static INLINE int
414 erase_properties (i)
415 INTERVAL i;
416 {
417 if (NILP (i->plist))
418 return 0;
419
420 i->plist = Qnil;
421 return 1;
422 }
423 #endif
424 \f
425 DEFUN ("text-properties-at", Ftext_properties_at,
426 Stext_properties_at, 1, 2, 0,
427 "Return the list of properties held by the character at POSITION\n\
428 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
429 defaults to the current buffer.\n\
430 If POSITION is at the end of OBJECT, the value is nil.")
431 (pos, object)
432 Lisp_Object pos, object;
433 {
434 register INTERVAL i;
435
436 if (NILP (object))
437 XSET (object, Lisp_Buffer, current_buffer);
438
439 i = validate_interval_range (object, &pos, &pos, soft);
440 if (NULL_INTERVAL_P (i))
441 return Qnil;
442 /* If POS is at the end of the interval,
443 it means it's the end of OBJECT.
444 There are no properties at the very end,
445 since no character follows. */
446 if (XINT (pos) == LENGTH (i) + i->position)
447 return Qnil;
448
449 return i->plist;
450 }
451
452 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
453 "Return the value of position POS's property PROP, in OBJECT.\n\
454 OBJECT is optional and defaults to the current buffer.\n\
455 If POSITION is at the end of OBJECT, the value is nil.")
456 (pos, prop, object)
457 Lisp_Object pos, object;
458 register Lisp_Object prop;
459 {
460 register INTERVAL i;
461 register Lisp_Object tail;
462
463 if (NILP (object))
464 XSET (object, Lisp_Buffer, current_buffer);
465 i = validate_interval_range (object, &pos, &pos, soft);
466 if (NULL_INTERVAL_P (i))
467 return Qnil;
468
469 /* If POS is at the end of the interval,
470 it means it's the end of OBJECT.
471 There are no properties at the very end,
472 since no character follows. */
473 if (XINT (pos) == LENGTH (i) + i->position)
474 return Qnil;
475
476 return textget (i->plist, prop);
477 }
478
479 DEFUN ("next-property-change", Fnext_property_change,
480 Snext_property_change, 1, 2, 0,
481 "Return the position of next property change.\n\
482 Scans characters forward from POS in OBJECT till it finds\n\
483 a change in some text property, then returns the position of the change.\n\
484 The optional second argument OBJECT is the string or buffer to scan.\n\
485 Return nil if the property is constant all the way to the end of OBJECT.\n\
486 If the value is non-nil, it is a position greater than POS, never equal.")
487 (pos, object)
488 Lisp_Object pos, object;
489 {
490 register INTERVAL i, next;
491
492 if (NILP (object))
493 XSET (object, Lisp_Buffer, current_buffer);
494
495 i = validate_interval_range (object, &pos, &pos, soft);
496 if (NULL_INTERVAL_P (i))
497 return Qnil;
498
499 next = next_interval (i);
500 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
501 next = next_interval (next);
502
503 if (NULL_INTERVAL_P (next))
504 return Qnil;
505
506 return next->position - (XTYPE (object) == Lisp_String);
507 ;
508 }
509
510 DEFUN ("next-single-property-change", Fnext_single_property_change,
511 Snext_single_property_change, 1, 3, 0,
512 "Return the position of next property change for a specific property.\n\
513 Scans characters forward from POS till it finds\n\
514 a change in the PROP property, then returns the position of the change.\n\
515 The optional third argument OBJECT is the string or buffer to scan.\n\
516 Return nil if the property is constant all the way to the end of OBJECT.\n\
517 If the value is non-nil, it is a position greater than POS, never equal.")
518 (pos, prop, object)
519 Lisp_Object pos, prop, object;
520 {
521 register INTERVAL i, next;
522 register Lisp_Object here_val;
523
524 if (NILP (object))
525 XSET (object, Lisp_Buffer, current_buffer);
526
527 i = validate_interval_range (object, &pos, &pos, soft);
528 if (NULL_INTERVAL_P (i))
529 return Qnil;
530
531 here_val = textget (i->plist, prop);
532 next = next_interval (i);
533 while (! NULL_INTERVAL_P (next)
534 && EQ (here_val, textget (next->plist, prop)))
535 next = next_interval (next);
536
537 if (NULL_INTERVAL_P (next))
538 return Qnil;
539
540 return next->position - (XTYPE (object) == Lisp_String);
541 }
542
543 DEFUN ("previous-property-change", Fprevious_property_change,
544 Sprevious_property_change, 1, 2, 0,
545 "Return the position of previous property change.\n\
546 Scans characters backwards from POS in OBJECT till it finds\n\
547 a change in some text property, then returns the position of the change.\n\
548 The optional second argument OBJECT is the string or buffer to scan.\n\
549 Return nil if the property is constant all the way to the start of OBJECT.\n\
550 If the value is non-nil, it is a position less than POS, never equal.")
551 (pos, object)
552 Lisp_Object pos, object;
553 {
554 register INTERVAL i, previous;
555
556 if (NILP (object))
557 XSET (object, Lisp_Buffer, current_buffer);
558
559 i = validate_interval_range (object, &pos, &pos, soft);
560 if (NULL_INTERVAL_P (i))
561 return Qnil;
562
563 previous = previous_interval (i);
564 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i))
565 previous = previous_interval (previous);
566 if (NULL_INTERVAL_P (previous))
567 return Qnil;
568
569 return (previous->position + LENGTH (previous) - 1
570 - (XTYPE (object) == Lisp_String));
571 }
572
573 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
574 Sprevious_single_property_change, 2, 3, 0,
575 "Return the position of previous property change for a specific property.\n\
576 Scans characters backward from POS till it finds\n\
577 a change in the PROP property, then returns the position of the change.\n\
578 The optional third argument OBJECT is the string or buffer to scan.\n\
579 Return nil if the property is constant all the way to the start of OBJECT.\n\
580 If the value is non-nil, it is a position less than POS, never equal.")
581 (pos, prop, object)
582 Lisp_Object pos, prop, object;
583 {
584 register INTERVAL i, previous;
585 register Lisp_Object here_val;
586
587 if (NILP (object))
588 XSET (object, Lisp_Buffer, current_buffer);
589
590 i = validate_interval_range (object, &pos, &pos, soft);
591 if (NULL_INTERVAL_P (i))
592 return Qnil;
593
594 here_val = textget (i->plist, prop);
595 previous = previous_interval (i);
596 while (! NULL_INTERVAL_P (previous)
597 && EQ (here_val, textget (previous->plist, prop)))
598 previous = previous_interval (previous);
599 if (NULL_INTERVAL_P (previous))
600 return Qnil;
601
602 return (previous->position + LENGTH (previous) - 1
603 - (XTYPE (object) == Lisp_String));
604 }
605
606 DEFUN ("add-text-properties", Fadd_text_properties,
607 Sadd_text_properties, 3, 4, 0,
608 "Add properties to the text from START to END.\n\
609 The third argument PROPS is a property list\n\
610 specifying the property values to add.\n\
611 The optional fourth argument, OBJECT,\n\
612 is the string or buffer containing the text.\n\
613 Return t if any property value actually changed, nil otherwise.")
614 (start, end, properties, object)
615 Lisp_Object start, end, properties, object;
616 {
617 register INTERVAL i, unchanged;
618 register int s, len, modified = 0;
619
620 properties = validate_plist (properties);
621 if (NILP (properties))
622 return Qnil;
623
624 if (NILP (object))
625 XSET (object, Lisp_Buffer, current_buffer);
626
627 i = validate_interval_range (object, &start, &end, hard);
628 if (NULL_INTERVAL_P (i))
629 return Qnil;
630
631 s = XINT (start);
632 len = XINT (end) - s;
633
634 /* If we're not starting on an interval boundary, we have to
635 split this interval. */
636 if (i->position != s)
637 {
638 /* If this interval already has the properties, we can
639 skip it. */
640 if (interval_has_all_properties (properties, i))
641 {
642 int got = (LENGTH (i) - (s - i->position));
643 if (got >= len)
644 return Qnil;
645 len -= got;
646 }
647 else
648 {
649 unchanged = i;
650 i = split_interval_right (unchanged, s - unchanged->position + 1);
651 copy_properties (unchanged, i);
652 }
653 }
654
655 /* We are at the beginning of interval I, with LEN chars to scan. */
656 for (;;)
657 {
658 if (i == 0)
659 abort ();
660
661 if (LENGTH (i) >= len)
662 {
663 if (interval_has_all_properties (properties, i))
664 return modified ? Qt : Qnil;
665
666 if (LENGTH (i) == len)
667 {
668 add_properties (properties, i, object);
669 return Qt;
670 }
671
672 /* i doesn't have the properties, and goes past the change limit */
673 unchanged = i;
674 i = split_interval_left (unchanged, len + 1);
675 copy_properties (unchanged, i);
676 add_properties (properties, i, object);
677 return Qt;
678 }
679
680 len -= LENGTH (i);
681 modified += add_properties (properties, i, object);
682 i = next_interval (i);
683 }
684 }
685
686 DEFUN ("put-text-property", Fput_text_property,
687 Sput_text_property, 4, 5, 0,
688 "Set one property of the text from START to END.\n\
689 The third and fourth arguments PROP and VALUE\n\
690 specify the property to add.\n\
691 The optional fifth argument, OBJECT,\n\
692 is the string or buffer containing the text.")
693 (start, end, prop, value, object)
694 Lisp_Object start, end, prop, value, object;
695 {
696 Fadd_text_properties (start, end,
697 Fcons (prop, Fcons (value, Qnil)),
698 object);
699 return Qnil;
700 }
701
702 DEFUN ("set-text-properties", Fset_text_properties,
703 Sset_text_properties, 3, 4, 0,
704 "Completely replace properties of text from START to END.\n\
705 The third argument PROPS is the new property list.\n\
706 The optional fourth argument, OBJECT,\n\
707 is the string or buffer containing the text.")
708 (start, end, props, object)
709 Lisp_Object start, end, props, object;
710 {
711 register INTERVAL i, unchanged;
712 register INTERVAL prev_changed = NULL_INTERVAL;
713 register int s, len;
714
715 props = validate_plist (props);
716 if (NILP (props))
717 return Qnil;
718
719 if (NILP (object))
720 XSET (object, Lisp_Buffer, current_buffer);
721
722 i = validate_interval_range (object, &start, &end, hard);
723 if (NULL_INTERVAL_P (i))
724 return Qnil;
725
726 s = XINT (start);
727 len = XINT (end) - s;
728
729 if (i->position != s)
730 {
731 unchanged = i;
732 i = split_interval_right (unchanged, s - unchanged->position + 1);
733
734 if (LENGTH (i) > len)
735 {
736 copy_properties (unchanged, i);
737 i = split_interval_left (i, len + 1);
738 set_properties (props, i, object);
739 return Qt;
740 }
741
742 set_properties (props, i, object);
743
744 if (LENGTH (i) == len)
745 return Qt;
746
747 prev_changed = i;
748 len -= LENGTH (i);
749 i = next_interval (i);
750 }
751
752 /* We are starting at the beginning of an interval, I */
753 while (len > 0)
754 {
755 if (i == 0)
756 abort ();
757
758 if (LENGTH (i) >= len)
759 {
760 if (LENGTH (i) > len)
761 i = split_interval_left (i, len + 1);
762
763 if (NULL_INTERVAL_P (prev_changed))
764 set_properties (props, i, object);
765 else
766 merge_interval_left (i);
767 return Qt;
768 }
769
770 len -= LENGTH (i);
771 if (NULL_INTERVAL_P (prev_changed))
772 {
773 set_properties (props, i, object);
774 prev_changed = i;
775 }
776 else
777 prev_changed = i = merge_interval_left (i);
778
779 i = next_interval (i);
780 }
781
782 return Qt;
783 }
784
785 DEFUN ("remove-text-properties", Fremove_text_properties,
786 Sremove_text_properties, 3, 4, 0,
787 "Remove some properties from text from START to END.\n\
788 The third argument PROPS is a property list\n\
789 whose property names specify the properties to remove.\n\
790 \(The values stored in PROPS are ignored.)\n\
791 The optional fourth argument, OBJECT,\n\
792 is the string or buffer containing the text.\n\
793 Return t if any property was actually removed, nil otherwise.")
794 (start, end, props, object)
795 Lisp_Object start, end, props, object;
796 {
797 register INTERVAL i, unchanged;
798 register int s, len, modified = 0;
799
800 if (NILP (object))
801 XSET (object, Lisp_Buffer, current_buffer);
802
803 i = validate_interval_range (object, &start, &end, soft);
804 if (NULL_INTERVAL_P (i))
805 return Qnil;
806
807 s = XINT (start);
808 len = XINT (end) - s;
809
810 if (i->position != s)
811 {
812 /* No properties on this first interval -- return if
813 it covers the entire region. */
814 if (! interval_has_some_properties (props, i))
815 {
816 int got = (LENGTH (i) - (s - i->position));
817 if (got >= len)
818 return Qnil;
819 len -= got;
820 }
821 /* Split away the beginning of this interval; what we don't
822 want to modify. */
823 else
824 {
825 unchanged = i;
826 i = split_interval_right (unchanged, s - unchanged->position + 1);
827 copy_properties (unchanged, i);
828 }
829 }
830
831 /* We are at the beginning of an interval, with len to scan */
832 for (;;)
833 {
834 if (i == 0)
835 abort ();
836
837 if (LENGTH (i) >= len)
838 {
839 if (! interval_has_some_properties (props, i))
840 return modified ? Qt : Qnil;
841
842 if (LENGTH (i) == len)
843 {
844 remove_properties (props, i, object);
845 return Qt;
846 }
847
848 /* i has the properties, and goes past the change limit */
849 unchanged = i;
850 i = split_interval_left (i, len + 1);
851 copy_properties (unchanged, i);
852 remove_properties (props, i, object);
853 return Qt;
854 }
855
856 len -= LENGTH (i);
857 modified += remove_properties (props, i, object);
858 i = next_interval (i);
859 }
860 }
861
862 #if 0 /* You can use set-text-properties for this. */
863
864 DEFUN ("erase-text-properties", Ferase_text_properties,
865 Serase_text_properties, 2, 3, 0,
866 "Remove all properties from the text from START to END.\n\
867 The optional third argument, OBJECT,\n\
868 is the string or buffer containing the text.")
869 (start, end, object)
870 Lisp_Object start, end, object;
871 {
872 register INTERVAL i;
873 register INTERVAL prev_changed = NULL_INTERVAL;
874 register int s, len, modified;
875
876 if (NILP (object))
877 XSET (object, Lisp_Buffer, current_buffer);
878
879 i = validate_interval_range (object, &start, &end, soft);
880 if (NULL_INTERVAL_P (i))
881 return Qnil;
882
883 s = XINT (start);
884 len = XINT (end) - s;
885
886 if (i->position != s)
887 {
888 register int got;
889 register INTERVAL unchanged = i;
890
891 /* If there are properties here, then this text will be modified. */
892 if (! NILP (i->plist))
893 {
894 i = split_interval_right (unchanged, s - unchanged->position + 1);
895 i->plist = Qnil;
896 modified++;
897
898 if (LENGTH (i) > len)
899 {
900 i = split_interval_right (i, len + 1);
901 copy_properties (unchanged, i);
902 return Qt;
903 }
904
905 if (LENGTH (i) == len)
906 return Qt;
907
908 got = LENGTH (i);
909 }
910 /* If the text of I is without any properties, and contains
911 LEN or more characters, then we may return without changing
912 anything.*/
913 else if (LENGTH (i) - (s - i->position) <= len)
914 return Qnil;
915 /* The amount of text to change extends past I, so just note
916 how much we've gotten. */
917 else
918 got = LENGTH (i) - (s - i->position);
919
920 len -= got;
921 prev_changed = i;
922 i = next_interval (i);
923 }
924
925 /* We are starting at the beginning of an interval, I. */
926 while (len > 0)
927 {
928 if (LENGTH (i) >= len)
929 {
930 /* If I has no properties, simply merge it if possible. */
931 if (NILP (i->plist))
932 {
933 if (! NULL_INTERVAL_P (prev_changed))
934 merge_interval_left (i);
935
936 return modified ? Qt : Qnil;
937 }
938
939 if (LENGTH (i) > len)
940 i = split_interval_left (i, len + 1);
941 if (! NULL_INTERVAL_P (prev_changed))
942 merge_interval_left (i);
943 else
944 i->plist = Qnil;
945
946 return Qt;
947 }
948
949 /* Here if we still need to erase past the end of I */
950 len -= LENGTH (i);
951 if (NULL_INTERVAL_P (prev_changed))
952 {
953 modified += erase_properties (i);
954 prev_changed = i;
955 }
956 else
957 {
958 modified += ! NILP (i->plist);
959 /* Merging I will give it the properties of PREV_CHANGED. */
960 prev_changed = i = merge_interval_left (i);
961 }
962
963 i = next_interval (i);
964 }
965
966 return modified ? Qt : Qnil;
967 }
968 #endif /* 0 */
969
970 void
971 syms_of_textprop ()
972 {
973 DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold,
974 "Threshold for rebalancing interval trees, expressed as the\n\
975 percentage by which the left interval tree should not differ from the right.");
976 interval_balance_threshold = 8;
977
978 /* Common attributes one might give text */
979
980 staticpro (&Qforeground);
981 Qforeground = intern ("foreground");
982 staticpro (&Qbackground);
983 Qbackground = intern ("background");
984 staticpro (&Qfont);
985 Qfont = intern ("font");
986 staticpro (&Qstipple);
987 Qstipple = intern ("stipple");
988 staticpro (&Qunderline);
989 Qunderline = intern ("underline");
990 staticpro (&Qread_only);
991 Qread_only = intern ("read-only");
992 staticpro (&Qinvisible);
993 Qinvisible = intern ("invisible");
994 staticpro (&Qcategory);
995 Qcategory = intern ("category");
996 staticpro (&Qlocal_map);
997 Qlocal_map = intern ("local-map");
998
999 /* Properties that text might use to specify certain actions */
1000
1001 staticpro (&Qmouse_left);
1002 Qmouse_left = intern ("mouse-left");
1003 staticpro (&Qmouse_entered);
1004 Qmouse_entered = intern ("mouse-entered");
1005 staticpro (&Qpoint_left);
1006 Qpoint_left = intern ("point-left");
1007 staticpro (&Qpoint_entered);
1008 Qpoint_entered = intern ("point-entered");
1009 staticpro (&Qmodification_hooks);
1010 Qmodification_hooks = intern ("modification-hooks");
1011
1012 defsubr (&Stext_properties_at);
1013 defsubr (&Sget_text_property);
1014 defsubr (&Snext_property_change);
1015 defsubr (&Snext_single_property_change);
1016 defsubr (&Sprevious_property_change);
1017 defsubr (&Sprevious_single_property_change);
1018 defsubr (&Sadd_text_properties);
1019 defsubr (&Sput_text_property);
1020 defsubr (&Sset_text_properties);
1021 defsubr (&Sremove_text_properties);
1022 /* defsubr (&Serase_text_properties); */
1023 }
1024
1025 #else
1026
1027 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1028
1029 #endif /* USE_TEXT_PROPERTIES */