]> code.delx.au - gnu-emacs/blob - src/textprop.c
Remove #definition of HAVE_CLOSEDIR; autoconf figures this out.
[gnu-emacs] / 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 2, 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 necessary 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
717 if (NILP (object))
718 XSET (object, Lisp_Buffer, current_buffer);
719
720 i = validate_interval_range (object, &start, &end, hard);
721 if (NULL_INTERVAL_P (i))
722 return Qnil;
723
724 s = XINT (start);
725 len = XINT (end) - s;
726
727 if (i->position != s)
728 {
729 unchanged = i;
730 i = split_interval_right (unchanged, s - unchanged->position + 1);
731
732 if (LENGTH (i) > len)
733 {
734 copy_properties (unchanged, i);
735 i = split_interval_left (i, len + 1);
736 set_properties (props, i, object);
737 return Qt;
738 }
739
740 set_properties (props, i, object);
741
742 if (LENGTH (i) == len)
743 return Qt;
744
745 prev_changed = i;
746 len -= LENGTH (i);
747 i = next_interval (i);
748 }
749
750 /* We are starting at the beginning of an interval, I */
751 while (len > 0)
752 {
753 if (i == 0)
754 abort ();
755
756 if (LENGTH (i) >= len)
757 {
758 if (LENGTH (i) > len)
759 i = split_interval_left (i, len + 1);
760
761 if (NULL_INTERVAL_P (prev_changed))
762 set_properties (props, i, object);
763 else
764 merge_interval_left (i);
765 return Qt;
766 }
767
768 len -= LENGTH (i);
769 if (NULL_INTERVAL_P (prev_changed))
770 {
771 set_properties (props, i, object);
772 prev_changed = i;
773 }
774 else
775 prev_changed = i = merge_interval_left (i);
776
777 i = next_interval (i);
778 }
779
780 return Qt;
781 }
782
783 DEFUN ("remove-text-properties", Fremove_text_properties,
784 Sremove_text_properties, 3, 4, 0,
785 "Remove some properties from text from START to END.\n\
786 The third argument PROPS is a property list\n\
787 whose property names specify the properties to remove.\n\
788 \(The values stored in PROPS are ignored.)\n\
789 The optional fourth argument, OBJECT,\n\
790 is the string or buffer containing the text.\n\
791 Return t if any property was actually removed, nil otherwise.")
792 (start, end, props, object)
793 Lisp_Object start, end, props, object;
794 {
795 register INTERVAL i, unchanged;
796 register int s, len, modified = 0;
797
798 if (NILP (object))
799 XSET (object, Lisp_Buffer, current_buffer);
800
801 i = validate_interval_range (object, &start, &end, soft);
802 if (NULL_INTERVAL_P (i))
803 return Qnil;
804
805 s = XINT (start);
806 len = XINT (end) - s;
807
808 if (i->position != s)
809 {
810 /* No properties on this first interval -- return if
811 it covers the entire region. */
812 if (! interval_has_some_properties (props, i))
813 {
814 int got = (LENGTH (i) - (s - i->position));
815 if (got >= len)
816 return Qnil;
817 len -= got;
818 }
819 /* Split away the beginning of this interval; what we don't
820 want to modify. */
821 else
822 {
823 unchanged = i;
824 i = split_interval_right (unchanged, s - unchanged->position + 1);
825 copy_properties (unchanged, i);
826 }
827 }
828
829 /* We are at the beginning of an interval, with len to scan */
830 for (;;)
831 {
832 if (i == 0)
833 abort ();
834
835 if (LENGTH (i) >= len)
836 {
837 if (! interval_has_some_properties (props, i))
838 return modified ? Qt : Qnil;
839
840 if (LENGTH (i) == len)
841 {
842 remove_properties (props, i, object);
843 return Qt;
844 }
845
846 /* i has the properties, and goes past the change limit */
847 unchanged = i;
848 i = split_interval_left (i, len + 1);
849 copy_properties (unchanged, i);
850 remove_properties (props, i, object);
851 return Qt;
852 }
853
854 len -= LENGTH (i);
855 modified += remove_properties (props, i, object);
856 i = next_interval (i);
857 }
858 }
859
860 #if 0 /* You can use set-text-properties for this. */
861
862 DEFUN ("erase-text-properties", Ferase_text_properties,
863 Serase_text_properties, 2, 3, 0,
864 "Remove all properties from the text from START to END.\n\
865 The optional third argument, OBJECT,\n\
866 is the string or buffer containing the text.")
867 (start, end, object)
868 Lisp_Object start, end, object;
869 {
870 register INTERVAL i;
871 register INTERVAL prev_changed = NULL_INTERVAL;
872 register int s, len, modified;
873
874 if (NILP (object))
875 XSET (object, Lisp_Buffer, current_buffer);
876
877 i = validate_interval_range (object, &start, &end, soft);
878 if (NULL_INTERVAL_P (i))
879 return Qnil;
880
881 s = XINT (start);
882 len = XINT (end) - s;
883
884 if (i->position != s)
885 {
886 register int got;
887 register INTERVAL unchanged = i;
888
889 /* If there are properties here, then this text will be modified. */
890 if (! NILP (i->plist))
891 {
892 i = split_interval_right (unchanged, s - unchanged->position + 1);
893 i->plist = Qnil;
894 modified++;
895
896 if (LENGTH (i) > len)
897 {
898 i = split_interval_right (i, len + 1);
899 copy_properties (unchanged, i);
900 return Qt;
901 }
902
903 if (LENGTH (i) == len)
904 return Qt;
905
906 got = LENGTH (i);
907 }
908 /* If the text of I is without any properties, and contains
909 LEN or more characters, then we may return without changing
910 anything.*/
911 else if (LENGTH (i) - (s - i->position) <= len)
912 return Qnil;
913 /* The amount of text to change extends past I, so just note
914 how much we've gotten. */
915 else
916 got = LENGTH (i) - (s - i->position);
917
918 len -= got;
919 prev_changed = i;
920 i = next_interval (i);
921 }
922
923 /* We are starting at the beginning of an interval, I. */
924 while (len > 0)
925 {
926 if (LENGTH (i) >= len)
927 {
928 /* If I has no properties, simply merge it if possible. */
929 if (NILP (i->plist))
930 {
931 if (! NULL_INTERVAL_P (prev_changed))
932 merge_interval_left (i);
933
934 return modified ? Qt : Qnil;
935 }
936
937 if (LENGTH (i) > len)
938 i = split_interval_left (i, len + 1);
939 if (! NULL_INTERVAL_P (prev_changed))
940 merge_interval_left (i);
941 else
942 i->plist = Qnil;
943
944 return Qt;
945 }
946
947 /* Here if we still need to erase past the end of I */
948 len -= LENGTH (i);
949 if (NULL_INTERVAL_P (prev_changed))
950 {
951 modified += erase_properties (i);
952 prev_changed = i;
953 }
954 else
955 {
956 modified += ! NILP (i->plist);
957 /* Merging I will give it the properties of PREV_CHANGED. */
958 prev_changed = i = merge_interval_left (i);
959 }
960
961 i = next_interval (i);
962 }
963
964 return modified ? Qt : Qnil;
965 }
966 #endif /* 0 */
967
968 void
969 syms_of_textprop ()
970 {
971 DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold,
972 "Threshold for rebalancing interval trees, expressed as the\n\
973 percentage by which the left interval tree should not differ from the right.");
974 interval_balance_threshold = 8;
975
976 /* Common attributes one might give text */
977
978 staticpro (&Qforeground);
979 Qforeground = intern ("foreground");
980 staticpro (&Qbackground);
981 Qbackground = intern ("background");
982 staticpro (&Qfont);
983 Qfont = intern ("font");
984 staticpro (&Qstipple);
985 Qstipple = intern ("stipple");
986 staticpro (&Qunderline);
987 Qunderline = intern ("underline");
988 staticpro (&Qread_only);
989 Qread_only = intern ("read-only");
990 staticpro (&Qinvisible);
991 Qinvisible = intern ("invisible");
992 staticpro (&Qcategory);
993 Qcategory = intern ("category");
994 staticpro (&Qlocal_map);
995 Qlocal_map = intern ("local-map");
996
997 /* Properties that text might use to specify certain actions */
998
999 staticpro (&Qmouse_left);
1000 Qmouse_left = intern ("mouse-left");
1001 staticpro (&Qmouse_entered);
1002 Qmouse_entered = intern ("mouse-entered");
1003 staticpro (&Qpoint_left);
1004 Qpoint_left = intern ("point-left");
1005 staticpro (&Qpoint_entered);
1006 Qpoint_entered = intern ("point-entered");
1007 staticpro (&Qmodification_hooks);
1008 Qmodification_hooks = intern ("modification-hooks");
1009
1010 defsubr (&Stext_properties_at);
1011 defsubr (&Sget_text_property);
1012 defsubr (&Snext_property_change);
1013 defsubr (&Snext_single_property_change);
1014 defsubr (&Sprevious_property_change);
1015 defsubr (&Sprevious_single_property_change);
1016 defsubr (&Sadd_text_properties);
1017 defsubr (&Sput_text_property);
1018 defsubr (&Sset_text_properties);
1019 defsubr (&Sremove_text_properties);
1020 /* defsubr (&Serase_text_properties); */
1021 }
1022
1023 #else
1024
1025 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1026
1027 #endif /* USE_TEXT_PROPERTIES */