]> code.delx.au - gnu-emacs/blob - src/textprop.c
(Ftext_properties_at, Fget_char_property, Fnext_property_change,
[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
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) && CONSP ((o2) = XCONS (o1)->cdr))
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 = b->intervals;
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 XFASTINT (*begin) += 1;
145 if (begin != end)
146 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 register Lisp_Object tail1, tail2, sym1, val1;
335 register int changed = 0;
336 register int found;
337
338 /* Go through each element of PLIST. */
339 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
340 {
341 sym1 = Fcar (tail1);
342 val1 = Fcar (Fcdr (tail1));
343 found = 0;
344
345 /* Go through I's plist, looking for sym1 */
346 for (tail2 = i->plist; ! NILP (tail2); tail2 = Fcdr (Fcdr (tail2)))
347 if (EQ (sym1, Fcar (tail2)))
348 {
349 register Lisp_Object this_cdr;
350
351 this_cdr = Fcdr (tail2);
352 /* Found the property. Now check its value. */
353 found = 1;
354
355 /* The properties have the same value on both lists.
356 Continue to the next property. */
357 if (EQ (val1, Fcar (this_cdr)))
358 break;
359
360 /* Record this change in the buffer, for undo purposes. */
361 if (BUFFERP (object))
362 {
363 modify_region (XBUFFER (object),
364 make_number (i->position),
365 make_number (i->position + LENGTH (i)));
366 record_property_change (i->position, LENGTH (i),
367 sym1, Fcar (this_cdr), object);
368 }
369
370 /* I's property has a different value -- change it */
371 Fsetcar (this_cdr, val1);
372 changed++;
373 break;
374 }
375
376 if (! found)
377 {
378 /* Record this change in the buffer, for undo purposes. */
379 if (BUFFERP (object))
380 {
381 modify_region (XBUFFER (object),
382 make_number (i->position),
383 make_number (i->position + LENGTH (i)));
384 record_property_change (i->position, LENGTH (i),
385 sym1, Qnil, object);
386 }
387 i->plist = Fcons (sym1, Fcons (val1, i->plist));
388 changed++;
389 }
390 }
391
392 return changed;
393 }
394
395 /* For any members of PLIST which are properties of I, remove them
396 from I's plist.
397 OBJECT is the string or buffer containing I. */
398
399 static int
400 remove_properties (plist, i, object)
401 Lisp_Object plist;
402 INTERVAL i;
403 Lisp_Object object;
404 {
405 register Lisp_Object tail1, tail2, sym, current_plist;
406 register int changed = 0;
407
408 current_plist = i->plist;
409 /* Go through each element of plist. */
410 for (tail1 = plist; ! NILP (tail1); tail1 = Fcdr (Fcdr (tail1)))
411 {
412 sym = Fcar (tail1);
413
414 /* First, remove the symbol if its at the head of the list */
415 while (! NILP (current_plist) && EQ (sym, Fcar (current_plist)))
416 {
417 if (BUFFERP (object))
418 {
419 modify_region (XBUFFER (object),
420 make_number (i->position),
421 make_number (i->position + LENGTH (i)));
422 record_property_change (i->position, LENGTH (i),
423 sym, Fcar (Fcdr (current_plist)),
424 object);
425 }
426
427 current_plist = Fcdr (Fcdr (current_plist));
428 changed++;
429 }
430
431 /* Go through i's plist, looking for sym */
432 tail2 = current_plist;
433 while (! NILP (tail2))
434 {
435 register Lisp_Object this;
436 this = Fcdr (Fcdr (tail2));
437 if (EQ (sym, Fcar (this)))
438 {
439 if (BUFFERP (object))
440 {
441 modify_region (XBUFFER (object),
442 make_number (i->position),
443 make_number (i->position + LENGTH (i)));
444 record_property_change (i->position, LENGTH (i),
445 sym, Fcar (Fcdr (this)), object);
446 }
447
448 Fsetcdr (Fcdr (tail2), Fcdr (Fcdr (this)));
449 changed++;
450 }
451 tail2 = this;
452 }
453 }
454
455 if (changed)
456 i->plist = current_plist;
457 return changed;
458 }
459
460 #if 0
461 /* Remove all properties from interval I. Return non-zero
462 if this changes the interval. */
463
464 static INLINE int
465 erase_properties (i)
466 INTERVAL i;
467 {
468 if (NILP (i->plist))
469 return 0;
470
471 i->plist = Qnil;
472 return 1;
473 }
474 #endif
475 \f
476 DEFUN ("text-properties-at", Ftext_properties_at,
477 Stext_properties_at, 1, 2, 0,
478 "Return the list of properties held by the character at POSITION\n\
479 in optional argument OBJECT, a string or buffer. If nil, OBJECT\n\
480 defaults to the current buffer.\n\
481 If POSITION is at the end of OBJECT, the value is nil.")
482 (pos, object)
483 Lisp_Object pos, object;
484 {
485 register INTERVAL i;
486
487 if (NILP (object))
488 XSETBUFFER (object, current_buffer);
489
490 i = validate_interval_range (object, &pos, &pos, soft);
491 if (NULL_INTERVAL_P (i))
492 return Qnil;
493 /* If POS is at the end of the interval,
494 it means it's the end of OBJECT.
495 There are no properties at the very end,
496 since no character follows. */
497 if (XINT (pos) == LENGTH (i) + i->position)
498 return Qnil;
499
500 return i->plist;
501 }
502
503 DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
504 "Return the value of position POS's property PROP, in OBJECT.\n\
505 OBJECT is optional and defaults to the current buffer.\n\
506 If POSITION is at the end of OBJECT, the value is nil.")
507 (pos, prop, object)
508 Lisp_Object pos, object;
509 Lisp_Object prop;
510 {
511 return textget (Ftext_properties_at (pos, object), prop);
512 }
513
514 DEFUN ("get-char-property", Fget_char_property, Sget_char_property, 2, 3, 0,
515 "Return the value of position POS's property PROP, in OBJECT.\n\
516 OBJECT is optional and defaults to the current buffer.\n\
517 If POS is at the end of OBJECT, the value is nil.\n\
518 If OBJECT is a buffer, then overlay properties are considered as well as\n\
519 text properties.\n\
520 If OBJECT is a window, then that window's buffer is used, but window-specific\n\
521 overlays are considered only if they are associated with OBJECT.")
522 (pos, prop, object)
523 Lisp_Object pos, object;
524 register Lisp_Object prop;
525 {
526 struct window *w = 0;
527
528 CHECK_NUMBER_COERCE_MARKER (pos, 0);
529
530 if (NILP (object))
531 XSETBUFFER (object, current_buffer);
532
533 if (WINDOWP (object))
534 {
535 w = XWINDOW (object);
536 object = w->buffer;
537 }
538 if (BUFFERP (object))
539 {
540 int posn = XINT (pos);
541 int noverlays;
542 Lisp_Object *overlay_vec, tem;
543 int next_overlay;
544 int len;
545
546 /* First try with room for 40 overlays. */
547 len = 40;
548 overlay_vec = (Lisp_Object *) alloca (len * sizeof (Lisp_Object));
549
550 noverlays = overlays_at (posn, 0, &overlay_vec, &len,
551 &next_overlay, NULL);
552
553 /* If there are more than 40,
554 make enough space for all, and try again. */
555 if (noverlays > len)
556 {
557 len = noverlays;
558 overlay_vec = (Lisp_Object *) alloca (len * sizeof (Lisp_Object));
559 noverlays = overlays_at (posn, 0, &overlay_vec, &len,
560 &next_overlay, NULL);
561 }
562 noverlays = sort_overlays (overlay_vec, noverlays, w);
563
564 /* Now check the overlays in order of decreasing priority. */
565 while (--noverlays >= 0)
566 {
567 tem = Foverlay_get (overlay_vec[noverlays], prop);
568 if (!NILP (tem))
569 return (tem);
570 }
571 }
572 /* Not a buffer, or no appropriate overlay, so fall through to the
573 simpler case. */
574 return (Fget_text_property (pos, prop, object));
575 }
576
577 DEFUN ("next-property-change", Fnext_property_change,
578 Snext_property_change, 1, 3, 0,
579 "Return the position of next property change.\n\
580 Scans characters forward from POS in OBJECT till it finds\n\
581 a change in some text property, then returns the position of the change.\n\
582 The optional second argument OBJECT is the string or buffer to scan.\n\
583 Return nil if the property is constant all the way to the end of OBJECT.\n\
584 If the value is non-nil, it is a position greater than POS, never equal.\n\n\
585 If the optional third argument LIMIT is non-nil, don't search\n\
586 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
587 (pos, object, limit)
588 Lisp_Object pos, object, limit;
589 {
590 register INTERVAL i, next;
591
592 if (NILP (object))
593 XSETBUFFER (object, current_buffer);
594
595 if (!NILP (limit))
596 CHECK_NUMBER_COERCE_MARKER (limit, 0);
597
598 i = validate_interval_range (object, &pos, &pos, soft);
599 if (NULL_INTERVAL_P (i))
600 return limit;
601
602 next = next_interval (i);
603 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next)
604 && (NILP (limit) || next->position < XFASTINT (limit)))
605 next = next_interval (next);
606
607 if (NULL_INTERVAL_P (next))
608 return limit;
609 if (! NILP (limit) && !(next->position < XFASTINT (limit)))
610 return limit;
611
612 XFASTINT (pos) = next->position - (STRINGP (object));
613 return pos;
614 }
615
616 /* Return 1 if there's a change in some property between BEG and END. */
617
618 int
619 property_change_between_p (beg, end)
620 int beg, end;
621 {
622 register INTERVAL i, next;
623 Lisp_Object object, pos;
624
625 XSETBUFFER (object, current_buffer);
626 XFASTINT (pos) = beg;
627
628 i = validate_interval_range (object, &pos, &pos, soft);
629 if (NULL_INTERVAL_P (i))
630 return 0;
631
632 next = next_interval (i);
633 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
634 {
635 next = next_interval (next);
636 if (NULL_INTERVAL_P (next))
637 return 0;
638 if (next->position >= end)
639 return 0;
640 }
641
642 if (NULL_INTERVAL_P (next))
643 return 0;
644
645 return 1;
646 }
647
648 DEFUN ("next-single-property-change", Fnext_single_property_change,
649 Snext_single_property_change, 2, 4, 0,
650 "Return the position of next property change for a specific property.\n\
651 Scans characters forward from POS till it finds\n\
652 a change in the PROP property, then returns the position of the change.\n\
653 The optional third argument OBJECT is the string or buffer to scan.\n\
654 The property values are compared with `eq'.\n\
655 Return nil if the property is constant all the way to the end of OBJECT.\n\
656 If the value is non-nil, it is a position greater than POS, never equal.\n\n\
657 If the optional fourth argument LIMIT is non-nil, don't search\n\
658 past position LIMIT; return LIMIT if nothing is found before LIMIT.")
659 (pos, prop, object, limit)
660 Lisp_Object pos, prop, object, limit;
661 {
662 register INTERVAL i, next;
663 register Lisp_Object here_val;
664
665 if (NILP (object))
666 XSETBUFFER (object, current_buffer);
667
668 if (!NILP (limit))
669 CHECK_NUMBER_COERCE_MARKER (limit, 0);
670
671 i = validate_interval_range (object, &pos, &pos, soft);
672 if (NULL_INTERVAL_P (i))
673 return limit;
674
675 here_val = textget (i->plist, prop);
676 next = next_interval (i);
677 while (! NULL_INTERVAL_P (next)
678 && EQ (here_val, textget (next->plist, prop))
679 && (NILP (limit) || next->position < XFASTINT (limit)))
680 next = next_interval (next);
681
682 if (NULL_INTERVAL_P (next))
683 return limit;
684 if (! NILP (limit) && !(next->position < XFASTINT (limit)))
685 return limit;
686
687 XFASTINT (pos) = next->position - (STRINGP (object));
688 return pos;
689 }
690
691 DEFUN ("previous-property-change", Fprevious_property_change,
692 Sprevious_property_change, 1, 3, 0,
693 "Return the position of previous property change.\n\
694 Scans characters backwards from POS in OBJECT till it finds\n\
695 a change in some text property, then returns the position of the change.\n\
696 The optional second argument OBJECT is the string or buffer to scan.\n\
697 Return nil if the property is constant all the way to the start of OBJECT.\n\
698 If the value is non-nil, it is a position less than POS, never equal.\n\n\
699 If the optional third argument LIMIT is non-nil, don't search\n\
700 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
701 (pos, object, limit)
702 Lisp_Object pos, object, limit;
703 {
704 register INTERVAL i, previous;
705
706 if (NILP (object))
707 XSETBUFFER (object, current_buffer);
708
709 if (!NILP (limit))
710 CHECK_NUMBER_COERCE_MARKER (limit, 0);
711
712 i = validate_interval_range (object, &pos, &pos, soft);
713 if (NULL_INTERVAL_P (i))
714 return limit;
715
716 /* Start with the interval containing the char before point. */
717 if (i->position == XFASTINT (pos))
718 i = previous_interval (i);
719
720 previous = previous_interval (i);
721 while (! NULL_INTERVAL_P (previous) && intervals_equal (previous, i)
722 && (NILP (limit)
723 || previous->position + LENGTH (previous) > XFASTINT (limit)))
724 previous = previous_interval (previous);
725 if (NULL_INTERVAL_P (previous))
726 return limit;
727 if (!NILP (limit)
728 && !(previous->position + LENGTH (previous) > XFASTINT (limit)))
729 return limit;
730
731 XFASTINT (pos) = (previous->position + LENGTH (previous)
732 - (STRINGP (object)));
733 return pos;
734 }
735
736 DEFUN ("previous-single-property-change", Fprevious_single_property_change,
737 Sprevious_single_property_change, 2, 4, 0,
738 "Return the position of previous property change for a specific property.\n\
739 Scans characters backward from POS till it finds\n\
740 a change in the PROP property, then returns the position of the change.\n\
741 The optional third argument OBJECT is the string or buffer to scan.\n\
742 The property values are compared with `eq'.\n\
743 Return nil if the property is constant all the way to the start of OBJECT.\n\
744 If the value is non-nil, it is a position less than POS, never equal.\n\n\
745 If the optional fourth argument LIMIT is non-nil, don't search\n\
746 back past position LIMIT; return LIMIT if nothing is found until LIMIT.")
747 (pos, prop, object, limit)
748 Lisp_Object pos, prop, object, limit;
749 {
750 register INTERVAL i, previous;
751 register Lisp_Object here_val;
752
753 if (NILP (object))
754 XSETBUFFER (object, current_buffer);
755
756 if (!NILP (limit))
757 CHECK_NUMBER_COERCE_MARKER (limit, 0);
758
759 i = validate_interval_range (object, &pos, &pos, soft);
760
761 /* Start with the interval containing the char before point. */
762 if (! NULL_INTERVAL_P (i) && i->position == XFASTINT (pos))
763 i = previous_interval (i);
764
765 if (NULL_INTERVAL_P (i))
766 return limit;
767
768 here_val = textget (i->plist, prop);
769 previous = previous_interval (i);
770 while (! NULL_INTERVAL_P (previous)
771 && EQ (here_val, textget (previous->plist, prop))
772 && (NILP (limit)
773 || previous->position + LENGTH (previous) > XFASTINT (limit)))
774 previous = previous_interval (previous);
775 if (NULL_INTERVAL_P (previous))
776 return limit;
777 if (!NILP (limit)
778 && !(previous->position + LENGTH (previous) > XFASTINT (limit)))
779 return limit;
780
781 XFASTINT (pos) = (previous->position + LENGTH (previous)
782 - (STRINGP (object)));
783 return pos;
784 }
785
786 DEFUN ("add-text-properties", Fadd_text_properties,
787 Sadd_text_properties, 3, 4, 0,
788 "Add properties to the text from START to END.\n\
789 The third argument PROPS is a property list\n\
790 specifying the property values to add.\n\
791 The optional fourth argument, OBJECT,\n\
792 is the string or buffer containing the text.\n\
793 Return t if any property value actually changed, nil otherwise.")
794 (start, end, properties, object)
795 Lisp_Object start, end, properties, object;
796 {
797 register INTERVAL i, unchanged;
798 register int s, len, modified = 0;
799
800 properties = validate_plist (properties);
801 if (NILP (properties))
802 return Qnil;
803
804 if (NILP (object))
805 XSETBUFFER (object, current_buffer);
806
807 i = validate_interval_range (object, &start, &end, hard);
808 if (NULL_INTERVAL_P (i))
809 return Qnil;
810
811 s = XINT (start);
812 len = XINT (end) - s;
813
814 /* If we're not starting on an interval boundary, we have to
815 split this interval. */
816 if (i->position != s)
817 {
818 /* If this interval already has the properties, we can
819 skip it. */
820 if (interval_has_all_properties (properties, i))
821 {
822 int got = (LENGTH (i) - (s - i->position));
823 if (got >= len)
824 return Qnil;
825 len -= got;
826 i = next_interval (i);
827 }
828 else
829 {
830 unchanged = i;
831 i = split_interval_right (unchanged, s - unchanged->position);
832 copy_properties (unchanged, i);
833 }
834 }
835
836 /* We are at the beginning of interval I, with LEN chars to scan. */
837 for (;;)
838 {
839 if (i == 0)
840 abort ();
841
842 if (LENGTH (i) >= len)
843 {
844 if (interval_has_all_properties (properties, i))
845 return modified ? Qt : Qnil;
846
847 if (LENGTH (i) == len)
848 {
849 add_properties (properties, i, object);
850 return Qt;
851 }
852
853 /* i doesn't have the properties, and goes past the change limit */
854 unchanged = i;
855 i = split_interval_left (unchanged, len);
856 copy_properties (unchanged, i);
857 add_properties (properties, i, object);
858 return Qt;
859 }
860
861 len -= LENGTH (i);
862 modified += add_properties (properties, i, object);
863 i = next_interval (i);
864 }
865 }
866
867 DEFUN ("put-text-property", Fput_text_property,
868 Sput_text_property, 4, 5, 0,
869 "Set one property of the text from START to END.\n\
870 The third and fourth arguments PROP and VALUE\n\
871 specify the property to add.\n\
872 The optional fifth argument, OBJECT,\n\
873 is the string or buffer containing the text.")
874 (start, end, prop, value, object)
875 Lisp_Object start, end, prop, value, object;
876 {
877 Fadd_text_properties (start, end,
878 Fcons (prop, Fcons (value, Qnil)),
879 object);
880 return Qnil;
881 }
882
883 DEFUN ("set-text-properties", Fset_text_properties,
884 Sset_text_properties, 3, 4, 0,
885 "Completely replace properties of text from START to END.\n\
886 The third argument PROPS is the new property list.\n\
887 The optional fourth argument, OBJECT,\n\
888 is the string or buffer containing the text.")
889 (start, end, props, object)
890 Lisp_Object start, end, props, object;
891 {
892 register INTERVAL i, unchanged;
893 register INTERVAL prev_changed = NULL_INTERVAL;
894 register int s, len;
895 Lisp_Object ostart, oend;
896
897 ostart = start;
898 oend = end;
899
900 props = validate_plist (props);
901
902 if (NILP (object))
903 XSETBUFFER (object, current_buffer);
904
905 i = validate_interval_range (object, &start, &end, soft);
906 if (NULL_INTERVAL_P (i))
907 {
908 /* If buffer has no props, and we want none, return now. */
909 if (NILP (props))
910 return Qnil;
911
912 /* Restore the original START and END values
913 because validate_interval_range increments them for strings. */
914 start = ostart;
915 end = oend;
916
917 i = validate_interval_range (object, &start, &end, hard);
918 /* This can return if start == end. */
919 if (NULL_INTERVAL_P (i))
920 return Qnil;
921 }
922
923 s = XINT (start);
924 len = XINT (end) - s;
925
926 if (i->position != s)
927 {
928 unchanged = i;
929 i = split_interval_right (unchanged, s - unchanged->position);
930
931 if (LENGTH (i) > len)
932 {
933 copy_properties (unchanged, i);
934 i = split_interval_left (i, len);
935 set_properties (props, i, object);
936 return Qt;
937 }
938
939 set_properties (props, i, object);
940
941 if (LENGTH (i) == len)
942 return Qt;
943
944 prev_changed = i;
945 len -= LENGTH (i);
946 i = next_interval (i);
947 }
948
949 /* We are starting at the beginning of an interval, I */
950 while (len > 0)
951 {
952 if (i == 0)
953 abort ();
954
955 if (LENGTH (i) >= len)
956 {
957 if (LENGTH (i) > len)
958 i = split_interval_left (i, len);
959
960 if (NULL_INTERVAL_P (prev_changed))
961 set_properties (props, i, object);
962 else
963 merge_interval_left (i);
964 return Qt;
965 }
966
967 len -= LENGTH (i);
968 if (NULL_INTERVAL_P (prev_changed))
969 {
970 set_properties (props, i, object);
971 prev_changed = i;
972 }
973 else
974 prev_changed = i = merge_interval_left (i);
975
976 i = next_interval (i);
977 }
978
979 return Qt;
980 }
981
982 DEFUN ("remove-text-properties", Fremove_text_properties,
983 Sremove_text_properties, 3, 4, 0,
984 "Remove some properties from text from START to END.\n\
985 The third argument PROPS is a property list\n\
986 whose property names specify the properties to remove.\n\
987 \(The values stored in PROPS are ignored.)\n\
988 The optional fourth argument, OBJECT,\n\
989 is the string or buffer containing the text.\n\
990 Return t if any property was actually removed, nil otherwise.")
991 (start, end, props, object)
992 Lisp_Object start, end, props, object;
993 {
994 register INTERVAL i, unchanged;
995 register int s, len, modified = 0;
996
997 if (NILP (object))
998 XSETBUFFER (object, current_buffer);
999
1000 i = validate_interval_range (object, &start, &end, soft);
1001 if (NULL_INTERVAL_P (i))
1002 return Qnil;
1003
1004 s = XINT (start);
1005 len = XINT (end) - s;
1006
1007 if (i->position != s)
1008 {
1009 /* No properties on this first interval -- return if
1010 it covers the entire region. */
1011 if (! interval_has_some_properties (props, i))
1012 {
1013 int got = (LENGTH (i) - (s - i->position));
1014 if (got >= len)
1015 return Qnil;
1016 len -= got;
1017 i = next_interval (i);
1018 }
1019 /* Split away the beginning of this interval; what we don't
1020 want to modify. */
1021 else
1022 {
1023 unchanged = i;
1024 i = split_interval_right (unchanged, s - unchanged->position);
1025 copy_properties (unchanged, i);
1026 }
1027 }
1028
1029 /* We are at the beginning of an interval, with len to scan */
1030 for (;;)
1031 {
1032 if (i == 0)
1033 abort ();
1034
1035 if (LENGTH (i) >= len)
1036 {
1037 if (! interval_has_some_properties (props, i))
1038 return modified ? Qt : Qnil;
1039
1040 if (LENGTH (i) == len)
1041 {
1042 remove_properties (props, i, object);
1043 return Qt;
1044 }
1045
1046 /* i has the properties, and goes past the change limit */
1047 unchanged = i;
1048 i = split_interval_left (i, len);
1049 copy_properties (unchanged, i);
1050 remove_properties (props, i, object);
1051 return Qt;
1052 }
1053
1054 len -= LENGTH (i);
1055 modified += remove_properties (props, i, object);
1056 i = next_interval (i);
1057 }
1058 }
1059
1060 DEFUN ("text-property-any", Ftext_property_any,
1061 Stext_property_any, 4, 5, 0,
1062 "Check text from START to END to see if PROP is ever `eq' to VALUE.\n\
1063 If so, return the position of the first character whose PROP is `eq'\n\
1064 to VALUE. Otherwise return nil.\n\
1065 The optional fifth argument, OBJECT, is the string or buffer\n\
1066 containing the text.")
1067 (start, end, prop, value, object)
1068 Lisp_Object start, end, prop, value, object;
1069 {
1070 register INTERVAL i;
1071 register int e, pos;
1072
1073 if (NILP (object))
1074 XSETBUFFER (object, current_buffer);
1075 i = validate_interval_range (object, &start, &end, soft);
1076 e = XINT (end);
1077
1078 while (! NULL_INTERVAL_P (i))
1079 {
1080 if (i->position >= e)
1081 break;
1082 if (EQ (textget (i->plist, prop), value))
1083 {
1084 pos = i->position;
1085 if (pos < XINT (start))
1086 pos = XINT (start);
1087 return make_number (pos - (STRINGP (object)));
1088 }
1089 i = next_interval (i);
1090 }
1091 return Qnil;
1092 }
1093
1094 DEFUN ("text-property-not-all", Ftext_property_not_all,
1095 Stext_property_not_all, 4, 5, 0,
1096 "Check text from START to END to see if PROP is ever not `eq' to VALUE.\n\
1097 If so, return the position of the first character whose PROP is not\n\
1098 `eq' to VALUE. Otherwise, return nil.\n\
1099 The optional fifth argument, OBJECT, is the string or buffer\n\
1100 containing the text.")
1101 (start, end, prop, value, object)
1102 Lisp_Object start, end, prop, value, object;
1103 {
1104 register INTERVAL i;
1105 register int s, e;
1106
1107 if (NILP (object))
1108 XSETBUFFER (object, current_buffer);
1109 i = validate_interval_range (object, &start, &end, soft);
1110 if (NULL_INTERVAL_P (i))
1111 return (NILP (value) || EQ (start, end)) ? Qnil : start;
1112 s = XINT (start);
1113 e = XINT (end);
1114
1115 while (! NULL_INTERVAL_P (i))
1116 {
1117 if (i->position >= e)
1118 break;
1119 if (! EQ (textget (i->plist, prop), value))
1120 {
1121 if (i->position > s)
1122 s = i->position;
1123 return make_number (s - (STRINGP (object)));
1124 }
1125 i = next_interval (i);
1126 }
1127 return Qnil;
1128 }
1129
1130 #if 0 /* You can use set-text-properties for this. */
1131
1132 DEFUN ("erase-text-properties", Ferase_text_properties,
1133 Serase_text_properties, 2, 3, 0,
1134 "Remove all properties from the text from START to END.\n\
1135 The optional third argument, OBJECT,\n\
1136 is the string or buffer containing the text.")
1137 (start, end, object)
1138 Lisp_Object start, end, object;
1139 {
1140 register INTERVAL i;
1141 register INTERVAL prev_changed = NULL_INTERVAL;
1142 register int s, len, modified;
1143
1144 if (NILP (object))
1145 XSETBUFFER (object, current_buffer);
1146
1147 i = validate_interval_range (object, &start, &end, soft);
1148 if (NULL_INTERVAL_P (i))
1149 return Qnil;
1150
1151 s = XINT (start);
1152 len = XINT (end) - s;
1153
1154 if (i->position != s)
1155 {
1156 register int got;
1157 register INTERVAL unchanged = i;
1158
1159 /* If there are properties here, then this text will be modified. */
1160 if (! NILP (i->plist))
1161 {
1162 i = split_interval_right (unchanged, s - unchanged->position);
1163 i->plist = Qnil;
1164 modified++;
1165
1166 if (LENGTH (i) > len)
1167 {
1168 i = split_interval_right (i, len);
1169 copy_properties (unchanged, i);
1170 return Qt;
1171 }
1172
1173 if (LENGTH (i) == len)
1174 return Qt;
1175
1176 got = LENGTH (i);
1177 }
1178 /* If the text of I is without any properties, and contains
1179 LEN or more characters, then we may return without changing
1180 anything.*/
1181 else if (LENGTH (i) - (s - i->position) <= len)
1182 return Qnil;
1183 /* The amount of text to change extends past I, so just note
1184 how much we've gotten. */
1185 else
1186 got = LENGTH (i) - (s - i->position);
1187
1188 len -= got;
1189 prev_changed = i;
1190 i = next_interval (i);
1191 }
1192
1193 /* We are starting at the beginning of an interval, I. */
1194 while (len > 0)
1195 {
1196 if (LENGTH (i) >= len)
1197 {
1198 /* If I has no properties, simply merge it if possible. */
1199 if (NILP (i->plist))
1200 {
1201 if (! NULL_INTERVAL_P (prev_changed))
1202 merge_interval_left (i);
1203
1204 return modified ? Qt : Qnil;
1205 }
1206
1207 if (LENGTH (i) > len)
1208 i = split_interval_left (i, len);
1209 if (! NULL_INTERVAL_P (prev_changed))
1210 merge_interval_left (i);
1211 else
1212 i->plist = Qnil;
1213
1214 return Qt;
1215 }
1216
1217 /* Here if we still need to erase past the end of I */
1218 len -= LENGTH (i);
1219 if (NULL_INTERVAL_P (prev_changed))
1220 {
1221 modified += erase_properties (i);
1222 prev_changed = i;
1223 }
1224 else
1225 {
1226 modified += ! NILP (i->plist);
1227 /* Merging I will give it the properties of PREV_CHANGED. */
1228 prev_changed = i = merge_interval_left (i);
1229 }
1230
1231 i = next_interval (i);
1232 }
1233
1234 return modified ? Qt : Qnil;
1235 }
1236 #endif /* 0 */
1237
1238 /* I don't think this is the right interface to export; how often do you
1239 want to do something like this, other than when you're copying objects
1240 around?
1241
1242 I think it would be better to have a pair of functions, one which
1243 returns the text properties of a region as a list of ranges and
1244 plists, and another which applies such a list to another object. */
1245
1246 /* DEFUN ("copy-text-properties", Fcopy_text_properties,
1247 Scopy_text_properties, 5, 6, 0,
1248 "Add properties from SRC-START to SRC-END of SRC at DEST-POS of DEST.\n\
1249 SRC and DEST may each refer to strings or buffers.\n\
1250 Optional sixth argument PROP causes only that property to be copied.\n\
1251 Properties are copied to DEST as if by `add-text-properties'.\n\
1252 Return t if any property value actually changed, nil otherwise.") */
1253
1254 Lisp_Object
1255 copy_text_properties (start, end, src, pos, dest, prop)
1256 Lisp_Object start, end, src, pos, dest, prop;
1257 {
1258 INTERVAL i;
1259 Lisp_Object res;
1260 Lisp_Object stuff;
1261 Lisp_Object plist;
1262 int s, e, e2, p, len, modified = 0;
1263
1264 i = validate_interval_range (src, &start, &end, soft);
1265 if (NULL_INTERVAL_P (i))
1266 return Qnil;
1267
1268 CHECK_NUMBER_COERCE_MARKER (pos, 0);
1269 {
1270 Lisp_Object dest_start, dest_end;
1271
1272 dest_start = pos;
1273 XFASTINT (dest_end) = XINT (dest_start) + (XINT (end) - XINT (start));
1274 /* Apply this to a copy of pos; it will try to increment its arguments,
1275 which we don't want. */
1276 validate_interval_range (dest, &dest_start, &dest_end, soft);
1277 }
1278
1279 s = XINT (start);
1280 e = XINT (end);
1281 p = XINT (pos);
1282
1283 stuff = Qnil;
1284
1285 while (s < e)
1286 {
1287 e2 = i->position + LENGTH (i);
1288 if (e2 > e)
1289 e2 = e;
1290 len = e2 - s;
1291
1292 plist = i->plist;
1293 if (! NILP (prop))
1294 while (! NILP (plist))
1295 {
1296 if (EQ (Fcar (plist), prop))
1297 {
1298 plist = Fcons (prop, Fcons (Fcar (Fcdr (plist)), Qnil));
1299 break;
1300 }
1301 plist = Fcdr (Fcdr (plist));
1302 }
1303 if (! NILP (plist))
1304 {
1305 /* Must defer modifications to the interval tree in case src
1306 and dest refer to the same string or buffer. */
1307 stuff = Fcons (Fcons (make_number (p),
1308 Fcons (make_number (p + len),
1309 Fcons (plist, Qnil))),
1310 stuff);
1311 }
1312
1313 i = next_interval (i);
1314 if (NULL_INTERVAL_P (i))
1315 break;
1316
1317 p += len;
1318 s = i->position;
1319 }
1320
1321 while (! NILP (stuff))
1322 {
1323 res = Fcar (stuff);
1324 res = Fadd_text_properties (Fcar (res), Fcar (Fcdr (res)),
1325 Fcar (Fcdr (Fcdr (res))), dest);
1326 if (! NILP (res))
1327 modified++;
1328 stuff = Fcdr (stuff);
1329 }
1330
1331 return modified ? Qt : Qnil;
1332 }
1333
1334 void
1335 syms_of_textprop ()
1336 {
1337 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks,
1338 "If non-nil, don't run `point-left' and `point-entered' text properties.\n\
1339 This also inhibits the use of the `intangible' text property.");
1340 Vinhibit_point_motion_hooks = Qnil;
1341
1342 /* Common attributes one might give text */
1343
1344 staticpro (&Qforeground);
1345 Qforeground = intern ("foreground");
1346 staticpro (&Qbackground);
1347 Qbackground = intern ("background");
1348 staticpro (&Qfont);
1349 Qfont = intern ("font");
1350 staticpro (&Qstipple);
1351 Qstipple = intern ("stipple");
1352 staticpro (&Qunderline);
1353 Qunderline = intern ("underline");
1354 staticpro (&Qread_only);
1355 Qread_only = intern ("read-only");
1356 staticpro (&Qinvisible);
1357 Qinvisible = intern ("invisible");
1358 staticpro (&Qintangible);
1359 Qintangible = intern ("intangible");
1360 staticpro (&Qcategory);
1361 Qcategory = intern ("category");
1362 staticpro (&Qlocal_map);
1363 Qlocal_map = intern ("local-map");
1364 staticpro (&Qfront_sticky);
1365 Qfront_sticky = intern ("front-sticky");
1366 staticpro (&Qrear_nonsticky);
1367 Qrear_nonsticky = intern ("rear-nonsticky");
1368
1369 /* Properties that text might use to specify certain actions */
1370
1371 staticpro (&Qmouse_left);
1372 Qmouse_left = intern ("mouse-left");
1373 staticpro (&Qmouse_entered);
1374 Qmouse_entered = intern ("mouse-entered");
1375 staticpro (&Qpoint_left);
1376 Qpoint_left = intern ("point-left");
1377 staticpro (&Qpoint_entered);
1378 Qpoint_entered = intern ("point-entered");
1379
1380 defsubr (&Stext_properties_at);
1381 defsubr (&Sget_text_property);
1382 defsubr (&Sget_char_property);
1383 defsubr (&Snext_property_change);
1384 defsubr (&Snext_single_property_change);
1385 defsubr (&Sprevious_property_change);
1386 defsubr (&Sprevious_single_property_change);
1387 defsubr (&Sadd_text_properties);
1388 defsubr (&Sput_text_property);
1389 defsubr (&Sset_text_properties);
1390 defsubr (&Sremove_text_properties);
1391 defsubr (&Stext_property_any);
1392 defsubr (&Stext_property_not_all);
1393 /* defsubr (&Serase_text_properties); */
1394 /* defsubr (&Scopy_text_properties); */
1395 }
1396
1397 #else
1398
1399 lose -- this shouldn't be compiled if USE_TEXT_PROPERTIES isn't defined
1400
1401 #endif /* USE_TEXT_PROPERTIES */