/*
* tkTextIndex.c --
*
* This module provides functions that manipulate indices for text
* widgets.
*
* Copyright © 1992-1994 The Regents of the University of California.
* Copyright © 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 "tkInt.h"
#include "tkText.h"
#include "default.h"
#ifdef _WIN32
#include "tkWinInt.h"
#endif
/*
* 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);
#if defined(USE_TCL_STUBS) && (TCL_MAJOR_VERSION < 9)
# undef Tcl_UtfPrev
# define Tcl_UtfPrev (((&tclStubsPtr->tcl_PkgProvideEx)[631]) ? \
((const char * (*)(const char *, const char *))(void *)((&tclStubsPtr->tcl_PkgProvideEx)[656])) \
: ((const char * (*)(const char *, const char *))(void *)((&tclStubsPtr->tcl_PkgProvideEx)[331])))
#endif
/*
* 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 = (void *) (size_t) (epoch))
/*
* Define the 'textindex' object type, which Tk uses to represent indices in
* text widgets internally.
*/
const TkObjType tkTextIndexType = {
{"textindex", /* name */
FreeTextIndexInternalRep, /* freeIntRepProc */
DupTextIndexInternalRep, /* dupIntRepProc */
NULL, /* updateStringProc */
NULL, /* setFromAnyProc */
TCL_OBJTYPE_V0},
0
};
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. */
{
Tcl_Size 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.objType;
}
/*
* 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 = (char *)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 = (TkTextIndex *)ckalloc(sizeof(TkTextIndex));
indexPtr->tree = origPtr->tree;
indexPtr->linePtr = origPtr->linePtr;
indexPtr->byteIndex = origPtr->byteIndex;
SET_TEXTINDEX(objPtr, indexPtr);
objPtr->typePtr = &tkTextIndexType.objType;
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.objType) {
Tcl_Size 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 + (int)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. */
{
TkTextSegment *segPtr;
char *p, *start, *end;
int index, offset;
Tcl_UniChar ch = 0;
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 = Tcl_UtfToUniChar(p, &ch);
index += offset;
}
} else {
if (charIndex < (int)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. */
Tcl_Size *offsetPtr) /* Where to store offset within segment, or
* NULL if offset isn't wanted. */
{
TkTextSegment *segPtr;
Tcl_Size 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.
*
*---------------------------------------------------------------------------
*/
Tcl_Size
TkTextSegToOffset(
const TkTextSegment *segPtr,/* Segment whose offset is desired. */
const TkTextLine *linePtr) /* Line containing segPtr. */
{
const TkTextSegment *segPtr2;
Tcl_Size 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) == TCL_OK) {
goto done;
}
if (TkTextImageIndex(textPtr, string, indexPtr) == TCL_OK) {
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, TCL_INDEX_NONE), '.');
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 = (TkTextTag *)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 = (const char *)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 == TCL_OK) {
goto gotBase;
}
}
if ((string[0] == 'e')
&& (strncmp(string, "end",
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 == TCL_OK) {
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");
}
TkTextIndexAdjustToStartEnd(textPtr, indexPtr, 0);
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;
}
/*
*---------------------------------------------------------------------------
*
* TkTextIndexAdjustToStartEnd --
*
* Adjust indexPtr to the -startline/-endline range, or just check
* if indexPtr is out of this range.
*
* Results:
* The return value is a standard Tcl return result. If check is true,
* return TCL_ERROR if indexPtr is outside the -startline/-endline
* range (indexPtr is not modified).
* If check is false, adjust indexPtr to -startline/-endline.
*
* Side effects:
* None.
*
*---------------------------------------------------------------------------
*/
int
TkTextIndexAdjustToStartEnd(
TkText *textPtr,
TkTextIndex *indexPtr, /* Pointer to index. */
int check) /* 1 means only check indexPtr against
* the -startline/-endline range
* 0 means adjust to this range */
{
int bound;
TkTextIndex indexBound;
if (!textPtr) {
return TCL_OK;
}
if (textPtr->start != NULL) {
bound = TkBTreeLinesTo(NULL, textPtr->start);
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, bound, 0,
&indexBound);
if (TkTextIndexCmp(indexPtr, &indexBound) < 0) {
if (check) {
return TCL_ERROR;
}
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, bound, 0,
indexPtr);
}
}
if (textPtr->end != NULL) {
bound = TkBTreeLinesTo(NULL, textPtr->end);
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, bound, 0,
&indexBound);
if (TkTextIndexCmp(indexPtr, &indexBound) > 0) {
if (check) {
return TCL_ERROR;
}
TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, bound, 0,
indexPtr);
}
}
return TCL_OK;
}
/*
*---------------------------------------------------------------------------
*
* 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.
*
*---------------------------------------------------------------------------
*/
Tcl_Size
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;
Tcl_Size 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 snprintf(string, TK_POS_CHARS, "%d.%" TCL_SIZE_MODIFIER "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. */
{
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) {
TkTextIndex indexPtr2;
/*
* 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, &indexPtr2,
COUNT_DISPLAY_INDICES);
/*
* If we couldn't go to the previous line, then we wanted
to go before the start of the text: arrange for returning
the first index of the first display line.
*/
if (!TkTextIndexCmp(indexPtr, &indexPtr2)) {
xOffset = 0;
break;
}
*indexPtr = indexPtr2;
}
}
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;
Tcl_Size 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 = (TkTextElideInfo *)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->elide >= 0) {
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 > 0;
break;
}
}
} else {
elide = tagPtr->elide > 0;
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 + byteOffset < segPtr->size) {
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. */
{
Tcl_Size 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;
Tcl_Size byteOffset, maxBytes, count = 0;
int 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(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->elide >= 0) {
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 > 0;
break;
}
}
} else {
elide = tagPtr->elide > 0;
infoPtr->elidePriority = tagPtr->priority;
}
}
}
}
if (elide) {
if (segPtr == seg2Ptr) {
goto countDone;
}
byteOffset = 0;
continue;
}
}
if (segPtr->typePtr == &tkTextCharType) {
Tcl_Size byteLen = segPtr->size - byteOffset;
unsigned char *str = (unsigned char *)
segPtr->body.chars + byteOffset;
Tcl_Size i;
if (segPtr == seg2Ptr) {
if (byteLen + byteOffset > maxBytes) {
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) {
Tcl_Size byteLen = segPtr->size - byteOffset;
if (segPtr == seg2Ptr) {
if (byteLen + byteOffset > maxBytes) {
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 = (TkTextElideInfo *)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 <= (int)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->elide >= 0) {
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 > 0;
break;
}
}
} else {
elide = tagPtr->elide > 0;
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;
}
if ((sizeof(Tcl_UniChar) == 2) && (unsigned)(UCHAR(*p) - 0xF0) <= 5) {
charCount--; /* Characters > U+FFFF count as 2 here */
}
if (charCount != 0) {
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;
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;
Tcl_Size 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;
Tcl_Size 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 + 1 > 1) {
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 ((int)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:
*/