1/*
2 * tkTextIndex.c --
3 *
4 *	This module provides functions that manipulate indices for text
5 *	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 of
11 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12 *
13 * RCS: @(#) $Id$
14 */
15
16#include "default.h"
17#include "tkInt.h"
18#include "tkText.h"
19
20/*
21 * Index to use to select last character in line (very large integer):
22 */
23
24#define LAST_CHAR 1000000
25
26/*
27 * Modifiers for index parsing: 'display', 'any' or nothing.
28 */
29
30#define TKINDEX_NONE	0
31#define TKINDEX_DISPLAY	1
32#define TKINDEX_ANY	2
33
34/*
35 * Forward declarations for functions defined later in this file:
36 */
37
38static CONST char *	ForwBack(TkText *textPtr, CONST char *string,
39			    TkTextIndex *indexPtr);
40static CONST char *	StartEnd(TkText *textPtr, CONST char *string,
41			    TkTextIndex *indexPtr);
42static int		GetIndex(Tcl_Interp *interp, TkSharedText *sharedPtr,
43			    TkText *textPtr, CONST char *string,
44			    TkTextIndex *indexPtr, int *canCachePtr);
45
46/*
47 * The "textindex" Tcl_Obj definition:
48 */
49
50static void		DupTextIndexInternalRep(Tcl_Obj *srcPtr,
51			    Tcl_Obj *copyPtr);
52static void		FreeTextIndexInternalRep(Tcl_Obj *listPtr);
53static int		SetTextIndexFromAny(Tcl_Interp *interp,
54			    Tcl_Obj *objPtr);
55static void		UpdateStringOfTextIndex(Tcl_Obj *objPtr);
56
57/*
58 * Accessor macros for the "textindex" type.
59 */
60
61#define GET_TEXTINDEX(objPtr) \
62	((TkTextIndex *) (objPtr)->internalRep.twoPtrValue.ptr1)
63#define GET_INDEXEPOCH(objPtr) \
64	(PTR2INT((objPtr)->internalRep.twoPtrValue.ptr2))
65#define SET_TEXTINDEX(objPtr, indexPtr) \
66	((objPtr)->internalRep.twoPtrValue.ptr1 = (VOID *) (indexPtr))
67#define SET_INDEXEPOCH(objPtr, epoch) \
68	((objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(epoch))
69
70/*
71 * Define the 'textindex' object type, which Tk uses to represent indices in
72 * text widgets internally.
73 */
74
75Tcl_ObjType tkTextIndexType = {
76    "textindex",		/* name */
77    FreeTextIndexInternalRep,	/* freeIntRepProc */
78    DupTextIndexInternalRep,	/* dupIntRepProc */
79    NULL,			/* updateStringProc */
80    SetTextIndexFromAny		/* setFromAnyProc */
81};
82
83static void
84FreeTextIndexInternalRep(
85    Tcl_Obj *indexObjPtr)	/* TextIndex object with internal rep to
86				 * free. */
87{
88    TkTextIndex *indexPtr = GET_TEXTINDEX(indexObjPtr);
89    if (indexPtr->textPtr != NULL) {
90	if (--indexPtr->textPtr->refCount == 0) {
91	    /*
92	     * The text widget has been deleted and we need to free it now.
93	     */
94
95	    ckfree((char *) (indexPtr->textPtr));
96	}
97    }
98    ckfree((char *) indexPtr);
99}
100
101static void
102DupTextIndexInternalRep(
103    Tcl_Obj *srcPtr,		/* TextIndex obj with internal rep to copy. */
104    Tcl_Obj *copyPtr)		/* TextIndex obj with internal rep to set. */
105{
106    int epoch;
107    TkTextIndex *dupIndexPtr, *indexPtr;
108
109    dupIndexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex));
110    indexPtr = GET_TEXTINDEX(srcPtr);
111    epoch = GET_INDEXEPOCH(srcPtr);
112
113    dupIndexPtr->tree = indexPtr->tree;
114    dupIndexPtr->linePtr = indexPtr->linePtr;
115    dupIndexPtr->byteIndex = indexPtr->byteIndex;
116    dupIndexPtr->textPtr = indexPtr->textPtr;
117    if (dupIndexPtr->textPtr != NULL) {
118	dupIndexPtr->textPtr->refCount++;
119    }
120    SET_TEXTINDEX(copyPtr, dupIndexPtr);
121    SET_INDEXEPOCH(copyPtr, epoch);
122    copyPtr->typePtr = &tkTextIndexType;
123}
124
125/*
126 * This will not be called except by TkTextNewIndexObj below. This is because
127 * if a TkTextIndex is no longer valid, it is not possible to regenerate the
128 * string representation.
129 */
130
131static void
132UpdateStringOfTextIndex(
133    Tcl_Obj *objPtr)
134{
135    char buffer[TK_POS_CHARS];
136    register int len;
137
138    CONST TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr);
139
140    len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer);
141
142    objPtr->bytes = ckalloc((unsigned) len + 1);
143    strcpy(objPtr->bytes, buffer);
144    objPtr->length = len;
145}
146
147static int
148SetTextIndexFromAny(
149    Tcl_Interp *interp,		/* Used for error reporting if not NULL. */
150    Tcl_Obj *objPtr)		/* The object to convert. */
151{
152    Tcl_AppendToObj(Tcl_GetObjResult(interp),
153	    "can't convert value to textindex except via TkTextGetIndexFromObj API",
154	    -1);
155    return TCL_ERROR;
156}
157
158/*
159 *---------------------------------------------------------------------------
160 *
161 * MakeObjIndex --
162 *
163 *	This function generates a Tcl_Obj description of an index, suitable
164 *	for reading in again later. If the 'textPtr' is NULL then we still
165 *	generate an index object, but it's internal description is deemed
166 *	non-cacheable, and therefore effectively useless (apart from as a
167 *	temporary memory storage). This is used for indices whose meaning is
168 *	very temporary (like @0,0 or the name of a mark or tag). The mapping
169 *	from such strings/objects to actual TkTextIndex pointers is not stable
170 *	to minor text widget changes which we do not track (we track
171 *	insertions and deletions).
172 *
173 * Results:
174 *	A pointer to an allocated TkTextIndex which will be freed
175 *	automatically when the Tcl_Obj is used for other purposes.
176 *
177 * Side effects:
178 *	A small amount of memory is allocated.
179 *
180 *---------------------------------------------------------------------------
181 */
182
183static TkTextIndex *
184MakeObjIndex(
185    TkText *textPtr,		/* Information about text widget. */
186    Tcl_Obj *objPtr,		/* Object containing description of
187				 * position. */
188    CONST TkTextIndex *origPtr)	/* Pointer to index. */
189{
190    TkTextIndex *indexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex));
191
192    indexPtr->tree = origPtr->tree;
193    indexPtr->linePtr = origPtr->linePtr;
194    indexPtr->byteIndex = origPtr->byteIndex;
195    SET_TEXTINDEX(objPtr, indexPtr);
196    objPtr->typePtr = &tkTextIndexType;
197    indexPtr->textPtr = textPtr;
198
199    if (textPtr != NULL) {
200	textPtr->refCount++;
201	SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch);
202    } else {
203	SET_INDEXEPOCH(objPtr, 0);
204    }
205    return indexPtr;
206}
207
208CONST TkTextIndex *
209TkTextGetIndexFromObj(
210    Tcl_Interp *interp,		/* Use this for error reporting. */
211    TkText *textPtr,		/* Information about text widget. */
212    Tcl_Obj *objPtr)		/* Object containing description of
213				 * position. */
214{
215    TkTextIndex index;
216    TkTextIndex *indexPtr = NULL;
217    int cache;
218
219    if (objPtr->typePtr == &tkTextIndexType) {
220	int epoch;
221
222	indexPtr = GET_TEXTINDEX(objPtr);
223	epoch = GET_INDEXEPOCH(objPtr);
224
225	if (epoch == textPtr->sharedTextPtr->stateEpoch) {
226	    if (indexPtr->textPtr == textPtr) {
227		return indexPtr;
228	    }
229	}
230    }
231
232    /*
233     * The object is either not an index type or referred to a different text
234     * widget, or referred to the correct widget, but it is out of date (text
235     * has been added/deleted since).
236     */
237
238    if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index,
239	    &cache) != TCL_OK) {
240	return NULL;
241    }
242
243    if (objPtr->typePtr != NULL) {
244	if (objPtr->bytes == NULL) {
245	    objPtr->typePtr->updateStringProc(objPtr);
246	}
247	if ((objPtr->typePtr->freeIntRepProc) != NULL) {
248	    (*objPtr->typePtr->freeIntRepProc)(objPtr);
249	}
250    }
251
252    return MakeObjIndex((cache ? textPtr : NULL), objPtr, &index);
253}
254
255/*
256 *---------------------------------------------------------------------------
257 *
258 * TkTextNewIndexObj --
259 *
260 *	This function generates a Tcl_Obj description of an index, suitable
261 *	for reading in again later. The index generated is effectively stable
262 *	to all except insertion/deletion operations on the widget.
263 *
264 * Results:
265 *	A new Tcl_Obj with refCount zero.
266 *
267 * Side effects:
268 *	A small amount of memory is allocated.
269 *
270 *---------------------------------------------------------------------------
271 */
272
273Tcl_Obj *
274TkTextNewIndexObj(
275    TkText *textPtr,		/* Text widget for this index */
276    CONST TkTextIndex *indexPtr)/* Pointer to index. */
277{
278    Tcl_Obj *retVal;
279
280    retVal = Tcl_NewObj();
281    retVal->bytes = NULL;
282
283    /*
284     * Assumption that the above call returns an object with:
285     * retVal->typePtr == NULL
286     */
287
288    MakeObjIndex(textPtr, retVal, indexPtr);
289
290    /*
291     * Unfortunately, it isn't possible for us to regenerate the string
292     * representation so we have to create it here, while we can be sure the
293     * contents of the index are still valid.
294     */
295
296    UpdateStringOfTextIndex(retVal);
297    return retVal;
298}
299
300/*
301 *---------------------------------------------------------------------------
302 *
303 * TkTextMakePixelIndex --
304 *
305 *	Given a pixel index and a byte index, look things up in the B-tree and
306 *	fill in a TkTextIndex structure.
307 *
308 *	The valid input range for pixelIndex is from 0 to the number of pixels
309 *	in the widget-1. Anything outside that range will be rounded to the
310 *	closest acceptable value.
311 *
312 * Results:
313 *
314 *	The structure at *indexPtr is filled in with information about the
315 *	character at pixelIndex (or the closest existing character, if the
316 *	specified one doesn't exist), and the number of excess pixels is
317 *	returned as a result. This means if the given pixel index is exactly
318 *	correct for the top-edge of the indexPtr, then zero will be returned,
319 *	and otherwise we will return the calculation 'desired pixelIndex' -
320 *	'actual pixel index of indexPtr'.
321 *
322 * Side effects:
323 *	None.
324 *
325 *---------------------------------------------------------------------------
326 */
327
328int
329TkTextMakePixelIndex(
330    TkText *textPtr,		/* The Text Widget */
331    int pixelIndex,		/* Pixel-index of desired line (0 means first
332				 * pixel of first line of text). */
333    TkTextIndex *indexPtr)	/* Structure to fill in. */
334{
335    int pixelOffset = 0;
336
337    indexPtr->tree = textPtr->sharedTextPtr->tree;
338    indexPtr->textPtr = textPtr;
339
340    if (pixelIndex < 0) {
341	pixelIndex = 0;
342    }
343    indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
344	    textPtr, pixelIndex, &pixelOffset);
345
346    /*
347     * 'pixelIndex' was too large, so we try again, just to find the last
348     * pixel in the window.
349     */
350
351    if (indexPtr->linePtr == NULL) {
352	int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
353		textPtr)-1;
354
355	indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
356		textPtr, lastMinusOne, &pixelOffset);
357	indexPtr->byteIndex = 0;
358	return pixelOffset;
359    }
360    indexPtr->byteIndex = 0;
361
362    if (pixelOffset <= 0) {
363	return 0;
364    }
365    return TkTextMeasureDown(textPtr, indexPtr, pixelOffset);
366}
367
368/*
369 *---------------------------------------------------------------------------
370 *
371 * TkTextMakeByteIndex --
372 *
373 *	Given a line index and a byte index, look things up in the B-tree and
374 *	fill in a TkTextIndex structure.
375 *
376 * Results:
377 *	The structure at *indexPtr is filled in with information about the
378 *	character at lineIndex and byteIndex (or the closest existing
379 *	character, if the specified one doesn't exist), and indexPtr is
380 *	returned as result.
381 *
382 * Side effects:
383 *	None.
384 *
385 *---------------------------------------------------------------------------
386 */
387
388TkTextIndex *
389TkTextMakeByteIndex(
390    TkTextBTree tree,		/* Tree that lineIndex and byteIndex refer
391				 * to. */
392    CONST TkText *textPtr,
393    int lineIndex,		/* Index of desired line (0 means first line
394				 * of text). */
395    int byteIndex,		/* Byte index of desired character. */
396    TkTextIndex *indexPtr)	/* Structure to fill in. */
397{
398    TkTextSegment *segPtr;
399    int index;
400    CONST char *p, *start;
401    Tcl_UniChar ch;
402
403    indexPtr->tree = tree;
404    if (lineIndex < 0) {
405	lineIndex = 0;
406	byteIndex = 0;
407    }
408    if (byteIndex < 0) {
409	byteIndex = 0;
410    }
411    indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex);
412    if (indexPtr->linePtr == NULL) {
413	indexPtr->linePtr = TkBTreeFindLine(tree, textPtr,
414		TkBTreeNumLines(tree, textPtr));
415	byteIndex = 0;
416    }
417    if (byteIndex == 0) {
418	indexPtr->byteIndex = byteIndex;
419	return indexPtr;
420    }
421
422    /*
423     * Verify that the index is within the range of the line and points to a
424     * valid character boundary.
425     */
426
427    index = 0;
428    for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
429	if (segPtr == NULL) {
430	    /*
431	     * Use the index of the last character in the line. Since the last
432	     * character on the line is guaranteed to be a '\n', we can back
433	     * up a constant sizeof(char) bytes.
434	     */
435
436	    indexPtr->byteIndex = index - sizeof(char);
437	    break;
438	}
439	if (index + segPtr->size > byteIndex) {
440	    indexPtr->byteIndex = byteIndex;
441	    if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) {
442		/*
443		 * Prevent UTF-8 character from being split up by ensuring
444		 * that byteIndex falls on a character boundary. If index
445		 * falls in the middle of a UTF-8 character, it will be
446		 * adjusted to the end of that UTF-8 character.
447		 */
448
449		start = segPtr->body.chars + (byteIndex - index);
450		p = Tcl_UtfPrev(start, segPtr->body.chars);
451		p += Tcl_UtfToUniChar(p, &ch);
452		indexPtr->byteIndex += p - start;
453	    }
454	    break;
455	}
456	index += segPtr->size;
457    }
458    return indexPtr;
459}
460
461/*
462 *---------------------------------------------------------------------------
463 *
464 * TkTextMakeCharIndex --
465 *
466 *	Given a line index and a character index, look things up in the B-tree
467 *	and fill in a TkTextIndex structure.
468 *
469 * Results:
470 *	The structure at *indexPtr is filled in with information about the
471 *	character at lineIndex and charIndex (or the closest existing
472 *	character, if the specified one doesn't exist), and indexPtr is
473 *	returned as result.
474 *
475 * Side effects:
476 *	None.
477 *
478 *---------------------------------------------------------------------------
479 */
480
481TkTextIndex *
482TkTextMakeCharIndex(
483    TkTextBTree tree,		/* Tree that lineIndex and charIndex refer
484				 * to. */
485    TkText *textPtr,
486    int lineIndex,		/* Index of desired line (0 means first line
487				 * of text). */
488    int charIndex,		/* Index of desired character. */
489    TkTextIndex *indexPtr)	/* Structure to fill in. */
490{
491    register TkTextSegment *segPtr;
492    char *p, *start, *end;
493    int index, offset;
494    Tcl_UniChar ch;
495
496    indexPtr->tree = tree;
497    if (lineIndex < 0) {
498	lineIndex = 0;
499	charIndex = 0;
500    }
501    if (charIndex < 0) {
502	charIndex = 0;
503    }
504    indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex);
505    if (indexPtr->linePtr == NULL) {
506	indexPtr->linePtr = TkBTreeFindLine(tree, textPtr,
507		TkBTreeNumLines(tree, textPtr));
508	charIndex = 0;
509    }
510
511    /*
512     * Verify that the index is within the range of the line. If not, just use
513     * the index of the last character in the line.
514     */
515
516    index = 0;
517    for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
518	if (segPtr == NULL) {
519	    /*
520	     * Use the index of the last character in the line. Since the last
521	     * character on the line is guaranteed to be a '\n', we can back
522	     * up a constant sizeof(char) bytes.
523	     */
524
525	    indexPtr->byteIndex = index - sizeof(char);
526	    break;
527	}
528	if (segPtr->typePtr == &tkTextCharType) {
529	    /*
530	     * Turn character offset into a byte offset.
531	     */
532
533	    start = segPtr->body.chars;
534	    end = start + segPtr->size;
535	    for (p = start; p < end; p += offset) {
536		if (charIndex == 0) {
537		    indexPtr->byteIndex = index;
538		    return indexPtr;
539		}
540		charIndex--;
541		offset = Tcl_UtfToUniChar(p, &ch);
542		index += offset;
543	    }
544	} else {
545	    if (charIndex < segPtr->size) {
546		indexPtr->byteIndex = index;
547		break;
548	    }
549	    charIndex -= segPtr->size;
550	    index += segPtr->size;
551	}
552    }
553    return indexPtr;
554}
555
556/*
557 *---------------------------------------------------------------------------
558 *
559 * TkTextIndexToSeg --
560 *
561 *	Given an index, this function returns the segment and offset within
562 *	segment for the index.
563 *
564 * Results:
565 *	The return value is a pointer to the segment referred to by indexPtr;
566 *	this will always be a segment with non-zero size. The variable at
567 *	*offsetPtr is set to hold the integer offset within the segment of the
568 *	character given by indexPtr.
569 *
570 * Side effects:
571 *	None.
572 *
573 *---------------------------------------------------------------------------
574 */
575
576TkTextSegment *
577TkTextIndexToSeg(
578    CONST TkTextIndex *indexPtr,/* Text index. */
579    int *offsetPtr)		/* Where to store offset within segment, or
580				 * NULL if offset isn't wanted. */
581{
582    TkTextSegment *segPtr;
583    int offset;
584
585    for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr;
586	    offset >= segPtr->size;
587	    offset -= segPtr->size, segPtr = segPtr->nextPtr) {
588	/* Empty loop body. */
589    }
590    if (offsetPtr != NULL) {
591	*offsetPtr = offset;
592    }
593    return segPtr;
594}
595
596/*
597 *---------------------------------------------------------------------------
598 *
599 * TkTextSegToOffset --
600 *
601 *	Given a segment pointer and the line containing it, this function
602 *	returns the offset of the segment within its line.
603 *
604 * Results:
605 *	The return value is the offset (within its line) of the first
606 *	character in segPtr.
607 *
608 * Side effects:
609 *	None.
610 *
611 *---------------------------------------------------------------------------
612 */
613
614int
615TkTextSegToOffset(
616    CONST TkTextSegment *segPtr,/* Segment whose offset is desired. */
617    CONST TkTextLine *linePtr)	/* Line containing segPtr. */
618{
619    CONST TkTextSegment *segPtr2;
620    int offset = 0;
621
622    for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
623	    segPtr2 = segPtr2->nextPtr) {
624	offset += segPtr2->size;
625    }
626    return offset;
627}
628
629/*
630 *---------------------------------------------------------------------------
631 *
632 * TkTextGetObjIndex --
633 *
634 *	Simpler wrapper around the string based function, but could be
635 *	enhanced with a new object type in the future.
636 *
637 * Results:
638 *	see TkTextGetIndex
639 *
640 * Side effects:
641 *	None.
642 *
643 *---------------------------------------------------------------------------
644 */
645
646int
647TkTextGetObjIndex(
648    Tcl_Interp *interp,		/* Use this for error reporting. */
649    TkText *textPtr,		/* Information about text widget. */
650    Tcl_Obj *idxObj,		/* Object containing textual description of
651				 * position. */
652    TkTextIndex *indexPtr)	/* Index structure to fill in. */
653{
654    return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr,
655	    NULL);
656}
657
658/*
659 *---------------------------------------------------------------------------
660 *
661 * TkTextSharedGetObjIndex --
662 *
663 *	Simpler wrapper around the string based function, but could be
664 *	enhanced with a new object type in the future.
665 *
666 * Results:
667 *	see TkTextGetIndex
668 *
669 * Side effects:
670 *	None.
671 *
672 *---------------------------------------------------------------------------
673 */
674
675int
676TkTextSharedGetObjIndex(
677    Tcl_Interp *interp,		/* Use this for error reporting. */
678    TkSharedText *sharedTextPtr,/* Information about text widget. */
679    Tcl_Obj *idxObj,		/* Object containing textual description of
680				 * position. */
681    TkTextIndex *indexPtr)	/* Index structure to fill in. */
682{
683    return GetIndex(interp, sharedTextPtr, NULL, Tcl_GetString(idxObj),
684	    indexPtr, NULL);
685}
686
687/*
688 *---------------------------------------------------------------------------
689 *
690 * TkTextGetIndex --
691 *
692 *	Given a string, return the index that is described.
693 *
694 * Results:
695 *	The return value is a standard Tcl return result. If TCL_OK is
696 *	returned, then everything went well and the index at *indexPtr is
697 *	filled in; otherwise TCL_ERROR is returned and an error message is
698 *	left in the interp's result.
699 *
700 * Side effects:
701 *	None.
702 *
703 *---------------------------------------------------------------------------
704 */
705
706int
707TkTextGetIndex(
708    Tcl_Interp *interp,		/* Use this for error reporting. */
709    TkText *textPtr,		/* Information about text widget. */
710    CONST char *string,		/* Textual description of position. */
711    TkTextIndex *indexPtr)	/* Index structure to fill in. */
712{
713    return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL);
714}
715
716/*
717 *---------------------------------------------------------------------------
718 *
719 * GetIndex --
720 *
721 *	Given a string, return the index that is described.
722 *
723 * Results:
724 *	The return value is a standard Tcl return result. If TCL_OK is
725 *	returned, then everything went well and the index at *indexPtr is
726 *	filled in; otherwise TCL_ERROR is returned and an error message is
727 *	left in the interp's result.
728 *
729 *	If *canCachePtr is non-NULL, and everything went well, the integer it
730 *	points to is set to 1 if the indexPtr is something which can be
731 *	cached, and zero otherwise.
732 *
733 * Side effects:
734 *	None.
735 *
736 *---------------------------------------------------------------------------
737 */
738
739static int
740GetIndex(
741    Tcl_Interp *interp,		/* Use this for error reporting. */
742    TkSharedText *sharedPtr,
743    TkText *textPtr,		/* Information about text widget. */
744    CONST char *string,		/* Textual description of position. */
745    TkTextIndex *indexPtr,	/* Index structure to fill in. */
746    int *canCachePtr)		/* Pointer to integer to store whether we can
747				 * cache the index (or NULL). */
748{
749    char *p, *end, *endOfBase;
750    TkTextIndex first, last;
751    int wantLast, result;
752    char c;
753    CONST char *cp;
754    Tcl_DString copy;
755    int canCache = 0;
756
757    if (sharedPtr == NULL) {
758	sharedPtr = textPtr->sharedTextPtr;
759    }
760
761    /*
762     *---------------------------------------------------------------------
763     * Stage 1: check to see if the index consists of nothing but a mark name.
764     * We do this check now even though it's also done later, in order to
765     * allow mark names that include funny characters such as spaces or "+1c".
766     *---------------------------------------------------------------------
767     */
768
769    if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
770	goto done;
771    }
772
773    /*
774     *------------------------------------------------
775     * Stage 2: start again by parsing the base index.
776     *------------------------------------------------
777     */
778
779    indexPtr->tree = sharedPtr->tree;
780
781    /*
782     * First look for the form "tag.first" or "tag.last" where "tag" is the
783     * name of a valid tag. Try to use up as much as possible of the string in
784     * this check (strrchr instead of strchr below). Doing the check now, and
785     * in this way, allows tag names to include funny characters like "@" or
786     * "+1c".
787     */
788
789    Tcl_DStringInit(&copy);
790    p = strrchr(Tcl_DStringAppend(&copy, string, -1), '.');
791    if (p != NULL) {
792	TkTextSearch search;
793	TkTextTag *tagPtr;
794	Tcl_HashEntry *hPtr = NULL;
795	CONST char *tagName;
796
797	if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
798	    wantLast = 0;
799	    endOfBase = p+6;
800	} else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
801	    wantLast = 1;
802	    endOfBase = p+5;
803	} else {
804	    goto tryxy;
805	}
806
807	tagPtr = NULL;
808	tagName = Tcl_DStringValue(&copy);
809	if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) {
810	    /*
811	     * Special case for sel tag which is not stored in the hash table.
812	     */
813
814	    tagPtr = textPtr->selTagPtr;
815	} else {
816	    *p = 0;
817	    hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName);
818	    *p = '.';
819	    if (hPtr != NULL) {
820		tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
821	    }
822	}
823
824	if (tagPtr == NULL) {
825	    goto tryxy;
826	}
827
828	TkTextMakeByteIndex(sharedPtr->tree, textPtr, 0, 0, &first);
829	TkTextMakeByteIndex(sharedPtr->tree, textPtr,
830		TkBTreeNumLines(sharedPtr->tree, textPtr), 0, &last);
831	TkBTreeStartSearch(&first, &last, tagPtr, &search);
832	if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) {
833	    if (tagPtr == textPtr->selTagPtr) {
834		tagName = "sel";
835	    } else {
836		tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr);
837	    }
838	    Tcl_ResetResult(interp);
839	    Tcl_AppendResult(interp,
840		    "text doesn't contain any characters tagged with \"",
841		    tagName, "\"", NULL);
842	    Tcl_DStringFree(&copy);
843	    return TCL_ERROR;
844	}
845	*indexPtr = search.curIndex;
846	if (wantLast) {
847	    while (TkBTreeNextTag(&search)) {
848		*indexPtr = search.curIndex;
849	    }
850	}
851	goto gotBase;
852    }
853
854  tryxy:
855    if (string[0] == '@') {
856	/*
857	 * Find character at a given x,y location in the window.
858	 */
859
860	int x, y;
861
862	cp = string+1;
863	x = strtol(cp, &end, 0);
864	if ((end == cp) || (*end != ',')) {
865	    goto error;
866	}
867	cp = end+1;
868	y = strtol(cp, &end, 0);
869	if (end == cp) {
870	    goto error;
871	}
872	TkTextPixelIndex(textPtr, x, y, indexPtr, NULL);
873	endOfBase = end;
874	goto gotBase;
875    }
876
877    if (isdigit(UCHAR(string[0])) || (string[0] == '-')) {
878	int lineIndex, charIndex;
879
880	/*
881	 * Base is identified with line and character indices.
882	 */
883
884	lineIndex = strtol(string, &end, 0) - 1;
885	if ((end == string) || (*end != '.')) {
886	    goto error;
887	}
888	p = end+1;
889	if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
890	    charIndex = LAST_CHAR;
891	    endOfBase = p+3;
892	} else {
893	    charIndex = strtol(p, &end, 0);
894	    if (end == p) {
895		goto error;
896	    }
897	    endOfBase = end;
898	}
899	TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, charIndex,
900		indexPtr);
901	canCache = 1;
902	goto gotBase;
903    }
904
905    for (p = Tcl_DStringValue(&copy); *p != 0; p++) {
906	if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) {
907	    break;
908	}
909    }
910    endOfBase = p;
911    if (string[0] == '.') {
912	/*
913	 * See if the base position is the name of an embedded window.
914	 */
915
916	c = *endOfBase;
917	*endOfBase = 0;
918	result = TkTextWindowIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
919	*endOfBase = c;
920	if (result != 0) {
921	    goto gotBase;
922	}
923    }
924    if ((string[0] == 'e')
925	    && (strncmp(string, "end",
926	    (size_t) (endOfBase-Tcl_DStringValue(&copy))) == 0)) {
927	/*
928	 * Base position is end of text.
929	 */
930
931	TkTextMakeByteIndex(sharedPtr->tree, textPtr,
932		TkBTreeNumLines(sharedPtr->tree, textPtr), 0, indexPtr);
933	canCache = 1;
934	goto gotBase;
935    } else {
936	/*
937	 * See if the base position is the name of a mark.
938	 */
939
940	c = *endOfBase;
941	*endOfBase = 0;
942	result = TkTextMarkNameToIndex(textPtr, Tcl_DStringValue(&copy),
943		indexPtr);
944	*endOfBase = c;
945	if (result == TCL_OK) {
946	    goto gotBase;
947	}
948
949	/*
950	 * See if the base position is the name of an embedded image.
951	 */
952
953	c = *endOfBase;
954	*endOfBase = 0;
955	result = TkTextImageIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
956	*endOfBase = c;
957	if (result != 0) {
958	    goto gotBase;
959	}
960    }
961    goto error;
962
963    /*
964     *-------------------------------------------------------------------
965     * Stage 3: process zero or more modifiers. Each modifier is either a
966     * keyword like "wordend" or "linestart", or it has the form "op count
967     * units" where op is + or -, count is a number, and units is "chars" or
968     * "lines".
969     *-------------------------------------------------------------------
970     */
971
972  gotBase:
973    cp = endOfBase;
974    while (1) {
975	while (isspace(UCHAR(*cp))) {
976	    cp++;
977	}
978	if (*cp == 0) {
979	    break;
980	}
981
982	if ((*cp == '+') || (*cp == '-')) {
983	    cp = ForwBack(textPtr, cp, indexPtr);
984	} else {
985	    cp = StartEnd(textPtr, cp, indexPtr);
986	}
987	if (cp == NULL) {
988	    goto error;
989	}
990    }
991    Tcl_DStringFree(&copy);
992
993  done:
994    if (canCachePtr != NULL) {
995	*canCachePtr = canCache;
996    }
997    if (indexPtr->linePtr == NULL) {
998	Tcl_Panic("Bad index created");
999    }
1000    return TCL_OK;
1001
1002  error:
1003    Tcl_DStringFree(&copy);
1004    Tcl_ResetResult(interp);
1005    Tcl_AppendResult(interp, "bad text index \"", string, "\"", NULL);
1006    return TCL_ERROR;
1007}
1008
1009/*
1010 *---------------------------------------------------------------------------
1011 *
1012 * TkTextPrintIndex --
1013 *
1014 *	This function generates a string description of an index, suitable for
1015 *	reading in again later.
1016 *
1017 * Results:
1018 *	The characters pointed to by string are modified. Returns the number
1019 *	of characters in the string.
1020 *
1021 * Side effects:
1022 *	None.
1023 *
1024 *---------------------------------------------------------------------------
1025 */
1026
1027int
1028TkTextPrintIndex(
1029    CONST TkText *textPtr,
1030    CONST TkTextIndex *indexPtr,/* Pointer to index. */
1031    char *string)		/* Place to store the position. Must have at
1032				 * least TK_POS_CHARS characters. */
1033{
1034    TkTextSegment *segPtr;
1035    TkTextLine *linePtr;
1036    int numBytes, charIndex;
1037
1038    numBytes = indexPtr->byteIndex;
1039    charIndex = 0;
1040    linePtr = indexPtr->linePtr;
1041
1042    for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
1043	if (segPtr == NULL) {
1044	    /*
1045	     * Two logical lines merged into one display line through eliding
1046	     * of a newline.
1047	     */
1048
1049	    linePtr = TkBTreeNextLine(NULL, linePtr);
1050	    segPtr = linePtr->segPtr;
1051	}
1052	if (numBytes <= segPtr->size) {
1053	    break;
1054	}
1055	if (segPtr->typePtr == &tkTextCharType) {
1056	    charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
1057	} else {
1058	    charIndex += segPtr->size;
1059	}
1060	numBytes -= segPtr->size;
1061    }
1062
1063    if (segPtr->typePtr == &tkTextCharType) {
1064	charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
1065    } else {
1066	charIndex += numBytes;
1067    }
1068
1069    return sprintf(string, "%d.%d",
1070	    TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex);
1071}
1072
1073/*
1074 *---------------------------------------------------------------------------
1075 *
1076 * TkTextIndexCmp --
1077 *
1078 *	Compare two indices to see which one is earlier in the text.
1079 *
1080 * Results:
1081 *	The return value is 0 if index1Ptr and index2Ptr refer to the same
1082 *	position in the file, -1 if index1Ptr refers to an earlier position
1083 *	than index2Ptr, and 1 otherwise.
1084 *
1085 * Side effects:
1086 *	None.
1087 *
1088 *---------------------------------------------------------------------------
1089 */
1090
1091int
1092TkTextIndexCmp(
1093    CONST TkTextIndex*index1Ptr,/* First index. */
1094    CONST TkTextIndex*index2Ptr)/* Second index. */
1095{
1096    int line1, line2;
1097
1098    if (index1Ptr->linePtr == index2Ptr->linePtr) {
1099	if (index1Ptr->byteIndex < index2Ptr->byteIndex) {
1100	    return -1;
1101	} else if (index1Ptr->byteIndex > index2Ptr->byteIndex) {
1102	    return 1;
1103	} else {
1104	    return 0;
1105	}
1106    }
1107
1108    /*
1109     * Assumption here that it is ok for comparisons to reflect the full
1110     * B-tree and not just the portion that is available to any client. This
1111     * should be true because the only indexPtr's we should be given are ones
1112     * which are valid for the current client.
1113     */
1114
1115    line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr);
1116    line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr);
1117    if (line1 < line2) {
1118	return -1;
1119    }
1120    if (line1 > line2) {
1121	return 1;
1122    }
1123    return 0;
1124}
1125
1126/*
1127 *---------------------------------------------------------------------------
1128 *
1129 * ForwBack --
1130 *
1131 *	This function handles +/- modifiers for indices to adjust the index
1132 *	forwards or backwards.
1133 *
1134 * Results:
1135 *	If the modifier in string is successfully parsed then the return value
1136 *	is the address of the first character after the modifier, and
1137 *	*indexPtr is updated to reflect the modifier. If there is a syntax
1138 *	error in the modifier then NULL is returned.
1139 *
1140 * Side effects:
1141 *	None.
1142 *
1143 *---------------------------------------------------------------------------
1144 */
1145
1146static CONST char *
1147ForwBack(
1148    TkText *textPtr,		/* Information about text widget. */
1149    CONST char *string,		/* String to parse for additional info about
1150				 * modifier (count and units). Points to "+"
1151				 * or "-" that starts modifier. */
1152    TkTextIndex *indexPtr)	/* Index to update as specified in string. */
1153{
1154    register CONST char *p, *units;
1155    char *end;
1156    int count, lineIndex, modifier;
1157    size_t length;
1158
1159    /*
1160     * Get the count (how many units forward or backward).
1161     */
1162
1163    p = string+1;
1164    while (isspace(UCHAR(*p))) {
1165	p++;
1166    }
1167    count = strtol(p, &end, 0);
1168    if (end == p) {
1169	return NULL;
1170    }
1171    p = end;
1172    while (isspace(UCHAR(*p))) {
1173	p++;
1174    }
1175
1176    /*
1177     * Find the end of this modifier (next space or + or - character), then
1178     * check if there is a textual 'display' or 'any' modifier. These
1179     * modifiers can be their own word (in which case they can be abbreviated)
1180     * or they can follow on to the actual unit in a single word (in which
1181     * case no abbreviation is allowed). So, 'display lines', 'd lines',
1182     * 'displaylin' are all ok, but 'dline' is not.
1183     */
1184
1185    units = p;
1186    while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
1187	p++;
1188    }
1189    length = p - units;
1190    if ((*units == 'd') &&
1191	    (strncmp(units, "display", (length > 7 ? 7 : length)) == 0)) {
1192	modifier = TKINDEX_DISPLAY;
1193	if (length > 7) {
1194	    p -= (length - 7);
1195	}
1196    } else if ((*units == 'a') &&
1197	    (strncmp(units, "any", (length > 3 ? 3 : length)) == 0)) {
1198	modifier = TKINDEX_ANY;
1199	if (length > 3) {
1200	    p -= (length - 3);
1201	}
1202    } else {
1203	modifier = TKINDEX_NONE;
1204    }
1205
1206    /*
1207     * If we had a modifier, which we interpreted ok, so now forward to the
1208     * actual units.
1209     */
1210
1211    if (modifier != TKINDEX_NONE) {
1212	while (isspace(UCHAR(*p))) {
1213	    p++;
1214	}
1215	units = p;
1216	while (*p!='\0' && !isspace(UCHAR(*p)) && *p!='+' && *p!='-') {
1217	    p++;
1218	}
1219	length = p - units;
1220    }
1221
1222    /*
1223     * Finally parse the units.
1224     */
1225
1226    if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
1227	TkTextCountType type;
1228
1229	if (modifier == TKINDEX_NONE) {
1230	    type = COUNT_INDICES;
1231	} else if (modifier == TKINDEX_ANY) {
1232	    type = COUNT_CHARS;
1233	} else {
1234	    type = COUNT_DISPLAY_CHARS;
1235	}
1236
1237	if (*string == '+') {
1238	    TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type);
1239	} else {
1240	    TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type);
1241	}
1242    } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) {
1243	TkTextCountType type;
1244
1245	if (modifier == TKINDEX_DISPLAY) {
1246	    type = COUNT_DISPLAY_INDICES;
1247	} else {
1248	    type = COUNT_INDICES;
1249	}
1250
1251	if (*string == '+') {
1252	    TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type);
1253	} else {
1254	    TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type);
1255	}
1256    } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
1257	if (modifier == TKINDEX_DISPLAY) {
1258	    /*
1259	     * Find the appropriate pixel offset of the current position
1260	     * within its display line. This also has the side-effect of
1261	     * moving indexPtr, but that doesn't matter since we will do it
1262	     * again below.
1263	     *
1264	     * Then find the right display line, and finally calculated the
1265	     * index we want in that display line, based on the original pixel
1266	     * offset.
1267	     */
1268
1269	    int xOffset, forward;
1270
1271	    if (TkTextIsElided(textPtr, indexPtr, NULL)) {
1272		/*
1273		 * Go forward to the first non-elided index.
1274		 */
1275
1276		TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
1277			COUNT_DISPLAY_INDICES);
1278	    }
1279
1280	    /*
1281	     * Unlike the Forw/BackChars code, the display line code is
1282	     * sensitive to whether we are genuinely going forwards or
1283	     * backwards. So, we need to determine that. This is important in
1284	     * the case where we have "+ -3 displaylines", for example.
1285	     */
1286
1287	    if ((count < 0) ^ (*string == '-')) {
1288		forward = 0;
1289	    } else {
1290		forward = 1;
1291	    }
1292
1293	    count = abs(count);
1294	    if (count == 0) {
1295		return p;
1296	    }
1297
1298	    if (forward) {
1299		TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset);
1300		while (count-- > 0) {
1301		    /*
1302		     * Go to the end of the line, then forward one char/byte
1303		     * to get to the beginning of the next line.
1304		     */
1305
1306		    TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
1307		    TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
1308			    COUNT_DISPLAY_INDICES);
1309		}
1310	    } else {
1311		TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset);
1312		while (count-- > 0) {
1313		    /*
1314		     * Go to the beginning of the line, then backward one
1315		     * char/byte to get to the end of the previous line.
1316		     */
1317
1318		    TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
1319		    TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr,
1320			    COUNT_DISPLAY_INDICES);
1321		}
1322		TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
1323	    }
1324
1325	    /*
1326	     * This call assumes indexPtr is the beginning of a display line
1327	     * and moves it to the 'xOffset' position of that line, which is
1328	     * just what we want.
1329	     */
1330
1331	    TkTextIndexOfX(textPtr, xOffset, indexPtr);
1332	} else {
1333	    lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
1334	    if (*string == '+') {
1335		lineIndex += count;
1336	    } else {
1337		lineIndex -= count;
1338
1339		/*
1340		 * The check below retains the character position, even if the
1341		 * line runs off the start of the file. Without it, the
1342		 * character position will get reset to 0 by TkTextMakeIndex.
1343		 */
1344
1345		if (lineIndex < 0) {
1346		    lineIndex = 0;
1347		}
1348	    }
1349
1350	    /*
1351	     * This doesn't work quite right if using a proportional font or
1352	     * UTF-8 characters with varying numbers of bytes, or if there are
1353	     * embedded windows, images, etc. The cursor will bop around,
1354	     * keeping a constant number of bytes (not characters) from the
1355	     * left edge (but making sure not to split any UTF-8 characters),
1356	     * regardless of the x-position the index corresponds to. The
1357	     * proper way to do this is to get the x-position of the index and
1358	     * then pick the character at the same x-position in the new line.
1359	     */
1360
1361	    TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex,
1362		    indexPtr->byteIndex, indexPtr);
1363	}
1364    } else {
1365	return NULL;
1366    }
1367    return p;
1368}
1369
1370/*
1371 *---------------------------------------------------------------------------
1372 *
1373 * TkTextIndexForwBytes --
1374 *
1375 *	Given an index for a text widget, this function creates a new index
1376 *	that points "count" bytes ahead of the source index.
1377 *
1378 * Results:
1379 *	*dstPtr is modified to refer to the character "count" bytes after
1380 *	srcPtr, or to the last character in the TkText if there aren't "count"
1381 *	bytes left.
1382 *
1383 *	In this latter case, the function returns '1' to indicate that not all
1384 *	of 'byteCount' could be used.
1385 *
1386 * Side effects:
1387 *	None.
1388 *
1389 *---------------------------------------------------------------------------
1390 */
1391
1392int
1393TkTextIndexForwBytes(
1394    CONST TkText *textPtr,
1395    CONST TkTextIndex *srcPtr,	/* Source index. */
1396    int byteCount,		/* How many bytes forward to move. May be
1397				 * negative. */
1398    TkTextIndex *dstPtr)	/* Destination index: gets modified. */
1399{
1400    TkTextLine *linePtr;
1401    TkTextSegment *segPtr;
1402    int lineLength;
1403
1404    if (byteCount < 0) {
1405	TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr);
1406	return 0;
1407    }
1408
1409    *dstPtr = *srcPtr;
1410    dstPtr->byteIndex += byteCount;
1411    while (1) {
1412	/*
1413	 * Compute the length of the current line.
1414	 */
1415
1416	lineLength = 0;
1417	for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
1418		segPtr = segPtr->nextPtr) {
1419	    lineLength += segPtr->size;
1420	}
1421
1422	/*
1423	 * If the new index is in the same line then we're done. Otherwise go
1424	 * on to the next line.
1425	 */
1426
1427	if (dstPtr->byteIndex < lineLength) {
1428	    return 0;
1429	}
1430	dstPtr->byteIndex -= lineLength;
1431	linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr);
1432	if (linePtr == NULL) {
1433	    dstPtr->byteIndex = lineLength - 1;
1434	    return 1;
1435	}
1436	dstPtr->linePtr = linePtr;
1437    }
1438}
1439
1440/*
1441 *---------------------------------------------------------------------------
1442 *
1443 * TkTextIndexForwChars --
1444 *
1445 *	Given an index for a text widget, this function creates a new index
1446 *	that points "count" items of type given by "type" ahead of the source
1447 *	index. "count" can be zero, which is useful in the case where one
1448 *	wishes to move forward by display (non-elided) chars or indices or one
1449 *	wishes to move forward by chars, skipping any intervening indices. In
1450 *	this case dstPtr will point to the first acceptable index which is
1451 *	encountered.
1452 *
1453 * Results:
1454 *	*dstPtr is modified to refer to the character "count" items after
1455 *	srcPtr, or to the last character in the TkText if there aren't
1456 *	sufficient items left in the widget.
1457 *
1458 * Side effects:
1459 *	None.
1460 *
1461 *---------------------------------------------------------------------------
1462 */
1463
1464void
1465TkTextIndexForwChars(
1466    CONST TkText *textPtr,	/* Overall information about text widget. */
1467    CONST TkTextIndex *srcPtr,	/* Source index. */
1468    int charCount,		/* How many characters forward to move. May
1469				 * be negative. */
1470    TkTextIndex *dstPtr,	/* Destination index: gets modified. */
1471    TkTextCountType type)	/* The type of item to count */
1472{
1473    TkTextLine *linePtr;
1474    TkTextSegment *segPtr;
1475    TkTextElideInfo *infoPtr = NULL;
1476    int byteOffset;
1477    char *start, *end, *p;
1478    Tcl_UniChar ch;
1479    int elide = 0;
1480    int checkElided = (type & COUNT_DISPLAY);
1481
1482    if (charCount < 0) {
1483	TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type);
1484	return;
1485    }
1486    if (checkElided) {
1487	infoPtr = (TkTextElideInfo *)
1488		ckalloc((unsigned) sizeof(TkTextElideInfo));
1489	elide = TkTextIsElided(textPtr, srcPtr, infoPtr);
1490    }
1491
1492    *dstPtr = *srcPtr;
1493
1494    /*
1495     * Find seg that contains src byteIndex. Move forward specified number of
1496     * chars.
1497     */
1498
1499    if (checkElided) {
1500	/*
1501	 * In this case we have already calculated the information we need, so
1502	 * no need to use TkTextIndexToSeg()
1503	 */
1504
1505	segPtr = infoPtr->segPtr;
1506	byteOffset = dstPtr->byteIndex - infoPtr->segOffset;
1507    } else {
1508	segPtr = TkTextIndexToSeg(dstPtr, &byteOffset);
1509    }
1510
1511    while (1) {
1512	/*
1513	 * Go through each segment in line looking for specified character
1514	 * index.
1515	 */
1516
1517	for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
1518	    /*
1519	     * If we do need to pay attention to the visibility of
1520	     * characters/indices, check that first. If the current segment
1521	     * isn't visible, then we simply continue the loop.
1522	     */
1523
1524	    if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType)
1525		    || (segPtr->typePtr == &tkTextToggleOnType))) {
1526		TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
1527
1528		/*
1529		 * The elide state only changes if this tag is either the
1530		 * current highest priority tag (and is therefore being
1531		 * toggled off), or it's a new tag with higher priority.
1532		 */
1533
1534		if (tagPtr->elideString != NULL) {
1535		    infoPtr->tagCnts[tagPtr->priority]++;
1536		    if (infoPtr->tagCnts[tagPtr->priority] & 1) {
1537			infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
1538		    }
1539
1540		    if (tagPtr->priority >= infoPtr->elidePriority) {
1541			if (segPtr->typePtr == &tkTextToggleOffType) {
1542			    /*
1543			     * If it is being toggled off, and it has an elide
1544			     * string, it must actually be the current highest
1545			     * priority tag, so this check is redundant:
1546			     */
1547
1548			    if (tagPtr->priority != infoPtr->elidePriority) {
1549				Tcl_Panic("Bad tag priority being toggled off");
1550			    }
1551
1552			    /*
1553			     * Find previous elide tag, if any (if not then
1554			     * elide will be zero, of course).
1555			     */
1556
1557			    elide = 0;
1558			    while (--infoPtr->elidePriority > 0) {
1559				if (infoPtr->tagCnts[infoPtr->elidePriority]
1560					& 1) {
1561				    elide = infoPtr->tagPtrs
1562					    [infoPtr->elidePriority]->elide;
1563				    break;
1564				}
1565			    }
1566			} else {
1567			    elide = tagPtr->elide;
1568			    infoPtr->elidePriority = tagPtr->priority;
1569			}
1570		    }
1571		}
1572	    }
1573
1574	    if (!elide) {
1575		if (segPtr->typePtr == &tkTextCharType) {
1576		    start = segPtr->body.chars + byteOffset;
1577		    end = segPtr->body.chars + segPtr->size;
1578		    for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) {
1579			if (charCount == 0) {
1580			    dstPtr->byteIndex += (p - start);
1581			    goto forwardCharDone;
1582			}
1583			charCount--;
1584		    }
1585		} else if (type & COUNT_INDICES) {
1586		    if (charCount < segPtr->size - byteOffset) {
1587			dstPtr->byteIndex += charCount;
1588			goto forwardCharDone;
1589		    }
1590		    charCount -= segPtr->size - byteOffset;
1591		}
1592	    }
1593
1594	    dstPtr->byteIndex += segPtr->size - byteOffset;
1595	    byteOffset = 0;
1596	}
1597
1598	/*
1599	 * Go to the next line. If we are at the end of the text item, back up
1600	 * one byte (for the terminal '\n' character) and return that index.
1601	 */
1602
1603	linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr);
1604	if (linePtr == NULL) {
1605	    dstPtr->byteIndex -= sizeof(char);
1606	    goto forwardCharDone;
1607	}
1608	dstPtr->linePtr = linePtr;
1609	dstPtr->byteIndex = 0;
1610	segPtr = dstPtr->linePtr->segPtr;
1611    }
1612
1613  forwardCharDone:
1614    if (infoPtr != NULL) {
1615	TkTextFreeElideInfo(infoPtr);
1616	ckfree((char *) infoPtr);
1617    }
1618}
1619
1620/*
1621 *---------------------------------------------------------------------------
1622 *
1623 * TkTextIndexCount --
1624 *
1625 *	Given an ordered pair of indices in a text widget, this function
1626 *	counts how many characters (not bytes) are between the two indices.
1627 *
1628 *	It is illegal to call this function with unordered indices.
1629 *
1630 *	Note that 'textPtr' is only used if we need to check for elided
1631 *	attributes, i.e. if type is COUNT_DISPLAY_INDICES or
1632 *	COUNT_DISPLAY_CHARS.
1633 *
1634 * Results:
1635 *	The number of characters in the given range, which meet the
1636 *	appropriate 'type' attributes.
1637 *
1638 * Side effects:
1639 *	None.
1640 *
1641 *---------------------------------------------------------------------------
1642 */
1643
1644int
1645TkTextIndexCount(
1646    CONST TkText *textPtr,	/* Overall information about text widget. */
1647    CONST TkTextIndex *indexPtr1,
1648				/* Index describing location of character from
1649				 * which to count. */
1650    CONST TkTextIndex *indexPtr2,
1651				/* Index describing location of last character
1652				 * at which to stop the count. */
1653    TkTextCountType type)	/* The kind of indices to count. */
1654{
1655    TkTextLine *linePtr1;
1656    TkTextSegment *segPtr, *seg2Ptr = NULL;
1657    TkTextElideInfo *infoPtr = NULL;
1658    int byteOffset, maxBytes, count = 0, elide = 0;
1659    int checkElided = (type & COUNT_DISPLAY);
1660
1661    /*
1662     * Find seg that contains src index, and remember how many bytes not to
1663     * count in the given segment.
1664     */
1665
1666    segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset);
1667    linePtr1 = indexPtr1->linePtr;
1668
1669    seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes);
1670
1671    if (checkElided) {
1672	infoPtr = (TkTextElideInfo *)
1673		ckalloc((unsigned) sizeof(TkTextElideInfo));
1674	elide = TkTextIsElided(textPtr, indexPtr1, infoPtr);
1675    }
1676
1677    while (1) {
1678	/*
1679	 * Go through each segment in line adding up the number of characters.
1680	 */
1681
1682	for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
1683	    /*
1684	     * If we do need to pay attention to the visibility of
1685	     * characters/indices, check that first. If the current segment
1686	     * isn't visible, then we simply continue the loop.
1687	     */
1688
1689	    if (checkElided) {
1690		if ((segPtr->typePtr == &tkTextToggleOffType)
1691			|| (segPtr->typePtr == &tkTextToggleOnType)) {
1692		    TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
1693
1694		    /*
1695		     * The elide state only changes if this tag is either the
1696		     * current highest priority tag (and is therefore being
1697		     * toggled off), or it's a new tag with higher priority.
1698		     */
1699
1700		    if (tagPtr->elideString != NULL) {
1701			infoPtr->tagCnts[tagPtr->priority]++;
1702			if (infoPtr->tagCnts[tagPtr->priority] & 1) {
1703			    infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
1704			}
1705			if (tagPtr->priority >= infoPtr->elidePriority) {
1706			    if (segPtr->typePtr == &tkTextToggleOffType) {
1707				/*
1708				 * If it is being toggled off, and it has an
1709				 * elide string, it must actually be the
1710				 * current highest priority tag, so this check
1711				 * is redundant:
1712				 */
1713
1714				if (tagPtr->priority!=infoPtr->elidePriority) {
1715				    Tcl_Panic("Bad tag priority being toggled off");
1716				}
1717
1718				/*
1719				 * Find previous elide tag, if any (if not
1720				 * then elide will be zero, of course).
1721				 */
1722
1723				elide = 0;
1724				while (--infoPtr->elidePriority > 0) {
1725				    if (infoPtr->tagCnts[
1726					    infoPtr->elidePriority] & 1) {
1727					elide = infoPtr->tagPtrs[
1728						infoPtr->elidePriority]->elide;
1729					break;
1730				    }
1731				}
1732			    } else {
1733				elide = tagPtr->elide;
1734				infoPtr->elidePriority = tagPtr->priority;
1735			    }
1736			}
1737		    }
1738		}
1739		if (elide) {
1740		    if (segPtr == seg2Ptr) {
1741			goto countDone;
1742		    }
1743		    byteOffset = 0;
1744		    continue;
1745		}
1746	    }
1747
1748	    if (segPtr->typePtr == &tkTextCharType) {
1749		int byteLen = segPtr->size - byteOffset;
1750		register unsigned char *str = (unsigned char *)
1751			segPtr->body.chars + byteOffset;
1752		register int i;
1753
1754		if (segPtr == seg2Ptr) {
1755		    if (byteLen > (maxBytes - byteOffset)) {
1756			byteLen = maxBytes - byteOffset;
1757		    }
1758		}
1759		i = byteLen;
1760
1761		/*
1762		 * This is a speed sensitive function, so run specially over
1763		 * the string to count continuous ascii characters before
1764		 * resorting to the Tcl_NumUtfChars call. This is a long form
1765		 * of:
1766		 *
1767		 *   stringPtr->numChars =
1768		 *	     Tcl_NumUtfChars(objPtr->bytes, objPtr->length);
1769		 */
1770
1771		while (i && (*str < 0xC0)) {
1772		    i--;
1773		    str++;
1774		}
1775		count += byteLen - i;
1776		if (i) {
1777		    count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset
1778			    + (byteLen - i), i);
1779		}
1780	    } else {
1781		if (type & COUNT_INDICES) {
1782		    int byteLen = segPtr->size - byteOffset;
1783
1784		    if (segPtr == seg2Ptr) {
1785			if (byteLen > (maxBytes - byteOffset)) {
1786			    byteLen = maxBytes - byteOffset;
1787			}
1788		    }
1789		    count += byteLen;
1790		}
1791	    }
1792	    if (segPtr == seg2Ptr) {
1793		goto countDone;
1794	    }
1795	    byteOffset = 0;
1796	}
1797
1798	/*
1799	 * Go to the next line. If we are at the end of the text item, back up
1800	 * one byte (for the terminal '\n' character) and return that index.
1801	 */
1802
1803	linePtr1 = TkBTreeNextLine(textPtr, linePtr1);
1804	if (linePtr1 == NULL) {
1805	    Tcl_Panic("Reached end of text widget when counting characters");
1806	}
1807	segPtr = linePtr1->segPtr;
1808    }
1809
1810  countDone:
1811    if (infoPtr != NULL) {
1812	TkTextFreeElideInfo(infoPtr);
1813	ckfree((char *) infoPtr);
1814    }
1815    return count;
1816}
1817
1818/*
1819 *---------------------------------------------------------------------------
1820 *
1821 * TkTextIndexBackBytes --
1822 *
1823 *	Given an index for a text widget, this function creates a new index
1824 *	that points "count" bytes earlier than the source index.
1825 *
1826 * Results:
1827 *	*dstPtr is modified to refer to the character "count" bytes before
1828 *	srcPtr, or to the first character in the TkText if there aren't
1829 *	"count" bytes earlier than srcPtr.
1830 *
1831 *	Returns 1 if we couldn't use all of 'byteCount' because we have run
1832 *	into the beginning or end of the text, and zero otherwise.
1833 *
1834 * Side effects:
1835 *	None.
1836 *
1837 *---------------------------------------------------------------------------
1838 */
1839
1840int
1841TkTextIndexBackBytes(
1842    CONST TkText *textPtr,
1843    CONST TkTextIndex *srcPtr,	/* Source index. */
1844    int byteCount,		/* How many bytes backward to move. May be
1845				 * negative. */
1846    TkTextIndex *dstPtr)	/* Destination index: gets modified. */
1847{
1848    TkTextSegment *segPtr;
1849    int lineIndex;
1850
1851    if (byteCount < 0) {
1852	return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr);
1853    }
1854
1855    *dstPtr = *srcPtr;
1856    dstPtr->byteIndex -= byteCount;
1857    lineIndex = -1;
1858    while (dstPtr->byteIndex < 0) {
1859	/*
1860	 * Move back one line in the text. If we run off the beginning of the
1861	 * file then just return the first character in the text.
1862	 */
1863
1864	if (lineIndex < 0) {
1865	    lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr);
1866	}
1867	if (lineIndex == 0) {
1868	    dstPtr->byteIndex = 0;
1869	    return 1;
1870	}
1871	lineIndex--;
1872	dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex);
1873
1874	/*
1875	 * Compute the length of the line and add that to dstPtr->charIndex.
1876	 */
1877
1878	for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
1879		segPtr = segPtr->nextPtr) {
1880	    dstPtr->byteIndex += segPtr->size;
1881	}
1882    }
1883    return 0;
1884}
1885
1886/*
1887 *---------------------------------------------------------------------------
1888 *
1889 * TkTextIndexBackChars --
1890 *
1891 *	Given an index for a text widget, this function creates a new index
1892 *	that points "count" items of type given by "type" earlier than the
1893 *	source index. "count" can be zero, which is useful in the case where
1894 *	one wishes to move backward by display (non-elided) chars or indices
1895 *	or one wishes to move backward by chars, skipping any intervening
1896 *	indices. In this case the returned index *dstPtr will point just
1897 *	_after_ the first acceptable index which is encountered.
1898 *
1899 * Results:
1900 *	*dstPtr is modified to refer to the character "count" items before
1901 *	srcPtr, or to the first index in the window if there aren't sufficient
1902 *	items earlier than srcPtr.
1903 *
1904 * Side effects:
1905 *	None.
1906 *
1907 *---------------------------------------------------------------------------
1908 */
1909
1910void
1911TkTextIndexBackChars(
1912    CONST TkText *textPtr,	/* Overall information about text widget. */
1913    CONST TkTextIndex *srcPtr,	/* Source index. */
1914    int charCount,		/* How many characters backward to move. May
1915				 * be negative. */
1916    TkTextIndex *dstPtr,	/* Destination index: gets modified. */
1917    TkTextCountType type)	/* The type of item to count */
1918{
1919    TkTextSegment *segPtr, *oldPtr;
1920    TkTextElideInfo *infoPtr = NULL;
1921    int lineIndex, segSize;
1922    CONST char *p, *start, *end;
1923    int elide = 0;
1924    int checkElided = (type & COUNT_DISPLAY);
1925
1926    if (charCount < 0) {
1927	TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type);
1928	return;
1929    }
1930    if (checkElided) {
1931	infoPtr = (TkTextElideInfo *) ckalloc(sizeof(TkTextElideInfo));
1932	elide = TkTextIsElided(textPtr, srcPtr, infoPtr);
1933    }
1934
1935    *dstPtr = *srcPtr;
1936
1937    /*
1938     * Find offset within seg that contains byteIndex. Move backward specified
1939     * number of chars.
1940     */
1941
1942    lineIndex = -1;
1943
1944    segSize = dstPtr->byteIndex;
1945
1946    if (checkElided) {
1947	segPtr = infoPtr->segPtr;
1948	segSize -= infoPtr->segOffset;
1949    } else {
1950	TkTextLine *linePtr = dstPtr->linePtr;
1951	for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
1952	    if (segPtr == NULL) {
1953		/*
1954		 * Two logical lines merged into one display line through
1955		 * eliding of a newline.
1956		 */
1957
1958		linePtr = TkBTreeNextLine(NULL, linePtr);
1959		segPtr = linePtr->segPtr;
1960	    }
1961	    if (segSize <= segPtr->size) {
1962		break;
1963	    }
1964	    segSize -= segPtr->size;
1965	}
1966    }
1967
1968    /*
1969     * Now segPtr points to the segment containing the starting index.
1970     */
1971
1972    while (1) {
1973	/*
1974	 * If we do need to pay attention to the visibility of
1975	 * characters/indices, check that first. If the current segment isn't
1976	 * visible, then we simply continue the loop.
1977	 */
1978
1979	if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType)
1980		|| (segPtr->typePtr == &tkTextToggleOnType))) {
1981	    TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
1982
1983	    /*
1984	     * The elide state only changes if this tag is either the current
1985	     * highest priority tag (and is therefore being toggled off), or
1986	     * it's a new tag with higher priority.
1987	     */
1988
1989	    if (tagPtr->elideString != NULL) {
1990		infoPtr->tagCnts[tagPtr->priority]++;
1991		if (infoPtr->tagCnts[tagPtr->priority] & 1) {
1992		    infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
1993		}
1994		if (tagPtr->priority >= infoPtr->elidePriority) {
1995		    if (segPtr->typePtr == &tkTextToggleOnType) {
1996			/*
1997			 * If it is being toggled on, and it has an elide
1998			 * string, it must actually be the current highest
1999			 * priority tag, so this check is redundant:
2000			 */
2001
2002			if (tagPtr->priority != infoPtr->elidePriority) {
2003			    Tcl_Panic("Bad tag priority being toggled on");
2004			}
2005
2006			/*
2007			 * Find previous elide tag, if any (if not then elide
2008			 * will be zero, of course).
2009			 */
2010
2011			elide = 0;
2012			while (--infoPtr->elidePriority > 0) {
2013			    if (infoPtr->tagCnts[infoPtr->elidePriority] & 1) {
2014				elide = infoPtr->tagPtrs[
2015					infoPtr->elidePriority]->elide;
2016				break;
2017			    }
2018			}
2019		    } else {
2020			elide = tagPtr->elide;
2021			infoPtr->elidePriority = tagPtr->priority;
2022		    }
2023		}
2024	    }
2025	}
2026
2027	if (!elide) {
2028	    if (segPtr->typePtr == &tkTextCharType) {
2029		start = segPtr->body.chars;
2030		end = segPtr->body.chars + segSize;
2031		for (p = end; ; p = Tcl_UtfPrev(p, start)) {
2032		    if (charCount == 0) {
2033			dstPtr->byteIndex -= (end - p);
2034			goto backwardCharDone;
2035		    }
2036		    if (p == start) {
2037			break;
2038		    }
2039		    charCount--;
2040		}
2041	    } else {
2042		if (type & COUNT_INDICES) {
2043		    if (charCount <= segSize) {
2044			dstPtr->byteIndex -= charCount;
2045			goto backwardCharDone;
2046		    }
2047		    charCount -= segSize;
2048		}
2049	    }
2050	}
2051	dstPtr->byteIndex -= segSize;
2052
2053	/*
2054	 * Move back into previous segment.
2055	 */
2056
2057	oldPtr = segPtr;
2058	segPtr = dstPtr->linePtr->segPtr;
2059	if (segPtr != oldPtr) {
2060	    for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) {
2061		/* Empty body. */
2062	    }
2063	    segSize = segPtr->size;
2064	    continue;
2065	}
2066
2067	/*
2068	 * Move back to previous line.
2069	 */
2070
2071	if (lineIndex < 0) {
2072	    lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr);
2073	}
2074	if (lineIndex == 0) {
2075	    dstPtr->byteIndex = 0;
2076	    goto backwardCharDone;
2077	}
2078	lineIndex--;
2079	dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex);
2080
2081	/*
2082	 * Compute the length of the line and add that to dstPtr->byteIndex.
2083	 */
2084
2085	oldPtr = dstPtr->linePtr->segPtr;
2086	for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
2087	    dstPtr->byteIndex += segPtr->size;
2088	    oldPtr = segPtr;
2089	}
2090	segPtr = oldPtr;
2091	segSize = segPtr->size;
2092    }
2093
2094  backwardCharDone:
2095    if (infoPtr != NULL) {
2096	TkTextFreeElideInfo(infoPtr);
2097	ckfree((char *) infoPtr);
2098    }
2099}
2100
2101/*
2102 *----------------------------------------------------------------------
2103 *
2104 * StartEnd --
2105 *
2106 *	This function handles modifiers like "wordstart" and "lineend" to
2107 *	adjust indices forwards or backwards.
2108 *
2109 * Results:
2110 *	If the modifier is successfully parsed then the return value is the
2111 *	address of the first character after the modifier, and *indexPtr is
2112 *	updated to reflect the modifier. If there is a syntax error in the
2113 *	modifier then NULL is returned.
2114 *
2115 * Side effects:
2116 *	None.
2117 *
2118 *----------------------------------------------------------------------
2119 */
2120
2121static CONST char *
2122StartEnd(
2123    TkText *textPtr,		/* Information about text widget. */
2124    CONST char *string,		/* String to parse for additional info about
2125				 * modifier (count and units). Points to first
2126				 * character of modifer word. */
2127    TkTextIndex *indexPtr)	/* Index to modify based on string. */
2128{
2129    CONST char *p;
2130    size_t length;
2131    register TkTextSegment *segPtr;
2132    int modifier;
2133
2134    /*
2135     * Find the end of the modifier word.
2136     */
2137
2138    for (p = string; isalnum(UCHAR(*p)); p++) {
2139	/* Empty loop body. */
2140    }
2141
2142    length = p-string;
2143    if ((*string == 'd') &&
2144	    (strncmp(string, "display", (length > 7 ? 7 : length)) == 0)) {
2145	modifier = TKINDEX_DISPLAY;
2146	if (length > 7) {
2147	    p -= (length - 7);
2148	}
2149    } else if ((*string == 'a') &&
2150	    (strncmp(string, "any", (length > 3 ? 3 : length)) == 0)) {
2151	modifier = TKINDEX_ANY;
2152	if (length > 3) {
2153	    p -= (length - 3);
2154	}
2155    } else {
2156	modifier = TKINDEX_NONE;
2157    }
2158
2159    /*
2160     * If we had a modifier, which we interpreted ok, so now forward to the
2161     * actual units.
2162     */
2163
2164    if (modifier != TKINDEX_NONE) {
2165	while (isspace(UCHAR(*p))) {
2166	    p++;
2167	}
2168	string = p;
2169	while ((*p!='\0') && !isspace(UCHAR(*p)) && (*p!='+') && (*p!='-')) {
2170	    p++;
2171	}
2172	length = p - string;
2173    }
2174
2175    if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
2176	    && (length >= 5)) {
2177	if (modifier == TKINDEX_DISPLAY) {
2178	    TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
2179	} else {
2180	    indexPtr->byteIndex = 0;
2181	    for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
2182		    segPtr = segPtr->nextPtr) {
2183		indexPtr->byteIndex += segPtr->size;
2184	    }
2185
2186	    /*
2187	     * We know '\n' is encoded with a single byte index.
2188	     */
2189
2190	    indexPtr->byteIndex -= sizeof(char);
2191	}
2192    } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
2193	    && (length >= 5)) {
2194	if (modifier == TKINDEX_DISPLAY) {
2195	    TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
2196	} else {
2197	    indexPtr->byteIndex = 0;
2198	}
2199    } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
2200	    && (length >= 5)) {
2201	int firstChar = 1;
2202	int offset;
2203
2204	/*
2205	 * If the current character isn't part of a word then just move
2206	 * forward one character. Otherwise move forward until finding a
2207	 * character that isn't part of a word and stop there.
2208	 */
2209
2210	if (modifier == TKINDEX_DISPLAY) {
2211	    TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
2212		    COUNT_DISPLAY_INDICES);
2213	}
2214	segPtr = TkTextIndexToSeg(indexPtr, &offset);
2215	while (1) {
2216	    int chSize = 1;
2217
2218	    if (segPtr->typePtr == &tkTextCharType) {
2219		Tcl_UniChar ch;
2220
2221		chSize = Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch);
2222		if (!Tcl_UniCharIsWordChar(ch)) {
2223		    break;
2224		}
2225		firstChar = 0;
2226	    }
2227	    offset += chSize;
2228	    indexPtr->byteIndex += chSize;
2229	    if (offset >= segPtr->size) {
2230		segPtr = TkTextIndexToSeg(indexPtr, &offset);
2231	    }
2232	}
2233	if (firstChar) {
2234	    if (modifier == TKINDEX_DISPLAY) {
2235		TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
2236			COUNT_DISPLAY_INDICES);
2237	    } else {
2238		TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
2239			COUNT_INDICES);
2240	    }
2241	}
2242    } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
2243	    && (length >= 5)) {
2244	int firstChar = 1;
2245	int offset;
2246
2247	if (modifier == TKINDEX_DISPLAY) {
2248	    TkTextIndexForwChars(NULL, indexPtr, 0, indexPtr,
2249		    COUNT_DISPLAY_INDICES);
2250	}
2251
2252	/*
2253	 * Starting with the current character, look for one that's not part
2254	 * of a word and keep moving backward until you find one. Then if the
2255	 * character found wasn't the first one, move forward again one
2256	 * position.
2257	 */
2258
2259	segPtr = TkTextIndexToSeg(indexPtr, &offset);
2260	while (1) {
2261	    int chSize = 1;
2262
2263	    if (segPtr->typePtr == &tkTextCharType) {
2264		Tcl_UniChar ch;
2265
2266		Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch);
2267		if (!Tcl_UniCharIsWordChar(ch)) {
2268		    break;
2269		}
2270		if (offset > 0) {
2271		    chSize = (segPtr->body.chars + offset
2272			    - Tcl_UtfPrev(segPtr->body.chars + offset,
2273			    segPtr->body.chars));
2274		}
2275		firstChar = 0;
2276	    }
2277	    offset -= chSize;
2278	    indexPtr->byteIndex -= chSize;
2279	    if (offset < 0) {
2280		if (indexPtr->byteIndex < 0) {
2281		    indexPtr->byteIndex = 0;
2282		    goto done;
2283		}
2284		segPtr = TkTextIndexToSeg(indexPtr, &offset);
2285	    }
2286	}
2287
2288	if (!firstChar) {
2289	    if (modifier == TKINDEX_DISPLAY) {
2290		TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
2291			COUNT_DISPLAY_INDICES);
2292	    } else {
2293		TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
2294			COUNT_INDICES);
2295	    }
2296	}
2297    } else {
2298	return NULL;
2299    }
2300
2301  done:
2302    return p;
2303}
2304
2305/*
2306 * Local Variables:
2307 * mode: c
2308 * c-basic-offset: 4
2309 * fill-column: 78
2310 * End:
2311 */
2312