(mark_interval): Add ignored arg.
[bpt/emacs.git] / src / intervals.c
CommitLineData
a50699fd
JA
1/* Code for doing intervals.
2 Copyright (C) 1991, 1992 Free Software Foundation, Inc.
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
8the Free Software Foundation; either version 1, or (at your option)
9any later version.
10
11GNU Emacs is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with GNU Emacs; see the file COPYING. If not, write to
18the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
19
20
21/* NOTES:
22
23 Have to ensure that we can't put symbol nil on a plist, or some
24 functions may work incorrectly.
25
26 An idea: Have the owner of the tree keep count of splits and/or
27 insertion lengths (in intervals), and balance after every N.
28
29 Need to call *_left_hook when buffer is killed.
30
31 Scan for zero-length, or 0-length to see notes about handling
32 zero length interval-markers.
33
34 There are comments around about freeing intervals. It might be
35 faster to explicitly free them (put them on the free list) than
36 to GC them.
37
38*/
39
40
41#include "config.h"
42#include "lisp.h"
43#include "intervals.h"
44#include "buffer.h"
a50699fd 45
d2f7a802
JA
46/* The rest of the file is within this conditional. */
47#ifdef USE_TEXT_PROPERTIES
48
a50699fd
JA
49/* Factor for weight-balancing interval trees. */
50Lisp_Object interval_balance_threshold;
51\f
52/* Utility functions for intervals. */
53
54
55/* Create the root interval of some object, a buffer or string. */
56
57INTERVAL
58create_root_interval (parent)
59 Lisp_Object parent;
60{
61 INTERVAL new = make_interval ();
62
63 if (XTYPE (parent) == Lisp_Buffer)
64 {
65 new->total_length = BUF_Z (XBUFFER (parent)) - 1;
66 XBUFFER (parent)->intervals = new;
67 }
68 else if (XTYPE (parent) == Lisp_String)
69 {
70 new->total_length = XSTRING (parent)->size;
71 XSTRING (parent)->intervals = new;
72 }
73
74 new->parent = (INTERVAL) parent;
75 new->position = 1;
76
77 return new;
78}
79
80/* Make the interval TARGET have exactly the properties of SOURCE */
81
82void
83copy_properties (source, target)
84 register INTERVAL source, target;
85{
86 if (DEFAULT_INTERVAL_P (source) && DEFAULT_INTERVAL_P (target))
87 return;
88
89 COPY_INTERVAL_CACHE (source, target);
90 target->plist = Fcopy_sequence (source->plist);
91}
92
93/* Merge the properties of interval SOURCE into the properties
94 of interval TARGET. */
95
96static void
97merge_properties (source, target)
98 register INTERVAL source, target;
99{
100 register Lisp_Object o, sym, val;
101
102 if (DEFAULT_INTERVAL_P (source) && DEFAULT_INTERVAL_P (target))
103 return;
104
105 MERGE_INTERVAL_CACHE (source, target);
106
107 o = source->plist;
108 while (! EQ (o, Qnil))
109 {
110 sym = Fcar (o);
111 val = Fmemq (sym, target->plist);
112
113 if (NILP (val))
114 {
115 o = Fcdr (o);
116 val = Fcar (o);
117 target->plist = Fcons (sym, Fcons (val, target->plist));
118 o = Fcdr (o);
119 }
120 else
121 o = Fcdr (Fcdr (o));
122 }
123}
124
125/* Return 1 if the two intervals have the same properties,
126 0 otherwise. */
127
128int
129intervals_equal (i0, i1)
130 INTERVAL i0, i1;
131{
132 register Lisp_Object i0_cdr, i0_sym, i1_val;
133 register i1_len;
134
135 if (DEFAULT_INTERVAL_P (i0) && DEFAULT_INTERVAL_P (i1))
136 return 1;
137
138 i1_len = XFASTINT (Flength (i1->plist));
139 if (i1_len & 0x1) /* Paranoia -- plists are always even */
140 abort ();
141 i1_len /= 2;
142 i0_cdr = i0->plist;
143 while (!NILP (i0_cdr))
144 {
145 /* Lengths of the two plists were unequal */
146 if (i1_len == 0)
147 return 0;
148
149 i0_sym = Fcar (i0_cdr);
150 i1_val = Fmemq (i0_sym, i1->plist);
151
152 /* i0 has something i1 doesn't */
153 if (EQ (i1_val, Qnil))
154 return 0;
155
156 /* i0 and i1 both have sym, but it has different values in each */
157 i0_cdr = Fcdr (i0_cdr);
158 if (! Fequal (i1_val, Fcar (i0_cdr)))
159 return 0;
160
161 i0_cdr = Fcdr (i0_cdr);
162 i1_len--;
163 }
164
165 /* Lengths of the two plists were unequal */
166 if (i1_len > 0)
167 return 0;
168
169 return 1;
170}
171\f
172static int icount;
173static int idepth;
174static int zero_length;
175
a50699fd
JA
176/* Traverse an interval tree TREE, performing FUNCTION on each node.
177
178 Perhaps we should pass the depth as an argument. */
179
180void
e0b63493 181traverse_intervals (tree, position, depth, function)
a50699fd 182 INTERVAL tree;
e0b63493 183 int position, depth;
a50699fd
JA
184 void (* function) ();
185{
186 if (NULL_INTERVAL_P (tree))
187 return;
188
e0b63493 189 traverse_intervals (tree->left, position, depth + 1, function);
a50699fd
JA
190 position += LEFT_TOTAL_LENGTH (tree);
191 tree->position = position;
192 (*function) (tree);
193 position += LENGTH (tree);
e0b63493 194 traverse_intervals (tree->right, position, depth + 1, function);
a50699fd
JA
195}
196\f
197#if 0
198/* These functions are temporary, for debugging purposes only. */
199
200INTERVAL search_interval, found_interval;
201
202void
203check_for_interval (i)
204 register INTERVAL i;
205{
206 if (i == search_interval)
207 {
208 found_interval = i;
209 icount++;
210 }
211}
212
213INTERVAL
214search_for_interval (i, tree)
215 register INTERVAL i, tree;
216{
217 icount = 0;
218 search_interval = i;
219 found_interval = NULL_INTERVAL;
e0b63493 220 traverse_intervals (tree, 1, 0, &check_for_interval);
a50699fd
JA
221 return found_interval;
222}
223
224static void
225inc_interval_count (i)
226 INTERVAL i;
227{
228 icount++;
229 if (LENGTH (i) == 0)
230 zero_length++;
231 if (depth > idepth)
232 idepth = depth;
233}
234
235int
236count_intervals (i)
237 register INTERVAL i;
238{
239 icount = 0;
240 idepth = 0;
241 zero_length = 0;
e0b63493 242 traverse_intervals (i, 1, 0, &inc_interval_count);
a50699fd
JA
243
244 return icount;
245}
246
247static INTERVAL
248root_interval (interval)
249 INTERVAL interval;
250{
251 register INTERVAL i = interval;
252
253 while (! ROOT_INTERVAL_P (i))
254 i = i->parent;
255
256 return i;
257}
258#endif
259\f
260/* Assuming that a left child exists, perform the following operation:
261
262 A B
263 / \ / \
264 B => A
265 / \ / \
266 c c
267*/
268
269static INTERVAL
270rotate_right (interval)
271 INTERVAL interval;
272{
273 INTERVAL i;
274 INTERVAL B = interval->left;
275 int len = LENGTH (interval);
276
277 /* Deal with any Parent of A; make it point to B. */
278 if (! ROOT_INTERVAL_P (interval))
279 if (AM_LEFT_CHILD (interval))
280 interval->parent->left = interval->left;
281 else
282 interval->parent->right = interval->left;
283 interval->left->parent = interval->parent;
284
285 /* B gets the same length as A, since it get A's position in the tree. */
286 interval->left->total_length = interval->total_length;
287
288 /* B becomes the parent of A. */
289 i = interval->left->right;
290 interval->left->right = interval;
291 interval->parent = interval->left;
292
293 /* A gets c as left child. */
294 interval->left = i;
295 if (! NULL_INTERVAL_P (i))
296 i->parent = interval;
297 interval->total_length = (len + LEFT_TOTAL_LENGTH (interval)
298 + RIGHT_TOTAL_LENGTH (interval));
299
300 return B;
301}
302\f
303/* Assuming that a right child exists, perform the following operation:
304
305 A B
306 / \ / \
307 B => A
308 / \ / \
309 c c
310*/
311
312static INTERVAL
313rotate_left (interval)
314 INTERVAL interval;
315{
316 INTERVAL i;
317 INTERVAL B = interval->right;
318 int len = LENGTH (interval);
319
320 /* Deal with the parent of A. */
321 if (! ROOT_INTERVAL_P (interval))
322 if (AM_LEFT_CHILD (interval))
323 interval->parent->left = interval->right;
324 else
325 interval->parent->right = interval->right;
326 interval->right->parent = interval->parent;
327
328 /* B must have the same total length of A. */
329 interval->right->total_length = interval->total_length;
330
331 /* Make B the parent of A */
332 i = interval->right->left;
333 interval->right->left = interval;
334 interval->parent = interval->right;
335
336 /* Make A point to c */
337 interval->right = i;
338 if (! NULL_INTERVAL_P (i))
339 i->parent = interval;
340 interval->total_length = (len + LEFT_TOTAL_LENGTH (interval)
341 + RIGHT_TOTAL_LENGTH (interval));
342
343 return B;
344}
345\f
90ba40fc
JA
346/* Split INTERVAL into two pieces, starting the second piece at character
347 position OFFSET (counting from 1), relative to INTERVAL. The right-hand
348 piece (second, lexicographically) is returned.
349
350 The size and position fields of the two intervals are set based upon
351 those of the original interval. The property list of the new interval
352 is reset, thus it is up to the caller to do the right thing with the
353 result.
a50699fd
JA
354
355 Note that this does not change the position of INTERVAL; if it is a root,
356 it is still a root after this operation. */
357
358INTERVAL
90ba40fc 359split_interval_right (interval, offset)
a50699fd 360 INTERVAL interval;
90ba40fc 361 int offset;
a50699fd
JA
362{
363 INTERVAL new = make_interval ();
364 int position = interval->position;
90ba40fc 365 int new_length = LENGTH (interval) - offset + 1;
a50699fd 366
90ba40fc 367 new->position = position + offset - 1;
a50699fd 368 new->parent = interval;
a50699fd
JA
369
370 if (LEAF_INTERVAL_P (interval) || NULL_RIGHT_CHILD (interval))
371 {
372 interval->right = new;
373 new->total_length = new_length;
374
375 return new;
376 }
377
378 /* Insert the new node between INTERVAL and its right child. */
379 new->right = interval->right;
380 interval->right->parent = new;
381 interval->right = new;
382
383 new->total_length = new_length + new->right->total_length;
384
385 return new;
386}
387
90ba40fc
JA
388/* Split INTERVAL into two pieces, starting the second piece at character
389 position OFFSET (counting from 1), relative to INTERVAL. The left-hand
390 piece (first, lexicographically) is returned.
a50699fd 391
90ba40fc
JA
392 The size and position fields of the two intervals are set based upon
393 those of the original interval. The property list of the new interval
394 is reset, thus it is up to the caller to do the right thing with the
395 result.
396
397 Note that this does not change the position of INTERVAL; if it is a root,
398 it is still a root after this operation. */
a50699fd
JA
399
400INTERVAL
90ba40fc 401split_interval_left (interval, offset)
a50699fd 402 INTERVAL interval;
90ba40fc 403 int offset;
a50699fd
JA
404{
405 INTERVAL new = make_interval ();
406 int position = interval->position;
90ba40fc 407 int new_length = offset - 1;
a50699fd 408
a50699fd 409 new->position = interval->position;
90ba40fc 410 interval->position = interval->position + offset - 1;
a50699fd
JA
411 new->parent = interval;
412
413 if (NULL_LEFT_CHILD (interval))
414 {
415 interval->left = new;
416 new->total_length = new_length;
417
418 return new;
419 }
420
421 /* Insert the new node between INTERVAL and its left child. */
422 new->left = interval->left;
423 new->left->parent = new;
424 interval->left = new;
425 new->total_length = LENGTH (new) + LEFT_TOTAL_LENGTH (new);
426
427 return new;
428}
429\f
90ba40fc
JA
430/* Find the interval containing text position POSITION in the text
431 represented by the interval tree TREE. POSITION is relative to
432 the beginning of that text.
a50699fd 433
90ba40fc
JA
434 The `position' field, which is a cache of an interval's position,
435 is updated in the interval found. Other functions (e.g., next_interval)
436 will update this cache based on the result of find_interval. */
437
438INLINE INTERVAL
a50699fd
JA
439find_interval (tree, position)
440 register INTERVAL tree;
441 register int position;
442{
443 register int relative_position = position;
444
445 if (NULL_INTERVAL_P (tree))
446 return NULL_INTERVAL;
447
448 if (position > TOTAL_LENGTH (tree))
449 abort (); /* Paranoia */
450#if 0
451 position = TOTAL_LENGTH (tree);
452#endif
453
454 while (1)
455 {
456 if (relative_position <= LEFT_TOTAL_LENGTH (tree))
457 {
458 tree = tree->left;
459 }
460 else if (relative_position > (TOTAL_LENGTH (tree)
461 - RIGHT_TOTAL_LENGTH (tree)))
462 {
463 relative_position -= (TOTAL_LENGTH (tree)
464 - RIGHT_TOTAL_LENGTH (tree));
465 tree = tree->right;
466 }
467 else
468 {
469 tree->position = LEFT_TOTAL_LENGTH (tree)
470 + position - relative_position + 1;
471 return tree;
472 }
473 }
474}
475\f
476/* Find the succeeding interval (lexicographically) to INTERVAL.
90ba40fc
JA
477 Sets the `position' field based on that of INTERVAL (see
478 find_interval). */
a50699fd
JA
479
480INTERVAL
481next_interval (interval)
482 register INTERVAL interval;
483{
484 register INTERVAL i = interval;
485 register int next_position;
486
487 if (NULL_INTERVAL_P (i))
488 return NULL_INTERVAL;
489 next_position = interval->position + LENGTH (interval);
490
491 if (! NULL_RIGHT_CHILD (i))
492 {
493 i = i->right;
494 while (! NULL_LEFT_CHILD (i))
495 i = i->left;
496
497 i->position = next_position;
498 return i;
499 }
500
501 while (! NULL_PARENT (i))
502 {
503 if (AM_LEFT_CHILD (i))
504 {
505 i = i->parent;
506 i->position = next_position;
507 return i;
508 }
509
510 i = i->parent;
511 }
512
513 return NULL_INTERVAL;
514}
515
516/* Find the preceding interval (lexicographically) to INTERVAL.
90ba40fc
JA
517 Sets the `position' field based on that of INTERVAL (see
518 find_interval). */
a50699fd
JA
519
520INTERVAL
521previous_interval (interval)
522 register INTERVAL interval;
523{
524 register INTERVAL i;
525 register position_of_previous;
526
527 if (NULL_INTERVAL_P (interval))
528 return NULL_INTERVAL;
529
530 if (! NULL_LEFT_CHILD (interval))
531 {
532 i = interval->left;
533 while (! NULL_RIGHT_CHILD (i))
534 i = i->right;
535
536 i->position = interval->position - LENGTH (i);
537 return i;
538 }
539
540 i = interval;
541 while (! NULL_PARENT (i))
542 {
543 if (AM_RIGHT_CHILD (i))
544 {
545 i = i->parent;
546
547 i->position = interval->position - LENGTH (i);
548 return i;
549 }
550 i = i->parent;
551 }
552
553 return NULL_INTERVAL;
554}
555\f
90ba40fc 556#if 0
a50699fd
JA
557/* Traverse a path down the interval tree TREE to the interval
558 containing POSITION, adjusting all nodes on the path for
559 an addition of LENGTH characters. Insertion between two intervals
560 (i.e., point == i->position, where i is second interval) means
561 text goes into second interval.
562
563 Modifications are needed to handle the hungry bits -- after simply
564 finding the interval at position (don't add length going down),
565 if it's the beginning of the interval, get the previous interval
566 and check the hugry bits of both. Then add the length going back up
567 to the root. */
568
569static INTERVAL
570adjust_intervals_for_insertion (tree, position, length)
571 INTERVAL tree;
572 int position, length;
573{
574 register int relative_position;
575 register INTERVAL this;
576
577 if (TOTAL_LENGTH (tree) == 0) /* Paranoia */
578 abort ();
579
580 /* If inserting at point-max of a buffer, that position
581 will be out of range */
582 if (position > TOTAL_LENGTH (tree))
583 position = TOTAL_LENGTH (tree);
584 relative_position = position;
585 this = tree;
586
587 while (1)
588 {
589 if (relative_position <= LEFT_TOTAL_LENGTH (this))
590 {
591 this->total_length += length;
592 this = this->left;
593 }
594 else if (relative_position > (TOTAL_LENGTH (this)
595 - RIGHT_TOTAL_LENGTH (this)))
596 {
597 relative_position -= (TOTAL_LENGTH (this)
598 - RIGHT_TOTAL_LENGTH (this));
599 this->total_length += length;
600 this = this->right;
601 }
602 else
603 {
604 /* If we are to use zero-length intervals as buffer pointers,
605 then this code will have to change. */
606 this->total_length += length;
607 this->position = LEFT_TOTAL_LENGTH (this)
608 + position - relative_position + 1;
609 return tree;
610 }
611 }
612}
90ba40fc
JA
613#endif
614
615/* Effect an adjustment corresponding to the addition of LENGTH characters
616 of text. Do this by finding the interval containing POSITION in the
617 interval tree TREE, and then adjusting all of it's ancestors by adding
618 LENGTH to them.
619
620 If POSITION is the first character of an interval, meaning that point
621 is actually between the two intervals, make the new text belong to
622 the interval which is "sticky".
623
1d1d7ba0 624 If both intervals are "sticky", then make them belong to the left-most
90ba40fc
JA
625 interval. Another possibility would be to create a new interval for
626 this text, and make it have the merged properties of both ends. */
627
628static INTERVAL
629adjust_intervals_for_insertion (tree, position, length)
630 INTERVAL tree;
631 int position, length;
632{
633 register INTERVAL i;
634
635 if (TOTAL_LENGTH (tree) == 0) /* Paranoia */
636 abort ();
637
638 /* If inserting at point-max of a buffer, that position
639 will be out of range. */
640 if (position > TOTAL_LENGTH (tree))
641 position = TOTAL_LENGTH (tree);
642
643 i = find_interval (tree, position);
644 /* If we are positioned between intervals, check the stickiness of
645 both of them. */
646 if (position == i->position
647 && position != 1)
648 {
249a6da9 649 register INTERVAL prev = previous_interval (i);
90ba40fc
JA
650
651 /* If both intervals are sticky here, then default to the
652 left-most one. But perhaps we should create a new
653 interval here instead... */
7b1d5b85 654 if (END_STICKY_P (prev))
90ba40fc
JA
655 i = prev;
656 }
657
658 while (! NULL_INTERVAL_P (i))
659 {
660 i->total_length += length;
249a6da9 661 i = i->parent;
90ba40fc
JA
662 }
663
664 return tree;
665}
a50699fd 666\f
90ba40fc
JA
667/* Delete an node I from its interval tree by merging its subtrees
668 into one subtree which is then returned. Caller is responsible for
a50699fd
JA
669 storing the resulting subtree into its parent. */
670
671static INTERVAL
672delete_node (i)
673 register INTERVAL i;
674{
675 register INTERVAL migrate, this;
676 register int migrate_amt;
677
678 if (NULL_INTERVAL_P (i->left))
679 return i->right;
680 if (NULL_INTERVAL_P (i->right))
681 return i->left;
682
683 migrate = i->left;
684 migrate_amt = i->left->total_length;
685 this = i->right;
686 this->total_length += migrate_amt;
687 while (! NULL_INTERVAL_P (this->left))
688 {
689 this = this->left;
690 this->total_length += migrate_amt;
691 }
692 this->left = migrate;
693 migrate->parent = this;
694
695 return i->right;
696}
697
698/* Delete interval I from its tree by calling `delete_node'
699 and properly connecting the resultant subtree.
700
701 I is presumed to be empty; that is, no adjustments are made
702 for the length of I. */
703
704void
705delete_interval (i)
706 register INTERVAL i;
707{
708 register INTERVAL parent;
709 int amt = LENGTH (i);
710
711 if (amt > 0) /* Only used on zero-length intervals now. */
712 abort ();
713
714 if (ROOT_INTERVAL_P (i))
715 {
716 Lisp_Object owner = (Lisp_Object) i->parent;
717 parent = delete_node (i);
718 if (! NULL_INTERVAL_P (parent))
719 parent->parent = (INTERVAL) owner;
720
721 if (XTYPE (owner) == Lisp_Buffer)
722 XBUFFER (owner)->intervals = parent;
723 else if (XTYPE (owner) == Lisp_String)
724 XSTRING (owner)->intervals = parent;
725 else
726 abort ();
727
728 return;
729 }
730
731 parent = i->parent;
732 if (AM_LEFT_CHILD (i))
733 {
734 parent->left = delete_node (i);
735 if (! NULL_INTERVAL_P (parent->left))
736 parent->left->parent = parent;
737 }
738 else
739 {
740 parent->right = delete_node (i);
741 if (! NULL_INTERVAL_P (parent->right))
742 parent->right->parent = parent;
743 }
744}
745\f
1d1d7ba0
JA
746/* Find the interval in TREE corresponding to the character position FROM
747 and delete as much as possible of AMOUNT from that interval, starting
748 after the relative position of FROM within it. Return the amount
749 actually deleted, and if the interval was zeroed-out, delete that
750 interval node from the tree.
a50699fd 751
1d1d7ba0
JA
752 Do this by recursing down TREE to the interval in question, and
753 deleting the appropriate amount of text. */
a50699fd
JA
754
755static int
756interval_deletion_adjustment (tree, from, amount)
757 register INTERVAL tree;
758 register int from, amount;
759{
760 register int relative_position = from;
761
762 if (NULL_INTERVAL_P (tree))
763 return 0;
764
765 /* Left branch */
766 if (relative_position <= LEFT_TOTAL_LENGTH (tree))
767 {
768 int subtract = interval_deletion_adjustment (tree->left,
769 relative_position,
770 amount);
771 tree->total_length -= subtract;
772 return subtract;
773 }
774 /* Right branch */
775 else if (relative_position > (TOTAL_LENGTH (tree)
776 - RIGHT_TOTAL_LENGTH (tree)))
777 {
778 int subtract;
779
780 relative_position -= (tree->total_length
781 - RIGHT_TOTAL_LENGTH (tree));
782 subtract = interval_deletion_adjustment (tree->right,
783 relative_position,
784 amount);
785 tree->total_length -= subtract;
786 return subtract;
787 }
788 /* Here -- this node */
789 else
790 {
791 /* If this is a zero-length, marker interval, then
792 we must skip it. */
793
794 if (relative_position == LEFT_TOTAL_LENGTH (tree) + 1)
795 {
796 /* This means we're deleting from the beginning of this interval. */
797 register int my_amount = LENGTH (tree);
798
799 if (amount < my_amount)
800 {
801 tree->total_length -= amount;
802 return amount;
803 }
804 else
805 {
806 tree->total_length -= my_amount;
807 if (LENGTH (tree) != 0)
808 abort (); /* Paranoia */
809
810 delete_interval (tree);
811 return my_amount;
812 }
813 }
814 else /* Deleting starting in the middle. */
815 {
816 register int my_amount = ((tree->total_length
817 - RIGHT_TOTAL_LENGTH (tree))
818 - relative_position + 1);
819
820 if (amount <= my_amount)
821 {
822 tree->total_length -= amount;
823 return amount;
824 }
825 else
826 {
827 tree->total_length -= my_amount;
828 return my_amount;
829 }
830 }
831 }
832
1d1d7ba0 833 /* Never reach here */
a50699fd
JA
834 abort ();
835}
836
1d1d7ba0
JA
837/* Effect the adjustments neccessary to the interval tree of BUFFER
838 to correspond to the deletion of LENGTH characters from that buffer
839 text. The deletion is effected at position START (relative to the
840 buffer). */
841
a50699fd
JA
842static void
843adjust_intervals_for_deletion (buffer, start, length)
844 struct buffer *buffer;
845 int start, length;
846{
847 register int left_to_delete = length;
848 register INTERVAL tree = buffer->intervals;
849 register int deleted;
850
851 if (NULL_INTERVAL_P (tree))
852 return;
853
854 if (length == TOTAL_LENGTH (tree))
855 {
856 buffer->intervals = NULL_INTERVAL;
857 return;
858 }
859
860 if (ONLY_INTERVAL_P (tree))
861 {
862 tree->total_length -= length;
863 return;
864 }
865
866 if (start > TOTAL_LENGTH (tree))
867 start = TOTAL_LENGTH (tree);
868 while (left_to_delete > 0)
869 {
870 left_to_delete -= interval_deletion_adjustment (tree, start,
871 left_to_delete);
872 tree = buffer->intervals;
873 if (left_to_delete == tree->total_length)
874 {
875 buffer->intervals = NULL_INTERVAL;
876 return;
877 }
878 }
879}
880\f
1d1d7ba0
JA
881/* Make the adjustments neccessary to the interval tree of BUFFER to
882 represent an addition or deletion of LENGTH characters starting
883 at position START. Addition or deletion is indicated by the sign
884 of LENGTH. */
a50699fd
JA
885
886INLINE void
887offset_intervals (buffer, start, length)
888 struct buffer *buffer;
889 int start, length;
890{
891 if (NULL_INTERVAL_P (buffer->intervals) || length == 0)
892 return;
893
894 if (length > 0)
895 adjust_intervals_for_insertion (buffer->intervals, start, length);
896 else
897 adjust_intervals_for_deletion (buffer, start, -length);
898}
9c79dd1b
JA
899\f
900/* Merge interval I with its lexicographic successor. The resulting
901 interval is returned, and has the properties of the original
902 successor. The properties of I are lost. I is removed from the
903 interval tree.
904
905 IMPORTANT:
906 The caller must verify that this is not the last (rightmost)
907 interval. */
908
909INTERVAL
910merge_interval_right (i)
911 register INTERVAL i;
912{
913 register int absorb = LENGTH (i);
914 register INTERVAL successor;
915
916 /* Zero out this interval. */
917 i->total_length -= absorb;
918
919 /* Find the succeeding interval. */
920 if (! NULL_RIGHT_CHILD (i)) /* It's below us. Add absorb
921 as we descend. */
922 {
923 successor = i->right;
924 while (! NULL_LEFT_CHILD (successor))
925 {
926 successor->total_length += absorb;
927 successor = successor->left;
928 }
929
930 successor->total_length += absorb;
931 delete_interval (i);
932 return successor;
933 }
934
935 successor = i;
936 while (! NULL_PARENT (successor)) /* It's above us. Subtract as
937 we ascend. */
938 {
939 if (AM_LEFT_CHILD (successor))
940 {
941 successor = successor->parent;
942 delete_interval (i);
943 return successor;
944 }
945
946 successor = successor->parent;
947 successor->total_length -= absorb;
948 }
949
950 /* This must be the rightmost or last interval and cannot
951 be merged right. The caller should have known. */
952 abort ();
953}
954\f
955/* Merge interval I with its lexicographic predecessor. The resulting
956 interval is returned, and has the properties of the original predecessor.
957 The properties of I are lost. Interval node I is removed from the tree.
958
959 IMPORTANT:
960 The caller must verify that this is not the first (leftmost) interval. */
961
962INTERVAL
963merge_interval_left (i)
964 register INTERVAL i;
965{
966 register int absorb = LENGTH (i);
967 register INTERVAL predecessor;
968
969 /* Zero out this interval. */
970 i->total_length -= absorb;
971
972 /* Find the preceding interval. */
973 if (! NULL_LEFT_CHILD (i)) /* It's below us. Go down,
974 adding ABSORB as we go. */
975 {
976 predecessor = i->left;
977 while (! NULL_RIGHT_CHILD (predecessor))
978 {
979 predecessor->total_length += absorb;
980 predecessor = predecessor->right;
981 }
982
983 predecessor->total_length += absorb;
984 delete_interval (i);
985 return predecessor;
986 }
987
988 predecessor = i;
989 while (! NULL_PARENT (predecessor)) /* It's above us. Go up,
990 subtracting ABSORB. */
991 {
992 if (AM_RIGHT_CHILD (predecessor))
993 {
994 predecessor = predecessor->parent;
995 delete_interval (i);
996 return predecessor;
997 }
998
999 predecessor = predecessor->parent;
1000 predecessor->total_length -= absorb;
1001 }
a50699fd 1002
9c79dd1b
JA
1003 /* This must be the leftmost or first interval and cannot
1004 be merged left. The caller should have known. */
1005 abort ();
1006}
1007\f
1d1d7ba0
JA
1008/* Make an exact copy of interval tree SOURCE which descends from
1009 PARENT. This is done by recursing through SOURCE, copying
1010 the current interval and its properties, and then adjusting
1011 the pointers of the copy. */
1012
a50699fd
JA
1013static INTERVAL
1014reproduce_tree (source, parent)
1015 INTERVAL source, parent;
1016{
1017 register INTERVAL t = make_interval ();
1018
1019 bcopy (source, t, INTERVAL_SIZE);
1020 copy_properties (source, t);
1021 t->parent = parent;
1022 if (! NULL_LEFT_CHILD (source))
1023 t->left = reproduce_tree (source->left, t);
1024 if (! NULL_RIGHT_CHILD (source))
1025 t->right = reproduce_tree (source->right, t);
1026
1027 return t;
1028}
1029
1d1d7ba0
JA
1030/* Make a new interval of length LENGTH starting at START in the
1031 group of intervals INTERVALS, which is actually an interval tree.
1032 Returns the new interval.
1033
1034 Generate an error if the new positions would overlap an existing
1035 interval. */
1036
a50699fd
JA
1037static INTERVAL
1038make_new_interval (intervals, start, length)
1039 INTERVAL intervals;
1040 int start, length;
1041{
1042 INTERVAL slot;
1043
1044 slot = find_interval (intervals, start);
1045 if (start + length > slot->position + LENGTH (slot))
1046 error ("Interval would overlap");
1047
1048 if (start == slot->position && length == LENGTH (slot))
1049 return slot;
1050
1051 if (slot->position == start)
1052 {
1053 /* New right node. */
1054 split_interval_right (slot, length + 1);
1055 return slot;
1056 }
1057
1058 if (slot->position + LENGTH (slot) == start + length)
1059 {
1060 /* New left node. */
1061 split_interval_left (slot, LENGTH (slot) - length + 1);
1062 return slot;
1063 }
1064
1065 /* Convert interval SLOT into three intervals. */
1066 split_interval_left (slot, start - slot->position + 1);
1067 split_interval_right (slot, length + 1);
1068 return slot;
1069}
1070
9c79dd1b 1071/* Insert the intervals of SOURCE into BUFFER at POSITION.
a50699fd
JA
1072
1073 This is used in insdel.c when inserting Lisp_Strings into
9c79dd1b 1074 the buffer. The text corresponding to SOURCE is already in
a50699fd
JA
1075 the buffer when this is called. The intervals of new tree are
1076 those belonging to the string being inserted; a copy is not made.
1077
1078 If the inserted text had no intervals associated, this function
1079 simply returns -- offset_intervals should handle placing the
90ba40fc 1080 text in the correct interval, depending on the sticky bits.
a50699fd
JA
1081
1082 If the inserted text had properties (intervals), then there are two
1083 cases -- either insertion happened in the middle of some interval,
1084 or between two intervals.
1085
1086 If the text goes into the middle of an interval, then new
1087 intervals are created in the middle with only the properties of
1088 the new text, *unless* the macro MERGE_INSERTIONS is true, in
1089 which case the new text has the union of its properties and those
1090 of the text into which it was inserted.
1091
1092 If the text goes between two intervals, then if neither interval
90ba40fc
JA
1093 had its appropriate sticky property set (front_sticky, rear_sticky),
1094 the new text has only its properties. If one of the sticky properties
a50699fd
JA
1095 is set, then the new text "sticks" to that region and its properties
1096 depend on merging as above. If both the preceding and succeding
90ba40fc
JA
1097 intervals to the new text are "sticky", then the new text retains
1098 only its properties, as if neither sticky property were set. Perhaps
a50699fd
JA
1099 we should consider merging all three sets of properties onto the new
1100 text... */
1101
1102void
9c79dd1b
JA
1103graft_intervals_into_buffer (source, position, buffer)
1104 INTERVAL source;
a50699fd 1105 int position;
9c79dd1b 1106 struct buffer *buffer;
a50699fd
JA
1107{
1108 register INTERVAL under, over, this;
9c79dd1b 1109 register INTERVAL tree = buffer->intervals;
a50699fd
JA
1110
1111 /* If the new text has no properties, it becomes part of whatever
1112 interval it was inserted into. */
9c79dd1b 1113 if (NULL_INTERVAL_P (source))
a50699fd
JA
1114 return;
1115
1116 /* Paranoia -- the text has already been added, so this buffer
1117 should be of non-zero length. */
1118 if (TOTAL_LENGTH (tree) == 0)
1119 abort ();
1120
1121 if (NULL_INTERVAL_P (tree))
1122 {
1123 /* The inserted text constitutes the whole buffer, so
1124 simply copy over the interval structure. */
249a6da9 1125 if (BUF_Z (buffer) == TOTAL_LENGTH (source))
a50699fd 1126 {
9c79dd1b 1127 buffer->intervals = reproduce_tree (source, tree->parent);
a50699fd
JA
1128 /* Explicitly free the old tree here. */
1129
1130 return;
1131 }
1132
1133 /* Create an interval tree in which to place a copy
1134 of the intervals of the inserted string. */
1135 {
249a6da9
JA
1136 Lisp_Object buf;
1137 XSET (buf, Lisp_Buffer, buffer);
a50699fd
JA
1138 create_root_interval (buffer);
1139 }
1140 }
1141 else
9c79dd1b 1142 if (TOTAL_LENGTH (tree) == TOTAL_LENGTH (source))
a50699fd
JA
1143
1144 /* If the buffer contains only the new string, but
1145 there was already some interval tree there, then it may be
1146 some zero length intervals. Eventually, do something clever
1147 about inserting properly. For now, just waste the old intervals. */
1148 {
9c79dd1b 1149 buffer->intervals = reproduce_tree (source, tree->parent);
a50699fd
JA
1150 /* Explicitly free the old tree here. */
1151
1152 return;
1153 }
1154
1155 this = under = find_interval (tree, position);
1156 if (NULL_INTERVAL_P (under)) /* Paranoia */
1157 abort ();
9c79dd1b 1158 over = find_interval (source, 1);
a50699fd
JA
1159
1160 /* Insertion between intervals */
1161 if (position == under->position)
1162 {
1163 /* First interval -- none precede it. */
1164 if (position == 1)
1165 {
7b1d5b85 1166 if (! FRONT_STICKY_P (under))
a50699fd
JA
1167 /* The inserted string keeps its own properties. */
1168 while (! NULL_INTERVAL_P (over))
1169 {
1170 position = LENGTH (over) + 1;
1171 this = split_interval_left (this, position);
1172 copy_properties (over, this);
1173 over = next_interval (over);
1174 }
1175 else
9c79dd1b
JA
1176 /* This string "sticks" to the first interval, `under',
1177 which means it gets those properties. */
a50699fd
JA
1178 while (! NULL_INTERVAL_P (over))
1179 {
1180 position = LENGTH (over) + 1;
1181 this = split_interval_left (this, position);
1182 copy_properties (under, this);
1183 if (MERGE_INSERTIONS (under))
1184 merge_properties (over, this);
1185 over = next_interval (over);
1186 }
1187 }
1188 else
1189 {
1190 INTERVAL prev = previous_interval (under);
1191 if (NULL_INTERVAL_P (prev))
1192 abort ();
1193
7b1d5b85 1194 if (END_STICKY_P (prev))
a50699fd 1195 {
7b1d5b85 1196 if (FRONT_STICKY_P (under))
90ba40fc 1197 /* The intervals go inbetween as the two sticky
a50699fd
JA
1198 properties cancel each other. Should we change
1199 this policy? */
1200 while (! NULL_INTERVAL_P (over))
1201 {
1202 position = LENGTH (over) + 1;
1203 this = split_interval_left (this, position);
1204 copy_properties (over, this);
1205 over = next_interval (over);
1206 }
1207 else
1208 /* The intervals stick to prev */
1209 while (! NULL_INTERVAL_P (over))
1210 {
1211 position = LENGTH (over) + 1;
1212 this = split_interval_left (this, position);
1213 copy_properties (prev, this);
1214 if (MERGE_INSERTIONS (prev))
1215 merge_properties (over, this);
1216 over = next_interval (over);
1217 }
1218 }
1219 else
1220 {
7b1d5b85 1221 if (FRONT_STICKY_P (under))
9c79dd1b
JA
1222 /* The inserted text "sticks" to the interval `under',
1223 which means it gets those properties. */
a50699fd
JA
1224 while (! NULL_INTERVAL_P (over))
1225 {
1226 position = LENGTH (over) + 1;
1227 this = split_interval_left (this, position);
1228 copy_properties (under, this);
1229 if (MERGE_INSERTIONS (under))
1230 merge_properties (over, this);
1231 over = next_interval (over);
1232 }
1233 else
1234 /* The intervals go inbetween */
1235 while (! NULL_INTERVAL_P (over))
1236 {
1237 position = LENGTH (over) + 1;
1238 this = split_interval_left (this, position);
1239 copy_properties (over, this);
1240 over = next_interval (over);
1241 }
1242 }
1243 }
1244
9c79dd1b 1245 buffer->intervals = balance_intervals (buffer->intervals);
a50699fd
JA
1246 return;
1247 }
1248
1249 /* Here for insertion in the middle of an interval. */
1250
9c79dd1b 1251 if (TOTAL_LENGTH (source) < LENGTH (this))
a50699fd
JA
1252 {
1253 INTERVAL end_unchanged
9c79dd1b 1254 = split_interval_right (this, TOTAL_LENGTH (source) + 1);
a50699fd
JA
1255 copy_properties (under, end_unchanged);
1256 }
1257
1258 position = position - tree->position + 1;
1259 while (! NULL_INTERVAL_P (over))
1260 {
1261 this = split_interval_right (under, position);
1262 copy_properties (over, this);
1263 if (MERGE_INSERTIONS (under))
1264 merge_properties (under, this);
1265
1266 position = LENGTH (over) + 1;
1267 over = next_interval (over);
1268 }
1269
9c79dd1b 1270 buffer->intervals = balance_intervals (buffer->intervals);
a50699fd
JA
1271 return;
1272}
1273
a50699fd
JA
1274/* Set point in BUFFER to POSITION. If the target position is in
1275 an invisible interval which is not displayed with a special glyph,
1276 skip intervals until we find one. Point may be at the first
1277 position of an invisible interval, if it is displayed with a
d7e3e52b 1278 special glyph. */
a50699fd
JA
1279
1280void
1281set_point (position, buffer)
1282 register int position;
1283 register struct buffer *buffer;
1284{
1285 register INTERVAL to, from, target;
1286 register int iposition = position;
1287 int buffer_point;
1288 register Lisp_Object obj;
1289 int backwards = (position < BUF_PT (buffer)) ? 1 : 0;
9c79dd1b 1290 int old_position = buffer->text.pt;
a50699fd
JA
1291
1292 if (position == buffer->text.pt)
1293 return;
1294
1295 if (NULL_INTERVAL_P (buffer->intervals))
1296 {
1297 buffer->text.pt = position;
1298 return;
1299 }
1300
1301 /* Perhaps we should just change `position' to the limit. */
1302 if (position > BUF_Z (buffer) || position < BUF_BEG (buffer))
1303 abort ();
1304
1305 /* Position Z is really one past the last char in the buffer. */
1306 if (position == BUF_Z (buffer))
1307 iposition = position - 1;
1308
1309 to = find_interval (buffer->intervals, iposition);
1310 buffer_point =(BUF_PT (buffer) == BUF_Z (buffer)
1311 ? BUF_Z (buffer) - 1
1312 : BUF_PT (buffer));
9c79dd1b
JA
1313
1314 /* We could cache this and save time. */
a50699fd 1315 from = find_interval (buffer->intervals, buffer_point);
9c79dd1b 1316
a50699fd
JA
1317 if (NULL_INTERVAL_P (to) || NULL_INTERVAL_P (from))
1318 abort (); /* Paranoia */
1319
1320 /* Moving within an interval */
1321 if (to == from && INTERVAL_VISIBLE_P (to))
1322 {
1323 buffer->text.pt = position;
1324 return;
1325 }
1326
1327 /* Here for the case of moving into another interval. */
1328
1329 target = to;
1330 while (! INTERVAL_VISIBLE_P (to) && ! DISPLAY_INVISIBLE_GLYPH (to)
1331 && ! NULL_INTERVAL_P (to))
1332 to = (backwards ? previous_interval (to) : next_interval (to));
1333 if (NULL_INTERVAL_P (to))
1334 return;
1335
1336 /* Here we know we are actually moving to another interval. */
1337 if (INTERVAL_VISIBLE_P (to))
1338 {
1339 /* If we skipped some intervals, go to the closest point
1340 in the interval we've stopped at. */
1341 if (to != target)
1342 buffer->text.pt = (backwards
1343 ? to->position + LENGTH (to) - 1
1344 : to->position);
1345 else
1346 buffer->text.pt = position;
1347 }
1348 else
1349 buffer->text.pt = to->position;
1350
d7e3e52b
JA
1351 /* We run point-left and point-entered hooks here, iff the
1352 two intervals are not equivalent. These hooks take
1353 (old_point, new_point) as arguments. */
9c79dd1b
JA
1354 if (! intervals_equal (from, to))
1355 {
1356 Lisp_Object val;
1357
1358 val = Fget (Qpoint_left, from->plist);
1359 if (! NILP (val))
1360 call2 (val, old_position, position);
1361
1362 val = Fget (Qpoint_entered, to->plist);
1363 if (! NILP (val))
1364 call2 (val, old_position, position);
1365 }
a50699fd
JA
1366}
1367
9c79dd1b 1368/* Set point temporarily, without checking any text properties. */
a50699fd 1369
9c79dd1b
JA
1370INLINE void
1371temp_set_point (position, buffer)
1372 int position;
1373 struct buffer *buffer;
1374{
1375 buffer->text.pt = position;
1376}
1377
1378/* Check for read-only intervals and signal an error if we find one.
1379 Then check for any modification hooks in the range START up to
1380 (but not including) TO. Create a list of all these hooks in
1381 lexicographic order, eliminating consecutive extra copies of the
1382 same hook. Then call those hooks in order, with START and END - 1
1383 as arguments. */
a50699fd
JA
1384
1385void
1386verify_interval_modification (buf, start, end)
1387 struct buffer *buf;
1388 int start, end;
1389{
1390 register INTERVAL intervals = buf->intervals;
1391 register INTERVAL i;
249a6da9 1392 Lisp_Object hooks = Qnil;
9c79dd1b
JA
1393 register prev_mod_hook = Qnil;
1394 register Lisp_Object mod_hook;
1395 struct gcpro gcpro1;
a50699fd
JA
1396
1397 if (NULL_INTERVAL_P (intervals))
1398 return;
1399
1400 if (start > end)
1401 {
1402 int temp = start;
1403 start = end;
1404 end = temp;
1405 }
1406
1407 if (start == BUF_Z (buf))
1408 {
9c79dd1b 1409 /* This should not be getting called on empty buffers. */
a50699fd
JA
1410 if (BUF_Z (buf) == 1)
1411 abort ();
1412
1413 i = find_interval (intervals, start - 1);
90ba40fc 1414 if (! END_STICKY_P (i))
a50699fd
JA
1415 return;
1416 }
1417 else
1418 i = find_interval (intervals, start);
1419
1420 do
1421 {
a50699fd 1422 if (! INTERVAL_WRITABLE_P (i))
9c79dd1b
JA
1423 error ("Attempt to modify read-only text");
1424
a50699fd 1425 mod_hook = Fget (Qmodification, i->plist);
9c79dd1b
JA
1426 if (! NILP (mod_hook) && ! EQ (mod_hook, prev_mod_hook))
1427 {
1428 hooks = Fcons (mod_hook, hooks);
1429 prev_mod_hook = mod_hook;
1430 }
1431
a50699fd
JA
1432 i = next_interval (i);
1433 }
1434 while (! NULL_INTERVAL_P (i) && i->position <= end);
1435
9c79dd1b 1436 GCPRO1 (hooks);
a50699fd
JA
1437 hooks = Fnreverse (hooks);
1438 while (! EQ (hooks, Qnil))
9c79dd1b
JA
1439 {
1440 call2 (Fcar (hooks), start, end - 1);
1441 hooks = Fcdr (hooks);
1442 }
1443 UNGCPRO;
a50699fd
JA
1444}
1445
1446/* Balance an interval node if the amount of text in its left and right
1447 subtrees differs by more than the percentage specified by
1448 `interval-balance-threshold'. */
1449
1450static INTERVAL
1451balance_an_interval (i)
1452 INTERVAL i;
1453{
1454 register int total_children_size = (LEFT_TOTAL_LENGTH (i)
1455 + RIGHT_TOTAL_LENGTH (i));
1456 register int threshold = (XFASTINT (interval_balance_threshold)
1457 * (total_children_size / 100));
1458
1459 if (LEFT_TOTAL_LENGTH (i) > RIGHT_TOTAL_LENGTH (i)
1460 && (LEFT_TOTAL_LENGTH (i) - RIGHT_TOTAL_LENGTH (i)) > threshold)
1461 return rotate_right (i);
1462
1463 if (LEFT_TOTAL_LENGTH (i) > RIGHT_TOTAL_LENGTH (i)
1464 && (LEFT_TOTAL_LENGTH (i) - RIGHT_TOTAL_LENGTH (i)) > threshold)
1465 return rotate_right (i);
1466
1467#if 0
1468 if (LEFT_TOTAL_LENGTH (i) >
1469 (RIGHT_TOTAL_LENGTH (i) + XINT (interval_balance_threshold)))
1470 return rotate_right (i);
1471
1472 if (RIGHT_TOTAL_LENGTH (i) >
1473 (LEFT_TOTAL_LENGTH (i) + XINT (interval_balance_threshold)))
1474 return rotate_left (i);
1475#endif
1476
1477 return i;
1478}
1479
1480/* Balance the interval tree TREE. Balancing is by weight
1481 (the amount of text). */
1482
1483INTERVAL
1484balance_intervals (tree)
1485 register INTERVAL tree;
1486{
1487 register INTERVAL new_tree;
1488
1489 if (NULL_INTERVAL_P (tree))
1490 return NULL_INTERVAL;
1491
1492 new_tree = tree;
1493 do
1494 {
1495 tree = new_tree;
1496 new_tree = balance_an_interval (new_tree);
1497 }
1498 while (new_tree != tree);
1499
1500 return new_tree;
1501}
1502
9c79dd1b 1503/* Produce an interval tree reflecting the intervals in
a50699fd
JA
1504 TREE from START to START + LENGTH. */
1505
7b1d5b85 1506INTERVAL
a50699fd
JA
1507copy_intervals (tree, start, length)
1508 INTERVAL tree;
1509 int start, length;
1510{
1511 register INTERVAL i, new, t;
1512 register int got;
1513
1514 if (NULL_INTERVAL_P (tree) || length <= 0)
1515 return NULL_INTERVAL;
1516
1517 i = find_interval (tree, start);
1518 if (NULL_INTERVAL_P (i) || LENGTH (i) == 0)
1519 abort ();
1520
1521 /* If there is only one interval and it's the default, return nil. */
1522 if ((start - i->position + 1 + length) < LENGTH (i)
1523 && DEFAULT_INTERVAL_P (i))
1524 return NULL_INTERVAL;
1525
1526 new = make_interval ();
1527 new->position = 1;
1528 got = (LENGTH (i) - (start - i->position));
9c79dd1b 1529 new->total_length = length;
a50699fd
JA
1530 copy_properties (i, new);
1531
1532 t = new;
1533 while (got < length)
1534 {
1535 i = next_interval (i);
9c79dd1b 1536 t = split_interval_right (t, got + 1);
a50699fd
JA
1537 copy_properties (i, t);
1538 got += LENGTH (i);
1539 }
1540
1541 if (got > length)
1542 t->total_length -= (got - length);
1543
1544 return balance_intervals (new);
1545}
1546
a50699fd
JA
1547/* Give STRING the properties of BUFFER from POSITION to LENGTH. */
1548
d7e3e52b 1549INLINE void
a50699fd
JA
1550copy_intervals_to_string (string, buffer, position, length)
1551 Lisp_Object string, buffer;
1552 int position, length;
1553{
1554 INTERVAL interval_copy = copy_intervals (XBUFFER (buffer)->intervals,
1555 position, length);
1556 if (NULL_INTERVAL_P (interval_copy))
1557 return;
1558
1559 interval_copy->parent = (INTERVAL) string;
1560 XSTRING (string)->intervals = interval_copy;
1561}
d2f7a802
JA
1562
1563#endif /* USE_TEXT_PROPERTIES */