diff options
Diffstat (limited to 'generic/tkTextIndex.c')
-rw-r--r-- | generic/tkTextIndex.c | 1725 |
1 files changed, 1420 insertions, 305 deletions
diff --git a/generic/tkTextIndex.c b/generic/tkTextIndex.c index 26576ed..5173e90 100644 --- a/generic/tkTextIndex.c +++ b/generic/tkTextIndex.c @@ -1,20 +1,19 @@ -/* +/* * tkTextIndex.c -- * - * This module provides procedures that manipulate indices for - * text widgets. + * This module provides functions that manipulate indices for text + * widgets. * * Copyright (c) 1992-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tkTextIndex.c,v 1.6 2002/08/05 04:30:40 dgp Exp $ + * RCS: @(#) $Id: tkTextIndex.c,v 1.28.2.1 2009/02/06 08:13:23 das Exp $ */ #include "default.h" -#include "tkPort.h" #include "tkInt.h" #include "tkText.h" @@ -25,21 +24,354 @@ #define LAST_CHAR 1000000 /* - * Forward declarations for procedures defined later in this file: + * Modifiers for index parsing: 'display', 'any' or nothing. + */ + +#define TKINDEX_NONE 0 +#define TKINDEX_DISPLAY 1 +#define TKINDEX_ANY 2 + +/* + * Forward declarations for functions defined later in this file: + */ + +static CONST char * ForwBack(TkText *textPtr, CONST char *string, + TkTextIndex *indexPtr); +static CONST char * StartEnd(TkText *textPtr, CONST char *string, + TkTextIndex *indexPtr); +static int GetIndex(Tcl_Interp *interp, TkSharedText *sharedPtr, + TkText *textPtr, CONST char *string, + TkTextIndex *indexPtr, int *canCachePtr); + +/* + * The "textindex" Tcl_Obj definition: + */ + +static void DupTextIndexInternalRep(Tcl_Obj *srcPtr, + Tcl_Obj *copyPtr); +static void FreeTextIndexInternalRep(Tcl_Obj *listPtr); +static int SetTextIndexFromAny(Tcl_Interp *interp, + Tcl_Obj *objPtr); +static void UpdateStringOfTextIndex(Tcl_Obj *objPtr); + +/* + * Accessor macros for the "textindex" type. + */ + +#define GET_TEXTINDEX(objPtr) \ + ((TkTextIndex *) (objPtr)->internalRep.twoPtrValue.ptr1) +#define GET_INDEXEPOCH(objPtr) \ + (PTR2INT((objPtr)->internalRep.twoPtrValue.ptr2)) +#define SET_TEXTINDEX(objPtr, indexPtr) \ + ((objPtr)->internalRep.twoPtrValue.ptr1 = (VOID *) (indexPtr)) +#define SET_INDEXEPOCH(objPtr, epoch) \ + ((objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(epoch)) + +/* + * Define the 'textindex' object type, which Tk uses to represent indices in + * text widgets internally. + */ + +Tcl_ObjType tkTextIndexType = { + "textindex", /* name */ + FreeTextIndexInternalRep, /* freeIntRepProc */ + DupTextIndexInternalRep, /* dupIntRepProc */ + NULL, /* updateStringProc */ + SetTextIndexFromAny /* setFromAnyProc */ +}; + +static void +FreeTextIndexInternalRep( + Tcl_Obj *indexObjPtr) /* TextIndex object with internal rep to + * free. */ +{ + TkTextIndex *indexPtr = GET_TEXTINDEX(indexObjPtr); + if (indexPtr->textPtr != NULL) { + if (--indexPtr->textPtr->refCount == 0) { + /* + * The text widget has been deleted and we need to free it now. + */ + + ckfree((char *) (indexPtr->textPtr)); + } + } + ckfree((char *) indexPtr); +} + +static void +DupTextIndexInternalRep( + Tcl_Obj *srcPtr, /* TextIndex obj with internal rep to copy. */ + Tcl_Obj *copyPtr) /* TextIndex obj with internal rep to set. */ +{ + int epoch; + TkTextIndex *dupIndexPtr, *indexPtr; + + dupIndexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex)); + indexPtr = GET_TEXTINDEX(srcPtr); + epoch = GET_INDEXEPOCH(srcPtr); + + dupIndexPtr->tree = indexPtr->tree; + dupIndexPtr->linePtr = indexPtr->linePtr; + dupIndexPtr->byteIndex = indexPtr->byteIndex; + dupIndexPtr->textPtr = indexPtr->textPtr; + if (dupIndexPtr->textPtr != NULL) { + dupIndexPtr->textPtr->refCount++; + } + SET_TEXTINDEX(copyPtr, dupIndexPtr); + SET_INDEXEPOCH(copyPtr, epoch); + copyPtr->typePtr = &tkTextIndexType; +} + +/* + * This will not be called except by TkTextNewIndexObj below. This is because + * if a TkTextIndex is no longer valid, it is not possible to regenerate the + * string representation. */ -static CONST char * ForwBack _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); -static CONST char * StartEnd _ANSI_ARGS_((CONST char *string, - TkTextIndex *indexPtr)); +static void +UpdateStringOfTextIndex( + Tcl_Obj *objPtr) +{ + char buffer[TK_POS_CHARS]; + register int len; + + CONST TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr); + + len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer); + + objPtr->bytes = ckalloc((unsigned) len + 1); + strcpy(objPtr->bytes, buffer); + objPtr->length = len; +} + +static int +SetTextIndexFromAny( + Tcl_Interp *interp, /* Used for error reporting if not NULL. */ + Tcl_Obj *objPtr) /* The object to convert. */ +{ + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "can't convert value to textindex except via TkTextGetIndexFromObj API", + -1); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * MakeObjIndex -- + * + * This function generates a Tcl_Obj description of an index, suitable + * for reading in again later. If the 'textPtr' is NULL then we still + * generate an index object, but it's internal description is deemed + * non-cacheable, and therefore effectively useless (apart from as a + * temporary memory storage). This is used for indices whose meaning is + * very temporary (like @0,0 or the name of a mark or tag). The mapping + * from such strings/objects to actual TkTextIndex pointers is not stable + * to minor text widget changes which we do not track (we track + * insertions and deletions). + * + * Results: + * A pointer to an allocated TkTextIndex which will be freed + * automatically when the Tcl_Obj is used for other purposes. + * + * Side effects: + * A small amount of memory is allocated. + * + *--------------------------------------------------------------------------- + */ + +static TkTextIndex * +MakeObjIndex( + TkText *textPtr, /* Information about text widget. */ + Tcl_Obj *objPtr, /* Object containing description of + * position. */ + CONST TkTextIndex *origPtr) /* Pointer to index. */ +{ + TkTextIndex *indexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex)); + + indexPtr->tree = origPtr->tree; + indexPtr->linePtr = origPtr->linePtr; + indexPtr->byteIndex = origPtr->byteIndex; + SET_TEXTINDEX(objPtr, indexPtr); + objPtr->typePtr = &tkTextIndexType; + indexPtr->textPtr = textPtr; + + if (textPtr != NULL) { + textPtr->refCount++; + SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch); + } else { + SET_INDEXEPOCH(objPtr, 0); + } + return indexPtr; +} + +CONST TkTextIndex * +TkTextGetIndexFromObj( + Tcl_Interp *interp, /* Use this for error reporting. */ + TkText *textPtr, /* Information about text widget. */ + Tcl_Obj *objPtr) /* Object containing description of + * position. */ +{ + TkTextIndex index; + TkTextIndex *indexPtr = NULL; + int cache; + + if (objPtr->typePtr == &tkTextIndexType) { + int epoch; + + indexPtr = GET_TEXTINDEX(objPtr); + epoch = GET_INDEXEPOCH(objPtr); + + if (epoch == textPtr->sharedTextPtr->stateEpoch) { + if (indexPtr->textPtr == textPtr) { + return indexPtr; + } + } + } + + /* + * The object is either not an index type or referred to a different text + * widget, or referred to the correct widget, but it is out of date (text + * has been added/deleted since). + */ + + if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index, + &cache) != TCL_OK) { + return NULL; + } + + if (objPtr->typePtr != NULL) { + if (objPtr->bytes == NULL) { + objPtr->typePtr->updateStringProc(objPtr); + } + if ((objPtr->typePtr->freeIntRepProc) != NULL) { + (*objPtr->typePtr->freeIntRepProc)(objPtr); + } + } + + return MakeObjIndex((cache ? textPtr : NULL), objPtr, &index); +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextNewIndexObj -- + * + * This function generates a Tcl_Obj description of an index, suitable + * for reading in again later. The index generated is effectively stable + * to all except insertion/deletion operations on the widget. + * + * Results: + * A new Tcl_Obj with refCount zero. + * + * Side effects: + * A small amount of memory is allocated. + * + *--------------------------------------------------------------------------- + */ + +Tcl_Obj * +TkTextNewIndexObj( + TkText *textPtr, /* Text widget for this index */ + CONST TkTextIndex *indexPtr)/* Pointer to index. */ +{ + Tcl_Obj *retVal; + + retVal = Tcl_NewObj(); + retVal->bytes = NULL; + + /* + * Assumption that the above call returns an object with: + * retVal->typePtr == NULL + */ + + MakeObjIndex(textPtr, retVal, indexPtr); + + /* + * Unfortunately, it isn't possible for us to regenerate the string + * representation so we have to create it here, while we can be sure the + * contents of the index are still valid. + */ + + UpdateStringOfTextIndex(retVal); + return retVal; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextMakePixelIndex -- + * + * Given a pixel index and a byte index, look things up in the B-tree and + * fill in a TkTextIndex structure. + * + * The valid input range for pixelIndex is from 0 to the number of pixels + * in the widget-1. Anything outside that range will be rounded to the + * closest acceptable value. + * + * Results: + * + * The structure at *indexPtr is filled in with information about the + * character at pixelIndex (or the closest existing character, if the + * specified one doesn't exist), and the number of excess pixels is + * returned as a result. This means if the given pixel index is exactly + * correct for the top-edge of the indexPtr, then zero will be returned, + * and otherwise we will return the calculation 'desired pixelIndex' - + * 'actual pixel index of indexPtr'. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextMakePixelIndex( + TkText *textPtr, /* The Text Widget */ + int pixelIndex, /* Pixel-index of desired line (0 means first + * pixel of first line of text). */ + TkTextIndex *indexPtr) /* Structure to fill in. */ +{ + int pixelOffset = 0; + + indexPtr->tree = textPtr->sharedTextPtr->tree; + indexPtr->textPtr = textPtr; + + if (pixelIndex < 0) { + pixelIndex = 0; + } + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, + textPtr, pixelIndex, &pixelOffset); + + /* + * 'pixelIndex' was too large, so we try again, just to find the last + * pixel in the window. + */ + + if (indexPtr->linePtr == NULL) { + int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, + textPtr)-1; + + indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, + textPtr, lastMinusOne, &pixelOffset); + indexPtr->byteIndex = 0; + return pixelOffset; + } + indexPtr->byteIndex = 0; + + if (pixelOffset <= 0) { + return 0; + } + return TkTextMeasureDown(textPtr, indexPtr, pixelOffset); +} /* *--------------------------------------------------------------------------- * * TkTextMakeByteIndex -- * - * Given a line index and a byte index, look things up in the B-tree - * and fill in a TkTextIndex structure. + * Given a line index and a byte index, look things up in the B-tree and + * fill in a TkTextIndex structure. * * Results: * The structure at *indexPtr is filled in with information about the @@ -54,13 +386,14 @@ static CONST char * StartEnd _ANSI_ARGS_((CONST char *string, */ TkTextIndex * -TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) - TkTextBTree tree; /* Tree that lineIndex and charIndex refer +TkTextMakeByteIndex( + TkTextBTree tree, /* Tree that lineIndex and byteIndex refer * to. */ - int lineIndex; /* Index of desired line (0 means first - * line of text). */ - int byteIndex; /* Byte index of desired character. */ - TkTextIndex *indexPtr; /* Structure to fill in. */ + CONST TkText *textPtr, + int lineIndex, /* Index of desired line (0 means first line + * of text). */ + int byteIndex, /* Byte index of desired character. */ + TkTextIndex *indexPtr) /* Structure to fill in. */ { TkTextSegment *segPtr; int index; @@ -75,9 +408,10 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) if (byteIndex < 0) { byteIndex = 0; } - indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); if (indexPtr->linePtr == NULL) { - indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree)); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, + TkBTreeNumLines(tree, textPtr)); byteIndex = 0; } if (byteIndex == 0) { @@ -86,19 +420,19 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) } /* - * Verify that the index is within the range of the line and points - * to a valid character boundary. + * Verify that the index is within the range of the line and points to a + * valid character boundary. */ index = 0; for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { if (segPtr == NULL) { /* - * Use the index of the last character in the line. Since - * the last character on the line is guaranteed to be a '\n', - * we can back up a constant sizeof(char) bytes. + * Use the index of the last character in the line. Since the last + * character on the line is guaranteed to be a '\n', we can back + * up a constant sizeof(char) bytes. */ - + indexPtr->byteIndex = index - sizeof(char); break; } @@ -107,7 +441,7 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) { /* * Prevent UTF-8 character from being split up by ensuring - * that byteIndex falls on a character boundary. If index + * that byteIndex falls on a character boundary. If index * falls in the middle of a UTF-8 character, it will be * adjusted to the end of that UTF-8 character. */ @@ -129,8 +463,8 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) * * TkTextMakeCharIndex -- * - * Given a line index and a character index, look things up in the - * B-tree and fill in a TkTextIndex structure. + * Given a line index and a character index, look things up in the B-tree + * and fill in a TkTextIndex structure. * * Results: * The structure at *indexPtr is filled in with information about the @@ -145,13 +479,14 @@ TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr) */ TkTextIndex * -TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) - TkTextBTree tree; /* Tree that lineIndex and charIndex refer +TkTextMakeCharIndex( + TkTextBTree tree, /* Tree that lineIndex and charIndex refer * to. */ - int lineIndex; /* Index of desired line (0 means first - * line of text). */ - int charIndex; /* Index of desired character. */ - TkTextIndex *indexPtr; /* Structure to fill in. */ + TkText *textPtr, + int lineIndex, /* Index of desired line (0 means first line + * of text). */ + int charIndex, /* Index of desired character. */ + TkTextIndex *indexPtr) /* Structure to fill in. */ { register TkTextSegment *segPtr; char *p, *start, *end; @@ -166,26 +501,27 @@ TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) if (charIndex < 0) { charIndex = 0; } - indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); if (indexPtr->linePtr == NULL) { - indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree)); + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, + TkBTreeNumLines(tree, textPtr)); charIndex = 0; } /* - * Verify that the index is within the range of the line. - * If not, just use the index of the last character in the line. + * Verify that the index is within the range of the line. If not, just use + * the index of the last character in the line. */ index = 0; for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { if (segPtr == NULL) { /* - * Use the index of the last character in the line. Since - * the last character on the line is guaranteed to be a '\n', - * we can back up a constant sizeof(char) bytes. + * Use the index of the last character in the line. Since the last + * character on the line is guaranteed to be a '\n', we can back + * up a constant sizeof(char) bytes. */ - + indexPtr->byteIndex = index - sizeof(char); break; } @@ -222,14 +558,14 @@ TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) * * TkTextIndexToSeg -- * - * Given an index, this procedure returns the segment and offset - * within segment for the index. + * Given an index, this function returns the segment and offset within + * segment for the index. * * Results: - * The return value is a pointer to the segment referred to by - * indexPtr; this will always be a segment with non-zero size. The - * variable at *offsetPtr is set to hold the integer offset within - * the segment of the character given by indexPtr. + * The return value is a pointer to the segment referred to by indexPtr; + * this will always be a segment with non-zero size. The variable at + * *offsetPtr is set to hold the integer offset within the segment of the + * character given by indexPtr. * * Side effects: * None. @@ -238,9 +574,9 @@ TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr) */ TkTextSegment * -TkTextIndexToSeg(indexPtr, offsetPtr) - CONST TkTextIndex *indexPtr;/* Text index. */ - int *offsetPtr; /* Where to store offset within segment, or +TkTextIndexToSeg( + CONST TkTextIndex *indexPtr,/* Text index. */ + int *offsetPtr) /* Where to store offset within segment, or * NULL if offset isn't wanted. */ { TkTextSegment *segPtr; @@ -262,7 +598,7 @@ TkTextIndexToSeg(indexPtr, offsetPtr) * * TkTextSegToOffset -- * - * Given a segment pointer and the line containing it, this procedure + * Given a segment pointer and the line containing it, this function * returns the offset of the segment within its line. * * Results: @@ -276,14 +612,13 @@ TkTextIndexToSeg(indexPtr, offsetPtr) */ int -TkTextSegToOffset(segPtr, linePtr) - CONST TkTextSegment *segPtr;/* Segment whose offset is desired. */ - CONST TkTextLine *linePtr; /* Line containing segPtr. */ +TkTextSegToOffset( + CONST TkTextSegment *segPtr,/* Segment whose offset is desired. */ + CONST TkTextLine *linePtr) /* Line containing segPtr. */ { CONST TkTextSegment *segPtr2; - int offset; + int offset = 0; - offset = 0; for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr; segPtr2 = segPtr2->nextPtr) { offset += segPtr2->size; @@ -294,15 +629,73 @@ TkTextSegToOffset(segPtr, linePtr) /* *--------------------------------------------------------------------------- * + * TkTextGetObjIndex -- + * + * Simpler wrapper around the string based function, but could be + * enhanced with a new object type in the future. + * + * Results: + * see TkTextGetIndex + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextGetObjIndex( + Tcl_Interp *interp, /* Use this for error reporting. */ + TkText *textPtr, /* Information about text widget. */ + Tcl_Obj *idxObj, /* Object containing textual description of + * position. */ + TkTextIndex *indexPtr) /* Index structure to fill in. */ +{ + return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr, + NULL); +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextSharedGetObjIndex -- + * + * Simpler wrapper around the string based function, but could be + * enhanced with a new object type in the future. + * + * Results: + * see TkTextGetIndex + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextSharedGetObjIndex( + Tcl_Interp *interp, /* Use this for error reporting. */ + TkSharedText *sharedTextPtr,/* Information about text widget. */ + Tcl_Obj *idxObj, /* Object containing textual description of + * position. */ + TkTextIndex *indexPtr) /* Index structure to fill in. */ +{ + return GetIndex(interp, sharedTextPtr, NULL, Tcl_GetString(idxObj), + indexPtr, NULL); +} + +/* + *--------------------------------------------------------------------------- + * * TkTextGetIndex -- * * Given a string, return the index that is described. * * Results: - * The return value is a standard Tcl return result. If TCL_OK is + * The return value is a standard Tcl return result. If TCL_OK is * returned, then everything went well and the index at *indexPtr is - * filled in; otherwise TCL_ERROR is returned and an error message - * is left in the interp's result. + * filled in; otherwise TCL_ERROR is returned and an error message is + * left in the interp's result. * * Side effects: * None. @@ -311,33 +704,70 @@ TkTextSegToOffset(segPtr, linePtr) */ int -TkTextGetIndex(interp, textPtr, string, indexPtr) - Tcl_Interp *interp; /* Use this for error reporting. */ - TkText *textPtr; /* Information about text widget. */ - CONST char *string; /* Textual description of position. */ - TkTextIndex *indexPtr; /* Index structure to fill in. */ +TkTextGetIndex( + Tcl_Interp *interp, /* Use this for error reporting. */ + TkText *textPtr, /* Information about text widget. */ + CONST char *string, /* Textual description of position. */ + TkTextIndex *indexPtr) /* Index structure to fill in. */ +{ + return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL); +} + +/* + *--------------------------------------------------------------------------- + * + * GetIndex -- + * + * Given a string, return the index that is described. + * + * Results: + * The return value is a standard Tcl return result. If TCL_OK is + * returned, then everything went well and the index at *indexPtr is + * filled in; otherwise TCL_ERROR is returned and an error message is + * left in the interp's result. + * + * If *canCachePtr is non-NULL, and everything went well, the integer it + * points to is set to 1 if the indexPtr is something which can be + * cached, and zero otherwise. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static int +GetIndex( + Tcl_Interp *interp, /* Use this for error reporting. */ + TkSharedText *sharedPtr, + TkText *textPtr, /* Information about text widget. */ + CONST char *string, /* Textual description of position. */ + TkTextIndex *indexPtr, /* Index structure to fill in. */ + int *canCachePtr) /* Pointer to integer to store whether we can + * cache the index (or NULL). */ { char *p, *end, *endOfBase; - Tcl_HashEntry *hPtr; - TkTextTag *tagPtr; - TkTextSearch search; TkTextIndex first, last; int wantLast, result; char c; CONST char *cp; Tcl_DString copy; + int canCache = 0; + + if (sharedPtr == NULL) { + sharedPtr = textPtr->sharedTextPtr; + } /* *--------------------------------------------------------------------- - * Stage 1: check to see if the index consists of nothing but a mark - * name. We do this check now even though it's also done later, in - * order to allow mark names that include funny characters such as - * spaces or "+1c". + * Stage 1: check to see if the index consists of nothing but a mark name. + * We do this check now even though it's also done later, in order to + * allow mark names that include funny characters such as spaces or "+1c". *--------------------------------------------------------------------- */ if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) { - return TCL_OK; + goto done; } /* @@ -346,19 +776,24 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) *------------------------------------------------ */ - indexPtr->tree = textPtr->tree; + indexPtr->tree = sharedPtr->tree; /* - * First look for the form "tag.first" or "tag.last" where "tag" - * is the name of a valid tag. Try to use up as much as possible - * of the string in this check (strrchr instead of strchr below). - * Doing the check now, and in this way, allows tag names to include - * funny characters like "@" or "+1c". + * First look for the form "tag.first" or "tag.last" where "tag" is the + * name of a valid tag. Try to use up as much as possible of the string in + * this check (strrchr instead of strchr below). Doing the check now, and + * in this way, allows tag names to include funny characters like "@" or + * "+1c". */ Tcl_DStringInit(©); p = strrchr(Tcl_DStringAppend(©, string, -1), '.'); if (p != NULL) { + TkTextSearch search; + TkTextTag *tagPtr; + Tcl_HashEntry *hPtr = NULL; + CONST char *tagName; + if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) { wantLast = 0; endOfBase = p+6; @@ -368,23 +803,42 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) } else { goto tryxy; } - *p = 0; - hPtr = Tcl_FindHashEntry(&textPtr->tagTable, Tcl_DStringValue(©)); - *p = '.'; - if (hPtr == NULL) { + + tagPtr = NULL; + tagName = Tcl_DStringValue(©); + if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) { + /* + * Special case for sel tag which is not stored in the hash table. + */ + + tagPtr = textPtr->selTagPtr; + } else { + *p = 0; + hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName); + *p = '.'; + if (hPtr != NULL) { + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + } + } + + if (tagPtr == NULL) { goto tryxy; } - tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, - &last); + + TkTextMakeByteIndex(sharedPtr->tree, textPtr, 0, 0, &first); + TkTextMakeByteIndex(sharedPtr->tree, textPtr, + TkBTreeNumLines(sharedPtr->tree, textPtr), 0, &last); TkBTreeStartSearch(&first, &last, tagPtr, &search); if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) { + if (tagPtr == textPtr->selTagPtr) { + tagName = "sel"; + } else { + tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr); + } Tcl_ResetResult(interp); Tcl_AppendResult(interp, "text doesn't contain any characters tagged with \"", - Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"", - (char *) NULL); + tagName, "\"", NULL); Tcl_DStringFree(©); return TCL_ERROR; } @@ -397,7 +851,7 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) goto gotBase; } - tryxy: + tryxy: if (string[0] == '@') { /* * Find character at a given x,y location in the window. @@ -415,9 +869,9 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) if (end == cp) { goto error; } - TkTextPixelIndex(textPtr, x, y, indexPtr); + TkTextPixelIndex(textPtr, x, y, indexPtr, NULL); endOfBase = end; - goto gotBase; + goto gotBase; } if (isdigit(UCHAR(string[0])) || (string[0] == '-')) { @@ -442,7 +896,9 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) } endOfBase = end; } - TkTextMakeCharIndex(textPtr->tree, lineIndex, charIndex, indexPtr); + TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, charIndex, + indexPtr); + canCache = 1; goto gotBase; } @@ -472,8 +928,9 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) * Base position is end of text. */ - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), - 0, indexPtr); + TkTextMakeByteIndex(sharedPtr->tree, textPtr, + TkBTreeNumLines(sharedPtr->tree, textPtr), 0, indexPtr); + canCache = 1; goto gotBase; } else { /* @@ -490,7 +947,7 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) } /* - * See if the base position is the name of an embedded image + * See if the base position is the name of an embedded image. */ c = *endOfBase; @@ -505,14 +962,14 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) /* *------------------------------------------------------------------- - * Stage 3: process zero or more modifiers. Each modifier is either - * a keyword like "wordend" or "linestart", or it has the form - * "op count units" where op is + or -, count is a number, and units - * is "chars" or "lines". + * Stage 3: process zero or more modifiers. Each modifier is either a + * keyword like "wordend" or "linestart", or it has the form "op count + * units" where op is + or -, count is a number, and units is "chars" or + * "lines". *------------------------------------------------------------------- */ - gotBase: + gotBase: cp = endOfBase; while (1) { while (isspace(UCHAR(*cp))) { @@ -521,24 +978,31 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) if (*cp == 0) { break; } - + if ((*cp == '+') || (*cp == '-')) { - cp = ForwBack(cp, indexPtr); + cp = ForwBack(textPtr, cp, indexPtr); } else { - cp = StartEnd(cp, indexPtr); + cp = StartEnd(textPtr, cp, indexPtr); } if (cp == NULL) { goto error; } } Tcl_DStringFree(©); + + done: + if (canCachePtr != NULL) { + *canCachePtr = canCache; + } + if (indexPtr->linePtr == NULL) { + Tcl_Panic("Bad index created"); + } return TCL_OK; - error: + error: Tcl_DStringFree(©); Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "bad text index \"", string, "\"", - (char *) NULL); + Tcl_AppendResult(interp, "bad text index \"", string, "\"", NULL); return TCL_ERROR; } @@ -546,12 +1010,13 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) *--------------------------------------------------------------------------- * * TkTextPrintIndex -- - * - * This procedure generates a string description of an index, suitable - * for reading in again later. + * + * This function generates a string description of an index, suitable for + * reading in again later. * * Results: - * The characters pointed to by string are modified. + * The characters pointed to by string are modified. Returns the number + * of characters in the string. * * Side effects: * None. @@ -559,18 +1024,31 @@ TkTextGetIndex(interp, textPtr, string, indexPtr) *--------------------------------------------------------------------------- */ -void -TkTextPrintIndex(indexPtr, string) - CONST TkTextIndex *indexPtr;/* Pointer to index. */ - char *string; /* Place to store the position. Must have - * at least TK_POS_CHARS characters. */ +int +TkTextPrintIndex( + CONST TkText *textPtr, + CONST TkTextIndex *indexPtr,/* Pointer to index. */ + char *string) /* Place to store the position. Must have at + * least TK_POS_CHARS characters. */ { TkTextSegment *segPtr; + TkTextLine *linePtr; int numBytes, charIndex; numBytes = indexPtr->byteIndex; charIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + linePtr = indexPtr->linePtr; + + for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line through eliding + * of a newline. + */ + + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } if (numBytes <= segPtr->size) { break; } @@ -581,13 +1059,15 @@ TkTextPrintIndex(indexPtr, string) } numBytes -= segPtr->size; } + if (segPtr->typePtr == &tkTextCharType) { charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes); } else { charIndex += numBytes; } - sprintf(string, "%d.%d", TkBTreeLineIndex(indexPtr->linePtr) + 1, - charIndex); + + return sprintf(string, "%d.%d", + TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex); } /* @@ -609,9 +1089,9 @@ TkTextPrintIndex(indexPtr, string) */ int -TkTextIndexCmp(index1Ptr, index2Ptr) - CONST TkTextIndex *index1Ptr; /* First index. */ - CONST TkTextIndex *index2Ptr; /* Second index. */ +TkTextIndexCmp( + CONST TkTextIndex*index1Ptr,/* First index. */ + CONST TkTextIndex*index2Ptr)/* Second index. */ { int line1, line2; @@ -624,8 +1104,16 @@ TkTextIndexCmp(index1Ptr, index2Ptr) return 0; } } - line1 = TkBTreeLineIndex(index1Ptr->linePtr); - line2 = TkBTreeLineIndex(index2Ptr->linePtr); + + /* + * Assumption here that it is ok for comparisons to reflect the full + * B-tree and not just the portion that is available to any client. This + * should be true because the only indexPtr's we should be given are ones + * which are valid for the current client. + */ + + line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr); + line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr); if (line1 < line2) { return -1; } @@ -640,14 +1128,14 @@ TkTextIndexCmp(index1Ptr, index2Ptr) * * ForwBack -- * - * This procedure handles +/- modifiers for indices to adjust the - * index forwards or backwards. + * This function handles +/- modifiers for indices to adjust the index + * forwards or backwards. * * Results: - * If the modifier in string is successfully parsed then the return - * value is the address of the first character after the modifier, - * and *indexPtr is updated to reflect the modifier. If there is a - * syntax error in the modifier then NULL is returned. + * If the modifier in string is successfully parsed then the return value + * is the address of the first character after the modifier, and + * *indexPtr is updated to reflect the modifier. If there is a syntax + * error in the modifier then NULL is returned. * * Side effects: * None. @@ -656,16 +1144,16 @@ TkTextIndexCmp(index1Ptr, index2Ptr) */ static CONST char * -ForwBack(string, indexPtr) - CONST char *string; /* String to parse for additional info - * about modifier (count and units). - * Points to "+" or "-" that starts - * modifier. */ - TkTextIndex *indexPtr; /* Index to update as specified in string. */ +ForwBack( + TkText *textPtr, /* Information about text widget. */ + CONST char *string, /* String to parse for additional info about + * modifier (count and units). Points to "+" + * or "-" that starts modifier. */ + TkTextIndex *indexPtr) /* Index to update as specified in string. */ { register CONST char *p, *units; char *end; - int count, lineIndex; + int count, lineIndex, modifier; size_t length; /* @@ -686,53 +1174,193 @@ ForwBack(string, indexPtr) } /* - * Find the end of this modifier (next space or + or - character), - * then parse the unit specifier and update the position - * accordingly. + * Find the end of this modifier (next space or + or - character), then + * check if there is a textual 'display' or 'any' modifier. These + * modifiers can be their own word (in which case they can be abbreviated) + * or they can follow on to the actual unit in a single word (in which + * case no abbreviation is allowed). So, 'display lines', 'd lines', + * 'displaylin' are all ok, but 'dline' is not. */ - units = p; + units = p; while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { p++; } length = p - units; + if ((*units == 'd') && + (strncmp(units, "display", (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*units == 'a') && + (strncmp(units, "any", (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward to the + * actual units. + */ + + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + units = p; + while (*p!='\0' && !isspace(UCHAR(*p)) && *p!='+' && *p!='-') { + p++; + } + length = p - units; + } + + /* + * Finally parse the units. + */ + if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { + TkTextCountType type; + + if (modifier == TKINDEX_NONE) { + type = COUNT_INDICES; + } else if (modifier == TKINDEX_ANY) { + type = COUNT_CHARS; + } else { + type = COUNT_DISPLAY_CHARS; + } + if (*string == '+') { - TkTextIndexForwChars(indexPtr, count, indexPtr); + TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type); } else { - TkTextIndexBackChars(indexPtr, count, indexPtr); + TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type); } - } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); + } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) { + TkTextCountType type; + + if (modifier == TKINDEX_DISPLAY) { + type = COUNT_DISPLAY_INDICES; + } else { + type = COUNT_INDICES; + } + if (*string == '+') { - lineIndex += count; + TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type); } else { - lineIndex -= count; + TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type); + } + } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { + if (modifier == TKINDEX_DISPLAY) { + /* + * Find the appropriate pixel offset of the current position + * within its display line. This also has the side-effect of + * moving indexPtr, but that doesn't matter since we will do it + * again below. + * + * Then find the right display line, and finally calculated the + * index we want in that display line, based on the original pixel + * offset. + */ + + int xOffset, forward; + + if (TkTextIsElided(textPtr, indexPtr, NULL)) { + /* + * Go forward to the first non-elided index. + */ + + TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, + COUNT_DISPLAY_INDICES); + } /* - * The check below retains the character position, even - * if the line runs off the start of the file. Without - * it, the character position will get reset to 0 by - * TkTextMakeIndex. + * Unlike the Forw/BackChars code, the display line code is + * sensitive to whether we are genuinely going forwards or + * backwards. So, we need to determine that. This is important in + * the case where we have "+ -3 displaylines", for example. */ - if (lineIndex < 0) { - lineIndex = 0; + if ((count < 0) ^ (*string == '-')) { + forward = 0; + } else { + forward = 1; } - } - /* - * This doesn't work quite right if using a proportional font or - * UTF-8 characters with varying numbers of bytes. The cursor will - * bop around, keeping a constant number of bytes (not characters) - * from the left edge (but making sure not to split any UTF-8 - * characters), regardless of the x-position the index corresponds - * to. The proper way to do this is to get the x-position of the - * index and then pick the character at the same x-position in the - * new line. - */ - TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex, - indexPtr); + count = abs(count); + if (count == 0) { + return p; + } + + if (forward) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset); + while (count-- > 0) { + /* + * Go to the end of the line, then forward one char/byte + * to get to the beginning of the next line. + */ + + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } + } else { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset); + while (count-- > 0) { + /* + * Go to the beginning of the line, then backward one + * char/byte to get to the end of the previous line. + */ + + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } + + /* + * This call assumes indexPtr is the beginning of a display line + * and moves it to the 'xOffset' position of that line, which is + * just what we want. + */ + + TkTextIndexOfX(textPtr, xOffset, indexPtr); + } else { + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (*string == '+') { + lineIndex += count; + } else { + lineIndex -= count; + + /* + * The check below retains the character position, even if the + * line runs off the start of the file. Without it, the + * character position will get reset to 0 by TkTextMakeIndex. + */ + + if (lineIndex < 0) { + lineIndex = 0; + } + } + + /* + * This doesn't work quite right if using a proportional font or + * UTF-8 characters with varying numbers of bytes, or if there are + * embedded windows, images, etc. The cursor will bop around, + * keeping a constant number of bytes (not characters) from the + * left edge (but making sure not to split any UTF-8 characters), + * regardless of the x-position the index corresponds to. The + * proper way to do this is to get the x-position of the index and + * then pick the character at the same x-position in the new line. + */ + + TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex, + indexPtr->byteIndex, indexPtr); + } } else { return NULL; } @@ -744,13 +1372,16 @@ ForwBack(string, indexPtr) * * TkTextIndexForwBytes -- * - * Given an index for a text widget, this procedure creates a new - * index that points "count" bytes ahead of the source index. + * Given an index for a text widget, this function creates a new index + * that points "count" bytes ahead of the source index. * * Results: * *dstPtr is modified to refer to the character "count" bytes after - * srcPtr, or to the last character in the TkText if there aren't - * "count" bytes left. + * srcPtr, or to the last character in the TkText if there aren't "count" + * bytes left. + * + * In this latter case, the function returns '1' to indicate that not all + * of 'byteCount' could be used. * * Side effects: * None. @@ -758,20 +1389,21 @@ ForwBack(string, indexPtr) *--------------------------------------------------------------------------- */ -void -TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) - CONST TkTextIndex *srcPtr; /* Source index. */ - int byteCount; /* How many bytes forward to move. May be +int +TkTextIndexForwBytes( + CONST TkText *textPtr, + CONST TkTextIndex *srcPtr, /* Source index. */ + int byteCount, /* How many bytes forward to move. May be * negative. */ - TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextIndex *dstPtr) /* Destination index: gets modified. */ { TkTextLine *linePtr; TkTextSegment *segPtr; int lineLength; if (byteCount < 0) { - TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr); - return; + TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr); + return 0; } *dstPtr = *srcPtr; @@ -788,18 +1420,18 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) } /* - * If the new index is in the same line then we're done. - * Otherwise go on to the next line. + * If the new index is in the same line then we're done. Otherwise go + * on to the next line. */ if (dstPtr->byteIndex < lineLength) { - return; + return 0; } dstPtr->byteIndex -= lineLength; - linePtr = TkBTreeNextLine(dstPtr->linePtr); + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex = lineLength - 1; - return; + return 1; } dstPtr->linePtr = linePtr; } @@ -810,13 +1442,18 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) * * TkTextIndexForwChars -- * - * Given an index for a text widget, this procedure creates a new - * index that points "count" characters ahead of the source index. + * Given an index for a text widget, this function creates a new index + * that points "count" items of type given by "type" ahead of the source + * index. "count" can be zero, which is useful in the case where one + * wishes to move forward by display (non-elided) chars or indices or one + * wishes to move forward by chars, skipping any intervening indices. In + * this case dstPtr will point to the first acceptable index which is + * encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters - * after srcPtr, or to the last character in the TkText if there - * aren't "count" characters left in the file. + * *dstPtr is modified to refer to the character "count" items after + * srcPtr, or to the last character in the TkText if there aren't + * sufficient items left in the widget. * * Side effects: * None. @@ -825,31 +1462,52 @@ TkTextIndexForwBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexForwChars(srcPtr, charCount, dstPtr) - CONST TkTextIndex *srcPtr; /* Source index. */ - int charCount; /* How many characters forward to move. - * May be negative. */ - TkTextIndex *dstPtr; /* Destination index: gets modified. */ +TkTextIndexForwChars( + CONST TkText *textPtr, /* Overall information about text widget. */ + CONST TkTextIndex *srcPtr, /* Source index. */ + int charCount, /* How many characters forward to move. May + * be negative. */ + TkTextIndex *dstPtr, /* Destination index: gets modified. */ + TkTextCountType type) /* The type of item to count */ { TkTextLine *linePtr; TkTextSegment *segPtr; + TkTextElideInfo *infoPtr = NULL; int byteOffset; char *start, *end, *p; Tcl_UniChar ch; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); if (charCount < 0) { - TkTextIndexBackChars(srcPtr, -charCount, dstPtr); + TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type); return; } + if (checkElided) { + infoPtr = (TkTextElideInfo *) + ckalloc((unsigned) sizeof(TkTextElideInfo)); + elide = TkTextIsElided(textPtr, srcPtr, infoPtr); + } *dstPtr = *srcPtr; /* - * Find seg that contains src byteIndex. - * Move forward specified number of chars. + * Find seg that contains src byteIndex. Move forward specified number of + * chars. */ - segPtr = TkTextIndexToSeg(dstPtr, &byteOffset); + if (checkElided) { + /* + * In this case we have already calculated the information we need, so + * no need to use TkTextIndexToSeg() + */ + + segPtr = infoPtr->segPtr; + byteOffset = dstPtr->byteIndex - infoPtr->segOffset; + } else { + segPtr = TkTextIndexToSeg(dstPtr, &byteOffset); + } + while (1) { /* * Go through each segment in line looking for specified character @@ -857,76 +1515,341 @@ TkTextIndexForwChars(srcPtr, charCount, dstPtr) */ for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars + byteOffset; - end = segPtr->body.chars + segPtr->size; - for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { - if (charCount == 0) { - dstPtr->byteIndex += (p - start); - return; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current segment + * isn't visible, then we simply continue the loop. + */ + + if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType))) { + TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; + + /* + * The elide state only changes if this tag is either the + * current highest priority tag (and is therefore being + * toggled off), or it's a new tag with higher priority. + */ + + if (tagPtr->elideString != NULL) { + infoPtr->tagCnts[tagPtr->priority]++; + if (infoPtr->tagCnts[tagPtr->priority] & 1) { + infoPtr->tagPtrs[tagPtr->priority] = tagPtr; + } + + if (tagPtr->priority >= infoPtr->elidePriority) { + if (segPtr->typePtr == &tkTextToggleOffType) { + /* + * If it is being toggled off, and it has an elide + * string, it must actually be the current highest + * priority tag, so this check is redundant: + */ + + if (tagPtr->priority != infoPtr->elidePriority) { + Tcl_Panic("Bad tag priority being toggled off"); + } + + /* + * Find previous elide tag, if any (if not then + * elide will be zero, of course). + */ + + elide = 0; + while (--infoPtr->elidePriority > 0) { + if (infoPtr->tagCnts[infoPtr->elidePriority] + & 1) { + elide = infoPtr->tagPtrs + [infoPtr->elidePriority]->elide; + break; + } + } + } else { + elide = tagPtr->elide; + infoPtr->elidePriority = tagPtr->priority; + } } - charCount--; } - } else { - if (charCount < segPtr->size - byteOffset) { - dstPtr->byteIndex += charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars + byteOffset; + end = segPtr->body.chars + segPtr->size; + for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { + if (charCount == 0) { + dstPtr->byteIndex += (p - start); + goto forwardCharDone; + } + charCount--; + } + } else if (type & COUNT_INDICES) { + if (charCount < segPtr->size - byteOffset) { + dstPtr->byteIndex += charCount; + goto forwardCharDone; + } + charCount -= segPtr->size - byteOffset; } - charCount -= segPtr->size - byteOffset; } + dstPtr->byteIndex += segPtr->size - byteOffset; byteOffset = 0; } /* - * Go to the next line. If we are at the end of the text item, - * back up one byte (for the terminal '\n' character) and return - * that index. + * Go to the next line. If we are at the end of the text item, back up + * one byte (for the terminal '\n' character) and return that index. */ - - linePtr = TkBTreeNextLine(dstPtr->linePtr); + + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); if (linePtr == NULL) { dstPtr->byteIndex -= sizeof(char); - return; + goto forwardCharDone; } dstPtr->linePtr = linePtr; dstPtr->byteIndex = 0; segPtr = dstPtr->linePtr->segPtr; } + + forwardCharDone: + if (infoPtr != NULL) { + TkTextFreeElideInfo(infoPtr); + ckfree((char *) infoPtr); + } } /* *--------------------------------------------------------------------------- * + * TkTextIndexCount -- + * + * Given an ordered pair of indices in a text widget, this function + * counts how many characters (not bytes) are between the two indices. + * + * It is illegal to call this function with unordered indices. + * + * Note that 'textPtr' is only used if we need to check for elided + * attributes, i.e. if type is COUNT_DISPLAY_INDICES or + * COUNT_DISPLAY_CHARS. + * + * Results: + * The number of characters in the given range, which meet the + * appropriate 'type' attributes. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextIndexCount( + CONST TkText *textPtr, /* Overall information about text widget. */ + CONST TkTextIndex *indexPtr1, + /* Index describing location of character from + * which to count. */ + CONST TkTextIndex *indexPtr2, + /* Index describing location of last character + * at which to stop the count. */ + TkTextCountType type) /* The kind of indices to count. */ +{ + TkTextLine *linePtr1; + TkTextSegment *segPtr, *seg2Ptr = NULL; + TkTextElideInfo *infoPtr = NULL; + int byteOffset, maxBytes, count = 0, elide = 0; + int checkElided = (type & COUNT_DISPLAY); + + /* + * Find seg that contains src index, and remember how many bytes not to + * count in the given segment. + */ + + segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset); + linePtr1 = indexPtr1->linePtr; + + seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes); + + if (checkElided) { + infoPtr = (TkTextElideInfo *) + ckalloc((unsigned) sizeof(TkTextElideInfo)); + elide = TkTextIsElided(textPtr, indexPtr1, infoPtr); + } + + while (1) { + /* + * Go through each segment in line adding up the number of characters. + */ + + for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current segment + * isn't visible, then we simply continue the loop. + */ + + if (checkElided) { + if ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType)) { + TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; + + /* + * The elide state only changes if this tag is either the + * current highest priority tag (and is therefore being + * toggled off), or it's a new tag with higher priority. + */ + + if (tagPtr->elideString != NULL) { + infoPtr->tagCnts[tagPtr->priority]++; + if (infoPtr->tagCnts[tagPtr->priority] & 1) { + infoPtr->tagPtrs[tagPtr->priority] = tagPtr; + } + if (tagPtr->priority >= infoPtr->elidePriority) { + if (segPtr->typePtr == &tkTextToggleOffType) { + /* + * If it is being toggled off, and it has an + * elide string, it must actually be the + * current highest priority tag, so this check + * is redundant: + */ + + if (tagPtr->priority!=infoPtr->elidePriority) { + Tcl_Panic("Bad tag priority being toggled off"); + } + + /* + * Find previous elide tag, if any (if not + * then elide will be zero, of course). + */ + + elide = 0; + while (--infoPtr->elidePriority > 0) { + if (infoPtr->tagCnts[ + infoPtr->elidePriority] & 1) { + elide = infoPtr->tagPtrs[ + infoPtr->elidePriority]->elide; + break; + } + } + } else { + elide = tagPtr->elide; + infoPtr->elidePriority = tagPtr->priority; + } + } + } + } + if (elide) { + if (segPtr == seg2Ptr) { + goto countDone; + } + byteOffset = 0; + continue; + } + } + + if (segPtr->typePtr == &tkTextCharType) { + int byteLen = segPtr->size - byteOffset; + register unsigned char *str = (unsigned char *) + segPtr->body.chars + byteOffset; + register int i; + + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + i = byteLen; + + /* + * This is a speed sensitive function, so run specially over + * the string to count continuous ascii characters before + * resorting to the Tcl_NumUtfChars call. This is a long form + * of: + * + * stringPtr->numChars = + * Tcl_NumUtfChars(objPtr->bytes, objPtr->length); + */ + + while (i && (*str < 0xC0)) { + i--; + str++; + } + count += byteLen - i; + if (i) { + count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset + + (byteLen - i), i); + } + } else { + if (type & COUNT_INDICES) { + int byteLen = segPtr->size - byteOffset; + + if (segPtr == seg2Ptr) { + if (byteLen > (maxBytes - byteOffset)) { + byteLen = maxBytes - byteOffset; + } + } + count += byteLen; + } + } + if (segPtr == seg2Ptr) { + goto countDone; + } + byteOffset = 0; + } + + /* + * Go to the next line. If we are at the end of the text item, back up + * one byte (for the terminal '\n' character) and return that index. + */ + + linePtr1 = TkBTreeNextLine(textPtr, linePtr1); + if (linePtr1 == NULL) { + Tcl_Panic("Reached end of text widget when counting characters"); + } + segPtr = linePtr1->segPtr; + } + + countDone: + if (infoPtr != NULL) { + TkTextFreeElideInfo(infoPtr); + ckfree((char *) infoPtr); + } + return count; +} + +/* + *--------------------------------------------------------------------------- + * * TkTextIndexBackBytes -- * - * Given an index for a text widget, this procedure creates a new - * index that points "count" bytes earlier than the source index. + * Given an index for a text widget, this function creates a new index + * that points "count" bytes earlier than the source index. * * Results: * *dstPtr is modified to refer to the character "count" bytes before * srcPtr, or to the first character in the TkText if there aren't * "count" bytes earlier than srcPtr. * + * Returns 1 if we couldn't use all of 'byteCount' because we have run + * into the beginning or end of the text, and zero otherwise. + * * Side effects: * None. * *--------------------------------------------------------------------------- */ -void -TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) - CONST TkTextIndex *srcPtr; /* Source index. */ - int byteCount; /* How many bytes backward to move. May be +int +TkTextIndexBackBytes( + CONST TkText *textPtr, + CONST TkTextIndex *srcPtr, /* Source index. */ + int byteCount, /* How many bytes backward to move. May be * negative. */ - TkTextIndex *dstPtr; /* Destination index: gets modified. */ + TkTextIndex *dstPtr) /* Destination index: gets modified. */ { TkTextSegment *segPtr; int lineIndex; if (byteCount < 0) { - TkTextIndexForwBytes(srcPtr, -byteCount, dstPtr); - return; + return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); } *dstPtr = *srcPtr; @@ -934,19 +1857,19 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) lineIndex = -1; while (dstPtr->byteIndex < 0) { /* - * Move back one line in the text. If we run off the beginning - * of the file then just return the first character in the text. + * Move back one line in the text. If we run off the beginning of the + * file then just return the first character in the text. */ if (lineIndex < 0) { - lineIndex = TkBTreeLineIndex(dstPtr->linePtr); + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); } if (lineIndex == 0) { dstPtr->byteIndex = 0; - return; + return 1; } lineIndex--; - dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex); + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); /* * Compute the length of the line and add that to dstPtr->charIndex. @@ -957,6 +1880,7 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) dstPtr->byteIndex += segPtr->size; } } + return 0; } /* @@ -964,13 +1888,18 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) * * TkTextIndexBackChars -- * - * Given an index for a text widget, this procedure creates a new - * index that points "count" characters earlier than the source index. + * Given an index for a text widget, this function creates a new index + * that points "count" items of type given by "type" earlier than the + * source index. "count" can be zero, which is useful in the case where + * one wishes to move backward by display (non-elided) chars or indices + * or one wishes to move backward by chars, skipping any intervening + * indices. In this case the returned index *dstPtr will point just + * _after_ the first acceptable index which is encountered. * * Results: - * *dstPtr is modified to refer to the character "count" characters - * before srcPtr, or to the first character in the file if there - * aren't "count" characters earlier than srcPtr. + * *dstPtr is modified to refer to the character "count" items before + * srcPtr, or to the first index in the window if there aren't sufficient + * items earlier than srcPtr. * * Side effects: * None. @@ -979,57 +1908,145 @@ TkTextIndexBackBytes(srcPtr, byteCount, dstPtr) */ void -TkTextIndexBackChars(srcPtr, charCount, dstPtr) - CONST TkTextIndex *srcPtr; /* Source index. */ - int charCount; /* How many characters backward to move. - * May be negative. */ - TkTextIndex *dstPtr; /* Destination index: gets modified. */ +TkTextIndexBackChars( + CONST TkText *textPtr, /* Overall information about text widget. */ + CONST TkTextIndex *srcPtr, /* Source index. */ + int charCount, /* How many characters backward to move. May + * be negative. */ + TkTextIndex *dstPtr, /* Destination index: gets modified. */ + TkTextCountType type) /* The type of item to count */ { TkTextSegment *segPtr, *oldPtr; + TkTextElideInfo *infoPtr = NULL; int lineIndex, segSize; CONST char *p, *start, *end; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); - if (charCount <= 0) { - TkTextIndexForwChars(srcPtr, -charCount, dstPtr); + if (charCount < 0) { + TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type); return; } + if (checkElided) { + infoPtr = (TkTextElideInfo *) ckalloc(sizeof(TkTextElideInfo)); + elide = TkTextIsElided(textPtr, srcPtr, infoPtr); + } *dstPtr = *srcPtr; /* - * Find offset within seg that contains byteIndex. - * Move backward specified number of chars. + * Find offset within seg that contains byteIndex. Move backward specified + * number of chars. */ lineIndex = -1; - + segSize = dstPtr->byteIndex; - for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { - if (segSize <= segPtr->size) { - break; + + if (checkElided) { + segPtr = infoPtr->segPtr; + segSize -= infoPtr->segOffset; + } else { + TkTextLine *linePtr = dstPtr->linePtr; + for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { + if (segPtr == NULL) { + /* + * Two logical lines merged into one display line through + * eliding of a newline. + */ + + linePtr = TkBTreeNextLine(NULL, linePtr); + segPtr = linePtr->segPtr; + } + if (segSize <= segPtr->size) { + break; + } + segSize -= segPtr->size; } - segSize -= segPtr->size; } + + /* + * Now segPtr points to the segment containing the starting index. + */ + while (1) { - if (segPtr->typePtr == &tkTextCharType) { - start = segPtr->body.chars; - end = segPtr->body.chars + segSize; - for (p = end; ; p = Tcl_UtfPrev(p, start)) { - if (charCount == 0) { - dstPtr->byteIndex -= (end - p); - return; + /* + * If we do need to pay attention to the visibility of + * characters/indices, check that first. If the current segment isn't + * visible, then we simply continue the loop. + */ + + if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType) + || (segPtr->typePtr == &tkTextToggleOnType))) { + TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; + + /* + * The elide state only changes if this tag is either the current + * highest priority tag (and is therefore being toggled off), or + * it's a new tag with higher priority. + */ + + if (tagPtr->elideString != NULL) { + infoPtr->tagCnts[tagPtr->priority]++; + if (infoPtr->tagCnts[tagPtr->priority] & 1) { + infoPtr->tagPtrs[tagPtr->priority] = tagPtr; } - if (p == start) { - break; + if (tagPtr->priority >= infoPtr->elidePriority) { + if (segPtr->typePtr == &tkTextToggleOnType) { + /* + * If it is being toggled on, and it has an elide + * string, it must actually be the current highest + * priority tag, so this check is redundant: + */ + + if (tagPtr->priority != infoPtr->elidePriority) { + Tcl_Panic("Bad tag priority being toggled on"); + } + + /* + * Find previous elide tag, if any (if not then elide + * will be zero, of course). + */ + + elide = 0; + while (--infoPtr->elidePriority > 0) { + if (infoPtr->tagCnts[infoPtr->elidePriority] & 1) { + elide = infoPtr->tagPtrs[ + infoPtr->elidePriority]->elide; + break; + } + } + } else { + elide = tagPtr->elide; + infoPtr->elidePriority = tagPtr->priority; + } } - charCount--; } - } else { - if (charCount <= segSize) { - dstPtr->byteIndex -= charCount; - return; + } + + if (!elide) { + if (segPtr->typePtr == &tkTextCharType) { + start = segPtr->body.chars; + end = segPtr->body.chars + segSize; + for (p = end; ; p = Tcl_UtfPrev(p, start)) { + if (charCount == 0) { + dstPtr->byteIndex -= (end - p); + goto backwardCharDone; + } + if (p == start) { + break; + } + charCount--; + } + } else { + if (type & COUNT_INDICES) { + if (charCount <= segSize) { + dstPtr->byteIndex -= charCount; + goto backwardCharDone; + } + charCount -= segSize; + } } - charCount -= segSize; } dstPtr->byteIndex -= segSize; @@ -1052,14 +2069,14 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) */ if (lineIndex < 0) { - lineIndex = TkBTreeLineIndex(dstPtr->linePtr); + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); } if (lineIndex == 0) { dstPtr->byteIndex = 0; - return; + goto backwardCharDone; } lineIndex--; - dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex); + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); /* * Compute the length of the line and add that to dstPtr->byteIndex. @@ -1073,6 +2090,12 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) segPtr = oldPtr; segSize = segPtr->size; } + + backwardCharDone: + if (infoPtr != NULL) { + TkTextFreeElideInfo(infoPtr); + ckfree((char *) infoPtr); + } } /* @@ -1080,14 +2103,14 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) * * StartEnd -- * - * This procedure handles modifiers like "wordstart" and "lineend" - * to adjust indices forwards or backwards. + * This function handles modifiers like "wordstart" and "lineend" to + * adjust indices forwards or backwards. * * Results: - * If the modifier is successfully parsed then the return value - * is the address of the first character after the modifier, and - * *indexPtr is updated to reflect the modifier. If there is a - * syntax error in the modifier then NULL is returned. + * If the modifier is successfully parsed then the return value is the + * address of the first character after the modifier, and *indexPtr is + * updated to reflect the modifier. If there is a syntax error in the + * modifier then NULL is returned. * * Side effects: * None. @@ -1096,17 +2119,17 @@ TkTextIndexBackChars(srcPtr, charCount, dstPtr) */ static CONST char * -StartEnd(string, indexPtr) - CONST char *string; /* String to parse for additional info - * about modifier (count and units). - * Points to first character of modifer - * word. */ - TkTextIndex *indexPtr; /* Index to mdoify based on string. */ +StartEnd( + TkText *textPtr, /* Information about text widget. */ + CONST char *string, /* String to parse for additional info about + * modifier (count and units). Points to first + * character of modifer word. */ + TkTextIndex *indexPtr) /* Index to modify based on string. */ { CONST char *p; - int c, offset; size_t length; register TkTextSegment *segPtr; + int modifier; /* * Find the end of the modifier word. @@ -1115,68 +2138,144 @@ StartEnd(string, indexPtr) for (p = string; isalnum(UCHAR(*p)); p++) { /* Empty loop body. */ } + length = p-string; + if ((*string == 'd') && + (strncmp(string, "display", (length > 7 ? 7 : length)) == 0)) { + modifier = TKINDEX_DISPLAY; + if (length > 7) { + p -= (length - 7); + } + } else if ((*string == 'a') && + (strncmp(string, "any", (length > 3 ? 3 : length)) == 0)) { + modifier = TKINDEX_ANY; + if (length > 3) { + p -= (length - 3); + } + } else { + modifier = TKINDEX_NONE; + } + + /* + * If we had a modifier, which we interpreted ok, so now forward to the + * actual units. + */ + + if (modifier != TKINDEX_NONE) { + while (isspace(UCHAR(*p))) { + p++; + } + string = p; + while ((*p!='\0') && !isspace(UCHAR(*p)) && (*p!='+') && (*p!='-')) { + p++; + } + length = p - string; + } + if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; - segPtr = segPtr->nextPtr) { - indexPtr->byteIndex += segPtr->size; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); + } else { + indexPtr->byteIndex = 0; + for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + indexPtr->byteIndex += segPtr->size; + } + + /* + * We know '\n' is encoded with a single byte index. + */ + + indexPtr->byteIndex -= sizeof(char); } - indexPtr->byteIndex -= sizeof(char); } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) && (length >= 5)) { - indexPtr->byteIndex = 0; + if (modifier == TKINDEX_DISPLAY) { + TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); + } else { + indexPtr->byteIndex = 0; + } } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) && (length >= 5)) { int firstChar = 1; + int offset; /* * If the current character isn't part of a word then just move - * forward one character. Otherwise move forward until finding - * a character that isn't part of a word and stop there. + * forward one character. Otherwise move forward until finding a + * character that isn't part of a word and stop there. */ + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, + COUNT_DISPLAY_INDICES); + } segPtr = TkTextIndexToSeg(indexPtr, &offset); while (1) { + int chSize = 1; + if (segPtr->typePtr == &tkTextCharType) { - c = segPtr->body.chars[offset]; - if (!isalnum(UCHAR(c)) && (c != '_')) { + Tcl_UniChar ch; + + chSize = Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch); + if (!Tcl_UniCharIsWordChar(ch)) { break; } firstChar = 0; } - offset += 1; - indexPtr->byteIndex += sizeof(char); + offset += chSize; + indexPtr->byteIndex += chSize; if (offset >= segPtr->size) { segPtr = TkTextIndexToSeg(indexPtr, &offset); } } if (firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } else { + TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr, + COUNT_INDICES); + } } } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) && (length >= 5)) { int firstChar = 1; + int offset; + + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(NULL, indexPtr, 0, indexPtr, + COUNT_DISPLAY_INDICES); + } /* - * Starting with the current character, look for one that's not - * part of a word and keep moving backward until you find one. - * Then if the character found wasn't the first one, move forward - * again one position. + * Starting with the current character, look for one that's not part + * of a word and keep moving backward until you find one. Then if the + * character found wasn't the first one, move forward again one + * position. */ segPtr = TkTextIndexToSeg(indexPtr, &offset); while (1) { + int chSize = 1; + if (segPtr->typePtr == &tkTextCharType) { - c = segPtr->body.chars[offset]; - if (!isalnum(UCHAR(c)) && (c != '_')) { + Tcl_UniChar ch; + + Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch); + if (!Tcl_UniCharIsWordChar(ch)) { break; } + if (offset > 0) { + chSize = (segPtr->body.chars + offset + - Tcl_UtfPrev(segPtr->body.chars + offset, + segPtr->body.chars)); + } firstChar = 0; } - offset -= 1; - indexPtr->byteIndex -= sizeof(char); + offset -= chSize; + indexPtr->byteIndex -= chSize; if (offset < 0) { if (indexPtr->byteIndex < 0) { indexPtr->byteIndex = 0; @@ -1185,12 +2284,28 @@ StartEnd(string, indexPtr) segPtr = TkTextIndexToSeg(indexPtr, &offset); } } + if (!firstChar) { - TkTextIndexForwChars(indexPtr, 1, indexPtr); + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } else { + TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr, + COUNT_INDICES); + } } } else { return NULL; } - done: + + done: return p; } + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |