#include "default.h"
#include "tkInt.h"
#include "tkText.h"
#include <stdlib.h>
#include <assert.h>
#ifndef MAX
# define MAX(a,b) (((int) a) < ((int) b) ? b : a)
#endif
#ifndef MIN
# define MIN(a,b) (((int) a) < ((int) b) ? a : b)
#endif
#if NDEBUG
# define DEBUG(expr)
#else
# define DEBUG(expr) expr
#endif
enum { TKINDEX_NONE, TKINDEX_DISPLAY, TKINDEX_CHAR };
static const char * ForwBack(TkText *textPtr, const char *string, TkTextIndex *indexPtr);
static const char * StartEnd(TkText *textPtr, const char *string, TkTextIndex *indexPtr);
static bool GetIndex(Tcl_Interp *interp, TkSharedText *sharedTextPtr, TkText *textPtr,
const char *string, TkTextIndex *indexPtr);
static TkTextSegment * IndexToSeg(const TkTextIndex *indexPtr, int *offsetPtr);
static int SegToIndex(const TkTextLine *linePtr, const TkTextSegment *segPtr);
#if TCL_MAJOR_VERSION > 8 || TCL_MINOR_VERSION > 5
const
#endif
Tcl_ObjType tkTextIndexType = {
"textindex",
NULL,
NULL,
NULL,
NULL
};
bool
TkTextIndexIsEmpty(
const TkTextIndex *indexPtr)
{
assert(indexPtr);
return indexPtr->priv.byteIndex == -1 && !indexPtr->priv.segPtr;
}
bool
TkTextGetIndexFromObj(
Tcl_Interp *interp,
TkText *textPtr,
Tcl_Obj *objPtr,
TkTextIndex *indexPtr)
{
assert(textPtr);
return GetIndex(interp, textPtr->sharedTextPtr, textPtr, Tcl_GetString(objPtr), indexPtr);
}
#if !NDEBUG
static bool
CheckLine(
const TkTextIndex *indexPtr,
const TkTextLine *linePtr)
{
assert(linePtr);
if (indexPtr->stateEpoch == TkBTreeEpoch(indexPtr->tree)) {
if (indexPtr->priv.segPtr
&& indexPtr->priv.segPtr->sectionPtr->linePtr != indexPtr->priv.linePtr) {
return false;
}
if (indexPtr->priv.lineNo != -1
&& indexPtr->priv.lineNo !=
TkBTreeLinesTo(indexPtr->tree, NULL, indexPtr->priv.linePtr, NULL)) {
return false;
}
if (indexPtr->priv.lineNoRel != -1
&& indexPtr->priv.lineNoRel !=
TkBTreeLinesTo(indexPtr->tree, indexPtr->textPtr, indexPtr->priv.linePtr, NULL)) {
return false;
}
}
if (!indexPtr->discardConsistencyCheck && indexPtr->textPtr) {
const TkTextLine *startLine = TkBTreeGetStartLine(indexPtr->textPtr);
const TkTextLine *endLine = TkBTreeGetLastLine(indexPtr->textPtr);
int lineNo = TkBTreeLinesTo(indexPtr->tree, NULL, linePtr, NULL);
if (lineNo < TkBTreeLinesTo(indexPtr->tree, NULL, startLine, NULL)) {
return false;
}
if (lineNo > TkBTreeLinesTo(indexPtr->tree, NULL, endLine, NULL)) {
return false;
}
}
return true;
}
#endif
static int
FindStartByteIndex(
const TkTextIndex *indexPtr)
{
const TkText *textPtr = indexPtr->textPtr;
const TkTextSegment *segPtr;
const TkTextSection *sectionPtr;
int byteIndex;
if (!textPtr) {
return 0;
}
if (textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker) {
return 0;
}
segPtr = textPtr->startMarker;
sectionPtr = segPtr->sectionPtr;
byteIndex = 0;
if (sectionPtr->linePtr == indexPtr->priv.linePtr) {
while (segPtr && sectionPtr == segPtr->sectionPtr) {
byteIndex += segPtr->size;
segPtr = segPtr->prevPtr;
}
while (sectionPtr->prevPtr) {
sectionPtr = sectionPtr->prevPtr;
byteIndex += sectionPtr->size;
}
}
return byteIndex;
}
static bool
DontNeedSpecialStartLineTreatment(
const TkTextIndex *indexPtr)
{
const TkText *textPtr = indexPtr->textPtr;
return !textPtr
|| textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker
|| indexPtr->priv.linePtr != textPtr->startMarker->sectionPtr->linePtr;
}
void
TkTextIndexSetLine(
TkTextIndex *indexPtr,
TkTextLine *linePtr)
{
assert(linePtr);
assert(indexPtr->tree);
assert(CheckLine(indexPtr, linePtr));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.segPtr = NULL;
indexPtr->priv.byteIndex = -1;
if ((indexPtr->priv.linePtr = linePtr)) {
assert(linePtr->parentPtr);
if (DontNeedSpecialStartLineTreatment(indexPtr)) {
indexPtr->priv.byteIndex = 0;
} else {
indexPtr->priv.segPtr = indexPtr->textPtr->startMarker;
indexPtr->priv.isCharSegment = false;
indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
}
}
}
#if !NDEBUG
static bool
CheckByteIndex(
const TkTextIndex *indexPtr,
const TkTextLine *linePtr,
int byteIndex)
{
const TkText *textPtr = indexPtr->textPtr;
if (byteIndex == -1 && (byteIndex = indexPtr->priv.byteIndex) == -1) {
assert(indexPtr->priv.segPtr);
assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
byteIndex = SegToIndex(indexPtr->priv.linePtr, indexPtr->priv.segPtr);
}
if (!indexPtr->discardConsistencyCheck && textPtr) {
if (linePtr == textPtr->startMarker->sectionPtr->linePtr) {
if (byteIndex < FindStartByteIndex(indexPtr)) {
return false;
}
}
if (linePtr == textPtr->endMarker->sectionPtr->linePtr) {
return byteIndex <= SegToIndex(linePtr, textPtr->endMarker);
}
if (linePtr == textPtr->endMarker->sectionPtr->linePtr->nextPtr) {
return byteIndex == 0;
}
}
return byteIndex < linePtr->size;
}
#endif
void
TkTextIndexSetPosition(
TkTextIndex *indexPtr,
int byteIndex,
TkTextSegment *segPtr)
{
assert(indexPtr->tree);
assert(byteIndex >= 0);
assert(segPtr);
assert(segPtr->typePtr);
assert(segPtr->sectionPtr);
assert(CheckLine(indexPtr, segPtr->sectionPtr->linePtr));
assert(CheckByteIndex(indexPtr, segPtr->sectionPtr->linePtr, byteIndex));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.linePtr = segPtr->sectionPtr->linePtr;
indexPtr->priv.byteIndex = byteIndex;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.segPtr = segPtr;
indexPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
#if !NDEBUG
{
int pos = SegToIndex(indexPtr->priv.linePtr, segPtr);
if (segPtr->typePtr == &tkTextCharType) {
assert(byteIndex - pos < segPtr->size);
} else {
assert(pos == byteIndex);
}
}
#endif
}
static bool
DontNeedSpecialEndLineTreatment(
const TkTextIndex *indexPtr)
{
const TkText *textPtr = indexPtr->textPtr;
return !textPtr
|| textPtr->endMarker == TkBTreeGetShared(indexPtr->tree)->endMarker
|| indexPtr->priv.linePtr != textPtr->endMarker->sectionPtr->linePtr;
}
static int
FindEndByteIndex(
const TkTextIndex *indexPtr)
{
if (indexPtr->textPtr && indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr)) {
return 0;
}
if (DontNeedSpecialEndLineTreatment(indexPtr)) {
return indexPtr->priv.linePtr->size - 1;
}
return SegToIndex(indexPtr->priv.linePtr, indexPtr->textPtr->endMarker);
}
void
TkTextIndexSetByteIndex(
TkTextIndex *indexPtr,
int byteIndex)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(byteIndex >= 0);
if (byteIndex == FindEndByteIndex(indexPtr) + 1) {
assert(indexPtr->priv.linePtr->nextPtr);
indexPtr->priv.linePtr = indexPtr->priv.linePtr->nextPtr;
indexPtr->priv.byteIndex = 0;
indexPtr->priv.segPtr = NULL;
if (indexPtr->priv.lineNo >= 0) {
indexPtr->priv.lineNo += 1;
}
if (indexPtr->priv.lineNoRel >= 0) {
indexPtr->priv.lineNoRel += 1;
}
} else {
indexPtr->priv.byteIndex = byteIndex;
indexPtr->priv.segPtr = NULL;
}
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, byteIndex));
}
void
TkTextIndexSetByteIndex2(
TkTextIndex *indexPtr,
TkTextLine *linePtr,
int byteIndex)
{
assert(indexPtr->tree);
assert(linePtr);
assert(linePtr->parentPtr);
assert(byteIndex >= 0);
if (indexPtr->priv.linePtr != linePtr) {
indexPtr->priv.linePtr = linePtr;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
}
TkTextIndexSetByteIndex(indexPtr, byteIndex);
}
void
TkTextIndexSetSegment(
TkTextIndex *indexPtr,
TkTextSegment *segPtr)
{
assert(indexPtr->tree);
assert(segPtr);
assert(segPtr->typePtr);
assert(segPtr->sectionPtr);
assert(CheckLine(indexPtr, segPtr->sectionPtr->linePtr));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.linePtr = segPtr->sectionPtr->linePtr;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.segPtr = segPtr;
if (segPtr->typePtr == &tkTextCharType) {
indexPtr->priv.byteIndex = SegToIndex(indexPtr->priv.linePtr, segPtr);
indexPtr->priv.isCharSegment = true;
} else {
indexPtr->priv.byteIndex = -1;
indexPtr->priv.isCharSegment = false;
}
assert(CheckByteIndex(indexPtr, segPtr->sectionPtr->linePtr, -1));
}
void
TkTextIndexSetToStartOfLine(
TkTextIndex *indexPtr)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.segPtr = NULL;
indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
}
void
TkTextIndexSetToStartOfLine2(
TkTextIndex *indexPtr,
TkTextLine *linePtr)
{
assert(indexPtr->tree);
assert(linePtr);
assert(linePtr->parentPtr);
assert(CheckLine(indexPtr, linePtr));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.linePtr = linePtr;
indexPtr->priv.segPtr = NULL;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
}
void
TkTextIndexSetToEndOfLine2(
TkTextIndex *indexPtr,
TkTextLine *linePtr)
{
assert(indexPtr->tree);
assert(linePtr);
assert(linePtr->parentPtr);
assert(linePtr->nextPtr);
assert(CheckLine(indexPtr, linePtr->nextPtr));
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.segPtr = NULL;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.linePtr = linePtr->nextPtr;
indexPtr->priv.byteIndex = 0;
}
void
TkTextIndexSetToLastChar(
TkTextIndex *indexPtr)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
indexPtr->stateEpoch = TkBTreeEpoch(indexPtr->tree);
indexPtr->priv.byteIndex = FindEndByteIndex(indexPtr);
indexPtr->priv.segPtr = NULL;
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
}
void
TkTextIndexSetupToStartOfText(
TkTextIndex *indexPtr,
TkText *textPtr,
TkTextBTree tree)
{
assert(indexPtr);
assert(tree);
indexPtr->textPtr = textPtr;
indexPtr->tree = tree;
indexPtr->stateEpoch = TkBTreeEpoch(tree);
indexPtr->priv.lineNo = textPtr ? -1 : 0;
indexPtr->priv.lineNoRel = 0;
indexPtr->priv.isCharSegment = false;
DEBUG(indexPtr->discardConsistencyCheck = false);
if (textPtr) {
indexPtr->priv.segPtr = textPtr->startMarker;
indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
indexPtr->priv.byteIndex = FindStartByteIndex(indexPtr);
} else {
indexPtr->priv.segPtr = TkBTreeGetShared(tree)->startMarker;
indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
indexPtr->priv.byteIndex = 0;
}
}
void
TkTextIndexSetupToEndOfText(
TkTextIndex *indexPtr,
TkText *textPtr,
TkTextBTree tree)
{
assert(indexPtr);
assert(tree);
indexPtr->textPtr = textPtr;
indexPtr->tree = tree;
indexPtr->stateEpoch = TkBTreeEpoch(tree);
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
DEBUG(indexPtr->discardConsistencyCheck = false);
if (!textPtr) {
indexPtr->priv.segPtr = TkBTreeGetShared(tree)->endMarker;
indexPtr->priv.isCharSegment = false;
indexPtr->priv.linePtr = indexPtr->priv.segPtr->sectionPtr->linePtr;
indexPtr->priv.byteIndex = 0;
} else {
indexPtr->priv.linePtr = TkBTreeGetLastLine(textPtr);
indexPtr->priv.segPtr = indexPtr->priv.linePtr->segPtr;
indexPtr->priv.isCharSegment = indexPtr->priv.segPtr->typePtr == &tkTextCharType;
indexPtr->priv.byteIndex = 0;
}
}
int
TkTextIndexGetByteIndex(
const TkTextIndex *indexPtr)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
if (indexPtr->priv.byteIndex == -1) {
assert(indexPtr->priv.segPtr);
assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
assert(indexPtr->priv.segPtr->typePtr);
assert(indexPtr->priv.segPtr->sectionPtr);
assert(indexPtr->priv.segPtr->sectionPtr->linePtr == indexPtr->priv.linePtr);
((TkTextIndex *)indexPtr)->priv.byteIndex =
SegToIndex(indexPtr->priv.linePtr, indexPtr->priv.segPtr);
}
return indexPtr->priv.byteIndex;
}
void
TkTextIndexToByteIndex(
TkTextIndex *indexPtr)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
if (indexPtr->priv.byteIndex == -1) {
(void) TkTextIndexGetByteIndex(indexPtr);
}
indexPtr->priv.segPtr = NULL;
}
void
TkTextIndexClear(
TkTextIndex *indexPtr,
TkText *textPtr)
{
assert(textPtr);
indexPtr->textPtr = textPtr;
indexPtr->tree = textPtr->sharedTextPtr->tree;
indexPtr->stateEpoch = 0;
indexPtr->priv.linePtr = NULL;
indexPtr->priv.segPtr = NULL;
indexPtr->priv.byteIndex = -1;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.isCharSegment = false;
DEBUG(indexPtr->discardConsistencyCheck = false);
}
void
TkTextIndexClear2(
TkTextIndex *indexPtr,
TkText *textPtr,
TkTextBTree tree)
{
assert(textPtr || tree);
assert(!textPtr || !tree || textPtr->sharedTextPtr->tree == tree);
indexPtr->textPtr = textPtr;
indexPtr->tree = tree ? tree : textPtr->sharedTextPtr->tree;
indexPtr->stateEpoch = 0;
indexPtr->priv.linePtr = NULL;
indexPtr->priv.segPtr = NULL;
indexPtr->priv.byteIndex = -1;
indexPtr->priv.lineNo = -1;
indexPtr->priv.lineNoRel = -1;
indexPtr->priv.isCharSegment = false;
DEBUG(indexPtr->discardConsistencyCheck = false);
}
unsigned
TkTextIndexGetLineNumber(
const TkTextIndex *indexPtr,
const TkText *textPtr)
{
unsigned epoch;
int32_t *lineNo;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(!textPtr || indexPtr->textPtr == textPtr);
lineNo = (int32_t *) (textPtr ? &indexPtr->priv.lineNoRel : &indexPtr->priv.lineNo);
epoch = TkBTreeEpoch(indexPtr->tree);
if (*lineNo == -1 || indexPtr->stateEpoch != epoch) {
TkTextIndex *iPtr = (TkTextIndex *) indexPtr;
if (iPtr->priv.byteIndex == -1) {
assert(iPtr->priv.segPtr);
assert(!iPtr->priv.isCharSegment || indexPtr->stateEpoch == epoch);
iPtr->priv.byteIndex = SegToIndex(iPtr->priv.linePtr, iPtr->priv.segPtr);
assert(CheckByteIndex(iPtr, iPtr->priv.linePtr, iPtr->priv.byteIndex));
}
TkTextIndexSetEpoch(iPtr, epoch);
*lineNo = TkBTreeLinesTo(iPtr->tree, textPtr, iPtr->priv.linePtr, NULL);
} else {
assert(*lineNo == TkBTreeLinesTo(indexPtr->tree, textPtr, indexPtr->priv.linePtr, NULL));
}
return *lineNo;
}
bool
TkTextIndexRebuild(
TkTextIndex *indexPtr)
{
TkTextLine *linePtr;
int byteIndex;
int lineNo;
bool rc;
assert(indexPtr->tree);
assert(indexPtr->priv.lineNo >= 0 || indexPtr->priv.lineNoRel >= 0);
assert(indexPtr->priv.byteIndex >= 0);
if (indexPtr->stateEpoch == TkBTreeEpoch(indexPtr->tree)) {
return true;
}
if (indexPtr->priv.lineNo >= 0) {
lineNo = MIN(TkBTreeNumLines(indexPtr->tree, NULL), indexPtr->priv.lineNo);
linePtr = TkBTreeFindLine(indexPtr->tree, NULL, lineNo);
indexPtr->priv.lineNo = lineNo;
} else {
lineNo = MIN(TkBTreeNumLines(indexPtr->tree, indexPtr->textPtr), indexPtr->priv.lineNoRel);
linePtr = TkBTreeFindLine(indexPtr->tree, indexPtr->textPtr, lineNo);
indexPtr->priv.lineNoRel = lineNo;
}
if (!(rc = (linePtr == indexPtr->priv.linePtr))) {
indexPtr->priv.linePtr = linePtr;
}
byteIndex = MIN(indexPtr->priv.byteIndex, FindEndByteIndex(indexPtr));
if (byteIndex != indexPtr->priv.byteIndex) {
rc = false;
}
indexPtr->priv.byteIndex = byteIndex;
indexPtr->priv.segPtr = NULL;
return rc;
}
int
TkTextIndexRestrictToStartRange(
TkTextIndex *indexPtr)
{
TkText *textPtr = indexPtr->textPtr;
TkTextIndex start;
int cmp;
assert(indexPtr->tree);
if (!textPtr || textPtr->startMarker == textPtr->sharedTextPtr->startMarker) {
return TkTextIndexIsStartOfText(indexPtr) ? 0 : 1;
}
start = *indexPtr;
TkTextIndexSetSegment(&start, textPtr->startMarker);
if ((cmp = TkTextIndexCompare(indexPtr, &start)) < 0) {
*indexPtr = start;
cmp = -1;
}
return cmp;
}
int
TkTextIndexRestrictToEndRange(
TkTextIndex *indexPtr)
{
TkText *textPtr = indexPtr->textPtr;
TkTextIndex last;
int cmp;
assert(indexPtr->tree);
if (!textPtr || textPtr->endMarker == textPtr->sharedTextPtr->endMarker) {
return TkTextIndexIsEndOfText(indexPtr) ? 0 : -1;
}
last = *indexPtr;
TkTextIndexSetByteIndex2(&last, TkBTreeGetLastLine(textPtr), 0);
if ((cmp = TkTextIndexCompare(indexPtr, &last)) > 0) {
*indexPtr = last;
cmp = 1;
} else if (cmp < 0) {
TkTextIndex end = *indexPtr;
TkTextIndexSetSegment(&end, textPtr->endMarker);
if (TkTextIndexCompare(indexPtr, &end) > 0) {
*indexPtr = last;
cmp = 0;
} else {
cmp = -1;
}
}
return cmp;
}
bool
TkTextIndexEnsureBeforeLastChar(
TkTextIndex *indexPtr)
{
TkText *textPtr = indexPtr->textPtr;
const TkTextLine *lastLinePtr;
assert(indexPtr->tree);
assert(indexPtr->textPtr);
if (TkTextIsDeadPeer(indexPtr->textPtr)) {
return false;
}
lastLinePtr = TkBTreeGetLastLine(textPtr);
if (lastLinePtr == indexPtr->priv.linePtr
&& (!textPtr || lastLinePtr != textPtr->startMarker->sectionPtr->linePtr)) {
TkTextIndexSetToLastChar2(indexPtr, lastLinePtr->prevPtr);
}
return true;
}
TkTextSegment *
TkTextIndexGetContentSegment(
const TkTextIndex *indexPtr,
int *offset)
{
TkTextSegment *segPtr;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
if ((segPtr = indexPtr->priv.segPtr)
&& (!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch)) {
while (segPtr->size == 0) {
segPtr = segPtr->nextPtr;
}
if (offset) {
if (indexPtr->priv.byteIndex == -1) {
*offset = 0;
} else {
int byteIndex = SegToIndex(indexPtr->priv.linePtr, segPtr);
assert(byteIndex <= indexPtr->priv.byteIndex);
assert(indexPtr->priv.byteIndex < byteIndex + segPtr->size);
*offset = indexPtr->priv.byteIndex - byteIndex;
}
assert(*offset >= 0);
assert(*offset < segPtr->size);
}
} else {
int myOffset;
assert(indexPtr->priv.byteIndex >= 0);
segPtr = IndexToSeg(indexPtr, &myOffset);
if (myOffset == 0) {
TkTextIndex *iPtr = (TkTextIndex *) indexPtr;
iPtr->priv.segPtr = segPtr;
iPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
}
if (offset) {
*offset = myOffset;
}
}
return segPtr;
}
TkTextSegment *
TkTextIndexGetFirstSegment(
const TkTextIndex *indexPtr,
int *offset)
{
TkTextSegment *segPtr;
TkTextSegment *prevPtr;
int myOffset;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
if ((segPtr = indexPtr->priv.segPtr)
&& (!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch)) {
if (indexPtr->priv.byteIndex >= 0) {
myOffset = indexPtr->priv.byteIndex - SegToIndex(indexPtr->priv.linePtr, segPtr);
assert(myOffset >= 0);
assert(segPtr->size == 0 || myOffset < segPtr->size);
} else {
myOffset = 0;
}
} else {
assert(indexPtr->priv.byteIndex >= 0);
segPtr = IndexToSeg(indexPtr, &myOffset);
}
assert(segPtr->typePtr);
assert(segPtr->sectionPtr);
assert(segPtr->sectionPtr->linePtr == indexPtr->priv.linePtr);
if (myOffset == 0) {
TkTextIndex *iPtr;
while ((prevPtr = segPtr->prevPtr) && prevPtr->size == 0) {
segPtr = prevPtr;
}
iPtr = (TkTextIndex *) indexPtr;
iPtr->priv.segPtr = segPtr;
iPtr->priv.isCharSegment = segPtr->typePtr == &tkTextCharType;
}
if (offset) {
*offset = myOffset;
}
return segPtr;
}
bool
TkTextIndexIsStartOfLine(
const TkTextIndex *indexPtr)
{
const TkTextSegment *segPtr;
const TkTextSegment *startPtr;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
if (indexPtr->priv.byteIndex >= 0) {
return FindStartByteIndex(indexPtr) == indexPtr->priv.byteIndex;
}
assert(indexPtr->priv.segPtr);
assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
startPtr = indexPtr->textPtr ? indexPtr->textPtr->startMarker : NULL;
segPtr = indexPtr->priv.segPtr;
if (segPtr->size > 0) {
segPtr = segPtr->prevPtr;
}
while (segPtr && segPtr->size == 0) {
if (segPtr == startPtr) {
return true;
}
segPtr = segPtr->prevPtr;
}
return !segPtr;
}
bool
TkTextIndexIsEndOfLine(
const TkTextIndex *indexPtr)
{
const TkTextSegment *segPtr;
const TkTextSegment *endPtr;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
if (indexPtr->priv.byteIndex >= 0) {
return indexPtr->priv.byteIndex == FindEndByteIndex(indexPtr);
}
assert(indexPtr->priv.segPtr);
assert(!indexPtr->priv.isCharSegment || TkBTreeEpoch(indexPtr->tree) == indexPtr->stateEpoch);
if (indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr)) {
return true;
}
segPtr = indexPtr->priv.segPtr;
if (DontNeedSpecialEndLineTreatment(indexPtr)) {
while (segPtr->size == 0) {
segPtr = segPtr->nextPtr;
}
return segPtr->size == 1 && segPtr == indexPtr->priv.linePtr->lastPtr;
}
assert(indexPtr->textPtr);
assert(indexPtr->textPtr->endMarker != indexPtr->textPtr->sharedTextPtr->endMarker);
endPtr = indexPtr->textPtr->endMarker;
while (segPtr->size == 0) {
if (segPtr == endPtr) {
return true;
}
segPtr = segPtr->nextPtr;
}
return false;
}
bool
TkTextIndexIsStartOfText(
const TkTextIndex *indexPtr)
{
const TkText *textPtr = indexPtr->textPtr;
const TkTextSegment *segPtr;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
segPtr = textPtr ? textPtr->startMarker : TkBTreeGetShared(indexPtr->tree)->startMarker;
return indexPtr->priv.linePtr == segPtr->sectionPtr->linePtr && TkTextIndexIsStartOfLine(indexPtr);
}
bool
TkTextIndexIsEndOfText(
const TkTextIndex *indexPtr)
{
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
if (indexPtr->textPtr) {
return indexPtr->priv.linePtr == TkBTreeGetLastLine(indexPtr->textPtr);
}
return !indexPtr->priv.linePtr->nextPtr;
}
bool
TkTextIndexIsEqual(
const TkTextIndex *indexPtr1,
const TkTextIndex *indexPtr2)
{
const TkTextSegment *segPtr1;
const TkTextSegment *segPtr2;
assert(indexPtr1->priv.linePtr);
assert(indexPtr2->priv.linePtr);
assert(indexPtr1->priv.linePtr->parentPtr);
assert(indexPtr2->priv.linePtr->parentPtr);
if (indexPtr1->priv.linePtr != indexPtr2->priv.linePtr) {
return false;
}
if ((segPtr1 = TkTextIndexGetSegment(indexPtr1))) {
if ((segPtr2 = TkTextIndexGetSegment(indexPtr2))) {
while (segPtr1->prevPtr && segPtr1->prevPtr->size == 0) {
segPtr1 = segPtr1->prevPtr;
}
while (segPtr2->prevPtr && segPtr2->prevPtr->size == 0) {
segPtr2 = segPtr2->prevPtr;
}
return segPtr1 == segPtr2;
}
}
return TkTextIndexGetByteIndex(indexPtr1) == TkTextIndexGetByteIndex(indexPtr2);
}
int
TkTextIndexCompare(
const TkTextIndex *indexPtr1,
const TkTextIndex *indexPtr2)
{
const TkTextSection *sectionPtr1;
const TkTextSection *sectionPtr2;
const TkTextSegment *segPtr1;
const TkTextSegment *segPtr2;
assert(indexPtr1->priv.linePtr);
assert(indexPtr2->priv.linePtr);
assert(indexPtr1->priv.linePtr->parentPtr);
assert(indexPtr2->priv.linePtr->parentPtr);
if (indexPtr1->priv.linePtr != indexPtr2->priv.linePtr) {
int lineNo1 = TkTextIndexGetLineNumber(indexPtr1, NULL);
int lineNo2 = TkTextIndexGetLineNumber(indexPtr2, NULL);
return lineNo1 - lineNo2;
}
if (indexPtr1->priv.byteIndex >= 0 && indexPtr2->priv.byteIndex >= 0) {
return indexPtr1->priv.byteIndex - indexPtr2->priv.byteIndex;
}
if (!(segPtr1 = TkTextIndexGetSegment(indexPtr1)) || !(segPtr2 = TkTextIndexGetSegment(indexPtr2))) {
return TkTextIndexGetByteIndex(indexPtr1) - TkTextIndexGetByteIndex(indexPtr2);
}
assert(!indexPtr1->priv.isCharSegment || TkBTreeEpoch(indexPtr1->tree) == indexPtr1->stateEpoch);
assert(!indexPtr2->priv.isCharSegment || TkBTreeEpoch(indexPtr2->tree) == indexPtr2->stateEpoch);
segPtr1 = indexPtr1->priv.segPtr;
segPtr2 = indexPtr2->priv.segPtr;
while (segPtr1->size == 0) {
segPtr1 = segPtr1->nextPtr;
}
while (segPtr2->size == 0) {
segPtr2 = segPtr2->nextPtr;
}
if (segPtr1 == segPtr2) {
return 0;
}
sectionPtr1 = indexPtr1->priv.segPtr->sectionPtr;
sectionPtr2 = indexPtr2->priv.segPtr->sectionPtr;
if (sectionPtr1 != sectionPtr2) {
while (sectionPtr1 && sectionPtr1 != sectionPtr2) {
sectionPtr1 = sectionPtr1->nextPtr;
}
return sectionPtr1 ? -1 : +1;
}
segPtr1 = indexPtr1->priv.segPtr;
segPtr2 = indexPtr2->priv.segPtr;
while (segPtr1 != segPtr2) {
if (!(segPtr1 = segPtr1->nextPtr) || segPtr1->sectionPtr != sectionPtr1) {
return +1;
}
}
return -1;
}
bool
TkTextIndexAddToByteIndex(
TkTextIndex *indexPtr,
int byteOffset)
{
bool rc = true;
assert(indexPtr->tree);
assert(indexPtr->priv.linePtr);
assert(indexPtr->priv.linePtr->parentPtr);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
if (byteOffset == 0) {
return true;
}
if (indexPtr->priv.byteIndex == -1) {
(void) TkTextIndexGetByteIndex(indexPtr);
}
if (byteOffset > 0) {
if ((indexPtr->priv.byteIndex += byteOffset) > FindEndByteIndex(indexPtr)) {
assert(indexPtr->priv.linePtr->nextPtr);
assert(indexPtr->priv.byteIndex <= indexPtr->priv.linePtr->size);
indexPtr->priv.linePtr = indexPtr->priv.linePtr->nextPtr;
if (indexPtr->priv.lineNo >= 0) {
indexPtr->priv.lineNo += 1;
}
if (indexPtr->priv.lineNoRel >= 0) {
indexPtr->priv.lineNoRel += 1;
}
indexPtr->priv.byteIndex = 0;
rc = false;
}
} else {
assert(-byteOffset <= indexPtr->priv.byteIndex);
indexPtr->priv.byteIndex += byteOffset;
}
indexPtr->priv.segPtr = NULL;
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
return rc;
}
#if !NDEBUG
void
TkpTextIndexDump(
TkText *textPtr,
const TkTextIndex *indexPtr)
{
char buf[TK_POS_CHARS];
TkTextIndexPrint(TkTextIndexGetShared(indexPtr), textPtr, indexPtr, buf);
printf("%s\n", buf);
}
#endif
Tcl_Obj *
TkTextNewIndexObj(
const TkTextIndex *indexPtr)
{
char buffer[TK_POS_CHARS];
int len;
assert(indexPtr->textPtr);
len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer);
return Tcl_NewStringObj(buffer, len);
}
TkTextIndex *
TkTextMakeByteIndex(
TkTextBTree tree,
const TkText *textPtr,
int lineIndex,
int byteIndex,
TkTextIndex *indexPtr)
{
TkTextSegment *segPtr;
TkTextSection *sectionPtr;
TkTextLine *linePtr;
int index, nextIndex;
TkTextIndexClear2(indexPtr, (TkText *) textPtr, tree);
if (lineIndex < 0) {
TkTextIndexSetupToStartOfText(indexPtr, (TkText *) textPtr, tree);
return indexPtr;
}
if (!(linePtr = TkBTreeFindLine(tree, textPtr, lineIndex))) {
TkTextIndexSetupToEndOfText(indexPtr, (TkText *) textPtr, tree);
return indexPtr;
}
if (byteIndex < 0) {
byteIndex = 0;
}
if (textPtr) {
if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker
&& textPtr->startMarker->sectionPtr->linePtr == linePtr) {
int startByteIndex;
TkTextIndexSetSegment(indexPtr, textPtr->startMarker);
startByteIndex = FindStartByteIndex(indexPtr);
if (byteIndex <= startByteIndex) {
return indexPtr;
}
}
if (textPtr->endMarker != textPtr->sharedTextPtr->endMarker
&& textPtr->endMarker->sectionPtr->linePtr == linePtr) {
int endByteIndex;
TkTextIndexSetSegment(indexPtr, textPtr->endMarker);
endByteIndex = FindEndByteIndex(indexPtr);
if (endByteIndex <= byteIndex) {
return indexPtr;
}
}
}
indexPtr->priv.linePtr = linePtr;
if (byteIndex == 0) {
TkTextIndexSetByteIndex(indexPtr, 0);
return indexPtr;
}
if (byteIndex >= linePtr->size) {
TkTextIndexSetByteIndex(indexPtr, linePtr->size - 1);
return indexPtr;
}
indexPtr->priv.byteIndex = byteIndex;
index = 0;
sectionPtr = linePtr->segPtr->sectionPtr;
while ((nextIndex = index + sectionPtr->size) <= byteIndex) {
index = nextIndex;
sectionPtr = sectionPtr->nextPtr;
assert(sectionPtr);
}
segPtr = sectionPtr->segPtr;
while ((nextIndex = index + segPtr->size) < byteIndex) {
index = nextIndex;
segPtr = segPtr->nextPtr;
assert(segPtr);
}
if (segPtr->typePtr == &tkTextCharType && byteIndex > index && index + segPtr->size > byteIndex) {
const char *p = segPtr->body.chars + (byteIndex - index);
while ((*p & 0xc0) == 0x80) {
++p;
indexPtr->priv.byteIndex += 1;
}
}
return indexPtr;
}
static unsigned
CountCharsInSeg(
const TkTextSegment *segPtr)
{
assert(segPtr->typePtr == &tkTextCharType);
return Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
}
TkTextIndex *
TkTextMakeCharIndex(
TkTextBTree tree,
TkText *textPtr,
int lineIndex,
int charIndex,
TkTextIndex *indexPtr)
{
TkTextSegment *segPtr, *lastPtr;
TkTextLine *linePtr;
char *p, *start, *end;
int index, offset;
TkTextIndexClear2(indexPtr, textPtr, tree);
if (lineIndex < 0) {
TkTextIndexSetupToStartOfText(indexPtr, textPtr, tree);
return indexPtr;
}
if (!(linePtr = TkBTreeFindLine(tree, textPtr, lineIndex))) {
TkTextIndexSetupToEndOfText(indexPtr, textPtr, tree);
return indexPtr;
}
indexPtr->priv.linePtr = linePtr;
if (charIndex >= linePtr->size - 1) {
TkTextIndexSetToLastChar(indexPtr);
return indexPtr;
}
if (charIndex <= 0) {
TkTextIndexSetToStartOfLine(indexPtr);
return indexPtr;
}
if (textPtr && textPtr->endMarker->sectionPtr->linePtr == linePtr) {
lastPtr = textPtr->endMarker;
} else {
lastPtr = NULL;
}
if (!textPtr
|| textPtr->startMarker == TkBTreeGetShared(indexPtr->tree)->startMarker
|| linePtr != textPtr->startMarker->sectionPtr->linePtr) {
segPtr = linePtr->segPtr;
index = 0;
} else {
TkTextSegment *startPtr;
TkTextIndexSetSegment(indexPtr, textPtr->startMarker);
startPtr = TkTextIndexGetFirstSegment(indexPtr, NULL);
for (segPtr = linePtr->segPtr; segPtr != startPtr; segPtr = segPtr->nextPtr) {
if (segPtr->typePtr == &tkTextCharType) {
charIndex -= CountCharsInSeg(segPtr);
} else {
assert(segPtr->size <= 1);
charIndex -= segPtr->size;
}
if (charIndex <= 0) {
return indexPtr;
}
}
index = TkTextIndexGetByteIndex(indexPtr);
indexPtr->priv.segPtr = NULL;
}
while (segPtr != lastPtr) {
if (segPtr->tagInfoPtr) {
if (segPtr->typePtr == &tkTextCharType) {
int ch;
start = segPtr->body.chars;
end = start + segPtr->size;
for (p = start; p < end; p += offset) {
if (charIndex == 0) {
indexPtr->priv.byteIndex = index;
return indexPtr;
}
charIndex -= 1;
offset = TkUtfToUniChar(p, &ch);
index += offset;
}
} else if (charIndex < segPtr->size) {
indexPtr->priv.byteIndex = index;
return indexPtr;
} else {
assert(segPtr->size == 1);
charIndex -= 1;
index += 1;
}
}
if (!(segPtr = segPtr->nextPtr)) {
TkTextIndexSetToLastChar(indexPtr);
return indexPtr;
}
}
indexPtr->priv.byteIndex = index;
return indexPtr;
}
static TkTextSegment *
IndexToSeg(
const TkTextIndex *indexPtr,
int *offsetPtr)
{
TkTextSection *sectionPtr;
TkTextSegment *segPtr;
TkTextLine *linePtr;
int index;
assert(indexPtr->priv.byteIndex >= 0);
assert(indexPtr->priv.byteIndex < indexPtr->priv.linePtr->size);
index = indexPtr->priv.byteIndex;
linePtr = indexPtr->priv.linePtr;
if (index == 0) {
segPtr = linePtr->segPtr;
while (segPtr->size == 0) {
segPtr = segPtr->nextPtr;
}
if (offsetPtr) {
*offsetPtr = 0;
}
return segPtr;
}
if (index == linePtr->size - 1) {
assert(linePtr->lastPtr->typePtr == &tkTextCharType);
if (offsetPtr) {
*offsetPtr = linePtr->lastPtr->size - 1;
}
return linePtr->lastPtr;
}
sectionPtr = linePtr->segPtr->sectionPtr;
for ( ; index >= sectionPtr->size; sectionPtr = sectionPtr->nextPtr) {
index -= sectionPtr->size;
assert(sectionPtr->nextPtr);
}
for (segPtr = sectionPtr->segPtr; index >= segPtr->size; segPtr = segPtr->nextPtr) {
index -= segPtr->size;
assert(segPtr->nextPtr);
}
assert(segPtr->size > 0);
if (offsetPtr) {
*offsetPtr = index;
}
return segPtr;
}
static int
SegToIndex(
const TkTextLine *linePtr,
const TkTextSegment *segPtr)
{
const TkTextSection *sectionPtr;
const TkTextSegment *segPtr2;
int offset;
assert(segPtr->sectionPtr);
assert(segPtr->sectionPtr->linePtr == linePtr);
sectionPtr = linePtr->segPtr->sectionPtr;
if (segPtr == sectionPtr->segPtr) {
return 0;
}
if (segPtr == linePtr->lastPtr) {
return linePtr->size - segPtr->size;
}
offset = 0;
for ( ; sectionPtr != segPtr->sectionPtr; sectionPtr = sectionPtr->nextPtr) {
offset += sectionPtr->size;
assert(sectionPtr->nextPtr);
}
for (segPtr2 = segPtr->sectionPtr->segPtr; segPtr2 != segPtr; segPtr2 = segPtr2->nextPtr) {
offset += segPtr2->size;
assert(segPtr2->nextPtr);
}
return offset;
}
int
TkTextSegToIndex(
const TkTextSegment *segPtr)
{
return SegToIndex(segPtr->sectionPtr->linePtr, segPtr);
}
int
TkTextGetIndex(
Tcl_Interp *interp,
TkText *textPtr,
const char *string,
TkTextIndex *indexPtr)
{
assert(textPtr);
return GetIndex(interp, textPtr->sharedTextPtr, textPtr, string, indexPtr) ? TCL_OK : TCL_ERROR;
}
static unsigned
SkipSegments(
TkTextSegment *startMarkPtr)
{
TkTextSegment *segPtr = startMarkPtr->sectionPtr->linePtr->segPtr;
unsigned charIndex = 0;
for ( ; segPtr != startMarkPtr; segPtr = segPtr->nextPtr) {
if (segPtr->tagInfoPtr) {
charIndex += (segPtr->typePtr == &tkTextCharType) ? CountCharsInSeg(segPtr) : 1;
}
}
return charIndex;
}
static bool
GetIndex(
Tcl_Interp *interp,
TkSharedText *sharedTextPtr,
TkText *textPtr,
const char *string,
TkTextIndex *indexPtr)
{
char *p, *end, *endOfBase;
TkTextIndex first, last;
char c;
const char *cp;
char *myString;
Tcl_DString copy;
bool tagFound;
bool skipMark;
bool result;
assert(textPtr);
assert(sharedTextPtr);
#if BEGIN_DOES_NOT_BELONG_TO_BASE
if (string[0] == 'b' && strncmp(string, "begin", 5)) {
if (TkTextMarkNameToIndex(textPtr, string, indexPtr)
|| TkTextWindowIndex(textPtr, string, indexPtr)
|| TkTextImageIndex(textPtr, string, indexPtr)) {
return true;
}
}
#endif
TkTextIndexClear(indexPtr, textPtr);
Tcl_DStringInit(©);
myString = Tcl_DStringAppend(©, string, -1);
p = strrchr(myString, '.');
skipMark = false;
if (p) {
TkTextSearch search;
TkTextTag *tagPtr;
Tcl_HashEntry *hPtr = NULL;
const char *tagName;
bool wantLast;
if (p[1] == 'f' && strncmp(p + 1, "first", 5) == 0) {
wantLast = false;
endOfBase = p + 6;
} else if (p[1] == 'l' && strncmp(p + 1, "last", 4) == 0) {
wantLast = true;
endOfBase = p + 5;
} else {
goto tryxy;
}
if (TkTextMarkNameToIndex(textPtr, string, indexPtr)) {
Tcl_DStringFree(©);
return true;
}
skipMark = true;
tagPtr = NULL;
tagName = myString;
if (p - tagName == 3 && strncmp(tagName, "sel", 3) == 0) {
tagPtr = textPtr->selTagPtr;
} else {
*p = '\0';
hPtr = Tcl_FindHashEntry(&sharedTextPtr->tagTable, tagName);
*p = '.';
if (hPtr) {
tagPtr = Tcl_GetHashValue(hPtr);
}
}
if (!tagPtr) {
goto tryxy;
}
TkTextIndexSetupToStartOfText(&first, textPtr, sharedTextPtr->tree);
TkTextIndexSetupToEndOfText(&last, textPtr, sharedTextPtr->tree);
if (wantLast) {
TkBTreeStartSearchBack(&last, &first, tagPtr, &search, SEARCH_EITHER_TAGON_TAGOFF);
tagFound = TkBTreePrevTag(&search);
} else {
TkBTreeStartSearch(&first, &last, tagPtr, &search, SEARCH_NEXT_TAGON);
tagFound = TkBTreeNextTag(&search);
}
if (!tagFound) {
if (tagPtr == textPtr->selTagPtr) {
tagName = "sel";
} else if (hPtr) {
tagName = Tcl_GetHashKey(&sharedTextPtr->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 false;
}
*indexPtr = search.curIndex;
goto gotBase;
}
tryxy:
if (string[0] == '@') {
int x, y;
cp = string + 1;
if (*cp == 'f' && strncmp(cp, "first,", 6) == 0) {
x = TkTextGetFirstXPixel(textPtr);
end = (char *) cp + 5;
} else if (*cp == 'l' && strncmp(cp, "last,", 5) == 0) {
x = TkTextGetLastXPixel(textPtr);
end = (char *) cp + 4;
} else {
x = strtol(cp, &end, 0);
if (end == cp || *end != ',') {
goto noBaseFound;
}
}
cp = end + 1;
if (*cp == 'f' && strcmp(cp, "first") == 0) {
y = TkTextGetFirstYPixel(textPtr);
end += 6;
} else if (*cp == 'l' && strcmp(cp, "last") == 0) {
y = TkTextGetLastYPixel(textPtr);
end += 5;
} else {
y = strtol(cp, &end, 0);
if (end == cp) {
goto noBaseFound;
}
}
TkTextPixelIndex(textPtr, x, y, indexPtr, NULL);
endOfBase = end;
goto gotBase;
}
if (isdigit(string[0]) || string[0] == '-') {
int lineIndex, charIndex;
lineIndex = strtol(string, &end, 0) - 1;
if (end == string || *end != '.') {
goto noBaseFound;
}
p = end + 1;
if (*p == 'e' && strncmp(p, "end", 3) == 0) {
charIndex = INT_MAX;
endOfBase = p + 3;
} else if (*p == 'b' && strncmp(p, "begin", 5) == 0) {
charIndex = 0;
endOfBase = p + 5;
} else {
charIndex = strtol(p, &end, 0);
if (end == p) {
goto noBaseFound;
}
endOfBase = end;
}
if (lineIndex == 0 && textPtr->startMarker != sharedTextPtr->startMarker) {
charIndex += SkipSegments(textPtr->startMarker);
}
TkTextMakeCharIndex(sharedTextPtr->tree, textPtr, lineIndex, charIndex, indexPtr);
goto gotBase;
}
for (p = myString; *p != 0; ++p) {
if (isspace(*p) || *p == '+' || *p == '-') {
break;
}
}
endOfBase = p;
if (string[0] == '.') {
c = *endOfBase;
*endOfBase = '\0';
result = TkTextWindowIndex(textPtr, myString, indexPtr);
*endOfBase = c;
if (result) {
goto gotBase;
}
}
if (string[0] == 'b' && endOfBase - myString == 5 && strncmp(string, "begin", 5) == 0) {
TkTextIndexSetupToStartOfText(indexPtr, textPtr, sharedTextPtr->tree);
goto gotBase;
}
if (string[0] == 'e' && endOfBase - myString == 3 && strncmp(string, "end", 3) == 0) {
TkTextIndexSetupToEndOfText(indexPtr, textPtr, sharedTextPtr->tree);
goto gotBase;
}
c = *endOfBase;
*endOfBase = '\0';
result = TkTextMarkNameToIndex(textPtr, myString, indexPtr);
if (result) {
*endOfBase = c;
goto gotBase;
}
result = TkTextImageIndex(textPtr, myString, indexPtr);
*endOfBase = c;
if (result) {
goto gotBase;
}
noBaseFound:
if ((!skipMark && TkTextMarkNameToIndex(textPtr, string, indexPtr))
|| TkTextWindowIndex(textPtr, string, indexPtr)
|| TkTextImageIndex(textPtr, string, indexPtr)) {
Tcl_DStringFree(©);
return true;
}
Tcl_DStringFree(©);
Tcl_ResetResult(interp);
Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad text index \"%s\"", string));
Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_INDEX", NULL);
return false;
gotBase:
cp = endOfBase;
while (true) {
while (isspace(*cp)) {
cp++;
}
if (*cp == '\0') {
break;
}
if (*cp == '+' || *cp == '-') {
cp = ForwBack(textPtr, cp, indexPtr);
} else {
cp = StartEnd(textPtr, cp, indexPtr);
}
if (!cp) {
goto noBaseFound;
}
}
Tcl_DStringFree(©);
return true;
}
int
TkTextIndexPrint(
const TkSharedText *sharedTextPtr,
const TkText *textPtr,
const TkTextIndex *indexPtr,
char *string)
{
const TkTextSegment *segPtr;
const TkTextLine *linePtr;
const TkTextSegment *startMarker;
int charIndex;
assert(sharedTextPtr);
assert(indexPtr);
assert(string);
assert(CheckLine(indexPtr, indexPtr->priv.linePtr));
assert(CheckByteIndex(indexPtr, indexPtr->priv.linePtr, -1));
charIndex = 0;
linePtr = indexPtr->priv.linePtr;
startMarker = textPtr ? textPtr->startMarker : sharedTextPtr->startMarker;
segPtr = (linePtr == startMarker->sectionPtr->linePtr) ? startMarker : linePtr->segPtr;
if (indexPtr->priv.segPtr && !indexPtr->priv.isCharSegment) {
TkTextSegment *lastPtr = indexPtr->priv.segPtr;
assert(indexPtr->priv.segPtr->typePtr);
while (lastPtr->size == 0) {
lastPtr = lastPtr->nextPtr;
}
for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
if (segPtr->typePtr == &tkTextCharType) {
charIndex += CountCharsInSeg(segPtr);
} else {
assert(segPtr->size <= 1);
charIndex += segPtr->size;
}
assert(segPtr->nextPtr);
}
} else {
int numBytes = TkTextIndexGetByteIndex(indexPtr);
if (segPtr == startMarker && startMarker != sharedTextPtr->startMarker) {
numBytes -= TkTextSegToIndex(startMarker);
}
assert(numBytes >= 0);
assert(numBytes < linePtr->size);
for ( ; numBytes > segPtr->size; segPtr = segPtr->nextPtr) {
if (segPtr->typePtr == &tkTextCharType) {
charIndex += CountCharsInSeg(segPtr);
} else {
assert(segPtr->size <= 1);
charIndex += segPtr->size;
}
numBytes -= segPtr->size;
assert(segPtr->nextPtr);
}
if (numBytes) {
if (segPtr->typePtr == &tkTextCharType) {
charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
} else {
assert(segPtr->size <= 1);
charIndex += numBytes;
}
}
}
return snprintf(string, TK_POS_CHARS, "%d.%d",
TkBTreeLinesTo(indexPtr->tree, textPtr, linePtr, NULL) + 1, charIndex);
}
static const char *
ForwBack(
TkText *textPtr,
const char *string,
TkTextIndex *indexPtr)
{
const char *p, *units;
char *end;
int count, lineIndex, modifier;
size_t length;
assert(textPtr);
p = string + 1;
while (isspace(*p)) {
p++;
}
count = strtol(p, &end, 0);
if (end == p) {
return NULL;
}
p = end;
while (isspace(*p)) {
p++;
}
units = p;
while (*p != '\0' && !isspace(*p) && *p != '+' && *p != '-') {
p++;
}
length = p - units;
if (*units == 'd' && strncmp(units, "display", MIN(length, 7)) == 0) {
modifier = TKINDEX_DISPLAY;
if (length > 7) {
p -= (length - 7);
}
} else if (*units == 'a' && strncmp(units, "any", MIN(length, 3)) == 0) {
modifier = TKINDEX_CHAR;
if (length > 3) {
p -= (length - 3);
}
} else {
modifier = TKINDEX_NONE;
}
if (modifier != TKINDEX_NONE) {
while (isspace(*p)) {
p++;
}
units = p;
while (*p != '\0' && !isspace(*p) && *p != '+' && *p != '-') {
p++;
}
length = p - units;
}
if (*units == 'c' && strncmp(units, "chars", length) == 0) {
TkTextCountType type;
if (modifier == TKINDEX_DISPLAY) {
type = COUNT_DISPLAY_CHARS;
} else {
assert(modifier == TKINDEX_NONE || modifier == TKINDEX_CHAR);
type = COUNT_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) {
int xOffset;
bool forward;
if (TkTextIsElided(indexPtr)) {
TkTextSkipElidedRegion(indexPtr);
}
if (count == 0) {
return p;
}
forward = (count < 0) == (*string == '-');
count = abs(count);
if (!forward) {
count = -count;
}
TkTextFindDisplayIndex(textPtr, indexPtr, count, &xOffset);
TkTextIndexOfX(textPtr, xOffset, indexPtr);
if (TkTextIsElided(indexPtr)) {
TkTextSkipElidedRegion(indexPtr);
}
} else {
lineIndex = TkBTreeLinesTo(indexPtr->tree, textPtr, indexPtr->priv.linePtr, NULL);
if (*string == '+') {
lineIndex += count;
} else {
lineIndex -= count;
if (lineIndex < 0) {
lineIndex = 0;
}
}
if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
indexPtr->priv.byteIndex += TkTextSegToIndex(textPtr->startMarker);
}
TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex, indexPtr->priv.byteIndex, indexPtr);
}
} else {
return NULL;
}
return p;
}
int
TkTextIndexForwBytes(
const TkText *textPtr,
const TkTextIndex *srcPtr,
int byteCount,
TkTextIndex *dstPtr)
{
TkTextLine *linePtr;
int byteIndex;
if (byteCount == 0) {
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
return 0;
}
if (byteCount < 0) {
TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr);
return 0;
}
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
TkTextIndexToByteIndex(dstPtr);
linePtr = TkTextIndexGetLine(dstPtr);
if (textPtr) {
if (linePtr == TkBTreeGetLastLine(textPtr)) {
return 1;
}
if (textPtr->endMarker->sectionPtr->linePtr == linePtr) {
int lineLength = SegToIndex(linePtr, textPtr->endMarker);
if ((byteIndex = (dstPtr->priv.byteIndex += byteCount)) > lineLength) {
assert(linePtr->nextPtr);
TkTextIndexSetByteIndex2(dstPtr, linePtr->nextPtr, 0);
}
return byteIndex <= lineLength ? 0 : 1;
}
} else if (!linePtr->nextPtr) {
return 1;
}
if ((byteIndex = dstPtr->priv.byteIndex + byteCount) > linePtr->size) {
DEBUG(TkTextIndex index = *srcPtr);
bool rc = TkBTreeMoveForward(dstPtr, byteCount);
assert(!rc || TkTextIndexCountBytes(&index, dstPtr) == byteCount);
return rc ? 0 : 1;
}
if (byteIndex == linePtr->size) {
assert(linePtr->nextPtr);
TkTextIndexSetByteIndex2(dstPtr, linePtr->nextPtr, 0);
} else {
TkTextIndexSetByteIndex(dstPtr, byteIndex);
}
return 0;
}
bool
TkTextIndexForwChars(
const TkText *textPtr,
const TkTextIndex *srcPtr,
int charCount,
TkTextIndex *dstPtr,
TkTextCountType type)
{
TkTextLine *linePtr;
TkTextSegment *segPtr;
TkTextSegment *endPtr;
TkSharedText *sharedTextPtr;
int byteOffset;
bool checkElided;
bool trimmed;
bool skipSpaces;
if (charCount < 0) {
return TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type);
}
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
if (TkTextIndexIsEndOfText(dstPtr)) {
return false;
}
sharedTextPtr = TkTextIndexGetShared(srcPtr);
checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(sharedTextPtr);
if (checkElided && TkTextIsElided(dstPtr) && !TkTextSkipElidedRegion(dstPtr)) {
return false;
}
if (charCount == 0) {
return false;
}
assert(dstPtr->priv.byteIndex <= FindEndByteIndex(dstPtr));
segPtr = TkTextIndexGetFirstSegment(dstPtr, &byteOffset);
endPtr = textPtr ? textPtr->endMarker : sharedTextPtr->endMarker;
TkTextIndexToByteIndex(dstPtr);
trimmed = textPtr && textPtr->spaceMode == TEXT_SPACEMODE_TRIM && !!(type & COUNT_DISPLAY);
skipSpaces = false;
while (true) {
for ( ; segPtr; segPtr = segPtr->nextPtr) {
if (segPtr->tagInfoPtr) {
if (segPtr->typePtr == &tkTextCharType) {
const char *start = segPtr->body.chars + byteOffset;
const char *end = segPtr->body.chars + segPtr->size;
const char *p = start;
int ch, n;
for (p = start; p < end; p += n) {
if (charCount <= 0) {
if (skipSpaces) {
while (*p == ' ') {
++p;
}
if (p == end) {
break;
}
}
dstPtr->priv.byteIndex += (p - start);
goto forwardCharDone;
}
n = TkUtfToUniChar(p, &ch);
if (ch == ' ') {
if (!skipSpaces) {
skipSpaces = trimmed;
charCount -= 1;
}
} else {
skipSpaces = false;
charCount -= (type & COUNT_INDICES) ? n : 1;
}
}
} else if (type & COUNT_INDICES) {
assert(byteOffset == 0);
assert(segPtr->size <= 1);
if (charCount < segPtr->size) {
dstPtr->priv.byteIndex += charCount;
dstPtr->priv.segPtr = segPtr;
dstPtr->priv.isCharSegment = false;
goto forwardCharDone;
}
charCount -= segPtr->size;
}
dstPtr->priv.byteIndex += segPtr->size - byteOffset;
byteOffset = 0;
} else if (checkElided && segPtr->typePtr == &tkTextBranchType) {
TkTextIndexSetSegment(dstPtr, segPtr = segPtr->body.branch.nextPtr);
if (TkTextIndexRestrictToEndRange(dstPtr) >= 0) {
goto forwardCharDone;
}
TkTextIndexToByteIndex(dstPtr);
} else if (segPtr == endPtr) {
if (charCount > 0) {
TkTextIndexSetupToEndOfText(dstPtr, (TkText *) textPtr, srcPtr->tree);
}
goto forwardCharDone;
}
}
if (!(linePtr = TkBTreeNextLine(textPtr, dstPtr->priv.linePtr))) {
TkTextIndexSetToLastChar(dstPtr);
goto forwardCharDone;
}
dstPtr->priv.linePtr = linePtr;
dstPtr->priv.byteIndex = 0;
dstPtr->priv.lineNo = -1;
dstPtr->priv.lineNoRel = -1;
segPtr = linePtr->segPtr;
}
forwardCharDone:
dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
return true;
}
bool
TkTextSkipElidedRegion(
TkTextIndex *indexPtr)
{
TkTextSegment *segPtr;
assert(indexPtr->textPtr);
assert(TkTextIsElided(indexPtr));
segPtr = TkBTreeFindEndOfElidedRange(indexPtr->textPtr->sharedTextPtr,
indexPtr->textPtr, TkTextIndexGetContentSegment(indexPtr, NULL));
TkTextIndexSetSegment(indexPtr, segPtr);
return !TkTextIndexIsEndOfText(indexPtr);
}
unsigned
TkTextIndexCountBytes(
const TkTextIndex *indexPtr1,
const TkTextIndex *indexPtr2)
{
int byteCount;
TkTextLine *linePtr;
assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
if (indexPtr1->priv.linePtr == indexPtr2->priv.linePtr) {
return TkTextIndexGetByteIndex(indexPtr2) - TkTextIndexGetByteIndex(indexPtr1);
}
linePtr = indexPtr1->priv.linePtr;
byteCount = linePtr->size - TkTextIndexGetByteIndex(indexPtr1);
byteCount += TkTextIndexGetByteIndex(indexPtr2);
byteCount += TkBTreeCountSize(indexPtr1->tree, NULL, linePtr->nextPtr, indexPtr2->priv.linePtr);
return byteCount;
}
Tcl_UniChar
TkTextIndexGetChar(
const TkTextIndex *indexPtr)
{
TkTextSegment *segPtr;
int byteOffset;
int ch;
segPtr = TkTextIndexGetContentSegment(indexPtr, &byteOffset);
TkUtfToUniChar(segPtr->body.chars + byteOffset, &ch);
return ch;
}
unsigned
TkTextIndexCount(
const TkText *textPtr,
const TkTextIndex *indexPtr1,
const TkTextIndex *indexPtr2,
TkTextCountType type)
{
TkTextLine *linePtr;
TkTextIndex index;
TkTextSegment *segPtr, *lastPtr;
int byteOffset, maxBytes;
unsigned count;
bool checkElided;
assert(textPtr);
assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(textPtr->sharedTextPtr);
index = *indexPtr1;
if (checkElided
&& TkTextIsElided(&index)
&& (!TkTextSkipElidedRegion(&index) || TkTextIndexCompare(&index, indexPtr2) >= 0)) {
return 0;
}
segPtr = TkTextIndexGetContentSegment(&index, &byteOffset);
lastPtr = TkTextIndexGetContentSegment(indexPtr2, &maxBytes);
linePtr = index.priv.linePtr;
count = 0;
if (byteOffset > 0) {
if (segPtr->tagInfoPtr) {
if (segPtr->typePtr == &tkTextCharType) {
if (type & (COUNT_INDICES|COUNT_TEXT)) {
count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset,
(segPtr == lastPtr ? maxBytes : segPtr->size) - byteOffset);
}
} else if (segPtr->typePtr == &tkTextHyphenType) {
if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
count += 1;
}
} else if (type & COUNT_INDICES) {
assert(segPtr->size == 1);
count += 1;
}
}
if (segPtr == lastPtr) {
return count;
}
segPtr = segPtr->nextPtr;
}
if (maxBytes > 0 && (!checkElided || !TkTextSegmentIsElided(textPtr, lastPtr))) {
if (lastPtr->typePtr == &tkTextCharType) {
if (type & (COUNT_TEXT|COUNT_INDICES)) {
count += Tcl_NumUtfChars(lastPtr->body.chars, maxBytes);
}
} else if (lastPtr->typePtr == &tkTextHyphenType) {
if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
count += 1;
}
} else if (type & COUNT_INDICES) {
assert(segPtr->size <= 1);
count += 1;
}
}
while (true) {
for ( ; segPtr; segPtr = segPtr->nextPtr) {
if (segPtr == lastPtr) {
return count;
}
if (segPtr->tagInfoPtr) {
if (segPtr->typePtr == &tkTextCharType) {
if (type & (COUNT_TEXT|COUNT_INDICES)) {
count += CountCharsInSeg(segPtr);
}
} else if (segPtr->typePtr == &tkTextHyphenType) {
if (type & (COUNT_HYPHENS|COUNT_INDICES)) {
count += CountCharsInSeg(segPtr);
}
} else if (type & COUNT_INDICES) {
assert(segPtr->size == 1);
count += 1;
}
} else if (checkElided && segPtr->typePtr == &tkTextBranchType) {
TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
if (TkTextIndexCompare(&index, indexPtr2) >= 0) {
return count;
}
linePtr = TkTextIndexGetLine(&index);
}
}
linePtr = TkBTreeNextLine(textPtr, linePtr);
assert(linePtr);
segPtr = linePtr->segPtr;
}
return 0;
}
int
TkTextIndexBackBytes(
const TkText *textPtr,
const TkTextIndex *srcPtr,
int byteCount,
TkTextIndex *dstPtr)
{
TkTextLine *linePtr;
int byteIndex;
if (byteCount == 0) {
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
return 0;
}
if (byteCount < 0) {
return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr);
}
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
byteIndex = TkTextIndexGetByteIndex(dstPtr);
linePtr = TkTextIndexGetLine(dstPtr);
if (textPtr
&& linePtr == textPtr->startMarker->sectionPtr->linePtr
&& textPtr->startMarker != textPtr->sharedTextPtr->startMarker) {
if ((byteIndex -= byteCount) < SegToIndex(linePtr, textPtr->startMarker)) {
TkTextIndexSetupToStartOfText(dstPtr, (TkText *) textPtr, textPtr->sharedTextPtr->tree);
return 1;
}
TkTextIndexSetByteIndex(dstPtr, byteIndex);
return 0;
}
if (byteCount > byteIndex + 1) {
DEBUG(TkTextIndex index = *srcPtr);
bool rc = TkBTreeMoveBackward(dstPtr, byteCount);
assert(!rc || TkTextIndexCountBytes(dstPtr, &index) == byteCount);
return rc ? 0 : 1;
}
if ((byteIndex -= byteCount) >= 0) {
TkTextIndexSetByteIndex(dstPtr, byteIndex);
return 0;
}
if (!(linePtr = TkBTreePrevLine(textPtr, linePtr))) {
TkTextIndexSetToStartOfLine(dstPtr);
return 1;
}
TkTextIndexSetToLastChar2(dstPtr, linePtr);
return 0;
}
bool
TkTextIndexBackChars(
const TkText *textPtr,
const TkTextIndex *srcPtr,
int charCount,
TkTextIndex *dstPtr,
TkTextCountType type)
{
TkSharedText *sharedTextPtr;
TkTextSegment *segPtr;
TkTextSegment *startPtr;
TkTextLine *linePtr;
int segSize;
bool checkElided;
bool trimmed;
bool skipSpaces;
int byteIndex;
assert(textPtr || !(type & COUNT_DISPLAY));
if (charCount < 0) {
return TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type);
}
if (dstPtr != srcPtr) {
*dstPtr = *srcPtr;
}
if (charCount == 0) {
return false;
}
sharedTextPtr = TkTextIndexGetShared(srcPtr);
checkElided = !!(type & COUNT_DISPLAY) && TkBTreeHaveElidedSegments(textPtr->sharedTextPtr);
if (checkElided && TkTextIsElided(dstPtr) && !TkTextSkipElidedRegion(dstPtr)) {
return false;
}
if (TkTextIndexIsStartOfLine(dstPtr) && !TkBTreePrevLine(textPtr, dstPtr->priv.linePtr)) {
return false;
}
if (textPtr && textPtr->endMarker != sharedTextPtr->endMarker) {
linePtr = TkBTreeGetLastLine(textPtr);
if (dstPtr->priv.linePtr == linePtr && linePtr != textPtr->endMarker->sectionPtr->linePtr) {
if (--charCount == 0) {
byteIndex = TkTextSegToIndex(textPtr->endMarker);
dstPtr->priv.linePtr = textPtr->endMarker->sectionPtr->linePtr;
dstPtr->priv.segPtr = NULL;
dstPtr->priv.lineNo = dstPtr->priv.lineNoRel = -1;
goto backwardCharDone;
}
TkTextIndexSetSegment(dstPtr, textPtr->endMarker);
}
}
segPtr = TkTextIndexGetFirstSegment(dstPtr, &segSize);
startPtr = textPtr ? textPtr->startMarker : sharedTextPtr->startMarker;
byteIndex = TkTextIndexGetByteIndex(dstPtr);
dstPtr->priv.segPtr = NULL;
trimmed = textPtr && textPtr->spaceMode == TEXT_SPACEMODE_TRIM && !!(type & COUNT_DISPLAY);
skipSpaces = false;
while (true) {
if (segPtr->tagInfoPtr) {
if (segPtr->typePtr == &tkTextCharType) {
const char *start = segPtr->body.chars;
const char *end = segPtr->body.chars + segSize;
const char *p = end;
while (true) {
const char *q;
if (charCount <= 0) {
if (skipSpaces) {
while (p > start && p[-1] == ' ') {
--p;
}
if (p == start) {
break;
}
}
byteIndex -= (end - p);
goto backwardCharDone;
}
if (p == start) {
break;
}
p = Tcl_UtfPrev(q = p, start);
if (*p == ' ') {
if (!skipSpaces) {
skipSpaces = trimmed;
charCount -= 1;
}
} else {
skipSpaces = false;
charCount -= (type & COUNT_INDICES) ? q - p : 1;
}
}
} else if (type & COUNT_INDICES) {
assert(segPtr->size <= 1);
if (charCount <= segSize) {
byteIndex -= charCount;
dstPtr->priv.segPtr = segPtr;
dstPtr->priv.isCharSegment = false;
goto backwardCharDone;
}
charCount -= segSize;
}
} else if (checkElided && segPtr->typePtr == &tkTextLinkType) {
TkTextIndexSetSegment(dstPtr, segPtr = segPtr->body.link.prevPtr);
dstPtr->priv.segPtr = segPtr;
dstPtr->priv.isCharSegment = false;
if (TkTextIndexRestrictToStartRange(dstPtr) <= 0) {
dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
return true;
}
TkTextIndexToByteIndex(dstPtr);
byteIndex = TkTextIndexGetByteIndex(dstPtr);
} else if (segPtr == startPtr) {
TkTextIndexSetSegment(dstPtr, segPtr = startPtr);
byteIndex = TkTextIndexGetByteIndex(dstPtr);
goto backwardCharDone;
}
if (!(segPtr = segPtr->prevPtr)) {
linePtr = TkBTreePrevLine(textPtr, dstPtr->priv.linePtr);
assert(linePtr);
dstPtr->priv.linePtr = linePtr;
dstPtr->priv.lineNo = -1;
dstPtr->priv.lineNoRel = -1;
byteIndex = linePtr->size;
segPtr = linePtr->lastPtr;
} else {
byteIndex -= segSize;
}
segSize = segPtr->size;
}
backwardCharDone:
dstPtr->stateEpoch = TkBTreeEpoch(dstPtr->tree);
dstPtr->priv.byteIndex = byteIndex;
return true;
}
static const char *
StartEnd(
TkText *textPtr,
const char *string,
TkTextIndex *indexPtr)
{
const char *p;
size_t length;
TkTextSegment *segPtr;
int modifier;
int mode;
assert(textPtr);
for (p = string; isalnum(*p); ++p) {
}
length = p - string;
if (*string == 'd' && strncmp(string, "display", MIN(length, 7)) == 0) {
modifier = TKINDEX_DISPLAY;
mode = COUNT_DISPLAY_INDICES;
if (length > 7) {
p -= (length - 7);
}
} else if (*string == 'a' && strncmp(string, "any", MIN(length, 3)) == 0) {
modifier = TKINDEX_CHAR;
mode = COUNT_CHARS;
if (length > 3) {
p -= (length - 3);
}
} else {
modifier = TKINDEX_NONE;
mode = COUNT_INDICES;
}
if (modifier != TKINDEX_NONE) {
while (isspace(*p)) {
++p;
}
string = p;
while (*p && !isspace(*p) && *p != '+' && *p != '-') {
++p;
}
length = p - string;
}
if (*string == 'l' && strncmp(string, "lineend", length) == 0 && length >= 5) {
if (modifier == TKINDEX_DISPLAY) {
TkTextFindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_END);
} else {
TkTextIndexSetToLastChar(indexPtr);
}
} else if (*string == 'l' && strncmp(string, "linestart", length) == 0 && length >= 5) {
if (modifier == TKINDEX_DISPLAY) {
TkTextFindDisplayLineStartEnd(textPtr, indexPtr, DISP_LINE_START);
} else {
TkTextIndexSetToStartOfLine(indexPtr);
}
} else if (*string == 'w' && strncmp(string, "wordend", length) == 0 && length >= 5) {
int firstChar = 1;
int offset;
if (modifier == TKINDEX_DISPLAY) {
TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, COUNT_DISPLAY_INDICES);
}
segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
while (true) {
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->priv.byteIndex += chSize;
if (offset >= segPtr->size) {
do {
segPtr = segPtr->nextPtr;
} while (segPtr->size == 0);
offset = 0;
}
}
if (firstChar) {
TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, mode);
}
} 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);
}
segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
while (true) {
int chSize = 1;
if (segPtr->typePtr == &tkTextCharType) {
int ch;
TkUtfToUniChar(segPtr->body.chars + offset, &ch);
if (!Tcl_UniCharIsWordChar(ch)) {
break;
}
if (offset > 0) {
const char *prevPtr = Tcl_UtfPrev(segPtr->body.chars + offset, segPtr->body.chars);
chSize = segPtr->body.chars + offset - prevPtr;
}
firstChar = 0;
}
if (offset == 0) {
TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, mode);
segPtr = TkTextIndexGetContentSegment(indexPtr, &offset);
if (offset < chSize && indexPtr->priv.byteIndex == 0) {
return p;
}
} else if ((indexPtr->priv.byteIndex -= chSize) == 0) {
return p;
} else if ((offset -= chSize) < 0) {
assert(indexPtr->priv.byteIndex > 0);
do {
segPtr = segPtr->prevPtr;
assert(segPtr);
} while (segPtr->size == 0);
offset = 0;
}
}
if (!firstChar) {
TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, mode);
}
} else {
p = NULL;
}
return p;
}