summaryrefslogtreecommitdiffstats
path: root/generic/tkText.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tkText.c')
-rw-r--r--generic/tkText.c1609
1 files changed, 1339 insertions, 270 deletions
diff --git a/generic/tkText.c b/generic/tkText.c
index 4ec38d7..b0b6a21 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -2,10 +2,10 @@
* tkText.c --
*
* This module provides a big chunk of the implementation of
- * multi-line editable text widgets for Tk. Among other things,
- * it provides the Tcl command interfaces to text widgets and
- * the display code. The B-tree representation of text is
- * implemented elsewhere.
+ * multi-line editable text widgets for Tk. Among other things, it
+ * provides the Tcl command interfaces to text widgets. The B-tree
+ * representation of text and its actual display are implemented
+ * elsewhere.
*
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
@@ -14,7 +14,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tkText.c,v 1.38 2003/05/27 15:35:52 vincentdarley Exp $
+ * RCS: @(#) $Id: tkText.c,v 1.39 2003/10/31 09:02:08 vincentdarley Exp $
*/
#include "default.h"
@@ -61,12 +61,16 @@ static Tk_OptionSpec optionSpecs[] = {
DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border),
0, (ClientData) DEF_TEXT_BG_MONO, 0},
{TK_OPTION_SYNONYM, "-bd", (char *) NULL, (char *) NULL,
- (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth", 0},
+ (char *) NULL, 0, -1, 0, (ClientData) "-borderwidth",
+ TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_SYNONYM, "-bg", (char *) NULL, (char *) NULL,
(char *) NULL, 0, -1, 0, (ClientData) "-background", 0},
+ {TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor",
+ "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1,
+ Tk_Offset(TkText, insertCursorType), 0, 0, 0},
{TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth),
- 0, 0, 0},
+ 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor),
TK_OPTION_NULL_OK, 0, 0},
@@ -76,7 +80,8 @@ static Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_SYNONYM, "-fg", "foreground", (char *) NULL,
(char *) NULL, 0, -1, 0, (ClientData) "-foreground", 0},
{TK_OPTION_FONT, "-font", "font", "Font",
- DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0, 0},
+ DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0,
+ TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0,
0, 0},
@@ -91,7 +96,7 @@ static Tk_OptionSpec optionSpecs[] = {
0, 0, 0},
{TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
"HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1,
- Tk_Offset(TkText, highlightWidth), 0, 0, 0},
+ Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
DEF_TEXT_INSERT_BG,
-1, Tk_Offset(TkText, insertBorder),
@@ -112,7 +117,8 @@ static Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo",
DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo), 0, 0, 0},
{TK_OPTION_PIXELS, "-padx", "padX", "Pad",
- DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0, 0},
+ DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0,
+ TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-pady", "padY", "Pad",
DEF_TEXT_PADY, -1, Tk_Offset(TkText, padY), 0, 0, 0},
{TK_OPTION_RELIEF, "-relief", "relief", "Relief",
@@ -132,29 +138,30 @@ static Tk_OptionSpec optionSpecs[] = {
DEF_TEXT_SET_GRID, -1, Tk_Offset(TkText, setGrid), 0, 0, 0},
{TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing",
DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing",
DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing",
DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0 },
+ TK_OPTION_DONT_SET_DEFAULT, 0 , TK_TEXT_LINE_GEOMETRY },
{TK_OPTION_STRING_TABLE, "-state", "state", "State",
DEF_TEXT_STATE, -1, Tk_Offset(TkText, state),
0, (ClientData) stateStrings, 0},
{TK_OPTION_STRING, "-tabs", "tabs", "Tabs",
DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1,
- TK_OPTION_NULL_OK, 0, 0},
+ TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus),
TK_OPTION_NULL_OK, 0, 0},
{TK_OPTION_BOOLEAN, "-undo", "undo", "Undo",
DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo), 0, 0 , 0},
{TK_OPTION_INT, "-width", "width", "Width",
- DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0, 0},
+ DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0,
+ TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap",
DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode),
- 0, (ClientData) wrapStrings, 0},
+ 0, (ClientData) wrapStrings, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd),
TK_OPTION_NULL_OK, 0, 0},
@@ -195,6 +202,14 @@ typedef struct SearchSpec {
int noCase; /* Case-insenstivive? */
int noLineStop; /* If not set, a regexp search will
* use the TCL_REG_NLSTOP flag */
+ int overlap; /* If set, results from multiple
+ * searches (-all) are allowed to
+ * overlap each other. */
+ int strictLimits; /* If set, matches must be
+ * completely inside the from,to
+ * range. Otherwise the limits
+ * only apply to the start of each
+ * match. */
int all; /* Whether all or the first match should
* be reported */
int startLine; /* First line to examine */
@@ -248,12 +263,16 @@ int tkTextDebug = 0;
static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int objc, Tcl_Obj *CONST objv[]));
static int DeleteChars _ANSI_ARGS_((TkText *textPtr,
- Tcl_Obj *index1Obj, Tcl_Obj *index2Obj,
CONST TkTextIndex *indexPtr1,
- CONST TkTextIndex *indexPtr2));
-static void DestroyText _ANSI_ARGS_((char *memPtr));
+ CONST TkTextIndex *indexPtr2, int noViewUpdate));
+static int CountIndices _ANSI_ARGS_((CONST TkText *textPtr,
+ CONST TkTextIndex *indexPtr1,
+ CONST TkTextIndex *indexPtr2,
+ TkTextCountType type));
+static void DestroyText _ANSI_ARGS_((TkText *textPtr));
static int InsertChars _ANSI_ARGS_((TkText *textPtr,
- TkTextIndex *indexPtr, Tcl_Obj *stringPtr));
+ TkTextIndex *indexPtr, Tcl_Obj *stringPtr,
+ int noViewUpdate));
static void TextBlinkProc _ANSI_ARGS_((ClientData clientData));
static void TextCmdDeletedProc _ANSI_ARGS_((
ClientData clientData));
@@ -263,6 +282,16 @@ static int TextFetchSelection _ANSI_ARGS_((ClientData clientData,
int offset, char *buffer, int maxBytes));
static int TextIndexSortProc _ANSI_ARGS_((CONST VOID *first,
CONST VOID *second));
+static int TextInsertCmd _ANSI_ARGS_((TkText *textPtr,
+ Tcl_Interp *interp,
+ int objc, Tcl_Obj *CONST objv[],
+ CONST TkTextIndex *indexPtr, int noViewUpdate));
+static int TextReplaceCmd _ANSI_ARGS_((TkText *textPtr,
+ Tcl_Interp *interp,
+ CONST TkTextIndex *indexFromPtr,
+ CONST TkTextIndex *indexToPtr,
+ int objc, Tcl_Obj *CONST objv[],
+ int noViewUpdate));
static int TextSearchCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
@@ -272,8 +301,10 @@ static int TextEditCmd _ANSI_ARGS_((TkText *textPtr,
static int TextWidgetObjCmd _ANSI_ARGS_((ClientData clientData,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
-static void TextWorldChanged _ANSI_ARGS_((
+static void TextWorldChangedCallback _ANSI_ARGS_((
ClientData instanceData));
+static void TextWorldChanged _ANSI_ARGS_((TkText *textPtr,
+ int mask));
static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr,
Tcl_Interp *interp,
int objc, Tcl_Obj *CONST objv[]));
@@ -281,13 +312,15 @@ static void DumpLine _ANSI_ARGS_((Tcl_Interp *interp,
TkText *textPtr, int what, TkTextLine *linePtr,
int start, int end, int lineno,
CONST char *command));
-static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp, char *key,
- char *value, CONST char * command,
+static int DumpSegment _ANSI_ARGS_((Tcl_Interp *interp,
+ CONST char *key,
+ CONST char *value, CONST char * command,
CONST TkTextIndex *index, int what));
static int TextEditUndo _ANSI_ARGS_((TkText *textPtr));
static int TextEditRedo _ANSI_ARGS_((TkText *textPtr));
-static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkTextIndex * index1,
- CONST TkTextIndex * index2));
+static Tcl_Obj* TextGetText _ANSI_ARGS_((CONST TkText *textPtr,
+ CONST TkTextIndex * index1,
+ CONST TkTextIndex * index2, int visibleOnly));
static void UpdateDirtyFlag _ANSI_ARGS_((TkText *textPtr));
static void TextPushUndoAction _ANSI_ARGS_((TkText *textPtr,
Tcl_Obj *undoString, int insert,
@@ -314,7 +347,7 @@ static SearchLineIndexProc TextSearchGetLineIndex;
static Tk_ClassProcs textClass = {
sizeof(Tk_ClassProcs), /* size */
- TextWorldChanged, /* worldChangedProc */
+ TextWorldChangedCallback, /* worldChangedProc */
};
@@ -387,9 +420,16 @@ Tk_TextObjCmd(clientData, interp, objc, objv)
textPtr->relief = TK_RELIEF_FLAT;
textPtr->cursor = None;
textPtr->charWidth = 1;
+ textPtr->charHeight = 10;
textPtr->wrapMode = TEXT_WRAPMODE_CHAR;
textPtr->prevWidth = Tk_Width(new);
textPtr->prevHeight = Tk_Height(new);
+ /*
+ * This refCount will be held until DestroyText is called.
+ * Note also that the following call to 'TkTextCreateDInfo'
+ * will add more refCounts.
+ */
+ textPtr->refCount = 1;
TkTextCreateDInfo(textPtr);
TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex);
TkTextSetYView(textPtr, &startIndex, 0);
@@ -402,7 +442,6 @@ Tk_TextObjCmd(clientData, interp, objc, objv)
textPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
textPtr->tabOptionPtr = NULL;
textPtr->stateEpoch = 0;
- textPtr->refCount = 1;
/*
* Create the "sel" tag and the "current" and "insert" marks.
@@ -486,16 +525,17 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
int index;
static CONST char *optionStrings[] = {
- "bbox", "cget", "compare", "configure", "debug", "delete",
- "dlineinfo", "dump", "edit", "get", "image", "index",
- "insert", "mark", "scan", "search", "see", "tag",
- "window", "xview", "yview", (char *) NULL
+ "bbox", "cget", "compare", "configure", "count", "debug",
+ "delete", "dlineinfo", "dump", "edit", "get", "image", "index",
+ "insert", "mark", "replace", "scan", "search", "see",
+ "tag", "window", "xview", "yview", (char *) NULL
};
enum options {
- TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_DEBUG,
- TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT, TEXT_GET,
- TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK, TEXT_SCAN,
- TEXT_SEARCH, TEXT_SEE, TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
+ TEXT_BBOX, TEXT_CGET, TEXT_COMPARE, TEXT_CONFIGURE, TEXT_COUNT,
+ TEXT_DEBUG, TEXT_DELETE, TEXT_DLINEINFO, TEXT_DUMP, TEXT_EDIT,
+ TEXT_GET, TEXT_IMAGE, TEXT_INDEX, TEXT_INSERT, TEXT_MARK,
+ TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE,
+ TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -507,7 +547,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
&index) != TCL_OK) {
return TCL_ERROR;
}
- Tcl_Preserve((ClientData) textPtr);
+ textPtr->refCount++;
switch ((enum options) index) {
case TEXT_BBOX: {
@@ -525,11 +565,19 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (TkTextCharBbox(textPtr, indexPtr, &x, &y,
- &width, &height) == 0) {
- char buf[TCL_INTEGER_SPACE * 4];
+ &width, &height, NULL) == 0) {
+ Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(x));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(y));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(width));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(height));
- sprintf(buf, "%d %d %d %d", x, y, width, height);
- Tcl_SetResult(interp, buf, TCL_VOLATILE);
+ Tcl_SetObjResult(interp, listObj);
}
break;
}
@@ -616,6 +664,176 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
break;
}
+ case TEXT_COUNT: {
+ CONST TkTextIndex *indexFromPtr, *indexToPtr;
+ int i, found = 0, update = 0;
+ Tcl_Obj *objPtr = NULL;
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?options? index1 index2");
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-2]);
+ if (indexFromPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[objc-1]);
+ if (indexToPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ for (i = 2; i < objc-2; i++) {
+ int value;
+ int length;
+ CONST char *option = Tcl_GetStringFromObj(objv[i],&length);
+ char c;
+ if (length < 2 || option[0] != '-') {
+ badOption:
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "bad option \"",
+ Tcl_GetString(objv[i]),
+ "\" must be -chars, -displaychars, -displayindices, ",
+ "-displaylines, -indices, -lines, -update, ",
+ "-xpixels, or -ypixels", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ c = option[1];
+ if (c == 'c' && !strncmp("-chars",option,length)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_CHARS);
+ } else if (c == 'd' && !strncmp("-displaychars", option,
+ length) && (length > 8)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_DISPLAY_CHARS);
+ } else if (c == 'd' && !strncmp("-displayindices", option,
+ length) && (length > 8)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_DISPLAY_INDICES);
+ } else if (c == 'd' && !strncmp("-displaylines", option,
+ length) && (length > 8)) {
+ TkTextLine *fromPtr, *lastPtr;
+ TkTextIndex index;
+
+ int compare = TkTextIndexCmp(indexFromPtr, indexToPtr);
+ value = 0;
+
+ if (compare == 0) goto countDone;
+
+ if (compare > 0) {
+ CONST TkTextIndex *tmpPtr = indexFromPtr;
+ indexFromPtr = indexToPtr;
+ indexToPtr = tmpPtr;
+ }
+
+ lastPtr = TkBTreeFindLine(textPtr->tree,
+ TkBTreeNumLines(textPtr->tree));
+ fromPtr = indexFromPtr->linePtr;
+ if (fromPtr == lastPtr) {
+ goto countDone;
+ }
+
+ /*
+ * Caution: we must NEVER call TkTextUpdateOneLine
+ * with the last artificial line in the widget.
+ */
+ while (fromPtr != indexToPtr->linePtr) {
+ value += TkTextUpdateOneLine(textPtr, fromPtr);
+ fromPtr = TkBTreeNextLine(fromPtr);
+ }
+ /*
+ * Now we need to adjust the count to add on the
+ * number of display lines in the last logical line,
+ * and subtract off the number of display lines
+ * overcounted in the first logical line. This logic
+ * is still ok if both indices are in the same
+ * logical line.
+ */
+ index.linePtr = indexFromPtr->linePtr;
+ index.byteIndex = 0;
+ while (1) {
+ TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
+ if (index.byteIndex >= indexFromPtr->byteIndex) {
+ break;
+ }
+ TkTextIndexForwBytes(&index, 1, &index);
+ value--;
+ }
+ if (indexToPtr->linePtr != lastPtr) {
+ index.linePtr = indexToPtr->linePtr;
+ index.byteIndex = 0;
+ while (1) {
+ TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
+ if (index.byteIndex >= indexToPtr->byteIndex) {
+ break;
+ }
+ TkTextIndexForwBytes(&index, 1, &index);
+ value++;
+ }
+ }
+
+ if (compare > 0) {
+ value = -value;
+ }
+ } else if (c == 'i' && !strncmp("-indices",option,length)) {
+ value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_INDICES);
+ } else if (c == 'l' && !strncmp("-lines",option,length)) {
+ value = TkBTreeLineIndex(indexToPtr->linePtr)
+ - TkBTreeLineIndex(indexFromPtr->linePtr);
+ } else if (c == 'u' && !strncmp("-update",option,length)) {
+ update = 1;
+ continue;
+ } else if (c == 'x' && !strncmp("-xpixels",option,length)) {
+ int x1, x2;
+ TkTextIndex index;
+ index = *indexFromPtr;
+ TkTextFindDisplayLineEnd(textPtr, &index, 0, &x1);
+ index = *indexToPtr;
+ TkTextFindDisplayLineEnd(textPtr, &index, 0, &x2);
+ value = x2 - x1;
+ } else if (c == 'y' && !strncmp("-ypixels",option,length)) {
+ if (update) {
+ TkTextUpdateLineMetrics(textPtr,
+ TkBTreeLineIndex(indexFromPtr->linePtr),
+ TkBTreeLineIndex(indexToPtr->linePtr), -1);
+ }
+ value = TkTextIndexYPixels(textPtr, indexToPtr)
+ - TkTextIndexYPixels(textPtr, indexFromPtr);
+ } else {
+ goto badOption;
+ }
+ countDone:
+ found++;
+ if (found == 1) {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
+ } else {
+ if (found == 2) {
+ /*
+ * Move the first item we put into the result into
+ * the first element of the list object.
+ */
+ objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr,
+ Tcl_GetObjResult(interp));
+ }
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewIntObj(value));
+ }
+ }
+ if (found == 0) {
+ /* Use the default '-indices' */
+ int value = CountIndices(textPtr, indexFromPtr, indexToPtr,
+ COUNT_INDICES);
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
+ } else if (found > 1) {
+ Tcl_SetObjResult(interp, objPtr);
+ }
+ break;
+ }
case TEXT_DEBUG: {
if (objc > 3) {
Tcl_WrongNumArgs(interp, 2, objv, "boolean");
@@ -645,8 +863,29 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
/*
* Simple case requires no predetermination of indices.
*/
- result = DeleteChars(textPtr, objv[2],
- (objc == 4) ? objv[3] : NULL, NULL, NULL);
+ CONST TkTextIndex *indexPtr1, *indexPtr2;
+
+ /*
+ * Parse the starting and stopping indices.
+ */
+
+ indexPtr1 = TkTextGetIndexFromObj(textPtr->interp,
+ textPtr, objv[2]);
+ if (indexPtr1 == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (objc == 4) {
+ indexPtr2 = TkTextGetIndexFromObj(textPtr->interp,
+ textPtr, objv[3]);
+ if (indexPtr2 == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ } else {
+ indexPtr2 = NULL;
+ }
+ DeleteChars(textPtr, indexPtr1, indexPtr2, 0);
} else {
int i;
/*
@@ -683,7 +922,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
*/
if (objc & 1) {
indices[i] = indices[i-1];
- TkTextIndexForwChars(&indices[i], 1, &indices[i]);
+ TkTextIndexForwChars(&indices[i], 1, &indices[i],
+ COUNT_INDICES);
objc++;
}
useIdx = (char *) ckalloc((unsigned) objc);
@@ -740,8 +980,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
* We don't need to check the return value
* because all indices are preparsed above.
*/
- DeleteChars(textPtr, NULL, NULL,
- &indices[i], &indices[i+1]);
+ DeleteChars(textPtr, &indices[i],
+ &indices[i+1], 0);
}
}
ckfree((char *) indices);
@@ -765,10 +1005,20 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width,
&height, &base) == 0) {
- char buf[TCL_INTEGER_SPACE * 5];
+ Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
- sprintf(buf, "%d %d %d %d %d", x, y, width, height, base);
- Tcl_SetResult(interp, buf, TCL_VOLATILE);
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(x));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(y));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(width));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(height));
+ Tcl_ListObjAppendElement(interp, listObj,
+ Tcl_NewIntObj(base));
+
+ Tcl_SetObjResult(interp, listObj);
}
break;
}
@@ -782,14 +1032,37 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
case TEXT_GET: {
Tcl_Obj *objPtr = NULL;
- int i, found = 0;
-
+ int i, found = 0, visible = 0;
+ CONST char *name;
+ int length;
+
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "?-displaychars? ?--? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- for (i = 2; i < objc; i += 2) {
+
+ /*
+ * Simple, restrictive argument parsing. The only options are --
+ * and -displaychars (or any unique prefix).
+ */
+ i = 2;
+ if (objc > 3) {
+ name = Tcl_GetStringFromObj(objv[i], &length);
+ if (length > 1 && name[0] == '-') {
+ if (strncmp("-displaychars", name, length) == 0) {
+ i++;
+ visible = 1;
+ name = Tcl_GetStringFromObj(objv[i], &length);
+ }
+ if ((i < objc-1) && (length == 2)
+ && (strcmp("--", name) == 0)) {
+ i++;
+ }
+ }
+ }
+ for (; i < objc; i += 2) {
CONST TkTextIndex *index1Ptr, *index2Ptr;
TkTextIndex index2;
@@ -802,7 +1075,7 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (i+1 == objc) {
- TkTextIndexForwChars(index1Ptr, 1, &index2);
+ TkTextIndexForwChars(index1Ptr, 1, &index2, COUNT_INDICES);
index2Ptr = &index2;
} else {
index2Ptr = TkTextGetIndexFromObj(interp, textPtr,
@@ -822,7 +1095,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
* be a megabyte or more, we want to do it
* efficiently!
*/
- Tcl_Obj *get = TextGetText(index1Ptr, index2Ptr);
+ Tcl_Obj *get = TextGetText(textPtr, index1Ptr,
+ index2Ptr, visible);
found++;
if (found == 1) {
Tcl_SetObjResult(interp, get);
@@ -881,51 +1155,8 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
goto done;
}
if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- TkTextIndex index1, index2;
- int j;
-
- index1 = *indexPtr;
- for (j = 3; j < objc; j += 2) {
- /*
- * Here we rely on this call to modify index1 if
- * it is outside the acceptable range. In particular,
- * if index1 is "end", it must be set to the last
- * allowable index for insertion, otherwise
- * subsequent tag insertions will fail.
- */
- int length = InsertChars(textPtr, &index1, objv[j]);
- if (objc > (j+1)) {
- Tcl_Obj **tagNamePtrs;
- TkTextTag **oldTagArrayPtr;
- int numTags;
-
- TkTextIndexForwBytes(&index1, length, &index2);
- oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
- if (oldTagArrayPtr != NULL) {
- int i;
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2,
- oldTagArrayPtr[i], 0);
- }
- ckfree((char *) oldTagArrayPtr);
- }
- if (Tcl_ListObjGetElements(interp, objv[j+1],
- &numTags, &tagNamePtrs)
- != TCL_OK) {
- result = TCL_ERROR;
- goto done;
- } else {
- int i;
-
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2,
- TkTextCreateTag(textPtr,
- Tcl_GetString(tagNamePtrs[i])), 1);
- }
- index1 = index2;
- }
- }
- }
+ result = TextInsertCmd(textPtr, interp, objc-3, objv+3,
+ indexPtr, 0);
}
break;
}
@@ -933,6 +1164,113 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
result = TkTextMarkCmd(textPtr, interp, objc, objv);
break;
}
+ case TEXT_REPLACE: {
+ CONST TkTextIndex *indexFromPtr, *indexToPtr;
+
+ if (objc < 5) {
+ Tcl_WrongNumArgs(interp, 2, objv,
+ "index1 index2 chars ?tagList chars tagList ...?");
+ result = TCL_ERROR;
+ goto done;
+ }
+ indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
+ if (indexFromPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]);
+ if (indexToPtr == NULL) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) {
+ Tcl_AppendResult(interp, "Index \"", Tcl_GetString(objv[3]),
+ "\" before \"", Tcl_GetString(objv[2]),
+ "\" in the text.", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
+ int lineNum, byteIndex;
+ TkTextIndex index;
+ /*
+ * The 'replace' operation is quite complex to do
+ * correctly, because we want a number of criteria
+ * to hold:
+ *
+ * 1. The insertion point shouldn't move, unless
+ * it is within the deleted range. In this case
+ * it should end up after the new text.
+ *
+ * 2. The window should not change the text it
+ * shows -- should not scroll vertically -- unless
+ * the result of the replace is that the insertion
+ * position which used to be on-screen is now
+ * off-screen.
+ */
+ byteIndex = textPtr->topIndex.byteIndex;
+ lineNum = TkBTreeLineIndex(textPtr->topIndex.linePtr);
+
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr,
+ &index);
+ if ((TkTextIndexCmp(indexFromPtr, &index) < 0)
+ && (TkTextIndexCmp(indexToPtr, &index) > 0)) {
+ /*
+ * The insertion point is inside the range to be
+ * replaced, so we have to do some calculations to
+ * ensure it doesn't move unnecessarily.
+ */
+ int deleteInsertOffset, insertLength, j;
+
+ insertLength = 0;
+ for (j = 4; j < objc; j += 2) {
+ insertLength += Tcl_GetCharLength(objv[j]);
+ }
+
+ /*
+ * Calculate 'deleteInsertOffset' as an offset we
+ * will apply to the insertion point after this
+ * operation.
+ */
+ deleteInsertOffset = CountIndices(textPtr, indexFromPtr,
+ &index, COUNT_CHARS);
+ if (deleteInsertOffset > insertLength) {
+ deleteInsertOffset = insertLength;
+ }
+
+ result = TextReplaceCmd(textPtr, interp,
+ indexFromPtr, indexToPtr,
+ objc, objv, 1);
+
+ if (result == TCL_OK) {
+ /*
+ * Move the insertion position to the correct
+ * place
+ */
+ TkTextIndexForwChars(indexFromPtr, deleteInsertOffset,
+ &index, COUNT_INDICES);
+ TkBTreeUnlinkSegment(textPtr->tree,
+ textPtr->insertMarkPtr,
+ textPtr->insertMarkPtr->body.mark.linePtr);
+ TkBTreeLinkSegment(textPtr->insertMarkPtr, &index);
+ }
+ } else {
+ result = TextReplaceCmd(textPtr, interp,
+ indexFromPtr, indexToPtr,
+ objc, objv, 0);
+ }
+ if (result == TCL_OK) {
+ /*
+ * Now ensure the top-line is in the right
+ * place
+ */
+ TkTextMakeByteIndex(textPtr->tree, lineNum,
+ byteIndex, &index);
+ TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST);
+ }
+ }
+ break;
+ }
case TEXT_SCAN: {
result = TkTextScanCmd(textPtr, interp, objc, objv);
break;
@@ -964,7 +1302,68 @@ TextWidgetObjCmd(clientData, interp, objc, objv)
}
done:
- Tcl_Release((ClientData) textPtr);
+ textPtr->refCount--;
+ if (textPtr->refCount == 0) {
+ ckfree((char *) textPtr);
+ }
+ return result;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextReplaceCmd --
+ *
+ * This procedure is invoked to process part of the "replace" widget
+ * command for text widgets.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextReplaceCmd(textPtr, interp, indexFromPtr, indexToPtr,
+ objc, objv, noViewUpdate)
+ TkText *textPtr; /* Information about text widget. */
+ Tcl_Interp *interp; /* Current interpreter. */
+ CONST TkTextIndex *indexFromPtr;/* Index from which to replace */
+ CONST TkTextIndex *indexToPtr; /* Index to which to replace */
+ int objc; /* Number of arguments. */
+ Tcl_Obj *CONST objv[]; /* Argument objects. */
+ int noViewUpdate; /* Don't update the view if set */
+{
+ int result;
+ /*
+ * Perform the deletion and insertion, but ensure
+ * no undo-separator is placed between the two
+ * operations. Since we are using the helper procedures
+ * 'DeleteChars' and 'TextInsertCmd' we have to pretend
+ * that the autoSeparators setting is off, so that we don't
+ * get an undo-separator between the delete and insert.
+ */
+ int origAutoSep = textPtr->autoSeparators;
+
+ if (textPtr->undo) {
+ textPtr->autoSeparators = 0;
+ if (origAutoSep && textPtr->lastEditMode != TK_TEXT_EDIT_REPLACE) {
+ TkUndoInsertUndoSeparator(textPtr->undoStack);
+ }
+ }
+
+ DeleteChars(textPtr, indexFromPtr, indexToPtr, noViewUpdate);
+ result = TextInsertCmd(textPtr, interp, objc-4, objv+4,
+ indexFromPtr, noViewUpdate);
+
+ if (textPtr->undo) {
+ textPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
+ textPtr->autoSeparators = origAutoSep;
+ }
+
return result;
}
@@ -1016,24 +1415,28 @@ TextIndexSortProc(first, second)
*
* DestroyText --
*
- * This procedure is invoked by Tcl_EventuallyFree or Tcl_Release
- * to clean up the internal structure of a text at a safe time
- * (when no-one is using it anymore).
+ * This procedure is invoked when we receive a destroy event
+ * to clean up the internal structure of a text widget. We will
+ * free up most of the internal structure and delete the
+ * associated Tcl command. If there are no outstanding
+ * references to the widget, we also free up the textPtr itself.
+ *
+ * The widget has already been flagged as deleted.
*
* Results:
* None.
*
* Side effects:
- * Everything associated with the text is freed up.
+ * Either everything or almost everything associated with the
+ * text is freed up.
*
*----------------------------------------------------------------------
*/
static void
-DestroyText(memPtr)
- char *memPtr; /* Info about text widget. */
+DestroyText(textPtr)
+ TkText *textPtr; /* Info about text widget. */
{
- register TkText *textPtr = (TkText *) memPtr;
Tcl_HashSearch search;
Tcl_HashEntry *hPtr;
TkTextTag *tagPtr;
@@ -1048,6 +1451,8 @@ DestroyText(memPtr)
*/
TkTextFreeDInfo(textPtr);
+ textPtr->dInfoPtr = NULL;
+
TkBTreeDestroy(textPtr->tree);
for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
@@ -1111,9 +1516,10 @@ ConfigureText(interp, textPtr, objc, objv)
{
Tk_SavedOptions savedOptions;
int oldExport = textPtr->exportSelection;
+ int mask = 0;
if (Tk_SetOptions(interp, (char*)textPtr, textPtr->optionTable,
- objc, objv, textPtr->tkwin, &savedOptions, NULL) != TCL_OK) {
+ objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) {
return TCL_ERROR;
}
@@ -1173,27 +1579,31 @@ ConfigureText(interp, textPtr, objc, objv)
}
textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
textPtr->selTagPtr->affectsDisplay = 0;
- if ((textPtr->selTagPtr->border != NULL)
- || (textPtr->selTagPtr->borderWidth != 0)
- || (textPtr->selTagPtr->reliefString != NULL)
- || (textPtr->selTagPtr->bgStipple != None)
- || (textPtr->selTagPtr->fgColor != NULL)
+ textPtr->selTagPtr->affectsDisplay = 0;
+ textPtr->selTagPtr->affectsDisplayGeometry = 0;
+ if ((textPtr->selTagPtr->elideString != NULL)
|| (textPtr->selTagPtr->tkfont != None)
- || (textPtr->selTagPtr->fgStipple != None)
|| (textPtr->selTagPtr->justifyString != NULL)
|| (textPtr->selTagPtr->lMargin1String != NULL)
|| (textPtr->selTagPtr->lMargin2String != NULL)
|| (textPtr->selTagPtr->offsetString != NULL)
- || (textPtr->selTagPtr->overstrikeString != NULL)
|| (textPtr->selTagPtr->rMarginString != NULL)
|| (textPtr->selTagPtr->spacing1String != NULL)
|| (textPtr->selTagPtr->spacing2String != NULL)
|| (textPtr->selTagPtr->spacing3String != NULL)
|| (textPtr->selTagPtr->tabStringPtr != NULL)
- || (textPtr->selTagPtr->underlineString != NULL)
- || (textPtr->selTagPtr->elideString != NULL)
|| (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
textPtr->selTagPtr->affectsDisplay = 1;
+ textPtr->selTagPtr->affectsDisplayGeometry = 1;
+ }
+ if ((textPtr->selTagPtr->border != NULL)
+ || (textPtr->selTagPtr->reliefString != NULL)
+ || (textPtr->selTagPtr->bgStipple != None)
+ || (textPtr->selTagPtr->fgColor != NULL)
+ || (textPtr->selTagPtr->fgStipple != None)
+ || (textPtr->selTagPtr->overstrikeString != NULL)
+ || (textPtr->selTagPtr->underlineString != NULL)) {
+ textPtr->selTagPtr->affectsDisplay = 1;
}
TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL,
textPtr->selTagPtr, 1);
@@ -1241,14 +1651,14 @@ ConfigureText(interp, textPtr, objc, objv)
textPtr->height = 1;
}
Tk_FreeSavedOptions(&savedOptions);
- TextWorldChanged((ClientData) textPtr);
+ TextWorldChanged(textPtr, mask);
return TCL_OK;
}
/*
*---------------------------------------------------------------------------
*
- * TextWorldChanged --
+ * TextWorldChangedCallback --
*
* This procedure is called when the world has changed in some
* way and the widget needs to recompute all its graphics contexts
@@ -1266,19 +1676,53 @@ ConfigureText(interp, textPtr, objc, objv)
*/
static void
-TextWorldChanged(instanceData)
+TextWorldChangedCallback(instanceData)
ClientData instanceData; /* Information about widget. */
{
TkText *textPtr;
- Tk_FontMetrics fm;
textPtr = (TkText *) instanceData;
+ TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TextWorldChanged --
+ *
+ * This procedure is called when the world has changed in some
+ * way and the widget needs to recompute all its graphics contexts
+ * and determine its new geometry.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Configures all tags in the Text with a empty objc/objv, for
+ * the side effect of causing all the items to recompute their
+ * geometry and to be redisplayed.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static void
+TextWorldChanged(textPtr, mask)
+ TkText *textPtr; /* Information about widget. */
+ int mask; /* OR'd collection of bits showing what
+ * has changed */
+{
+ Tk_FontMetrics fm;
textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
if (textPtr->charWidth <= 0) {
textPtr->charWidth = 1;
}
Tk_GetFontMetrics(textPtr->tkfont, &fm);
+
+ textPtr->charHeight = fm.linespace;
+ if (textPtr->charHeight <= 0) {
+ textPtr->charHeight = 1;
+ }
Tk_GeometryRequest(textPtr->tkwin,
textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth
+ 2*textPtr->padX + 2*textPtr->highlightWidth,
@@ -1289,12 +1733,12 @@ TextWorldChanged(instanceData)
textPtr->borderWidth + textPtr->highlightWidth);
if (textPtr->setGrid) {
Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
- textPtr->charWidth, fm.linespace);
+ textPtr->charWidth, textPtr->charHeight);
} else {
Tk_UnsetGrid(textPtr->tkwin);
}
- TkTextRelayoutWindow(textPtr);
+ TkTextRelayoutWindow(textPtr, mask);
}
/*
@@ -1331,7 +1775,11 @@ TextEventProc(clientData, eventPtr)
} else if (eventPtr->type == ConfigureNotify) {
if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
|| (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
- TkTextRelayoutWindow(textPtr);
+ int mask = 0;
+ if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)) {
+ mask = 1;
+ }
+ TkTextRelayoutWindow(textPtr, mask);
textPtr->prevWidth = Tk_Width(textPtr->tkwin);
textPtr->prevHeight = Tk_Height(textPtr->tkwin);
}
@@ -1360,13 +1808,13 @@ TextEventProc(clientData, eventPtr)
textPtr->flags |= DESTROYED;
/*
- * We don't delete the associated Tcl command yet, because
- * that will cause textPtr->tkWin to be nulled out, and that
- * is needed inside DestroyText to clean up certain tags
- * which might have been created (e.g. in the text widget
- * styles demo).
+ * Call 'DestroyTest' to handle the deletion for us. The
+ * actual textPtr may still exist after this, if there are
+ * some outstanding references. But we have flagged it
+ * as DESTROYED just above, so nothing will try to make use
+ * of it very extensively.
*/
- Tcl_EventuallyFree((ClientData) textPtr, DestroyText);
+ DestroyText(textPtr);
} else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
if (eventPtr->xfocus.detail != NotifyInferior) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
@@ -1385,11 +1833,15 @@ TextEventProc(clientData, eventPtr)
TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1);
#endif
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(&index, 1, &index2);
+ TkTextIndexForwChars(&index, 1, &index2, COUNT_INDICES);
+ /*
+ * While we wish to redisplay, no heights have changed, so
+ * no need to call TkTextInvalidateLineMetrics
+ */
TkTextChanged(textPtr, &index, &index2);
if (textPtr->highlightWidth > 0) {
TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
- textPtr->highlightWidth);
+ textPtr->highlightWidth);
}
}
}
@@ -1451,18 +1903,22 @@ TextCmdDeletedProc(clientData)
* Side effects:
* The characters in "stringPtr" get added to the text just before
* the character indicated by "indexPtr".
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
*
*----------------------------------------------------------------------
*/
static int
-InsertChars(textPtr, indexPtr, stringPtr)
+InsertChars(textPtr, indexPtr, stringPtr, noViewUpdate)
TkText *textPtr; /* Overall information about text widget. */
TkTextIndex *indexPtr; /* Where to insert new characters. May be
* modified if the index is not valid
* for insertion (e.g. if at "end"). */
Tcl_Obj *stringPtr; /* Null-terminated string containing new
* information to add to text. */
+ int noViewUpdate; /* Don't update the view if set */
{
int lineIndex, resetView, offset, length;
@@ -1519,7 +1975,7 @@ InsertChars(textPtr, indexPtr, stringPtr)
UpdateDirtyFlag(textPtr);
- if (resetView) {
+ if (resetView && !noViewUpdate) {
TkTextIndex newTop;
TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop);
TkTextIndexForwBytes(&newTop, offset, &newTop);
@@ -1643,69 +2099,94 @@ TextPushUndoAction (textPtr, undoString, insert, index1Ptr, index2Ptr)
/*
*----------------------------------------------------------------------
*
+ * CountIndices --
+ *
+ * This procedure implements most of the functionality of the
+ * "count" widget command.
+ *
+ * Note that 'textPtr' is only used if we need to check for elided
+ * attributes, i.e. if type is COUNT_DISPLAY_INDICES or
+ * COUNT_DISPLAY_CHARS.
+ *
+ * Results:
+ * Returns the number of characters in the range.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+CountIndices(textPtr, indexPtr1, indexPtr2, type)
+ CONST TkText *textPtr; /* Overall information about text widget. */
+ CONST TkTextIndex *indexPtr1;/* Index describing location of first
+ * character to delete. */
+ CONST TkTextIndex *indexPtr2;/* Index describing location of last
+ * character to delete. NULL means just
+ * delete the one character given by
+ * indexPtr1. */
+ TkTextCountType type; /* The kind of indices to count */
+{
+ /*
+ * Order the starting and stopping indices.
+ */
+
+ int compare = TkTextIndexCmp(indexPtr1, indexPtr2);
+
+ if (compare == 0) {
+ return 0;
+ } else if (compare > 0) {
+ return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type);
+ } else {
+ return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* DeleteChars --
*
* This procedure implements most of the functionality of the
* "delete" widget command.
*
* Results:
- * Returns a standard Tcl result, and leaves an error message
- * in textPtr->interp if there is an error.
+ * Returns a standard Tcl result, currently always TCL_OK.
*
* Side effects:
* Characters get deleted from the text.
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
*
*----------------------------------------------------------------------
*/
static int
-DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
+DeleteChars(textPtr, indexPtr1, indexPtr2, noViewUpdate)
TkText *textPtr; /* Overall information about text widget. */
- Tcl_Obj *index1Obj; /* Object describing location of first
- * character to delete. */
- Tcl_Obj *index2Obj; /* Object describing location of last
- * character to delete. NULL means just
- * delete the one character given by
- * index1Obj. */
CONST TkTextIndex *indexPtr1;/* Index describing location of first
* character to delete. */
CONST TkTextIndex *indexPtr2;/* Index describing location of last
* character to delete. NULL means just
* delete the one character given by
* indexPtr1. */
+ int noViewUpdate; /* Don't update the view if set */
{
int line1, line2, line, byteIndex, resetView;
TkTextIndex index1, index2;
/*
- * Parse the starting and stopping indices.
+ * Prepare the starting and stopping indices.
*/
- if (index1Obj != NULL) {
- indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr, index1Obj);
- if (indexPtr1 == NULL) {
- return TCL_ERROR;
- }
- index1 = *indexPtr1;
- if (index2Obj != NULL) {
- indexPtr2 = TkTextGetIndexFromObj(textPtr->interp, textPtr,
- index2Obj);
- if (indexPtr2 == NULL) {
- return TCL_ERROR;
- }
- index2 = *indexPtr2;
- } else {
- index2 = index1;
- TkTextIndexForwChars(&index2, 1, &index2);
- }
+ index1 = *indexPtr1;
+ if (indexPtr2 != NULL) {
+ index2 = *indexPtr2;
} else {
- index1 = *indexPtr1;
- if (indexPtr2 != NULL) {
- index2 = *indexPtr2;
- } else {
- index2 = index1;
- TkTextIndexForwChars(&index2, 1, &index2);
- }
+ index2 = index1;
+ TkTextIndexForwChars(&index2, 1, &index2, COUNT_INDICES);
}
/*
@@ -1736,10 +2217,10 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
TkTextIndex oldIndex2;
oldIndex2 = index2;
- TkTextIndexBackChars(&oldIndex2, 1, &index2);
+ TkTextIndexBackChars(&oldIndex2, 1, &index2, COUNT_INDICES);
line2--;
if ((index1.byteIndex == 0) && (line1 != 0)) {
- TkTextIndexBackChars(&index1, 1, &index1);
+ TkTextIndexBackChars(&index1, 1, &index1, COUNT_INDICES);
line1--;
}
arrayPtr = TkBTreeGetTags(&index2, &arraySize);
@@ -1751,6 +2232,54 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
}
}
+ if (line1 < line2) {
+ /*
+ * We are deleting more than one line. For speed,
+ * we remove all tags from the range first. If we
+ * don't do this, the code below can (when there are
+ * many tags) grow non-linearly in execution time.
+ */
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+ int i;
+
+ for (i = 0, hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search);
+ hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextTag *tagPtr;
+ tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
+ if (TkBTreeTag(&index1, &index2, tagPtr, 0)) {
+ /*
+ * If the tag is "sel", and we actually adjusted anything
+ * then grab the selection if we're supposed to export it
+ * and don't already have it. Also, invalidate
+ * partially-completed selection retrievals.
+ *
+ * This code copied from tkTextTag.c's 'tag remove'
+ */
+
+ if (tagPtr == textPtr->selTagPtr) {
+ XEvent event;
+ /*
+ * Send an event that the selection changed.
+ * This is equivalent to
+ * "event generate $textWidget <<Selection>>"
+ */
+
+ memset((VOID *) &event, 0, sizeof(event));
+ event.xany.type = VirtualEvent;
+ event.xany.serial = NextRequest(Tk_Display(textPtr->tkwin));
+ event.xany.send_event = False;
+ event.xany.window = Tk_WindowId(textPtr->tkwin);
+ event.xany.display = Tk_Display(textPtr->tkwin);
+ ((XVirtualEvent *) &event)->name = Tk_GetUid("Selection");
+ Tk_HandleEvent(&event);
+
+ textPtr->abortSelections = 1;
+ }
+ }
+ }
+ }
+
/*
* Tell the display what's about to happen so it can discard
* obsolete display information, then do the deletion. Also,
@@ -1815,14 +2344,15 @@ DeleteChars(textPtr, index1Obj, index2Obj, indexPtr1, indexPtr2)
textPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
- get = TextGetText(&index1, &index2);
+ get = TextGetText(textPtr, &index1, &index2, 0);
TextPushUndoAction(textPtr, get, 0, &index1, &index2);
}
UpdateDirtyFlag(textPtr);
textPtr->stateEpoch ++;
TkBTreeDeleteChars(&index1, &index2);
- if (resetView) {
+
+ if (resetView && !noViewUpdate) {
TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1);
TkTextSetYView(textPtr, &index1, 0);
}
@@ -2061,7 +2591,7 @@ TextBlinkProc(clientData)
{
register TkText *textPtr = (TkText *) clientData;
TkTextIndex index;
- int x, y, w, h;
+ int x, y, w, h, charWidth;
if ((textPtr->state == TK_TEXT_STATE_DISABLED) ||
!(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
@@ -2077,15 +2607,98 @@ TextBlinkProc(clientData)
textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr);
}
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h) == 0) {
- TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
- textPtr->insertWidth, h);
+ if (TkTextCharBbox(textPtr, &index, &x, &y, &w, &h, &charWidth) == 0) {
+ if (textPtr->insertCursorType) {
+ /* Block cursor */
+ TkTextRedrawRegion(textPtr, x - textPtr->width / 2, y,
+ charWidth + textPtr->insertWidth / 2, h);
+ } else {
+ /* I-beam cursor */
+ TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
+ textPtr->insertWidth, h);
+ }
}
}
/*
*----------------------------------------------------------------------
*
+ * TextInsertCmd --
+ *
+ * This procedure is invoked to process the "insert" and "replace"
+ * widget commands for text widgets.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * See the user documentation.
+ *
+ * Unless 'noViewUpdate' is set, we may adjust the window
+ * contents' y-position, and scrollbar setting.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+TextInsertCmd(textPtr, interp, objc, objv, indexPtr, noViewUpdate)
+ TkText *textPtr; /* Information about text widget. */
+ Tcl_Interp *interp; /* Current interpreter. */
+ int objc; /* Number of arguments. */
+ Tcl_Obj *CONST objv[]; /* Argument objects. */
+ CONST TkTextIndex *indexPtr;/* Index at which to insert */
+ int noViewUpdate; /* Don't update the view if set */
+{
+ TkTextIndex index1, index2;
+ int j;
+
+ index1 = *indexPtr;
+ for (j = 0; j < objc; j += 2) {
+ /*
+ * Here we rely on this call to modify index1 if
+ * it is outside the acceptable range. In particular,
+ * if index1 is "end", it must be set to the last
+ * allowable index for insertion, otherwise
+ * subsequent tag insertions will fail.
+ */
+ int length = InsertChars(textPtr, &index1, objv[j], noViewUpdate);
+ if (objc > (j+1)) {
+ Tcl_Obj **tagNamePtrs;
+ TkTextTag **oldTagArrayPtr;
+ int numTags;
+
+ TkTextIndexForwBytes(&index1, length, &index2);
+ oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags);
+ if (oldTagArrayPtr != NULL) {
+ int i;
+ for (i = 0; i < numTags; i++) {
+ TkBTreeTag(&index1, &index2,
+ oldTagArrayPtr[i], 0);
+ }
+ ckfree((char *) oldTagArrayPtr);
+ }
+ if (Tcl_ListObjGetElements(interp, objv[j+1],
+ &numTags, &tagNamePtrs)
+ != TCL_OK) {
+ return TCL_ERROR;
+ } else {
+ int i;
+
+ for (i = 0; i < numTags; i++) {
+ TkBTreeTag(&index1, &index2,
+ TkTextCreateTag(textPtr,
+ Tcl_GetString(tagNamePtrs[i])), 1);
+ }
+ index1 = index2;
+ }
+ }
+ }
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TextSearchCmd --
*
* This procedure is invoked to process the "search" widget command
@@ -2113,12 +2726,14 @@ TextSearchCmd(textPtr, interp, objc, objv)
static CONST char *switchStrings[] = {
"--", "-all", "-backwards", "-count", "-elide", "-exact",
- "-forwards", "-hidden", "-nocase", "-nolinestop", "-regexp", NULL
+ "-forwards", "-hidden", "-nocase", "-nolinestop",
+ "-overlap", "-regexp", "-strictlimits", NULL
};
enum SearchSwitches {
SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE,
SEARCH_EXACT, SEARCH_FWD, SEARCH_HIDDEN, SEARCH_NOCASE,
- SEARCH_NOLINE, SEARCH_REGEXP
+ SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP,
+ SEARCH_STRICTLIMITS
};
/*
@@ -2134,6 +2749,8 @@ TextSearchCmd(textPtr, interp, objc, objv)
searchSpec.resPtr = NULL;
searchSpec.searchElide = 0;
searchSpec.noLineStop = 0;
+ searchSpec.overlap = 0;
+ searchSpec.strictLimits = 0;
searchSpec.numLines = TkBTreeNumLines(textPtr->tree);
searchSpec.clientData = (ClientData)textPtr;
searchSpec.addLineProc = &TextSearchAddNextLine;
@@ -2158,7 +2775,8 @@ TextSearchCmd(textPtr, interp, objc, objv)
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "bad switch \"", Tcl_GetString(objv[i]),
"\": must be --, -all, -backward, -count, -elide, ",
- "-exact, -forward, -nocase, -nolinestop, or -regexp",
+ "-exact, -forward, -nocase, -nolinestop, -overlap, ",
+ "-regexp, or -strictlimits",
(char *) NULL);
return TCL_ERROR;
}
@@ -2199,9 +2817,15 @@ TextSearchCmd(textPtr, interp, objc, objv)
case SEARCH_NOCASE:
searchSpec.noCase = 1;
break;
- case SEARCH_NOLINE:
+ case SEARCH_NOLINESTOP:
searchSpec.noLineStop = 1;
break;
+ case SEARCH_OVERLAP:
+ searchSpec.overlap = 1;
+ break;
+ case SEARCH_STRICTLIMITS:
+ searchSpec.strictLimits = 1;
+ break;
case SEARCH_REGEXP:
searchSpec.exact = 0;
break;
@@ -2224,6 +2848,12 @@ TextSearchCmd(textPtr, interp, objc, objv)
return TCL_ERROR;
}
+ if (searchSpec.overlap && !searchSpec.all) {
+ Tcl_SetResult(interp, "the \"-overlap\" option requires the "
+ "\"-all\" option to be present", TCL_STATIC);
+ return TCL_ERROR;
+ }
+
/*
* Scan through all of the lines of the text circularly, starting
* at the given index. 'objv[i]' is the pattern which may be an
@@ -2487,12 +3117,17 @@ TextSearchAddNextLine(lineNum, searchSpecPtr, theLine, lenPtr)
static int
TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine,
- matchOffset, matchLength)
+ matchOffset, matchLength)
int lineNum; /* Line on which match was found */
SearchSpec *searchSpecPtr; /* Search parameters */
ClientData clientData; /* Token returned by the 'addNextLineProc',
- * TextSearchAddNextLine */
- Tcl_Obj *theLine; /* Text from current line */
+ * TextSearchAddNextLine. May be
+ * NULL, in which we case we must
+ * generate it (from lineNum) */
+ Tcl_Obj *theLine; /* Text from current line, only
+ * accessed for exact searches, and
+ * is allowed to be NULL for regexp
+ * searches. */
int matchOffset; /* Offset of found item in utf-8 bytes
* for exact search, Unicode chars
* for regexp */
@@ -2544,6 +3179,10 @@ TextSearchFoundMatch(lineNum, searchSpecPtr, clientData, theLine,
*/
linePtr = (TkTextLine *)clientData;
+ if (linePtr == NULL) {
+ linePtr = TkBTreeFindLine(textPtr->tree, lineNum);
+ }
+
curIndex.tree = textPtr->tree;
curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
/* Find the starting point */
@@ -2844,7 +3483,7 @@ TextDumpCmd(textPtr, interp, objc, objv)
arg++;
atEnd = 0;
if (objc == arg) {
- TkTextIndexForwChars(&index1, 1, &index2);
+ TkTextIndexForwChars(&index1, 1, &index2, COUNT_INDICES);
} else {
int length;
char *str;
@@ -3008,8 +3647,8 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command)
static int
DumpSegment(interp, key, value, command, index, what)
Tcl_Interp *interp;
- char *key; /* Segment type key */
- char *value; /* Segment value */
+ CONST char *key; /* Segment type key */
+ CONST char *value; /* Segment value */
CONST char *command; /* Script callback */
CONST TkTextIndex *index; /* index with line/byte position info */
int what; /* Look for TK_DUMP_INDEX bit */
@@ -3267,7 +3906,10 @@ TextEditCmd(textPtr, interp, objc, objv)
* be as simple as replacing Tcl_NewObj by Tcl_NewUnicodeObj.
*
* Results:
- * Tcl_Obj of string type containing the specified text.
+ * Tcl_Obj of string type containing the specified text. If the
+ * visibleOnly flag is set to 1, then only those characters which
+ * are not elided will be returned. Otherwise (flag is 0) all
+ * characters in the given range are returned.
*
* Side effects:
* Memory will be allocated for the new object. Remember to free it if
@@ -3277,9 +3919,12 @@ TextEditCmd(textPtr, interp, objc, objv)
*/
static Tcl_Obj*
-TextGetText(indexPtr1,indexPtr2)
- CONST TkTextIndex *indexPtr1;
- CONST TkTextIndex *indexPtr2;
+TextGetText(textPtr, indexPtr1,indexPtr2, visibleOnly)
+ CONST TkText *textPtr; /* Information about text widget. */
+ CONST TkTextIndex *indexPtr1; /* Get text from this index... */
+ CONST TkTextIndex *indexPtr2; /* ...to this index */
+ int visibleOnly; /* If non-zero, then only return
+ * non-elided characters. */
{
TkTextIndex tmpIndex;
Tcl_Obj *resultPtr = Tcl_NewObj();
@@ -3311,8 +3956,10 @@ TextGetText(indexPtr1,indexPtr2)
}
}
if (segPtr->typePtr == &tkTextCharType) {
- Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
- last - offset);
+ if (!visibleOnly || !TkTextIsElided(textPtr, &tmpIndex)) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
+ last - offset);
+ }
}
TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex);
}
@@ -3423,9 +4070,9 @@ SearchPerform(interp, searchSpecPtr, patObj, fromPtr, toPtr)
/*
* Scan through all of the lines of the text circularly, starting
- * at the given index. 'objv[i]' is the pattern which may be an
- * exact string or a regexp pattern depending on the flags set
- * above.
+ * at the given index. 'patObj' is the pattern which may be an
+ * exact string or a regexp pattern depending on the flags in
+ * searchSpecPtr.
*/
return SearchCore(interp, searchSpecPtr, patObj);
@@ -3482,10 +4129,27 @@ SearchCore(interp, searchSpecPtr, patObj)
int lineNum = searchSpecPtr->startLine;
int code = TCL_OK;
Tcl_Obj *theLine;
-
+ int alreadySearchOffset = -1;
+
Tcl_RegExp regexp = NULL; /* For regexp searches only */
- CONST char *pattern = NULL; /* For exact searches only */
- int firstNewLine = -1; /* For exact searches only */
+ /*
+ * These items are for backward regexp searches only. They are for
+ * two purposes: to allow us to report backwards matches in the
+ * correct order, even though the implementation uses repeated
+ * forward searches; and to provide for overlap checking between
+ * backwards matches on different text lines.
+ */
+#define LOTS_OF_MATCHES 20
+ int matchNum = LOTS_OF_MATCHES;
+ int smArray[2 * LOTS_OF_MATCHES];
+ int *storeMatch = smArray;
+ int *storeLength = smArray + LOTS_OF_MATCHES;
+ int lastBackwardsLineMatch = -1;
+ int lastBackwardsMatchOffset = -1;
+
+ /* These two items are for exact searches only */
+ CONST char *pattern = NULL;
+ int firstNewLine = -1;
if (searchSpecPtr->exact) {
/*
@@ -3521,7 +4185,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* will have to search across multiple lines.
*/
if (searchSpecPtr->exact) {
- char *nl;
+ CONST char *nl;
/*
* We only need to set the matchLength once for exact searches,
@@ -3571,13 +4235,31 @@ SearchCore(interp, searchSpecPtr, patObj)
* 'lastOffset' (in bytes if exact, chars if regexp), since
* obviously the length is the maximum offset at which
* it is possible to find something on this line, which is
- * what we 'lastOffset' represents.
+ * what 'lastOffset' represents.
*/
lineInfo = (*searchSpecPtr->addLineProc)(lineNum,
searchSpecPtr, theLine, &lastOffset);
- firstOffset = 0;
+ if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) {
+ firstOffset = searchSpecPtr->stopOffset;
+ } else {
+ firstOffset = 0;
+ }
+
+ if (alreadySearchOffset != -1) {
+ if (searchSpecPtr->backwards) {
+ if (alreadySearchOffset < lastOffset) {
+ lastOffset = alreadySearchOffset;
+ }
+ } else {
+ if (alreadySearchOffset > firstOffset) {
+ firstOffset = alreadySearchOffset;
+ }
+ }
+ alreadySearchOffset = -1;
+ }
+
if (lineNum == searchSpecPtr->startLine) {
/*
* The starting line is tricky: the first time we see it
@@ -3593,18 +4275,21 @@ SearchCore(interp, searchSpecPtr, patObj)
* Only use the last part of the line.
*/
- if ((searchSpecPtr->startOffset >= lastOffset)
- && ((lastOffset != 0) || searchSpecPtr->exact)) {
+ if (searchSpecPtr->startOffset > firstOffset) {
+ firstOffset = searchSpecPtr->startOffset;
+ }
+ if ((firstOffset >= lastOffset)
+ && ((lastOffset != 0) || searchSpecPtr->exact)) {
goto nextLine;
}
-
- firstOffset = searchSpecPtr->startOffset;
} else {
/*
* Use only the first part of the line.
*/
- lastOffset = searchSpecPtr->startOffset;
+ if (searchSpecPtr->startOffset < lastOffset) {
+ lastOffset = searchSpecPtr->startOffset;
+ }
}
}
@@ -3625,18 +4310,71 @@ SearchCore(interp, searchSpecPtr, patObj)
do {
Tcl_UniChar ch;
CONST char *p;
+ int lastFullLine = lastOffset;
- p = strstr(startOfLine + firstOffset, pattern);
- if (p == NULL) {
- if (firstNewLine == -1 ||
- firstNewLine >= (lastOffset - firstOffset)) {
+ if (firstNewLine == -1) {
+ if (searchSpecPtr->strictLimits
+ && (firstOffset + matchLength > lastOffset)) {
+ /* Not enough characters to match. */
+ break;
+ }
+ /*
+ * Single line matching. We want to scan forwards
+ * or backwards as appropriate.
+ */
+ if (searchSpecPtr->backwards) {
+ /*
+ * Search back either from the previous match or
+ * from 'startOfLine + lastOffset - 1' until we
+ * find a match.
+ */
+ CONST char c = pattern[0];
+ if (alreadySearchOffset != -1) {
+ p = startOfLine + alreadySearchOffset;
+ alreadySearchOffset = -1;
+ } else {
+ p = startOfLine + lastOffset -1;
+ }
+ while (p >= startOfLine + firstOffset) {
+ if (p[0] == c && !strncmp(p, pattern, matchLength)) {
+ goto backwardsMatch;
+ }
+ p--;
+ }
break;
+ } else {
+ p = strstr(startOfLine + firstOffset, pattern);
}
+ if (p == NULL) {
+ /*
+ * Single line match failed.
+ */
+ break;
+ }
+ } else if (firstNewLine >= (lastOffset - firstOffset)) {
+ /*
+ * Multi-line match, but not enough characters to
+ * match.
+ */
+ break;
+ } else {
+ /*
+ * Multi-line match has only one possible match
+ * position, because we know where the '\n' is.
+ */
p = startOfLine + lastOffset - firstNewLine - 1;
if (strncmp(p, pattern, (unsigned)(firstNewLine + 1))) {
+ /* No match */
break;
} else {
int extraLines = 1;
+ /*
+ * If we find a match that overlaps more than one
+ * line, we will use this value to determine the
+ * first allowed starting offset for the following
+ * search (to avoid overlapping results).
+ */
+ int lastTotal = lastOffset;
int skipFirst = lastOffset - firstNewLine -1;
/*
* We may be able to match if given more text.
@@ -3644,7 +4382,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* exact searches.
*/
while (1) {
- int len;
+ lastFullLine = lastTotal;
if (lineNum+extraLines>=searchSpecPtr->numLines) {
p = NULL;
@@ -3658,8 +4396,11 @@ SearchCore(interp, searchSpecPtr, patObj)
if (extraLines > maxExtraLines) {
if ((*searchSpecPtr->addLineProc)(lineNum
+ extraLines, searchSpecPtr, theLine,
- &len) == NULL) {
+ &lastTotal) == NULL) {
p = NULL;
+ if (!searchSpecPtr->backwards) {
+ linesSearched = extraLines + 1;
+ }
break;
}
maxExtraLines = extraLines;
@@ -3671,7 +4412,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* Use the fact that 'matchLength = patLength'
* for exact searches
*/
- if ((len - skipFirst) >= matchLength) {
+ if ((lastTotal - skipFirst) >= matchLength) {
/*
* We now have enough text to match, so
* we make a final test and break
@@ -3686,7 +4427,7 @@ SearchCore(interp, searchSpecPtr, patObj)
* Not enough text yet, but check the prefix
*/
if (strncmp(p, pattern,
- (unsigned)(len - skipFirst))) {
+ (unsigned)(lastTotal - skipFirst))) {
p = NULL;
break;
}
@@ -3704,44 +4445,77 @@ SearchCore(interp, searchSpecPtr, patObj)
if (p == NULL) {
break;
}
+ linesSearched = extraLines;
}
}
- firstOffset = p - startOfLine;
- if (firstOffset >= lastOffset) {
+ backwardsMatch:
+ if ((p - startOfLine) >= lastOffset) {
break;
}
-
/*
* Remember the match
*/
- matchOffset = firstOffset;
+ matchOffset = p - startOfLine;
- /*
- * Move the starting point one character on from the
- * previous match, in case we are doing repeated or
- * backwards searches (for the latter, we actually do
- * repeated forward searches).
- */
- firstOffset += Tcl_UtfToUniChar(startOfLine+matchOffset, &ch);
if (searchSpecPtr->all &&
- !(*searchSpecPtr->foundMatchProc)(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
+ !(*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength)) {
/*
* We reached the end of the search
*/
goto searchDone;
}
- } while (searchSpecPtr->backwards || searchSpecPtr->all);
+ if (!searchSpecPtr->overlap) {
+ if (searchSpecPtr->backwards) {
+ alreadySearchOffset = p - startOfLine;
+ if (firstNewLine != -1) {
+ break;
+ } else {
+ alreadySearchOffset -= matchLength;
+ }
+ } else {
+ firstOffset = p - startOfLine + matchLength;
+ if (firstOffset >= lastOffset) {
+ /*
+ * Now, we have to be careful not to find
+ * overlapping matches either on the same or
+ * following lines. Assume that if we did find
+ * something, it goes until the last extra line
+ * we added.
+ *
+ * We can break out of the loop, since we know
+ * no more will be found.
+ */
+ if (!searchSpecPtr->backwards) {
+ alreadySearchOffset = firstOffset - lastFullLine;
+ break;
+ }
+ }
+ }
+ } else {
+ if (searchSpecPtr->backwards) {
+ alreadySearchOffset = p - startOfLine - 1;
+ if (alreadySearchOffset < 0) {
+ break;
+ }
+ } else {
+ firstOffset = p - startOfLine
+ + Tcl_UtfToUniChar(startOfLine + matchOffset, &ch);
+ }
+ }
+ } while (searchSpecPtr->all);
} else {
int maxExtraLines = 0;
-
+ int matches = 0;
+ int lastNonOverlap = -1;
+
do {
Tcl_RegExpInfo info;
int match;
-
+ int lastFullLine = lastOffset;
+
match = Tcl_RegExpExecObj(interp, regexp, theLine, firstOffset,
1, ((firstOffset > 0) ? TCL_REG_NOTBOL : 0));
if (match < 0) {
@@ -3750,24 +4524,45 @@ SearchCore(interp, searchSpecPtr, patObj)
}
Tcl_RegExpGetInfo(regexp, &info);
- if (!match) {
+ /*
+ * If we don't have a match, or if we do, but it
+ * extends to the end of the line, we must try to
+ * add more lines to get a full greedy match.
+ */
+ if (!match
+ || ((info.extendStart == info.matches[0].start)
+ && (info.matches[0].end == lastOffset))) {
int extraLines = 1;
- int curLen = 0;
+ int prevFullLine;
+ /*
+ * If we find a match that overlaps more than one
+ * line, we will use this value to determine the
+ * first allowed starting offset for the following
+ * search (to avoid overlapping results).
+ */
+ int lastTotal = lastOffset;
+
+ if ((lastBackwardsLineMatch != -1)
+ && (lastBackwardsLineMatch == (lineNum + 1))) {
+ lastNonOverlap = lastTotal;
+ }
if (info.extendStart < 0) {
+ /* No multi-line match is possible */
break;
}
/*
* We may be able to match if given more text.
* The following 'while' block handles multi-line
- * exact searches.
+ * regexp searches.
*/
while (1) {
+ prevFullLine = lastTotal;
/*
* Move firstOffset to first possible start
*/
- firstOffset += info.extendStart;
+ if (!match) firstOffset += info.extendStart;
if (firstOffset >= lastOffset) {
/*
* We're being told that the only possible
@@ -3775,8 +4570,8 @@ SearchCore(interp, searchSpecPtr, patObj)
* the line. But, that is the next line which
* we will handle when we look at that line.
*/
- if (!searchSpecPtr->backwards
- && (firstOffset == curLen)) {
+ if (!match && !searchSpecPtr->backwards
+ && (firstOffset == 0)) {
linesSearched = extraLines + 1;
}
break;
@@ -3791,17 +4586,22 @@ SearchCore(interp, searchSpecPtr, patObj)
if (extraLines > maxExtraLines) {
if ((*searchSpecPtr->addLineProc)(lineNum
+ extraLines, searchSpecPtr, theLine,
- NULL) == NULL) {
+ &lastTotal) == NULL) {
/*
* There are no more acceptable lines, so
* we can say we have searched all of these
*/
- if (!searchSpecPtr->backwards) {
+ if (!match && !searchSpecPtr->backwards) {
linesSearched = extraLines + 1;
}
break;
}
maxExtraLines = extraLines;
+ if ((lastBackwardsLineMatch != -1)
+ && (lastBackwardsLineMatch
+ == (lineNum + extraLines + 1))) {
+ lastNonOverlap = lastTotal;
+ }
}
match = Tcl_RegExpExecObj(interp, regexp, theLine,
@@ -3812,64 +4612,286 @@ SearchCore(interp, searchSpecPtr, patObj)
goto searchDone;
}
Tcl_RegExpGetInfo(regexp, &info);
- if (match || info.extendStart < 0) {
+
+ /*
+ * Unfortunately there are bugs in Tcl's regexp
+ * library, which tells us that info.extendStart
+ * is zero when it should not be (should be -1),
+ * which makes our task a bit more complicated
+ * here. We check if there was a match, and the
+ * end of the match leaves an entire extra line
+ * unmatched, then we stop searching. Clearly it
+ * still might sometimes be possible to add more
+ * text and match again, but Tcl's regexp library
+ * doesn't tell us that.
+ *
+ * This means we often add and search one more
+ * line than might be necessary if Tcl were able
+ * to give us a correct value of info.extendStart
+ * under all circumstances.
+ */
+ if ((match && ((firstOffset + info.matches[0].end)
+ != lastTotal)
+ && ((firstOffset + info.matches[0].end)
+ < prevFullLine))
+ || info.extendStart < 0) {
break;
}
+ if (match && ((firstOffset + info.matches[0].end)
+ >= prevFullLine)) {
+ linesSearched = extraLines;
+ lastFullLine = prevFullLine;
+ }
/*
* The prefix matches, so keep looking
*/
extraLines++;
}
/*
- * If we reach here, with match == 1, we've found a
+ * If we reach here with 'match == 1', we've found a
* multi-line match, which we will record in the code
* which follows directly else we started a
* multi-line match but didn't finish it off, so we
* go to the next line.
- *
- * Here is where we could perform an optimisation,
- * since we have already retrieved the contents of
- * the next line (and many more), so we shouldn't
- * really throw it all away and start again. This
- * could be particularly important for complex regexp
- * searches.
*/
if (!match) {
- /*
- * This 'break' will take us to just before
- * the 'nextLine:' below.
+ /*
+ * Here is where we could perform an optimisation,
+ * since we have already retrieved the contents of
+ * the next line (perhaps many more), so we shouldn't
+ * really throw it all away and start again. This
+ * could be particularly important for complex regexp
+ * searches.
+ *
+ * This 'break' will take us to just before the
+ * 'nextLine:' below.
*/
break;
}
- }
+ if (lastBackwardsLineMatch != -1) {
+ if ((lineNum + linesSearched)
+ == lastBackwardsLineMatch) {
+ /* Possible overlap or inclusion */
+ int thisOffset = firstOffset + info.matches[0].end
+ - info.matches[0].start;
+
+ if (lastNonOverlap != -1) {
+ /* Possible overlap or enclosure */
+ if ((thisOffset - lastNonOverlap)
+ >= (lastBackwardsMatchOffset
+ + matchLength)) {
+ /*
+ * Totally encloses previous match, so
+ * forget the previous match
+ */
+ lastBackwardsLineMatch = -1;
+ } else if ((thisOffset - lastNonOverlap)
+ > lastBackwardsMatchOffset) {
+ /*
+ * Overlap. Previous match is ok, and
+ * the current match is only ok if
+ * we are searching with -overlap.
+ */
+ if (searchSpecPtr->overlap) {
+ goto recordBackwardsMatch;
+ } else {
+ match = 0;
+ break;
+ }
+ } else {
+ /*
+ * No overlap, although the same
+ * line was reached.
+ */
+ goto recordBackwardsMatch;
+ }
+ } else {
+ /* No overlap */
+ goto recordBackwardsMatch;
+ }
+ } else if (lineNum + linesSearched
+ < lastBackwardsLineMatch) {
+ /* No overlap */
+ goto recordBackwardsMatch;
+ } else {
+ /* Totally enclosed */
+ lastBackwardsLineMatch = -1;
+ }
+ }
+
+ } else {
+ /* Matched in a single line */
+ if (lastBackwardsLineMatch != -1) {
+ recordBackwardsMatch:
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch,
+ searchSpecPtr, NULL, NULL,
+ lastBackwardsMatchOffset, matchLength);
+ lastBackwardsLineMatch = -1;
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ }
+ }
+
firstOffset += info.matches[0].start;
if (firstOffset >= lastOffset) {
break;
}
/*
- * Remember the match
+ * Update our local variables with the match, if we
+ * haven't yet found anything, or if we're doing '-all'
+ * or '-backwards' _and_ this match isn't fully enclosed
+ * in the previous match.
*/
- matchOffset = firstOffset;
- matchLength = info.matches[0].end - info.matches[0].start;
+
+ if (matchOffset == -1 ||
+ ((searchSpecPtr->all || searchSpecPtr->backwards)
+ && ((firstOffset < matchOffset)
+ || ((firstOffset + info.matches[0].end
+ - info.matches[0].start)
+ > (matchOffset + matchLength))
+ )
+ )
+ ) {
+
+ matchOffset = firstOffset;
+ matchLength = info.matches[0].end - info.matches[0].start;
+
+ if (searchSpecPtr->backwards) {
+ /*
+ * To get backwards searches in the correct
+ * order, we must store them away here.
+ */
+ if (matches == matchNum) {
+ /*
+ * We've run out of space in our normal
+ * store, so we must allocate space for
+ * these backwards matches on the heap.
+ */
+ int *newArray;
+ newArray = (int*) ckalloc(4*matchNum *sizeof(int));
+ memcpy(newArray, storeMatch, matchNum*sizeof(int));
+ memcpy(newArray + 2*matchNum,
+ storeLength, matchNum*sizeof(int));
+ if (storeMatch != smArray) {
+ ckfree((char*)storeMatch);
+ }
+ matchNum *= 2;
+ storeMatch = newArray;
+ storeLength = newArray + matchNum;
+ }
+ storeMatch[matches] = matchOffset;
+ storeLength[matches] = matchLength;
+ matches++;
+ } else {
+ /*
+ * Now actually record the match, but only if we
+ * are doing an '-all' search.
+ */
+ if (searchSpecPtr->all &&
+ !(*searchSpecPtr->foundMatchProc)(lineNum,
+ searchSpecPtr, lineInfo, theLine, matchOffset,
+ matchLength)) {
+ /*
+ * We reached the end of the search
+ */
+ goto searchDone;
+ }
+ }
+ /*
+ * For forward matches, unless we allow overlaps, we
+ * move this on by the length of the current match so
+ * that we explicitly disallow overlapping matches.
+ */
+ if (matchLength > 0 && !searchSpecPtr->overlap
+ && !searchSpecPtr->backwards) {
+ firstOffset += matchLength;
+ if (firstOffset >= lastOffset) {
+ /*
+ * Now, we have to be careful not to find
+ * overlapping matches either on the same or
+ * following lines. Assume that if we did find
+ * something, it goes until the last extra line
+ * we added.
+ *
+ * We can break out of the loop, since we know
+ * no more will be found.
+ */
+ alreadySearchOffset = firstOffset - lastFullLine;
+ break;
+ }
+ /* We'll add this on again just below */
+ firstOffset --;
+ }
+ }
/*
- * Move the starting point one character on, in case
- * we are doing repeated or backwards searches (for the
- * latter, we actually do repeated forward searches).
+ * Move the starting point on, in case we are doing
+ * repeated or backwards searches (for the latter, we
+ * actually do repeated forward searches).
*/
firstOffset++;
- if (searchSpecPtr->all &&
- !(*searchSpecPtr->foundMatchProc)(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
- /*
- * We reached the end of the search
+ } while (searchSpecPtr->backwards || searchSpecPtr->all);
+
+ if (matches > 0) {
+ /*
+ * Now we have all the matches in our array, but not stored
+ * with 'foundMatchProc' yet.
+ */
+ matches--;
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ while (--matches >= 0) {
+ if (lineNum == searchSpecPtr->stopLine) {
+ /*
+ * It appears as if a condition like
+ * 'if (storeMatch[matches]
+ * < searchSpecPtr->stopOffset) break;'
+ *
+ * might be needed here, but no test case
+ * has been found which would exercise such
+ * a problem.
+ */
+ }
+ if (storeMatch[matches] + storeLength[matches]
+ >= matchOffset + matchLength) {
+ /*
+ * The new match totally encloses the previous
+ * one, so we overwrite the previous one.
+ */
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ continue;
+ }
+ if (!searchSpecPtr->overlap) {
+ if (storeMatch[matches] + storeLength[matches]
+ > matchOffset) {
+ continue;
+ }
+ }
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ matchOffset = storeMatch[matches];
+ matchLength = storeLength[matches];
+ }
+ if (searchSpecPtr->all && matches > 0) {
+ /*
+ * We only need to do this for the '-all' case,
+ * because just below we will call the
+ * foundMatchProc for the non-all case
*/
- goto searchDone;
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
+ } else {
+ lastBackwardsLineMatch = lineNum;
+ lastBackwardsMatchOffset = matchOffset;
}
- } while (searchSpecPtr->backwards || searchSpecPtr->all);
+ }
}
/*
@@ -3880,9 +4902,11 @@ SearchCore(interp, searchSpecPtr, patObj)
* and we are done.
*/
- if ((matchOffset >= 0) && !searchSpecPtr->all) {
- (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
- lineInfo, theLine, matchOffset, matchLength);
+ if ((lastBackwardsLineMatch == -1)
+ && (matchOffset >= 0)
+ && !searchSpecPtr->all) {
+ (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr,
+ lineInfo, theLine, matchOffset, matchLength);
goto searchDone;
}
@@ -3892,7 +4916,7 @@ SearchCore(interp, searchSpecPtr, patObj)
nextLine:
- for (; linesSearched>0 ; linesSearched--) {
+ while (linesSearched-- > 0) {
/*
* If we have just completed the 'stopLine', we are done
*/
@@ -3902,26 +4926,71 @@ SearchCore(interp, searchSpecPtr, patObj)
if (searchSpecPtr->backwards) {
lineNum--;
+
+ if (lastBackwardsLineMatch != -1
+ && ((lineNum < 0) || (lineNum + 2 < lastBackwardsLineMatch))) {
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch,
+ searchSpecPtr, NULL, NULL,
+ lastBackwardsMatchOffset,
+ matchLength);
+ lastBackwardsLineMatch = -1;
+ if (!searchSpecPtr->all) {
+ goto searchDone;
+ }
+ }
+
if (lineNum < 0) {
lineNum = searchSpecPtr->numLines-1;
}
+ if (!searchSpecPtr->exact) {
+ /*
+ * The 'exact' search loops above are designed to
+ * give us an accurate picture of the number of lines
+ * which we can skip here. For 'regexp' searches, on
+ * the other hand, which can match potentially variable
+ * lengths, we cannot skip multiple lines when searching
+ * backwards. Therefore we only allow one line to be
+ * skipped here.
+ */
+ break;
+ }
} else {
lineNum++;
if (lineNum >= searchSpecPtr->numLines) {
lineNum = 0;
}
}
+ if (lineNum == searchSpecPtr->startLine && linesSearched > 0) {
+ /*
+ * We've just searched all the way round and have
+ * gone right through the start line without finding
+ * anything in the last attempt.
+ */
+ break;
+ }
}
Tcl_SetObjLength(theLine, 0);
}
searchDone:
+ if (lastBackwardsLineMatch != -1) {
+ (*searchSpecPtr->foundMatchProc)(lastBackwardsLineMatch, searchSpecPtr,
+ NULL, NULL, lastBackwardsMatchOffset, matchLength);
+ }
+
/*
* Free up the cached line and pattern
*/
Tcl_DecrRefCount(theLine);
Tcl_DecrRefCount(patObj);
+ /*
+ * Free up any extra space we allocated
+ */
+ if (storeMatch != smArray) {
+ ckfree((char*)storeMatch);
+ }
+
return code;
}