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