2480 lines
65 KiB
C
2480 lines
65 KiB
C
/*
|
||
* tkTextIndex.c --
|
||
*
|
||
* This module provides functions that manipulate indices for text
|
||
* widgets.
|
||
*
|
||
* Copyright (c) 1992-1994 The Regents of the University of California.
|
||
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
|
||
*
|
||
* See the file "license.terms" for information on usage and redistribution of
|
||
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
||
*/
|
||
|
||
#include "tkInt.h"
|
||
#include "tkText.h"
|
||
#include "default.h"
|
||
|
||
/*
|
||
* Index to use to select last character in line (very large integer):
|
||
*/
|
||
|
||
#define LAST_CHAR 1000000
|
||
|
||
/*
|
||
* Modifiers for index parsing: 'display', 'any' or nothing.
|
||
*/
|
||
|
||
#define TKINDEX_NONE 0
|
||
#define TKINDEX_DISPLAY 1
|
||
#define TKINDEX_ANY 2
|
||
|
||
/*
|
||
* Forward declarations for functions defined later in this file:
|
||
*/
|
||
|
||
static const char * ForwBack(TkText *textPtr, const char *string,
|
||
TkTextIndex *indexPtr);
|
||
static const char * StartEnd(TkText *textPtr, const char *string,
|
||
TkTextIndex *indexPtr);
|
||
static int GetIndex(Tcl_Interp *interp, TkSharedText *sharedPtr,
|
||
TkText *textPtr, const char *string,
|
||
TkTextIndex *indexPtr, int *canCachePtr);
|
||
static int IndexCountBytesOrdered(const TkText *textPtr,
|
||
const TkTextIndex *indexPtr1,
|
||
const TkTextIndex *indexPtr2);
|
||
|
||
/*
|
||
* The "textindex" Tcl_Obj definition:
|
||
*/
|
||
|
||
static void DupTextIndexInternalRep(Tcl_Obj *srcPtr,
|
||
Tcl_Obj *copyPtr);
|
||
static void FreeTextIndexInternalRep(Tcl_Obj *listPtr);
|
||
static void UpdateStringOfTextIndex(Tcl_Obj *objPtr);
|
||
|
||
/*
|
||
* Accessor macros for the "textindex" type.
|
||
*/
|
||
|
||
#define GET_TEXTINDEX(objPtr) \
|
||
((TkTextIndex *) (objPtr)->internalRep.twoPtrValue.ptr1)
|
||
#define GET_INDEXEPOCH(objPtr) \
|
||
(PTR2INT((objPtr)->internalRep.twoPtrValue.ptr2))
|
||
#define SET_TEXTINDEX(objPtr, indexPtr) \
|
||
((objPtr)->internalRep.twoPtrValue.ptr1 = (void *)(indexPtr))
|
||
#define SET_INDEXEPOCH(objPtr, epoch) \
|
||
((objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(epoch))
|
||
|
||
/*
|
||
* Define the 'textindex' object type, which Tk uses to represent indices in
|
||
* text widgets internally.
|
||
*/
|
||
|
||
const Tcl_ObjType tkTextIndexType = {
|
||
"textindex", /* name */
|
||
FreeTextIndexInternalRep, /* freeIntRepProc */
|
||
DupTextIndexInternalRep, /* dupIntRepProc */
|
||
NULL, /* updateStringProc */
|
||
NULL /* setFromAnyProc */
|
||
};
|
||
|
||
static void
|
||
FreeTextIndexInternalRep(
|
||
Tcl_Obj *indexObjPtr) /* TextIndex object with internal rep to
|
||
* free. */
|
||
{
|
||
TkTextIndex *indexPtr = GET_TEXTINDEX(indexObjPtr);
|
||
|
||
if (indexPtr->textPtr != NULL) {
|
||
if (indexPtr->textPtr->refCount-- <= 1) {
|
||
/*
|
||
* The text widget has been deleted and we need to free it now.
|
||
*/
|
||
|
||
ckfree(indexPtr->textPtr);
|
||
}
|
||
}
|
||
ckfree(indexPtr);
|
||
indexObjPtr->typePtr = NULL;
|
||
}
|
||
|
||
static void
|
||
DupTextIndexInternalRep(
|
||
Tcl_Obj *srcPtr, /* TextIndex obj with internal rep to copy. */
|
||
Tcl_Obj *copyPtr) /* TextIndex obj with internal rep to set. */
|
||
{
|
||
int epoch;
|
||
TkTextIndex *dupIndexPtr, *indexPtr;
|
||
|
||
dupIndexPtr = (TkTextIndex *)ckalloc(sizeof(TkTextIndex));
|
||
indexPtr = GET_TEXTINDEX(srcPtr);
|
||
epoch = GET_INDEXEPOCH(srcPtr);
|
||
|
||
dupIndexPtr->tree = indexPtr->tree;
|
||
dupIndexPtr->linePtr = indexPtr->linePtr;
|
||
dupIndexPtr->byteIndex = indexPtr->byteIndex;
|
||
dupIndexPtr->textPtr = indexPtr->textPtr;
|
||
if (dupIndexPtr->textPtr != NULL) {
|
||
dupIndexPtr->textPtr->refCount++;
|
||
}
|
||
SET_TEXTINDEX(copyPtr, dupIndexPtr);
|
||
SET_INDEXEPOCH(copyPtr, epoch);
|
||
copyPtr->typePtr = &tkTextIndexType;
|
||
}
|
||
|
||
/*
|
||
* This will not be called except by TkTextNewIndexObj below. This is because
|
||
* if a TkTextIndex is no longer valid, it is not possible to regenerate the
|
||
* string representation.
|
||
*/
|
||
|
||
static 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;
|
||
indexPtr->textPtr = textPtr;
|
||
|
||
if (textPtr != NULL) {
|
||
textPtr->refCount++;
|
||
SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch);
|
||
} else {
|
||
SET_INDEXEPOCH(objPtr, 0);
|
||
}
|
||
return indexPtr;
|
||
}
|
||
|
||
const TkTextIndex *
|
||
TkTextGetIndexFromObj(
|
||
Tcl_Interp *interp, /* Use this for error reporting. */
|
||
TkText *textPtr, /* Information about text widget. */
|
||
Tcl_Obj *objPtr) /* Object containing description of
|
||
* position. */
|
||
{
|
||
TkTextIndex index;
|
||
TkTextIndex *indexPtr = NULL;
|
||
int cache;
|
||
|
||
if (objPtr->typePtr == &tkTextIndexType) {
|
||
int epoch;
|
||
|
||
indexPtr = GET_TEXTINDEX(objPtr);
|
||
epoch = GET_INDEXEPOCH(objPtr);
|
||
|
||
if (epoch == textPtr->sharedTextPtr->stateEpoch) {
|
||
if (indexPtr->textPtr == textPtr) {
|
||
return indexPtr;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* The object is either not an index type or referred to a different text
|
||
* widget, or referred to the correct widget, but it is out of date (text
|
||
* has been added/deleted since).
|
||
*/
|
||
|
||
if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index,
|
||
&cache) != TCL_OK) {
|
||
return NULL;
|
||
}
|
||
|
||
if (objPtr->typePtr != NULL) {
|
||
if (objPtr->bytes == NULL) {
|
||
objPtr->typePtr->updateStringProc(objPtr);
|
||
}
|
||
if (objPtr->typePtr->freeIntRepProc != NULL) {
|
||
objPtr->typePtr->freeIntRepProc(objPtr);
|
||
}
|
||
}
|
||
|
||
return MakeObjIndex((cache ? textPtr : NULL), objPtr, &index);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextNewIndexObj --
|
||
*
|
||
* This function generates a Tcl_Obj description of an index, suitable
|
||
* for reading in again later. The index generated is effectively stable
|
||
* to all except insertion/deletion operations on the widget.
|
||
*
|
||
* Results:
|
||
* A new Tcl_Obj with refCount zero.
|
||
*
|
||
* Side effects:
|
||
* A small amount of memory is allocated.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
Tcl_Obj *
|
||
TkTextNewIndexObj(
|
||
TkText *textPtr, /* Text widget for this index */
|
||
const TkTextIndex *indexPtr)/* Pointer to index. */
|
||
{
|
||
Tcl_Obj *retVal;
|
||
|
||
retVal = Tcl_NewObj();
|
||
retVal->bytes = NULL;
|
||
|
||
/*
|
||
* Assumption that the above call returns an object with:
|
||
* retVal->typePtr == NULL
|
||
*/
|
||
|
||
MakeObjIndex(textPtr, retVal, indexPtr);
|
||
|
||
/*
|
||
* Unfortunately, it isn't possible for us to regenerate the string
|
||
* representation so we have to create it here, while we can be sure the
|
||
* contents of the index are still valid.
|
||
*/
|
||
|
||
UpdateStringOfTextIndex(retVal);
|
||
return retVal;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextMakePixelIndex --
|
||
*
|
||
* Given a pixel index and a byte index, look things up in the B-tree and
|
||
* fill in a TkTextIndex structure.
|
||
*
|
||
* The valid input range for pixelIndex is from 0 to the number of pixels
|
||
* in the widget-1. Anything outside that range will be rounded to the
|
||
* closest acceptable value.
|
||
*
|
||
* Results:
|
||
*
|
||
* The structure at *indexPtr is filled in with information about the
|
||
* character at pixelIndex (or the closest existing character, if the
|
||
* specified one doesn't exist), and the number of excess pixels is
|
||
* returned as a result. This means if the given pixel index is exactly
|
||
* correct for the top-edge of the indexPtr, then zero will be returned,
|
||
* and otherwise we will return the calculation 'desired pixelIndex' -
|
||
* 'actual pixel index of indexPtr'.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextMakePixelIndex(
|
||
TkText *textPtr, /* The Text Widget */
|
||
int pixelIndex, /* Pixel-index of desired line (0 means first
|
||
* pixel of first line of text). */
|
||
TkTextIndex *indexPtr) /* Structure to fill in. */
|
||
{
|
||
int pixelOffset = 0;
|
||
|
||
indexPtr->tree = textPtr->sharedTextPtr->tree;
|
||
indexPtr->textPtr = textPtr;
|
||
|
||
if (pixelIndex < 0) {
|
||
pixelIndex = 0;
|
||
}
|
||
indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
|
||
textPtr, pixelIndex, &pixelOffset);
|
||
|
||
/*
|
||
* 'pixelIndex' was too large, so we try again, just to find the last
|
||
* pixel in the window.
|
||
*/
|
||
|
||
if (indexPtr->linePtr == NULL) {
|
||
int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree,
|
||
textPtr)-1;
|
||
|
||
indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree,
|
||
textPtr, lastMinusOne, &pixelOffset);
|
||
indexPtr->byteIndex = 0;
|
||
return pixelOffset;
|
||
}
|
||
indexPtr->byteIndex = 0;
|
||
|
||
if (pixelOffset <= 0) {
|
||
return 0;
|
||
}
|
||
return TkTextMeasureDown(textPtr, indexPtr, pixelOffset);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextMakeByteIndex --
|
||
*
|
||
* Given a line index and a byte index, look things up in the B-tree and
|
||
* fill in a TkTextIndex structure.
|
||
*
|
||
* Results:
|
||
* The structure at *indexPtr is filled in with information about the
|
||
* character at lineIndex and byteIndex (or the closest existing
|
||
* character, if the specified one doesn't exist), and indexPtr is
|
||
* returned as result.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
TkTextIndex *
|
||
TkTextMakeByteIndex(
|
||
TkTextBTree tree, /* Tree that lineIndex and byteIndex refer
|
||
* to. */
|
||
const TkText *textPtr,
|
||
int lineIndex, /* Index of desired line (0 means first line
|
||
* of text). */
|
||
int byteIndex, /* Byte index of desired character. */
|
||
TkTextIndex *indexPtr) /* Structure to fill in. */
|
||
{
|
||
TkTextSegment *segPtr;
|
||
int index;
|
||
const char *p, *start;
|
||
int ch;
|
||
|
||
indexPtr->tree = tree;
|
||
if (lineIndex < 0) {
|
||
lineIndex = 0;
|
||
byteIndex = 0;
|
||
}
|
||
if (byteIndex < 0) {
|
||
byteIndex = 0;
|
||
}
|
||
indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex);
|
||
if (indexPtr->linePtr == NULL) {
|
||
indexPtr->linePtr = TkBTreeFindLine(tree, textPtr,
|
||
TkBTreeNumLines(tree, textPtr));
|
||
byteIndex = 0;
|
||
}
|
||
if (byteIndex == 0) {
|
||
indexPtr->byteIndex = byteIndex;
|
||
return indexPtr;
|
||
}
|
||
|
||
/*
|
||
* Verify that the index is within the range of the line and points to a
|
||
* valid character boundary.
|
||
*/
|
||
|
||
index = 0;
|
||
for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
|
||
if (segPtr == NULL) {
|
||
/*
|
||
* Use the index of the last character in the line. Since the last
|
||
* character on the line is guaranteed to be a '\n', we can back
|
||
* up a constant sizeof(char) bytes.
|
||
*/
|
||
|
||
indexPtr->byteIndex = index - sizeof(char);
|
||
break;
|
||
}
|
||
if (index + segPtr->size > byteIndex) {
|
||
indexPtr->byteIndex = byteIndex;
|
||
if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) {
|
||
/*
|
||
* Prevent UTF-8 character from being split up by ensuring
|
||
* that byteIndex falls on a character boundary. If the index
|
||
* falls in the middle of a UTF-8 character, it will be
|
||
* adjusted to the end of that UTF-8 character.
|
||
*/
|
||
|
||
start = segPtr->body.chars + (byteIndex - index);
|
||
p = TkUtfPrev(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 < segPtr->size) {
|
||
indexPtr->byteIndex = index;
|
||
break;
|
||
}
|
||
charIndex -= segPtr->size;
|
||
index += segPtr->size;
|
||
}
|
||
}
|
||
return indexPtr;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextIndexToSeg --
|
||
*
|
||
* Given an index, this function returns the segment and offset within
|
||
* segment for the index.
|
||
*
|
||
* Results:
|
||
* The return value is a pointer to the segment referred to by indexPtr;
|
||
* this will always be a segment with non-zero size. The variable at
|
||
* *offsetPtr is set to hold the integer offset within the segment of the
|
||
* character given by indexPtr.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
TkTextSegment *
|
||
TkTextIndexToSeg(
|
||
const TkTextIndex *indexPtr,/* Text index. */
|
||
int *offsetPtr) /* Where to store offset within segment, or
|
||
* NULL if offset isn't wanted. */
|
||
{
|
||
TkTextSegment *segPtr;
|
||
int offset;
|
||
|
||
for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr;
|
||
offset >= segPtr->size;
|
||
offset -= segPtr->size, segPtr = segPtr->nextPtr) {
|
||
/* Empty loop body. */
|
||
}
|
||
if (offsetPtr != NULL) {
|
||
*offsetPtr = offset;
|
||
}
|
||
return segPtr;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextSegToOffset --
|
||
*
|
||
* Given a segment pointer and the line containing it, this function
|
||
* returns the offset of the segment within its line.
|
||
*
|
||
* Results:
|
||
* The return value is the offset (within its line) of the first
|
||
* character in segPtr.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextSegToOffset(
|
||
const TkTextSegment *segPtr,/* Segment whose offset is desired. */
|
||
const TkTextLine *linePtr) /* Line containing segPtr. */
|
||
{
|
||
const TkTextSegment *segPtr2;
|
||
int offset = 0;
|
||
|
||
for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
|
||
segPtr2 = segPtr2->nextPtr) {
|
||
offset += segPtr2->size;
|
||
}
|
||
return offset;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextGetObjIndex --
|
||
*
|
||
* Simpler wrapper around the string based function, but could be
|
||
* enhanced with a new object type in the future.
|
||
*
|
||
* Results:
|
||
* see TkTextGetIndex
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextGetObjIndex(
|
||
Tcl_Interp *interp, /* Use this for error reporting. */
|
||
TkText *textPtr, /* Information about text widget. */
|
||
Tcl_Obj *idxObj, /* Object containing textual description of
|
||
* position. */
|
||
TkTextIndex *indexPtr) /* Index structure to fill in. */
|
||
{
|
||
return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr,
|
||
NULL);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextSharedGetObjIndex --
|
||
*
|
||
* Simpler wrapper around the string based function, but could be
|
||
* enhanced with a new object type in the future.
|
||
*
|
||
* Results:
|
||
* see TkTextGetIndex
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextSharedGetObjIndex(
|
||
Tcl_Interp *interp, /* Use this for error reporting. */
|
||
TkSharedText *sharedTextPtr,/* Information about text widget. */
|
||
Tcl_Obj *idxObj, /* Object containing textual description of
|
||
* position. */
|
||
TkTextIndex *indexPtr) /* Index structure to fill in. */
|
||
{
|
||
return GetIndex(interp, sharedTextPtr, NULL, Tcl_GetString(idxObj),
|
||
indexPtr, NULL);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextGetIndex --
|
||
*
|
||
* Given a string, return the index that is described.
|
||
*
|
||
* Results:
|
||
* The return value is a standard Tcl return result. If TCL_OK is
|
||
* returned, then everything went well and the index at *indexPtr is
|
||
* filled in; otherwise TCL_ERROR is returned and an error message is
|
||
* left in the interp's result.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextGetIndex(
|
||
Tcl_Interp *interp, /* Use this for error reporting. */
|
||
TkText *textPtr, /* Information about text widget. */
|
||
const char *string, /* Textual description of position. */
|
||
TkTextIndex *indexPtr) /* Index structure to fill in. */
|
||
{
|
||
return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* GetIndex --
|
||
*
|
||
* Given a string, return the index that is described.
|
||
*
|
||
* Results:
|
||
* The return value is a standard Tcl return result. If TCL_OK is
|
||
* returned, then everything went well and the index at *indexPtr is
|
||
* filled in; otherwise TCL_ERROR is returned and an error message is
|
||
* left in the interp's result.
|
||
*
|
||
* If *canCachePtr is non-NULL, and everything went well, the integer it
|
||
* points to is set to 1 if the indexPtr is something which can be
|
||
* cached, and zero otherwise.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
static int
|
||
GetIndex(
|
||
Tcl_Interp *interp, /* Use this for error reporting. */
|
||
TkSharedText *sharedPtr,
|
||
TkText *textPtr, /* Information about text widget. */
|
||
const char *string, /* Textual description of position. */
|
||
TkTextIndex *indexPtr, /* Index structure to fill in. */
|
||
int *canCachePtr) /* Pointer to integer to store whether we can
|
||
* cache the index (or NULL). */
|
||
{
|
||
char *p, *end, *endOfBase;
|
||
TkTextIndex first, last;
|
||
int wantLast, result;
|
||
char c;
|
||
const char *cp;
|
||
Tcl_DString copy;
|
||
int canCache = 0;
|
||
|
||
if (sharedPtr == NULL) {
|
||
sharedPtr = textPtr->sharedTextPtr;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------
|
||
* Stage 1: check to see if the index consists of nothing but a mark
|
||
* name, an embedded window or an embedded image. We do this check
|
||
* now even though it's also done later, in order to allow mark names,
|
||
* embedded window names or image names that include funny characters
|
||
* such as spaces or "+1c".
|
||
*---------------------------------------------------------------------
|
||
*/
|
||
|
||
if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
|
||
goto done;
|
||
}
|
||
|
||
if (TkTextWindowIndex(textPtr, string, indexPtr) == 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, -1), '.');
|
||
if (p != NULL) {
|
||
TkTextSearch search;
|
||
TkTextTag *tagPtr;
|
||
Tcl_HashEntry *hPtr = NULL;
|
||
const char *tagName;
|
||
|
||
if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
|
||
wantLast = 0;
|
||
endOfBase = p+6;
|
||
} else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
|
||
wantLast = 1;
|
||
endOfBase = p+5;
|
||
} else {
|
||
goto tryxy;
|
||
}
|
||
|
||
tagPtr = NULL;
|
||
tagName = Tcl_DStringValue(©);
|
||
if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) {
|
||
/*
|
||
* Special case for sel tag which is not stored in the hash table.
|
||
*/
|
||
|
||
tagPtr = textPtr->selTagPtr;
|
||
} else {
|
||
*p = 0;
|
||
hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName);
|
||
*p = '.';
|
||
if (hPtr != NULL) {
|
||
tagPtr = (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.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextPrintIndex(
|
||
const TkText *textPtr,
|
||
const TkTextIndex *indexPtr,/* Pointer to index. */
|
||
char *string) /* Place to store the position. Must have at
|
||
* least TK_POS_CHARS characters. */
|
||
{
|
||
TkTextSegment *segPtr;
|
||
TkTextLine *linePtr;
|
||
int numBytes, charIndex;
|
||
|
||
numBytes = indexPtr->byteIndex;
|
||
charIndex = 0;
|
||
linePtr = indexPtr->linePtr;
|
||
|
||
for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
|
||
if (segPtr == NULL) {
|
||
/*
|
||
* Two logical lines merged into one display line through eliding
|
||
* of a newline.
|
||
*/
|
||
|
||
linePtr = TkBTreeNextLine(NULL, linePtr);
|
||
segPtr = linePtr->segPtr;
|
||
}
|
||
if (numBytes <= segPtr->size) {
|
||
break;
|
||
}
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
|
||
} else {
|
||
charIndex += segPtr->size;
|
||
}
|
||
numBytes -= segPtr->size;
|
||
}
|
||
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
|
||
} else {
|
||
charIndex += numBytes;
|
||
}
|
||
|
||
return sprintf(string, "%d.%d",
|
||
TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex);
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextIndexCmp --
|
||
*
|
||
* Compare two indices to see which one is earlier in the text.
|
||
*
|
||
* Results:
|
||
* The return value is 0 if index1Ptr and index2Ptr refer to the same
|
||
* position in the file, -1 if index1Ptr refers to an earlier position
|
||
* than index2Ptr, and 1 otherwise.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextIndexCmp(
|
||
const TkTextIndex*index1Ptr,/* First index. */
|
||
const TkTextIndex*index2Ptr)/* Second index. */
|
||
{
|
||
int line1, line2;
|
||
|
||
if (index1Ptr->linePtr == index2Ptr->linePtr) {
|
||
if (index1Ptr->byteIndex < index2Ptr->byteIndex) {
|
||
return -1;
|
||
} else if (index1Ptr->byteIndex > index2Ptr->byteIndex) {
|
||
return 1;
|
||
} else {
|
||
return 0;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Assumption here that it is ok for comparisons to reflect the full
|
||
* B-tree and not just the portion that is available to any client. This
|
||
* should be true because the only indexPtr's we should be given are ones
|
||
* which are valid for the current client.
|
||
*/
|
||
|
||
line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr);
|
||
line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr);
|
||
if (line1 < line2) {
|
||
return -1;
|
||
}
|
||
if (line1 > line2) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* ForwBack --
|
||
*
|
||
* This function handles +/- modifiers for indices to adjust the index
|
||
* forwards or backwards.
|
||
*
|
||
* Results:
|
||
* If the modifier in string is successfully parsed then the return value
|
||
* is the address of the first character after the modifier, and
|
||
* *indexPtr is updated to reflect the modifier. If there is a syntax
|
||
* error in the modifier then NULL is returned.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
static const char *
|
||
ForwBack(
|
||
TkText *textPtr, /* Information about text widget. */
|
||
const char *string, /* String to parse for additional info about
|
||
* modifier (count and units). Points to "+"
|
||
* or "-" that starts modifier. */
|
||
TkTextIndex *indexPtr) /* Index to update as specified in string. */
|
||
{
|
||
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;
|
||
int byteOffset;
|
||
char *start, *end, *p;
|
||
int ch;
|
||
int elide = 0;
|
||
int checkElided = (type & COUNT_DISPLAY);
|
||
|
||
if (charCount < 0) {
|
||
TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type);
|
||
return;
|
||
}
|
||
if (checkElided) {
|
||
infoPtr = (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->elideString != NULL) {
|
||
infoPtr->tagCnts[tagPtr->priority]++;
|
||
if (infoPtr->tagCnts[tagPtr->priority] & 1) {
|
||
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
|
||
}
|
||
|
||
if (tagPtr->priority >= infoPtr->elidePriority) {
|
||
if (segPtr->typePtr == &tkTextToggleOffType) {
|
||
/*
|
||
* If it is being toggled off, and it has an elide
|
||
* string, it must actually be the current highest
|
||
* priority tag, so this check is redundant:
|
||
*/
|
||
|
||
if (tagPtr->priority != infoPtr->elidePriority) {
|
||
Tcl_Panic("Bad tag priority being toggled off");
|
||
}
|
||
|
||
/*
|
||
* Find previous elide tag, if any (if not then
|
||
* elide will be zero, of course).
|
||
*/
|
||
|
||
elide = 0;
|
||
while (--infoPtr->elidePriority > 0) {
|
||
if (infoPtr->tagCnts[infoPtr->elidePriority]
|
||
& 1) {
|
||
elide = infoPtr->tagPtrs
|
||
[infoPtr->elidePriority]->elide;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
elide = tagPtr->elide;
|
||
infoPtr->elidePriority = tagPtr->priority;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!elide) {
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
start = segPtr->body.chars + byteOffset;
|
||
end = segPtr->body.chars + segPtr->size;
|
||
for (p = start; p < end; p += TkUtfToUniChar(p, &ch)) {
|
||
if (charCount == 0) {
|
||
dstPtr->byteIndex += (p - start);
|
||
goto forwardCharDone;
|
||
}
|
||
charCount--;
|
||
}
|
||
} else if (type & COUNT_INDICES) {
|
||
if (charCount + 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. */
|
||
{
|
||
int byteCount, offset;
|
||
TkTextSegment *segPtr, *segPtr1;
|
||
TkTextLine *linePtr;
|
||
|
||
if (indexPtr1->linePtr == indexPtr2->linePtr) {
|
||
return indexPtr2->byteIndex - indexPtr1->byteIndex;
|
||
}
|
||
|
||
/*
|
||
* indexPtr2 is on a line strictly after the line containing indexPtr1.
|
||
* Add up:
|
||
* bytes between indexPtr1 and end of its line
|
||
* bytes in lines strictly between indexPtr1 and indexPtr2
|
||
* bytes between start of the indexPtr2 line and indexPtr2
|
||
*/
|
||
|
||
segPtr1 = TkTextIndexToSeg(indexPtr1, &offset);
|
||
byteCount = -offset;
|
||
for (segPtr = segPtr1; segPtr != NULL; segPtr = segPtr->nextPtr) {
|
||
byteCount += segPtr->size;
|
||
}
|
||
|
||
linePtr = TkBTreeNextLine(textPtr, indexPtr1->linePtr);
|
||
while (linePtr != indexPtr2->linePtr) {
|
||
for (segPtr = linePtr->segPtr; segPtr != NULL;
|
||
segPtr = segPtr->nextPtr) {
|
||
byteCount += segPtr->size;
|
||
}
|
||
linePtr = TkBTreeNextLine(textPtr, linePtr);
|
||
if (linePtr == NULL) {
|
||
Tcl_Panic("TextIndexCountBytesOrdered ran out of lines");
|
||
}
|
||
}
|
||
|
||
byteCount += indexPtr2->byteIndex;
|
||
|
||
return byteCount;
|
||
}
|
||
|
||
/*
|
||
*---------------------------------------------------------------------------
|
||
*
|
||
* TkTextIndexCount --
|
||
*
|
||
* Given an ordered pair of indices in a text widget, this function
|
||
* counts how many characters (not bytes) are between the two indices.
|
||
*
|
||
* It is illegal to call this function with unordered indices.
|
||
*
|
||
* Note that 'textPtr' is only used if we need to check for elided
|
||
* attributes, i.e. if type is COUNT_DISPLAY_INDICES or
|
||
* COUNT_DISPLAY_CHARS.
|
||
*
|
||
* Results:
|
||
* The number of characters in the given range, which meet the
|
||
* appropriate 'type' attributes.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*---------------------------------------------------------------------------
|
||
*/
|
||
|
||
int
|
||
TkTextIndexCount(
|
||
const TkText *textPtr, /* Overall information about text widget. */
|
||
const TkTextIndex *indexPtr1,
|
||
/* Index describing location of character from
|
||
* which to count. */
|
||
const TkTextIndex *indexPtr2,
|
||
/* Index describing location of last character
|
||
* at which to stop the count. */
|
||
TkTextCountType type) /* The kind of indices to count. */
|
||
{
|
||
TkTextLine *linePtr1;
|
||
TkTextSegment *segPtr, *seg2Ptr = NULL;
|
||
TkTextElideInfo *infoPtr = NULL;
|
||
int byteOffset, maxBytes, count = 0, elide = 0;
|
||
int checkElided = (type & COUNT_DISPLAY);
|
||
|
||
/*
|
||
* Find seg that contains src index, and remember how many bytes not to
|
||
* count in the given segment.
|
||
*/
|
||
|
||
segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset);
|
||
linePtr1 = indexPtr1->linePtr;
|
||
|
||
seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes);
|
||
|
||
if (checkElided) {
|
||
infoPtr = (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->elideString != NULL) {
|
||
infoPtr->tagCnts[tagPtr->priority]++;
|
||
if (infoPtr->tagCnts[tagPtr->priority] & 1) {
|
||
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
|
||
}
|
||
if (tagPtr->priority >= infoPtr->elidePriority) {
|
||
if (segPtr->typePtr == &tkTextToggleOffType) {
|
||
/*
|
||
* If it is being toggled off, and it has an
|
||
* elide string, it must actually be the
|
||
* current highest priority tag, so this check
|
||
* is redundant:
|
||
*/
|
||
|
||
if (tagPtr->priority!=infoPtr->elidePriority) {
|
||
Tcl_Panic("Bad tag priority being toggled off");
|
||
}
|
||
|
||
/*
|
||
* Find previous elide tag, if any (if not
|
||
* then elide will be zero, of course).
|
||
*/
|
||
|
||
elide = 0;
|
||
while (--infoPtr->elidePriority > 0) {
|
||
if (infoPtr->tagCnts[
|
||
infoPtr->elidePriority] & 1) {
|
||
elide = infoPtr->tagPtrs[
|
||
infoPtr->elidePriority]->elide;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
elide = tagPtr->elide;
|
||
infoPtr->elidePriority = tagPtr->priority;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (elide) {
|
||
if (segPtr == seg2Ptr) {
|
||
goto countDone;
|
||
}
|
||
byteOffset = 0;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
int byteLen = segPtr->size - byteOffset;
|
||
unsigned char *str = (unsigned char *)
|
||
segPtr->body.chars + byteOffset;
|
||
int 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) {
|
||
int 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 <= segPtr->size) {
|
||
break;
|
||
}
|
||
segSize -= segPtr->size;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* Now segPtr points to the segment containing the starting index.
|
||
*/
|
||
|
||
while (1) {
|
||
/*
|
||
* If we do need to pay attention to the visibility of
|
||
* characters/indices, check that first. If the current segment isn't
|
||
* visible, then we simply continue the loop.
|
||
*/
|
||
|
||
if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType)
|
||
|| (segPtr->typePtr == &tkTextToggleOnType))) {
|
||
TkTextTag *tagPtr = segPtr->body.toggle.tagPtr;
|
||
|
||
/*
|
||
* The elide state only changes if this tag is either the current
|
||
* highest priority tag (and is therefore being toggled off), or
|
||
* it's a new tag with higher priority.
|
||
*/
|
||
|
||
if (tagPtr->elideString != NULL) {
|
||
infoPtr->tagCnts[tagPtr->priority]++;
|
||
if (infoPtr->tagCnts[tagPtr->priority] & 1) {
|
||
infoPtr->tagPtrs[tagPtr->priority] = tagPtr;
|
||
}
|
||
if (tagPtr->priority >= infoPtr->elidePriority) {
|
||
if (segPtr->typePtr == &tkTextToggleOnType) {
|
||
/*
|
||
* If it is being toggled on, and it has an elide
|
||
* string, it must actually be the current highest
|
||
* priority tag, so this check is redundant:
|
||
*/
|
||
|
||
if (tagPtr->priority != infoPtr->elidePriority) {
|
||
Tcl_Panic("Bad tag priority being toggled on");
|
||
}
|
||
|
||
/*
|
||
* Find previous elide tag, if any (if not then elide
|
||
* will be zero, of course).
|
||
*/
|
||
|
||
elide = 0;
|
||
while (--infoPtr->elidePriority > 0) {
|
||
if (infoPtr->tagCnts[infoPtr->elidePriority] & 1) {
|
||
elide = infoPtr->tagPtrs[
|
||
infoPtr->elidePriority]->elide;
|
||
break;
|
||
}
|
||
}
|
||
} else {
|
||
elide = tagPtr->elide;
|
||
infoPtr->elidePriority = tagPtr->priority;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (!elide) {
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
start = segPtr->body.chars;
|
||
end = segPtr->body.chars + segSize;
|
||
for (p = end; ; p = TkUtfPrev(p, start)) {
|
||
if (charCount == 0) {
|
||
dstPtr->byteIndex -= (end - p);
|
||
goto backwardCharDone;
|
||
}
|
||
if (p == start) {
|
||
break;
|
||
}
|
||
charCount--;
|
||
}
|
||
} else {
|
||
if (type & COUNT_INDICES) {
|
||
if (charCount <= segSize) {
|
||
dstPtr->byteIndex -= charCount;
|
||
goto backwardCharDone;
|
||
}
|
||
charCount -= segSize;
|
||
}
|
||
}
|
||
}
|
||
dstPtr->byteIndex -= segSize;
|
||
|
||
/*
|
||
* Move back into previous segment.
|
||
*/
|
||
|
||
oldPtr = segPtr;
|
||
segPtr = dstPtr->linePtr->segPtr;
|
||
if (segPtr != oldPtr) {
|
||
for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) {
|
||
/* Empty body. */
|
||
}
|
||
segSize = segPtr->size;
|
||
continue;
|
||
}
|
||
|
||
/*
|
||
* Move back to previous line.
|
||
*/
|
||
|
||
if (lineIndex < 0) {
|
||
lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr);
|
||
}
|
||
if (lineIndex == 0) {
|
||
dstPtr->byteIndex = 0;
|
||
goto backwardCharDone;
|
||
}
|
||
lineIndex--;
|
||
dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex);
|
||
|
||
/*
|
||
* Compute the length of the line and add that to dstPtr->byteIndex.
|
||
*/
|
||
|
||
oldPtr = dstPtr->linePtr->segPtr;
|
||
for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
|
||
dstPtr->byteIndex += segPtr->size;
|
||
oldPtr = segPtr;
|
||
}
|
||
segPtr = oldPtr;
|
||
segSize = segPtr->size;
|
||
}
|
||
|
||
backwardCharDone:
|
||
if (infoPtr != NULL) {
|
||
TkTextFreeElideInfo(infoPtr);
|
||
ckfree(infoPtr);
|
||
}
|
||
}
|
||
|
||
/*
|
||
*----------------------------------------------------------------------
|
||
*
|
||
* StartEnd --
|
||
*
|
||
* This function handles modifiers like "wordstart" and "lineend" to
|
||
* adjust indices forwards or backwards.
|
||
*
|
||
* Results:
|
||
* If the modifier is successfully parsed then the return value is the
|
||
* address of the first character after the modifier, and *indexPtr is
|
||
* updated to reflect the modifier. If there is a syntax error in the
|
||
* modifier then NULL is returned.
|
||
*
|
||
* Side effects:
|
||
* None.
|
||
*
|
||
*----------------------------------------------------------------------
|
||
*/
|
||
|
||
static const char *
|
||
StartEnd(
|
||
TkText *textPtr, /* Information about text widget. */
|
||
const char *string, /* String to parse for additional info about
|
||
* modifier (count and units). Points to first
|
||
* character of modifier word. */
|
||
TkTextIndex *indexPtr) /* Index to modify based on string. */
|
||
{
|
||
const char *p;
|
||
size_t length;
|
||
TkTextSegment *segPtr;
|
||
int modifier;
|
||
|
||
/*
|
||
* Find the end of the modifier word.
|
||
*/
|
||
|
||
for (p = string; isalnum(UCHAR(*p)); p++) {
|
||
/* Empty loop body. */
|
||
}
|
||
|
||
length = p-string;
|
||
if ((*string == 'd') &&
|
||
(strncmp(string, "display", (length > 7 ? 7 : length)) == 0)) {
|
||
modifier = TKINDEX_DISPLAY;
|
||
if (length > 7) {
|
||
p -= (length - 7);
|
||
}
|
||
} else if ((*string == 'a') &&
|
||
(strncmp(string, "any", (length > 3 ? 3 : length)) == 0)) {
|
||
modifier = TKINDEX_ANY;
|
||
if (length > 3) {
|
||
p -= (length - 3);
|
||
}
|
||
} else {
|
||
modifier = TKINDEX_NONE;
|
||
}
|
||
|
||
/*
|
||
* If we had a modifier, which we interpreted ok, so now forward to the
|
||
* actual units.
|
||
*/
|
||
|
||
if (modifier != TKINDEX_NONE) {
|
||
while (isspace(UCHAR(*p))) {
|
||
p++;
|
||
}
|
||
string = p;
|
||
while ((*p!='\0') && !isspace(UCHAR(*p)) && (*p!='+') && (*p!='-')) {
|
||
p++;
|
||
}
|
||
length = p - string;
|
||
}
|
||
|
||
if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
|
||
&& (length >= 5)) {
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL);
|
||
} else {
|
||
indexPtr->byteIndex = 0;
|
||
for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
|
||
segPtr = segPtr->nextPtr) {
|
||
indexPtr->byteIndex += segPtr->size;
|
||
}
|
||
|
||
/*
|
||
* We know '\n' is encoded with a single byte index.
|
||
*/
|
||
|
||
indexPtr->byteIndex -= sizeof(char);
|
||
}
|
||
} else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
|
||
&& (length >= 5)) {
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL);
|
||
} else {
|
||
indexPtr->byteIndex = 0;
|
||
}
|
||
} else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
|
||
&& (length >= 5)) {
|
||
int firstChar = 1;
|
||
int offset;
|
||
|
||
/*
|
||
* If the current character isn't part of a word then just move
|
||
* forward one character. Otherwise move forward until finding a
|
||
* character that isn't part of a word and stop there.
|
||
*/
|
||
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
|
||
COUNT_DISPLAY_INDICES);
|
||
}
|
||
segPtr = TkTextIndexToSeg(indexPtr, &offset);
|
||
while (1) {
|
||
int chSize = 1;
|
||
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
int ch;
|
||
|
||
chSize = TkUtfToUniChar(segPtr->body.chars + offset, &ch);
|
||
if (!Tcl_UniCharIsWordChar(ch)) {
|
||
break;
|
||
}
|
||
firstChar = 0;
|
||
}
|
||
offset += chSize;
|
||
indexPtr->byteIndex += chSize;
|
||
if (offset >= segPtr->size) {
|
||
segPtr = TkTextIndexToSeg(indexPtr, &offset);
|
||
}
|
||
}
|
||
if (firstChar) {
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
|
||
COUNT_DISPLAY_INDICES);
|
||
} else {
|
||
TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
|
||
COUNT_INDICES);
|
||
}
|
||
}
|
||
} else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
|
||
&& (length >= 5)) {
|
||
int firstChar = 1;
|
||
int offset;
|
||
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr,
|
||
COUNT_DISPLAY_INDICES);
|
||
}
|
||
|
||
/*
|
||
* Starting with the current character, look for one that's not part
|
||
* of a word and keep moving backward until you find one. Then if the
|
||
* character found wasn't the first one, move forward again one
|
||
* position.
|
||
*/
|
||
|
||
segPtr = TkTextIndexToSeg(indexPtr, &offset);
|
||
while (1) {
|
||
int chSize = 1;
|
||
|
||
if (segPtr->typePtr == &tkTextCharType) {
|
||
|
||
int ch;
|
||
TkUtfToUniChar(segPtr->body.chars + offset, &ch);
|
||
if (!Tcl_UniCharIsWordChar(ch)) {
|
||
break;
|
||
}
|
||
if (offset > 0) {
|
||
chSize = (segPtr->body.chars + offset
|
||
- TkUtfPrev(segPtr->body.chars + offset,
|
||
segPtr->body.chars));
|
||
}
|
||
firstChar = 0;
|
||
}
|
||
if (offset == 0) {
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr,
|
||
COUNT_DISPLAY_INDICES);
|
||
} else {
|
||
TkTextIndexBackChars(NULL, indexPtr, 1, indexPtr,
|
||
COUNT_INDICES);
|
||
}
|
||
} else {
|
||
indexPtr->byteIndex -= chSize;
|
||
}
|
||
offset -= chSize;
|
||
if (offset < 0) {
|
||
if (indexPtr->byteIndex == 0) {
|
||
goto done;
|
||
}
|
||
segPtr = TkTextIndexToSeg(indexPtr, &offset);
|
||
}
|
||
}
|
||
|
||
if (!firstChar) {
|
||
if (modifier == TKINDEX_DISPLAY) {
|
||
TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr,
|
||
COUNT_DISPLAY_INDICES);
|
||
} else {
|
||
TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr,
|
||
COUNT_INDICES);
|
||
}
|
||
}
|
||
} else {
|
||
return NULL;
|
||
}
|
||
|
||
done:
|
||
return p;
|
||
}
|
||
|
||
/*
|
||
* Local Variables:
|
||
* mode: c
|
||
* c-basic-offset: 4
|
||
* fill-column: 78
|
||
* End:
|
||
*/
|