OSDN Git Service

touched all tk files to ease next import
[pf3gnuchains/pf3gnuchains4x.git] / tk / generic / tkTextIndex.c
1 /* 
2  * tkTextIndex.c --
3  *
4  *      This module provides procedures that manipulate indices for
5  *      text widgets.
6  *
7  * Copyright (c) 1992-1994 The Regents of the University of California.
8  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
9  *
10  * See the file "license.terms" for information on usage and redistribution
11  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * RCS: @(#) $Id$
14  */
15
16 #include "default.h"
17 #include "tkPort.h"
18 #include "tkInt.h"
19 #include "tkText.h"
20
21 /*
22  * Index to use to select last character in line (very large integer):
23  */
24
25 #define LAST_CHAR 1000000
26
27 /*
28  * Forward declarations for procedures defined later in this file:
29  */
30
31 static char *           ForwBack _ANSI_ARGS_((char *string,
32                             TkTextIndex *indexPtr));
33 static char *           StartEnd _ANSI_ARGS_(( char *string,
34                             TkTextIndex *indexPtr));
35 \f
36 /*
37  *---------------------------------------------------------------------------
38  *
39  * TkTextMakeByteIndex --
40  *
41  *      Given a line index and a byte index, look things up in the B-tree
42  *      and fill in a TkTextIndex structure.
43  *
44  * Results:
45  *      The structure at *indexPtr is filled in with information about the
46  *      character at lineIndex and byteIndex (or the closest existing
47  *      character, if the specified one doesn't exist), and indexPtr is
48  *      returned as result.
49  *
50  * Side effects:
51  *      None.
52  *
53  *---------------------------------------------------------------------------
54  */
55
56 TkTextIndex *
57 TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr)
58     TkTextBTree tree;           /* Tree that lineIndex and charIndex refer
59                                  * to. */
60     int lineIndex;              /* Index of desired line (0 means first
61                                  * line of text). */
62     int byteIndex;              /* Byte index of desired character. */
63     TkTextIndex *indexPtr;      /* Structure to fill in. */
64 {
65     TkTextSegment *segPtr;
66     int index;
67     char *p, *start;
68     Tcl_UniChar ch;
69
70     indexPtr->tree = tree;
71     if (lineIndex < 0) {
72         lineIndex = 0;
73         byteIndex = 0;
74     }
75     if (byteIndex < 0) {
76         byteIndex = 0;
77     }
78     indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex);
79     if (indexPtr->linePtr == NULL) {
80         indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree));
81         byteIndex = 0;
82     }
83     if (byteIndex == 0) {
84         indexPtr->byteIndex = byteIndex;
85         return indexPtr;
86     }
87
88     /*
89      * Verify that the index is within the range of the line and points
90      * to a valid character boundary.  
91      */
92
93     index = 0;
94     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
95         if (segPtr == NULL) {
96             /*
97              * Use the index of the last character in the line.  Since
98              * the last character on the line is guaranteed to be a '\n',
99              * we can back up a constant sizeof(char) bytes.
100              */
101              
102             indexPtr->byteIndex = index - sizeof(char);
103             break;
104         }
105         if (index + segPtr->size > byteIndex) {
106             indexPtr->byteIndex = byteIndex;
107             if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) {
108                 /*
109                  * Prevent UTF-8 character from being split up by ensuring
110                  * that byteIndex falls on a character boundary.  If index
111                  * falls in the middle of a UTF-8 character, it will be
112                  * adjusted to the end of that UTF-8 character.
113                  */
114
115                 start = segPtr->body.chars + (byteIndex - index);
116                 p = Tcl_UtfPrev(start, segPtr->body.chars);
117                 p += Tcl_UtfToUniChar(p, &ch);
118                 indexPtr->byteIndex += p - start;
119             }
120             break;
121         }
122         index += segPtr->size;
123     }
124     return indexPtr;
125 }
126 \f
127 /*
128  *---------------------------------------------------------------------------
129  *
130  * TkTextMakeCharIndex --
131  *
132  *      Given a line index and a character index, look things up in the
133  *      B-tree and fill in a TkTextIndex structure.
134  *
135  * Results:
136  *      The structure at *indexPtr is filled in with information about the
137  *      character at lineIndex and charIndex (or the closest existing
138  *      character, if the specified one doesn't exist), and indexPtr is
139  *      returned as result.
140  *
141  * Side effects:
142  *      None.
143  *
144  *---------------------------------------------------------------------------
145  */
146
147 TkTextIndex *
148 TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr)
149     TkTextBTree tree;           /* Tree that lineIndex and charIndex refer
150                                  * to. */
151     int lineIndex;              /* Index of desired line (0 means first
152                                  * line of text). */
153     int charIndex;              /* Index of desired character. */
154     TkTextIndex *indexPtr;      /* Structure to fill in. */
155 {
156     register TkTextSegment *segPtr;
157     char *p, *start, *end;
158     int index, offset;
159     Tcl_UniChar ch;
160
161     indexPtr->tree = tree;
162     if (lineIndex < 0) {
163         lineIndex = 0;
164         charIndex = 0;
165     }
166     if (charIndex < 0) {
167         charIndex = 0;
168     }
169     indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex);
170     if (indexPtr->linePtr == NULL) {
171         indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree));
172         charIndex = 0;
173     }
174
175     /*
176      * Verify that the index is within the range of the line.
177      * If not, just use the index of the last character in the line.
178      */
179
180     index = 0;
181     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
182         if (segPtr == NULL) {
183             /*
184              * Use the index of the last character in the line.  Since
185              * the last character on the line is guaranteed to be a '\n',
186              * we can back up a constant sizeof(char) bytes.
187              */
188              
189             indexPtr->byteIndex = index - sizeof(char);
190             break;
191         }
192         if (segPtr->typePtr == &tkTextCharType) {
193             /*
194              * Turn character offset into a byte offset.
195              */
196
197             start = segPtr->body.chars;
198             end = start + segPtr->size;
199             for (p = start; p < end; p += offset) {
200                 if (charIndex == 0) {
201                     indexPtr->byteIndex = index;
202                     return indexPtr;
203                 }
204                 charIndex--;
205                 offset = Tcl_UtfToUniChar(p, &ch);
206                 index += offset;
207             }
208         } else {
209             if (charIndex < segPtr->size) {
210                 indexPtr->byteIndex = index;
211                 break;
212             }
213             charIndex -= segPtr->size;
214             index += segPtr->size;
215         }
216     }
217     return indexPtr;
218 }
219 \f
220 /*
221  *---------------------------------------------------------------------------
222  *
223  * TkTextIndexToSeg --
224  *
225  *      Given an index, this procedure returns the segment and offset
226  *      within segment for the index.
227  *
228  * Results:
229  *      The return value is a pointer to the segment referred to by
230  *      indexPtr; this will always be a segment with non-zero size.  The
231  *      variable at *offsetPtr is set to hold the integer offset within
232  *      the segment of the character given by indexPtr.
233  *
234  * Side effects:
235  *      None.
236  *
237  *---------------------------------------------------------------------------
238  */
239
240 TkTextSegment *
241 TkTextIndexToSeg(indexPtr, offsetPtr)
242     CONST TkTextIndex *indexPtr;/* Text index. */
243     int *offsetPtr;             /* Where to store offset within segment, or
244                                  * NULL if offset isn't wanted. */
245 {
246     TkTextSegment *segPtr;
247     int offset;
248
249     for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr;
250             offset >= segPtr->size;
251             offset -= segPtr->size, segPtr = segPtr->nextPtr) {
252         /* Empty loop body. */
253     }
254     if (offsetPtr != NULL) {
255         *offsetPtr = offset;
256     }
257     return segPtr;
258 }
259 \f
260 /*
261  *---------------------------------------------------------------------------
262  *
263  * TkTextSegToOffset --
264  *
265  *      Given a segment pointer and the line containing it, this procedure
266  *      returns the offset of the segment within its line.
267  *
268  * Results:
269  *      The return value is the offset (within its line) of the first
270  *      character in segPtr.
271  *
272  * Side effects:
273  *      None.
274  *
275  *---------------------------------------------------------------------------
276  */
277
278 int
279 TkTextSegToOffset(segPtr, linePtr)
280     CONST TkTextSegment *segPtr;/* Segment whose offset is desired. */
281     CONST TkTextLine *linePtr;  /* Line containing segPtr. */
282 {
283     CONST TkTextSegment *segPtr2;
284     int offset;
285
286     offset = 0;
287     for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
288             segPtr2 = segPtr2->nextPtr) {
289         offset += segPtr2->size;
290     }
291     return offset;
292 }
293 \f
294 /*
295  *---------------------------------------------------------------------------
296  *
297  * TkTextGetIndex --
298  *
299  *      Given a string, return the index that is described.
300  *
301  * Results:
302  *      The return value is a standard Tcl return result.  If TCL_OK is
303  *      returned, then everything went well and the index at *indexPtr is
304  *      filled in; otherwise TCL_ERROR is returned and an error message
305  *      is left in the interp's result.
306  *
307  * Side effects:
308  *      None.
309  *
310  *---------------------------------------------------------------------------
311  */
312
313 int
314 TkTextGetIndex(interp, textPtr, string, indexPtr)
315     Tcl_Interp *interp;         /* Use this for error reporting. */
316     TkText *textPtr;            /* Information about text widget. */
317     char *string;               /* Textual description of position. */
318     TkTextIndex *indexPtr;      /* Index structure to fill in. */
319 {
320     char *p, *end, *endOfBase;
321     Tcl_HashEntry *hPtr;
322     TkTextTag *tagPtr;
323     TkTextSearch search;
324     TkTextIndex first, last;
325     int wantLast, result;
326     char c;
327
328     /*
329      *---------------------------------------------------------------------
330      * Stage 1: check to see if the index consists of nothing but a mark
331      * name.  We do this check now even though it's also done later, in
332      * order to allow mark names that include funny characters such as
333      * spaces or "+1c".
334      *---------------------------------------------------------------------
335      */
336
337     if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
338         return TCL_OK;
339     }
340
341     /*
342      *------------------------------------------------
343      * Stage 2: start again by parsing the base index.
344      *------------------------------------------------
345      */
346
347     indexPtr->tree = textPtr->tree;
348
349     /*
350      * First look for the form "tag.first" or "tag.last" where "tag"
351      * is the name of a valid tag.  Try to use up as much as possible
352      * of the string in this check (strrchr instead of strchr below).
353      * Doing the check now, and in this way, allows tag names to include
354      * funny characters like "@" or "+1c".
355      */
356
357     p = strrchr(string, '.');
358     if (p != NULL) {
359         if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
360             wantLast = 0;
361             endOfBase = p+6;
362         } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
363             wantLast = 1;
364             endOfBase = p+5;
365         } else {
366             goto tryxy;
367         }
368         *p = 0;
369         hPtr = Tcl_FindHashEntry(&textPtr->tagTable, string);
370         *p = '.';
371         if (hPtr == NULL) {
372             goto tryxy;
373         }
374         tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
375         TkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
376         TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0,
377                 &last);
378         TkBTreeStartSearch(&first, &last, tagPtr, &search);
379         if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) {
380             Tcl_AppendResult(interp,
381                     "text doesn't contain any characters tagged with \"",
382                     Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"",
383                             (char *) NULL);
384             return TCL_ERROR;
385         }
386         *indexPtr = search.curIndex;
387         if (wantLast) {
388             while (TkBTreeNextTag(&search)) {
389                 *indexPtr = search.curIndex;
390             }
391         }
392         goto gotBase;
393     }
394
395     tryxy:
396     if (string[0] == '@') {
397         /*
398          * Find character at a given x,y location in the window.
399          */
400
401         int x, y;
402
403         p = string+1;
404         x = strtol(p, &end, 0);
405         if ((end == p) || (*end != ',')) {
406             goto error;
407         }
408         p = end+1;
409         y = strtol(p, &end, 0);
410         if (end == p) {
411             goto error;
412         }
413         TkTextPixelIndex(textPtr, x, y, indexPtr);
414         endOfBase = end;
415         goto gotBase; 
416     }
417
418     if (isdigit(UCHAR(string[0])) || (string[0] == '-')) {
419         int lineIndex, charIndex;
420
421         /*
422          * Base is identified with line and character indices.
423          */
424
425         lineIndex = strtol(string, &end, 0) - 1;
426         if ((end == string) || (*end != '.')) {
427             goto error;
428         }
429         p = end+1;
430         if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
431             charIndex = LAST_CHAR;
432             endOfBase = p+3;
433         } else {
434             charIndex = strtol(p, &end, 0);
435             if (end == p) {
436                 goto error;
437             }
438             endOfBase = end;
439         }
440         TkTextMakeCharIndex(textPtr->tree, lineIndex, charIndex, indexPtr);
441         goto gotBase;
442     }
443
444     for (p = string; *p != 0; p++) {
445         if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) {
446             break;
447         }
448     }
449     endOfBase = p;
450     if (string[0] == '.') {
451         /*
452          * See if the base position is the name of an embedded window.
453          */
454
455         c = *endOfBase;
456         *endOfBase = 0;
457         result = TkTextWindowIndex(textPtr, string, indexPtr);
458         *endOfBase = c;
459         if (result != 0) {
460             goto gotBase;
461         }
462     }
463     if ((string[0] == 'e')
464             && (strncmp(string, "end", (size_t) (endOfBase-string)) == 0)) {
465         /*
466          * Base position is end of text.
467          */
468
469         TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
470                 0, indexPtr);
471         goto gotBase;
472     } else {
473         /*
474          * See if the base position is the name of a mark.
475          */
476
477         c = *endOfBase;
478         *endOfBase = 0;
479         result = TkTextMarkNameToIndex(textPtr, string, indexPtr);
480         *endOfBase = c;
481         if (result == TCL_OK) {
482             goto gotBase;
483         }
484
485         /*
486          * See if the base position is the name of an embedded image
487          */
488
489         c = *endOfBase;
490         *endOfBase = 0;
491         result = TkTextImageIndex(textPtr, string, indexPtr);
492         *endOfBase = c;
493         if (result != 0) {
494             goto gotBase;
495         }
496     }
497     goto error;
498
499     /*
500      *-------------------------------------------------------------------
501      * Stage 3: process zero or more modifiers.  Each modifier is either
502      * a keyword like "wordend" or "linestart", or it has the form
503      * "op count units" where op is + or -, count is a number, and units
504      * is "chars" or "lines".
505      *-------------------------------------------------------------------
506      */
507
508     gotBase:
509     p = endOfBase;
510     while (1) {
511         while (isspace(UCHAR(*p))) {
512             p++;
513         }
514         if (*p == 0) {
515             break;
516         }
517     
518         if ((*p == '+') || (*p == '-')) {
519             p = ForwBack(p, indexPtr);
520         } else {
521             p = StartEnd(p, indexPtr);
522         }
523         if (p == NULL) {
524             goto error;
525         }
526     }
527     return TCL_OK;
528
529     error:
530     Tcl_AppendResult(interp, "bad text index \"", string, "\"",
531             (char *) NULL);
532     return TCL_ERROR;
533 }
534 \f
535 /*
536  *---------------------------------------------------------------------------
537  *
538  * TkTextPrintIndex --
539  *      
540  *      This procedure generates a string description of an index, suitable
541  *      for reading in again later.
542  *
543  * Results:
544  *      The characters pointed to by string are modified.
545  *
546  * Side effects:
547  *      None.
548  *
549  *---------------------------------------------------------------------------
550  */
551
552 void
553 TkTextPrintIndex(indexPtr, string)
554     CONST TkTextIndex *indexPtr;/* Pointer to index. */
555     char *string;               /* Place to store the position.  Must have
556                                  * at least TK_POS_CHARS characters. */
557 {
558     TkTextSegment *segPtr;
559     int numBytes, charIndex;
560
561     numBytes = indexPtr->byteIndex;
562     charIndex = 0;
563     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
564         if (numBytes <= segPtr->size) {
565             break;
566         }
567         if (segPtr->typePtr == &tkTextCharType) {
568             charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
569         } else {
570             charIndex += segPtr->size;
571         }
572         numBytes -= segPtr->size;
573     }
574     if (segPtr->typePtr == &tkTextCharType) {
575         charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
576     } else {
577         charIndex += numBytes;
578     }
579     sprintf(string, "%d.%d", TkBTreeLineIndex(indexPtr->linePtr) + 1,
580             charIndex);
581 }
582 \f
583 /*
584  *---------------------------------------------------------------------------
585  *
586  * TkTextIndexCmp --
587  *
588  *      Compare two indices to see which one is earlier in the text.
589  *
590  * Results:
591  *      The return value is 0 if index1Ptr and index2Ptr refer to the same
592  *      position in the file, -1 if index1Ptr refers to an earlier position
593  *      than index2Ptr, and 1 otherwise.
594  *
595  * Side effects:
596  *      None.
597  *
598  *---------------------------------------------------------------------------
599  */
600
601 int
602 TkTextIndexCmp(index1Ptr, index2Ptr)
603     CONST TkTextIndex *index1Ptr;               /* First index. */
604     CONST TkTextIndex *index2Ptr;               /* Second index. */
605 {
606     int line1, line2;
607
608     if (index1Ptr->linePtr == index2Ptr->linePtr) {
609         if (index1Ptr->byteIndex < index2Ptr->byteIndex) {
610             return -1;
611         } else if (index1Ptr->byteIndex > index2Ptr->byteIndex) {
612             return 1;
613         } else {
614             return 0;
615         }
616     }
617     line1 = TkBTreeLineIndex(index1Ptr->linePtr);
618     line2 = TkBTreeLineIndex(index2Ptr->linePtr);
619     if (line1 < line2) {
620         return -1;
621     }
622     if (line1 > line2) {
623         return 1;
624     }
625     return 0;
626 }
627 \f
628 /*
629  *---------------------------------------------------------------------------
630  *
631  * ForwBack --
632  *
633  *      This procedure handles +/- modifiers for indices to adjust the
634  *      index forwards or backwards.
635  *
636  * Results:
637  *      If the modifier in string is successfully parsed then the return
638  *      value is the address of the first character after the modifier,
639  *      and *indexPtr is updated to reflect the modifier.  If there is a
640  *      syntax error in the modifier then NULL is returned.
641  *
642  * Side effects:
643  *      None.
644  *
645  *---------------------------------------------------------------------------
646  */
647
648 static char *
649 ForwBack(string, indexPtr)
650     char *string;               /* String to parse for additional info
651                                  * about modifier (count and units). 
652                                  * Points to "+" or "-" that starts
653                                  * modifier. */
654     TkTextIndex *indexPtr;      /* Index to update as specified in string. */
655 {
656     register char *p;
657     char *end, *units;
658     int count, lineIndex;
659     size_t length;
660
661     /*
662      * Get the count (how many units forward or backward).
663      */
664
665     p = string+1;
666     while (isspace(UCHAR(*p))) {
667         p++;
668     }
669     count = strtol(p, &end, 0);
670     if (end == p) {
671         return NULL;
672     }
673     p = end;
674     while (isspace(UCHAR(*p))) {
675         p++;
676     }
677
678     /*
679      * Find the end of this modifier (next space or + or - character),
680      * then parse the unit specifier and update the position
681      * accordingly.
682      */
683
684     units = p; 
685     while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
686         p++;
687     }
688     length = p - units;
689     if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
690         if (*string == '+') {
691             TkTextIndexForwChars(indexPtr, count, indexPtr);
692         } else {
693             TkTextIndexBackChars(indexPtr, count, indexPtr);
694         }
695     } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
696         lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
697         if (*string == '+') {
698             lineIndex += count;
699         } else {
700             lineIndex -= count;
701
702             /*
703              * The check below retains the character position, even
704              * if the line runs off the start of the file.  Without
705              * it, the character position will get reset to 0 by
706              * TkTextMakeIndex.
707              */
708
709             if (lineIndex < 0) {
710                 lineIndex = 0;
711             }
712         }
713         /*
714          * This doesn't work quite right if using a proportional font or
715          * UTF-8 characters with varying numbers of bytes.  The cursor will
716          * bop around, keeping a constant number of bytes (not characters)
717          * from the left edge (but making sure not to split any UTF-8
718          * characters), regardless of the x-position the index corresponds
719          * to.  The proper way to do this is to get the x-position of the
720          * index and then pick the character at the same x-position in the
721          * new line.
722          */
723
724         TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex,
725                 indexPtr);
726     } else {
727         return NULL;
728     }
729     return p;
730 }
731 \f
732 /*
733  *---------------------------------------------------------------------------
734  *
735  * TkTextIndexForwBytes --
736  *
737  *      Given an index for a text widget, this procedure creates a new
738  *      index that points "count" bytes ahead of the source index.
739  *
740  * Results:
741  *      *dstPtr is modified to refer to the character "count" bytes after
742  *      srcPtr, or to the last character in the TkText if there aren't
743  *      "count" bytes left.
744  *
745  * Side effects:
746  *      None.
747  *
748  *---------------------------------------------------------------------------
749  */
750
751 void
752 TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
753     CONST TkTextIndex *srcPtr;  /* Source index. */
754     int byteCount;              /* How many bytes forward to move.  May be
755                                  * negative. */
756     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
757 {
758     TkTextLine *linePtr;
759     TkTextSegment *segPtr;
760     int lineLength;
761
762     if (byteCount < 0) {
763         TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr);
764         return;
765     }
766
767     *dstPtr = *srcPtr;
768     dstPtr->byteIndex += byteCount;
769     while (1) {
770         /*
771          * Compute the length of the current line.
772          */
773
774         lineLength = 0;
775         for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
776                 segPtr = segPtr->nextPtr) {
777             lineLength += segPtr->size;
778         }
779
780         /*
781          * If the new index is in the same line then we're done.
782          * Otherwise go on to the next line.
783          */
784
785         if (dstPtr->byteIndex < lineLength) {
786             return;
787         }
788         dstPtr->byteIndex -= lineLength;
789         linePtr = TkBTreeNextLine(dstPtr->linePtr);
790         if (linePtr == NULL) {
791             dstPtr->byteIndex = lineLength - 1;
792             return;
793         }
794         dstPtr->linePtr = linePtr;
795     }
796 }
797 \f
798 /*
799  *---------------------------------------------------------------------------
800  *
801  * TkTextIndexForwChars --
802  *
803  *      Given an index for a text widget, this procedure creates a new
804  *      index that points "count" characters ahead of the source index.
805  *
806  * Results:
807  *      *dstPtr is modified to refer to the character "count" characters
808  *      after srcPtr, or to the last character in the TkText if there
809  *      aren't "count" characters left in the file.
810  *
811  * Side effects:
812  *      None.
813  *
814  *---------------------------------------------------------------------------
815  */
816
817 void
818 TkTextIndexForwChars(srcPtr, charCount, dstPtr)
819     CONST TkTextIndex *srcPtr;  /* Source index. */
820     int charCount;              /* How many characters forward to move.
821                                  * May be negative. */
822     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
823 {
824     TkTextLine *linePtr;
825     TkTextSegment *segPtr;
826     int byteOffset;
827     char *start, *end, *p;
828     Tcl_UniChar ch;
829
830     if (charCount < 0) {
831         TkTextIndexBackChars(srcPtr, -charCount, dstPtr);
832         return;
833     }
834
835     *dstPtr = *srcPtr;
836
837     /*
838      * Find seg that contains src byteIndex.
839      * Move forward specified number of chars.
840      */
841
842     segPtr = TkTextIndexToSeg(dstPtr, &byteOffset);
843     while (1) {
844         /*
845          * Go through each segment in line looking for specified character
846          * index.
847          */
848
849         for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
850             if (segPtr->typePtr == &tkTextCharType) {
851                 start = segPtr->body.chars + byteOffset;
852                 end = segPtr->body.chars + segPtr->size;
853                 for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) {
854                     if (charCount == 0) {
855                         dstPtr->byteIndex += (p - start);
856                         return;
857                     }
858                     charCount--;
859                 }
860             } else {
861                 if (charCount < segPtr->size - byteOffset) {
862                     dstPtr->byteIndex += charCount;
863                     return;
864                 }
865                 charCount -= segPtr->size - byteOffset;
866             }
867             dstPtr->byteIndex += segPtr->size - byteOffset;
868             byteOffset = 0;
869         }
870
871         /*
872          * Go to the next line.  If we are at the end of the text item,
873          * back up one byte (for the terminal '\n' character) and return
874          * that index.
875          */
876          
877         linePtr = TkBTreeNextLine(dstPtr->linePtr);
878         if (linePtr == NULL) {
879             dstPtr->byteIndex -= sizeof(char);
880             return;
881         }
882         dstPtr->linePtr = linePtr;
883         dstPtr->byteIndex = 0;
884         segPtr = dstPtr->linePtr->segPtr;
885     }
886 }
887 \f
888 /*
889  *---------------------------------------------------------------------------
890  *
891  * TkTextIndexBackBytes --
892  *
893  *      Given an index for a text widget, this procedure creates a new
894  *      index that points "count" bytes earlier than the source index.
895  *
896  * Results:
897  *      *dstPtr is modified to refer to the character "count" bytes before
898  *      srcPtr, or to the first character in the TkText if there aren't
899  *      "count" bytes earlier than srcPtr.
900  *
901  * Side effects:
902  *      None.
903  *
904  *---------------------------------------------------------------------------
905  */
906
907 void
908 TkTextIndexBackBytes(srcPtr, byteCount, dstPtr)
909     CONST TkTextIndex *srcPtr;  /* Source index. */
910     int byteCount;              /* How many bytes backward to move.  May be
911                                  * negative. */
912     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
913 {
914     TkTextSegment *segPtr;
915     int lineIndex;
916
917     if (byteCount < 0) {
918         TkTextIndexForwBytes(srcPtr, -byteCount, dstPtr);
919         return;
920     }
921
922     *dstPtr = *srcPtr;
923     dstPtr->byteIndex -= byteCount;
924     lineIndex = -1;
925     while (dstPtr->byteIndex < 0) {
926         /*
927          * Move back one line in the text.  If we run off the beginning
928          * of the file then just return the first character in the text.
929          */
930
931         if (lineIndex < 0) {
932             lineIndex = TkBTreeLineIndex(dstPtr->linePtr);
933         }
934         if (lineIndex == 0) {
935             dstPtr->byteIndex = 0;
936             return;
937         }
938         lineIndex--;
939         dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex);
940
941         /*
942          * Compute the length of the line and add that to dstPtr->charIndex.
943          */
944
945         for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
946                 segPtr = segPtr->nextPtr) {
947             dstPtr->byteIndex += segPtr->size;
948         }
949     }
950 }
951 \f
952 /*
953  *---------------------------------------------------------------------------
954  *
955  * TkTextIndexBackChars --
956  *
957  *      Given an index for a text widget, this procedure creates a new
958  *      index that points "count" characters earlier than the source index.
959  *
960  * Results:
961  *      *dstPtr is modified to refer to the character "count" characters
962  *      before srcPtr, or to the first character in the file if there
963  *      aren't "count" characters earlier than srcPtr.
964  *
965  * Side effects:
966  *      None.
967  *
968  *---------------------------------------------------------------------------
969  */
970
971 void
972 TkTextIndexBackChars(srcPtr, charCount, dstPtr)
973     CONST TkTextIndex *srcPtr;  /* Source index. */
974     int charCount;              /* How many characters backward to move.
975                                  * May be negative. */
976     TkTextIndex *dstPtr;        /* Destination index: gets modified. */
977 {
978     TkTextSegment *segPtr, *oldPtr;
979     int lineIndex, segSize;
980     char *p, *start, *end;
981
982     if (charCount <= 0) {
983         TkTextIndexForwChars(srcPtr, -charCount, dstPtr);
984         return;
985     }
986
987     *dstPtr = *srcPtr;
988
989     /*
990      * Find offset within seg that contains byteIndex.
991      * Move backward specified number of chars.
992      */
993
994     lineIndex = -1;
995     
996     segSize = dstPtr->byteIndex;
997     for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
998         if (segSize <= segPtr->size) {
999             break;
1000         }
1001         segSize -= segPtr->size;
1002     }
1003     while (1) {
1004         if (segPtr->typePtr == &tkTextCharType) {
1005             start = segPtr->body.chars;
1006             end = segPtr->body.chars + segSize;
1007             for (p = end; ; p = Tcl_UtfPrev(p, start)) {
1008                 if (charCount == 0) {
1009                     dstPtr->byteIndex -= (end - p);
1010                     return;
1011                 }
1012                 if (p == start) {
1013                     break;
1014                 }
1015                 charCount--;
1016             }
1017         } else {
1018             if (charCount <= segSize) {
1019                 dstPtr->byteIndex -= charCount;
1020                 return;
1021             }
1022             charCount -= segSize;
1023         }
1024         dstPtr->byteIndex -= segSize;
1025
1026         /*
1027          * Move back into previous segment.
1028          */
1029
1030         oldPtr = segPtr;
1031         segPtr = dstPtr->linePtr->segPtr;
1032         if (segPtr != oldPtr) {
1033             for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) {
1034                 /* Empty body. */
1035             }
1036             segSize = segPtr->size;
1037             continue;
1038         }
1039
1040         /*
1041          * Move back to previous line.
1042          */
1043
1044         if (lineIndex < 0) {
1045             lineIndex = TkBTreeLineIndex(dstPtr->linePtr);
1046         }
1047         if (lineIndex == 0) {
1048             dstPtr->byteIndex = 0;
1049             return;
1050         }
1051         lineIndex--;
1052         dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex);
1053
1054         /*
1055          * Compute the length of the line and add that to dstPtr->byteIndex.
1056          */
1057
1058         oldPtr = dstPtr->linePtr->segPtr;
1059         for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
1060             dstPtr->byteIndex += segPtr->size;
1061             oldPtr = segPtr;
1062         }
1063         segPtr = oldPtr;
1064         segSize = segPtr->size;
1065     }
1066 }
1067 \f
1068 /*
1069  *----------------------------------------------------------------------
1070  *
1071  * StartEnd --
1072  *
1073  *      This procedure handles modifiers like "wordstart" and "lineend"
1074  *      to adjust indices forwards or backwards.
1075  *
1076  * Results:
1077  *      If the modifier is successfully parsed then the return value
1078  *      is the address of the first character after the modifier, and
1079  *      *indexPtr is updated to reflect the modifier. If there is a
1080  *      syntax error in the modifier then NULL is returned.
1081  *
1082  * Side effects:
1083  *      None.
1084  *
1085  *----------------------------------------------------------------------
1086  */
1087
1088 static char *
1089 StartEnd(string, indexPtr)
1090     char *string;               /* String to parse for additional info
1091                                  * about modifier (count and units). 
1092                                  * Points to first character of modifer
1093                                  * word. */
1094     TkTextIndex *indexPtr;      /* Index to mdoify based on string. */
1095 {
1096     char *p;
1097     int c, offset;
1098     size_t length;
1099     register TkTextSegment *segPtr;
1100
1101     /*
1102      * Find the end of the modifier word.
1103      */
1104
1105     for (p = string; isalnum(UCHAR(*p)); p++) {
1106         /* Empty loop body. */
1107     }
1108     length = p-string;
1109     if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
1110             && (length >= 5)) {
1111         indexPtr->byteIndex = 0;
1112         for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
1113                 segPtr = segPtr->nextPtr) {
1114             indexPtr->byteIndex += segPtr->size;
1115         }
1116         indexPtr->byteIndex -= sizeof(char);
1117     } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
1118             && (length >= 5)) {
1119         indexPtr->byteIndex = 0;
1120     } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
1121             && (length >= 5)) {
1122         int firstChar = 1;
1123
1124         /*
1125          * If the current character isn't part of a word then just move
1126          * forward one character.  Otherwise move forward until finding
1127          * a character that isn't part of a word and stop there.
1128          */
1129
1130         segPtr = TkTextIndexToSeg(indexPtr, &offset);
1131         while (1) {
1132             if (segPtr->typePtr == &tkTextCharType) {
1133                 c = segPtr->body.chars[offset];
1134                 if (!isalnum(UCHAR(c)) && (c != '_')) {
1135                     break;
1136                 }
1137                 firstChar = 0;
1138             }
1139             offset += 1;
1140             indexPtr->byteIndex += sizeof(char);
1141             if (offset >= segPtr->size) {
1142                 segPtr = TkTextIndexToSeg(indexPtr, &offset);
1143             }
1144         }
1145         if (firstChar) {
1146             TkTextIndexForwChars(indexPtr, 1, indexPtr);
1147         }
1148     } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
1149             && (length >= 5)) {
1150         int firstChar = 1;
1151
1152         /*
1153          * Starting with the current character, look for one that's not
1154          * part of a word and keep moving backward until you find one.
1155          * Then if the character found wasn't the first one, move forward
1156          * again one position.
1157          */
1158
1159         segPtr = TkTextIndexToSeg(indexPtr, &offset);
1160         while (1) {
1161             if (segPtr->typePtr == &tkTextCharType) {
1162                 c = segPtr->body.chars[offset];
1163                 if (!isalnum(UCHAR(c)) && (c != '_')) {
1164                     break;
1165                 }
1166                 firstChar = 0;
1167             }
1168             offset -= 1;
1169             indexPtr->byteIndex -= sizeof(char);
1170             if (offset < 0) {
1171                 if (indexPtr->byteIndex < 0) {
1172                     indexPtr->byteIndex = 0;
1173                     goto done;
1174                 }
1175                 segPtr = TkTextIndexToSeg(indexPtr, &offset);
1176             }
1177         }
1178         if (!firstChar) {
1179             TkTextIndexForwChars(indexPtr, 1, indexPtr);
1180         }
1181     } else {
1182         return NULL;
1183     }
1184     done:
1185     return p;
1186 }
1187