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