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