Follow Glenn's lead and update format of Copyright.
[bpt/emacs.git] / src / textprop.c
CommitLineData
d418ef42 1/* Interface code for dealing with text properties.
0b5538bd 2 Copyright (C) 1993, 1994, 1995, 1997, 1999, 2000, 2001, 2002, 2003,
76b6f707 3 2004, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.
d418ef42
JA
4
5This file is part of GNU Emacs.
6
9ec0b715 7GNU Emacs is free software: you can redistribute it and/or modify
d418ef42 8it under the terms of the GNU General Public License as published by
9ec0b715
GM
9the Free Software Foundation, either version 3 of the License, or
10(at your option) any later version.
d418ef42
JA
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
9ec0b715 18along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. */
d418ef42 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
cdf3e5a2 46 is enforced by the subrs installing properties onto the intervals. */
d418ef42
JA
47
48\f
cdf3e5a2 49/* Types of hooks. */
d418ef42
JA
50Lisp_Object Qmouse_left;
51Lisp_Object Qmouse_entered;
52Lisp_Object Qpoint_left;
53Lisp_Object Qpoint_entered;
dc70cea7
RS
54Lisp_Object Qcategory;
55Lisp_Object Qlocal_map;
d418ef42 56
cdf3e5a2 57/* Visual properties text (including strings) may have. */
d418ef42 58Lisp_Object Qforeground, Qbackground, Qfont, Qunderline, Qstipple;
69bb837e 59Lisp_Object Qinvisible, Qread_only, Qintangible, Qmouse_face;
54b33868 60Lisp_Object Qminibuffer_prompt;
19e1c426
RS
61
62/* Sticky properties */
63Lisp_Object Qfront_sticky, Qrear_nonsticky;
d7b4e137
JB
64
65/* If o1 is a cons whose cdr is a cons, return non-zero and set o2 to
66 the o1's cdr. Otherwise, return zero. This is handy for
67 traversing plists. */
70949dac 68#define PLIST_ELT_P(o1, o2) (CONSP (o1) && ((o2)=XCDR (o1), CONSP (o2)))
d7b4e137 69
688a5a0f 70Lisp_Object Vinhibit_point_motion_hooks;
ad1b2f20 71Lisp_Object Vdefault_text_properties;
49d110a8 72Lisp_Object Vchar_property_alias_alist;
abc2f676 73Lisp_Object Vtext_property_default_nonsticky;
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;
7cb66899 79
2381d38d
DN
80static void text_read_only P_ ((Lisp_Object)) NO_RETURN;
81
7cb66899
GM
82
83/* Signal a `text-read-only' error. This function makes it easier
84 to capture that error in GDB by putting a breakpoint on it. */
85
86static void
bcf97349
SM
87text_read_only (propval)
88 Lisp_Object propval;
7cb66899 89{
d0a29e1d
KS
90 if (STRINGP (propval))
91 xsignal1 (Qtext_read_only, propval);
92
93 xsignal0 (Qtext_read_only);
7cb66899
GM
94}
95
96
d418ef42 97\f
ac876a79
JA
98/* Extract the interval at the position pointed to by BEGIN from
99 OBJECT, a string or buffer. Additionally, check that the positions
100 pointed to by BEGIN and END are within the bounds of OBJECT, and
101 reverse them if *BEGIN is greater than *END. The objects pointed
102 to by BEGIN and END may be integers or markers; if the latter, they
103 are coerced to integers.
d418ef42 104
d4b530ad
RS
105 When OBJECT is a string, we increment *BEGIN and *END
106 to make them origin-one.
107
d418ef42
JA
108 Note that buffer points don't correspond to interval indices.
109 For example, point-max is 1 greater than the index of the last
110 character. This difference is handled in the caller, which uses
111 the validated points to determine a length, and operates on that.
112 Exceptions are Ftext_properties_at, Fnext_property_change, and
113 Fprevious_property_change which call this function with BEGIN == END.
114 Handle this case specially.
115
116 If FORCE is soft (0), it's OK to return NULL_INTERVAL. Otherwise,
ac876a79
JA
117 create an interval tree for OBJECT if one doesn't exist, provided
118 the object actually contains text. In the current design, if there
d4b530ad 119 is no text, there can be no text properties. */
d418ef42
JA
120
121#define soft 0
122#define hard 1
123
9dd7eec6 124INTERVAL
d418ef42
JA
125validate_interval_range (object, begin, end, force)
126 Lisp_Object object, *begin, *end;
127 int force;
128{
129 register INTERVAL i;
d4b530ad
RS
130 int searchpos;
131
b7826503
PJ
132 CHECK_STRING_OR_BUFFER (object);
133 CHECK_NUMBER_COERCE_MARKER (*begin);
134 CHECK_NUMBER_COERCE_MARKER (*end);
d418ef42
JA
135
136 /* If we are asked for a point, but from a subr which operates
cdf3e5a2 137 on a range, then return nothing. */
64a49ca7 138 if (EQ (*begin, *end) && begin != end)
d418ef42
JA
139 return NULL_INTERVAL;
140
141 if (XINT (*begin) > XINT (*end))
142 {
d4b530ad
RS
143 Lisp_Object n;
144 n = *begin;
d418ef42 145 *begin = *end;
d4b530ad 146 *end = n;
d418ef42
JA
147 }
148
5d2fa46f 149 if (BUFFERP (object))
d418ef42
JA
150 {
151 register struct buffer *b = XBUFFER (object);
152
d418ef42
JA
153 if (!(BUF_BEGV (b) <= XINT (*begin) && XINT (*begin) <= XINT (*end)
154 && XINT (*end) <= BUF_ZV (b)))
155 args_out_of_range (*begin, *end);
866bf246 156 i = BUF_INTERVALS (b);
d418ef42 157
cdf3e5a2 158 /* If there's no text, there are no properties. */
d4b530ad
RS
159 if (BUF_BEGV (b) == BUF_ZV (b))
160 return NULL_INTERVAL;
161
162 searchpos = XINT (*begin);
d418ef42
JA
163 }
164 else
165 {
943afcc7 166 int len = SCHARS (object);
d418ef42 167
d4b530ad 168 if (! (0 <= XINT (*begin) && XINT (*begin) <= XINT (*end)
943afcc7 169 && XINT (*end) <= len))
d418ef42 170 args_out_of_range (*begin, *end);
ad077db0 171 XSETFASTINT (*begin, XFASTINT (*begin));
b1e94638 172 if (begin != end)
ad077db0 173 XSETFASTINT (*end, XFASTINT (*end));
943afcc7 174 i = STRING_INTERVALS (object);
d4b530ad 175
943afcc7 176 if (len == 0)
d4b530ad
RS
177 return NULL_INTERVAL;
178
179 searchpos = XINT (*begin);
d418ef42
JA
180 }
181
182 if (NULL_INTERVAL_P (i))
183 return (force ? create_root_interval (object) : i);
81f6e55f 184
d4b530ad 185 return find_interval (i, searchpos);
d418ef42
JA
186}
187
188/* Validate LIST as a property list. If LIST is not a list, then
189 make one consisting of (LIST nil). Otherwise, verify that LIST
cdf3e5a2 190 is even numbered and thus suitable as a plist. */
d418ef42
JA
191
192static Lisp_Object
193validate_plist (list)
4d780c76 194 Lisp_Object list;
d418ef42
JA
195{
196 if (NILP (list))
197 return Qnil;
198
199 if (CONSP (list))
200 {
201 register int i;
202 register Lisp_Object tail;
99784d63 203 for (i = 0, tail = list; CONSP (tail); i++)
b1e94638 204 {
99784d63 205 tail = XCDR (tail);
b1e94638
JB
206 QUIT;
207 }
d418ef42
JA
208 if (i & 1)
209 error ("Odd length text property list");
210 return list;
211 }
212
213 return Fcons (list, Fcons (Qnil, Qnil));
214}
215
d418ef42 216/* Return nonzero if interval I has all the properties,
cdf3e5a2 217 with the same values, of list PLIST. */
d418ef42
JA
218
219static int
220interval_has_all_properties (plist, i)
221 Lisp_Object plist;
222 INTERVAL i;
223{
695f302f 224 register Lisp_Object tail1, tail2, sym1;
d418ef42
JA
225 register int found;
226
cdf3e5a2 227 /* Go through each element of PLIST. */
99784d63 228 for (tail1 = plist; CONSP (tail1); tail1 = Fcdr (XCDR (tail1)))
d418ef42 229 {
99784d63 230 sym1 = XCAR (tail1);
d418ef42
JA
231 found = 0;
232
233 /* Go through I's plist, looking for sym1 */
99784d63
SM
234 for (tail2 = i->plist; CONSP (tail2); tail2 = Fcdr (XCDR (tail2)))
235 if (EQ (sym1, XCAR (tail2)))
d418ef42
JA
236 {
237 /* Found the same property on both lists. If the
cdf3e5a2 238 values are unequal, return zero. */
99784d63 239 if (! EQ (Fcar (XCDR (tail1)), Fcar (XCDR (tail2))))
d418ef42
JA
240 return 0;
241
cdf3e5a2 242 /* Property has same value on both lists; go to next one. */
d418ef42
JA
243 found = 1;
244 break;
245 }
246
247 if (! found)
248 return 0;
249 }
250
251 return 1;
252}
253
254/* Return nonzero if the plist of interval I has any of the
cdf3e5a2 255 properties of PLIST, regardless of their values. */
d418ef42
JA
256
257static INLINE int
258interval_has_some_properties (plist, i)
259 Lisp_Object plist;
260 INTERVAL i;
261{
262 register Lisp_Object tail1, tail2, sym;
263
cdf3e5a2 264 /* Go through each element of PLIST. */
99784d63 265 for (tail1 = plist; CONSP (tail1); tail1 = Fcdr (XCDR (tail1)))
d418ef42 266 {
99784d63 267 sym = XCAR (tail1);
d418ef42
JA
268
269 /* Go through i's plist, looking for tail1 */
99784d63
SM
270 for (tail2 = i->plist; CONSP (tail2); tail2 = Fcdr (XCDR (tail2)))
271 if (EQ (sym, XCAR (tail2)))
d418ef42
JA
272 return 1;
273 }
274
275 return 0;
276}
11713b6d
RS
277
278/* Return nonzero if the plist of interval I has any of the
279 property names in LIST, regardless of their values. */
280
281static INLINE int
282interval_has_some_properties_list (list, i)
283 Lisp_Object list;
284 INTERVAL i;
285{
286 register Lisp_Object tail1, tail2, sym;
287
288 /* Go through each element of LIST. */
99784d63 289 for (tail1 = list; CONSP (tail1); tail1 = XCDR (tail1))
11713b6d
RS
290 {
291 sym = Fcar (tail1);
292
293 /* Go through i's plist, looking for tail1 */
99784d63 294 for (tail2 = i->plist; CONSP (tail2); tail2 = XCDR (XCDR (tail2)))
11713b6d
RS
295 if (EQ (sym, XCAR (tail2)))
296 return 1;
297 }
298
299 return 0;
300}
d4b530ad 301\f
d7b4e137
JB
302/* Changing the plists of individual intervals. */
303
304/* Return the value of PROP in property-list PLIST, or Qunbound if it
305 has none. */
64a49ca7 306static Lisp_Object
d7b4e137 307property_value (plist, prop)
33ca3504 308 Lisp_Object plist, prop;
d7b4e137
JB
309{
310 Lisp_Object value;
311
312 while (PLIST_ELT_P (plist, value))
70949dac
KR
313 if (EQ (XCAR (plist), prop))
314 return XCAR (value);
d7b4e137 315 else
70949dac 316 plist = XCDR (value);
d7b4e137
JB
317
318 return Qunbound;
319}
320
d4b530ad
RS
321/* Set the properties of INTERVAL to PROPERTIES,
322 and record undo info for the previous values.
323 OBJECT is the string or buffer that INTERVAL belongs to. */
324
325static void
326set_properties (properties, interval, object)
327 Lisp_Object properties, object;
328 INTERVAL interval;
329{
d7b4e137 330 Lisp_Object sym, value;
d4b530ad 331
d7b4e137 332 if (BUFFERP (object))
d4b530ad 333 {
d7b4e137
JB
334 /* For each property in the old plist which is missing from PROPERTIES,
335 or has a different value in PROPERTIES, make an undo record. */
336 for (sym = interval->plist;
337 PLIST_ELT_P (sym, value);
70949dac
KR
338 sym = XCDR (value))
339 if (! EQ (property_value (properties, XCAR (sym)),
340 XCAR (value)))
f7a9275a 341 {
f7a9275a 342 record_property_change (interval->position, LENGTH (interval),
70949dac 343 XCAR (sym), XCAR (value),
f7a9275a
RS
344 object);
345 }
d7b4e137
JB
346
347 /* For each new property that has no value at all in the old plist,
348 make an undo record binding it to nil, so it will be removed. */
349 for (sym = properties;
350 PLIST_ELT_P (sym, value);
70949dac
KR
351 sym = XCDR (value))
352 if (EQ (property_value (interval->plist, XCAR (sym)), Qunbound))
f7a9275a 353 {
f7a9275a 354 record_property_change (interval->position, LENGTH (interval),
70949dac 355 XCAR (sym), Qnil,
f7a9275a
RS
356 object);
357 }
d4b530ad
RS
358 }
359
360 /* Store new properties. */
361 interval->plist = Fcopy_sequence (properties);
362}
d418ef42
JA
363
364/* Add the properties of PLIST to the interval I, or set
365 the value of I's property to the value of the property on PLIST
366 if they are different.
367
d4b530ad
RS
368 OBJECT should be the string or buffer the interval is in.
369
d418ef42
JA
370 Return nonzero if this changes I (i.e., if any members of PLIST
371 are actually added to I's plist) */
372
d4b530ad
RS
373static int
374add_properties (plist, i, object)
d418ef42
JA
375 Lisp_Object plist;
376 INTERVAL i;
d4b530ad 377 Lisp_Object object;
d418ef42 378{
c98da214 379 Lisp_Object tail1, tail2, sym1, val1;
d418ef42
JA
380 register int changed = 0;
381 register int found;
c98da214
RS
382 struct gcpro gcpro1, gcpro2, gcpro3;
383
384 tail1 = plist;
385 sym1 = Qnil;
386 val1 = Qnil;
387 /* No need to protect OBJECT, because we can GC only in the case
388 where it is a buffer, and live buffers are always protected.
389 I and its plist are also protected, via OBJECT. */
390 GCPRO3 (tail1, sym1, val1);
d418ef42 391
cdf3e5a2 392 /* Go through each element of PLIST. */
99784d63 393 for (tail1 = plist; CONSP (tail1); tail1 = Fcdr (XCDR (tail1)))
d418ef42 394 {
99784d63
SM
395 sym1 = XCAR (tail1);
396 val1 = Fcar (XCDR (tail1));
d418ef42
JA
397 found = 0;
398
399 /* Go through I's plist, looking for sym1 */
99784d63
SM
400 for (tail2 = i->plist; CONSP (tail2); tail2 = Fcdr (XCDR (tail2)))
401 if (EQ (sym1, XCAR (tail2)))
d418ef42 402 {
c98da214
RS
403 /* No need to gcpro, because tail2 protects this
404 and it must be a cons cell (we get an error otherwise). */
3814ccf5 405 register Lisp_Object this_cdr;
d418ef42 406
99784d63 407 this_cdr = XCDR (tail2);
cdf3e5a2 408 /* Found the property. Now check its value. */
d418ef42
JA
409 found = 1;
410
411 /* The properties have the same value on both lists.
cdf3e5a2 412 Continue to the next property. */
734c51b2 413 if (EQ (val1, Fcar (this_cdr)))
d418ef42
JA
414 break;
415
d4b530ad 416 /* Record this change in the buffer, for undo purposes. */
5d2fa46f 417 if (BUFFERP (object))
d4b530ad 418 {
f7a9275a
RS
419 record_property_change (i->position, LENGTH (i),
420 sym1, Fcar (this_cdr), object);
d4b530ad
RS
421 }
422
d418ef42
JA
423 /* I's property has a different value -- change it */
424 Fsetcar (this_cdr, val1);
425 changed++;
426 break;
427 }
428
429 if (! found)
430 {
d4b530ad 431 /* Record this change in the buffer, for undo purposes. */
5d2fa46f 432 if (BUFFERP (object))
d4b530ad 433 {
f7a9275a
RS
434 record_property_change (i->position, LENGTH (i),
435 sym1, Qnil, object);
d4b530ad 436 }
d418ef42
JA
437 i->plist = Fcons (sym1, Fcons (val1, i->plist));
438 changed++;
439 }
440 }
441
c98da214
RS
442 UNGCPRO;
443
d418ef42
JA
444 return changed;
445}
446
11713b6d
RS
447/* For any members of PLIST, or LIST,
448 which are properties of I, remove them from I's plist.
449 (If PLIST is non-nil, use that, otherwise use LIST.)
d4b530ad 450 OBJECT is the string or buffer containing I. */
d418ef42 451
d4b530ad 452static int
11713b6d
RS
453remove_properties (plist, list, i, object)
454 Lisp_Object plist, list;
d418ef42 455 INTERVAL i;
d4b530ad 456 Lisp_Object object;
d418ef42 457{
3814ccf5 458 register Lisp_Object tail1, tail2, sym, current_plist;
d418ef42
JA
459 register int changed = 0;
460
f25d60d6
KS
461 /* Nonzero means tail1 is a plist, otherwise it is a list. */
462 int use_plist;
11713b6d 463
3814ccf5 464 current_plist = i->plist;
11713b6d
RS
465
466 if (! NILP (plist))
f25d60d6 467 tail1 = plist, use_plist = 1;
11713b6d 468 else
f25d60d6 469 tail1 = list, use_plist = 0;
11713b6d
RS
470
471 /* Go through each element of LIST or PLIST. */
b079118d 472 while (CONSP (tail1))
d418ef42 473 {
11713b6d 474 sym = XCAR (tail1);
d418ef42 475
11713b6d 476 /* First, remove the symbol if it's at the head of the list */
b079118d 477 while (CONSP (current_plist) && EQ (sym, XCAR (current_plist)))
d418ef42 478 {
5d2fa46f 479 if (BUFFERP (object))
11713b6d
RS
480 record_property_change (i->position, LENGTH (i),
481 sym, XCAR (XCDR (current_plist)),
482 object);
d4b530ad 483
11713b6d 484 current_plist = XCDR (XCDR (current_plist));
d418ef42
JA
485 changed++;
486 }
487
11713b6d 488 /* Go through I's plist, looking for SYM. */
d418ef42
JA
489 tail2 = current_plist;
490 while (! NILP (tail2))
491 {
3814ccf5 492 register Lisp_Object this;
11713b6d 493 this = XCDR (XCDR (tail2));
b079118d 494 if (CONSP (this) && EQ (sym, XCAR (this)))
d418ef42 495 {
5d2fa46f 496 if (BUFFERP (object))
11713b6d
RS
497 record_property_change (i->position, LENGTH (i),
498 sym, XCAR (XCDR (this)), object);
d4b530ad 499
11713b6d 500 Fsetcdr (XCDR (tail2), XCDR (XCDR (this)));
d418ef42
JA
501 changed++;
502 }
503 tail2 = this;
504 }
11713b6d
RS
505
506 /* Advance thru TAIL1 one way or the other. */
f25d60d6
KS
507 tail1 = XCDR (tail1);
508 if (use_plist && CONSP (tail1))
11713b6d 509 tail1 = XCDR (tail1);
d418ef42
JA
510 }
511
512 if (changed)
513 i->plist = current_plist;
514 return changed;
515}
516
d4b530ad 517#if 0
d418ef42 518/* Remove all properties from interval I. Return non-zero
cdf3e5a2 519 if this changes the interval. */
d418ef42
JA
520
521static INLINE int
522erase_properties (i)
523 INTERVAL i;
524{
525 if (NILP (i->plist))
526 return 0;
527
528 i->plist = Qnil;
529 return 1;
530}
d4b530ad 531#endif
d418ef42 532\f
81f6e55f 533/* Returns the interval of POSITION in OBJECT.
cdf3e5a2
RS
534 POSITION is BEG-based. */
535
536INTERVAL
537interval_of (position, object)
538 int position;
539 Lisp_Object object;
540{
541 register INTERVAL i;
542 int beg, end;
543
544 if (NILP (object))
545 XSETBUFFER (object, current_buffer);
d0cb872a
KH
546 else if (EQ (object, Qt))
547 return NULL_INTERVAL;
cdf3e5a2 548
b7826503 549 CHECK_STRING_OR_BUFFER (object);
cdf3e5a2
RS
550
551 if (BUFFERP (object))
552 {
553 register struct buffer *b = XBUFFER (object);
554
555 beg = BUF_BEGV (b);
556 end = BUF_ZV (b);
557 i = BUF_INTERVALS (b);
558 }
559 else
560 {
ad077db0 561 beg = 0;
943afcc7
KR
562 end = SCHARS (object);
563 i = STRING_INTERVALS (object);
cdf3e5a2
RS
564 }
565
566 if (!(beg <= position && position <= end))
c7ef8e24 567 args_out_of_range (make_number (position), make_number (position));
cdf3e5a2
RS
568 if (beg == end || NULL_INTERVAL_P (i))
569 return NULL_INTERVAL;
81f6e55f 570
cdf3e5a2
RS
571 return find_interval (i, position);
572}
573\f
d418ef42
JA
574DEFUN ("text-properties-at", Ftext_properties_at,
575 Stext_properties_at, 1, 2, 0,
8c1a1077 576 doc: /* Return the list of properties of the character at POSITION in OBJECT.
81f6e55f
FP
577If the optional second argument OBJECT is a buffer (or nil, which means
578the current buffer), POSITION is a buffer position (integer or marker).
579If OBJECT is a string, POSITION is a 0-based index into it.
8c1a1077
PJ
580If POSITION is at the end of OBJECT, the value is nil. */)
581 (position, object)
1f5e848a 582 Lisp_Object position, object;
d418ef42
JA
583{
584 register INTERVAL i;
d418ef42
JA
585
586 if (NILP (object))
c8a4fc3d 587 XSETBUFFER (object, current_buffer);
d418ef42 588
1f5e848a 589 i = validate_interval_range (object, &position, &position, soft);
d418ef42
JA
590 if (NULL_INTERVAL_P (i))
591 return Qnil;
1f5e848a 592 /* If POSITION is at the end of the interval,
d4b530ad
RS
593 it means it's the end of OBJECT.
594 There are no properties at the very end,
595 since no character follows. */
1f5e848a 596 if (XINT (position) == LENGTH (i) + i->position)
d4b530ad 597 return Qnil;
d418ef42
JA
598
599 return i->plist;
600}
601
5fbe2a44 602DEFUN ("get-text-property", Fget_text_property, Sget_text_property, 2, 3, 0,
8c1a1077
PJ
603 doc: /* Return the value of POSITION's property PROP, in OBJECT.
604OBJECT is optional and defaults to the current buffer.
605If POSITION is at the end of OBJECT, the value is nil. */)
606 (position, prop, object)
1f5e848a 607 Lisp_Object position, object;
46bb7c2b 608 Lisp_Object prop;
5fbe2a44 609{
1f5e848a 610 return textget (Ftext_properties_at (position, object), prop);
5fbe2a44
RS
611}
612
bcf97349 613/* Return the value of char's property PROP, in OBJECT at POSITION.
8d41abc4
MB
614 OBJECT is optional and defaults to the current buffer.
615 If OVERLAY is non-0, then in the case that the returned property is from
616 an overlay, the overlay found is returned in *OVERLAY, otherwise nil is
617 returned in *OVERLAY.
618 If POSITION is at the end of OBJECT, the value is nil.
619 If OBJECT is a buffer, then overlay properties are considered as well as
620 text properties.
621 If OBJECT is a window, then that window's buffer is used, but
622 window-specific overlays are considered only if they are associated
623 with OBJECT. */
624Lisp_Object
625get_char_property_and_overlay (position, prop, object, overlay)
1f5e848a 626 Lisp_Object position, object;
f5957179 627 register Lisp_Object prop;
8d41abc4 628 Lisp_Object *overlay;
f5957179
KH
629{
630 struct window *w = 0;
631
b7826503 632 CHECK_NUMBER_COERCE_MARKER (position);
f5957179
KH
633
634 if (NILP (object))
c8a4fc3d 635 XSETBUFFER (object, current_buffer);
f5957179
KH
636
637 if (WINDOWP (object))
638 {
639 w = XWINDOW (object);
64a49ca7 640 object = w->buffer;
f5957179
KH
641 }
642 if (BUFFERP (object))
643 {
f5957179 644 int noverlays;
b5be4dbe 645 Lisp_Object *overlay_vec;
cbc55f55
RS
646 struct buffer *obuf = current_buffer;
647
dd6f2802
RS
648 if (XINT (position) < BUF_BEGV (XBUFFER (object))
649 || XINT (position) > BUF_ZV (XBUFFER (object)))
650 xsignal1 (Qargs_out_of_range, position);
651
cbc55f55 652 set_buffer_temp (XBUFFER (object));
f5957179 653
b5be4dbe 654 GET_OVERLAYS_AT (XINT (position), overlay_vec, noverlays, NULL, 0);
f5957179
KH
655 noverlays = sort_overlays (overlay_vec, noverlays, w);
656
cbc55f55
RS
657 set_buffer_temp (obuf);
658
f5957179
KH
659 /* Now check the overlays in order of decreasing priority. */
660 while (--noverlays >= 0)
661 {
b5be4dbe 662 Lisp_Object tem = Foverlay_get (overlay_vec[noverlays], prop);
f5957179 663 if (!NILP (tem))
8d41abc4
MB
664 {
665 if (overlay)
666 /* Return the overlay we got the property from. */
667 *overlay = overlay_vec[noverlays];
668 return tem;
669 }
f5957179
KH
670 }
671 }
8d41abc4
MB
672
673 if (overlay)
674 /* Indicate that the return value is not from an overlay. */
675 *overlay = Qnil;
676
f5957179
KH
677 /* Not a buffer, or no appropriate overlay, so fall through to the
678 simpler case. */
8d41abc4
MB
679 return Fget_text_property (position, prop, object);
680}
681
682DEFUN ("get-char-property", Fget_char_property, Sget_char_property, 2, 3, 0,
8c1a1077 683 doc: /* Return the value of POSITION's property PROP, in OBJECT.
8faef085 684Both overlay properties and text properties are checked.
8c1a1077
PJ
685OBJECT is optional and defaults to the current buffer.
686If POSITION is at the end of OBJECT, the value is nil.
687If OBJECT is a buffer, then overlay properties are considered as well as
688text properties.
689If OBJECT is a window, then that window's buffer is used, but window-specific
690overlays are considered only if they are associated with OBJECT. */)
691 (position, prop, object)
8d41abc4
MB
692 Lisp_Object position, object;
693 register Lisp_Object prop;
694{
695 return get_char_property_and_overlay (position, prop, object, 0);
f5957179 696}
97a1bc63
LT
697
698DEFUN ("get-char-property-and-overlay", Fget_char_property_and_overlay,
699 Sget_char_property_and_overlay, 2, 3, 0,
700 doc: /* Like `get-char-property', but with extra overlay information.
95555145
RS
701The value is a cons cell. Its car is the return value of `get-char-property'
702with the same arguments--that is, the value of POSITION's property
703PROP in OBJECT. Its cdr is the overlay in which the property was
97a1bc63 704found, or nil, if it was found as a text property or not found at all.
95555145 705
97a1bc63
LT
706OBJECT is optional and defaults to the current buffer. OBJECT may be
707a string, a buffer or a window. For strings, the cdr of the return
708value is always nil, since strings do not have overlays. If OBJECT is
709a window, then that window's buffer is used, but window-specific
710overlays are considered only if they are associated with OBJECT. If
711POSITION is at the end of OBJECT, both car and cdr are nil. */)
712 (position, prop, object)
713 Lisp_Object position, object;
714 register Lisp_Object prop;
715{
716 Lisp_Object overlay;
717 Lisp_Object val
718 = get_char_property_and_overlay (position, prop, object, &overlay);
6519d955 719 return Fcons (val, overlay);
97a1bc63
LT
720}
721
fcab51aa
RS
722\f
723DEFUN ("next-char-property-change", Fnext_char_property_change,
724 Snext_char_property_change, 1, 2, 0,
8c1a1077 725 doc: /* Return the position of next text property or overlay change.
81f6e55f
FP
726This scans characters forward in the current buffer from POSITION till
727it finds a change in some text property, or the beginning or end of an
728overlay, and returns the position of that.
85cc6738 729If none is found up to (point-max), the function returns (point-max).
8c1a1077 730
a41292c2 731If the optional second argument LIMIT is non-nil, don't search
85cc6738
RS
732past position LIMIT; return LIMIT if nothing is found before LIMIT.
733LIMIT is a no-op if it is greater than (point-max). */)
8c1a1077 734 (position, limit)
fcab51aa
RS
735 Lisp_Object position, limit;
736{
737 Lisp_Object temp;
738
739 temp = Fnext_overlay_change (position);
740 if (! NILP (limit))
741 {
d615870a 742 CHECK_NUMBER_COERCE_MARKER (limit);
fcab51aa
RS
743 if (XINT (limit) < XINT (temp))
744 temp = limit;
745 }
746 return Fnext_property_change (position, Qnil, temp);
747}
748
749DEFUN ("previous-char-property-change", Fprevious_char_property_change,
750 Sprevious_char_property_change, 1, 2, 0,
8c1a1077 751 doc: /* Return the position of previous text property or overlay change.
81f6e55f
FP
752Scans characters backward in the current buffer from POSITION till it
753finds a change in some text property, or the beginning or end of an
754overlay, and returns the position of that.
85cc6738 755If none is found since (point-min), the function returns (point-min).
8c1a1077 756
a41292c2 757If the optional second argument LIMIT is non-nil, don't search
85cc6738
RS
758past position LIMIT; return LIMIT if nothing is found before LIMIT.
759LIMIT is a no-op if it is less than (point-min). */)
8c1a1077 760 (position, limit)
fcab51aa
RS
761 Lisp_Object position, limit;
762{
763 Lisp_Object temp;
f5957179 764
fcab51aa
RS
765 temp = Fprevious_overlay_change (position);
766 if (! NILP (limit))
767 {
d615870a 768 CHECK_NUMBER_COERCE_MARKER (limit);
fcab51aa
RS
769 if (XINT (limit) > XINT (temp))
770 temp = limit;
771 }
772 return Fprevious_property_change (position, Qnil, temp);
773}
0b0737d1
GM
774
775
b7e047fb
MB
776DEFUN ("next-single-char-property-change", Fnext_single_char_property_change,
777 Snext_single_char_property_change, 2, 4, 0,
8c1a1077
PJ
778 doc: /* Return the position of next text property or overlay change for a specific property.
779Scans characters forward from POSITION till it finds
780a change in the PROP property, then returns the position of the change.
81f6e55f
FP
781If the optional third argument OBJECT is a buffer (or nil, which means
782the current buffer), POSITION is a buffer position (integer or marker).
783If OBJECT is a string, POSITION is a 0-based index into it.
784
85cc6738
RS
785In a string, scan runs to the end of the string.
786In a buffer, it runs to (point-max), and the value cannot exceed that.
787
8c1a1077
PJ
788The property values are compared with `eq'.
789If the property is constant all the way to the end of OBJECT, return the
790last valid position in OBJECT.
791If the optional fourth argument LIMIT is non-nil, don't search
792past position LIMIT; return LIMIT if nothing is found before LIMIT. */)
793 (position, prop, object, limit)
b7e047fb 794 Lisp_Object prop, position, object, limit;
0b0737d1
GM
795{
796 if (STRINGP (object))
797 {
b7e047fb
MB
798 position = Fnext_single_property_change (position, prop, object, limit);
799 if (NILP (position))
0b0737d1
GM
800 {
801 if (NILP (limit))
d5db4077 802 position = make_number (SCHARS (object));
0b0737d1 803 else
d615870a
DK
804 {
805 CHECK_NUMBER (limit);
806 position = limit;
807 }
0b0737d1
GM
808 }
809 }
810 else
811 {
812 Lisp_Object initial_value, value;
aed13378 813 int count = SPECPDL_INDEX ();
0b0737d1 814
b7e047fb 815 if (! NILP (object))
b7826503 816 CHECK_BUFFER (object);
81f6e55f 817
0b0737d1
GM
818 if (BUFFERP (object) && current_buffer != XBUFFER (object))
819 {
820 record_unwind_protect (Fset_buffer, Fcurrent_buffer ());
821 Fset_buffer (object);
822 }
823
d615870a
DK
824 CHECK_NUMBER_COERCE_MARKER (position);
825
b7e047fb 826 initial_value = Fget_char_property (position, prop, object);
81f6e55f 827
b7e047fb 828 if (NILP (limit))
85cc6738 829 XSETFASTINT (limit, ZV);
b7e047fb 830 else
b7826503 831 CHECK_NUMBER_COERCE_MARKER (limit);
b7e047fb 832
85cc6738 833 if (XFASTINT (position) >= XFASTINT (limit))
0b0737d1 834 {
85cc6738
RS
835 position = limit;
836 if (XFASTINT (position) > ZV)
837 XSETFASTINT (position, ZV);
0b0737d1 838 }
85cc6738
RS
839 else
840 while (1)
841 {
842 position = Fnext_char_property_change (position, limit);
843 if (XFASTINT (position) >= XFASTINT (limit))
844 {
845 position = limit;
846 break;
847 }
848
849 value = Fget_char_property (position, prop, object);
850 if (!EQ (value, initial_value))
851 break;
852 }
0b0737d1
GM
853
854 unbind_to (count, Qnil);
855 }
856
b7e047fb 857 return position;
0b0737d1
GM
858}
859
b7e047fb
MB
860DEFUN ("previous-single-char-property-change",
861 Fprevious_single_char_property_change,
862 Sprevious_single_char_property_change, 2, 4, 0,
8c1a1077
PJ
863 doc: /* Return the position of previous text property or overlay change for a specific property.
864Scans characters backward from POSITION till it finds
865a change in the PROP property, then returns the position of the change.
81f6e55f
FP
866If the optional third argument OBJECT is a buffer (or nil, which means
867the current buffer), POSITION is a buffer position (integer or marker).
868If OBJECT is a string, POSITION is a 0-based index into it.
869
85cc6738
RS
870In a string, scan runs to the start of the string.
871In a buffer, it runs to (point-min), and the value cannot be less than that.
872
8c1a1077
PJ
873The property values are compared with `eq'.
874If the property is constant all the way to the start of OBJECT, return the
875first valid position in OBJECT.
876If the optional fourth argument LIMIT is non-nil, don't search
877back past position LIMIT; return LIMIT if nothing is found before LIMIT. */)
878 (position, prop, object, limit)
b7e047fb
MB
879 Lisp_Object prop, position, object, limit;
880{
881 if (STRINGP (object))
882 {
883 position = Fprevious_single_property_change (position, prop, object, limit);
884 if (NILP (position))
885 {
886 if (NILP (limit))
1e02f3cb 887 position = make_number (0);
b7e047fb 888 else
d615870a
DK
889 {
890 CHECK_NUMBER (limit);
891 position = limit;
892 }
b7e047fb
MB
893 }
894 }
895 else
896 {
aed13378 897 int count = SPECPDL_INDEX ();
b7e047fb
MB
898
899 if (! NILP (object))
b7826503 900 CHECK_BUFFER (object);
81f6e55f 901
b7e047fb
MB
902 if (BUFFERP (object) && current_buffer != XBUFFER (object))
903 {
904 record_unwind_protect (Fset_buffer, Fcurrent_buffer ());
905 Fset_buffer (object);
906 }
81f6e55f 907
d615870a
DK
908 CHECK_NUMBER_COERCE_MARKER (position);
909
b7e047fb 910 if (NILP (limit))
85cc6738 911 XSETFASTINT (limit, BEGV);
b7e047fb 912 else
b7826503 913 CHECK_NUMBER_COERCE_MARKER (limit);
b7e047fb 914
ce6b02e0 915 if (XFASTINT (position) <= XFASTINT (limit))
85cc6738
RS
916 {
917 position = limit;
918 if (XFASTINT (position) < BEGV)
919 XSETFASTINT (position, BEGV);
920 }
ce6b02e0 921 else
b7e047fb 922 {
85cc6738
RS
923 Lisp_Object initial_value
924 = Fget_char_property (make_number (XFASTINT (position) - 1),
925 prop, object);
81f6e55f 926
85cc6738 927 while (1)
ce6b02e0
MB
928 {
929 position = Fprevious_char_property_change (position, limit);
0b0737d1 930
ce6b02e0
MB
931 if (XFASTINT (position) <= XFASTINT (limit))
932 {
933 position = limit;
934 break;
935 }
936 else
937 {
85cc6738
RS
938 Lisp_Object value
939 = Fget_char_property (make_number (XFASTINT (position) - 1),
940 prop, object);
ce6b02e0
MB
941
942 if (!EQ (value, initial_value))
943 break;
944 }
945 }
b7e047fb
MB
946 }
947
948 unbind_to (count, Qnil);
949 }
950
951 return position;
952}
fcab51aa 953\f
d418ef42 954DEFUN ("next-property-change", Fnext_property_change,
111b637d 955 Snext_property_change, 1, 3, 0,
8c1a1077
PJ
956 doc: /* Return the position of next property change.
957Scans characters forward from POSITION in OBJECT till it finds
958a change in some text property, then returns the position of the change.
81f6e55f
FP
959If the optional second argument OBJECT is a buffer (or nil, which means
960the current buffer), POSITION is a buffer position (integer or marker).
961If OBJECT is a string, POSITION is a 0-based index into it.
8c1a1077
PJ
962Return nil if the property is constant all the way to the end of OBJECT.
963If the value is non-nil, it is a position greater than POSITION, never equal.
964
965If the optional third argument LIMIT is non-nil, don't search
966past position LIMIT; return LIMIT if nothing is found before LIMIT. */)
967 (position, object, limit)
1f5e848a 968 Lisp_Object position, object, limit;
d418ef42
JA
969{
970 register INTERVAL i, next;
971
5fbe2a44 972 if (NILP (object))
c8a4fc3d 973 XSETBUFFER (object, current_buffer);
5fbe2a44 974
3a232704 975 if (!NILP (limit) && !EQ (limit, Qt))
b7826503 976 CHECK_NUMBER_COERCE_MARKER (limit);
1387d54e 977
1f5e848a 978 i = validate_interval_range (object, &position, &position, soft);
d418ef42 979
041aa96f
RS
980 /* If LIMIT is t, return start of next interval--don't
981 bother checking further intervals. */
982 if (EQ (limit, Qt))
983 {
44214c1b
RS
984 if (NULL_INTERVAL_P (i))
985 next = i;
986 else
987 next = next_interval (i);
81f6e55f 988
c7b6dfa6 989 if (NULL_INTERVAL_P (next))
1f5e848a 990 XSETFASTINT (position, (STRINGP (object)
d5db4077 991 ? SCHARS (object)
1f5e848a 992 : BUF_ZV (XBUFFER (object))));
c7b6dfa6 993 else
ad077db0 994 XSETFASTINT (position, next->position);
1f5e848a 995 return position;
041aa96f
RS
996 }
997
44214c1b
RS
998 if (NULL_INTERVAL_P (i))
999 return limit;
1000
1001 next = next_interval (i);
1002
3a232704 1003 while (!NULL_INTERVAL_P (next) && intervals_equal (i, next)
ad077db0 1004 && (NILP (limit) || next->position < XFASTINT (limit)))
d418ef42
JA
1005 next = next_interval (next);
1006
52c0f270
CY
1007 if (NULL_INTERVAL_P (next)
1008 || (next->position
1009 >= (INTEGERP (limit)
1010 ? XFASTINT (limit)
1011 : (STRINGP (object)
1012 ? SCHARS (object)
1013 : BUF_ZV (XBUFFER (object))))))
111b637d 1014 return limit;
52c0f270
CY
1015 else
1016 return make_number (next->position);
19e1c426
RS
1017}
1018
1019/* Return 1 if there's a change in some property between BEG and END. */
1020
1021int
1022property_change_between_p (beg, end)
1023 int beg, end;
1024{
1025 register INTERVAL i, next;
1026 Lisp_Object object, pos;
1027
c8a4fc3d 1028 XSETBUFFER (object, current_buffer);
e9c4fbcd 1029 XSETFASTINT (pos, beg);
19e1c426
RS
1030
1031 i = validate_interval_range (object, &pos, &pos, soft);
1032 if (NULL_INTERVAL_P (i))
1033 return 0;
1034
1035 next = next_interval (i);
1036 while (! NULL_INTERVAL_P (next) && intervals_equal (i, next))
1037 {
1038 next = next_interval (next);
e050ef74
RS
1039 if (NULL_INTERVAL_P (next))
1040 return 0;
ad077db0 1041 if (next->position >= end)
19e1c426
RS
1042 return 0;
1043 }
1044
1045 if (NULL_INTERVAL_P (next))
1046 return 0;
1047
1048 return 1;
d418ef42
JA
1049}
1050
9c79dd1b 1051DEFUN ("next-single-property-change", Fnext_single_property_change,
111b637d 1052 Snext_single_property_change, 2, 4, 0,
8c1a1077
PJ
1053 doc: /* Return the position of next property change for a specific property.
1054Scans characters forward from POSITION till it finds
1055a change in the PROP property, then returns the position of the change.
81f6e55f
FP
1056If the optional third argument OBJECT is a buffer (or nil, which means
1057the current buffer), POSITION is a buffer position (integer or marker).
1058If OBJECT is a string, POSITION is a 0-based index into it.
8c1a1077
PJ
1059The property values are compared with `eq'.
1060Return nil if the property is constant all the way to the end of OBJECT.
1061If the value is non-nil, it is a position greater than POSITION, never equal.
1062
1063If the optional fourth argument LIMIT is non-nil, don't search
1064past position LIMIT; return LIMIT if nothing is found before LIMIT. */)
1065 (position, prop, object, limit)
1f5e848a 1066 Lisp_Object position, prop, object, limit;
9c79dd1b
JA
1067{
1068 register INTERVAL i, next;
1069 register Lisp_Object here_val;
1070
5fbe2a44 1071 if (NILP (object))
c8a4fc3d 1072 XSETBUFFER (object, current_buffer);
5fbe2a44 1073
1387d54e 1074 if (!NILP (limit))
b7826503 1075 CHECK_NUMBER_COERCE_MARKER (limit);
1387d54e 1076
1f5e848a 1077 i = validate_interval_range (object, &position, &position, soft);
9c79dd1b 1078 if (NULL_INTERVAL_P (i))
111b637d 1079 return limit;
9c79dd1b 1080
6a0486dd 1081 here_val = textget (i->plist, prop);
9c79dd1b 1082 next = next_interval (i);
81f6e55f 1083 while (! NULL_INTERVAL_P (next)
111b637d 1084 && EQ (here_val, textget (next->plist, prop))
ad077db0 1085 && (NILP (limit) || next->position < XFASTINT (limit)))
9c79dd1b
JA
1086 next = next_interval (next);
1087
52c0f270
CY
1088 if (NULL_INTERVAL_P (next)
1089 || (next->position
1090 >= (INTEGERP (limit)
1091 ? XFASTINT (limit)
1092 : (STRINGP (object)
1093 ? SCHARS (object)
1094 : BUF_ZV (XBUFFER (object))))))
111b637d 1095 return limit;
52c0f270
CY
1096 else
1097 return make_number (next->position);
9c79dd1b
JA
1098}
1099
d418ef42 1100DEFUN ("previous-property-change", Fprevious_property_change,
111b637d 1101 Sprevious_property_change, 1, 3, 0,
8c1a1077
PJ
1102 doc: /* Return the position of previous property change.
1103Scans characters backwards from POSITION in OBJECT till it finds
1104a change in some text property, then returns the position of the change.
81f6e55f
FP
1105If the optional second argument OBJECT is a buffer (or nil, which means
1106the current buffer), POSITION is a buffer position (integer or marker).
1107If OBJECT is a string, POSITION is a 0-based index into it.
8c1a1077
PJ
1108Return nil if the property is constant all the way to the start of OBJECT.
1109If the value is non-nil, it is a position less than POSITION, never equal.
1110
1111If the optional third argument LIMIT is non-nil, don't search
1112back past position LIMIT; return LIMIT if nothing is found until LIMIT. */)
1113 (position, object, limit)
1f5e848a 1114 Lisp_Object position, object, limit;
d418ef42
JA
1115{
1116 register INTERVAL i, previous;
1117
5fbe2a44 1118 if (NILP (object))
c8a4fc3d 1119 XSETBUFFER (object, current_buffer);
5fbe2a44 1120
1387d54e 1121 if (!NILP (limit))
b7826503 1122 CHECK_NUMBER_COERCE_MARKER (limit);
1387d54e 1123
1f5e848a 1124 i = validate_interval_range (object, &position, &position, soft);
d418ef42 1125 if (NULL_INTERVAL_P (i))
111b637d 1126 return limit;
d418ef42 1127
53b7feec 1128 /* Start with the interval containing the char before point. */
1f5e848a 1129 if (i->position == XFASTINT (position))
53b7feec
RS
1130 i = previous_interval (i);
1131
d418ef42 1132 previous = previous_interval (i);
3a232704 1133 while (!NULL_INTERVAL_P (previous) && intervals_equal (previous, i)
111b637d 1134 && (NILP (limit)
ad077db0 1135 || (previous->position + LENGTH (previous) > XFASTINT (limit))))
d418ef42 1136 previous = previous_interval (previous);
d418ef42 1137
52c0f270
CY
1138 if (NULL_INTERVAL_P (previous)
1139 || (previous->position + LENGTH (previous)
1140 <= (INTEGERP (limit)
1141 ? XFASTINT (limit)
1142 : (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
1143 return limit;
1144 else
1145 return make_number (previous->position + LENGTH (previous));
d418ef42
JA
1146}
1147
9c79dd1b 1148DEFUN ("previous-single-property-change", Fprevious_single_property_change,
111b637d 1149 Sprevious_single_property_change, 2, 4, 0,
8c1a1077
PJ
1150 doc: /* Return the position of previous property change for a specific property.
1151Scans characters backward from POSITION till it finds
1152a change in the PROP property, then returns the position of the change.
81f6e55f
FP
1153If the optional third argument OBJECT is a buffer (or nil, which means
1154the current buffer), POSITION is a buffer position (integer or marker).
1155If OBJECT is a string, POSITION is a 0-based index into it.
8c1a1077
PJ
1156The property values are compared with `eq'.
1157Return nil if the property is constant all the way to the start of OBJECT.
1158If the value is non-nil, it is a position less than POSITION, never equal.
1159
1160If the optional fourth argument LIMIT is non-nil, don't search
1161back past position LIMIT; return LIMIT if nothing is found until LIMIT. */)
1f5e848a
EN
1162 (position, prop, object, limit)
1163 Lisp_Object position, prop, object, limit;
9c79dd1b
JA
1164{
1165 register INTERVAL i, previous;
1166 register Lisp_Object here_val;
1167
5fbe2a44 1168 if (NILP (object))
c8a4fc3d 1169 XSETBUFFER (object, current_buffer);
5fbe2a44 1170
1387d54e 1171 if (!NILP (limit))
b7826503 1172 CHECK_NUMBER_COERCE_MARKER (limit);
1387d54e 1173
1f5e848a 1174 i = validate_interval_range (object, &position, &position, soft);
9c79dd1b 1175
53b7feec 1176 /* Start with the interval containing the char before point. */
3a232704 1177 if (!NULL_INTERVAL_P (i) && i->position == XFASTINT (position))
53b7feec
RS
1178 i = previous_interval (i);
1179
6873cfa3
KH
1180 if (NULL_INTERVAL_P (i))
1181 return limit;
1182
6a0486dd 1183 here_val = textget (i->plist, prop);
9c79dd1b 1184 previous = previous_interval (i);
3a232704 1185 while (!NULL_INTERVAL_P (previous)
111b637d
RS
1186 && EQ (here_val, textget (previous->plist, prop))
1187 && (NILP (limit)
ad077db0 1188 || (previous->position + LENGTH (previous) > XFASTINT (limit))))
9c79dd1b 1189 previous = previous_interval (previous);
9c79dd1b 1190
52c0f270
CY
1191 if (NULL_INTERVAL_P (previous)
1192 || (previous->position + LENGTH (previous)
1193 <= (INTEGERP (limit)
1194 ? XFASTINT (limit)
1195 : (STRINGP (object) ? 0 : BUF_BEGV (XBUFFER (object))))))
1196 return limit;
1197 else
1198 return make_number (previous->position + LENGTH (previous));
9c79dd1b 1199}
fcab51aa 1200\f
c98da214
RS
1201/* Callers note, this can GC when OBJECT is a buffer (or nil). */
1202
d418ef42 1203DEFUN ("add-text-properties", Fadd_text_properties,
5fbe2a44 1204 Sadd_text_properties, 3, 4, 0,
8c1a1077
PJ
1205 doc: /* Add properties to the text from START to END.
1206The third argument PROPERTIES is a property list
81f6e55f
FP
1207specifying the property values to add. If the optional fourth argument
1208OBJECT is a buffer (or nil, which means the current buffer),
1209START and END are buffer positions (integers or markers).
1210If OBJECT is a string, START and END are 0-based indices into it.
8c1a1077
PJ
1211Return t if any property value actually changed, nil otherwise. */)
1212 (start, end, properties, object)
5fbe2a44 1213 Lisp_Object start, end, properties, object;
d418ef42
JA
1214{
1215 register INTERVAL i, unchanged;
caa31568 1216 register int s, len, modified = 0;
c98da214 1217 struct gcpro gcpro1;
d418ef42
JA
1218
1219 properties = validate_plist (properties);
1220 if (NILP (properties))
1221 return Qnil;
1222
5fbe2a44 1223 if (NILP (object))
c8a4fc3d 1224 XSETBUFFER (object, current_buffer);
5fbe2a44 1225
d418ef42
JA
1226 i = validate_interval_range (object, &start, &end, hard);
1227 if (NULL_INTERVAL_P (i))
1228 return Qnil;
1229
1230 s = XINT (start);
1231 len = XINT (end) - s;
1232
c98da214
RS
1233 /* No need to protect OBJECT, because we GC only if it's a buffer,
1234 and live buffers are always protected. */
1235 GCPRO1 (properties);
1236
d418ef42 1237 /* If we're not starting on an interval boundary, we have to
cdf3e5a2 1238 split this interval. */
d418ef42
JA
1239 if (i->position != s)
1240 {
1241 /* If this interval already has the properties, we can
cdf3e5a2 1242 skip it. */
d418ef42
JA
1243 if (interval_has_all_properties (properties, i))
1244 {
1245 int got = (LENGTH (i) - (s - i->position));
1246 if (got >= len)
64db1307 1247 RETURN_UNGCPRO (Qnil);
d418ef42 1248 len -= got;
05d5b93e 1249 i = next_interval (i);
d418ef42
JA
1250 }
1251 else
1252 {
1253 unchanged = i;
ad9c1940 1254 i = split_interval_right (unchanged, s - unchanged->position);
d418ef42 1255 copy_properties (unchanged, i);
d418ef42
JA
1256 }
1257 }
1258
2a631db1 1259 if (BUFFERP (object))
3e145152 1260 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
26c76ace 1261
daa5e28f 1262 /* We are at the beginning of interval I, with LEN chars to scan. */
caa31568 1263 for (;;)
d418ef42 1264 {
d4b530ad
RS
1265 if (i == 0)
1266 abort ();
1267
d418ef42
JA
1268 if (LENGTH (i) >= len)
1269 {
c98da214
RS
1270 /* We can UNGCPRO safely here, because there will be just
1271 one more chance to gc, in the next call to add_properties,
1272 and after that we will not need PROPERTIES or OBJECT again. */
1273 UNGCPRO;
1274
d418ef42 1275 if (interval_has_all_properties (properties, i))
26c76ace 1276 {
2a631db1
RS
1277 if (BUFFERP (object))
1278 signal_after_change (XINT (start), XINT (end) - XINT (start),
1279 XINT (end) - XINT (start));
26c76ace
RS
1280
1281 return modified ? Qt : Qnil;
1282 }
d418ef42
JA
1283
1284 if (LENGTH (i) == len)
1285 {
d4b530ad 1286 add_properties (properties, i, object);
2a631db1
RS
1287 if (BUFFERP (object))
1288 signal_after_change (XINT (start), XINT (end) - XINT (start),
1289 XINT (end) - XINT (start));
d418ef42
JA
1290 return Qt;
1291 }
1292
1293 /* i doesn't have the properties, and goes past the change limit */
1294 unchanged = i;
ad9c1940 1295 i = split_interval_left (unchanged, len);
d418ef42 1296 copy_properties (unchanged, i);
d4b530ad 1297 add_properties (properties, i, object);
2a631db1
RS
1298 if (BUFFERP (object))
1299 signal_after_change (XINT (start), XINT (end) - XINT (start),
1300 XINT (end) - XINT (start));
d418ef42
JA
1301 return Qt;
1302 }
1303
1304 len -= LENGTH (i);
d4b530ad 1305 modified += add_properties (properties, i, object);
d418ef42
JA
1306 i = next_interval (i);
1307 }
1308}
1309
c98da214
RS
1310/* Callers note, this can GC when OBJECT is a buffer (or nil). */
1311
d4b530ad
RS
1312DEFUN ("put-text-property", Fput_text_property,
1313 Sput_text_property, 4, 5, 0,
8c1a1077
PJ
1314 doc: /* Set one property of the text from START to END.
1315The third and fourth arguments PROPERTY and VALUE
1316specify the property to add.
81f6e55f
FP
1317If the optional fifth argument OBJECT is a buffer (or nil, which means
1318the current buffer), START and END are buffer positions (integers or
1319markers). If OBJECT is a string, START and END are 0-based indices into it. */)
8c1a1077 1320 (start, end, property, value, object)
1f5e848a 1321 Lisp_Object start, end, property, value, object;
d4b530ad
RS
1322{
1323 Fadd_text_properties (start, end,
1f5e848a 1324 Fcons (property, Fcons (value, Qnil)),
d4b530ad
RS
1325 object);
1326 return Qnil;
1327}
1328
d418ef42 1329DEFUN ("set-text-properties", Fset_text_properties,
5fbe2a44 1330 Sset_text_properties, 3, 4, 0,
8c1a1077
PJ
1331 doc: /* Completely replace properties of text from START to END.
1332The third argument PROPERTIES is the new property list.
81f6e55f
FP
1333If the optional fourth argument OBJECT is a buffer (or nil, which means
1334the current buffer), START and END are buffer positions (integers or
1335markers). If OBJECT is a string, START and END are 0-based indices into it.
8c1a1077
PJ
1336If PROPERTIES is nil, the effect is to remove all properties from
1337the designated part of OBJECT. */)
1338 (start, end, properties, object)
1f5e848a 1339 Lisp_Object start, end, properties, object;
0087ade6
GM
1340{
1341 return set_text_properties (start, end, properties, object, Qt);
1342}
1343
1344
1345/* Replace properties of text from START to END with new list of
1346 properties PROPERTIES. OBJECT is the buffer or string containing
1347 the text. OBJECT nil means use the current buffer.
1348 SIGNAL_AFTER_CHANGE_P nil means don't signal after changes. Value
537562fa
LT
1349 is nil if the function _detected_ that it did not replace any
1350 properties, non-nil otherwise. */
0087ade6
GM
1351
1352Lisp_Object
1353set_text_properties (start, end, properties, object, signal_after_change_p)
1354 Lisp_Object start, end, properties, object, signal_after_change_p;
d418ef42 1355{
28ff4293 1356 register INTERVAL i;
33d7d0df
RS
1357 Lisp_Object ostart, oend;
1358
1359 ostart = start;
1360 oend = end;
d418ef42 1361
1f5e848a 1362 properties = validate_plist (properties);
d418ef42 1363
5fbe2a44 1364 if (NILP (object))
c8a4fc3d 1365 XSETBUFFER (object, current_buffer);
5fbe2a44 1366
919fa9cb
RS
1367 /* If we want no properties for a whole string,
1368 get rid of its intervals. */
1f5e848a 1369 if (NILP (properties) && STRINGP (object)
919fa9cb 1370 && XFASTINT (start) == 0
d5db4077 1371 && XFASTINT (end) == SCHARS (object))
919fa9cb 1372 {
d5db4077 1373 if (! STRING_INTERVALS (object))
537562fa 1374 return Qnil;
26c76ace 1375
9056febe 1376 STRING_SET_INTERVALS (object, NULL_INTERVAL);
919fa9cb
RS
1377 return Qt;
1378 }
1379
facc570e 1380 i = validate_interval_range (object, &start, &end, soft);
919fa9cb 1381
d418ef42 1382 if (NULL_INTERVAL_P (i))
facc570e 1383 {
1f5e848a
EN
1384 /* If buffer has no properties, and we want none, return now. */
1385 if (NILP (properties))
facc570e
RS
1386 return Qnil;
1387
33d7d0df
RS
1388 /* Restore the original START and END values
1389 because validate_interval_range increments them for strings. */
1390 start = ostart;
1391 end = oend;
1392
facc570e
RS
1393 i = validate_interval_range (object, &start, &end, hard);
1394 /* This can return if start == end. */
1395 if (NULL_INTERVAL_P (i))
1396 return Qnil;
1397 }
d418ef42 1398
2a631db1 1399 if (BUFFERP (object))
3e145152 1400 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
26c76ace 1401
78ff4175
RS
1402 set_text_properties_1 (start, end, properties, object, i);
1403
1404 if (BUFFERP (object) && !NILP (signal_after_change_p))
1405 signal_after_change (XINT (start), XINT (end) - XINT (start),
1406 XINT (end) - XINT (start));
1407 return Qt;
1408}
1409
1410/* Replace properties of text from START to END with new list of
1411 properties PROPERTIES. BUFFER is the buffer containing
1412 the text. This does not obey any hooks.
1413 You can provide the interval that START is located in as I,
ce768453 1414 or pass NULL for I and this function will find it.
49f68fd2 1415 START and END can be in any order. */
78ff4175
RS
1416
1417void
1418set_text_properties_1 (start, end, properties, buffer, i)
1419 Lisp_Object start, end, properties, buffer;
1420 INTERVAL i;
1421{
1422 register INTERVAL prev_changed = NULL_INTERVAL;
1423 register int s, len;
1424 INTERVAL unchanged;
1425
1426 s = XINT (start);
1427 len = XINT (end) - s;
49f68fd2
RS
1428 if (len == 0)
1429 return;
1430 if (len < 0)
1431 {
1432 s = s + len;
1433 len = - len;
1434 }
1435
78ff4175
RS
1436 if (i == 0)
1437 i = find_interval (BUF_INTERVALS (XBUFFER (buffer)), s);
1438
d418ef42
JA
1439 if (i->position != s)
1440 {
1441 unchanged = i;
ad9c1940 1442 i = split_interval_right (unchanged, s - unchanged->position);
7855e674 1443
d418ef42
JA
1444 if (LENGTH (i) > len)
1445 {
9c79dd1b 1446 copy_properties (unchanged, i);
ad9c1940 1447 i = split_interval_left (i, len);
78ff4175
RS
1448 set_properties (properties, i, buffer);
1449 return;
d418ef42
JA
1450 }
1451
78ff4175 1452 set_properties (properties, i, buffer);
daa5e28f 1453
9c79dd1b 1454 if (LENGTH (i) == len)
78ff4175 1455 return;
9c79dd1b
JA
1456
1457 prev_changed = i;
d418ef42
JA
1458 len -= LENGTH (i);
1459 i = next_interval (i);
1460 }
1461
cd7d971d 1462 /* We are starting at the beginning of an interval, I */
7855e674 1463 while (len > 0)
d418ef42 1464 {
d4b530ad
RS
1465 if (i == 0)
1466 abort ();
1467
d418ef42
JA
1468 if (LENGTH (i) >= len)
1469 {
cd7d971d 1470 if (LENGTH (i) > len)
ad9c1940 1471 i = split_interval_left (i, len);
d418ef42 1472
6f232881
RS
1473 /* We have to call set_properties even if we are going to
1474 merge the intervals, so as to make the undo records
1475 and cause redisplay to happen. */
78ff4175 1476 set_properties (properties, i, buffer);
6f232881 1477 if (!NULL_INTERVAL_P (prev_changed))
9c79dd1b 1478 merge_interval_left (i);
78ff4175 1479 return;
d418ef42
JA
1480 }
1481
1482 len -= LENGTH (i);
6f232881
RS
1483
1484 /* We have to call set_properties even if we are going to
1485 merge the intervals, so as to make the undo records
1486 and cause redisplay to happen. */
78ff4175 1487 set_properties (properties, i, buffer);
9c79dd1b 1488 if (NULL_INTERVAL_P (prev_changed))
6f232881 1489 prev_changed = i;
9c79dd1b
JA
1490 else
1491 prev_changed = i = merge_interval_left (i);
1492
d418ef42
JA
1493 i = next_interval (i);
1494 }
d418ef42
JA
1495}
1496
1497DEFUN ("remove-text-properties", Fremove_text_properties,
5fbe2a44 1498 Sremove_text_properties, 3, 4, 0,
8c1a1077
PJ
1499 doc: /* Remove some properties from text from START to END.
1500The third argument PROPERTIES is a property list
1501whose property names specify the properties to remove.
1502\(The values stored in PROPERTIES are ignored.)
81f6e55f
FP
1503If the optional fourth argument OBJECT is a buffer (or nil, which means
1504the current buffer), START and END are buffer positions (integers or
1505markers). If OBJECT is a string, START and END are 0-based indices into it.
1506Return t if any property was actually removed, nil otherwise.
1507
518c0b83 1508Use `set-text-properties' if you want to remove all text properties. */)
8c1a1077 1509 (start, end, properties, object)
1f5e848a 1510 Lisp_Object start, end, properties, object;
d418ef42
JA
1511{
1512 register INTERVAL i, unchanged;
caa31568 1513 register int s, len, modified = 0;
d418ef42 1514
5fbe2a44 1515 if (NILP (object))
c8a4fc3d 1516 XSETBUFFER (object, current_buffer);
5fbe2a44 1517
d418ef42
JA
1518 i = validate_interval_range (object, &start, &end, soft);
1519 if (NULL_INTERVAL_P (i))
1520 return Qnil;
1521
1522 s = XINT (start);
1523 len = XINT (end) - s;
9c79dd1b 1524
d418ef42
JA
1525 if (i->position != s)
1526 {
1527 /* No properties on this first interval -- return if
cdf3e5a2 1528 it covers the entire region. */
1f5e848a 1529 if (! interval_has_some_properties (properties, i))
d418ef42
JA
1530 {
1531 int got = (LENGTH (i) - (s - i->position));
1532 if (got >= len)
1533 return Qnil;
1534 len -= got;
05d5b93e 1535 i = next_interval (i);
d418ef42 1536 }
daa5e28f
RS
1537 /* Split away the beginning of this interval; what we don't
1538 want to modify. */
d418ef42
JA
1539 else
1540 {
1541 unchanged = i;
ad9c1940 1542 i = split_interval_right (unchanged, s - unchanged->position);
d418ef42 1543 copy_properties (unchanged, i);
d418ef42
JA
1544 }
1545 }
1546
2a631db1 1547 if (BUFFERP (object))
3e145152 1548 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
26c76ace 1549
d418ef42 1550 /* We are at the beginning of an interval, with len to scan */
caa31568 1551 for (;;)
d418ef42 1552 {
d4b530ad
RS
1553 if (i == 0)
1554 abort ();
1555
d418ef42
JA
1556 if (LENGTH (i) >= len)
1557 {
1f5e848a 1558 if (! interval_has_some_properties (properties, i))
d418ef42
JA
1559 return modified ? Qt : Qnil;
1560
1561 if (LENGTH (i) == len)
1562 {
11713b6d
RS
1563 remove_properties (properties, Qnil, i, object);
1564 if (BUFFERP (object))
1565 signal_after_change (XINT (start), XINT (end) - XINT (start),
1566 XINT (end) - XINT (start));
1567 return Qt;
1568 }
1569
1570 /* i has the properties, and goes past the change limit */
1571 unchanged = i;
1572 i = split_interval_left (i, len);
1573 copy_properties (unchanged, i);
1574 remove_properties (properties, Qnil, i, object);
1575 if (BUFFERP (object))
1576 signal_after_change (XINT (start), XINT (end) - XINT (start),
1577 XINT (end) - XINT (start));
1578 return Qt;
1579 }
1580
1581 len -= LENGTH (i);
1582 modified += remove_properties (properties, Qnil, i, object);
1583 i = next_interval (i);
1584 }
1585}
1586
1587DEFUN ("remove-list-of-text-properties", Fremove_list_of_text_properties,
1588 Sremove_list_of_text_properties, 3, 4, 0,
1589 doc: /* Remove some properties from text from START to END.
1590The third argument LIST-OF-PROPERTIES is a list of property names to remove.
81f6e55f
FP
1591If the optional fourth argument OBJECT is a buffer (or nil, which means
1592the current buffer), START and END are buffer positions (integers or
1593markers). If OBJECT is a string, START and END are 0-based indices into it.
11713b6d
RS
1594Return t if any property was actually removed, nil otherwise. */)
1595 (start, end, list_of_properties, object)
1596 Lisp_Object start, end, list_of_properties, object;
1597{
1598 register INTERVAL i, unchanged;
1599 register int s, len, modified = 0;
1600 Lisp_Object properties;
1601 properties = list_of_properties;
1602
1603 if (NILP (object))
1604 XSETBUFFER (object, current_buffer);
1605
1606 i = validate_interval_range (object, &start, &end, soft);
1607 if (NULL_INTERVAL_P (i))
1608 return Qnil;
1609
1610 s = XINT (start);
1611 len = XINT (end) - s;
1612
1613 if (i->position != s)
1614 {
1615 /* No properties on this first interval -- return if
1616 it covers the entire region. */
1617 if (! interval_has_some_properties_list (properties, i))
1618 {
1619 int got = (LENGTH (i) - (s - i->position));
1620 if (got >= len)
1621 return Qnil;
1622 len -= got;
1623 i = next_interval (i);
1624 }
1625 /* Split away the beginning of this interval; what we don't
1626 want to modify. */
1627 else
1628 {
1629 unchanged = i;
1630 i = split_interval_right (unchanged, s - unchanged->position);
1631 copy_properties (unchanged, i);
1632 }
1633 }
1634
9b17c9f5
LH
1635 /* We are at the beginning of an interval, with len to scan.
1636 The flag `modified' records if changes have been made.
1637 When object is a buffer, we must call modify_region before changes are
1638 made and signal_after_change when we are done.
e0f24100
GM
1639 We call modify_region before calling remove_properties if modified == 0,
1640 and we call signal_after_change before returning if modified != 0. */
11713b6d
RS
1641 for (;;)
1642 {
1643 if (i == 0)
1644 abort ();
1645
1646 if (LENGTH (i) >= len)
1647 {
1648 if (! interval_has_some_properties_list (properties, i))
9b17c9f5
LH
1649 if (modified)
1650 {
1651 if (BUFFERP (object))
1652 signal_after_change (XINT (start), XINT (end) - XINT (start),
1653 XINT (end) - XINT (start));
1654 return Qt;
1655 }
1656 else
1657 return Qnil;
11713b6d
RS
1658
1659 if (LENGTH (i) == len)
1660 {
9b17c9f5 1661 if (!modified && BUFFERP (object))
3e145152 1662 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
11713b6d 1663 remove_properties (Qnil, properties, i, object);
2a631db1
RS
1664 if (BUFFERP (object))
1665 signal_after_change (XINT (start), XINT (end) - XINT (start),
1666 XINT (end) - XINT (start));
d418ef42
JA
1667 return Qt;
1668 }
1669
1670 /* i has the properties, and goes past the change limit */
daa5e28f 1671 unchanged = i;
ad9c1940 1672 i = split_interval_left (i, len);
d418ef42 1673 copy_properties (unchanged, i);
9b17c9f5 1674 if (!modified && BUFFERP (object))
3e145152 1675 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
11713b6d 1676 remove_properties (Qnil, properties, i, object);
2a631db1
RS
1677 if (BUFFERP (object))
1678 signal_after_change (XINT (start), XINT (end) - XINT (start),
1679 XINT (end) - XINT (start));
d418ef42
JA
1680 return Qt;
1681 }
1682
9b17c9f5
LH
1683 if (interval_has_some_properties_list (properties, i))
1684 {
1685 if (!modified && BUFFERP (object))
3e145152 1686 modify_region (XBUFFER (object), XINT (start), XINT (end), 1);
9b17c9f5
LH
1687 remove_properties (Qnil, properties, i, object);
1688 modified = 1;
1689 }
d418ef42 1690 len -= LENGTH (i);
d418ef42
JA
1691 i = next_interval (i);
1692 }
1693}
fcab51aa 1694\f
ad9c1940
JB
1695DEFUN ("text-property-any", Ftext_property_any,
1696 Stext_property_any, 4, 5, 0,
8c1a1077
PJ
1697 doc: /* Check text from START to END for property PROPERTY equalling VALUE.
1698If so, return the position of the first character whose property PROPERTY
1699is `eq' to VALUE. Otherwise return nil.
81f6e55f
FP
1700If the optional fifth argument OBJECT is a buffer (or nil, which means
1701the current buffer), START and END are buffer positions (integers or
1702markers). If OBJECT is a string, START and END are 0-based indices into it. */)
8c1a1077
PJ
1703 (start, end, property, value, object)
1704 Lisp_Object start, end, property, value, object;
ad9c1940
JB
1705{
1706 register INTERVAL i;
1707 register int e, pos;
1708
1709 if (NILP (object))
c8a4fc3d 1710 XSETBUFFER (object, current_buffer);
ad9c1940 1711 i = validate_interval_range (object, &start, &end, soft);
2084fddb
KH
1712 if (NULL_INTERVAL_P (i))
1713 return (!NILP (value) || EQ (start, end) ? Qnil : start);
ad9c1940
JB
1714 e = XINT (end);
1715
1716 while (! NULL_INTERVAL_P (i))
1717 {
1718 if (i->position >= e)
1719 break;
1f5e848a 1720 if (EQ (textget (i->plist, property), value))
ad9c1940
JB
1721 {
1722 pos = i->position;
1723 if (pos < XINT (start))
1724 pos = XINT (start);
ad077db0 1725 return make_number (pos);
ad9c1940
JB
1726 }
1727 i = next_interval (i);
1728 }
1729 return Qnil;
1730}
1731
1732DEFUN ("text-property-not-all", Ftext_property_not_all,
1733 Stext_property_not_all, 4, 5, 0,
8c1a1077
PJ
1734 doc: /* Check text from START to END for property PROPERTY not equalling VALUE.
1735If so, return the position of the first character whose property PROPERTY
1736is not `eq' to VALUE. Otherwise, return nil.
81f6e55f
FP
1737If the optional fifth argument OBJECT is a buffer (or nil, which means
1738the current buffer), START and END are buffer positions (integers or
1739markers). If OBJECT is a string, START and END are 0-based indices into it. */)
8c1a1077
PJ
1740 (start, end, property, value, object)
1741 Lisp_Object start, end, property, value, object;
ad9c1940
JB
1742{
1743 register INTERVAL i;
1744 register int s, e;
1745
1746 if (NILP (object))
c8a4fc3d 1747 XSETBUFFER (object, current_buffer);
ad9c1940
JB
1748 i = validate_interval_range (object, &start, &end, soft);
1749 if (NULL_INTERVAL_P (i))
916a3119 1750 return (NILP (value) || EQ (start, end)) ? Qnil : start;
ad9c1940
JB
1751 s = XINT (start);
1752 e = XINT (end);
1753
1754 while (! NULL_INTERVAL_P (i))
1755 {
1756 if (i->position >= e)
1757 break;
1f5e848a 1758 if (! EQ (textget (i->plist, property), value))
ad9c1940
JB
1759 {
1760 if (i->position > s)
1761 s = i->position;
ad077db0 1762 return make_number (s);
ad9c1940
JB
1763 }
1764 i = next_interval (i);
1765 }
1766 return Qnil;
1767}
e138dfdc
MB
1768
1769\f
1770/* Return the direction from which the text-property PROP would be
1771 inherited by any new text inserted at POS: 1 if it would be
1772 inherited from the char after POS, -1 if it would be inherited from
6f716644
SM
1773 the char before POS, and 0 if from neither.
1774 BUFFER can be either a buffer or nil (meaning current buffer). */
e138dfdc
MB
1775
1776int
6f716644
SM
1777text_property_stickiness (prop, pos, buffer)
1778 Lisp_Object prop, pos, buffer;
e138dfdc
MB
1779{
1780 Lisp_Object prev_pos, front_sticky;
1781 int is_rear_sticky = 1, is_front_sticky = 0; /* defaults */
1782
6f716644
SM
1783 if (NILP (buffer))
1784 XSETBUFFER (buffer, current_buffer);
1785
1786 if (XINT (pos) > BUF_BEGV (XBUFFER (buffer)))
e138dfdc
MB
1787 /* Consider previous character. */
1788 {
1789 Lisp_Object rear_non_sticky;
1790
1791 prev_pos = make_number (XINT (pos) - 1);
6f716644 1792 rear_non_sticky = Fget_text_property (prev_pos, Qrear_nonsticky, buffer);
e138dfdc
MB
1793
1794 if (!NILP (CONSP (rear_non_sticky)
1795 ? Fmemq (prop, rear_non_sticky)
1796 : rear_non_sticky))
1797 /* PROP is rear-non-sticky. */
1798 is_rear_sticky = 0;
1799 }
506d2f9a
CY
1800 else
1801 return 0;
e138dfdc
MB
1802
1803 /* Consider following character. */
506d2f9a
CY
1804 /* This signals an arg-out-of-range error if pos is outside the
1805 buffer's accessible range. */
6f716644 1806 front_sticky = Fget_text_property (pos, Qfront_sticky, buffer);
e138dfdc
MB
1807
1808 if (EQ (front_sticky, Qt)
1809 || (CONSP (front_sticky)
1810 && !NILP (Fmemq (prop, front_sticky))))
1811 /* PROP is inherited from after. */
1812 is_front_sticky = 1;
1813
1814 /* Simple cases, where the properties are consistent. */
1815 if (is_rear_sticky && !is_front_sticky)
1816 return -1;
1817 else if (!is_rear_sticky && is_front_sticky)
1818 return 1;
1819 else if (!is_rear_sticky && !is_front_sticky)
1820 return 0;
1821
1822 /* The stickiness properties are inconsistent, so we have to
1823 disambiguate. Basically, rear-sticky wins, _except_ if the
1824 property that would be inherited has a value of nil, in which case
1825 front-sticky wins. */
6f716644
SM
1826 if (XINT (pos) == BUF_BEGV (XBUFFER (buffer))
1827 || NILP (Fget_text_property (prev_pos, prop, buffer)))
e138dfdc
MB
1828 return 1;
1829 else
1830 return -1;
1831}
1832
fcab51aa 1833\f
15e4954b
JB
1834/* I don't think this is the right interface to export; how often do you
1835 want to do something like this, other than when you're copying objects
1836 around?
1837
1838 I think it would be better to have a pair of functions, one which
1839 returns the text properties of a region as a list of ranges and
1840 plists, and another which applies such a list to another object. */
1841
c98da214
RS
1842/* Add properties from SRC to SRC of SRC, starting at POS in DEST.
1843 SRC and DEST may each refer to strings or buffers.
1844 Optional sixth argument PROP causes only that property to be copied.
1845 Properties are copied to DEST as if by `add-text-properties'.
1846 Return t if any property value actually changed, nil otherwise. */
1847
1848/* Note this can GC when DEST is a buffer. */
ad077db0 1849
15e4954b
JB
1850Lisp_Object
1851copy_text_properties (start, end, src, pos, dest, prop)
1852 Lisp_Object start, end, src, pos, dest, prop;
1853{
1854 INTERVAL i;
1855 Lisp_Object res;
1856 Lisp_Object stuff;
1857 Lisp_Object plist;
1858 int s, e, e2, p, len, modified = 0;
c98da214 1859 struct gcpro gcpro1, gcpro2;
15e4954b
JB
1860
1861 i = validate_interval_range (src, &start, &end, soft);
1862 if (NULL_INTERVAL_P (i))
1863 return Qnil;
1864
b7826503 1865 CHECK_NUMBER_COERCE_MARKER (pos);
15e4954b
JB
1866 {
1867 Lisp_Object dest_start, dest_end;
1868
1869 dest_start = pos;
e9c4fbcd 1870 XSETFASTINT (dest_end, XINT (dest_start) + (XINT (end) - XINT (start)));
15e4954b
JB
1871 /* Apply this to a copy of pos; it will try to increment its arguments,
1872 which we don't want. */
1873 validate_interval_range (dest, &dest_start, &dest_end, soft);
1874 }
1875
1876 s = XINT (start);
1877 e = XINT (end);
1878 p = XINT (pos);
1879
1880 stuff = Qnil;
1881
1882 while (s < e)
1883 {
1884 e2 = i->position + LENGTH (i);
1885 if (e2 > e)
1886 e2 = e;
1887 len = e2 - s;
1888
1889 plist = i->plist;
1890 if (! NILP (prop))
1891 while (! NILP (plist))
1892 {
1893 if (EQ (Fcar (plist), prop))
1894 {
1895 plist = Fcons (prop, Fcons (Fcar (Fcdr (plist)), Qnil));
1896 break;
1897 }
1898 plist = Fcdr (Fcdr (plist));
1899 }
1900 if (! NILP (plist))
1901 {
1902 /* Must defer modifications to the interval tree in case src
cdf3e5a2 1903 and dest refer to the same string or buffer. */
15e4954b
JB
1904 stuff = Fcons (Fcons (make_number (p),
1905 Fcons (make_number (p + len),
1906 Fcons (plist, Qnil))),
1907 stuff);
1908 }
1909
1910 i = next_interval (i);
1911 if (NULL_INTERVAL_P (i))
1912 break;
1913
1914 p += len;
1915 s = i->position;
1916 }
1917
c98da214
RS
1918 GCPRO2 (stuff, dest);
1919
15e4954b
JB
1920 while (! NILP (stuff))
1921 {
1922 res = Fcar (stuff);
1923 res = Fadd_text_properties (Fcar (res), Fcar (Fcdr (res)),
1924 Fcar (Fcdr (Fcdr (res))), dest);
1925 if (! NILP (res))
1926 modified++;
1927 stuff = Fcdr (stuff);
1928 }
1929
c98da214
RS
1930 UNGCPRO;
1931
15e4954b
JB
1932 return modified ? Qt : Qnil;
1933}
9dd7eec6
GM
1934
1935
1936/* Return a list representing the text properties of OBJECT between
1937 START and END. if PROP is non-nil, report only on that property.
1938 Each result list element has the form (S E PLIST), where S and E
1939 are positions in OBJECT and PLIST is a property list containing the
1940 text properties of OBJECT between S and E. Value is nil if OBJECT
1941 doesn't contain text properties between START and END. */
1942
1943Lisp_Object
1944text_property_list (object, start, end, prop)
1945 Lisp_Object object, start, end, prop;
1946{
1947 struct interval *i;
1948 Lisp_Object result;
9dd7eec6
GM
1949
1950 result = Qnil;
81f6e55f 1951
9dd7eec6
GM
1952 i = validate_interval_range (object, &start, &end, soft);
1953 if (!NULL_INTERVAL_P (i))
1954 {
1955 int s = XINT (start);
1956 int e = XINT (end);
81f6e55f 1957
9dd7eec6
GM
1958 while (s < e)
1959 {
1960 int interval_end, len;
1961 Lisp_Object plist;
81f6e55f 1962
9dd7eec6
GM
1963 interval_end = i->position + LENGTH (i);
1964 if (interval_end > e)
1965 interval_end = e;
1966 len = interval_end - s;
81f6e55f 1967
9dd7eec6
GM
1968 plist = i->plist;
1969
1970 if (!NILP (prop))
99784d63
SM
1971 for (; CONSP (plist); plist = Fcdr (XCDR (plist)))
1972 if (EQ (XCAR (plist), prop))
9dd7eec6 1973 {
99784d63 1974 plist = Fcons (prop, Fcons (Fcar (XCDR (plist)), Qnil));
9dd7eec6
GM
1975 break;
1976 }
1977
1978 if (!NILP (plist))
1979 result = Fcons (Fcons (make_number (s),
1980 Fcons (make_number (s + len),
1981 Fcons (plist, Qnil))),
1982 result);
81f6e55f 1983
9dd7eec6
GM
1984 i = next_interval (i);
1985 if (NULL_INTERVAL_P (i))
1986 break;
1987 s = i->position;
1988 }
1989 }
81f6e55f 1990
9dd7eec6
GM
1991 return result;
1992}
1993
1994
1995/* Add text properties to OBJECT from LIST. LIST is a list of triples
1996 (START END PLIST), where START and END are positions and PLIST is a
1997 property list containing the text properties to add. Adjust START
1998 and END positions by DELTA before adding properties. Value is
1999 non-zero if OBJECT was modified. */
2000
2001int
2002add_text_properties_from_list (object, list, delta)
2003 Lisp_Object object, list, delta;
2004{
2005 struct gcpro gcpro1, gcpro2;
2006 int modified_p = 0;
81f6e55f 2007
9dd7eec6 2008 GCPRO2 (list, object);
81f6e55f 2009
9dd7eec6
GM
2010 for (; CONSP (list); list = XCDR (list))
2011 {
2012 Lisp_Object item, start, end, plist, tem;
81f6e55f 2013
9dd7eec6
GM
2014 item = XCAR (list);
2015 start = make_number (XINT (XCAR (item)) + XINT (delta));
2016 end = make_number (XINT (XCAR (XCDR (item))) + XINT (delta));
2017 plist = XCAR (XCDR (XCDR (item)));
81f6e55f 2018
9dd7eec6
GM
2019 tem = Fadd_text_properties (start, end, plist, object);
2020 if (!NILP (tem))
2021 modified_p = 1;
2022 }
2023
2024 UNGCPRO;
2025 return modified_p;
2026}
2027
2028
2029
2030/* Modify end-points of ranges in LIST destructively. LIST is a list
2031 as returned from text_property_list. Change end-points equal to
2032 OLD_END to NEW_END. */
2033
2034void
2035extend_property_ranges (list, old_end, new_end)
2036 Lisp_Object list, old_end, new_end;
2037{
2038 for (; CONSP (list); list = XCDR (list))
2039 {
2040 Lisp_Object item, end;
81f6e55f 2041
9dd7eec6
GM
2042 item = XCAR (list);
2043 end = XCAR (XCDR (item));
2044
2045 if (EQ (end, old_end))
f3fbd155 2046 XSETCAR (XCDR (item), new_end);
9dd7eec6
GM
2047 }
2048}
2049
2050
318d2fa8
RS
2051\f
2052/* Call the modification hook functions in LIST, each with START and END. */
2053
2054static void
2055call_mod_hooks (list, start, end)
2056 Lisp_Object list, start, end;
2057{
2058 struct gcpro gcpro1;
2059 GCPRO1 (list);
2060 while (!NILP (list))
2061 {
2062 call2 (Fcar (list), start, end);
2063 list = Fcdr (list);
2064 }
2065 UNGCPRO;
2066}
2067
96f90544
RS
2068/* Check for read-only intervals between character positions START ... END,
2069 in BUF, and signal an error if we find one.
2070
2071 Then check for any modification hooks in the range.
2072 Create a list of all these hooks in lexicographic order,
2073 eliminating consecutive extra copies of the same hook. Then call
2074 those hooks in order, with START and END - 1 as arguments. */
15e4954b 2075
318d2fa8
RS
2076void
2077verify_interval_modification (buf, start, end)
2078 struct buffer *buf;
2079 int start, end;
2080{
2081 register INTERVAL intervals = BUF_INTERVALS (buf);
695f302f 2082 register INTERVAL i;
318d2fa8
RS
2083 Lisp_Object hooks;
2084 register Lisp_Object prev_mod_hooks;
2085 Lisp_Object mod_hooks;
2086 struct gcpro gcpro1;
2087
2088 hooks = Qnil;
2089 prev_mod_hooks = Qnil;
2090 mod_hooks = Qnil;
2091
2092 interval_insert_behind_hooks = Qnil;
2093 interval_insert_in_front_hooks = Qnil;
2094
2095 if (NULL_INTERVAL_P (intervals))
2096 return;
2097
2098 if (start > end)
2099 {
2100 int temp = start;
2101 start = end;
2102 end = temp;
2103 }
2104
2105 /* For an insert operation, check the two chars around the position. */
2106 if (start == end)
2107 {
7cb66899 2108 INTERVAL prev = NULL;
318d2fa8
RS
2109 Lisp_Object before, after;
2110
2111 /* Set I to the interval containing the char after START,
2112 and PREV to the interval containing the char before START.
2113 Either one may be null. They may be equal. */
2114 i = find_interval (intervals, start);
2115
2116 if (start == BUF_BEGV (buf))
2117 prev = 0;
2118 else if (i->position == start)
2119 prev = previous_interval (i);
2120 else if (i->position < start)
2121 prev = i;
2122 if (start == BUF_ZV (buf))
2123 i = 0;
2124
2125 /* If Vinhibit_read_only is set and is not a list, we can
2126 skip the read_only checks. */
2127 if (NILP (Vinhibit_read_only) || CONSP (Vinhibit_read_only))
2128 {
2129 /* If I and PREV differ we need to check for the read-only
cdf3e5a2 2130 property together with its stickiness. If either I or
318d2fa8
RS
2131 PREV are 0, this check is all we need.
2132 We have to take special care, since read-only may be
2133 indirectly defined via the category property. */
2134 if (i != prev)
2135 {
2136 if (! NULL_INTERVAL_P (i))
2137 {
2138 after = textget (i->plist, Qread_only);
81f6e55f 2139
318d2fa8
RS
2140 /* If interval I is read-only and read-only is
2141 front-sticky, inhibit insertion.
2142 Check for read-only as well as category. */
2143 if (! NILP (after)
2144 && NILP (Fmemq (after, Vinhibit_read_only)))
2145 {
2146 Lisp_Object tem;
2147
2148 tem = textget (i->plist, Qfront_sticky);
2149 if (TMEM (Qread_only, tem)
2150 || (NILP (Fplist_get (i->plist, Qread_only))
2151 && TMEM (Qcategory, tem)))
bcf97349 2152 text_read_only (after);
318d2fa8
RS
2153 }
2154 }
2155
2156 if (! NULL_INTERVAL_P (prev))
2157 {
2158 before = textget (prev->plist, Qread_only);
81f6e55f 2159
318d2fa8
RS
2160 /* If interval PREV is read-only and read-only isn't
2161 rear-nonsticky, inhibit insertion.
2162 Check for read-only as well as category. */
2163 if (! NILP (before)
2164 && NILP (Fmemq (before, Vinhibit_read_only)))
2165 {
2166 Lisp_Object tem;
2167
2168 tem = textget (prev->plist, Qrear_nonsticky);
2169 if (! TMEM (Qread_only, tem)
2170 && (! NILP (Fplist_get (prev->plist,Qread_only))
2171 || ! TMEM (Qcategory, tem)))
bcf97349 2172 text_read_only (before);
318d2fa8
RS
2173 }
2174 }
2175 }
2176 else if (! NULL_INTERVAL_P (i))
2177 {
2178 after = textget (i->plist, Qread_only);
81f6e55f 2179
318d2fa8
RS
2180 /* If interval I is read-only and read-only is
2181 front-sticky, inhibit insertion.
2182 Check for read-only as well as category. */
2183 if (! NILP (after) && NILP (Fmemq (after, Vinhibit_read_only)))
2184 {
2185 Lisp_Object tem;
2186
2187 tem = textget (i->plist, Qfront_sticky);
2188 if (TMEM (Qread_only, tem)
2189 || (NILP (Fplist_get (i->plist, Qread_only))
2190 && TMEM (Qcategory, tem)))
bcf97349 2191 text_read_only (after);
318d2fa8
RS
2192
2193 tem = textget (prev->plist, Qrear_nonsticky);
2194 if (! TMEM (Qread_only, tem)
2195 && (! NILP (Fplist_get (prev->plist, Qread_only))
2196 || ! TMEM (Qcategory, tem)))
bcf97349 2197 text_read_only (after);
318d2fa8
RS
2198 }
2199 }
2200 }
2201
2202 /* Run both insert hooks (just once if they're the same). */
2203 if (!NULL_INTERVAL_P (prev))
2204 interval_insert_behind_hooks
2205 = textget (prev->plist, Qinsert_behind_hooks);
2206 if (!NULL_INTERVAL_P (i))
2207 interval_insert_in_front_hooks
2208 = textget (i->plist, Qinsert_in_front_hooks);
2209 }
0ba7995b 2210 else
318d2fa8
RS
2211 {
2212 /* Loop over intervals on or next to START...END,
2213 collecting their hooks. */
2214
2215 i = find_interval (intervals, start);
2216 do
2217 {
2218 if (! INTERVAL_WRITABLE_P (i))
bcf97349 2219 text_read_only (textget (i->plist, Qread_only));
318d2fa8 2220
0ba7995b 2221 if (!inhibit_modification_hooks)
318d2fa8 2222 {
0ba7995b
GM
2223 mod_hooks = textget (i->plist, Qmodification_hooks);
2224 if (! NILP (mod_hooks) && ! EQ (mod_hooks, prev_mod_hooks))
2225 {
2226 hooks = Fcons (mod_hooks, hooks);
2227 prev_mod_hooks = mod_hooks;
2228 }
318d2fa8
RS
2229 }
2230
2231 i = next_interval (i);
2232 }
2233 /* Keep going thru the interval containing the char before END. */
2234 while (! NULL_INTERVAL_P (i) && i->position < end);
2235
0ba7995b 2236 if (!inhibit_modification_hooks)
318d2fa8 2237 {
0ba7995b
GM
2238 GCPRO1 (hooks);
2239 hooks = Fnreverse (hooks);
2240 while (! EQ (hooks, Qnil))
2241 {
2242 call_mod_hooks (Fcar (hooks), make_number (start),
2243 make_number (end));
2244 hooks = Fcdr (hooks);
2245 }
2246 UNGCPRO;
318d2fa8 2247 }
318d2fa8
RS
2248 }
2249}
2250
96f90544 2251/* Run the interval hooks for an insertion on character range START ... END.
318d2fa8
RS
2252 verify_interval_modification chose which hooks to run;
2253 this function is called after the insertion happens
2254 so it can indicate the range of inserted text. */
2255
2256void
2257report_interval_modification (start, end)
2258 Lisp_Object start, end;
2259{
2260 if (! NILP (interval_insert_behind_hooks))
2e34157c 2261 call_mod_hooks (interval_insert_behind_hooks, start, end);
318d2fa8
RS
2262 if (! NILP (interval_insert_in_front_hooks)
2263 && ! EQ (interval_insert_in_front_hooks,
2264 interval_insert_behind_hooks))
2e34157c 2265 call_mod_hooks (interval_insert_in_front_hooks, start, end);
318d2fa8
RS
2266}
2267\f
d418ef42
JA
2268void
2269syms_of_textprop ()
2270{
ad1b2f20 2271 DEFVAR_LISP ("default-text-properties", &Vdefault_text_properties,
8c1a1077
PJ
2272 doc: /* Property-list used as default values.
2273The value of a property in this list is seen as the value for every
2274character that does not have its own value for that property. */);
ad1b2f20 2275 Vdefault_text_properties = Qnil;
c7dd82a3 2276
49d110a8
CW
2277 DEFVAR_LISP ("char-property-alias-alist", &Vchar_property_alias_alist,
2278 doc: /* Alist of alternative properties for properties without a value.
2279Each element should look like (PROPERTY ALTERNATIVE1 ALTERNATIVE2...).
2280If a piece of text has no direct value for a particular property, then
2281this alist is consulted. If that property appears in the alist, then
2282the first non-nil value from the associated alternative properties is
2283returned. */);
2284 Vchar_property_alias_alist = Qnil;
2285
688a5a0f 2286 DEFVAR_LISP ("inhibit-point-motion-hooks", &Vinhibit_point_motion_hooks,
8c1a1077
PJ
2287 doc: /* If non-nil, don't run `point-left' and `point-entered' text properties.
2288This also inhibits the use of the `intangible' text property. */);
688a5a0f 2289 Vinhibit_point_motion_hooks = Qnil;
318d2fa8 2290
abc2f676
KH
2291 DEFVAR_LISP ("text-property-default-nonsticky",
2292 &Vtext_property_default_nonsticky,
8c1a1077
PJ
2293 doc: /* Alist of properties vs the corresponding non-stickinesses.
2294Each element has the form (PROPERTY . NONSTICKINESS).
2295
2296If a character in a buffer has PROPERTY, new text inserted adjacent to
2297the character doesn't inherit PROPERTY if NONSTICKINESS is non-nil,
518c0b83
JB
2298inherits it if NONSTICKINESS is nil. The `front-sticky' and
2299`rear-nonsticky' properties of the character override NONSTICKINESS. */);
98ebf860
SM
2300 /* Text property `syntax-table' should be nonsticky by default. */
2301 Vtext_property_default_nonsticky
2302 = Fcons (Fcons (intern ("syntax-table"), Qt), Qnil);
abc2f676 2303
318d2fa8
RS
2304 staticpro (&interval_insert_behind_hooks);
2305 staticpro (&interval_insert_in_front_hooks);
2306 interval_insert_behind_hooks = Qnil;
2307 interval_insert_in_front_hooks = Qnil;
2308
81f6e55f 2309
d418ef42
JA
2310 /* Common attributes one might give text */
2311
2312 staticpro (&Qforeground);
2313 Qforeground = intern ("foreground");
2314 staticpro (&Qbackground);
2315 Qbackground = intern ("background");
2316 staticpro (&Qfont);
2317 Qfont = intern ("font");
2318 staticpro (&Qstipple);
2319 Qstipple = intern ("stipple");
2320 staticpro (&Qunderline);
2321 Qunderline = intern ("underline");
2322 staticpro (&Qread_only);
2323 Qread_only = intern ("read-only");
2324 staticpro (&Qinvisible);
2325 Qinvisible = intern ("invisible");
46b4e741
KH
2326 staticpro (&Qintangible);
2327 Qintangible = intern ("intangible");
dc70cea7
RS
2328 staticpro (&Qcategory);
2329 Qcategory = intern ("category");
2330 staticpro (&Qlocal_map);
2331 Qlocal_map = intern ("local-map");
19e1c426
RS
2332 staticpro (&Qfront_sticky);
2333 Qfront_sticky = intern ("front-sticky");
2334 staticpro (&Qrear_nonsticky);
2335 Qrear_nonsticky = intern ("rear-nonsticky");
69bb837e
RS
2336 staticpro (&Qmouse_face);
2337 Qmouse_face = intern ("mouse-face");
54b33868
MR
2338 staticpro (&Qminibuffer_prompt);
2339 Qminibuffer_prompt = intern ("minibuffer-prompt");
d418ef42
JA
2340
2341 /* Properties that text might use to specify certain actions */
2342
2343 staticpro (&Qmouse_left);
2344 Qmouse_left = intern ("mouse-left");
2345 staticpro (&Qmouse_entered);
2346 Qmouse_entered = intern ("mouse-entered");
2347 staticpro (&Qpoint_left);
2348 Qpoint_left = intern ("point-left");
2349 staticpro (&Qpoint_entered);
2350 Qpoint_entered = intern ("point-entered");
d418ef42
JA
2351
2352 defsubr (&Stext_properties_at);
5fbe2a44 2353 defsubr (&Sget_text_property);
eb769fd7 2354 defsubr (&Sget_char_property);
97a1bc63 2355 defsubr (&Sget_char_property_and_overlay);
fcab51aa
RS
2356 defsubr (&Snext_char_property_change);
2357 defsubr (&Sprevious_char_property_change);
b7e047fb
MB
2358 defsubr (&Snext_single_char_property_change);
2359 defsubr (&Sprevious_single_char_property_change);
d418ef42 2360 defsubr (&Snext_property_change);
9c79dd1b 2361 defsubr (&Snext_single_property_change);
d418ef42 2362 defsubr (&Sprevious_property_change);
9c79dd1b 2363 defsubr (&Sprevious_single_property_change);
d418ef42 2364 defsubr (&Sadd_text_properties);
d4b530ad 2365 defsubr (&Sput_text_property);
d418ef42
JA
2366 defsubr (&Sset_text_properties);
2367 defsubr (&Sremove_text_properties);
11713b6d 2368 defsubr (&Sremove_list_of_text_properties);
ad9c1940
JB
2369 defsubr (&Stext_property_any);
2370 defsubr (&Stext_property_not_all);
5fbe2a44 2371/* defsubr (&Serase_text_properties); */
15e4954b 2372/* defsubr (&Scopy_text_properties); */
d418ef42 2373}
ab5796a9
MB
2374
2375/* arch-tag: 454cdde8-5f86-4faa-a078-101e3625d479
2376 (do not change this comment) */