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