diff options
Diffstat (limited to 'generic/tkTextMark.c')
-rw-r--r-- | generic/tkTextMark.c | 3051 |
1 files changed, 2529 insertions, 522 deletions
diff --git a/generic/tkTextMark.c b/generic/tkTextMark.c index 6a41c77..118b29b 100644 --- a/generic/tkTextMark.c +++ b/generic/tkTextMark.c @@ -6,6 +6,7 @@ * * Copyright (c) 1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 2015-2017 Gregor Cramer * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. @@ -13,37 +14,176 @@ #include "tkInt.h" #include "tkText.h" +#include "tkAlloc.h" #include "tk3d.h" +#include <assert.h> + +#if HAVE_INTTYPES_H +# include <inttypes.h> +#elif defined(_WIN32) || defined(_WIN64) +/* work-around for ancient MSVC versions */ +# define PRIx64 "I64x" +# define PRIx32 "x" +#else +# error "configure failed - can't include inttypes.h" +#endif + +#ifndef MAX +# define MAX(a,b) ((a) < (b) ? b : a) +#endif +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? a : b) +#endif + +#ifdef NDEBUG +# define DEBUG(expr) +#else +# define DEBUG(expr) expr +#endif /* - * Macro that determines the size of a mark segment: + * Forward references for functions defined in this file: */ -#define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \ - + sizeof(TkTextMark))) - +static void InsertUndisplayProc(TkText *textPtr, TkTextDispChunk *chunkPtr); +static bool MarkDeleteProc(TkSharedText *sharedTextPtr, TkTextSegment *segPtr, int flags); +static Tcl_Obj * MarkInspectProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr); +static bool MarkRestoreProc(TkSharedText *sharedTextPtr, TkTextSegment *segPtr); +static void MarkCheckProc(const TkSharedText *sharedTextPtr, const TkTextSegment *segPtr); +static int MarkLayoutProc(const TkTextIndex *indexPtr, + TkTextSegment *segPtr, int offset, int maxX, + int maxChars, bool noCharsYet, TkWrapMode wrapMode, + TkTextSpaceMode spaceMode, TkTextDispChunk *chunkPtr); +static int MarkFindNext(Tcl_Interp *interp, TkText *textPtr, bool discardSpecial, + Tcl_Obj* indexObj, const char *pattern, bool forward); +static void ChangeGravity(TkSharedText *sharedTextPtr, TkText *textPtr, + TkTextSegment *markPtr, const Tk_SegType *newTypePtr, + TkTextUndoInfo *redoInfo); +static struct TkTextSegment *SetMark(struct TkText *textPtr, const char *name, + const Tk_SegType *typePtr, TkTextIndex *indexPtr); +static void UnsetMark(TkSharedText *sharedTextPtr, TkTextSegment *markPtr, + TkTextUndoInfo *redoInfo); +static void ReactivateMark(TkSharedText *sharedTextPtr, TkTextSegment *markPtr); + +static const TkTextDispChunkProcs layoutInsertProcs = { + TEXT_DISP_CURSOR, /* type */ + TkTextInsertDisplayProc, /* displayProc */ + InsertUndisplayProc, /* undisplayProc */ + NULL, /* measureProc */ + NULL, /* bboxProc */ +}; /* - * Forward references for functions defined in this file: + * We need some private undo/redo stuff. */ -static Tcl_Obj * GetMarkName(TkText *textPtr, TkTextSegment *segPtr); -static void InsertUndisplayProc(TkText *textPtr, - TkTextDispChunk *chunkPtr); -static int MarkDeleteProc(TkTextSegment *segPtr, - TkTextLine *linePtr, int treeGone); -static TkTextSegment * MarkCleanupProc(TkTextSegment *segPtr, - TkTextLine *linePtr); -static void MarkCheckProc(TkTextSegment *segPtr, - TkTextLine *linePtr); -static int MarkLayoutProc(TkText *textPtr, TkTextIndex *indexPtr, - TkTextSegment *segPtr, int offset, int maxX, - int maxChars, int noCharsYet, TkWrapMode wrapMode, - TkTextDispChunk *chunkPtr); -static int MarkFindNext(Tcl_Interp *interp, - TkText *textPtr, Tcl_Obj *markName); -static int MarkFindPrev(Tcl_Interp *interp, - TkText *textPtr, Tcl_Obj *markName); +static void UndoToggleGravityPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool); +static void UndoSetMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool); +static void RedoSetMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool); +static void UndoMoveMarkPerform(TkSharedText *, TkTextUndoInfo *, TkTextUndoInfo *, bool); +static void UndoToggleGravityDestroy(TkSharedText *, TkTextUndoToken *, bool); +static void UndoSetMarkDestroy(TkSharedText *, TkTextUndoToken *, bool); +static void RedoSetMarkDestroy(TkSharedText *, TkTextUndoToken *, bool); +static void UndoMoveMarkDestroy(TkSharedText *, TkTextUndoToken *, bool); +static void UndoMarkGetRange(const TkSharedText *, const TkTextUndoToken *, + TkTextIndex *, TkTextIndex *); +static void RedoSetMarkGetRange(const TkSharedText *, const TkTextUndoToken *, + TkTextIndex *, TkTextIndex *); +static void RedoMoveMarkGetRange(const TkSharedText *, const TkTextUndoToken *, + TkTextIndex *, TkTextIndex *); +static Tcl_Obj *UndoToggleGravityGetCommand(const TkSharedText *, const TkTextUndoToken *); +static Tcl_Obj *UndoSetMarkGetCommand(const TkSharedText *, const TkTextUndoToken *); +static Tcl_Obj *UndoToggleGravityInspect(const TkSharedText *, const TkTextUndoToken *); +static Tcl_Obj *UndoSetMarkInspect(const TkSharedText *, const TkTextUndoToken *); + +static const Tk_UndoType undoTokenToggleGravityType = { + TK_TEXT_UNDO_MARK_GRAVITY, /* action */ + UndoToggleGravityGetCommand,/* commandProc */ + UndoToggleGravityPerform, /* undoProc */ + UndoToggleGravityDestroy, /* destroyProc */ + UndoMarkGetRange, /* rangeProc */ + UndoToggleGravityInspect /* inspectProc */ +}; + +static const Tk_UndoType redoTokenToggleGravityType = { + TK_TEXT_REDO_MARK_GRAVITY, /* action */ + UndoToggleGravityGetCommand,/* commandProc */ + UndoToggleGravityPerform, /* undoProc */ + UndoToggleGravityDestroy, /* destroyProc */ + UndoMarkGetRange, /* rangeProc */ + UndoToggleGravityInspect /* inspectProc */ +}; +static const Tk_UndoType undoTokenSetMarkType = { + TK_TEXT_UNDO_MARK_SET, /* action */ + UndoSetMarkGetCommand, /* commandProc */ + UndoSetMarkPerform, /* undoProc */ + UndoSetMarkDestroy, /* destroyProc */ + UndoMarkGetRange, /* rangeProc */ + UndoSetMarkInspect /* inspectProc */ +}; + +static const Tk_UndoType redoTokenSetMarkType = { + TK_TEXT_REDO_MARK_SET, /* action */ + UndoSetMarkGetCommand, /* commandProc */ + RedoSetMarkPerform, /* undoProc */ + RedoSetMarkDestroy, /* destroyProc */ + RedoSetMarkGetRange, /* rangeProc */ + UndoSetMarkInspect /* inspectProc */ +}; + +static const Tk_UndoType undoTokenMoveMarkType = { + TK_TEXT_UNDO_MARK_MOVE, /* action */ + UndoSetMarkGetCommand, /* commandProc */ + UndoMoveMarkPerform, /* undoProc */ + UndoMoveMarkDestroy, /* destroyProc */ + RedoMoveMarkGetRange, /* rangeProc */ + UndoSetMarkInspect /* inspectProc */ +}; + +static const Tk_UndoType redoTokenMoveMarkType = { + TK_TEXT_REDO_MARK_MOVE, /* action */ + UndoSetMarkGetCommand, /* commandProc */ + UndoMoveMarkPerform, /* undoProc */ + UndoMoveMarkDestroy, /* destroyProc */ + RedoMoveMarkGetRange, /* rangeProc */ + UndoSetMarkInspect /* inspectProc */ +}; + +typedef struct UndoTokenToggleMark { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; +} UndoTokenToggleMark; + +typedef struct UndoTokenToggleIndex { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; +} UndoTokenToggleIndex; + +/* derivation of UndoTokenToggleMark */ +typedef struct UndoTokenToggleGravity { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; +} UndoTokenToggleGravity; + +/* derivation of UndoTokenToggleMark */ +typedef struct UndoTokenSetMark { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; +} UndoTokenSetMark; + +/* derivation of UndoTokenSetMark */ +typedef struct RedoTokenSetMark { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; + TkTextUndoIndex index; +} RedoTokenSetMark; + +/* derivation of UndoTokenSetMark */ +typedef struct UndoTokenMoveMark { + const Tk_UndoType *undoType; + TkTextSegment *markPtr; + TkTextUndoIndex index; +} UndoTokenMoveMark; /* * The following structures declare the "mark" segment types. There are @@ -52,28 +192,338 @@ static int MarkFindPrev(Tcl_Interp *interp, */ const Tk_SegType tkTextRightMarkType = { - "mark", /* name */ - 0, /* leftGravity */ - NULL, /* splitProc */ - MarkDeleteProc, /* deleteProc */ - MarkCleanupProc, /* cleanupProc */ - NULL, /* lineChangeProc */ - MarkLayoutProc, /* layoutProc */ - MarkCheckProc /* checkProc */ + "mark", /* name */ + SEG_GROUP_MARK, /* group */ + GRAVITY_RIGHT, /* gravity */ + MarkDeleteProc, /* deleteProc */ + MarkRestoreProc, /* restoreProc */ + MarkLayoutProc, /* layoutProc */ + MarkCheckProc, /* checkProc */ + MarkInspectProc /* inspectProc */ }; const Tk_SegType tkTextLeftMarkType = { - "mark", /* name */ - 1, /* leftGravity */ - NULL, /* splitProc */ - MarkDeleteProc, /* deleteProc */ - MarkCleanupProc, /* cleanupProc */ - NULL, /* lineChangeProc */ - MarkLayoutProc, /* layoutProc */ - MarkCheckProc /* checkProc */ + "mark", /* name */ + SEG_GROUP_MARK, /* group */ + GRAVITY_LEFT, /* gravity */ + MarkDeleteProc, /* deleteProc */ + MarkRestoreProc, /* restoreProc */ + MarkLayoutProc, /* layoutProc */ + MarkCheckProc, /* checkProc */ + MarkInspectProc /* inspectProc */ }; /* + * Pointer to int, for some portable pointer hacks - it's guaranteed that + * 'uintptr_t' and 'void *' are convertible in both directions (C99 7.18.1.4). + */ + +typedef union { + void *ptr; + uintptr_t flag; +} __ptr_to_int; + +#define MARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag |= (uintptr_t) 1) +#define UNMARK_POINTER(ptr) (((__ptr_to_int *) &ptr)->flag &= ~(uintptr_t) 1) +#define POINTER_IS_MARKED(ptr) (((__ptr_to_int *) &ptr)->flag & (uintptr_t) 1) + +#define IS_PRESERVED(seg) POINTER_IS_MARKED(seg->body.mark.ptr) +#define MAKE_PRESERVED(seg) MARK_POINTER(seg->body.mark.ptr) + +#define GET_POINTER(ptr) ((void *) (((__ptr_to_int *) &ptr)->flag & ~(uintptr_t) 1)) + +#define GET_NAME(seg) ((char *) GET_POINTER(seg->body.mark.ptr)) +#define GET_HPTR(seg) ((Tcl_HashEntry *) seg->body.mark.ptr) +#define PTR_TO_INT(ptr) ((uintptr_t) ptr) + +#ifndef NDEBUG + +# undef GET_HPTR +# undef GET_NAME + +static Tcl_HashEntry *GET_HPTR(const TkTextSegment *markPtr) +{ assert(!IS_PRESERVED(markPtr)); return (Tcl_HashEntry *) markPtr->body.mark.ptr; } + +static char *GET_NAME(const TkTextSegment *markPtr) +{ assert(IS_PRESERVED(markPtr)); return (char *) GET_POINTER(markPtr->body.mark.ptr); } + +#endif /* NDEBUG */ + +DEBUG_ALLOC(extern unsigned tkTextCountNewSegment); +DEBUG_ALLOC(extern unsigned tkTextCountDestroySegment); +DEBUG_ALLOC(extern unsigned tkTextCountNewUndoToken); +DEBUG_ALLOC(extern unsigned tkTextCountDestroyUndoToken); + +/* + * Some functions for the undo/redo mechanism. + */ + +static Tcl_Obj * +AppendName( + Tcl_Obj *objPtr, + const TkSharedText *sharedTextPtr, + const TkTextSegment *markPtr) +{ + const char *name; + + if (IS_PRESERVED(markPtr)) { + name = GET_NAME(markPtr); + } else { + name = TkTextMarkName(sharedTextPtr, NULL, markPtr); + } + assert(name); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(name, -1)); + return objPtr; +} + +static Tcl_Obj * +UndoToggleGravityGetCommand( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item) +{ + Tcl_Obj *objPtr = Tcl_NewObj(); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("mark", -1)); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("gravity", -1)); + return objPtr; +} + +static Tcl_Obj * +UndoToggleGravityInspect( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item) +{ + const UndoTokenToggleGravity *token = (const UndoTokenToggleGravity *) item; + return AppendName(UndoToggleGravityGetCommand(sharedTextPtr, item), sharedTextPtr, token->markPtr); +} + +static void +UndoToggleGravityPerform( + TkSharedText *sharedTextPtr, + TkTextUndoInfo *undoInfo, + TkTextUndoInfo *redoInfo, + bool isRedo) +{ + UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) undoInfo->token; + const Tk_SegType *newTypePtr; + const Tk_SegType *oldTypePtr; + + assert(!token->markPtr->body.mark.changePtr); + + oldTypePtr = token->markPtr->typePtr; + newTypePtr = (oldTypePtr == &tkTextRightMarkType) ? &tkTextLeftMarkType : &tkTextRightMarkType; + ChangeGravity(sharedTextPtr, NULL, token->markPtr, newTypePtr, NULL); + + if (redoInfo) { + redoInfo->token = undoInfo->token; + redoInfo->token->undoType = isRedo ? &undoTokenToggleGravityType : &redoTokenToggleGravityType; + } +} + +static void +UndoToggleGravityDestroy( + TkSharedText *sharedTextPtr, + TkTextUndoToken *item, + bool reused) +{ + assert(!((UndoTokenToggleGravity *) item)->markPtr->body.mark.changePtr); + + if (!reused) { + UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) item; + MarkDeleteProc(sharedTextPtr, token->markPtr, DELETE_MARKS); + } +} + +static void +UndoMoveMarkPerform( + TkSharedText *sharedTextPtr, + TkTextUndoInfo *undoInfo, + TkTextUndoInfo *redoInfo, + bool isRedo) +{ + UndoTokenMoveMark *token = (UndoTokenMoveMark *) undoInfo->token; + TkTextUndoIndex index = token->index; + + assert(!token->markPtr->body.mark.changePtr); + + if (redoInfo) { + TkBTreeMakeUndoIndex(sharedTextPtr, token->markPtr, &index); + token->index = index; + redoInfo->token = undoInfo->token; + redoInfo->token->undoType = isRedo ? &undoTokenMoveMarkType : &redoTokenMoveMarkType; + } + + TkBTreeUnlinkSegment(sharedTextPtr, token->markPtr); + TkBTreeReInsertSegment(sharedTextPtr, &index, token->markPtr); +} + +static void +UndoMoveMarkDestroy( + TkSharedText *sharedTextPtr, + TkTextUndoToken *item, + bool reused) +{ + assert(!((UndoTokenMoveMark *) item)->markPtr->body.mark.changePtr); + + if (!reused) { + UndoTokenMoveMark *token = (UndoTokenMoveMark *) item; + MarkDeleteProc(sharedTextPtr, token->markPtr, DELETE_MARKS); + } +} + +static Tcl_Obj * +UndoSetMarkGetCommand( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item) +{ + const UndoTokenSetMark *token = (const UndoTokenSetMark *) item; + const char *operation = POINTER_IS_MARKED(token->markPtr) ? "unset" : "set"; + Tcl_Obj *objPtr = Tcl_NewObj(); + + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj("mark", -1)); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(operation, -1)); + return objPtr; +} + +static Tcl_Obj * +UndoSetMarkInspect( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item) +{ + const UndoTokenSetMark *token = (const UndoTokenSetMark *) item; + const TkTextSegment *markPtr = GET_POINTER(token->markPtr); + Tcl_Obj *objPtr = UndoSetMarkGetCommand(sharedTextPtr, item); + + objPtr = AppendName(objPtr, sharedTextPtr, markPtr); + + if (!POINTER_IS_MARKED(token->markPtr)) { + const char *gravity = (markPtr->typePtr == &tkTextLeftMarkType) ? "left" : "right"; + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(gravity, -1)); + } + + return objPtr; +} + +static void +UndoSetMarkPerform( + TkSharedText *sharedTextPtr, + TkTextUndoInfo *undoInfo, + TkTextUndoInfo *redoInfo, + bool isRedo) +{ + const UndoTokenSetMark *token = (const UndoTokenSetMark *) undoInfo->token; + TkTextSegment *markPtr = GET_POINTER(token->markPtr); + + assert(!markPtr->body.mark.changePtr); + UnsetMark(sharedTextPtr, markPtr, redoInfo); + if (redoInfo && !isRedo) { + UNMARK_POINTER(((RedoTokenSetMark *) redoInfo->token)->markPtr); + } +} + +static void +UndoSetMarkDestroy( + TkSharedText *sharedTextPtr, + TkTextUndoToken *item, + bool reused) +{ + UndoTokenSetMark *token = (UndoTokenSetMark *) item; + TkTextSegment *markPtr = GET_POINTER(token->markPtr); + + assert(!reused); + assert(!markPtr->body.mark.changePtr); + + MarkDeleteProc(sharedTextPtr, markPtr, DELETE_CLEANUP); +} + +static void +RedoSetMarkPerform( + TkSharedText *sharedTextPtr, + TkTextUndoInfo *undoInfo, + TkTextUndoInfo *redoInfo, + bool isRedo) +{ + RedoTokenSetMark *token = (RedoTokenSetMark *) undoInfo->token; + TkTextSegment *markPtr = GET_POINTER(token->markPtr); + + assert(!markPtr->body.mark.changePtr); + assert(TkTextIsNormalMark(markPtr)); + + if (IS_PRESERVED(markPtr)) { + ReactivateMark(sharedTextPtr, markPtr); + sharedTextPtr->numMarks += 1; + } + + TkBTreeReInsertSegment(sharedTextPtr, &token->index, markPtr); + markPtr->refCount += 1; + + if (redoInfo) { + UndoTokenSetMark *redoToken; + + redoToken = malloc(sizeof(UndoTokenSetMark)); + redoToken->markPtr = token->markPtr; + redoToken->undoType = &undoTokenSetMarkType; + redoInfo->token = (TkTextUndoToken *) redoToken; + DEBUG_ALLOC(tkTextCountNewUndoToken++); + markPtr->refCount += 1; + } +} + +static void +RedoSetMarkDestroy( + TkSharedText *sharedTextPtr, + TkTextUndoToken *item, + bool reused) +{ + RedoTokenSetMark *token = (RedoTokenSetMark *) item; + TkTextSegment *markPtr = GET_POINTER(token->markPtr); + + assert(!reused); + assert(!markPtr->body.mark.changePtr); + MarkDeleteProc(sharedTextPtr, markPtr, DELETE_MARKS); +} + +static void +UndoMarkGetRange( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item, + TkTextIndex *startIndex, + TkTextIndex *endIndex) +{ + const UndoTokenToggleMark *token = (UndoTokenToggleMark *) item; + + TkTextIndexClear2(startIndex, NULL, sharedTextPtr->tree); + TkTextIndexSetSegment(startIndex, GET_POINTER(token->markPtr)); + *endIndex = *startIndex; +} + +static void +RedoSetMarkGetRange( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item, + TkTextIndex *startIndex, + TkTextIndex *endIndex) +{ + RedoTokenSetMark *token = (RedoTokenSetMark *) item; + TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex); + *endIndex = *startIndex; +} + +static void +RedoMoveMarkGetRange( + const TkSharedText *sharedTextPtr, + const TkTextUndoToken *item, + TkTextIndex *startIndex, + TkTextIndex *endIndex) +{ + UndoTokenMoveMark *token = (UndoTokenMoveMark *) item; + TkTextSegment *markPtr = GET_POINTER(token->markPtr); + + TkBTreeUndoIndexToIndex(sharedTextPtr, &token->index, startIndex); + TkTextIndexClear2(endIndex, NULL, sharedTextPtr->tree); + TkTextIndexSetSegment(endIndex, markPtr); +} + +/* *-------------------------------------------------------------- * * TkTextMarkCmd -- @@ -91,14 +541,20 @@ const Tk_SegType tkTextLeftMarkType = { *-------------------------------------------------------------- */ +static int +SetResultNoMarkNamed(Tcl_Interp *interp, const char *name) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("there is no mark named \"%s\"", name)); + Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", name, NULL); + return TCL_ERROR; +} + int TkTextMarkCmd( - register TkText *textPtr, /* Information about text widget. */ + TkText *textPtr, /* Information about text widget. */ Tcl_Interp *interp, /* Current interpreter. */ int objc, /* Number of arguments. */ - Tcl_Obj *const objv[]) /* Argument objects. Someone else has already - * parsed this command enough to know that - * objv[1] is "mark". */ + Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command + * enough to know that objv[1] is "mark". */ { Tcl_HashEntry *hPtr; TkTextSegment *markPtr; @@ -107,25 +563,109 @@ TkTextMarkCmd( const Tk_SegType *newTypePtr; int optionIndex; static const char *const markOptionStrings[] = { - "gravity", "names", "next", "previous", "set", "unset", NULL + "compare", "exists", "generate", "gravity", "names", "next", "previous", + "set", "unset", NULL }; enum markOptions { - MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS, MARK_SET, - MARK_UNSET + MARK_COMPARE, MARK_EXISTS, MARK_GENERATE, MARK_GRAVITY, MARK_NAMES, MARK_NEXT, MARK_PREVIOUS, + MARK_SET, MARK_UNSET }; if (objc < 3) { - Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?"); + Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); return TCL_ERROR; } + if (Tcl_GetIndexFromObjStruct(interp, objv[2], markOptionStrings, sizeof(char *), "mark option", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } switch ((enum markOptions) optionIndex) { + case MARK_COMPARE: { + TkTextSegment *markPtr1, *markPtr2; + int relation, value; + + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "markName1 op markName2"); + return TCL_ERROR; + } + if (!(markPtr1 = TkTextFindMark(textPtr, Tcl_GetString(objv[2])))) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad comparison operand \"%s\": " + "must be an existing mark", Tcl_GetString(objv[2]))); + Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_COMPARISON", NULL); + return TCL_ERROR; + } + if (!(markPtr2 = TkTextFindMark(textPtr, Tcl_GetString(objv[4])))) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad comparison operand \"%s\": " + "must be an existing mark", Tcl_GetString(objv[4]))); + Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_COMPARISON", NULL); + return TCL_ERROR; + } + + if (markPtr1 == markPtr2) { + relation = 0; + } else { + TkTextIndex index1, index2; + + TkTextIndexClear(&index1, textPtr); + TkTextIndexClear(&index2, textPtr); + TkTextIndexSetSegment(&index1, markPtr1); + TkTextIndexSetSegment(&index2, markPtr2); + relation = TkTextIndexCompare(&index1, &index2); + + if (relation == 0) { + TkTextSegment *segPtr = markPtr1->nextPtr; + + while (segPtr && segPtr != markPtr2 && segPtr->size == 0) { + segPtr = segPtr->nextPtr; + } + relation = (segPtr == markPtr2) ? -1 : +1; + } + } + + value = TkTextTestRelation(interp, relation, Tcl_GetString(objv[3])); + if (value == -1) { + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); + break; + } + case MARK_EXISTS: { + if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "markName"); + return TCL_ERROR; + } + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(!!TkTextFindMark(textPtr, Tcl_GetString(objv[3])))); + break; + } + case MARK_GENERATE: { + TkTextSegment *markPtr; + TkTextIndex index; + char uniqName[100]; + + TkTextIndexClear(&index, textPtr); + TkTextIndexSetSegment(&index, textPtr->startMarker); + /* IMPORTANT NOTE: ensure fixed length (depending on pointer size) */ + snprintf(uniqName, sizeof(uniqName), +#ifdef TK_IS_64_BIT_ARCH + "##ID##0x%016"PRIx64"##0x%016"PRIx64"##%08u##", /* we're on a real 64-bit system */ + (uint64_t) textPtr, (uint64_t) textPtr->sharedTextPtr, ++textPtr->uniqueIdCounter +#else /* if defined(TK_IS_32_BIT_ARCH) */ + "##ID##0x%08"PRIx32"##0x%08"PRIx32"##%08u##", /* we're most likely on a 32-bit system */ + (uint32_t) textPtr, (uint32_t) textPtr->sharedTextPtr, ++textPtr->uniqueIdCounter +#endif /* TK_IS_64_BIT_ARCH */ + ); + assert(!TkTextFindMark(textPtr, uniqName)); + markPtr = TkTextMakeMark(textPtr, uniqName); + markPtr->privateMarkFlag = true; + textPtr->sharedTextPtr->numMarks -= 1; /* take back counting */ + textPtr->sharedTextPtr->numPrivateMarks += 1; + TkBTreeLinkSegment(textPtr->sharedTextPtr, markPtr, &index); + Tcl_SetObjResult(textPtr->interp, Tcl_NewStringObj(uniqName, -1)); + break; + } case MARK_GRAVITY: { - char c; int length; const char *str; @@ -134,38 +674,26 @@ TkTextMarkCmd( return TCL_ERROR; } str = Tcl_GetStringFromObj(objv[3], &length); - if (length == 6 && !strcmp(str, "insert")) { + if (strcmp(str, "insert") == 0) { markPtr = textPtr->insertMarkPtr; - } else if (length == 7 && !strcmp(str, "current")) { + } else if (strcmp(str, "current") == 0) { markPtr = textPtr->currentMarkPtr; } else { - hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str); - if (hPtr == NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "there is no mark named \"%s\"", str)); - Tcl_SetErrorCode(interp, "TK", "LOOKUP", "TEXT_MARK", str, - NULL); - return TCL_ERROR; + if (!(hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, str))) { + return SetResultNoMarkNamed(interp, Tcl_GetString(objv[3])); } markPtr = Tcl_GetHashValue(hPtr); } if (objc == 4) { const char *typeStr; - - if (markPtr->typePtr == &tkTextRightMarkType) { - typeStr = "right"; - } else { - typeStr = "left"; - } + typeStr = markPtr->typePtr == &tkTextRightMarkType ? "right" : "left"; Tcl_SetObjResult(interp, Tcl_NewStringObj(typeStr, -1)); return TCL_OK; } - str = Tcl_GetStringFromObj(objv[4],&length); - c = str[0]; - if ((c == 'l') && (strncmp(str, "left", (unsigned) length) == 0)) { + str = Tcl_GetStringFromObj(objv[4], &length); + if (strncmp(str, "left", length) == 0) { newTypePtr = &tkTextLeftMarkType; - } else if ((c == 'r') && - (strncmp(str, "right", (unsigned) length) == 0)) { + } else if (strncmp(str, "right", length) == 0) { newTypePtr = &tkTextRightMarkType; } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( @@ -173,75 +701,212 @@ TkTextMarkCmd( Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL); return TCL_ERROR; } - TkTextMarkSegToIndex(textPtr, markPtr, &index); - TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); - markPtr->typePtr = newTypePtr; - TkBTreeLinkSegment(markPtr, &index); + /* + * We have to force the re-insertion of the mark when steadyMarks is not enabled. + */ + + if (markPtr->typePtr != newTypePtr || !textPtr->sharedTextPtr->steadyMarks) { + TkTextUndoInfo undoInfo; + TkTextUndoInfo *undoInfoPtr = NULL; + + if (textPtr->sharedTextPtr->steadyMarks + && TkTextIsNormalMark(markPtr) + && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) { + undoInfoPtr = &undoInfo; + } + ChangeGravity(textPtr->sharedTextPtr, textPtr, markPtr, newTypePtr, undoInfoPtr); + } break; } case MARK_NAMES: { + bool discardSpecial = false; + int numArgs = 3; + const char *pattern; Tcl_Obj *resultObj; - if (objc != 3) { - Tcl_WrongNumArgs(interp, 3, objv, NULL); + if (objc > 4 && *Tcl_GetString(objv[3]) == '-') { + if (strcmp(Tcl_GetString(objv[3]), "-discardspecial") == 0) { + discardSpecial = true; + numArgs = 4; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\": must be -discardspecial", Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL); + return TCL_ERROR; + } + } + + if (objc != numArgs && objc != numArgs + 1) { + Tcl_WrongNumArgs(interp, 3, objv, "?-discardspecial? ?pattern?"); return TCL_ERROR; } + + pattern = objc > numArgs ? Tcl_GetString(objv[numArgs]) : NULL; resultObj = Tcl_NewObj(); - Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj( - "insert", -1)); - Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj( - "current", -1)); - for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable, - &search); hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj( - Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr), - -1)); + + if (!discardSpecial && (!pattern || Tcl_StringMatch("insert", pattern))) { + Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj("insert", -1)); + } + if (!discardSpecial && (!pattern || Tcl_StringMatch("current", pattern))) { + Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj("current", -1)); + } + + for (hPtr = Tcl_FirstHashEntry(&textPtr->sharedTextPtr->markTable, &search); + hPtr; + hPtr = Tcl_NextHashEntry(&search)) { + TkTextSegment *markPtr = Tcl_GetHashValue(hPtr); + + if (!markPtr->privateMarkFlag && !markPtr->startEndMarkFlag) { + const char *name = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, hPtr); + + if (!pattern || Tcl_StringMatch(name, pattern)) { + Tcl_ListObjAppendElement(NULL, resultObj, Tcl_NewStringObj(name, -1)); + } + } } + Tcl_SetObjResult(interp, resultObj); break; } - case MARK_NEXT: - if (objc != 4) { - Tcl_WrongNumArgs(interp, 3, objv, "index"); + case MARK_NEXT: { + bool discardSpecial = false; + int numArgs = 4; + const char *pattern; + + if (objc > 4 && *Tcl_GetString(objv[3]) == '-') { + if (strcmp(Tcl_GetString(objv[3]), "-discardspecial") == 0) { + discardSpecial = true; + numArgs = 5; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\": must be -discardspecial", Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL); + return TCL_ERROR; + } + } + + if (objc != numArgs && objc != numArgs + 1) { + Tcl_WrongNumArgs(interp, 3, objv, "?-discardspecial? index ?pattern?"); return TCL_ERROR; } - return MarkFindNext(interp, textPtr, objv[3]); - case MARK_PREVIOUS: - if (objc != 4) { - Tcl_WrongNumArgs(interp, 3, objv, "index"); + + pattern = objc > numArgs ? Tcl_GetString(objv[numArgs]) : NULL; + return MarkFindNext(interp, textPtr, discardSpecial, objv[numArgs - 1], pattern, true); + } + case MARK_PREVIOUS: { + bool discardSpecial = false; + int numArgs = 4; + const char *pattern; + + if (objc > 4 && *Tcl_GetString(objv[3]) == '-') { + if (strcmp(Tcl_GetString(objv[3]), "-discardspecial") == 0) { + discardSpecial = true; + numArgs = 5; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad option \"%s\": must be -discardspecial", Tcl_GetString(objv[3]))); + Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL); + return TCL_ERROR; + } + } + + if (objc != numArgs && objc != numArgs + 1) { + Tcl_WrongNumArgs(interp, 3, objv, "?-discardspecial? index ?pattern?"); return TCL_ERROR; } - return MarkFindPrev(interp, textPtr, objv[3]); - case MARK_SET: - if (objc != 5) { - Tcl_WrongNumArgs(interp, 3, objv, "markName index"); + + pattern = objc > numArgs ? Tcl_GetString(objv[numArgs]) : NULL; + return MarkFindNext(interp, textPtr, discardSpecial, objv[numArgs - 1], pattern, false); + } + case MARK_SET: { + const Tk_SegType *typePtr = NULL; + const char *name; + + if (objc != 5 && objc != 6) { + Tcl_WrongNumArgs(interp, 3, objv, "markName index ?direction?"); return TCL_ERROR; } - if (TkTextGetObjIndex(interp, textPtr, objv[4], &index) != TCL_OK) { + if (!TkTextGetIndexFromObj(interp, textPtr, objv[4], &index)) { return TCL_ERROR; } - TkTextSetMark(textPtr, Tcl_GetString(objv[3]), &index); - return TCL_OK; + if (objc == 6) { + const char *direction = Tcl_GetString(objv[5]); + + if (strcmp(direction, "left") == 0) { + typePtr = &tkTextLeftMarkType; + } else if (strcmp(direction, "right") == 0) { + typePtr = &tkTextRightMarkType; + } else { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "bad mark gravity \"%s\": must be left or right", direction)); + Tcl_SetErrorCode(interp, "TK", "VALUE", "MARK_GRAVITY", NULL); + return TCL_ERROR; + } + } + + name = Tcl_GetString(objv[3]); + +#if BEGIN_DOES_NOT_BELONG_TO_BASE + + if (*name == 'b' && strcmp(name, "begin") == 0) { + static bool printWarning = true; + + if (printWarning) { + fprintf(stderr, "tk::text: \"begin\" is a reserved index identifier and shouldn't " + "be used for mark names anymore.\n"); + printWarning = false; + } + } + +#else /* if !BEGIN_DOES_NOT_BELONG_TO_BASE */ + + /* + * TODO: + * Probably we should print a warning if the mark name is matching any of the + * following forms: + * - "begin"|"end" + * - <integer> "." (<integer>|"begin"|"end") + * - "@" (<integer>|"first"|"last") "," (<integer>|"first"|"last") + * - "##ID##" .* + */ + +#endif /* BEGIN_DOES_NOT_BELONG_TO_BASE */ + + if (!SetMark(textPtr, name, typePtr, &index)) { + Tcl_Obj *msgPtr; + + if (strcmp(name, "insert") == 0) { + return TCL_OK; /* the "watch" command did destroy the widget */ + } + msgPtr = Tcl_ObjPrintf("\"%s\" is an expired generated mark", name); + Tcl_SetObjResult(interp, msgPtr); + Tcl_SetErrorCode(interp, "TK", "SET", "TEXT_MARK", name, NULL); + return TCL_ERROR; + } + break; + } case MARK_UNSET: { + TkTextUndoInfo undoInfo; + TkTextUndoInfo *undoInfoPtr = NULL; int i; - for (i = 3; i < objc; i++) { - hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, - Tcl_GetString(objv[i])); - if (hPtr != NULL) { - markPtr = Tcl_GetHashValue(hPtr); - - /* - * Special case not needed with peer widgets. - */ + if (textPtr->sharedTextPtr->steadyMarks + && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) { + undoInfoPtr = &undoInfo; + } - if ((markPtr == textPtr->insertMarkPtr) - || (markPtr == textPtr->currentMarkPtr)) { - continue; + for (i = 3; i < objc; i++) { + if ((hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, Tcl_GetString(objv[i])))) { + TkTextSegment *markPtr = Tcl_GetHashValue(hPtr); + + if (TkTextIsPrivateMark(markPtr)) { + UnsetMark(textPtr->sharedTextPtr, markPtr, NULL); + } else if (!TkTextIsSpecialMark(markPtr)) { + UnsetMark(textPtr->sharedTextPtr, markPtr, undoInfoPtr); + if (undoInfoPtr && undoInfo.token) { + TkTextPushUndoToken(textPtr->sharedTextPtr, undoInfo.token, 0); + } } - TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); - Tcl_DeleteHashEntry(hPtr); - ckfree(markPtr); } } break; @@ -253,11 +918,738 @@ TkTextMarkCmd( /* *---------------------------------------------------------------------- * - * TkTextSetMark -- + * TkTextFindMark -- + * + * Return mark segment of given name, if exisiting. + * + * Results: + * The mark with this name, or NULL if not existing. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +TkTextSegment * +TkTextFindMark( + const TkText *textPtr, + const char *name) +{ + Tcl_HashEntry *hPtr; + + assert(textPtr); + assert(name); + + switch (name[0]) { + case 'i': + if (strcmp(name, "insert") == 0) { + return textPtr->insertMarkPtr; + } + break; + case 'c': + if (strcmp(name, "current") == 0) { + return textPtr->currentMarkPtr; + } + break; + } + hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name); + return hPtr ? Tcl_GetHashValue(hPtr) : NULL; +} + +/* + *---------------------------------------------------------------------- + * + * ReactivateMark -- + * + * Reactivate a preserved mark. + * + * Results: + * None. + * + * Side effects: + * Allocates some memory for hash table entry, and release memory + * of preserved name. + * + *---------------------------------------------------------------------- + */ + +static void +ReactivateMark( + TkSharedText *sharedTextPtr, /* Handle to shared text resource. */ + TkTextSegment *markPtr) /* Reactivate this mark. */ +{ + Tcl_HashEntry *hPtr; + char *name; + int isNew; + + assert(IS_PRESERVED(markPtr)); + + name = GET_NAME(markPtr); + hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, name, &isNew); + assert(isNew); + free(name); + Tcl_SetHashValue(hPtr, markPtr); + markPtr->body.mark.ptr = PTR_TO_INT(hPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextFreeMarks -- + * + * Free all used marks, also the hash table for marks will be + * destroyed. But do not free private marks if 'retainPrivateMarks' + * is true, in this case a new hash table will be built, only + * with the remaining private marks. + * + * Results: + * If 'retainPrivateMarks' is false, then return NULL. Otherwise + * the chain of retained private marks will be returned. + * + * Side effects: + * Free some memory, the old hash table for marks will be destroyed, + * and a new one will be created if 'retainPrivateMarks' is true. + * + *---------------------------------------------------------------------- + */ + +TkTextSegment * +TkTextFreeMarks( + TkSharedText *sharedTextPtr, /* Handle to shared text resource. */ + bool retainPrivateMarks) /* Priate marks will be retained? */ +{ + Tcl_HashSearch search; + Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search); + TkTextSegment *deletePtr = NULL; + TkTextSegment *retainedPtr = NULL; + TkTextSegment *markPtr; + + for ( ; hPtr; hPtr = Tcl_NextHashEntry(&search)) { + markPtr = Tcl_GetHashValue(hPtr); + + assert(markPtr->body.mark.changePtr != (void *) 0xdeadbeef); + assert(markPtr->refCount > 0); + + if (!retainPrivateMarks || !TkTextIsPrivateMark(markPtr)) { + markPtr->nextPtr = deletePtr; + markPtr->prevPtr = NULL; + deletePtr = markPtr; + } else { + const char *name = Tcl_GetHashKey(&sharedTextPtr->markTable, hPtr); + char *dup; + + markPtr->sectionPtr = NULL; + markPtr->prevPtr = NULL; + markPtr->nextPtr = retainedPtr; + dup = malloc(strlen(name) + 1); + markPtr->body.mark.ptr = PTR_TO_INT(strcpy(dup, name)); + MAKE_PRESERVED(markPtr); + retainedPtr = markPtr; + } + } + + markPtr = deletePtr; + while (markPtr) { + TkTextSegment *nextPtr = markPtr->nextPtr; + assert(TkTextIsSpecialMark(markPtr) || markPtr->refCount == 1); + MarkDeleteProc(sharedTextPtr, markPtr, DELETE_CLEANUP|TREE_GONE); + markPtr = nextPtr; + } + + Tcl_DeleteHashTable(&sharedTextPtr->markTable); + sharedTextPtr->numMarks = 0; + + if (retainPrivateMarks) { + Tcl_InitHashTable(&sharedTextPtr->markTable, TCL_STRING_KEYS); + + for (markPtr = retainedPtr; markPtr; markPtr = markPtr->nextPtr) { + ReactivateMark(sharedTextPtr, markPtr); + } + } else { + sharedTextPtr->numPrivateMarks = 0; + } + + return retainedPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUpdateCurrentMark -- + * + * If a position change of the "current" mark has been postponed + * we will do now the update. + * + * Results: + * None. + * + * Side effects: + * The "current" mark will change the position. + * + *---------------------------------------------------------------------- + */ + +void +TkTextUpdateCurrentMark( + TkSharedText *sharedTextPtr) /* Shared text resource. */ +{ + TkText *tPtr; + + assert(sharedTextPtr->haveToSetCurrentMark); + + sharedTextPtr->haveToSetCurrentMark = false; + + for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) { + if (tPtr->haveToSetCurrentMark) { + tPtr->haveToSetCurrentMark = false; + TkBTreeUnlinkSegment(sharedTextPtr, tPtr->currentMarkPtr); + TkBTreeLinkSegment(sharedTextPtr, tPtr->currentMarkPtr, &tPtr->currentMarkIndex); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextMakeStartEndMark -- + * + * Make (allocate) a new start/end mark. + * + * Results: + * The return value is a pointer to the new mark. + * + * Side effects: + * A new mark is created. + * + *---------------------------------------------------------------------- + */ + +TkTextSegment * +TkTextMakeStartEndMark( + TkText *textPtr, /* can be NULL */ + Tk_SegType const *typePtr) +{ + TkTextSegment *markPtr = TkTextMakeMark(NULL, NULL); + + assert(typePtr == &tkTextLeftMarkType || typePtr == &tkTextRightMarkType); + + markPtr->typePtr = typePtr; + markPtr->startEndMarkFlag = true; + markPtr->privateMarkFlag = true; + markPtr->body.mark.textPtr = textPtr; + return markPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextMakeMark -- + * + * Make (allocate) a new mark, the gravity default to right. + * + * Results: + * The return value is a pointer to the new mark. + * + * Side effects: + * A new mark is created. + * + *---------------------------------------------------------------------- + */ + +static TkTextSegment * +MakeMark( + TkText *textPtr) /* Text widget in which to create mark. */ +{ + TkTextSegment *markPtr; + + markPtr = calloc(1, SEG_SIZE(TkTextMark)); + NEW_SEGMENT(markPtr); + markPtr->typePtr = &tkTextRightMarkType; + markPtr->refCount = 1; + markPtr->body.mark.textPtr = textPtr; + DEBUG_ALLOC(tkTextCountNewSegment++); + return markPtr; +} + +TkTextSegment * +TkTextMakeMark( + TkText *textPtr, /* Text widget in which to create mark, can be NULL. */ + const char *name) /* Name of this mark, can be NULL. */ +{ + TkTextSegment *markPtr; + Tcl_HashEntry *hPtr; + int isNew; + + assert(!name || textPtr); + assert(!name || strcmp(name, "insert") != 0); + assert(!name || strcmp(name, "current") != 0); + + if (!name) { + return MakeMark(textPtr); + } + + hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name, &isNew); + + if (isNew) { + markPtr = MakeMark(textPtr); + markPtr->body.mark.ptr = PTR_TO_INT(hPtr); + Tcl_SetHashValue(hPtr, markPtr); + textPtr->sharedTextPtr->numMarks += 1; + } else { + markPtr = Tcl_GetHashValue(hPtr); + } + + return markPtr; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextMakeNewMark -- + * + * Make (allocate) a new mark, the gravity default to right. This + * function will return NULL if the mark name already exists. + * + * Results: + * The return value is a pointer to the new mark, and will be NULL + * if the mark already exists. + * + * Side effects: + * A new mark is created. + * + *---------------------------------------------------------------------- + */ + +TkTextSegment * +TkTextMakeNewMark( + TkSharedText *sharedTextPtr, /* Shared text resource. */ + const char *name) /* Name of this mark. */ +{ + TkTextSegment *markPtr; + Tcl_HashEntry *hPtr; + int isNew; + + assert(name); + + hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, name, &isNew); + + if (!isNew) { + return NULL; + } + + markPtr = MakeMark(NULL); + markPtr->body.mark.ptr = PTR_TO_INT(hPtr); + markPtr->normalMarkFlag = true; + Tcl_SetHashValue(hPtr, markPtr); + sharedTextPtr->numMarks += 1; + + return markPtr; +} + +/* + *---------------------------------------------------------------------- + * + * ChangeGravity -- + * + * Change the gravity of a given mark. + * + * Results: + * None. + * + * Side effects: + * Reset the type pointer of the mark, and set the undo information. + * + *---------------------------------------------------------------------- + */ + +static TkTextMarkChange * +MakeChangeItem( + TkSharedText *sharedTextPtr, + TkTextSegment *markPtr) +{ + TkTextMarkChange *changePtr = markPtr->body.mark.changePtr; + + assert(TkTextIsNormalMark(markPtr)); + + if (!changePtr) { + if (sharedTextPtr->undoMarkListCount == sharedTextPtr->undoMarkListSize) { + sharedTextPtr->undoMarkListSize = MAX(20u, 2*sharedTextPtr->undoMarkListSize); + sharedTextPtr->undoMarkList = realloc(sharedTextPtr->undoMarkList, + sharedTextPtr->undoMarkListSize * sizeof(sharedTextPtr->undoMarkList[0])); + } + changePtr = &sharedTextPtr->undoMarkList[sharedTextPtr->undoMarkListCount++]; + memset(changePtr, 0, sizeof(*changePtr)); + markPtr->body.mark.changePtr = changePtr; + (changePtr->markPtr = markPtr)->refCount += 1; + } + + return changePtr; +} + +static TkTextUndoToken * +MakeUndoToggleGravity( + TkSharedText *sharedTextPtr, + TkTextSegment *markPtr, + const Tk_SegType *oldTypePtr) +{ + assert(TkTextIsNormalMark(markPtr)); + + sharedTextPtr->undoStackEvent = true; + + if (!markPtr->body.mark.changePtr + || (!markPtr->body.mark.changePtr->setMark + && !markPtr->body.mark.changePtr->toggleGravity)) { + TkTextMarkChange *changePtr = MakeChangeItem(sharedTextPtr, markPtr); + UndoTokenToggleGravity *token; + + token = calloc(1, sizeof(UndoTokenToggleGravity)); + token->undoType = &undoTokenToggleGravityType; + (token->markPtr = markPtr)->refCount += 1; + DEBUG_ALLOC(tkTextCountNewUndoToken++); + changePtr->toggleGravity = (TkTextUndoToken *) token; + changePtr->savedMarkType = oldTypePtr; + sharedTextPtr->lastUndoTokenType = -1; + return (TkTextUndoToken *) token; + } + + return NULL; +} + +static void +ChangeGravity( + TkSharedText *sharedTextPtr, /* Shared text resource. */ + TkText *textPtr, /* The text widget, can be NULL. */ + TkTextSegment *markPtr, /* Change toggle of this mark. */ + const Tk_SegType *newTypePtr, /* This is the new toggle type. */ + TkTextUndoInfo *undoInfo) /* Undo information, can be NULL */ +{ + const Tk_SegType *oldTypePtr; + bool isNormalMark; + + assert(markPtr); + assert(markPtr->typePtr->group == SEG_GROUP_MARK); + assert(sharedTextPtr); + assert(!undoInfo || TkTextIsNormalMark(markPtr)); + + oldTypePtr = markPtr->typePtr; + markPtr->typePtr = newTypePtr; + isNormalMark = TkTextIsNormalMark(markPtr); + + if (!sharedTextPtr->steadyMarks) { + if (!textPtr || markPtr != textPtr->insertMarkPtr) { + /* + * We must re-insert the mark, the old rules of gravity may force + * a shuffle of the existing marks. + */ + + TkTextIndex index; + + if (textPtr) { + TkTextIndexClear(&index, textPtr); + } else { + TkTextIndexClear2(&index, NULL, sharedTextPtr->tree); + } + TkTextIndexSetSegment(&index, markPtr); + TkTextIndexToByteIndex(&index); + TkBTreeUnlinkSegment(sharedTextPtr, markPtr); + TkBTreeLinkSegment(sharedTextPtr, markPtr, &index); + } + + if (isNormalMark) { + TkTextUpdateAlteredFlag(sharedTextPtr); + } + } + + if (undoInfo && isNormalMark) { + undoInfo->token = MakeUndoToggleGravity(sharedTextPtr, markPtr, oldTypePtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * UnsetMark -- + * + * Unset given mark. + * + * Results: + * None. + * + * Side effects: + * Free some memory, and add a token to the undo/redo stack. + * + *---------------------------------------------------------------------- + */ + +static void +UnsetMark( + TkSharedText *sharedTextPtr, + TkTextSegment *markPtr, + TkTextUndoInfo *redoInfo) +{ + assert(markPtr); + assert(markPtr->typePtr->group == SEG_GROUP_MARK); + assert(!TkTextIsSpecialMark(markPtr)); + assert(!TkTextIsPrivateMark(markPtr) || !redoInfo); + + if (redoInfo) { + RedoTokenSetMark *token; + TkTextMarkChange *changePtr; + + if ((changePtr = markPtr->body.mark.changePtr)) { + if (changePtr->toggleGravity) { + TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->toggleGravity, 0); + changePtr->toggleGravity = NULL; + } + if (changePtr->moveMark) { + free(changePtr->moveMark); + changePtr->moveMark = NULL; + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(markPtr->refCount > 1); + markPtr->refCount -= 1; + } + if (changePtr->setMark) { + free(changePtr->setMark); + changePtr->setMark = NULL; + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(markPtr->refCount > 1); + markPtr->refCount -= 1; + } + } + + memset(redoInfo, 0, sizeof(*redoInfo)); + token = malloc(sizeof(RedoTokenSetMark)); + token->undoType = &redoTokenSetMarkType; + markPtr->refCount += 1; + token->markPtr = markPtr; + MARK_POINTER(token->markPtr); + TkBTreeMakeUndoIndex(sharedTextPtr, markPtr, &token->index); + DEBUG_ALLOC(tkTextCountNewUndoToken++); + redoInfo->token = (TkTextUndoToken *) token; + redoInfo->byteSize = 0; + } + + sharedTextPtr->undoStackEvent = true; + sharedTextPtr->lastUndoTokenType = -1; + TkBTreeUnlinkSegment(sharedTextPtr, markPtr); + MarkDeleteProc(sharedTextPtr, markPtr, DELETE_CLEANUP); +} + +/* + *---------------------------------------------------------------------- + * + * TriggerWatchCursor -- + * + * Trigger the watch command for movements of the insert cursor. + * + * Results: + * Returns 'false' if the referenced widget has been destroyed, otherwise + * 'true' will be returned. + * + * Side effects: + * It might happen that the receiver of the "watch" command is destroying the widget. + * + *---------------------------------------------------------------------- + */ + +static bool +TriggerWatchCursor( + TkText *textPtr, + const TkTextIndex *oldCursorIndexPtr, + const TkTextIndex *newCursorIndexPtr) /* NULL is allowed. */ +{ + TkTextIndex index, newIndex; + char idx[2][TK_POS_CHARS]; + TkTextTag *tagPtr; + TkTextTag *tagArrayBuffer[32]; + TkTextTag **tagArrayPtr; + unsigned tagArraySize; + unsigned numTags, i; + Tcl_DString buf; + bool rc; + + assert(oldCursorIndexPtr); + assert(!TkTextIndexIsEmpty(oldCursorIndexPtr)); + assert(!newCursorIndexPtr || !TkTextIndexIsEmpty(newCursorIndexPtr)); + + if (!newCursorIndexPtr) { + TkTextIndexClear(&newIndex, textPtr); + TkTextIndexSetSegment(&newIndex, textPtr->insertMarkPtr); + newCursorIndexPtr = &newIndex; + } + + if (TkTextIndexIsEqual(oldCursorIndexPtr, newCursorIndexPtr)) { + return true; + } + + Tcl_DStringInit(&buf); + if (TkTextIndexIsEmpty(oldCursorIndexPtr)) { + idx[0][0] = '\0'; + } else { + TkTextPrintIndex(textPtr, oldCursorIndexPtr, idx[0]); + } + TkTextPrintIndex(textPtr, newCursorIndexPtr, idx[1]); + if (textPtr->insertMarkPtr->typePtr == &tkTextLeftMarkType) { + index = *newCursorIndexPtr; + } else { + TkTextIndexBackChars(textPtr, newCursorIndexPtr, 1, &index, COUNT_INDICES); + } + + numTags = 0; + tagArrayPtr = tagArrayBuffer; + tagArraySize = sizeof(tagArrayBuffer)/sizeof(tagArrayBuffer[0]); + tagPtr = TkBTreeGetTags(&index, TK_TEXT_SORT_ASCENDING, NULL); + for ( ; tagPtr; tagPtr = tagPtr->nextPtr) { + if (numTags == tagArraySize) { + tagArraySize *= 2; + tagArrayPtr = realloc(tagArrayPtr == tagArrayBuffer ? NULL : tagArrayPtr, tagArraySize); + } + tagArrayPtr[numTags++] = tagPtr; + } + for (i = 0; i < numTags; ++i) { + Tcl_DStringAppendElement(&buf, tagArrayPtr[i]->name); + } + if (tagArrayPtr != tagArrayBuffer) { + free(tagArrayPtr); + } + + rc = TkTextTriggerWatchCmd(textPtr, "cursor", idx[0], idx[1], Tcl_DStringValue(&buf), + NULL, NULL, false); + Tcl_DStringFree(&buf); + return rc; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextReleaseUndoMarkTokens -- + * + * Release retained undo tokens for mark operations. + * + * Results: + * None. + * + * Side effects: + * Free some memory. + * + *---------------------------------------------------------------------- + */ + +void +TkTextReleaseUndoMarkTokens( + TkSharedText *sharedTextPtr, + TkTextMarkChange *changePtr) +{ + assert(sharedTextPtr); + assert(changePtr); + + if (!changePtr->markPtr) { + return; /* already released */ + } + + assert(changePtr->markPtr->body.mark.changePtr); + + if (changePtr->toggleGravity) { + free(changePtr->toggleGravity); + changePtr->toggleGravity = NULL; + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + } + if (changePtr->moveMark) { + free(changePtr->moveMark); + changePtr->moveMark = NULL; + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + } + if (changePtr->setMark) { + free(changePtr->setMark); + changePtr->setMark = NULL; + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + } + + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + changePtr->markPtr->body.mark.changePtr = NULL; + changePtr->markPtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextPushUndoMarkTokens -- + * + * Push retained undo tokens for mark operations onto the undo stack. + * + * Results: + * None. + * + * Side effects: + * Same as TkTextUndoPushItem. + * + *---------------------------------------------------------------------- + */ + +void +TkTextPushUndoMarkTokens( + TkSharedText *sharedTextPtr, + TkTextMarkChange *changePtr) +{ + assert(sharedTextPtr); + assert(sharedTextPtr->undoStack); + assert(changePtr); + assert(changePtr->markPtr); + assert(changePtr->markPtr->body.mark.changePtr == changePtr); + + if (changePtr->toggleGravity) { + UndoTokenToggleGravity *token = (UndoTokenToggleGravity *) changePtr->toggleGravity; + + if (changePtr->savedMarkType != token->markPtr->typePtr) { + TkTextUndoPushItem(sharedTextPtr->undoStack, (TkTextUndoToken *) token, 0); + } else { + free(token); + DEBUG_ALLOC(tkTextCountDestroyUndoToken++); + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + } + changePtr->toggleGravity = NULL; + } + if (changePtr->moveMark) { + TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->moveMark, 0); + changePtr->moveMark = NULL; + } + if (changePtr->setMark) { + TkTextUndoPushItem(sharedTextPtr->undoStack, changePtr->setMark, 0); + changePtr->setMark = NULL; + } + + assert(changePtr->markPtr->refCount > 1); + changePtr->markPtr->refCount -= 1; + changePtr->markPtr->body.mark.changePtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSetMark -- SetMark -- * * Set a mark to a particular position, creating a new mark if one * doesn't already exist. * + * Take care when setting the "insert" mark. In this case it might + * happen that the receiver of the "watch" command is destroying the + * widget. In this case this function will return NULL (otherwise + * this function will always return non-NULL in case of setting the + * "insert" mark). + * + * Note that parameter indexPtr may be adjusted if the position + * is outside of visible text, and we are setting the "insert" + * mark. + * * Results: * The return value is a pointer to the mark that was just set. * @@ -267,32 +1659,118 @@ TkTextMarkCmd( *---------------------------------------------------------------------- */ -TkTextSegment * -TkTextSetMark( +static TkTextSegment * +SetMark( TkText *textPtr, /* Text widget in which to create mark. */ const char *name, /* Name of mark to set. */ + const Tk_SegType *typePtr, /* Sepcifies the gravity, either tkTextLeftMarkType, + * tkTextRightMarkType, or NULL. */ TkTextIndex *indexPtr) /* Where to set mark. */ { Tcl_HashEntry *hPtr = NULL; + TkSharedText *sharedTextPtr; TkTextSegment *markPtr; - TkTextIndex insertIndex; - int isNew, widgetSpecific; - - if (!strcmp(name, "insert")) { - widgetSpecific = 1; - markPtr = textPtr->insertMarkPtr; - isNew = (markPtr == NULL ? 1 : 0); - } else if (!strcmp(name, "current")) { - widgetSpecific = 2; - markPtr = textPtr->currentMarkPtr; - isNew = (markPtr == NULL ? 1 : 0); - } else { - widgetSpecific = 0; - hPtr = Tcl_CreateHashEntry(&textPtr->sharedTextPtr->markTable, name, - &isNew); + TkTextIndex oldIndex; + TkTextUndoIndex undoIndex; + bool pushUndoToken; + bool widgetSpecific; + const Tk_SegType *oldTypePtr = NULL; + + assert(textPtr); + assert(indexPtr->textPtr == textPtr); + + widgetSpecific = false; + markPtr = NULL; + + switch (*name) { + case 'i': + if (strcmp(name, "insert") == 0) { + widgetSpecific = true; + markPtr = textPtr->insertMarkPtr; + if (TkTextIsElided(indexPtr)) { + TkTextSkipElidedRegion(indexPtr); + } + } + break; + case 'c': + if (strcmp(name, "current") == 0) { + widgetSpecific = true; + markPtr = textPtr->currentMarkPtr; + } + break; + } + + sharedTextPtr = textPtr->sharedTextPtr; + TkTextIndexClear(&oldIndex, textPtr); + + if (!widgetSpecific) { + int dummy; + hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, name, &dummy); markPtr = Tcl_GetHashValue(hPtr); } - if (!isNew) { + + if (!markPtr) { + if (name[0] == '#' && name[1] == '#' && name[2] == 'I') { +#ifdef TK_IS_64_BIT_ARCH + static const size_t length = 32 + 2*sizeof(uint64_t); +#else /* if defined(TK_IS_32_BIT_ARCH) */ + static const size_t length = 32 + 2*sizeof(uint32_t); +#endif /* TK_IS_64_BIT_ARCH */ + + void *sPtr, *tPtr; + unsigned num; + + if (strlen(name) == length && sscanf(name, "##ID##%p##%p##%u##", &sPtr, &tPtr, &num) == 3) { + assert(hPtr); + Tcl_DeleteHashEntry(hPtr); + return NULL; /* this is an expired generated mark */ + } + } + + if (widgetSpecific) { + /* + * This is a special mark. + */ + markPtr = MakeMark(textPtr); + if (*name == 'i') { /* "insert" */ + textPtr->insertMarkPtr = markPtr; + markPtr->insertMarkFlag = true; + } else { /* "current" */ + textPtr->currentMarkPtr = markPtr; + markPtr->currentMarkFlag = true; + } + pushUndoToken = false; + } else { + markPtr = MakeMark(NULL); + markPtr->body.mark.ptr = PTR_TO_INT(hPtr); + markPtr->normalMarkFlag = true; + Tcl_SetHashValue(hPtr, markPtr); + textPtr->sharedTextPtr->numMarks += 1; + pushUndoToken = sharedTextPtr->steadyMarks && sharedTextPtr->undoStack; + } + } else { + const TkTextSegment *segPtr; + + TkTextMarkSegToIndex(textPtr, markPtr, &oldIndex); + + if (markPtr == textPtr->insertMarkPtr && TkTextIndexIsEndOfText(indexPtr)) { + /* + * The index is outside of visible text, so backup one char. + */ + TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, COUNT_INDICES); + } + + if (!sharedTextPtr->steadyMarks + && (!typePtr || typePtr == markPtr->typePtr) + && TkTextIndexIsEqual(&oldIndex, indexPtr)) { + return markPtr; /* this mark did not change the position */ + } + + TkTextIndexToByteIndex(&oldIndex); + pushUndoToken = sharedTextPtr->steadyMarks + && sharedTextPtr->undoStack + && TkTextIsNormalMark(markPtr); + /* * If this is the insertion point that's being moved, be sure to force * a display update at the old position. Also, don't let the insertion @@ -300,71 +1778,243 @@ TkTextSetMark( */ if (markPtr == textPtr->insertMarkPtr) { - TkTextIndex index, index2; - int nblines; + TkTextIndex index2; - TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES); + TkTextIndexToByteIndex(indexPtr); - /* - * While we wish to redisplay, no heights have changed, so no need - * to call TkTextInvalidateLineMetrics. - */ - - TkTextChanged(NULL, textPtr, &index, &index2); - - /* - * The number of lines in the widget is zero if and only if it is - * a partial peer with -startline == -endline, i.e. an empty - * peer. In this case the mark shall be set exactly at the given - * index, and not one character backwards (bug 3487407). - */ - - nblines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); - if ((TkBTreeLinesTo(textPtr, indexPtr->linePtr) == nblines) - && (nblines > 0)) { - TkTextIndexBackChars(NULL,indexPtr, 1, &insertIndex, - COUNT_INDICES); - indexPtr = &insertIndex; + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + /* + * Test whether cursor is inside the actual range. + */ + if (TkTextIndexRestrictToStartRange(&oldIndex) >= 0 + && TkTextIndexRestrictToEndRange(&oldIndex) <= 0 + && TkTextIndexForwChars(textPtr, &oldIndex, 1, &index2, COUNT_INDICES)) { + /* + * While we wish to redisplay, no heights have changed, so no need + * to call TkTextInvalidateLineMetrics. + */ + + /* + * TODO: this will do too much, but currently the implementation + * lacks on an efficient redraw functionality especially designed + * for cursor updates. + */ + TkTextChanged(NULL, textPtr, &oldIndex, &index2); + } } + } else if (markPtr == textPtr->currentMarkPtr) { + textPtr->haveToSetCurrentMark = false; + } else if (pushUndoToken) { + TkBTreeMakeUndoIndex(sharedTextPtr, markPtr, &undoIndex); } - TkBTreeUnlinkSegment(markPtr, markPtr->body.mark.linePtr); - } else { - markPtr = ckalloc(MSEG_SIZE); - markPtr->typePtr = &tkTextRightMarkType; - markPtr->size = 0; - markPtr->body.mark.textPtr = textPtr; - markPtr->body.mark.linePtr = indexPtr->linePtr; - markPtr->body.mark.hPtr = hPtr; - if (widgetSpecific == 0) { - Tcl_SetHashValue(hPtr, markPtr); - } else if (widgetSpecific == 1) { - textPtr->insertMarkPtr = markPtr; - } else { - textPtr->currentMarkPtr = markPtr; + + if ((segPtr = TkTextIndexGetSegment(indexPtr)) == markPtr) { + return markPtr; + } + + if (segPtr && segPtr->size > 1) { + /* because TkBTreeUnlinkSegment may invalidate this index */ + TkTextIndexToByteIndex(indexPtr); } + + TkBTreeUnlinkSegment(sharedTextPtr, markPtr); } - TkBTreeLinkSegment(markPtr, indexPtr); - /* - * If the mark is the insertion cursor, then update the screen at the - * mark's new location. - */ + if (typePtr && typePtr != markPtr->typePtr) { + oldTypePtr = markPtr->typePtr; + markPtr->typePtr = typePtr; + } + + /* this function will also update 'sectionPtr' */ + TkBTreeLinkSegment(sharedTextPtr, markPtr, indexPtr); + + if (pushUndoToken) { + TkTextMarkChange *changePtr; - if (markPtr == textPtr->insertMarkPtr) { - TkTextIndex index2; + changePtr = MakeChangeItem(sharedTextPtr, markPtr); - TkTextIndexForwChars(NULL, indexPtr, 1, &index2, COUNT_INDICES); + if (!changePtr->setMark && !changePtr->moveMark) { + if (TkTextIndexIsEmpty(&oldIndex)) { + UndoTokenSetMark *token; + + token = malloc(sizeof(UndoTokenSetMark)); + token->undoType = &undoTokenSetMarkType; + (token->markPtr = markPtr)->refCount += 1; + DEBUG_ALLOC(tkTextCountNewUndoToken++); + changePtr->setMark = (TkTextUndoToken *) token; + sharedTextPtr->undoStackEvent = true; + sharedTextPtr->lastUndoTokenType = -1; + oldTypePtr = NULL; + } else { + UndoTokenMoveMark *token; + + token = malloc(sizeof(UndoTokenMoveMark)); + token->undoType = &undoTokenMoveMarkType; + (token->markPtr = markPtr)->refCount += 1; + token->index = undoIndex; + DEBUG_ALLOC(tkTextCountNewUndoToken++); + changePtr->moveMark = (TkTextUndoToken *) token; + sharedTextPtr->undoStackEvent = true; + sharedTextPtr->lastUndoTokenType = -1; + } + } + if (oldTypePtr) { + MakeUndoToggleGravity(sharedTextPtr, markPtr, oldTypePtr); + } + } + + if (sharedTextPtr->steadyMarks && TkTextIsNormalMark(markPtr)) { + TkTextUpdateAlteredFlag(sharedTextPtr); + } + + if (textPtr->state == TK_TEXT_STATE_NORMAL) { /* - * While we wish to redisplay, no heights have changed, so no need to - * call TkTextInvalidateLineMetrics + * If the mark is the insertion cursor, then update the screen at the mark's new location. */ - TkTextChanged(NULL, textPtr, indexPtr, &index2); + if (markPtr == textPtr->insertMarkPtr) { + TkTextIndex index2; + + TkTextIndexForwChars(textPtr, indexPtr, 1, &index2, COUNT_INDICES); + + /* + * While we wish to redisplay, no heights have changed, so no need to + * call TkTextInvalidateLineMetrics. + */ + + /* TODO: this is very inefficient, it would be more appopriate to trigger + * a special cursor redraw function (see DisplayDLine in tkTextDisp). + * Instead of inserting a cursor chunk (not needed) we want to overlay + * with a cursor. This would speed up cursor movement. + */ + + TkTextChanged(NULL, textPtr, indexPtr, &index2); + + /* + * Finally trigger the "watch" command for the "insert" cursor, + * this must be the last action. + */ + + if (textPtr->watchCmd && !TriggerWatchCursor(textPtr, &oldIndex, indexPtr)) { + return NULL; /* the receiver did destroy the widget */ + } + } } + return markPtr; } + +TkTextSegment * +TkTextSetMark( + TkText *textPtr, /* Text widget in which to create mark. */ + const char *name, /* Name of mark to set. */ + TkTextIndex *indexPtr) /* Where to set mark. */ +{ + return SetMark(textPtr, name, NULL, indexPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextUnsetMark -- + * + * Unset (delete) given mark. + * + * Results: + * None. + * + * Side effects: + * A mark will be deleted. + * + *---------------------------------------------------------------------- + */ + +void +TkTextUnsetMark( + TkText *textPtr, /* Text widget in which to create mark. */ + TkTextSegment *markPtr) /* Delete this mark. */ +{ + TkTextUndoInfo undoInfo; + TkTextUndoInfo *undoInfoPtr = NULL; + bool isNormalMark = TkTextIsNormalMark(markPtr); + + assert(TkTextIsNormalMark(markPtr)); + + if (isNormalMark + && textPtr->sharedTextPtr->steadyMarks + && !TkTextUndoUndoStackIsFull(textPtr->sharedTextPtr->undoStack)) { + undoInfoPtr = &undoInfo; + } + UnsetMark(textPtr->sharedTextPtr, markPtr, undoInfoPtr); + if (isNormalMark) { + if (undoInfoPtr && undoInfo.token) { + TkTextPushUndoToken(textPtr->sharedTextPtr, undoInfo.token, 0); + } + if (textPtr->sharedTextPtr->steadyMarks) { + TkTextUpdateAlteredFlag(textPtr->sharedTextPtr); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextSaveCursorIndex -- + * + * Save the current position of the insert cursor, but only if + * it is not yet saved. Use this function only if a trigger of + * the "watch" command is wanted in case of cursor movement. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TkTextSaveCursorIndex( + TkText *textPtr) +{ + if (TkTextIndexIsEmpty(&textPtr->insertIndex)) { + TkTextIndexSetSegment(&textPtr->insertIndex, textPtr->insertMarkPtr); + TkTextIndexSave(&textPtr->insertIndex); + } +} + +/* + *---------------------------------------------------------------------- + * + * TkTextTriggerWatchCursor -- + * + * Trigger the watch command for movements of the insert cursor. + * + * Results: + * Returns 'false' if the referenced widget has been destroyed, otherwise + * 'true' will be returned. + * + * Side effects: + * It might happen that the receiver of the "watch" command is destroying the widget. + * + *---------------------------------------------------------------------- + */ + +bool +TkTextTriggerWatchCursor( + TkText *textPtr) +{ + assert(textPtr->watchCmd); + + if (TkTextIndexIsEmpty(&textPtr->insertIndex)) { + return true; + } + + TkTextIndexRebuild(&textPtr->insertIndex); + return TriggerWatchCursor(textPtr, &textPtr->insertIndex, NULL); +} /* *-------------------------------------------------------------- @@ -386,19 +2036,18 @@ TkTextSetMark( void TkTextMarkSegToIndex( - TkText *textPtr, /* Text widget containing mark. */ + TkText *textPtr, /* Text widget containing mark, can be NULL. */ TkTextSegment *markPtr, /* Mark segment. */ TkTextIndex *indexPtr) /* Index information gets stored here. */ { - TkTextSegment *segPtr; - - indexPtr->tree = textPtr->sharedTextPtr->tree; - indexPtr->linePtr = markPtr->body.mark.linePtr; - indexPtr->byteIndex = 0; - for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr; - segPtr = segPtr->nextPtr) { - indexPtr->byteIndex += segPtr->size; - } + assert(textPtr); + assert(markPtr); + assert(markPtr->sectionPtr); /* otherwise not linked */ + + TkTextIndexClear(indexPtr, textPtr); + /* disable range checks, because here it's is allowed that the index is out of range. */ + DEBUG(indexPtr->discardConsistencyCheck = true); + TkTextIndexSetSegment(indexPtr, markPtr); } /* @@ -410,16 +2059,16 @@ TkTextMarkSegToIndex( * name. * * Results: - * The return value is TCL_OK if "name" exists as a mark in the text - * widget and is located within its -starline/-endline range. In this + * The return value is 'true' if "name" exists as a mark in the text + * widget and is located within its -start/-end range. In this * case *indexPtr is filled in with the next segment who is after the - * mark whose size is non-zero. TCL_ERROR is returned if the mark - * doesn't exist in the text widget, or if it is out of its -starline/ - * -endline range. In this latter case *indexPtr still contains valid + * mark whose size is non-zero. 'false' is returned if the mark + * doesn't exist in the text widget, or if it is out of its -start/ + * -end range. In this latter case *indexPtr still contains valid * information, in particular TkTextMarkNameToIndex called with the * "insert" or "current" mark name may return TCL_ERROR, but *indexPtr - * contains the correct index of this mark before -startline or after - * -endline. + * contains the correct index of this mark before -start or after + * -end. * * Side effects: * None. @@ -427,58 +2076,148 @@ TkTextMarkSegToIndex( *-------------------------------------------------------------- */ -int +static bool +MarkToIndex( + TkText *textPtr, /* Text widget containing mark. */ + TkTextSegment *markPtr, /* Pointer to mark segment. */ + TkTextIndex *indexPtr) /* Index information gets stored here. */ +{ + TkTextIndex index; + + assert(textPtr); + TkTextMarkSegToIndex(textPtr, markPtr, indexPtr); + indexPtr->textPtr = textPtr; + + /* + * If indexPtr refers to somewhere outside the -start/-end range + * limits of the widget, error out since the mark indeed is not + * reachable from this text widget (it may be reachable from a peer) + * (bug 1630271). + */ + + if (textPtr->startMarker != textPtr->sharedTextPtr->startMarker) { + TkTextIndexClear(&index, textPtr); + TkTextIndexSetSegment(&index, textPtr->startMarker); + if (TkTextIndexCompare(indexPtr, &index) < 0) { + return false; + } + } + if (textPtr->endMarker != textPtr->sharedTextPtr->endMarker) { + if (TkTextIndexGetLine(indexPtr) == textPtr->endMarker->sectionPtr->linePtr) { + TkTextIndexClear(&index, textPtr); + TkTextIndexSetSegment(&index, textPtr->endMarker); + } else { + TkTextIndexSetupToEndOfText(&index, textPtr, indexPtr->tree); + } + if (TkTextIndexCompare(indexPtr, &index) > 0) { + return false; + } + } + + return true; +} + +bool TkTextMarkNameToIndex( TkText *textPtr, /* Text widget containing mark. */ const char *name, /* Name of mark. */ TkTextIndex *indexPtr) /* Index information gets stored here. */ { TkTextSegment *segPtr; - TkTextIndex index; - int start, end; - if (textPtr == NULL) { - return TCL_ERROR; - } + assert(textPtr); - if (!strcmp(name, "insert")) { + if (strcmp(name, "insert") == 0) { segPtr = textPtr->insertMarkPtr; - } else if (!strcmp(name, "current")) { + } else if (strcmp(name, "current") == 0) { segPtr = textPtr->currentMarkPtr; } else { - Tcl_HashEntry *hPtr = - Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name); + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, name); if (hPtr == NULL) { - return TCL_ERROR; + return false; } segPtr = Tcl_GetHashValue(hPtr); } - TkTextMarkSegToIndex(textPtr, segPtr, indexPtr); - /* If indexPtr refers to somewhere outside the -startline/-endline - * range limits of the widget, error out since the mark indeed is not - * reachable from this text widget (it may be reachable from a peer) - * (bug 1630271). - */ + return MarkToIndex(textPtr, segPtr, indexPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TkTextInspectUndoMarkItem -- + * + * Inspect retained undo token. + * + * Results: + * None. + * + * Side effects: + * Memory is allocated for the result. + * + *---------------------------------------------------------------------- + */ - if (textPtr->start != NULL) { - start = TkBTreeLinesTo(NULL, textPtr->start); - TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0, - &index); - if (TkTextIndexCmp(indexPtr, &index) < 0) { - return TCL_ERROR; - } +void +TkTextInspectUndoMarkItem( + const TkSharedText *sharedTextPtr, + const TkTextMarkChange *changePtr, + Tcl_Obj* objPtr) +{ + assert(changePtr); + + if (changePtr->setMark) { + Tcl_ListObjAppendElement(NULL, objPtr, + changePtr->setMark->undoType->inspectProc(sharedTextPtr, changePtr->setMark)); } - if (textPtr->end != NULL) { - end = TkBTreeLinesTo(NULL, textPtr->end); - TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0, - &index); - if (TkTextIndexCmp(indexPtr, &index) > 0) { - return TCL_ERROR; - } + if (changePtr->moveMark) { + Tcl_ListObjAppendElement(NULL, objPtr, + changePtr->moveMark->undoType->inspectProc(sharedTextPtr, changePtr->moveMark)); } - return TCL_OK; + if (changePtr->toggleGravity) { + Tcl_ListObjAppendElement(NULL, objPtr, + changePtr->toggleGravity->undoType->inspectProc(sharedTextPtr, + changePtr->toggleGravity)); + } +} + +/* + *-------------------------------------------------------------- + * + * MarkInspectProc -- + * + * This function is invoked to build the information for + * "inspect". + * + * Results: + * Return a TCL object containing the information for + * "inspect". + * + * Side effects: + * Storage is allocated. + * + *-------------------------------------------------------------- + */ + +static Tcl_Obj * +MarkInspectProc( + const TkSharedText *sharedTextPtr, + const TkTextSegment *segPtr) +{ + Tcl_Obj *objPtr = Tcl_NewObj(); + const char *gravity = (segPtr->typePtr == &tkTextLeftMarkType) ? "left" : "right"; + const char *name; + + assert(!TkTextIsPrivateMark(segPtr)); + assert(!IS_PRESERVED(segPtr)); + + name = TkTextMarkName(sharedTextPtr, NULL, segPtr); + assert(name); + + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(gravity, -1)); + Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewStringObj(name, -1)); + return objPtr; } /* @@ -487,54 +2226,260 @@ TkTextMarkNameToIndex( * MarkDeleteProc -- * * This function is invoked by the text B-tree code whenever a mark lies - * in a range of characters being deleted. + * in a range being deleted. * * Results: - * Returns 1 to indicate that deletion has been rejected. + * Returns false to indicate that deletion has been rejected. Otherwise, if + * deletion has been done (virtually) because DELETE_MARKS is set, true + * will be returned. If the reference count of this segment is not going + * to zero then this segment will be preserved for undo. * * Side effects: - * None (even if the whole tree is being deleted we don't free up the - * mark; it will be done elsewhere). + * None when this functions returns false (even if the whole tree is being + * deleted we don't free up the mark; it will be done elsewhere). But + * if a deletion has been done the hash entry of this mark will be + * removed. * *-------------------------------------------------------------- */ - /* ARGSUSED */ -static int +static bool MarkDeleteProc( + TkSharedText *sharedTextPtr,/* Handle to shared text resource. */ TkTextSegment *segPtr, /* Segment being deleted. */ - TkTextLine *linePtr, /* Line containing segment. */ - int treeGone) /* Non-zero means the entire tree is being - * deleted, so everything must get cleaned - * up. */ + int flags) /* The deletion flags. */ { - return 1; + assert(segPtr->refCount > 0); + + if (TkTextIsSpecialMark(segPtr)) { + assert(!segPtr->body.mark.changePtr); + return false; + } + + if (TkTextIsPrivateMark(segPtr)) { + assert(!segPtr->body.mark.changePtr); + if (!(flags & DELETE_CLEANUP)) { + return false; + } + if (--segPtr->refCount == 0) { + DEBUG(segPtr->body.mark.changePtr = (void *) 0xdeadbeef); + if (!(flags & TREE_GONE)) { + Tcl_DeleteHashEntry(GET_HPTR(segPtr)); + } + FREE_SEGMENT(segPtr); + DEBUG_ALLOC(tkTextCountDestroySegment++); + } + return true; + } + + if (!(flags & (DELETE_CLEANUP|DELETE_MARKS|TREE_GONE))) { + return false; + } + + assert(segPtr->body.mark.ptr); + + if (segPtr->body.mark.changePtr) { + unsigned index; + + assert(sharedTextPtr->steadyMarks); + index = segPtr->body.mark.changePtr - sharedTextPtr->undoMarkList; + TkTextReleaseUndoMarkTokens(sharedTextPtr, segPtr->body.mark.changePtr); + memmove(sharedTextPtr->undoMarkList + index, sharedTextPtr->undoMarkList + index + 1, + --sharedTextPtr->undoMarkListCount - index); + assert(!segPtr->body.mark.changePtr); + } + + if (--segPtr->refCount == 0) { + if (IS_PRESERVED(segPtr)) { + assert(sharedTextPtr->steadyMarks); + free(GET_NAME(segPtr)); + } else { + Tcl_DeleteHashEntry(GET_HPTR(segPtr)); + sharedTextPtr->numMarks -= 1; + } + DEBUG(segPtr->body.mark.changePtr = (void *) 0xdeadbeef); + FREE_SEGMENT(segPtr); + DEBUG_ALLOC(tkTextCountDestroySegment++); + } else if (!IS_PRESERVED(segPtr)) { + if (sharedTextPtr->steadyMarks) { + /* + * This case should only happen if this mark belongs to undo/redo stack. + * We have to preserve the mark if not already preserved. + */ + + Tcl_HashEntry *hPtr = GET_HPTR(segPtr); + const char *name = Tcl_GetHashKey(&sharedTextPtr->markTable, hPtr); + unsigned size = strlen(name) + 1; + + assert(sharedTextPtr->steadyMarks); + segPtr->body.mark.ptr = PTR_TO_INT(memcpy(malloc(size), name, size)); + MAKE_PRESERVED(segPtr); + Tcl_DeleteHashEntry(hPtr); + sharedTextPtr->numMarks -= 1; + } else { + /* + * It seems that we have a bug with reference counting. So print a warning + * and delete it anyway. + */ + + fprintf(stderr, "reference count of mark '%s' is %d (should be zero)\n", + TkTextMarkName(sharedTextPtr, NULL, segPtr), segPtr->refCount); + Tcl_DeleteHashEntry(GET_HPTR(segPtr)); + sharedTextPtr->numMarks -= 1; + FREE_SEGMENT(segPtr); + DEBUG_ALLOC(tkTextCountDestroySegment++); + } + } + + return true; } /* *-------------------------------------------------------------- * - * MarkCleanupProc -- + * MarkRestoreProc -- * - * This function is invoked by the B-tree code whenever a mark segment is - * moved from one line to another. + * This function is called when a mark segment will be resused + * from the undo chain. But a re-use is possible only if option + * -steadymarks is enabled, otherwise the segment will be + * deleted instead if this is a preserved mark. * * Results: * None. * * Side effects: - * The linePtr field of the segment gets updated. + * Either the name of the mark will be freed, and the mark + * will be re-entered into the hash table, or it will be + * deleted. * *-------------------------------------------------------------- */ -static TkTextSegment * -MarkCleanupProc( - TkTextSegment *markPtr, /* Mark segment that's being moved. */ - TkTextLine *linePtr) /* Line that now contains segment. */ +static bool +MarkRestoreProc( + TkSharedText *sharedTextPtr,/* Handle to shared text resource. */ + TkTextSegment *segPtr) /* Segment to restore. */ { - markPtr->body.mark.linePtr = linePtr; - return markPtr; + assert(TkTextIsNormalMark(segPtr)); + + if (IS_PRESERVED(segPtr)) { + Tcl_HashEntry *hPtr; + int isNew; + + if (!sharedTextPtr->steadyMarks) { + MarkDeleteProc(sharedTextPtr, segPtr, DELETE_CLEANUP); + return false; + } + + hPtr = Tcl_CreateHashEntry(&sharedTextPtr->markTable, GET_NAME(segPtr), &isNew); + assert(isNew); + Tcl_SetHashValue(hPtr, segPtr); + sharedTextPtr->numMarks += 1; + free(GET_NAME(segPtr)); + segPtr->body.mark.ptr = PTR_TO_INT(hPtr); + } + + return true; +} + +/* + *-------------------------------------------------------------- + * + * MarkCheckProc -- + * + * This function is invoked by the B-tree code to perform consistency + * checks on mark segments. + * + * Results: + * None. + * + * Side effects: + * The function panics if it detects anything wrong with + * the mark. + * + *-------------------------------------------------------------- + */ + +static void +MarkCheckProc( + const TkSharedText *sharedTextPtr, /* Handle to shared text resource. */ + const TkTextSegment *markPtr) /* Segment to check. */ +{ + if (!markPtr->nextPtr) { + Tcl_Panic("MarkCheckProc: mark is last segment in line"); + } + if (markPtr->size != 0) { + Tcl_Panic("MarkCheckProc: mark has size %d", markPtr->size); + } + if (!markPtr->insertMarkFlag + && !markPtr->currentMarkFlag + && !markPtr->privateMarkFlag + && !markPtr->normalMarkFlag) { + Tcl_Panic("MarkCheckProc: mark is not specialized"); + } + if (markPtr->insertMarkFlag + + markPtr->currentMarkFlag + + markPtr->privateMarkFlag + + markPtr->normalMarkFlag > 1) { + Tcl_Panic("MarkCheckProc: mark has more than one specialization"); + } + if (markPtr->startEndMarkFlag && !markPtr->privateMarkFlag) { + Tcl_Panic("MarkCheckProc: start/end marks have to be private"); + } + + if (markPtr->body.mark.changePtr) { + /* + * Private marks and special marks will not have undo/redo data. + */ + + if (TkTextIsSpecialOrPrivateMark(markPtr)) { + Tcl_Panic("MarkCheckProc: private/special marks should not have undo/redo data"); + } + } + + /* + * The special marks ("insert", "current") are not in the hash table, + * the same with the start/end markers. + */ + + if (markPtr->body.mark.ptr) { + if (IS_PRESERVED(markPtr)) { + if (!sharedTextPtr->steadyMarks) { + Tcl_Panic("MarkCheckProc: preserved mark detected, though we don't have steady marks"); + } + else + { + Tcl_Panic("MarkCheckProc: detected preserved mark '%s' outside of the undo chain", + GET_NAME(markPtr)); + } + } else { + void *hPtr; + hPtr = Tcl_GetHashKey(&sharedTextPtr->markTable, (Tcl_HashEntry *) markPtr->body.mark.ptr); + if (!hPtr) { + Tcl_Panic("MarkCheckProc: couldn't find hash table entry for mark"); + } + } + } else if (!markPtr->insertMarkFlag && !markPtr->currentMarkFlag && !markPtr->startEndMarkFlag) { + Tcl_Panic("MarkCheckProc: mark is not in hash table, though it's not a special mark"); + } + + if (markPtr->startEndMarkFlag) { + if (markPtr->typePtr == &tkTextLeftMarkType) { + if (markPtr->prevPtr + && markPtr->prevPtr->typePtr->group == SEG_GROUP_MARK + && (!markPtr->prevPtr->startEndMarkFlag + || markPtr->prevPtr->typePtr != &tkTextLeftMarkType)) { + Tcl_Panic("MarkCheckProc: start marker must be leftmost mark"); + } + } else { + if (markPtr->nextPtr + && markPtr->nextPtr->typePtr->group == SEG_GROUP_MARK + && (!markPtr->nextPtr->startEndMarkFlag + || markPtr->nextPtr->typePtr != &tkTextRightMarkType)) { + Tcl_Panic("MarkCheckProc: end marker must be rightmost mark"); + } + } + } } /* @@ -558,31 +2503,26 @@ MarkCleanupProc( static int MarkLayoutProc( - TkText *textPtr, /* Text widget being layed out. */ - TkTextIndex *indexPtr, /* Identifies first character in chunk. */ + const TkTextIndex *indexPtr,/* Identifies first character in chunk. */ TkTextSegment *segPtr, /* Segment corresponding to indexPtr. */ - int offset, /* Offset within segPtr corresponding to - * indexPtr (always 0). */ - int maxX, /* Chunk must not occupy pixels at this - * position or higher. */ - int maxChars, /* Chunk must not include more than this many - * characters. */ - int noCharsYet, /* Non-zero means no characters have been - * assigned to this line yet. */ + int offset, /* Offset within segPtr corresponding to indexPtr (always 0). */ + int maxX, /* Chunk must not occupy pixels at this position or higher. */ + int maxChars, /* Chunk must not include more than this many characters. */ + bool noCharsYet, /* 'true' means no characters have been assigned to this line yet. */ TkWrapMode wrapMode, /* Not used. */ - register TkTextDispChunk *chunkPtr) - /* Structure to fill in with information about - * this chunk. The x field has already been - * set by the caller. */ + TkTextSpaceMode spaceMode, /* Not used. */ + TkTextDispChunk *chunkPtr) /* Structure to fill in with information about this chunk. The x + * field has already been set by the caller. */ { + TkText *textPtr = indexPtr->textPtr; + + assert(indexPtr->textPtr); + if (segPtr != textPtr->insertMarkPtr) { return -1; } - chunkPtr->displayProc = TkTextInsertDisplayProc; - chunkPtr->undisplayProc = InsertUndisplayProc; - chunkPtr->measureProc = NULL; - chunkPtr->bboxProc = NULL; + chunkPtr->layoutProcs = &layoutInsertProcs; chunkPtr->numBytes = 0; chunkPtr->minAscent = 0; chunkPtr->minDescent = 0; @@ -602,6 +2542,172 @@ MarkLayoutProc( /* *-------------------------------------------------------------- * + * TkTextDrawBlockCursor -- + * + * This function returns whether a block will be drawn, which covers + * characters. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +bool +TkTextDrawBlockCursor( + TkText *textPtr) /* The current text widget. */ +{ + if (textPtr->blockCursorType) { + if (textPtr->flags & HAVE_FOCUS) { + if ((textPtr->flags & INSERT_ON) || textPtr->selAttrs.border == textPtr->insertBorder) { + return true; + } + } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) { + return true; + } + } + return false; +} + +/* + *-------------------------------------------------------------- + * + * TkTextGetCursorBbox -- + * + * This function computes the cursor dimensions. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +bool +TkTextGetCursorBbox( + TkText *textPtr, /* The current text widget. */ + int *x, int *y, /* X/Y coordinate. */ + int *w, int *h) /* Width/height of cursor. */ +{ + TkTextIndex index; + Tcl_UniChar thisChar; + int cursorExtent; + int charWidth = -1; + + assert(textPtr); + assert(x); + assert(y); + assert(w); + assert(h); + + cursorExtent = MIN(textPtr->padX, textPtr->insertWidth/2); + TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); + + if (!TkTextIndexBbox(textPtr, &index, false, x, y, w, h, &charWidth, &thisChar)) { + int base, ix, iw; + + /* + * Testing whether the cursor is visible is not as trivial at it seems, + * see this example: + * + * ~~~~~~~~~~~~~~~~ + * | +-----+ + * | | | + * |~~~|~~~~~|~~~~~ + * | | | + * | a | | + * | | | + * | +-----+ + * + * At left side we have the visible cursor, then char "a", then a window. + * The region between the tilde bars is the visible screen. The cursor + * is positioned before char "a", and the bbox of char "a" is outside of + * the visible screen, so a simple test with TkTextIndexBbox() at char + * position "a" fails here. Now we have to test with the display line. + */ + + if (!TkTextGetDLineInfo(textPtr, &index, false, &ix, y, &iw, h, &base)) { + return false; /* cursor is not visible at all */ + } + + if (charWidth == -1) { + /* This char is outside of the screen, so use a default. */ + charWidth = textPtr->charWidth; + } + } + + /* + * Don't draw the full lengh of a tab, in this case we are drawing + * a cursor at the right boundary with a standard width. + */ + + if (thisChar == '\t') { + *x += charWidth; + charWidth = MIN(textPtr->charWidth, charWidth); + *x -= charWidth; + } + + if (textPtr->blockCursorType) { + /* NOTE: the block cursor extent is always rounded towards zero. */ + *w = charWidth + 2*cursorExtent; + } else { + *w = MIN(textPtr->insertWidth, textPtr->padX + cursorExtent); + } + + *x -= cursorExtent; + return true; +} + +/* + *-------------------------------------------------------------- + * + * TkTextGetCursorWidth -- + * + * This function computes the cursor dimensions. + * + * Results: + * None. + * + * Side effects: + * None. + * + *-------------------------------------------------------------- + */ + +unsigned +TkTextGetCursorWidth( + TkText *textPtr, /* The current text widget. */ + int *x, /* Shift x coordinate, can be NULL. */ + int *extent) /* Extent of cursor to left side, can be NULL. */ +{ + int width; + int cursorExtent = MIN(textPtr->padX, textPtr->insertWidth/2); + + if (extent) { + *extent = -cursorExtent; + } + + if (textPtr->blockCursorType) { + int ix, iy, ih; + + if (!TkTextGetCursorBbox(textPtr, &ix, &iy, &width, &ih)) { + return 0; /* cursor is not visible at all */ + } + } else { + width = MIN(textPtr->insertWidth, textPtr->padX + cursorExtent); + } + + return width; +} + +/* + *-------------------------------------------------------------- + * * TkTextInsertDisplayProc -- * * This function is called to display the insertion cursor. @@ -615,52 +2721,45 @@ MarkLayoutProc( *-------------------------------------------------------------- */ - /* ARGSUSED */ void TkTextInsertDisplayProc( TkText *textPtr, /* The current text widget. */ TkTextDispChunk *chunkPtr, /* Chunk that is to be drawn. */ - int x, /* X-position in dst at which to draw this - * chunk (may differ from the x-position in - * the chunk because of scrolling). */ - int y, /* Y-position at which to draw this chunk in - * dst (x-position is in the chunk itself). */ + int x, /* X-position in dst at which to draw this chunk (may differ + * from the x-position in the chunk because of scrolling). */ + int y, /* Y-position at which to draw this chunk in dst (x-position + * is in the chunk itself). */ int height, /* Total height of line. */ int baseline, /* Offset of baseline from y. */ Display *display, /* Display to use for drawing. */ Drawable dst, /* Pixmap or window in which to draw chunk. */ - int screenY) /* Y-coordinate in text window that - * corresponds to y. */ + int screenY) /* Y-coordinate in text window that corresponds to y. */ { - /* - * We have no need for the clientData. - */ - - /* TkText *textPtr = chunkPtr->clientData; */ - TkTextIndex index; int halfWidth = textPtr->insertWidth/2; - int rightSideWidth; - int ix = 0, iy = 0, iw = 0, ih = 0, charWidth = 0; - - if (textPtr->insertCursorType) { - TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexBbox(textPtr, &index, &ix, &iy, &iw, &ih, &charWidth); - rightSideWidth = charWidth + halfWidth; - } else { - rightSideWidth = halfWidth; - } + int width = TkTextGetCursorWidth(textPtr, &x, NULL); + int rightSideWidth = width + halfWidth - textPtr->insertWidth; if ((x + rightSideWidth) < 0) { /* - * The insertion cursor is off-screen. Indicate caret at 0,0 and - * return. + * The insertion cursor is off-screen. Indicate caret at 0,0 and return. */ Tk_SetCaretPos(textPtr->tkwin, 0, 0, height); return; } - Tk_SetCaretPos(textPtr->tkwin, x - halfWidth, screenY, height); + x -= halfWidth; + + Tk_SetCaretPos(textPtr->tkwin, x, screenY, height); + + if (POINTER_IS_MARKED(chunkPtr)) { + /* + * HACK: We are drawing into a tailored pixmap, because Tk has no clipping; + * see DisplayDLine(). + */ + + x = y = 0; + } /* * As a special hack to keep the cursor visible on mono displays (or @@ -670,37 +2769,31 @@ TkTextInsertDisplayProc( * the cursor. */ - if (textPtr->flags & GOT_FOCUS) { + if (textPtr->flags & HAVE_FOCUS) { if (textPtr->flags & INSERT_ON) { - Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, - x - halfWidth, y, charWidth + textPtr->insertWidth, - height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); - } else if (textPtr->selBorder == textPtr->insertBorder) { - Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border, - x - halfWidth, y, charWidth + textPtr->insertWidth, - height, 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y, + width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); + } else if (textPtr->selAttrs.border == textPtr->insertBorder) { + Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border, x, y, + width, height, 0, TK_RELIEF_FLAT); } } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_HOLLOW) { if (textPtr->insertBorderWidth < 1) { /* - * Hack to work around the fact that a "solid" border always - * paints in black. + * Hack to work around the fact that a "solid" border always paints in black. */ TkBorder *borderPtr = (TkBorder *) textPtr->insertBorder; - XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC, - x - halfWidth, y, charWidth + textPtr->insertWidth - 1, - height - 1); + XDrawRectangle(Tk_Display(textPtr->tkwin), dst, borderPtr->bgGC, x, y, + width - 1, height - 1); } else { - Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, - x - halfWidth, y, charWidth + textPtr->insertWidth, - height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); + Tk_Draw3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y, + width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); } } else if (textPtr->insertUnfocussed == TK_TEXT_INSERT_NOFOCUS_SOLID) { - Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, - x - halfWidth, y, charWidth + textPtr->insertWidth, height, - textPtr->insertBorderWidth, TK_RELIEF_RAISED); + Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder, x, y, + width, height, textPtr->insertBorderWidth, TK_RELIEF_RAISED); } } @@ -721,73 +2814,18 @@ TkTextInsertDisplayProc( *-------------------------------------------------------------- */ - /* ARGSUSED */ static void InsertUndisplayProc( TkText *textPtr, /* Overall information about text widget. */ TkTextDispChunk *chunkPtr) /* Chunk that is about to be freed. */ { + chunkPtr->clientData = NULL; return; } /* *-------------------------------------------------------------- * - * MarkCheckProc -- - * - * This function is invoked by the B-tree code to perform consistency - * checks on mark segments. - * - * Results: - * None. - * - * Side effects: - * The function panics if it detects anything wrong with - * the mark. - * - *-------------------------------------------------------------- - */ - -static void -MarkCheckProc( - TkTextSegment *markPtr, /* Segment to check. */ - TkTextLine *linePtr) /* Line containing segment. */ -{ - Tcl_HashSearch search; - Tcl_HashEntry *hPtr; - - if (markPtr->body.mark.linePtr != linePtr) { - Tcl_Panic("MarkCheckProc: markPtr->body.mark.linePtr bogus"); - } - - /* - * These two marks are not in the hash table - */ - - if (markPtr->body.mark.textPtr->insertMarkPtr == markPtr) { - return; - } - if (markPtr->body.mark.textPtr->currentMarkPtr == markPtr) { - return; - } - - /* - * Make sure that the mark is still present in the text's mark hash table. - */ - - for (hPtr = Tcl_FirstHashEntry( - &markPtr->body.mark.textPtr->sharedTextPtr->markTable, - &search); hPtr != markPtr->body.mark.hPtr; - hPtr = Tcl_NextHashEntry(&search)) { - if (hPtr == NULL) { - Tcl_Panic("MarkCheckProc couldn't find hash table entry for mark"); - } - } -} - -/* - *-------------------------------------------------------------- - * * MarkFindNext -- * * This function searches forward for the next mark. @@ -805,223 +2843,192 @@ static int MarkFindNext( Tcl_Interp *interp, /* For error reporting */ TkText *textPtr, /* The widget */ - Tcl_Obj *obj) /* The starting index or mark name */ + bool discardSpecial, /* Discard marks "insert" and "current" when searching= */ + Tcl_Obj* indexObj, /* Start search at this index. */ + const char *pattern, /* Result must match this pattern, can be NULL. */ + bool forward) /* Search forward. */ { TkTextIndex index; Tcl_HashEntry *hPtr; - register TkTextSegment *segPtr; - int offset; - const char *string = Tcl_GetString(obj); + TkTextSegment *segPtr; + TkTextLine *linePtr; + const char *indexStr; + + assert(textPtr); + assert(indexObj); - if (!strcmp(string, "insert")) { + if (TkTextIsDeadPeer(textPtr)) { + return TCL_OK; + } + + indexStr = Tcl_GetString(indexObj); + + if (strcmp(indexStr, "insert") == 0) { segPtr = textPtr->insertMarkPtr; - TkTextMarkSegToIndex(textPtr, segPtr, &index); - segPtr = segPtr->nextPtr; - } else if (!strcmp(string, "current")) { + linePtr = segPtr->sectionPtr->linePtr; + segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr; + } else if (strcmp(indexStr, "current") == 0) { segPtr = textPtr->currentMarkPtr; - TkTextMarkSegToIndex(textPtr, segPtr, &index); - segPtr = segPtr->nextPtr; + linePtr = segPtr->sectionPtr->linePtr; + segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr; } else { - hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string); - if (hPtr != NULL) { + if ((hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, indexStr))) { /* - * If given a mark name, return the next mark in the list of - * segments, even if it happens to be at the same character - * position. + * If given a mark name, return the next/prev mark in the list of segments, even + * if it happens to be at the same character position. */ segPtr = Tcl_GetHashValue(hPtr); - TkTextMarkSegToIndex(textPtr, segPtr, &index); - segPtr = segPtr->nextPtr; + if (!MarkToIndex(textPtr, segPtr, &index)) { + return SetResultNoMarkNamed(interp, indexStr); + } + linePtr = segPtr->sectionPtr->linePtr; + segPtr = forward ? segPtr->nextPtr : segPtr->prevPtr; } else { /* * For non-mark name indices we want to return any marks that are - * right at the index. + * right at the index, when searching forward, otherwise we do not + * return any marks that are right at the index. */ - if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) { + if (!TkTextGetIndexFromObj(interp, textPtr, indexObj, &index)) { return TCL_ERROR; } - for (offset = 0, segPtr = index.linePtr->segPtr; - segPtr != NULL && offset < index.byteIndex; - offset += segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body */ ; + segPtr = TkTextIndexGetFirstSegment(&index, NULL); + linePtr = segPtr->sectionPtr->linePtr; + + if (!forward) { + while (segPtr && segPtr->size == 0) { + segPtr = segPtr->prevPtr; + } } } } - while (1) { + if (forward) { + TkTextSegment *lastPtr = textPtr->endMarker; + /* - * segPtr points at the first possible candidate, or NULL if we ran - * off the end of the line. + * Ensure that we can reach 'lastPtr'. */ - for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr == &tkTextRightMarkType || - segPtr->typePtr == &tkTextLeftMarkType) { - Tcl_Obj *markName = GetMarkName(textPtr, segPtr); - - if (markName != NULL) { - Tcl_SetObjResult(interp, markName); - return TCL_OK; - } - } + while (lastPtr->size == 0) { + lastPtr = lastPtr->nextPtr; } - index.linePtr = TkBTreeNextLine(textPtr, index.linePtr); - if (index.linePtr == NULL) { - return TCL_OK; - } - index.byteIndex = 0; - segPtr = index.linePtr->segPtr; - } -} - -/* - *-------------------------------------------------------------- - * - * MarkFindPrev -- - * - * This function searches backwards for the previous mark. - * - * Results: - * A standard Tcl result, which is a mark name or an empty string. - * - * Side effects: - * None. - * - *-------------------------------------------------------------- - */ - -static int -MarkFindPrev( - Tcl_Interp *interp, /* For error reporting */ - TkText *textPtr, /* The widget */ - Tcl_Obj *obj) /* The starting index or mark name */ -{ - TkTextIndex index; - Tcl_HashEntry *hPtr; - register TkTextSegment *segPtr, *seg2Ptr, *prevPtr; - int offset; - const char *string = Tcl_GetString(obj); - if (!strcmp(string, "insert")) { - segPtr = textPtr->insertMarkPtr; - TkTextMarkSegToIndex(textPtr, segPtr, &index); - } else if (!strcmp(string, "current")) { - segPtr = textPtr->currentMarkPtr; - TkTextMarkSegToIndex(textPtr, segPtr, &index); - } else { - hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, string); - if (hPtr != NULL) { - /* - * If given a mark name, return the previous mark in the list of - * segments, even if it happens to be at the same character - * position. - */ - - segPtr = Tcl_GetHashValue(hPtr); - TkTextMarkSegToIndex(textPtr, segPtr, &index); - } else { + while (true) { /* - * For non-mark name indices we do not return any marks that are - * right at the index. + * 'segPtr' points at the first possible candidate, or NULL if we ran + * off the end of the line. */ - if (TkTextGetObjIndex(interp, textPtr, obj, &index) != TCL_OK) { - return TCL_ERROR; + for ( ; segPtr; segPtr = segPtr->nextPtr) { + if (segPtr == lastPtr) { + return TCL_OK; + } + if (TkTextIsNormalOrSpecialMark(segPtr)) { + const char *name = TkTextMarkName(textPtr->sharedTextPtr, textPtr, segPtr); + + if (name /* otherwise it's a special mark not belonging to this widget */ + && (!discardSpecial || !TkTextIsSpecialMark(segPtr)) + && (!pattern || Tcl_StringMatch(name, pattern))) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1)); + return TCL_OK; + } + } } - for (offset = 0, segPtr = index.linePtr->segPtr; - segPtr != NULL && offset < index.byteIndex; - offset += segPtr->size, segPtr = segPtr->nextPtr) { - /* Empty loop body */ + + if (!(linePtr = linePtr->nextPtr)) { + return TCL_OK; } + segPtr = linePtr->segPtr; } - } + } else { + TkTextSegment *firstPtr = textPtr->startMarker; - while (1) { /* - * segPtr points just past the first possible candidate, or at the - * beginning of the line. + * Ensure that we can reach 'firstPtr'. */ - for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr; - seg2Ptr != NULL && seg2Ptr != segPtr; - seg2Ptr = seg2Ptr->nextPtr) { - if (seg2Ptr->typePtr == &tkTextRightMarkType || - seg2Ptr->typePtr == &tkTextLeftMarkType) { - if (seg2Ptr->body.mark.hPtr == NULL) { - if (seg2Ptr != textPtr->currentMarkPtr && - seg2Ptr != textPtr->insertMarkPtr) { - /* - * This is an insert or current mark from a - * peer of textPtr. - */ - continue; - } - } - prevPtr = seg2Ptr; - } + while (firstPtr->prevPtr && firstPtr->prevPtr->size == 0) { + firstPtr = firstPtr->prevPtr; } - if (prevPtr != NULL) { - Tcl_Obj *markName = GetMarkName(textPtr, prevPtr); - if (markName != NULL) { - Tcl_SetObjResult(interp, markName); + while (true) { + /* + * 'segPtr' points at the first possible candidate, or NULL if we ran + * off the start of the line. + */ + + for ( ; segPtr; segPtr = segPtr->prevPtr) { + if (segPtr == firstPtr) { + return TCL_OK; + } + if (TkTextIsNormalOrSpecialMark(segPtr)) { + const char *name = TkTextMarkName(textPtr->sharedTextPtr, textPtr, segPtr); + + if (name /* otherwise it's a special mark not belonging to this widget */ + && (!discardSpecial || !TkTextIsSpecialMark(segPtr)) + && (!pattern || Tcl_StringMatch(name, pattern))) { + Tcl_SetObjResult(interp, Tcl_NewStringObj(name, -1)); + return TCL_OK; + } + } + } + + if (!(linePtr = linePtr->prevPtr)) { return TCL_OK; } + segPtr = linePtr->lastPtr; } - index.linePtr = TkBTreePreviousLine(textPtr, index.linePtr); - if (index.linePtr == NULL) { - return TCL_OK; - } - segPtr = NULL; } + + return TCL_OK; /* never reached */ } /* * ------------------------------------------------------------------------ * - * GetMarkName -- + * TkTextMarkName -- + * * Returns the name of the mark that is the given text segment, or NULL * if it is unnamed (i.e., a widget-specific mark that isn't "current" or * "insert"). * + * Results: + * The name of the mark, or NULL. + * + * Side effects: + * None. + * * ------------------------------------------------------------------------ */ -static Tcl_Obj * -GetMarkName( - TkText *textPtr, - TkTextSegment *segPtr) +const char * +TkTextMarkName( + const TkSharedText *sharedTextPtr, + const TkText *textPtr, /* can be NULL */ + const TkTextSegment *markPtr) { - const char *markName; - - if (segPtr == textPtr->currentMarkPtr) { - markName = "current"; - } else if (segPtr == textPtr->insertMarkPtr) { - markName = "insert"; - } else if (segPtr->body.mark.hPtr == NULL) { - /* - * Ignore widget-specific marks for the other widgets. This is either - * an insert or a current mark (markPtr->body.mark.hPtr actually - * receives NULL for these marks in TkTextSetMark). The insert and - * current marks for textPtr having already been tested above, the - * current segment is an insert or current mark from a peer of - * textPtr, which we don't want to return. - */ + assert(!IS_PRESERVED(markPtr)); + if (markPtr->insertMarkFlag) { + return !textPtr || textPtr == markPtr->body.mark.textPtr ? "insert" : NULL; + } + if (markPtr->currentMarkFlag) { + return !textPtr || textPtr == markPtr->body.mark.textPtr ? "current" : NULL; + } + if (!markPtr->body.mark.ptr) { return NULL; - } else { - markName = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, - segPtr->body.mark.hPtr); } - return Tcl_NewStringObj(markName, -1); + return Tcl_GetHashKey(&sharedTextPtr->markTable, (Tcl_HashEntry *) markPtr->body.mark.ptr); } /* * Local Variables: * mode: c * c-basic-offset: 4 - * fill-column: 78 + * fill-column: 105 * End: + * vi:set ts=8 sw=4: */ |