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