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