diff options
Diffstat (limited to 'tk8.6/generic/tkTextIndex.c')
-rw-r--r-- | tk8.6/generic/tkTextIndex.c | 2402 |
1 files changed, 2402 insertions, 0 deletions
diff --git a/tk8.6/generic/tkTextIndex.c b/tk8.6/generic/tkTextIndex.c new file mode 100644 index 0000000..7430197 --- /dev/null +++ b/tk8.6/generic/tkTextIndex.c @@ -0,0 +1,2402 @@ +/* + * tkTextIndex.c -- + * + * 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. + */ + +#include "default.h" +#include "tkInt.h" +#include "tkText.h" + +/* + * Index to use to select last character in line (very large integer): + */ + +#define LAST_CHAR 1000000 + +/* + * 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); +static int IndexCountBytesOrdered(const TkText *textPtr, + const TkTextIndex *indexPtr1, + const TkTextIndex *indexPtr2); + +/* + * The "textindex" Tcl_Obj definition: + */ + +static void DupTextIndexInternalRep(Tcl_Obj *srcPtr, + Tcl_Obj *copyPtr); +static void FreeTextIndexInternalRep(Tcl_Obj *listPtr); +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. + */ + +const Tcl_ObjType tkTextIndexType = { + "textindex", /* name */ + FreeTextIndexInternalRep, /* freeIntRepProc */ + DupTextIndexInternalRep, /* dupIntRepProc */ + NULL, /* updateStringProc */ + NULL /* 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-- <= 1) { + /* + * The text widget has been deleted and we need to free it now. + */ + + ckfree(indexPtr->textPtr); + } + } + ckfree(indexPtr); + indexObjPtr->typePtr = NULL; +} + +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 = 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 void +UpdateStringOfTextIndex( + Tcl_Obj *objPtr) +{ + char buffer[TK_POS_CHARS]; + size_t len; + const TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr); + + len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer); + + objPtr->bytes = ckalloc(len + 1); + strcpy(objPtr->bytes, buffer); + objPtr->length = len; +} + +/* + *--------------------------------------------------------------------------- + * + * 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 = 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. + * + * Results: + * The structure at *indexPtr is filled in with information about the + * character at lineIndex and byteIndex (or the closest existing + * character, if the specified one doesn't exist), and indexPtr is + * returned as result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +TkTextIndex * +TkTextMakeByteIndex( + TkTextBTree tree, /* Tree that lineIndex and byteIndex refer + * to. */ + 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; + const char *p, *start; + int ch; + + indexPtr->tree = tree; + if (lineIndex < 0) { + lineIndex = 0; + byteIndex = 0; + } + if (byteIndex < 0) { + byteIndex = 0; + } + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); + if (indexPtr->linePtr == NULL) { + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, + TkBTreeNumLines(tree, textPtr)); + byteIndex = 0; + } + if (byteIndex == 0) { + indexPtr->byteIndex = byteIndex; + return indexPtr; + } + + /* + * 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. + */ + + indexPtr->byteIndex = index - sizeof(char); + break; + } + if (index + segPtr->size > byteIndex) { + indexPtr->byteIndex = byteIndex; + if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) { + /* + * Prevent UTF-8 character from being split up by ensuring + * that byteIndex falls on a character boundary. If the index + * falls in the middle of a UTF-8 character, it will be + * adjusted to the end of that UTF-8 character. + */ + + start = segPtr->body.chars + (byteIndex - index); + p = Tcl_UtfPrev(start, segPtr->body.chars); + p += TkUtfToUniChar(p, &ch); + indexPtr->byteIndex += p - start; + } + break; + } + index += segPtr->size; + } + return indexPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextMakeCharIndex -- + * + * 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 + * character at lineIndex and charIndex (or the closest existing + * character, if the specified one doesn't exist), and indexPtr is + * returned as result. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +TkTextIndex * +TkTextMakeCharIndex( + TkTextBTree tree, /* Tree that lineIndex and charIndex refer + * to. */ + 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; + int index, offset; + int ch; + + indexPtr->tree = tree; + if (lineIndex < 0) { + lineIndex = 0; + charIndex = 0; + } + if (charIndex < 0) { + charIndex = 0; + } + indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); + if (indexPtr->linePtr == NULL) { + 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. + */ + + 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. + */ + + indexPtr->byteIndex = index - sizeof(char); + break; + } + if (segPtr->typePtr == &tkTextCharType) { + /* + * Turn character offset into a byte offset. + */ + + start = segPtr->body.chars; + end = start + segPtr->size; + for (p = start; p < end; p += offset) { + if (charIndex == 0) { + indexPtr->byteIndex = index; + return indexPtr; + } + charIndex--; + offset = TkUtfToUniChar(p, &ch); + index += offset; + } + } else { + if (charIndex < segPtr->size) { + indexPtr->byteIndex = index; + break; + } + charIndex -= segPtr->size; + index += segPtr->size; + } + } + return indexPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexToSeg -- + * + * 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. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +TkTextSegment * +TkTextIndexToSeg( + const TkTextIndex *indexPtr,/* Text index. */ + int *offsetPtr) /* Where to store offset within segment, or + * NULL if offset isn't wanted. */ +{ + TkTextSegment *segPtr; + int offset; + + for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr; + offset >= segPtr->size; + offset -= segPtr->size, segPtr = segPtr->nextPtr) { + /* Empty loop body. */ + } + if (offsetPtr != NULL) { + *offsetPtr = offset; + } + return segPtr; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextSegToOffset -- + * + * Given a segment pointer and the line containing it, this function + * returns the offset of the segment within its line. + * + * Results: + * The return value is the offset (within its line) of the first + * character in segPtr. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextSegToOffset( + const TkTextSegment *segPtr,/* Segment whose offset is desired. */ + const TkTextLine *linePtr) /* Line containing segPtr. */ +{ + const TkTextSegment *segPtr2; + int offset = 0; + + for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr; + segPtr2 = segPtr2->nextPtr) { + offset += segPtr2->size; + } + return offset; +} + +/* + *--------------------------------------------------------------------------- + * + * 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 + * 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. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +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; + 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, an embedded window or an embedded image. We do this check + * now even though it's also done later, in order to allow mark names, + * embedded window names or image names that include funny characters + * such as spaces or "+1c". + *--------------------------------------------------------------------- + */ + + if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) { + goto done; + } + + if (TkTextWindowIndex(textPtr, string, indexPtr) != 0) { + goto done; + } + + if (TkTextImageIndex(textPtr, string, indexPtr) != 0) { + goto done; + } + + /* + *------------------------------------------------ + * Stage 2: start again by parsing the base index. + *------------------------------------------------ + */ + + 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". + */ + + 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; + } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) { + wantLast = 1; + endOfBase = p+5; + } else { + goto tryxy; + } + + 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 = Tcl_GetHashValue(hPtr); + } + } + + if (tagPtr == NULL) { + goto tryxy; + } + + 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 if (hPtr != NULL) { + tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr); + } + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "text doesn't contain any characters tagged with \"%s\"", + tagName)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_INDEX", tagName, + NULL); + Tcl_DStringFree(©); + return TCL_ERROR; + } + *indexPtr = search.curIndex; + if (wantLast) { + while (TkBTreeNextTag(&search)) { + *indexPtr = search.curIndex; + } + } + goto gotBase; + } + + tryxy: + if (string[0] == '@') { + /* + * Find character at a given x,y location in the window. + */ + + int x, y; + + cp = string+1; + x = strtol(cp, &end, 0); + if ((end == cp) || (*end != ',')) { + goto error; + } + cp = end+1; + y = strtol(cp, &end, 0); + if (end == cp) { + goto error; + } + TkTextPixelIndex(textPtr, x, y, indexPtr, NULL); + endOfBase = end; + goto gotBase; + } + + if (isdigit(UCHAR(string[0])) || (string[0] == '-')) { + int lineIndex, charIndex; + + /* + * Base is identified with line and character indices. + */ + + lineIndex = strtol(string, &end, 0) - 1; + if ((end == string) || (*end != '.')) { + goto error; + } + p = end+1; + if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) { + charIndex = LAST_CHAR; + endOfBase = p+3; + } else { + charIndex = strtol(p, &end, 0); + if (end == p) { + goto error; + } + endOfBase = end; + } + TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, charIndex, + indexPtr); + canCache = 1; + goto gotBase; + } + + for (p = Tcl_DStringValue(©); *p != 0; p++) { + if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) { + break; + } + } + endOfBase = p; + if (string[0] == '.') { + /* + * See if the base position is the name of an embedded window. + */ + + c = *endOfBase; + *endOfBase = 0; + result = TkTextWindowIndex(textPtr, Tcl_DStringValue(©), indexPtr); + *endOfBase = c; + if (result != 0) { + goto gotBase; + } + } + if ((string[0] == 'e') + && (strncmp(string, "end", + (size_t) (endOfBase-Tcl_DStringValue(©))) == 0)) { + /* + * Base position is end of text. + */ + + TkTextMakeByteIndex(sharedPtr->tree, textPtr, + TkBTreeNumLines(sharedPtr->tree, textPtr), 0, indexPtr); + canCache = 1; + goto gotBase; + } else { + /* + * See if the base position is the name of a mark. + */ + + c = *endOfBase; + *endOfBase = 0; + result = TkTextMarkNameToIndex(textPtr, Tcl_DStringValue(©), + indexPtr); + *endOfBase = c; + if (result == TCL_OK) { + goto gotBase; + } + + /* + * See if the base position is the name of an embedded image. + */ + + c = *endOfBase; + *endOfBase = 0; + result = TkTextImageIndex(textPtr, Tcl_DStringValue(©), indexPtr); + *endOfBase = c; + if (result != 0) { + goto gotBase; + } + } + goto error; + + /* + *------------------------------------------------------------------- + * 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: + cp = endOfBase; + while (1) { + while (isspace(UCHAR(*cp))) { + cp++; + } + if (*cp == 0) { + break; + } + + if ((*cp == '+') || (*cp == '-')) { + cp = ForwBack(textPtr, cp, indexPtr); + } else { + 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: + Tcl_DStringFree(©); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad text index \"%s\"", string)); + Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_INDEX", NULL); + return TCL_ERROR; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextPrintIndex -- + * + * This function generates a string description of an index, suitable for + * reading in again later. + * + * Results: + * The characters pointed to by string are modified. Returns the number + * of characters in the string. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +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; + 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; + } + if (segPtr->typePtr == &tkTextCharType) { + charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size); + } else { + charIndex += segPtr->size; + } + numBytes -= segPtr->size; + } + + if (segPtr->typePtr == &tkTextCharType) { + charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes); + } else { + charIndex += numBytes; + } + + return sprintf(string, "%d.%d", + TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex); +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexCmp -- + * + * Compare two indices to see which one is earlier in the text. + * + * Results: + * The return value is 0 if index1Ptr and index2Ptr refer to the same + * position in the file, -1 if index1Ptr refers to an earlier position + * than index2Ptr, and 1 otherwise. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextIndexCmp( + const TkTextIndex*index1Ptr,/* First index. */ + const TkTextIndex*index2Ptr)/* Second index. */ +{ + int line1, line2; + + if (index1Ptr->linePtr == index2Ptr->linePtr) { + if (index1Ptr->byteIndex < index2Ptr->byteIndex) { + return -1; + } else if (index1Ptr->byteIndex > index2Ptr->byteIndex) { + return 1; + } else { + return 0; + } + } + + /* + * 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; + } + if (line1 > line2) { + return 1; + } + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * ForwBack -- + * + * 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. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +static const char * +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, modifier; + size_t length; + + /* + * Get the count (how many units forward or backward). + */ + + p = string+1; + while (isspace(UCHAR(*p))) { + p++; + } + count = strtol(p, &end, 0); + if (end == p) { + return NULL; + } + p = end; + while (isspace(UCHAR(*p))) { + p++; + } + + /* + * 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; + 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(textPtr, indexPtr, count, indexPtr, type); + } else { + TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type); + } + } 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 == '+') { + TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type); + } else { + 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); + } + + /* + * 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 ((count < 0) ^ (*string == '-')) { + forward = 0; + } else { + forward = 1; + } + + 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; + } + return p; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexForwBytes -- + * + * 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. + * + * In this latter case, the function returns '1' to indicate that not all + * of 'byteCount' could be used. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +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. */ +{ + TkTextLine *linePtr; + TkTextSegment *segPtr; + int lineLength; + + if (byteCount < 0) { + TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr); + return 0; + } + + *dstPtr = *srcPtr; + dstPtr->byteIndex += byteCount; + while (1) { + /* + * Compute the length of the current line. + */ + + lineLength = 0; + for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + lineLength += segPtr->size; + } + + /* + * 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 0; + } + dstPtr->byteIndex -= lineLength; + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); + if (linePtr == NULL) { + dstPtr->byteIndex = lineLength - 1; + return 1; + } + dstPtr->linePtr = linePtr; + } +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexForwChars -- + * + * 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" items after + * srcPtr, or to the last character in the TkText if there aren't + * sufficient items left in the widget. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +void +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; + int ch; + int elide = 0; + int checkElided = (type & COUNT_DISPLAY); + + if (charCount < 0) { + TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type); + return; + } + if (checkElided) { + infoPtr = ckalloc(sizeof(TkTextElideInfo)); + elide = TkTextIsElided(textPtr, srcPtr, infoPtr); + } + + *dstPtr = *srcPtr; + + /* + * Find seg that contains src byteIndex. Move forward specified number of + * chars. + */ + + 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 + * index. + */ + + 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 && ((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->typePtr == &tkTextCharType) { + start = segPtr->body.chars + byteOffset; + end = segPtr->body.chars + segPtr->size; + for (p = start; p < end; p += TkUtfToUniChar(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; + } + } + + 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. + */ + + linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); + if (linePtr == NULL) { + dstPtr->byteIndex -= sizeof(char); + goto forwardCharDone; + } + dstPtr->linePtr = linePtr; + dstPtr->byteIndex = 0; + segPtr = dstPtr->linePtr->segPtr; + } + + forwardCharDone: + if (infoPtr != NULL) { + TkTextFreeElideInfo(infoPtr); + ckfree(infoPtr); + } +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexCountBytes -- + * + * Given a pair of indices in a text widget, this function counts how + * many bytes are between the two indices. The two indices do not need + * to be ordered. + * + * Results: + * The number of bytes in the given range. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +int +TkTextIndexCountBytes( + const TkText *textPtr, + const TkTextIndex *indexPtr1, /* Index describing one location. */ + const TkTextIndex *indexPtr2) /* Index describing second location. */ +{ + int compare = TkTextIndexCmp(indexPtr1, indexPtr2); + + if (compare == 0) { + return 0; + } else if (compare > 0) { + return IndexCountBytesOrdered(textPtr, indexPtr2, indexPtr1); + } else { + return IndexCountBytesOrdered(textPtr, indexPtr1, indexPtr2); + } +} + +static int +IndexCountBytesOrdered( + const TkText *textPtr, + 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. */ +{ + int byteCount, offset; + TkTextSegment *segPtr, *segPtr1; + TkTextLine *linePtr; + + if (indexPtr1->linePtr == indexPtr2->linePtr) { + return indexPtr2->byteIndex - indexPtr1->byteIndex; + } + + /* + * indexPtr2 is on a line strictly after the line containing indexPtr1. + * Add up: + * bytes between indexPtr1 and end of its line + * bytes in lines strictly between indexPtr1 and indexPtr2 + * bytes between start of the indexPtr2 line and indexPtr2 + */ + + segPtr1 = TkTextIndexToSeg(indexPtr1, &offset); + byteCount = -offset; + for (segPtr = segPtr1; segPtr != NULL; segPtr = segPtr->nextPtr) { + byteCount += segPtr->size; + } + + linePtr = TkBTreeNextLine(textPtr, indexPtr1->linePtr); + while (linePtr != indexPtr2->linePtr) { + for (segPtr = linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + byteCount += segPtr->size; + } + linePtr = TkBTreeNextLine(textPtr, linePtr); + if (linePtr == NULL) { + Tcl_Panic("TextIndexCountBytesOrdered ran out of lines"); + } + } + + byteCount += indexPtr2->byteIndex; + + return byteCount; +} + +/* + *--------------------------------------------------------------------------- + * + * 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 = ckalloc(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(infoPtr); + } + return count; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexBackBytes -- + * + * 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. + * + *--------------------------------------------------------------------------- + */ + +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. */ +{ + TkTextSegment *segPtr; + int lineIndex; + + if (byteCount < 0) { + return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); + } + + *dstPtr = *srcPtr; + dstPtr->byteIndex -= byteCount; + 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. + */ + + if (lineIndex < 0) { + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); + } + if (lineIndex == 0) { + dstPtr->byteIndex = 0; + return 1; + } + lineIndex--; + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); + + /* + * Compute the length of the line and add that to dstPtr->charIndex. + */ + + for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL; + segPtr = segPtr->nextPtr) { + dstPtr->byteIndex += segPtr->size; + } + } + return 0; +} + +/* + *--------------------------------------------------------------------------- + * + * TkTextIndexBackChars -- + * + * 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" items before + * srcPtr, or to the first index in the window if there aren't sufficient + * items earlier than srcPtr. + * + * Side effects: + * None. + * + *--------------------------------------------------------------------------- + */ + +void +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(textPtr, srcPtr, -charCount, dstPtr, type); + return; + } + if (checkElided) { + infoPtr = ckalloc(sizeof(TkTextElideInfo)); + elide = TkTextIsElided(textPtr, srcPtr, infoPtr); + } + + *dstPtr = *srcPtr; + + /* + * Find offset within seg that contains byteIndex. Move backward specified + * number of chars. + */ + + lineIndex = -1; + + segSize = dstPtr->byteIndex; + + 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; + } + } + + /* + * Now segPtr points to the segment containing the starting index. + */ + + while (1) { + /* + * 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 == &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; + } + } + } + } + + 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; + } + } + } + dstPtr->byteIndex -= segSize; + + /* + * Move back into previous segment. + */ + + oldPtr = segPtr; + segPtr = dstPtr->linePtr->segPtr; + if (segPtr != oldPtr) { + for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) { + /* Empty body. */ + } + segSize = segPtr->size; + continue; + } + + /* + * Move back to previous line. + */ + + if (lineIndex < 0) { + lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); + } + if (lineIndex == 0) { + dstPtr->byteIndex = 0; + goto backwardCharDone; + } + lineIndex--; + dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); + + /* + * Compute the length of the line and add that to dstPtr->byteIndex. + */ + + oldPtr = dstPtr->linePtr->segPtr; + for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { + dstPtr->byteIndex += segPtr->size; + oldPtr = segPtr; + } + segPtr = oldPtr; + segSize = segPtr->size; + } + + backwardCharDone: + if (infoPtr != NULL) { + TkTextFreeElideInfo(infoPtr); + ckfree(infoPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * StartEnd -- + * + * 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. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static const char * +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 modifier word. */ + TkTextIndex *indexPtr) /* Index to modify based on string. */ +{ + const char *p; + size_t length; + register TkTextSegment *segPtr; + int modifier; + + /* + * Find the end of the modifier word. + */ + + 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)) { + 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); + } + } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) + && (length >= 5)) { + 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. + */ + + 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) { + int ch; + + chSize = TkUtfToUniChar(segPtr->body.chars + offset, &ch); + if (!Tcl_UniCharIsWordChar(ch)) { + break; + } + firstChar = 0; + } + offset += chSize; + indexPtr->byteIndex += chSize; + if (offset >= segPtr->size) { + segPtr = TkTextIndexToSeg(indexPtr, &offset); + } + } + if (firstChar) { + 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(textPtr, 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. + */ + + segPtr = TkTextIndexToSeg(indexPtr, &offset); + while (1) { + int chSize = 1; + + if (segPtr->typePtr == &tkTextCharType) { + + int ch; + TkUtfToUniChar(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; + } + if (offset == 0) { + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } else { + TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr, + COUNT_INDICES); + } + } else { + indexPtr->byteIndex -= chSize; + } + offset -= chSize; + if (offset < 0) { + if (indexPtr->byteIndex == 0) { + goto done; + } + segPtr = TkTextIndexToSeg(indexPtr, &offset); + } + } + + if (!firstChar) { + if (modifier == TKINDEX_DISPLAY) { + TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, + COUNT_DISPLAY_INDICES); + } else { + TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr, + COUNT_INDICES); + } + } + } else { + return NULL; + } + + done: + return p; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |