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