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