]> code.delx.au - gnu-emacs/blob - src/textprop.c
Initial revision
[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 Qcategory;
47 Lisp_Object Qlocal_map;
48
49 /* Visual properties text (including strings) may have. */
50 Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple;
51 Lisp_Object Qinvisible, Qread_only;
52
53 /* If o1 is a cons whose cdr is a cons, return non-zero and set o2 to
54 the o1's cdr. Otherwise, return zero. This is handy for
55 traversing plists. */
56 #define PLIST_ELT_P(o1, o2) (CONSP (o1) && CONSP ((o2) = XCONS (o1)->cdr))
57
58 Lisp_Object Vinhibit_point_motion_hooks;
59
60 \f
61 /* Extract the interval at the position pointed to by BEGIN from
62 OBJECT, a string or buffer. Additionally, check that the positions
63 pointed to by BEGIN and END are within the bounds of OBJECT, and
64 reverse them if *BEGIN is greater than *END. The objects pointed
65 to by BEGIN and END may be integers or markers; if the latter, they
66 are coerced to integers.
67
68 When OBJECT is a string, we increment *BEGIN and *END
69 to make them origin-one.
70
71 Note that buffer points don't correspond to interval indices.
72 For example, point-max is 1 greater than the index of the last
73 character. This difference is handled in the caller, which uses
74 the validated points to determine a length, and operates on that.
75 Exceptions are Ftext_properties_at, Fnext_property_change, and
76 Fprevious_property_change which call this function with BEGIN == END.
77 Handle this case specially.
78
79 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
80 create an interval tree for OBJECT if one doesn't exist, provided
81 the object actually contains text. In the current design, if there
82 is no text, there can be no text properties. */
83
84 #define soft 0
85 #define hard 1
86
87 static INTERVAL
88 validate_interval_range (object, begin, end, force)
89 Lisp_Object object, *begin, *end;
90 int force;
91 {
92 register INTERVAL i;
93 int searchpos;
94
95 CHECK_STRING_OR_BUFFER (object, 0);
96 CHECK_NUMBER_COERCE_MARKER (*begin, 0);
97 CHECK_NUMBER_COERCE_MARKER (*end, 0);
98
99 /* If we are asked for a point, but from a subr which operates
100 on a range, then return nothing. */
101 if (*begin == *end && begin != end)
102 return NULL_INTERVAL;
103
104 if (XINT (*begin) > XINT (*end))
105 {
106 Lisp_Object n;
107 n = *begin;
108 *begin = *end;
109 *end = n;
110 }
111
112 if (XTYPE (object) == Lisp_Buffer)
113 {
114 register struct buffer *b = XBUFFER (object);
115
116 if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end)
117 && XINT (*end) <= BUF_ZV (b)))
118 args_out_of_range (*begin, *end);
119 i = b->intervals;
120
121 /* If there's no text, there are no properties. */
122 if (BUF_BEGV (b) == BUF_ZV (b))
123 return NULL_INTERVAL;
124
125 searchpos = XINT (*begin);
126 }
127 else
128 {
129 register struct Lisp_String *s = XSTRING (object);
130
131 if (! (0 <= XINT (*begin) && XINT (*begin) <= XINT (*end)
132 && XINT (*end) <= s->size))
133 args_out_of_range (*begin, *end);
134 /* User-level Positions in strings start with 0,
135 but the interval code always wants positions starting with 1. */
136 XFASTINT (*begin) += 1;
137 if (begin != end)
138 XFASTINT (*end) += 1;
139 i = s->intervals;
140
141 if (s->size == 0)
142 return NULL_INTERVAL;
143
144 searchpos = XINT (*begin);
145 }
146
147 if (NULL_INTERVAL_P (i))
148 return (force ? create_root_interval (object) : i);
149
150 return find_interval (i, searchpos);
151 }
152
153 /* Validate LIST as a property list. If LIST is not a list, then
154 make one consisting of (LIST nil). Otherwise, verify that LIST
155 is even numbered and thus suitable as a plist. */
156
157 static Lisp_Object
158 validate_plist (list)
159 {
160 if (NILP (list))
161 return Qnil;
162
163 if (CONSP (list))
164 {
165 register int i;
166 register Lisp_Object tail;
167 for (i = 0, tail = list; !NILP (tail); i++)
168 {
169 tail = Fcdr (tail);
170 QUIT;
171 }
172 if (i & 1)
173 error ("Odd length text property list");
174 return list;
175 }
176
177 return Fcons (list, Fcons (Qnil, Qnil));
178 }
179
180 /* Return nonzero if interval I has all the properties,
181 with the same values, of list PLIST. */
182
183 static int
184 interval_has_all_properties (plist, i)
185 Lisp_Object plist;
186 INTERVAL i;
187 {
188 register Lisp_Object tail1, tail2, sym1, sym2;
189 register int found;
190
191 /* Go through each element of PLIST. */
192 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
193 {
194 sym1 = Fcar (tail1);
195 found = 0;
196
197 /* Go through I's plist, looking for sym1 */
198 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
199 if (EQ (sym1, Fcar (tail2)))
200 {
201 /* Found the same property on both lists. If the
202 values are unequal, return zero. */
203 if (! EQ (Fcar (Fcdr (tail1)), Fcar (Fcdr (tail2))))
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 /* Changing the plists of individual intervals. */
243
244 /* Return the value of PROP in property-list PLIST, or Qunbound if it
245 has none. */
246 static int
247 property_value (plist, prop)
248 {
249 Lisp_Object value;
250
251 while (PLIST_ELT_P (plist, value))
252 if (EQ (XCONS (plist)->car, prop))
253 return XCONS (value)->car;
254 else
255 plist = XCONS (value)->cdr;
256
257 return Qunbound;
258 }
259
260 /* Set the properties of INTERVAL to PROPERTIES,
261 and record undo info for the previous values.
262 OBJECT is the string or buffer that INTERVAL belongs to. */
263
264 static void
265 set_properties (properties, interval, object)
266 Lisp_Object properties, object;
267 INTERVAL interval;
268 {
269 Lisp_Object sym, value;
270
271 if (BUFFERP (object))
272 {
273 /* For each property in the old plist which is missing from PROPERTIES,
274 or has a different value in PROPERTIES, make an undo record. */
275 for (sym = interval->plist;
276 PLIST_ELT_P (sym, value);
277 sym = XCONS (value)->cdr)
278 if (! EQ (property_value (properties, XCONS (sym)->car),
279 XCONS (value)->car))
280 {
281 modify_region (XBUFFER (object),
282 make_number (interval->position),
283 make_number (interval->position + LENGTH (interval)));
284 record_property_change (interval->position, LENGTH (interval),
285 XCONS (sym)->car, XCONS (value)->car,
286 object);
287 }
288
289 /* For each new property that has no value at all in the old plist,
290 make an undo record binding it to nil, so it will be removed. */
291 for (sym = properties;
292 PLIST_ELT_P (sym, value);
293 sym = XCONS (value)->cdr)
294 if (EQ (property_value (interval->plist, XCONS (sym)->car), Qunbound))
295 {
296 modify_region (XBUFFER (object),
297 make_number (interval->position),
298 make_number (interval->position + LENGTH (interval)));
299 record_property_change (interval->position, LENGTH (interval),
300 XCONS (sym)->car, Qnil,
301 object);
302 }
303 }
304
305 /* Store new properties. */
306 interval->plist = Fcopy_sequence (properties);
307 }
308
309 /* Add the properties of PLIST to the interval I, or set
310 the value of I's property to the value of the property on PLIST
311 if they are different.
312
313 OBJECT should be the string or buffer the interval is in.
314
315 Return nonzero if this changes I (i.e., if any members of PLIST
316 are actually added to I's plist) */
317
318 static int
319 add_properties (plist, i, object)
320 Lisp_Object plist;
321 INTERVAL i;
322 Lisp_Object object;
323 {
324 register Lisp_Object tail1, tail2, sym1, val1;
325 register int changed = 0;
326 register int found;
327
328 /* Go through each element of PLIST. */
329 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
330 {
331 sym1 = Fcar (tail1);
332 val1 = Fcar (Fcdr (tail1));
333 found = 0;
334
335 /* Go through I's plist, looking for sym1 */
336 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
337 if (EQ (sym1, Fcar (tail2)))
338 {
339 register Lisp_Object this_cdr = Fcdr (tail2);
340
341 /* Found the property. Now check its value. */
342 found = 1;
343
344 /* The properties have the same value on both lists.
345 Continue to the next property. */
346 if (EQ (val1, Fcar (this_cdr)))
347 break;
348
349 /* Record this change in the buffer, for undo purposes. */
350 if (XTYPE (object) == Lisp_Buffer)
351 {
352 modify_region (XBUFFER (object),
353 make_number (i->position),
354 make_number (i->position + LENGTH (i)));
355 record_property_change (i->position, LENGTH (i),
356 sym1, Fcar (this_cdr), object);
357 }
358
359 /* I's property has a different value -- change it */
360 Fsetcar (this_cdr, val1);
361 changed++;
362 break;
363 }
364
365 if (! found)
366 {
367 /* Record this change in the buffer, for undo purposes. */
368 if (XTYPE (object) == Lisp_Buffer)
369 {
370 modify_region (XBUFFER (object),
371 make_number (i->position),
372 make_number (i->position + LENGTH (i)));
373 record_property_change (i->position, LENGTH (i),
374 sym1, Qnil, object);
375 }
376 i->plist = Fcons (sym1, Fcons (val1, i->plist));
377 changed++;
378 }
379 }
380
381 return changed;
382 }
383
384 /* For any members of PLIST which are properties of I, remove them
385 from I's plist.
386 OBJECT is the string or buffer containing I. */
387
388 static int
389 remove_properties (plist, i, object)
390 Lisp_Object plist;
391 INTERVAL i;
392 Lisp_Object object;
393 {
394 register Lisp_Object tail1, tail2, sym;
395 register Lisp_Object current_plist = i->plist;
396 register int changed = 0;
397
398 /* Go through each element of plist. */
399 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
400 {
401 sym = Fcar (tail1);
402
403 /* First, remove the symbol if its at the head of the list */
404 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
405 {
406 if (XTYPE (object) == Lisp_Buffer)
407 {
408 modify_region (XBUFFER (object),
409 make_number (i->position),
410 make_number (i->position + LENGTH (i)));
411 record_property_change (i->position, LENGTH (i),
412 sym, Fcar (Fcdr (current_plist)),
413 object);
414 }
415
416 current_plist = Fcdr (Fcdr (current_plist));
417 changed++;
418 }
419
420 /* Go through i's plist, looking for sym */
421 tail2 = current_plist;
422 while (! NILP (tail2))
423 {
424 register Lisp_Object this = Fcdr (Fcdr (tail2));
425 if (EQ (sym, Fcar (this)))
426 {
427 if (XTYPE (object) == Lisp_Buffer)
428 {
429 modify_region (XBUFFER (object),
430 make_number (i->position),
431 make_number (i->position + LENGTH (i)));
432 record_property_change (i->position, LENGTH (i),
433 sym, Fcar (Fcdr (this)), object);
434 }
435
436 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
437 changed++;
438 }
439 tail2 = this;
440 }
441 }
442
443 if (changed)
444 i->plist = current_plist;
445 return changed;
446 }
447
448 #if 0
449 /* Remove all properties from interval I. Return non-zero
450 if this changes the interval. */
451
452 static INLINE int
453 erase_properties (i)
454 INTERVAL i;
455 {
456 if (NILP (i->plist))
457 return 0;
458
459 i->plist = Qnil;
460 return 1;
461 }
462 #endif
463 \f
464 DEFUN ("text-properties-at", Ftext_properties_at,
465 Stext_properties_at, 1, 2, 0,
466 "Return the list of properties held by the character at POSITION\n\
467 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
468 defaults to the current buffer.\n\
469 If POSITION is at the end of OBJECT, the value is nil.")
470 (pos, object)
471 Lisp_Object pos, object;
472 {
473 register INTERVAL i;
474
475 if (NILP (object))
476 XSET (object, Lisp_Buffer, current_buffer);
477
478 i = validate_interval_range (object, &pos, &pos, soft);
479 if (NULL_INTERVAL_P (i))
480 return Qnil;
481 /* If POS is at the end of the interval,
482 it means it's the end of OBJECT.
483 There are no properties at the very end,
484 since no character follows. */
485 if (XINT (pos) == LENGTH (i) + i->position)
486 return Qnil;
487
488 return i->plist;
489 }
490
491 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
492 "Return the value of position POS's property PROP, in OBJECT.\n\
493 OBJECT is optional and defaults to the current buffer.\n\
494 If POSITION is at the end of OBJECT, the value is nil.")
495 (pos, prop, object)
496 Lisp_Object pos, object;
497 register Lisp_Object prop;
498 {
499 register INTERVAL i;
500 register Lisp_Object tail;
501
502 if (NILP (object))
503 XSET (object, Lisp_Buffer, current_buffer);
504 i = validate_interval_range (object, &pos, &pos, soft);
505 if (NULL_INTERVAL_P (i))
506 return Qnil;
507
508 /* If POS is at the end of the interval,
509 it means it's the end of OBJECT.
510 There are no properties at the very end,
511 since no character follows. */
512 if (XINT (pos) == LENGTH (i) + i->position)
513 return Qnil;
514
515 return textget (i->plist, prop);
516 }
517
518 DEFUN ("next-property-change", Fnext_property_change,
519 Snext_property_change, 1, 2, 0,
520 "Return the position of next property change.\n\
521 Scans characters forward from POS in OBJECT till it finds\n\
522 a change in some text property, then returns the position of the change.\n\
523 The optional second argument OBJECT is the string or buffer to scan.\n\
524 Return nil if the property is constant all the way to the end of OBJECT.\n\
525 If the value is non-nil, it is a position greater than POS, never equal.")
526 (pos, object)
527 Lisp_Object pos, object;
528 {
529 register INTERVAL i, next;
530
531 if (NILP (object))
532 XSET (object, Lisp_Buffer, current_buffer);
533
534 i = validate_interval_range (object, &pos, &pos, soft);
535 if (NULL_INTERVAL_P (i))
536 return Qnil;
537
538 next = next_interval (i);
539 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
540 next = next_interval (next);
541
542 if (NULL_INTERVAL_P (next))
543 return Qnil;
544
545 return next->position - (XTYPE (object) == Lisp_String);
546 ;
547 }
548
549 DEFUN ("next-single-property-change", Fnext_single_property_change,
550 Snext_single_property_change, 1, 3, 0,
551 "Return the position of next property change for a specific property.\n\
552 Scans characters forward from POS till it finds\n\
553 a change in the PROP property, then returns the position of the change.\n\
554 The optional third argument OBJECT is the string or buffer to scan.\n\
555 Return nil if the property is constant all the way to the end of OBJECT.\n\
556 If the value is non-nil, it is a position greater than POS, never equal.")
557 (pos, prop, object)
558 Lisp_Object pos, prop, object;
559 {
560 register INTERVAL i, next;
561 register Lisp_Object here_val;
562
563 if (NILP (object))
564 XSET (object, Lisp_Buffer, current_buffer);
565
566 i = validate_interval_range (object, &pos, &pos, soft);
567 if (NULL_INTERVAL_P (i))
568 return Qnil;
569
570 here_val = textget (i->plist, prop);
571 next = next_interval (i);
572 while (! NULL_INTERVAL_P (next)
573 && EQ (here_val, textget (next->plist, prop)))
574 next = next_interval (next);
575
576 if (NULL_INTERVAL_P (next))
577 return Qnil;
578
579 return next->position - (XTYPE (object) == Lisp_String);
580 }
581
582 DEFUN ("previous-property-change", Fprevious_property_change,
583 Sprevious_property_change, 1, 2, 0,
584 "Return the position of previous property change.\n\
585 Scans characters backwards from POS in OBJECT till it finds\n\
586 a change in some text property, then returns the position of the change.\n\
587 The optional second argument OBJECT is the string or buffer to scan.\n\
588 Return nil if the property is constant all the way to the start of OBJECT.\n\
589 If the value is non-nil, it is a position less than POS, never equal.")
590 (pos, object)
591 Lisp_Object pos, object;
592 {
593 register INTERVAL i, previous;
594
595 if (NILP (object))
596 XSET (object, Lisp_Buffer, current_buffer);
597
598 i = validate_interval_range (object, &pos, &pos, soft);
599 if (NULL_INTERVAL_P (i))
600 return Qnil;
601
602 previous = previous_interval (i);
603 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i))
604 previous = previous_interval (previous);
605 if (NULL_INTERVAL_P (previous))
606 return Qnil;
607
608 return (previous->position + LENGTH (previous) - 1
609 - (XTYPE (object) == Lisp_String));
610 }
611
612 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
613 Sprevious_single_property_change, 2, 3, 0,
614 "Return the position of previous property change for a specific property.\n\
615 Scans characters backward from POS till it finds\n\
616 a change in the PROP property, then returns the position of the change.\n\
617 The optional third argument OBJECT is the string or buffer to scan.\n\
618 Return nil if the property is constant all the way to the start of OBJECT.\n\
619 If the value is non-nil, it is a position less than POS, never equal.")
620 (pos, prop, object)
621 Lisp_Object pos, prop, object;
622 {
623 register INTERVAL i, previous;
624 register Lisp_Object here_val;
625
626 if (NILP (object))
627 XSET (object, Lisp_Buffer, current_buffer);
628
629 i = validate_interval_range (object, &pos, &pos, soft);
630 if (NULL_INTERVAL_P (i))
631 return Qnil;
632
633 here_val = textget (i->plist, prop);
634 previous = previous_interval (i);
635 while (! NULL_INTERVAL_P (previous)
636 && EQ (here_val, textget (previous->plist, prop)))
637 previous = previous_interval (previous);
638 if (NULL_INTERVAL_P (previous))
639 return Qnil;
640
641 return (previous->position + LENGTH (previous) - 1
642 - (XTYPE (object) == Lisp_String));
643 }
644
645 DEFUN ("add-text-properties", Fadd_text_properties,
646 Sadd_text_properties, 3, 4, 0,
647 "Add properties to the text from START to END.\n\
648 The third argument PROPS is a property list\n\
649 specifying the property values to add.\n\
650 The optional fourth argument, OBJECT,\n\
651 is the string or buffer containing the text.\n\
652 Return t if any property value actually changed, nil otherwise.")
653 (start, end, properties, object)
654 Lisp_Object start, end, properties, object;
655 {
656 register INTERVAL i, unchanged;
657 register int s, len, modified = 0;
658
659 properties = validate_plist (properties);
660 if (NILP (properties))
661 return Qnil;
662
663 if (NILP (object))
664 XSET (object, Lisp_Buffer, current_buffer);
665
666 i = validate_interval_range (object, &start, &end, hard);
667 if (NULL_INTERVAL_P (i))
668 return Qnil;
669
670 s = XINT (start);
671 len = XINT (end) - s;
672
673 /* If we're not starting on an interval boundary, we have to
674 split this interval. */
675 if (i->position != s)
676 {
677 /* If this interval already has the properties, we can
678 skip it. */
679 if (interval_has_all_properties (properties, i))
680 {
681 int got = (LENGTH (i) - (s - i->position));
682 if (got >= len)
683 return Qnil;
684 len -= got;
685 i = next_interval (i);
686 }
687 else
688 {
689 unchanged = i;
690 i = split_interval_right (unchanged, s - unchanged->position);
691 copy_properties (unchanged, i);
692 }
693 }
694
695 /* We are at the beginning of interval I, with LEN chars to scan. */
696 for (;;)
697 {
698 if (i == 0)
699 abort ();
700
701 if (LENGTH (i) >= len)
702 {
703 if (interval_has_all_properties (properties, i))
704 return modified ? Qt : Qnil;
705
706 if (LENGTH (i) == len)
707 {
708 add_properties (properties, i, object);
709 return Qt;
710 }
711
712 /* i doesn't have the properties, and goes past the change limit */
713 unchanged = i;
714 i = split_interval_left (unchanged, len);
715 copy_properties (unchanged, i);
716 add_properties (properties, i, object);
717 return Qt;
718 }
719
720 len -= LENGTH (i);
721 modified += add_properties (properties, i, object);
722 i = next_interval (i);
723 }
724 }
725
726 DEFUN ("put-text-property", Fput_text_property,
727 Sput_text_property, 4, 5, 0,
728 "Set one property of the text from START to END.\n\
729 The third and fourth arguments PROP and VALUE\n\
730 specify the property to add.\n\
731 The optional fifth argument, OBJECT,\n\
732 is the string or buffer containing the text.")
733 (start, end, prop, value, object)
734 Lisp_Object start, end, prop, value, object;
735 {
736 Fadd_text_properties (start, end,
737 Fcons (prop, Fcons (value, Qnil)),
738 object);
739 return Qnil;
740 }
741
742 DEFUN ("set-text-properties", Fset_text_properties,
743 Sset_text_properties, 3, 4, 0,
744 "Completely replace properties of text from START to END.\n\
745 The third argument PROPS is the new property list.\n\
746 The optional fourth argument, OBJECT,\n\
747 is the string or buffer containing the text.")
748 (start, end, props, object)
749 Lisp_Object start, end, props, object;
750 {
751 register INTERVAL i, unchanged;
752 register INTERVAL prev_changed = NULL_INTERVAL;
753 register int s, len;
754
755 props = validate_plist (props);
756
757 if (NILP (object))
758 XSET (object, Lisp_Buffer, current_buffer);
759
760 i = validate_interval_range (object, &start, &end, hard);
761 if (NULL_INTERVAL_P (i))
762 return Qnil;
763
764 s = XINT (start);
765 len = XINT (end) - s;
766
767 if (i->position != s)
768 {
769 unchanged = i;
770 i = split_interval_right (unchanged, s - unchanged->position);
771
772 if (LENGTH (i) > len)
773 {
774 copy_properties (unchanged, i);
775 i = split_interval_left (i, len);
776 set_properties (props, i, object);
777 return Qt;
778 }
779
780 set_properties (props, i, object);
781
782 if (LENGTH (i) == len)
783 return Qt;
784
785 prev_changed = i;
786 len -= LENGTH (i);
787 i = next_interval (i);
788 }
789
790 /* We are starting at the beginning of an interval, I */
791 while (len > 0)
792 {
793 if (i == 0)
794 abort ();
795
796 if (LENGTH (i) >= len)
797 {
798 if (LENGTH (i) > len)
799 i = split_interval_left (i, len);
800
801 if (NULL_INTERVAL_P (prev_changed))
802 set_properties (props, i, object);
803 else
804 merge_interval_left (i);
805 return Qt;
806 }
807
808 len -= LENGTH (i);
809 if (NULL_INTERVAL_P (prev_changed))
810 {
811 set_properties (props, i, object);
812 prev_changed = i;
813 }
814 else
815 prev_changed = i = merge_interval_left (i);
816
817 i = next_interval (i);
818 }
819
820 return Qt;
821 }
822
823 DEFUN ("remove-text-properties", Fremove_text_properties,
824 Sremove_text_properties, 3, 4, 0,
825 "Remove some properties from text from START to END.\n\
826 The third argument PROPS is a property list\n\
827 whose property names specify the properties to remove.\n\
828 \(The values stored in PROPS are ignored.)\n\
829 The optional fourth argument, OBJECT,\n\
830 is the string or buffer containing the text.\n\
831 Return t if any property was actually removed, nil otherwise.")
832 (start, end, props, object)
833 Lisp_Object start, end, props, object;
834 {
835 register INTERVAL i, unchanged;
836 register int s, len, modified = 0;
837
838 if (NILP (object))
839 XSET (object, Lisp_Buffer, current_buffer);
840
841 i = validate_interval_range (object, &start, &end, soft);
842 if (NULL_INTERVAL_P (i))
843 return Qnil;
844
845 s = XINT (start);
846 len = XINT (end) - s;
847
848 if (i->position != s)
849 {
850 /* No properties on this first interval -- return if
851 it covers the entire region. */
852 if (! interval_has_some_properties (props, i))
853 {
854 int got = (LENGTH (i) - (s - i->position));
855 if (got >= len)
856 return Qnil;
857 len -= got;
858 i = next_interval (i);
859 }
860 /* Split away the beginning of this interval; what we don't
861 want to modify. */
862 else
863 {
864 unchanged = i;
865 i = split_interval_right (unchanged, s - unchanged->position);
866 copy_properties (unchanged, i);
867 }
868 }
869
870 /* We are at the beginning of an interval, with len to scan */
871 for (;;)
872 {
873 if (i == 0)
874 abort ();
875
876 if (LENGTH (i) >= len)
877 {
878 if (! interval_has_some_properties (props, i))
879 return modified ? Qt : Qnil;
880
881 if (LENGTH (i) == len)
882 {
883 remove_properties (props, i, object);
884 return Qt;
885 }
886
887 /* i has the properties, and goes past the change limit */
888 unchanged = i;
889 i = split_interval_left (i, len);
890 copy_properties (unchanged, i);
891 remove_properties (props, i, object);
892 return Qt;
893 }
894
895 len -= LENGTH (i);
896 modified += remove_properties (props, i, object);
897 i = next_interval (i);
898 }
899 }
900
901 DEFUN ("text-property-any", Ftext_property_any,
902 Stext_property_any, 4, 5, 0,
903 "Check text from START to END to see if PROP is ever `eq' to VALUE.\n\
904 If so, return the position of the first character whose PROP is `eq'\n\
905 to VALUE. Otherwise return nil.\n\
906 The optional fifth argument, OBJECT, is the string or buffer\n\
907 containing the text.")
908 (start, end, prop, value, object)
909 Lisp_Object start, end, prop, value, object;
910 {
911 register INTERVAL i;
912 register int e, pos;
913
914 if (NILP (object))
915 XSET (object, Lisp_Buffer, current_buffer);
916 i = validate_interval_range (object, &start, &end, soft);
917 e = XINT (end);
918
919 while (! NULL_INTERVAL_P (i))
920 {
921 if (i->position >= e)
922 break;
923 if (EQ (textget (i->plist, prop), value))
924 {
925 pos = i->position;
926 if (pos < XINT (start))
927 pos = XINT (start);
928 return make_number (pos - (XTYPE (object) == Lisp_String));
929 }
930 i = next_interval (i);
931 }
932 return Qnil;
933 }
934
935 DEFUN ("text-property-not-all", Ftext_property_not_all,
936 Stext_property_not_all, 4, 5, 0,
937 "Check text from START to END to see if PROP is ever not `eq' to VALUE.\n\
938 If so, return the position of the first character whose PROP is not\n\
939 `eq' to VALUE. Otherwise, return nil.\n\
940 The optional fifth argument, OBJECT, is the string or buffer\n\
941 containing the text.")
942 (start, end, prop, value, object)
943 Lisp_Object start, end, prop, value, object;
944 {
945 register INTERVAL i;
946 register int s, e;
947
948 if (NILP (object))
949 XSET (object, Lisp_Buffer, current_buffer);
950 i = validate_interval_range (object, &start, &end, soft);
951 if (NULL_INTERVAL_P (i))
952 return (NILP (value) || EQ (start, end)) ? Qt : Qnil;
953 s = XINT (start);
954 e = XINT (end);
955
956 while (! NULL_INTERVAL_P (i))
957 {
958 if (i->position >= e)
959 break;
960 if (! EQ (textget (i->plist, prop), value))
961 {
962 if (i->position > s)
963 s = i->position;
964 return make_number (s - (XTYPE (object) == Lisp_String));
965 }
966 i = next_interval (i);
967 }
968 return Qnil;
969 }
970
971 #if 0 /* You can use set-text-properties for this. */
972
973 DEFUN ("erase-text-properties", Ferase_text_properties,
974 Serase_text_properties, 2, 3, 0,
975 "Remove all properties from the text from START to END.\n\
976 The optional third argument, OBJECT,\n\
977 is the string or buffer containing the text.")
978 (start, end, object)
979 Lisp_Object start, end, object;
980 {
981 register INTERVAL i;
982 register INTERVAL prev_changed = NULL_INTERVAL;
983 register int s, len, modified;
984
985 if (NILP (object))
986 XSET (object, Lisp_Buffer, current_buffer);
987
988 i = validate_interval_range (object, &start, &end, soft);
989 if (NULL_INTERVAL_P (i))
990 return Qnil;
991
992 s = XINT (start);
993 len = XINT (end) - s;
994
995 if (i->position != s)
996 {
997 register int got;
998 register INTERVAL unchanged = i;
999
1000 /* If there are properties here, then this text will be modified. */
1001 if (! NILP (i->plist))
1002 {
1003 i = split_interval_right (unchanged, s - unchanged->position);
1004 i->plist = Qnil;
1005 modified++;
1006
1007 if (LENGTH (i) > len)
1008 {
1009 i = split_interval_right (i, len);
1010 copy_properties (unchanged, i);
1011 return Qt;
1012 }
1013
1014 if (LENGTH (i) == len)
1015 return Qt;
1016
1017 got = LENGTH (i);
1018 }
1019 /* If the text of I is without any properties, and contains
1020 LEN or more characters, then we may return without changing
1021 anything.*/
1022 else if (LENGTH (i) - (s - i->position) <= len)
1023 return Qnil;
1024 /* The amount of text to change extends past I, so just note
1025 how much we've gotten. */
1026 else
1027 got = LENGTH (i) - (s - i->position);
1028
1029 len -= got;
1030 prev_changed = i;
1031 i = next_interval (i);
1032 }
1033
1034 /* We are starting at the beginning of an interval, I. */
1035 while (len > 0)
1036 {
1037 if (LENGTH (i) >= len)
1038 {
1039 /* If I has no properties, simply merge it if possible. */
1040 if (NILP (i->plist))
1041 {
1042 if (! NULL_INTERVAL_P (prev_changed))
1043 merge_interval_left (i);
1044
1045 return modified ? Qt : Qnil;
1046 }
1047
1048 if (LENGTH (i) > len)
1049 i = split_interval_left (i, len);
1050 if (! NULL_INTERVAL_P (prev_changed))
1051 merge_interval_left (i);
1052 else
1053 i->plist = Qnil;
1054
1055 return Qt;
1056 }
1057
1058 /* Here if we still need to erase past the end of I */
1059 len -= LENGTH (i);
1060 if (NULL_INTERVAL_P (prev_changed))
1061 {
1062 modified += erase_properties (i);
1063 prev_changed = i;
1064 }
1065 else
1066 {
1067 modified += ! NILP (i->plist);
1068 /* Merging I will give it the properties of PREV_CHANGED. */
1069 prev_changed = i = merge_interval_left (i);
1070 }
1071
1072 i = next_interval (i);
1073 }
1074
1075 return modified ? Qt : Qnil;
1076 }
1077 #endif /* 0 */
1078
1079 /* I don't think this is the right interface to export; how often do you
1080 want to do something like this, other than when you're copying objects
1081 around?
1082
1083 I think it would be better to have a pair of functions, one which
1084 returns the text properties of a region as a list of ranges and
1085 plists, and another which applies such a list to another object. */
1086
1087 /* DEFUN ("copy-text-properties", Fcopy_text_properties,
1088 Scopy_text_properties, 5, 6, 0,
1089 "Add properties from SRC-START to SRC-END of SRC at DEST-POS of DEST.\n\
1090 SRC and DEST may each refer to strings or buffers.\n\
1091 Optional sixth argument PROP causes only that property to be copied.\n\
1092 Properties are copied to DEST as if by `add-text-properties'.\n\
1093 Return t if any property value actually changed, nil otherwise.") */
1094
1095 Lisp_Object
1096 copy_text_properties (start, end, src, pos, dest, prop)
1097 Lisp_Object start, end, src, pos, dest, prop;
1098 {
1099 INTERVAL i;
1100 Lisp_Object res;
1101 Lisp_Object stuff;
1102 Lisp_Object plist;
1103 int s, e, e2, p, len, modified = 0;
1104
1105 i = validate_interval_range (src, &start, &end, soft);
1106 if (NULL_INTERVAL_P (i))
1107 return Qnil;
1108
1109 CHECK_NUMBER_COERCE_MARKER (pos, 0);
1110 {
1111 Lisp_Object dest_start, dest_end;
1112
1113 dest_start = pos;
1114 XFASTINT (dest_end) = XINT (dest_start) + (XINT (end) - XINT (start));
1115 /* Apply this to a copy of pos; it will try to increment its arguments,
1116 which we don't want. */
1117 validate_interval_range (dest, &dest_start, &dest_end, soft);
1118 }
1119
1120 s = XINT (start);
1121 e = XINT (end);
1122 p = XINT (pos);
1123
1124 stuff = Qnil;
1125
1126 while (s < e)
1127 {
1128 e2 = i->position + LENGTH (i);
1129 if (e2 > e)
1130 e2 = e;
1131 len = e2 - s;
1132
1133 plist = i->plist;
1134 if (! NILP (prop))
1135 while (! NILP (plist))
1136 {
1137 if (EQ (Fcar (plist), prop))
1138 {
1139 plist = Fcons (prop, Fcons (Fcar (Fcdr (plist)), Qnil));
1140 break;
1141 }
1142 plist = Fcdr (Fcdr (plist));
1143 }
1144 if (! NILP (plist))
1145 {
1146 /* Must defer modifications to the interval tree in case src
1147 and dest refer to the same string or buffer. */
1148 stuff = Fcons (Fcons (make_number (p),
1149 Fcons (make_number (p + len),
1150 Fcons (plist, Qnil))),
1151 stuff);
1152 }
1153
1154 i = next_interval (i);
1155 if (NULL_INTERVAL_P (i))
1156 break;
1157
1158 p += len;
1159 s = i->position;
1160 }
1161
1162 while (! NILP (stuff))
1163 {
1164 res = Fcar (stuff);
1165 res = Fadd_text_properties (Fcar (res), Fcar (Fcdr (res)),
1166 Fcar (Fcdr (Fcdr (res))), dest);
1167 if (! NILP (res))
1168 modified++;
1169 stuff = Fcdr (stuff);
1170 }
1171
1172 return modified ? Qt : Qnil;
1173 }
1174
1175 void
1176 syms_of_textprop ()
1177 {
1178 DEFVAR_INT ("interval-balance-threshold", &interval_balance_threshold,
1179 "Threshold for rebalancing interval trees, expressed as the\n\
1180 percentage by which the left interval tree should not differ from the right.");
1181 interval_balance_threshold = 8;
1182
1183 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks,
1184 "If nonnil, don't call the text property values of\n\
1185 `point-left' and `point-entered'.");
1186 Vinhibit_point_motion_hooks = Qnil;
1187
1188 /* Common attributes one might give text */
1189
1190 staticpro (&Qforeground);
1191 Qforeground = intern ("foreground");
1192 staticpro (&Qbackground);
1193 Qbackground = intern ("background");
1194 staticpro (&Qfont);
1195 Qfont = intern ("font");
1196 staticpro (&Qstipple);
1197 Qstipple = intern ("stipple");
1198 staticpro (&Qunderline);
1199 Qunderline = intern ("underline");
1200 staticpro (&Qread_only);
1201 Qread_only = intern ("read-only");
1202 staticpro (&Qinvisible);
1203 Qinvisible = intern ("invisible");
1204 staticpro (&Qcategory);
1205 Qcategory = intern ("category");
1206 staticpro (&Qlocal_map);
1207 Qlocal_map = intern ("local-map");
1208
1209 /* Properties that text might use to specify certain actions */
1210
1211 staticpro (&Qmouse_left);
1212 Qmouse_left = intern ("mouse-left");
1213 staticpro (&Qmouse_entered);
1214 Qmouse_entered = intern ("mouse-entered");
1215 staticpro (&Qpoint_left);
1216 Qpoint_left = intern ("point-left");
1217 staticpro (&Qpoint_entered);
1218 Qpoint_entered = intern ("point-entered");
1219
1220 defsubr (&Stext_properties_at);
1221 defsubr (&Sget_text_property);
1222 defsubr (&Snext_property_change);
1223 defsubr (&Snext_single_property_change);
1224 defsubr (&Sprevious_property_change);
1225 defsubr (&Sprevious_single_property_change);
1226 defsubr (&Sadd_text_properties);
1227 defsubr (&Sput_text_property);
1228 defsubr (&Sset_text_properties);
1229 defsubr (&Sremove_text_properties);
1230 defsubr (&Stext_property_any);
1231 defsubr (&Stext_property_not_all);
1232 /* defsubr (&Serase_text_properties); */
1233 /* defsubr (&Scopy_text_properties); */
1234 }
1235
1236 #else
1237
1238 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1239
1240 #endif /* USE_TEXT_PROPERTIES */