]> code.delx.au - gnu-emacs/blob - src/textprop.c
(mallocobj): Don't add gmalloc if using glibc.
[gnu-emacs] / src / textprop.c
1 /* Interface code for dealing with text properties.
2 Copyright (C) 1993, 1994, 1995 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, Inc., 59 Temple Place - Suite 330,
19 Boston, MA 02111-1307, USA. */
20
21 #include <config.h>
22 #include "lisp.h"
23 #include "intervals.h"
24 #include "buffer.h"
25 #include "window.h"
26
27 #ifndef NULL
28 #define NULL (void *)0
29 #endif
30
31 /* Test for membership, allowing for t (actually any non-cons) to mean the
32 universal set. */
33
34 #define TMEM(sym, set) (CONSP (set) ? ! NILP (Fmemq (sym, set)) : ! NILP (set))
35 \f
36
37 /* NOTES: previous- and next- property change will have to skip
38 zero-length intervals if they are implemented. This could be done
39 inside next_interval and previous_interval.
40
41 set_properties needs to deal with the interval property cache.
42
43 It is assumed that for any interval plist, a property appears
44 only once on the list. Although some code i.e., remove_properties,
45 handles the more general case, the uniqueness of properties is
46 necessary for the system to remain consistent. This requirement
47 is enforced by the subrs installing properties onto the intervals. */
48
49 /* The rest of the file is within this conditional */
50 #ifdef USE_TEXT_PROPERTIES
51 \f
52 /* Types of hooks. */
53 Lisp_Object Qmouse_left;
54 Lisp_Object Qmouse_entered;
55 Lisp_Object Qpoint_left;
56 Lisp_Object Qpoint_entered;
57 Lisp_Object Qcategory;
58 Lisp_Object Qlocal_map;
59
60 /* Visual properties text (including strings) may have. */
61 Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple;
62 Lisp_Object Qinvisible, Qread_only, Qintangible;
63
64 /* Sticky properties */
65 Lisp_Object Qfront_sticky, Qrear_nonsticky;
66
67 /* If o1 is a cons whose cdr is a cons, return non-zero and set o2 to
68 the o1's cdr. Otherwise, return zero. This is handy for
69 traversing plists. */
70 #define PLIST_ELT_P(o1, o2) (CONSP (o1) && ((o2)=XCONS (o1)->cdr, CONSP (o2)))
71
72 Lisp_Object Vinhibit_point_motion_hooks;
73 Lisp_Object Vdefault_text_properties;
74
75 /* verify_interval_modification saves insertion hooks here
76 to be run later by report_interval_modification. */
77 Lisp_Object interval_insert_behind_hooks;
78 Lisp_Object interval_insert_in_front_hooks;
79 \f
80 /* Extract the interval at the position pointed to by BEGIN from
81 OBJECT, a string or buffer. Additionally, check that the positions
82 pointed to by BEGIN and END are within the bounds of OBJECT, and
83 reverse them if *BEGIN is greater than *END. The objects pointed
84 to by BEGIN and END may be integers or markers; if the latter, they
85 are coerced to integers.
86
87 When OBJECT is a string, we increment *BEGIN and *END
88 to make them origin-one.
89
90 Note that buffer points don't correspond to interval indices.
91 For example, point-max is 1 greater than the index of the last
92 character. This difference is handled in the caller, which uses
93 the validated points to determine a length, and operates on that.
94 Exceptions are Ftext_properties_at, Fnext_property_change, and
95 Fprevious_property_change which call this function with BEGIN == END.
96 Handle this case specially.
97
98 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
99 create an interval tree for OBJECT if one doesn't exist, provided
100 the object actually contains text. In the current design, if there
101 is no text, there can be no text properties. */
102
103 #define soft 0
104 #define hard 1
105
106 static INTERVAL
107 validate_interval_range (object, begin, end, force)
108 Lisp_Object object, *begin, *end;
109 int force;
110 {
111 register INTERVAL i;
112 int searchpos;
113
114 CHECK_STRING_OR_BUFFER (object, 0);
115 CHECK_NUMBER_COERCE_MARKER (*begin, 0);
116 CHECK_NUMBER_COERCE_MARKER (*end, 0);
117
118 /* If we are asked for a point, but from a subr which operates
119 on a range, then return nothing. */
120 if (EQ (*begin, *end) && begin != end)
121 return NULL_INTERVAL;
122
123 if (XINT (*begin) > XINT (*end))
124 {
125 Lisp_Object n;
126 n = *begin;
127 *begin = *end;
128 *end = n;
129 }
130
131 if (BUFFERP (object))
132 {
133 register struct buffer *b = XBUFFER (object);
134
135 if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end)
136 && XINT (*end) <= BUF_ZV (b)))
137 args_out_of_range (*begin, *end);
138 i = BUF_INTERVALS (b);
139
140 /* If there's no text, there are no properties. */
141 if (BUF_BEGV (b) == BUF_ZV (b))
142 return NULL_INTERVAL;
143
144 searchpos = XINT (*begin);
145 }
146 else
147 {
148 register struct Lisp_String *s = XSTRING (object);
149
150 if (! (0 <= XINT (*begin) && XINT (*begin) <= XINT (*end)
151 && XINT (*end) <= s->size))
152 args_out_of_range (*begin, *end);
153 /* User-level Positions in strings start with 0,
154 but the interval code always wants positions starting with 1. */
155 XSETFASTINT (*begin, XFASTINT (*begin) + 1);
156 if (begin != end)
157 XSETFASTINT (*end, XFASTINT (*end) + 1);
158 i = s->intervals;
159
160 if (s->size == 0)
161 return NULL_INTERVAL;
162
163 searchpos = XINT (*begin);
164 }
165
166 if (NULL_INTERVAL_P (i))
167 return (force ? create_root_interval (object) : i);
168
169 return find_interval (i, searchpos);
170 }
171
172 /* Validate LIST as a property list. If LIST is not a list, then
173 make one consisting of (LIST nil). Otherwise, verify that LIST
174 is even numbered and thus suitable as a plist. */
175
176 static Lisp_Object
177 validate_plist (list)
178 Lisp_Object list;
179 {
180 if (NILP (list))
181 return Qnil;
182
183 if (CONSP (list))
184 {
185 register int i;
186 register Lisp_Object tail;
187 for (i = 0, tail = list; !NILP (tail); i++)
188 {
189 tail = Fcdr (tail);
190 QUIT;
191 }
192 if (i & 1)
193 error ("Odd length text property list");
194 return list;
195 }
196
197 return Fcons (list, Fcons (Qnil, Qnil));
198 }
199
200 /* Return nonzero if interval I has all the properties,
201 with the same values, of list PLIST. */
202
203 static int
204 interval_has_all_properties (plist, i)
205 Lisp_Object plist;
206 INTERVAL i;
207 {
208 register Lisp_Object tail1, tail2, sym1, sym2;
209 register int found;
210
211 /* Go through each element of PLIST. */
212 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
213 {
214 sym1 = Fcar (tail1);
215 found = 0;
216
217 /* Go through I's plist, looking for sym1 */
218 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
219 if (EQ (sym1, Fcar (tail2)))
220 {
221 /* Found the same property on both lists. If the
222 values are unequal, return zero. */
223 if (! EQ (Fcar (Fcdr (tail1)), Fcar (Fcdr (tail2))))
224 return 0;
225
226 /* Property has same value on both lists; go to next one. */
227 found = 1;
228 break;
229 }
230
231 if (! found)
232 return 0;
233 }
234
235 return 1;
236 }
237
238 /* Return nonzero if the plist of interval I has any of the
239 properties of PLIST, regardless of their values. */
240
241 static INLINE int
242 interval_has_some_properties (plist, i)
243 Lisp_Object plist;
244 INTERVAL i;
245 {
246 register Lisp_Object tail1, tail2, sym;
247
248 /* Go through each element of PLIST. */
249 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
250 {
251 sym = Fcar (tail1);
252
253 /* Go through i's plist, looking for tail1 */
254 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
255 if (EQ (sym, Fcar (tail2)))
256 return 1;
257 }
258
259 return 0;
260 }
261 \f
262 /* Changing the plists of individual intervals. */
263
264 /* Return the value of PROP in property-list PLIST, or Qunbound if it
265 has none. */
266 static Lisp_Object
267 property_value (plist, prop)
268 Lisp_Object plist, prop;
269 {
270 Lisp_Object value;
271
272 while (PLIST_ELT_P (plist, value))
273 if (EQ (XCONS (plist)->car, prop))
274 return XCONS (value)->car;
275 else
276 plist = XCONS (value)->cdr;
277
278 return Qunbound;
279 }
280
281 /* Set the properties of INTERVAL to PROPERTIES,
282 and record undo info for the previous values.
283 OBJECT is the string or buffer that INTERVAL belongs to. */
284
285 static void
286 set_properties (properties, interval, object)
287 Lisp_Object properties, object;
288 INTERVAL interval;
289 {
290 Lisp_Object sym, value;
291
292 if (BUFFERP (object))
293 {
294 /* For each property in the old plist which is missing from PROPERTIES,
295 or has a different value in PROPERTIES, make an undo record. */
296 for (sym = interval->plist;
297 PLIST_ELT_P (sym, value);
298 sym = XCONS (value)->cdr)
299 if (! EQ (property_value (properties, XCONS (sym)->car),
300 XCONS (value)->car))
301 {
302 record_property_change (interval->position, LENGTH (interval),
303 XCONS (sym)->car, XCONS (value)->car,
304 object);
305 }
306
307 /* For each new property that has no value at all in the old plist,
308 make an undo record binding it to nil, so it will be removed. */
309 for (sym = properties;
310 PLIST_ELT_P (sym, value);
311 sym = XCONS (value)->cdr)
312 if (EQ (property_value (interval->plist, XCONS (sym)->car), Qunbound))
313 {
314 record_property_change (interval->position, LENGTH (interval),
315 XCONS (sym)->car, Qnil,
316 object);
317 }
318 }
319
320 /* Store new properties. */
321 interval->plist = Fcopy_sequence (properties);
322 }
323
324 /* Add the properties of PLIST to the interval I, or set
325 the value of I's property to the value of the property on PLIST
326 if they are different.
327
328 OBJECT should be the string or buffer the interval is in.
329
330 Return nonzero if this changes I (i.e., if any members of PLIST
331 are actually added to I's plist) */
332
333 static int
334 add_properties (plist, i, object)
335 Lisp_Object plist;
336 INTERVAL i;
337 Lisp_Object object;
338 {
339 Lisp_Object tail1, tail2, sym1, val1;
340 register int changed = 0;
341 register int found;
342 struct gcpro gcpro1, gcpro2, gcpro3;
343
344 tail1 = plist;
345 sym1 = Qnil;
346 val1 = Qnil;
347 /* No need to protect OBJECT, because we can GC only in the case
348 where it is a buffer, and live buffers are always protected.
349 I and its plist are also protected, via OBJECT. */
350 GCPRO3 (tail1, sym1, val1);
351
352 /* Go through each element of PLIST. */
353 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
354 {
355 sym1 = Fcar (tail1);
356 val1 = Fcar (Fcdr (tail1));
357 found = 0;
358
359 /* Go through I's plist, looking for sym1 */
360 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
361 if (EQ (sym1, Fcar (tail2)))
362 {
363 /* No need to gcpro, because tail2 protects this
364 and it must be a cons cell (we get an error otherwise). */
365 register Lisp_Object this_cdr;
366
367 this_cdr = Fcdr (tail2);
368 /* Found the property. Now check its value. */
369 found = 1;
370
371 /* The properties have the same value on both lists.
372 Continue to the next property. */
373 if (EQ (val1, Fcar (this_cdr)))
374 break;
375
376 /* Record this change in the buffer, for undo purposes. */
377 if (BUFFERP (object))
378 {
379 record_property_change (i->position, LENGTH (i),
380 sym1, Fcar (this_cdr), object);
381 }
382
383 /* I's property has a different value -- change it */
384 Fsetcar (this_cdr, val1);
385 changed++;
386 break;
387 }
388
389 if (! found)
390 {
391 /* Record this change in the buffer, for undo purposes. */
392 if (BUFFERP (object))
393 {
394 record_property_change (i->position, LENGTH (i),
395 sym1, Qnil, object);
396 }
397 i->plist = Fcons (sym1, Fcons (val1, i->plist));
398 changed++;
399 }
400 }
401
402 UNGCPRO;
403
404 return changed;
405 }
406
407 /* For any members of PLIST which are properties of I, remove them
408 from I's plist.
409 OBJECT is the string or buffer containing I. */
410
411 static int
412 remove_properties (plist, i, object)
413 Lisp_Object plist;
414 INTERVAL i;
415 Lisp_Object object;
416 {
417 register Lisp_Object tail1, tail2, sym, current_plist;
418 register int changed = 0;
419
420 current_plist = i->plist;
421 /* Go through each element of plist. */
422 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
423 {
424 sym = Fcar (tail1);
425
426 /* First, remove the symbol if its at the head of the list */
427 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
428 {
429 if (BUFFERP (object))
430 {
431 record_property_change (i->position, LENGTH (i),
432 sym, Fcar (Fcdr (current_plist)),
433 object);
434 }
435
436 current_plist = Fcdr (Fcdr (current_plist));
437 changed++;
438 }
439
440 /* Go through i's plist, looking for sym */
441 tail2 = current_plist;
442 while (! NILP (tail2))
443 {
444 register Lisp_Object this;
445 this = Fcdr (Fcdr (tail2));
446 if (EQ (sym, Fcar (this)))
447 {
448 if (BUFFERP (object))
449 {
450 record_property_change (i->position, LENGTH (i),
451 sym, Fcar (Fcdr (this)), object);
452 }
453
454 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
455 changed++;
456 }
457 tail2 = this;
458 }
459 }
460
461 if (changed)
462 i->plist = current_plist;
463 return changed;
464 }
465
466 #if 0
467 /* Remove all properties from interval I. Return non-zero
468 if this changes the interval. */
469
470 static INLINE int
471 erase_properties (i)
472 INTERVAL i;
473 {
474 if (NILP (i->plist))
475 return 0;
476
477 i->plist = Qnil;
478 return 1;
479 }
480 #endif
481 \f
482 DEFUN ("text-properties-at", Ftext_properties_at,
483 Stext_properties_at, 1, 2, 0,
484 "Return the list of properties held by the character at POSITION\n\
485 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
486 defaults to the current buffer.\n\
487 If POSITION is at the end of OBJECT, the value is nil.")
488 (position, object)
489 Lisp_Object position, object;
490 {
491 register INTERVAL i;
492
493 if (NILP (object))
494 XSETBUFFER (object, current_buffer);
495
496 i = validate_interval_range (object, &position, &position, soft);
497 if (NULL_INTERVAL_P (i))
498 return Qnil;
499 /* If POSITION is at the end of the interval,
500 it means it's the end of OBJECT.
501 There are no properties at the very end,
502 since no character follows. */
503 if (XINT (position) == LENGTH (i) + i->position)
504 return Qnil;
505
506 return i->plist;
507 }
508
509 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
510 "Return the value of POSITION's property PROP, in OBJECT.\n\
511 OBJECT is optional and defaults to the current buffer.\n\
512 If POSITION is at the end of OBJECT, the value is nil.")
513 (position, prop, object)
514 Lisp_Object position, object;
515 Lisp_Object prop;
516 {
517 return textget (Ftext_properties_at (position, object), prop);
518 }
519
520 DEFUN ("get-char-property", Fget_char_property, Sget_char_property, 2, 3, 0,
521 "Return the value of POSITION's property PROP, in OBJECT.\n\
522 OBJECT is optional and defaults to the current buffer.\n\
523 If POSITION is at the end of OBJECT, the value is nil.\n\
524 If OBJECT is a buffer, then overlay properties are considered as well as\n\
525 text properties.\n\
526 If OBJECT is a window, then that window's buffer is used, but window-specific\n\
527 overlays are considered only if they are associated with OBJECT.")
528 (position, prop, object)
529 Lisp_Object position, object;
530 register Lisp_Object prop;
531 {
532 struct window *w = 0;
533
534 CHECK_NUMBER_COERCE_MARKER (position, 0);
535
536 if (NILP (object))
537 XSETBUFFER (object, current_buffer);
538
539 if (WINDOWP (object))
540 {
541 w = XWINDOW (object);
542 object = w->buffer;
543 }
544 if (BUFFERP (object))
545 {
546 int posn = XINT (position);
547 int noverlays;
548 Lisp_Object *overlay_vec, tem;
549 int next_overlay;
550 int len;
551 struct buffer *obuf = current_buffer;
552
553 set_buffer_temp (XBUFFER (object));
554
555 /* First try with room for 40 overlays. */
556 len = 40;
557 overlay_vec = (Lisp_Object *) alloca (len * sizeof (Lisp_Object));
558
559 noverlays = overlays_at (posn, 0, &overlay_vec, &len,
560 &next_overlay, NULL);
561
562 /* If there are more than 40,
563 make enough space for all, and try again. */
564 if (noverlays > len)
565 {
566 len = noverlays;
567 overlay_vec = (Lisp_Object *) alloca (len * sizeof (Lisp_Object));
568 noverlays = overlays_at (posn, 0, &overlay_vec, &len,
569 &next_overlay, NULL);
570 }
571 noverlays = sort_overlays (overlay_vec, noverlays, w);
572
573 set_buffer_temp (obuf);
574
575 /* Now check the overlays in order of decreasing priority. */
576 while (--noverlays >= 0)
577 {
578 tem = Foverlay_get (overlay_vec[noverlays], prop);
579 if (!NILP (tem))
580 return (tem);
581 }
582 }
583 /* Not a buffer, or no appropriate overlay, so fall through to the
584 simpler case. */
585 return (Fget_text_property (position, prop, object));
586 }
587 \f
588 DEFUN ("next-char-property-change", Fnext_char_property_change,
589 Snext_char_property_change, 1, 2, 0,
590 "Return the position of next text property or overlay change.\n\
591 This scans characters forward from POSITION in OBJECT till it finds\n\
592 a change in some text property, or the beginning or end of an overlay,\n\
593 and returns the position of that.\n\
594 If none is found, the function returns (point-max).\n\
595 \n\
596 If the optional third argument LIMIT is non-nil, don't search\n\
597 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
598 (position, limit)
599 Lisp_Object position, limit;
600 {
601 Lisp_Object temp;
602
603 temp = Fnext_overlay_change (position);
604 if (! NILP (limit))
605 {
606 CHECK_NUMBER (limit, 2);
607 if (XINT (limit) < XINT (temp))
608 temp = limit;
609 }
610 return Fnext_property_change (position, Qnil, temp);
611 }
612
613 DEFUN ("previous-char-property-change", Fprevious_char_property_change,
614 Sprevious_char_property_change, 1, 2, 0,
615 "Return the position of previous text property or overlay change.\n\
616 Scans characters backward from POSITION in OBJECT till it finds\n\
617 a change in some text property, or the beginning or end of an overlay,\n\
618 and returns the position of that.\n\
619 If none is found, the function returns (point-max).\n\
620 \n\
621 If the optional third argument LIMIT is non-nil, don't search\n\
622 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
623 (position, limit)
624 Lisp_Object position, limit;
625 {
626 Lisp_Object temp;
627
628 temp = Fprevious_overlay_change (position);
629 if (! NILP (limit))
630 {
631 CHECK_NUMBER (limit, 2);
632 if (XINT (limit) > XINT (temp))
633 temp = limit;
634 }
635 return Fprevious_property_change (position, Qnil, temp);
636 }
637 \f
638 DEFUN ("next-property-change", Fnext_property_change,
639 Snext_property_change, 1, 3, 0,
640 "Return the position of next property change.\n\
641 Scans characters forward from POSITION in OBJECT till it finds\n\
642 a change in some text property, then returns the position of the change.\n\
643 The optional second argument OBJECT is the string or buffer to scan.\n\
644 Return nil if the property is constant all the way to the end of OBJECT.\n\
645 If the value is non-nil, it is a position greater than POSITION, never equal.\n\n\
646 If the optional third argument LIMIT is non-nil, don't search\n\
647 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
648 (position, object, limit)
649 Lisp_Object position, object, limit;
650 {
651 register INTERVAL i, next;
652
653 if (NILP (object))
654 XSETBUFFER (object, current_buffer);
655
656 if (! NILP (limit) && ! EQ (limit, Qt))
657 CHECK_NUMBER_COERCE_MARKER (limit, 0);
658
659 i = validate_interval_range (object, &position, &position, soft);
660
661 /* If LIMIT is t, return start of next interval--don't
662 bother checking further intervals. */
663 if (EQ (limit, Qt))
664 {
665 if (NULL_INTERVAL_P (i))
666 next = i;
667 else
668 next = next_interval (i);
669
670 if (NULL_INTERVAL_P (next))
671 XSETFASTINT (position, (STRINGP (object)
672 ? XSTRING (object)->size
673 : BUF_ZV (XBUFFER (object))));
674 else
675 XSETFASTINT (position, next->position - (STRINGP (object)));
676 return position;
677 }
678
679 if (NULL_INTERVAL_P (i))
680 return limit;
681
682 next = next_interval (i);
683
684 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next)
685 && (NILP (limit) || next->position < XFASTINT (limit)))
686 next = next_interval (next);
687
688 if (NULL_INTERVAL_P (next))
689 return limit;
690 if (! NILP (limit) && !(next->position < XFASTINT (limit)))
691 return limit;
692
693 XSETFASTINT (position, next->position - (STRINGP (object)));
694 return position;
695 }
696
697 /* Return 1 if there's a change in some property between BEG and END. */
698
699 int
700 property_change_between_p (beg, end)
701 int beg, end;
702 {
703 register INTERVAL i, next;
704 Lisp_Object object, pos;
705
706 XSETBUFFER (object, current_buffer);
707 XSETFASTINT (pos, beg);
708
709 i = validate_interval_range (object, &pos, &pos, soft);
710 if (NULL_INTERVAL_P (i))
711 return 0;
712
713 next = next_interval (i);
714 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
715 {
716 next = next_interval (next);
717 if (NULL_INTERVAL_P (next))
718 return 0;
719 if (next->position >= end)
720 return 0;
721 }
722
723 if (NULL_INTERVAL_P (next))
724 return 0;
725
726 return 1;
727 }
728
729 DEFUN ("next-single-property-change", Fnext_single_property_change,
730 Snext_single_property_change, 2, 4, 0,
731 "Return the position of next property change for a specific property.\n\
732 Scans characters forward from POSITION till it finds\n\
733 a change in the PROP property, then returns the position of the change.\n\
734 The optional third argument OBJECT is the string or buffer to scan.\n\
735 The property values are compared with `eq'.\n\
736 Return nil if the property is constant all the way to the end of OBJECT.\n\
737 If the value is non-nil, it is a position greater than POSITION, never equal.\n\n\
738 If the optional fourth argument LIMIT is non-nil, don't search\n\
739 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
740 (position, prop, object, limit)
741 Lisp_Object position, prop, object, limit;
742 {
743 register INTERVAL i, next;
744 register Lisp_Object here_val;
745
746 if (NILP (object))
747 XSETBUFFER (object, current_buffer);
748
749 if (!NILP (limit))
750 CHECK_NUMBER_COERCE_MARKER (limit, 0);
751
752 i = validate_interval_range (object, &position, &position, soft);
753 if (NULL_INTERVAL_P (i))
754 return limit;
755
756 here_val = textget (i->plist, prop);
757 next = next_interval (i);
758 while (! NULL_INTERVAL_P (next)
759 && EQ (here_val, textget (next->plist, prop))
760 && (NILP (limit) || next->position < XFASTINT (limit)))
761 next = next_interval (next);
762
763 if (NULL_INTERVAL_P (next))
764 return limit;
765 if (! NILP (limit) && !(next->position < XFASTINT (limit)))
766 return limit;
767
768 XSETFASTINT (position, next->position - (STRINGP (object)));
769 return position;
770 }
771
772 DEFUN ("previous-property-change", Fprevious_property_change,
773 Sprevious_property_change, 1, 3, 0,
774 "Return the position of previous property change.\n\
775 Scans characters backwards from POSITION in OBJECT till it finds\n\
776 a change in some text property, then returns the position of the change.\n\
777 The optional second argument OBJECT is the string or buffer to scan.\n\
778 Return nil if the property is constant all the way to the start of OBJECT.\n\
779 If the value is non-nil, it is a position less than POSITION, never equal.\n\n\
780 If the optional third argument LIMIT is non-nil, don't search\n\
781 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
782 (position, object, limit)
783 Lisp_Object position, object, limit;
784 {
785 register INTERVAL i, previous;
786
787 if (NILP (object))
788 XSETBUFFER (object, current_buffer);
789
790 if (!NILP (limit))
791 CHECK_NUMBER_COERCE_MARKER (limit, 0);
792
793 i = validate_interval_range (object, &position, &position, soft);
794 if (NULL_INTERVAL_P (i))
795 return limit;
796
797 /* Start with the interval containing the char before point. */
798 if (i->position == XFASTINT (position))
799 i = previous_interval (i);
800
801 previous = previous_interval (i);
802 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i)
803 && (NILP (limit)
804 || previous->position + LENGTH (previous) > XFASTINT (limit)))
805 previous = previous_interval (previous);
806 if (NULL_INTERVAL_P (previous))
807 return limit;
808 if (!NILP (limit)
809 && !(previous->position + LENGTH (previous) > XFASTINT (limit)))
810 return limit;
811
812 XSETFASTINT (position, (previous->position + LENGTH (previous)
813 - (STRINGP (object))));
814 return position;
815 }
816
817 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
818 Sprevious_single_property_change, 2, 4, 0,
819 "Return the position of previous property change for a specific property.\n\
820 Scans characters backward from POSITION till it finds\n\
821 a change in the PROP property, then returns the position of the change.\n\
822 The optional third argument OBJECT is the string or buffer to scan.\n\
823 The property values are compared with `eq'.\n\
824 Return nil if the property is constant all the way to the start of OBJECT.\n\
825 If the value is non-nil, it is a position less than POSITION, never equal.\n\n\
826 If the optional fourth argument LIMIT is non-nil, don't search\n\
827 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
828 (position, prop, object, limit)
829 Lisp_Object position, prop, object, limit;
830 {
831 register INTERVAL i, previous;
832 register Lisp_Object here_val;
833
834 if (NILP (object))
835 XSETBUFFER (object, current_buffer);
836
837 if (!NILP (limit))
838 CHECK_NUMBER_COERCE_MARKER (limit, 0);
839
840 i = validate_interval_range (object, &position, &position, soft);
841
842 /* Start with the interval containing the char before point. */
843 if (! NULL_INTERVAL_P (i) && i->position == XFASTINT (position))
844 i = previous_interval (i);
845
846 if (NULL_INTERVAL_P (i))
847 return limit;
848
849 here_val = textget (i->plist, prop);
850 previous = previous_interval (i);
851 while (! NULL_INTERVAL_P (previous)
852 && EQ (here_val, textget (previous->plist, prop))
853 && (NILP (limit)
854 || previous->position + LENGTH (previous) > XFASTINT (limit)))
855 previous = previous_interval (previous);
856 if (NULL_INTERVAL_P (previous))
857 return limit;
858 if (!NILP (limit)
859 && !(previous->position + LENGTH (previous) > XFASTINT (limit)))
860 return limit;
861
862 XSETFASTINT (position, (previous->position + LENGTH (previous)
863 - (STRINGP (object))));
864 return position;
865 }
866 \f
867 /* Callers note, this can GC when OBJECT is a buffer (or nil). */
868
869 DEFUN ("add-text-properties", Fadd_text_properties,
870 Sadd_text_properties, 3, 4, 0,
871 "Add properties to the text from START to END.\n\
872 The third argument PROPERTIES is a property list\n\
873 specifying the property values to add.\n\
874 The optional fourth argument, OBJECT,\n\
875 is the string or buffer containing the text.\n\
876 Return t if any property value actually changed, nil otherwise.")
877 (start, end, properties, object)
878 Lisp_Object start, end, properties, object;
879 {
880 register INTERVAL i, unchanged;
881 register int s, len, modified = 0;
882 struct gcpro gcpro1;
883
884 properties = validate_plist (properties);
885 if (NILP (properties))
886 return Qnil;
887
888 if (NILP (object))
889 XSETBUFFER (object, current_buffer);
890
891 i = validate_interval_range (object, &start, &end, hard);
892 if (NULL_INTERVAL_P (i))
893 return Qnil;
894
895 s = XINT (start);
896 len = XINT (end) - s;
897
898 /* No need to protect OBJECT, because we GC only if it's a buffer,
899 and live buffers are always protected. */
900 GCPRO1 (properties);
901
902 /* If we're not starting on an interval boundary, we have to
903 split this interval. */
904 if (i->position != s)
905 {
906 /* If this interval already has the properties, we can
907 skip it. */
908 if (interval_has_all_properties (properties, i))
909 {
910 int got = (LENGTH (i) - (s - i->position));
911 if (got >= len)
912 RETURN_UNGCPRO (Qnil);
913 len -= got;
914 i = next_interval (i);
915 }
916 else
917 {
918 unchanged = i;
919 i = split_interval_right (unchanged, s - unchanged->position);
920 copy_properties (unchanged, i);
921 }
922 }
923
924 if (BUFFERP (object))
925 modify_region (XBUFFER (object), XINT (start), XINT (end));
926
927 /* We are at the beginning of interval I, with LEN chars to scan. */
928 for (;;)
929 {
930 if (i == 0)
931 abort ();
932
933 if (LENGTH (i) >= len)
934 {
935 /* We can UNGCPRO safely here, because there will be just
936 one more chance to gc, in the next call to add_properties,
937 and after that we will not need PROPERTIES or OBJECT again. */
938 UNGCPRO;
939
940 if (interval_has_all_properties (properties, i))
941 {
942 if (BUFFERP (object))
943 signal_after_change (XINT (start), XINT (end) - XINT (start),
944 XINT (end) - XINT (start));
945
946 return modified ? Qt : Qnil;
947 }
948
949 if (LENGTH (i) == len)
950 {
951 add_properties (properties, i, object);
952 if (BUFFERP (object))
953 signal_after_change (XINT (start), XINT (end) - XINT (start),
954 XINT (end) - XINT (start));
955 return Qt;
956 }
957
958 /* i doesn't have the properties, and goes past the change limit */
959 unchanged = i;
960 i = split_interval_left (unchanged, len);
961 copy_properties (unchanged, i);
962 add_properties (properties, i, object);
963 if (BUFFERP (object))
964 signal_after_change (XINT (start), XINT (end) - XINT (start),
965 XINT (end) - XINT (start));
966 return Qt;
967 }
968
969 len -= LENGTH (i);
970 modified += add_properties (properties, i, object);
971 i = next_interval (i);
972 }
973 }
974
975 /* Callers note, this can GC when OBJECT is a buffer (or nil). */
976
977 DEFUN ("put-text-property", Fput_text_property,
978 Sput_text_property, 4, 5, 0,
979 "Set one property of the text from START to END.\n\
980 The third and fourth arguments PROPERTY and VALUE\n\
981 specify the property to add.\n\
982 The optional fifth argument, OBJECT,\n\
983 is the string or buffer containing the text.")
984 (start, end, property, value, object)
985 Lisp_Object start, end, property, value, object;
986 {
987 Fadd_text_properties (start, end,
988 Fcons (property, Fcons (value, Qnil)),
989 object);
990 return Qnil;
991 }
992
993 DEFUN ("set-text-properties", Fset_text_properties,
994 Sset_text_properties, 3, 4, 0,
995 "Completely replace properties of text from START to END.\n\
996 The third argument PROPERTIES is the new property list.\n\
997 The optional fourth argument, OBJECT,\n\
998 is the string or buffer containing the text.")
999 (start, end, properties, object)
1000 Lisp_Object start, end, properties, object;
1001 {
1002 register INTERVAL i, unchanged;
1003 register INTERVAL prev_changed = NULL_INTERVAL;
1004 register int s, len;
1005 Lisp_Object ostart, oend;
1006 int have_modified = 0;
1007
1008 ostart = start;
1009 oend = end;
1010
1011 properties = validate_plist (properties);
1012
1013 if (NILP (object))
1014 XSETBUFFER (object, current_buffer);
1015
1016 /* If we want no properties for a whole string,
1017 get rid of its intervals. */
1018 if (NILP (properties) && STRINGP (object)
1019 && XFASTINT (start) == 0
1020 && XFASTINT (end) == XSTRING (object)->size)
1021 {
1022 if (! XSTRING (object)->intervals)
1023 return Qt;
1024
1025 XSTRING (object)->intervals = 0;
1026 return Qt;
1027 }
1028
1029 i = validate_interval_range (object, &start, &end, soft);
1030
1031 if (NULL_INTERVAL_P (i))
1032 {
1033 /* If buffer has no properties, and we want none, return now. */
1034 if (NILP (properties))
1035 return Qnil;
1036
1037 /* Restore the original START and END values
1038 because validate_interval_range increments them for strings. */
1039 start = ostart;
1040 end = oend;
1041
1042 i = validate_interval_range (object, &start, &end, hard);
1043 /* This can return if start == end. */
1044 if (NULL_INTERVAL_P (i))
1045 return Qnil;
1046 }
1047
1048 s = XINT (start);
1049 len = XINT (end) - s;
1050
1051 if (BUFFERP (object))
1052 modify_region (XBUFFER (object), XINT (start), XINT (end));
1053
1054 if (i->position != s)
1055 {
1056 unchanged = i;
1057 i = split_interval_right (unchanged, s - unchanged->position);
1058
1059 if (LENGTH (i) > len)
1060 {
1061 copy_properties (unchanged, i);
1062 i = split_interval_left (i, len);
1063 set_properties (properties, i, object);
1064 if (BUFFERP (object))
1065 signal_after_change (XINT (start), XINT (end) - XINT (start),
1066 XINT (end) - XINT (start));
1067
1068 return Qt;
1069 }
1070
1071 set_properties (properties, i, object);
1072
1073 if (LENGTH (i) == len)
1074 {
1075 if (BUFFERP (object))
1076 signal_after_change (XINT (start), XINT (end) - XINT (start),
1077 XINT (end) - XINT (start));
1078
1079 return Qt;
1080 }
1081
1082 prev_changed = i;
1083 len -= LENGTH (i);
1084 i = next_interval (i);
1085 }
1086
1087 /* We are starting at the beginning of an interval, I */
1088 while (len > 0)
1089 {
1090 if (i == 0)
1091 abort ();
1092
1093 if (LENGTH (i) >= len)
1094 {
1095 if (LENGTH (i) > len)
1096 i = split_interval_left (i, len);
1097
1098 /* We have to call set_properties even if we are going to
1099 merge the intervals, so as to make the undo records
1100 and cause redisplay to happen. */
1101 set_properties (properties, i, object);
1102 if (!NULL_INTERVAL_P (prev_changed))
1103 merge_interval_left (i);
1104 if (BUFFERP (object))
1105 signal_after_change (XINT (start), XINT (end) - XINT (start),
1106 XINT (end) - XINT (start));
1107 return Qt;
1108 }
1109
1110 len -= LENGTH (i);
1111
1112 /* We have to call set_properties even if we are going to
1113 merge the intervals, so as to make the undo records
1114 and cause redisplay to happen. */
1115 set_properties (properties, i, object);
1116 if (NULL_INTERVAL_P (prev_changed))
1117 prev_changed = i;
1118 else
1119 prev_changed = i = merge_interval_left (i);
1120
1121 i = next_interval (i);
1122 }
1123
1124 if (BUFFERP (object))
1125 signal_after_change (XINT (start), XINT (end) - XINT (start),
1126 XINT (end) - XINT (start));
1127 return Qt;
1128 }
1129
1130 DEFUN ("remove-text-properties", Fremove_text_properties,
1131 Sremove_text_properties, 3, 4, 0,
1132 "Remove some properties from text from START to END.\n\
1133 The third argument PROPERTIES is a property list\n\
1134 whose property names specify the properties to remove.\n\
1135 \(The values stored in PROPERTIES are ignored.)\n\
1136 The optional fourth argument, OBJECT,\n\
1137 is the string or buffer containing the text.\n\
1138 Return t if any property was actually removed, nil otherwise.")
1139 (start, end, properties, object)
1140 Lisp_Object start, end, properties, object;
1141 {
1142 register INTERVAL i, unchanged;
1143 register int s, len, modified = 0;
1144
1145 if (NILP (object))
1146 XSETBUFFER (object, current_buffer);
1147
1148 i = validate_interval_range (object, &start, &end, soft);
1149 if (NULL_INTERVAL_P (i))
1150 return Qnil;
1151
1152 s = XINT (start);
1153 len = XINT (end) - s;
1154
1155 if (i->position != s)
1156 {
1157 /* No properties on this first interval -- return if
1158 it covers the entire region. */
1159 if (! interval_has_some_properties (properties, i))
1160 {
1161 int got = (LENGTH (i) - (s - i->position));
1162 if (got >= len)
1163 return Qnil;
1164 len -= got;
1165 i = next_interval (i);
1166 }
1167 /* Split away the beginning of this interval; what we don't
1168 want to modify. */
1169 else
1170 {
1171 unchanged = i;
1172 i = split_interval_right (unchanged, s - unchanged->position);
1173 copy_properties (unchanged, i);
1174 }
1175 }
1176
1177 if (BUFFERP (object))
1178 modify_region (XBUFFER (object), XINT (start), XINT (end));
1179
1180 /* We are at the beginning of an interval, with len to scan */
1181 for (;;)
1182 {
1183 if (i == 0)
1184 abort ();
1185
1186 if (LENGTH (i) >= len)
1187 {
1188 if (! interval_has_some_properties (properties, i))
1189 return modified ? Qt : Qnil;
1190
1191 if (LENGTH (i) == len)
1192 {
1193 remove_properties (properties, i, object);
1194 if (BUFFERP (object))
1195 signal_after_change (XINT (start), XINT (end) - XINT (start),
1196 XINT (end) - XINT (start));
1197 return Qt;
1198 }
1199
1200 /* i has the properties, and goes past the change limit */
1201 unchanged = i;
1202 i = split_interval_left (i, len);
1203 copy_properties (unchanged, i);
1204 remove_properties (properties, i, object);
1205 if (BUFFERP (object))
1206 signal_after_change (XINT (start), XINT (end) - XINT (start),
1207 XINT (end) - XINT (start));
1208 return Qt;
1209 }
1210
1211 len -= LENGTH (i);
1212 modified += remove_properties (properties, i, object);
1213 i = next_interval (i);
1214 }
1215 }
1216 \f
1217 DEFUN ("text-property-any", Ftext_property_any,
1218 Stext_property_any, 4, 5, 0,
1219 "Check text from START to END for property PROPERTY equalling VALUE.\n\
1220 If so, return the position of the first character whose property PROPERTY\n\
1221 is `eq' to VALUE. Otherwise return nil.\n\
1222 The optional fifth argument, OBJECT, is the string or buffer\n\
1223 containing the text.")
1224 (start, end, property, value, object)
1225 Lisp_Object start, end, property, value, object;
1226 {
1227 register INTERVAL i;
1228 register int e, pos;
1229
1230 if (NILP (object))
1231 XSETBUFFER (object, current_buffer);
1232 i = validate_interval_range (object, &start, &end, soft);
1233 if (NULL_INTERVAL_P (i))
1234 return (!NILP (value) || EQ (start, end) ? Qnil : start);
1235 e = XINT (end);
1236
1237 while (! NULL_INTERVAL_P (i))
1238 {
1239 if (i->position >= e)
1240 break;
1241 if (EQ (textget (i->plist, property), value))
1242 {
1243 pos = i->position;
1244 if (pos < XINT (start))
1245 pos = XINT (start);
1246 return make_number (pos - (STRINGP (object)));
1247 }
1248 i = next_interval (i);
1249 }
1250 return Qnil;
1251 }
1252
1253 DEFUN ("text-property-not-all", Ftext_property_not_all,
1254 Stext_property_not_all, 4, 5, 0,
1255 "Check text from START to END for property PROPERTY not equalling VALUE.\n\
1256 If so, return the position of the first character whose property PROPERTY\n\
1257 is not `eq' to VALUE. Otherwise, return nil.\n\
1258 The optional fifth argument, OBJECT, is the string or buffer\n\
1259 containing the text.")
1260 (start, end, property, value, object)
1261 Lisp_Object start, end, property, value, object;
1262 {
1263 register INTERVAL i;
1264 register int s, e;
1265
1266 if (NILP (object))
1267 XSETBUFFER (object, current_buffer);
1268 i = validate_interval_range (object, &start, &end, soft);
1269 if (NULL_INTERVAL_P (i))
1270 return (NILP (value) || EQ (start, end)) ? Qnil : start;
1271 s = XINT (start);
1272 e = XINT (end);
1273
1274 while (! NULL_INTERVAL_P (i))
1275 {
1276 if (i->position >= e)
1277 break;
1278 if (! EQ (textget (i->plist, property), value))
1279 {
1280 if (i->position > s)
1281 s = i->position;
1282 return make_number (s - (STRINGP (object)));
1283 }
1284 i = next_interval (i);
1285 }
1286 return Qnil;
1287 }
1288 \f
1289 #if 0 /* You can use set-text-properties for this. */
1290
1291 DEFUN ("erase-text-properties", Ferase_text_properties,
1292 Serase_text_properties, 2, 3, 0,
1293 "Remove all properties from the text from START to END.\n\
1294 The optional third argument, OBJECT,\n\
1295 is the string or buffer containing the text.")
1296 (start, end, object)
1297 Lisp_Object start, end, object;
1298 {
1299 register INTERVAL i;
1300 register INTERVAL prev_changed = NULL_INTERVAL;
1301 register int s, len, modified;
1302
1303 if (NILP (object))
1304 XSETBUFFER (object, current_buffer);
1305
1306 i = validate_interval_range (object, &start, &end, soft);
1307 if (NULL_INTERVAL_P (i))
1308 return Qnil;
1309
1310 s = XINT (start);
1311 len = XINT (end) - s;
1312
1313 if (i->position != s)
1314 {
1315 register int got;
1316 register INTERVAL unchanged = i;
1317
1318 /* If there are properties here, then this text will be modified. */
1319 if (! NILP (i->plist))
1320 {
1321 i = split_interval_right (unchanged, s - unchanged->position);
1322 i->plist = Qnil;
1323 modified++;
1324
1325 if (LENGTH (i) > len)
1326 {
1327 i = split_interval_right (i, len);
1328 copy_properties (unchanged, i);
1329 return Qt;
1330 }
1331
1332 if (LENGTH (i) == len)
1333 return Qt;
1334
1335 got = LENGTH (i);
1336 }
1337 /* If the text of I is without any properties, and contains
1338 LEN or more characters, then we may return without changing
1339 anything.*/
1340 else if (LENGTH (i) - (s - i->position) <= len)
1341 return Qnil;
1342 /* The amount of text to change extends past I, so just note
1343 how much we've gotten. */
1344 else
1345 got = LENGTH (i) - (s - i->position);
1346
1347 len -= got;
1348 prev_changed = i;
1349 i = next_interval (i);
1350 }
1351
1352 /* We are starting at the beginning of an interval, I. */
1353 while (len > 0)
1354 {
1355 if (LENGTH (i) >= len)
1356 {
1357 /* If I has no properties, simply merge it if possible. */
1358 if (NILP (i->plist))
1359 {
1360 if (! NULL_INTERVAL_P (prev_changed))
1361 merge_interval_left (i);
1362
1363 return modified ? Qt : Qnil;
1364 }
1365
1366 if (LENGTH (i) > len)
1367 i = split_interval_left (i, len);
1368 if (! NULL_INTERVAL_P (prev_changed))
1369 merge_interval_left (i);
1370 else
1371 i->plist = Qnil;
1372
1373 return Qt;
1374 }
1375
1376 /* Here if we still need to erase past the end of I */
1377 len -= LENGTH (i);
1378 if (NULL_INTERVAL_P (prev_changed))
1379 {
1380 modified += erase_properties (i);
1381 prev_changed = i;
1382 }
1383 else
1384 {
1385 modified += ! NILP (i->plist);
1386 /* Merging I will give it the properties of PREV_CHANGED. */
1387 prev_changed = i = merge_interval_left (i);
1388 }
1389
1390 i = next_interval (i);
1391 }
1392
1393 return modified ? Qt : Qnil;
1394 }
1395 #endif /* 0 */
1396
1397 /* I don't think this is the right interface to export; how often do you
1398 want to do something like this, other than when you're copying objects
1399 around?
1400
1401 I think it would be better to have a pair of functions, one which
1402 returns the text properties of a region as a list of ranges and
1403 plists, and another which applies such a list to another object. */
1404
1405 /* Add properties from SRC to SRC of SRC, starting at POS in DEST.
1406 SRC and DEST may each refer to strings or buffers.
1407 Optional sixth argument PROP causes only that property to be copied.
1408 Properties are copied to DEST as if by `add-text-properties'.
1409 Return t if any property value actually changed, nil otherwise. */
1410
1411 /* Note this can GC when DEST is a buffer. */
1412 \f
1413 Lisp_Object
1414 copy_text_properties (start, end, src, pos, dest, prop)
1415 Lisp_Object start, end, src, pos, dest, prop;
1416 {
1417 INTERVAL i;
1418 Lisp_Object res;
1419 Lisp_Object stuff;
1420 Lisp_Object plist;
1421 int s, e, e2, p, len, modified = 0;
1422 struct gcpro gcpro1, gcpro2;
1423
1424 i = validate_interval_range (src, &start, &end, soft);
1425 if (NULL_INTERVAL_P (i))
1426 return Qnil;
1427
1428 CHECK_NUMBER_COERCE_MARKER (pos, 0);
1429 {
1430 Lisp_Object dest_start, dest_end;
1431
1432 dest_start = pos;
1433 XSETFASTINT (dest_end, XINT (dest_start) + (XINT (end) - XINT (start)));
1434 /* Apply this to a copy of pos; it will try to increment its arguments,
1435 which we don't want. */
1436 validate_interval_range (dest, &dest_start, &dest_end, soft);
1437 }
1438
1439 s = XINT (start);
1440 e = XINT (end);
1441 p = XINT (pos);
1442
1443 stuff = Qnil;
1444
1445 while (s < e)
1446 {
1447 e2 = i->position + LENGTH (i);
1448 if (e2 > e)
1449 e2 = e;
1450 len = e2 - s;
1451
1452 plist = i->plist;
1453 if (! NILP (prop))
1454 while (! NILP (plist))
1455 {
1456 if (EQ (Fcar (plist), prop))
1457 {
1458 plist = Fcons (prop, Fcons (Fcar (Fcdr (plist)), Qnil));
1459 break;
1460 }
1461 plist = Fcdr (Fcdr (plist));
1462 }
1463 if (! NILP (plist))
1464 {
1465 /* Must defer modifications to the interval tree in case src
1466 and dest refer to the same string or buffer. */
1467 stuff = Fcons (Fcons (make_number (p),
1468 Fcons (make_number (p + len),
1469 Fcons (plist, Qnil))),
1470 stuff);
1471 }
1472
1473 i = next_interval (i);
1474 if (NULL_INTERVAL_P (i))
1475 break;
1476
1477 p += len;
1478 s = i->position;
1479 }
1480
1481 GCPRO2 (stuff, dest);
1482
1483 while (! NILP (stuff))
1484 {
1485 res = Fcar (stuff);
1486 res = Fadd_text_properties (Fcar (res), Fcar (Fcdr (res)),
1487 Fcar (Fcdr (Fcdr (res))), dest);
1488 if (! NILP (res))
1489 modified++;
1490 stuff = Fcdr (stuff);
1491 }
1492
1493 UNGCPRO;
1494
1495 return modified ? Qt : Qnil;
1496 }
1497 \f
1498 /* Call the modification hook functions in LIST, each with START and END. */
1499
1500 static void
1501 call_mod_hooks (list, start, end)
1502 Lisp_Object list, start, end;
1503 {
1504 struct gcpro gcpro1;
1505 GCPRO1 (list);
1506 while (!NILP (list))
1507 {
1508 call2 (Fcar (list), start, end);
1509 list = Fcdr (list);
1510 }
1511 UNGCPRO;
1512 }
1513
1514 /* Check for read-only intervals and signal an error if we find one.
1515 Then check for any modification hooks in the range START up to
1516 (but not including) END. Create a list of all these hooks in
1517 lexicographic order, eliminating consecutive extra copies of the
1518 same hook. Then call those hooks in order, with START and END - 1
1519 as arguments. */
1520
1521 void
1522 verify_interval_modification (buf, start, end)
1523 struct buffer *buf;
1524 int start, end;
1525 {
1526 register INTERVAL intervals = BUF_INTERVALS (buf);
1527 register INTERVAL i, prev;
1528 Lisp_Object hooks;
1529 register Lisp_Object prev_mod_hooks;
1530 Lisp_Object mod_hooks;
1531 struct gcpro gcpro1;
1532
1533 hooks = Qnil;
1534 prev_mod_hooks = Qnil;
1535 mod_hooks = Qnil;
1536
1537 interval_insert_behind_hooks = Qnil;
1538 interval_insert_in_front_hooks = Qnil;
1539
1540 if (NULL_INTERVAL_P (intervals))
1541 return;
1542
1543 if (start > end)
1544 {
1545 int temp = start;
1546 start = end;
1547 end = temp;
1548 }
1549
1550 /* For an insert operation, check the two chars around the position. */
1551 if (start == end)
1552 {
1553 INTERVAL prev;
1554 Lisp_Object before, after;
1555
1556 /* Set I to the interval containing the char after START,
1557 and PREV to the interval containing the char before START.
1558 Either one may be null. They may be equal. */
1559 i = find_interval (intervals, start);
1560
1561 if (start == BUF_BEGV (buf))
1562 prev = 0;
1563 else if (i->position == start)
1564 prev = previous_interval (i);
1565 else if (i->position < start)
1566 prev = i;
1567 if (start == BUF_ZV (buf))
1568 i = 0;
1569
1570 /* If Vinhibit_read_only is set and is not a list, we can
1571 skip the read_only checks. */
1572 if (NILP (Vinhibit_read_only) || CONSP (Vinhibit_read_only))
1573 {
1574 /* If I and PREV differ we need to check for the read-only
1575 property together with its stickiness. If either I or
1576 PREV are 0, this check is all we need.
1577 We have to take special care, since read-only may be
1578 indirectly defined via the category property. */
1579 if (i != prev)
1580 {
1581 if (! NULL_INTERVAL_P (i))
1582 {
1583 after = textget (i->plist, Qread_only);
1584
1585 /* If interval I is read-only and read-only is
1586 front-sticky, inhibit insertion.
1587 Check for read-only as well as category. */
1588 if (! NILP (after)
1589 && NILP (Fmemq (after, Vinhibit_read_only)))
1590 {
1591 Lisp_Object tem;
1592
1593 tem = textget (i->plist, Qfront_sticky);
1594 if (TMEM (Qread_only, tem)
1595 || (NILP (Fplist_get (i->plist, Qread_only))
1596 && TMEM (Qcategory, tem)))
1597 error ("Attempt to insert within read-only text");
1598 }
1599 }
1600
1601 if (! NULL_INTERVAL_P (prev))
1602 {
1603 before = textget (prev->plist, Qread_only);
1604
1605 /* If interval PREV is read-only and read-only isn't
1606 rear-nonsticky, inhibit insertion.
1607 Check for read-only as well as category. */
1608 if (! NILP (before)
1609 && NILP (Fmemq (before, Vinhibit_read_only)))
1610 {
1611 Lisp_Object tem;
1612
1613 tem = textget (prev->plist, Qrear_nonsticky);
1614 if (! TMEM (Qread_only, tem)
1615 && (! NILP (Fplist_get (prev->plist,Qread_only))
1616 || ! TMEM (Qcategory, tem)))
1617 error ("Attempt to insert within read-only text");
1618 }
1619 }
1620 }
1621 else if (! NULL_INTERVAL_P (i))
1622 {
1623 after = textget (i->plist, Qread_only);
1624
1625 /* If interval I is read-only and read-only is
1626 front-sticky, inhibit insertion.
1627 Check for read-only as well as category. */
1628 if (! NILP (after) && NILP (Fmemq (after, Vinhibit_read_only)))
1629 {
1630 Lisp_Object tem;
1631
1632 tem = textget (i->plist, Qfront_sticky);
1633 if (TMEM (Qread_only, tem)
1634 || (NILP (Fplist_get (i->plist, Qread_only))
1635 && TMEM (Qcategory, tem)))
1636 error ("Attempt to insert within read-only text");
1637
1638 tem = textget (prev->plist, Qrear_nonsticky);
1639 if (! TMEM (Qread_only, tem)
1640 && (! NILP (Fplist_get (prev->plist, Qread_only))
1641 || ! TMEM (Qcategory, tem)))
1642 error ("Attempt to insert within read-only text");
1643 }
1644 }
1645 }
1646
1647 /* Run both insert hooks (just once if they're the same). */
1648 if (!NULL_INTERVAL_P (prev))
1649 interval_insert_behind_hooks
1650 = textget (prev->plist, Qinsert_behind_hooks);
1651 if (!NULL_INTERVAL_P (i))
1652 interval_insert_in_front_hooks
1653 = textget (i->plist, Qinsert_in_front_hooks);
1654 }
1655 else
1656 {
1657 /* Loop over intervals on or next to START...END,
1658 collecting their hooks. */
1659
1660 i = find_interval (intervals, start);
1661 do
1662 {
1663 if (! INTERVAL_WRITABLE_P (i))
1664 error ("Attempt to modify read-only text");
1665
1666 mod_hooks = textget (i->plist, Qmodification_hooks);
1667 if (! NILP (mod_hooks) && ! EQ (mod_hooks, prev_mod_hooks))
1668 {
1669 hooks = Fcons (mod_hooks, hooks);
1670 prev_mod_hooks = mod_hooks;
1671 }
1672
1673 i = next_interval (i);
1674 }
1675 /* Keep going thru the interval containing the char before END. */
1676 while (! NULL_INTERVAL_P (i) && i->position < end);
1677
1678 GCPRO1 (hooks);
1679 hooks = Fnreverse (hooks);
1680 while (! EQ (hooks, Qnil))
1681 {
1682 call_mod_hooks (Fcar (hooks), make_number (start),
1683 make_number (end));
1684 hooks = Fcdr (hooks);
1685 }
1686 UNGCPRO;
1687 }
1688 }
1689
1690 /* Run the interval hooks for an insertion.
1691 verify_interval_modification chose which hooks to run;
1692 this function is called after the insertion happens
1693 so it can indicate the range of inserted text. */
1694
1695 void
1696 report_interval_modification (start, end)
1697 Lisp_Object start, end;
1698 {
1699 if (! NILP (interval_insert_behind_hooks))
1700 call_mod_hooks (interval_insert_behind_hooks,
1701 make_number (start), make_number (end));
1702 if (! NILP (interval_insert_in_front_hooks)
1703 && ! EQ (interval_insert_in_front_hooks,
1704 interval_insert_behind_hooks))
1705 call_mod_hooks (interval_insert_in_front_hooks,
1706 make_number (start), make_number (end));
1707 }
1708 \f
1709 void
1710 syms_of_textprop ()
1711 {
1712 DEFVAR_LISP ("default-text-properties", &Vdefault_text_properties,
1713 "Property-list used as default values.\n\
1714 The value of a property in this list is seen as the value for every\n\
1715 character that does not have its own value for that property.");
1716 Vdefault_text_properties = Qnil;
1717
1718 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks,
1719 "If non-nil, don't run `point-left' and `point-entered' text properties.\n\
1720 This also inhibits the use of the `intangible' text property.");
1721 Vinhibit_point_motion_hooks = Qnil;
1722
1723 staticpro (&interval_insert_behind_hooks);
1724 staticpro (&interval_insert_in_front_hooks);
1725 interval_insert_behind_hooks = Qnil;
1726 interval_insert_in_front_hooks = Qnil;
1727
1728
1729 /* Common attributes one might give text */
1730
1731 staticpro (&Qforeground);
1732 Qforeground = intern ("foreground");
1733 staticpro (&Qbackground);
1734 Qbackground = intern ("background");
1735 staticpro (&Qfont);
1736 Qfont = intern ("font");
1737 staticpro (&Qstipple);
1738 Qstipple = intern ("stipple");
1739 staticpro (&Qunderline);
1740 Qunderline = intern ("underline");
1741 staticpro (&Qread_only);
1742 Qread_only = intern ("read-only");
1743 staticpro (&Qinvisible);
1744 Qinvisible = intern ("invisible");
1745 staticpro (&Qintangible);
1746 Qintangible = intern ("intangible");
1747 staticpro (&Qcategory);
1748 Qcategory = intern ("category");
1749 staticpro (&Qlocal_map);
1750 Qlocal_map = intern ("local-map");
1751 staticpro (&Qfront_sticky);
1752 Qfront_sticky = intern ("front-sticky");
1753 staticpro (&Qrear_nonsticky);
1754 Qrear_nonsticky = intern ("rear-nonsticky");
1755
1756 /* Properties that text might use to specify certain actions */
1757
1758 staticpro (&Qmouse_left);
1759 Qmouse_left = intern ("mouse-left");
1760 staticpro (&Qmouse_entered);
1761 Qmouse_entered = intern ("mouse-entered");
1762 staticpro (&Qpoint_left);
1763 Qpoint_left = intern ("point-left");
1764 staticpro (&Qpoint_entered);
1765 Qpoint_entered = intern ("point-entered");
1766
1767 defsubr (&Stext_properties_at);
1768 defsubr (&Sget_text_property);
1769 defsubr (&Sget_char_property);
1770 defsubr (&Snext_char_property_change);
1771 defsubr (&Sprevious_char_property_change);
1772 defsubr (&Snext_property_change);
1773 defsubr (&Snext_single_property_change);
1774 defsubr (&Sprevious_property_change);
1775 defsubr (&Sprevious_single_property_change);
1776 defsubr (&Sadd_text_properties);
1777 defsubr (&Sput_text_property);
1778 defsubr (&Sset_text_properties);
1779 defsubr (&Sremove_text_properties);
1780 defsubr (&Stext_property_any);
1781 defsubr (&Stext_property_not_all);
1782 /* defsubr (&Serase_text_properties); */
1783 /* defsubr (&Scopy_text_properties); */
1784 }
1785
1786 #else
1787
1788 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1789
1790 #endif /* USE_TEXT_PROPERTIES */