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