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