diff options
Diffstat (limited to 'generic/tkText.c')
-rw-r--r-- | generic/tkText.c | 6920 |
1 files changed, 5257 insertions, 1663 deletions
diff --git a/generic/tkText.c b/generic/tkText.c index fb15468..f3e1c26 100644 --- a/generic/tkText.c +++ b/generic/tkText.c @@ -1,22 +1,20 @@ -/* +/* * 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. + * 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. 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. * Copyright (c) 1999 by Scriptics Corporation. * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "default.h" -#include "tkPort.h" #include "tkInt.h" #include "tkUndo.h" @@ -25,323 +23,409 @@ #define DInfo TkDInfo #endif +/* + * For compatibility with Tk 4.0 through 8.4.x, we allow tabs to be + * mis-specified with non-increasing values. These are converted into tabs + * which are the equivalent of at least a character width apart. + */ + +#if (TK_MAJOR_VERSION < 9) +#define _TK_ALLOW_DECREASING_TABS +#endif + #include "tkText.h" /* - * Custom options for handling "-state" + * Used to avoid having to allocate and deallocate arrays on the fly for + * commonly used functions. Must be > 0. + */ + +#define PIXEL_CLIENTS 5 + +/* + * The 'TkTextState' enum in tkText.h is used to define a type for the -state + * option of the Text widget. These values are used as indices into the string + * table below. */ -static Tk_CustomOption stateOption = { - (Tk_OptionParseProc *) TkStateParseProc, - TkStatePrintProc, (ClientData) NULL /* only "normal" and "disabled" */ +static const char *const stateStrings[] = { + "disabled", "normal", NULL }; /* - * Information used to parse text configuration options: + * The 'TkWrapMode' enum in tkText.h is used to define a type for the -wrap + * option of the Text widget. These values are used as indices into the string + * table below. */ -static Tk_ConfigSpec configSpecs[] = { - {TK_CONFIG_BOOLEAN, "-autoseparators", "autoSeparators", - "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, - Tk_Offset(TkText, autoSeparators), 0}, - {TK_CONFIG_BORDER, "-background", "background", "Background", - DEF_TEXT_BG_COLOR, Tk_Offset(TkText, border), TK_CONFIG_COLOR_ONLY}, - {TK_CONFIG_BORDER, "-background", "background", "Background", - DEF_TEXT_BG_MONO, Tk_Offset(TkText, border), TK_CONFIG_MONO_ONLY}, - {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL, - (char *) NULL, 0, 0}, - {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL, - (char *) NULL, 0, 0}, - {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", - DEF_TEXT_BORDER_WIDTH, Tk_Offset(TkText, borderWidth), 0}, - {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor", - DEF_TEXT_CURSOR, Tk_Offset(TkText, cursor), TK_CONFIG_NULL_OK}, - {TK_CONFIG_BOOLEAN, "-exportselection", "exportSelection", - "ExportSelection", DEF_TEXT_EXPORT_SELECTION, - Tk_Offset(TkText, exportSelection), 0}, - {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL, - (char *) NULL, 0, 0}, - {TK_CONFIG_FONT, "-font", "font", "Font", - DEF_TEXT_FONT, Tk_Offset(TkText, tkfont), 0}, - {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground", - DEF_TEXT_FG, Tk_Offset(TkText, fgColor), 0}, - {TK_CONFIG_PIXELS, "-height", "height", "Height", - DEF_TEXT_HEIGHT, Tk_Offset(TkText, height), 0}, - {TK_CONFIG_COLOR, "-highlightbackground", "highlightBackground", - "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG, - Tk_Offset(TkText, highlightBgColorPtr), 0}, - {TK_CONFIG_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", - DEF_TEXT_HIGHLIGHT, Tk_Offset(TkText, highlightColorPtr), 0}, - {TK_CONFIG_PIXELS, "-highlightthickness", "highlightThickness", - "HighlightThickness", - DEF_TEXT_HIGHLIGHT_WIDTH, Tk_Offset(TkText, highlightWidth), 0}, - {TK_CONFIG_BORDER, "-insertbackground", "insertBackground", "Foreground", - DEF_TEXT_INSERT_BG, Tk_Offset(TkText, insertBorder), 0}, - {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", - DEF_TEXT_INSERT_BD_COLOR, Tk_Offset(TkText, insertBorderWidth), - TK_CONFIG_COLOR_ONLY}, - {TK_CONFIG_PIXELS, "-insertborderwidth", "insertBorderWidth", "BorderWidth", - DEF_TEXT_INSERT_BD_MONO, Tk_Offset(TkText, insertBorderWidth), - TK_CONFIG_MONO_ONLY}, - {TK_CONFIG_INT, "-insertofftime", "insertOffTime", "OffTime", - DEF_TEXT_INSERT_OFF_TIME, Tk_Offset(TkText, insertOffTime), 0}, - {TK_CONFIG_INT, "-insertontime", "insertOnTime", "OnTime", - DEF_TEXT_INSERT_ON_TIME, Tk_Offset(TkText, insertOnTime), 0}, - {TK_CONFIG_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", - DEF_TEXT_INSERT_WIDTH, Tk_Offset(TkText, insertWidth), 0}, - {TK_CONFIG_INT, "-maxundo", "maxUndo", "MaxUndo", - DEF_TEXT_MAX_UNDO, Tk_Offset(TkText, maxUndo), 0}, - {TK_CONFIG_PIXELS, "-padx", "padX", "Pad", - DEF_TEXT_PADX, Tk_Offset(TkText, padX), 0}, - {TK_CONFIG_PIXELS, "-pady", "padY", "Pad", - DEF_TEXT_PADY, Tk_Offset(TkText, padY), 0}, - {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", - DEF_TEXT_RELIEF, Tk_Offset(TkText, relief), 0}, - {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", - DEF_TEXT_SELECT_COLOR, Tk_Offset(TkText, selBorder), - TK_CONFIG_COLOR_ONLY}, - {TK_CONFIG_BORDER, "-selectbackground", "selectBackground", "Foreground", - DEF_TEXT_SELECT_MONO, Tk_Offset(TkText, selBorder), - TK_CONFIG_MONO_ONLY}, - {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth", - DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selBdString), - TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK}, - {TK_CONFIG_STRING, "-selectborderwidth", "selectBorderWidth", "BorderWidth", - DEF_TEXT_SELECT_BD_MONO, Tk_Offset(TkText, selBdString), - TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK}, - {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", - DEF_TEXT_SELECT_FG_COLOR, Tk_Offset(TkText, selFgColorPtr), - TK_CONFIG_COLOR_ONLY|TK_CONFIG_NULL_OK}, - {TK_CONFIG_COLOR, "-selectforeground", "selectForeground", "Background", - DEF_TEXT_SELECT_FG_MONO, Tk_Offset(TkText, selFgColorPtr), - TK_CONFIG_MONO_ONLY|TK_CONFIG_NULL_OK}, - {TK_CONFIG_BOOLEAN, "-setgrid", "setGrid", "SetGrid", - DEF_TEXT_SET_GRID, Tk_Offset(TkText, setGrid), 0}, - {TK_CONFIG_PIXELS, "-spacing1", "spacing1", "Spacing", - DEF_TEXT_SPACING1, Tk_Offset(TkText, spacing1), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_PIXELS, "-spacing2", "spacing2", "Spacing", - DEF_TEXT_SPACING2, Tk_Offset(TkText, spacing2), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_PIXELS, "-spacing3", "spacing3", "Spacing", - DEF_TEXT_SPACING3, Tk_Offset(TkText, spacing3), - TK_CONFIG_DONT_SET_DEFAULT}, - {TK_CONFIG_CUSTOM, "-state", "state", "State", - DEF_TEXT_STATE, Tk_Offset(TkText, state), 0, &stateOption}, - {TK_CONFIG_STRING, "-tabs", "tabs", "Tabs", - DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionString), TK_CONFIG_NULL_OK}, - {TK_CONFIG_STRING, "-takefocus", "takeFocus", "TakeFocus", - DEF_TEXT_TAKE_FOCUS, Tk_Offset(TkText, takeFocus), - TK_CONFIG_NULL_OK}, - {TK_CONFIG_BOOLEAN, "-undo", "undo", "Undo", - DEF_TEXT_UNDO, Tk_Offset(TkText, undo), 0}, - {TK_CONFIG_INT, "-width", "width", "Width", - DEF_TEXT_WIDTH, Tk_Offset(TkText, width), 0}, - {TK_CONFIG_CUSTOM, "-wrap", "wrap", "Wrap", - DEF_TEXT_WRAP, Tk_Offset(TkText, wrapMode), 0, &TkTextWrapModeOption}, - {TK_CONFIG_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand", - DEF_TEXT_XSCROLL_COMMAND, Tk_Offset(TkText, xScrollCmd), - TK_CONFIG_NULL_OK}, - {TK_CONFIG_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", - DEF_TEXT_YSCROLL_COMMAND, Tk_Offset(TkText, yScrollCmd), - TK_CONFIG_NULL_OK}, - {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL, - (char *) NULL, 0, 0} +static const char *const wrapStrings[] = { + "char", "none", "word", NULL }; /* - * Boolean variable indicating whether or not special debugging code - * should be executed. + * The 'TkTextTabStyle' enum in tkText.h is used to define a type for the + * -tabstyle option of the Text widget. These values are used as indices into + * the string table below. */ -int tkTextDebug = 0; +static const char *const tabStyleStrings[] = { + "tabular", "wordprocessor", NULL +}; /* - * Custom options for handling "-wrap": + * The following functions and custom option type are used to define the + * "line" option type, and thereby handle the text widget '-startline', + * '-endline' configuration options which are of that type. + * + * We do not need a 'freeProc' because all changes to these two options are + * handled through the TK_TEXT_LINE_RANGE flag in the optionSpecs list, and + * the internal storage is just a pointer, which therefore doesn't need + * freeing. */ -static int WrapModeParseProc _ANSI_ARGS_((ClientData clientData, +static int SetLineStartEnd(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin, - CONST char *value, char *widgRec, int offset)); -static char * WrapModePrintProc _ANSI_ARGS_((ClientData clientData, - Tk_Window tkwin, char *widgRec, int offset, - Tcl_FreeProc **freeProcPtr)); - -Tk_CustomOption TkTextWrapModeOption = { - WrapModeParseProc, - WrapModePrintProc, - (ClientData) NULL + Tcl_Obj **value, char *recordPtr, + int internalOffset, char *oldInternalPtr, + int flags); +static Tcl_Obj * GetLineStartEnd(ClientData clientData, + Tk_Window tkwin, char *recordPtr, + int internalOffset); +static void RestoreLineStartEnd(ClientData clientData, + Tk_Window tkwin, char *internalPtr, + char *oldInternalPtr); +static int ObjectIsEmpty(Tcl_Obj *objPtr); + +static const Tk_ObjCustomOption lineOption = { + "line", /* name */ + SetLineStartEnd, /* setProc */ + GetLineStartEnd, /* getProc */ + RestoreLineStartEnd, /* restoreProc */ + NULL, /* freeProc */ + 0 }; /* - *-------------------------------------------------------------- - * - * WrapModeParseProc -- - * - * This procedure is invoked during option processing to handle - * "-wrap" options for text widgets. - * - * Results: - * A standard Tcl return value. - * - * Side effects: - * The wrap mode for a given item gets replaced by the wrap mode - * indicated in the value argument. - * - *-------------------------------------------------------------- + * Information used to parse text configuration options: */ -static int -WrapModeParseProc(clientData, interp, tkwin, value, widgRec, offset) - ClientData clientData; /* some flags.*/ - Tcl_Interp *interp; /* Used for reporting errors. */ - Tk_Window tkwin; /* Window containing canvas widget. */ - CONST char *value; /* Value of option (list of tag - * names). */ - char *widgRec; /* Pointer to record for item. */ - int offset; /* Offset into item. */ -{ - int c; - size_t length; +static const Tk_OptionSpec optionSpecs[] = { + {TK_OPTION_BOOLEAN, "-autoseparators", "autoSeparators", + "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, -1, + Tk_Offset(TkText, autoSeparators), 0, 0, 0}, + {TK_OPTION_BORDER, "-background", "background", "Background", + DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border), + 0, (ClientData) DEF_TEXT_BG_MONO, 0}, + {TK_OPTION_SYNONYM, "-bd", NULL, NULL, + NULL, 0, -1, 0, (ClientData) "-borderwidth", + TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_SYNONYM, "-bg", NULL, NULL, + 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, TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor", + DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_CUSTOM, "-endline", NULL, NULL, + NULL, -1, Tk_Offset(TkText, end), TK_OPTION_NULL_OK, + (ClientData) &lineOption, TK_TEXT_LINE_RANGE}, + {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection", + "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1, + Tk_Offset(TkText, exportSelection), 0, 0, 0}, + {TK_OPTION_SYNONYM, "-fg", "foreground", NULL, + NULL, 0, -1, 0, (ClientData) "-foreground", 0}, + {TK_OPTION_FONT, "-font", "font", "Font", + 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}, + {TK_OPTION_PIXELS, "-height", "height", "Height", + DEF_TEXT_HEIGHT, -1, Tk_Offset(TkText, height), 0, 0, 0}, + {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground", + "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG, + -1, Tk_Offset(TkText, highlightBgColorPtr), + 0, 0, 0}, + {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor", + DEF_TEXT_HIGHLIGHT, -1, Tk_Offset(TkText, highlightColorPtr), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness", + "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1, + Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_BORDER, "-inactiveselectbackground","inactiveSelectBackground", + "Foreground", + DEF_TEXT_INACTIVE_SELECT_COLOR, + -1, Tk_Offset(TkText, inactiveSelBorder), + TK_OPTION_NULL_OK, (ClientData) DEF_TEXT_SELECT_MONO, 0}, + {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground", + DEF_TEXT_INSERT_BG, + -1, Tk_Offset(TkText, insertBorder), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth", + "BorderWidth", DEF_TEXT_INSERT_BD_COLOR, -1, + Tk_Offset(TkText, insertBorderWidth), 0, + (ClientData) DEF_TEXT_INSERT_BD_MONO, 0}, + {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime", + DEF_TEXT_INSERT_OFF_TIME, -1, Tk_Offset(TkText, insertOffTime), + 0, 0, 0}, + {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime", + DEF_TEXT_INSERT_ON_TIME, -1, Tk_Offset(TkText, insertOnTime), + 0, 0, 0}, + {TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth", + DEF_TEXT_INSERT_WIDTH, -1, Tk_Offset(TkText, insertWidth), + 0, 0, 0}, + {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, + 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", + DEF_TEXT_RELIEF, -1, Tk_Offset(TkText, relief), 0, 0, 0}, + {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground", + DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selBorder), + 0, (ClientData) DEF_TEXT_SELECT_MONO, 0}, + {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", + "BorderWidth", DEF_TEXT_SELECT_BD_COLOR, + Tk_Offset(TkText, selBorderWidthPtr), + Tk_Offset(TkText, selBorderWidth), + TK_OPTION_NULL_OK, (ClientData) DEF_TEXT_SELECT_BD_MONO, 0}, + {TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background", + DEF_TEXT_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selFgColorPtr), + TK_CONFIG_NULL_OK, (ClientData) DEF_TEXT_SELECT_FG_MONO, 0}, + {TK_OPTION_BOOLEAN, "-setgrid", "setGrid", "SetGrid", + 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 , TK_TEXT_LINE_GEOMETRY }, + {TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing", + DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2), + 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 , TK_TEXT_LINE_GEOMETRY }, + {TK_OPTION_CUSTOM, "-startline", NULL, NULL, + NULL, -1, Tk_Offset(TkText, start), TK_OPTION_NULL_OK, + (ClientData) &lineOption, TK_TEXT_LINE_RANGE}, + {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, TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_STRING_TABLE, "-tabstyle", "tabStyle", "TabStyle", + DEF_TEXT_TABSTYLE, -1, Tk_Offset(TkText, tabStyle), + 0, (ClientData) tabStyleStrings, 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, + TK_TEXT_LINE_GEOMETRY}, + {TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap", + DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode), + 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}, + {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand", + DEF_TEXT_YSCROLL_COMMAND, -1, Tk_Offset(TkText, yScrollCmd), + TK_OPTION_NULL_OK, 0, 0}, + {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0} +}; - register TkWrapMode *wrapPtr = (TkWrapMode *) (widgRec + offset); +/* + * These three typedefs, the structure and the SearchPerform, SearchCore + * functions below are used for line-based searches of the text widget, and, + * in particular, to handle multi-line matching even though the text widget is + * a single-line based data structure. They are completely abstracted away + * from the Text widget internals, however, so could easily be re-used with + * any line-based entity to provide multi-line matching. + * + * We have abstracted this code away from the text widget to try to keep Tk as + * modular as possible. + */ - if(value == NULL || *value == 0) { - *wrapPtr = TEXT_WRAPMODE_NULL; - return TCL_OK; - } +struct SearchSpec; /* Forward declaration. */ + +typedef ClientData SearchAddLineProc(int lineNum, + struct SearchSpec *searchSpecPtr, + Tcl_Obj *theLine, int *lenPtr, + int *extraLinesPtr); +typedef int SearchMatchProc(int lineNum, + struct SearchSpec *searchSpecPtr, + ClientData clientData, Tcl_Obj *theLine, + int matchOffset, int matchLength); +typedef int SearchLineIndexProc(Tcl_Interp *interp, + Tcl_Obj *objPtr, struct SearchSpec *searchSpecPtr, + int *linePosPtr, int *offsetPosPtr); + +typedef struct SearchSpec { + int exact; /* Whether search is exact or regexp. */ + 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. */ + int startOffset; /* Index in first line to start at. */ + int stopLine; /* Last line to examine, or -1 when we search + * all available text. */ + int stopOffset; /* Index to stop at, provided stopLine is not + * -1. */ + int numLines; /* Total lines which are available. */ + int backwards; /* Searching forwards or backwards. */ + Tcl_Obj *varPtr; /* If non-NULL, store length(s) of match(es) + * in this variable. */ + Tcl_Obj *countPtr; /* Keeps track of currently found lengths. */ + Tcl_Obj *resPtr; /* Keeps track of currently found locations */ + int searchElide; /* Search in hidden text as well. */ + SearchAddLineProc *addLineProc; + /* Function to call when we need to add + * another line to the search string so far */ + SearchMatchProc *foundMatchProc; + /* Function to call when we have found a + * match. */ + SearchLineIndexProc *lineIndexProc; + /* Function to call when we have found a + * match. */ + ClientData clientData; /* Information about structure being searched, + * in this case a text widget. */ +} SearchSpec; - c = value[0]; - length = strlen(value); +/* + * The text-widget-independent functions which actually perform the search, + * handling both regexp and exact searches. + */ - if ((c == 'c') && (strncmp(value, "char", length) == 0)) { - *wrapPtr = TEXT_WRAPMODE_CHAR; - return TCL_OK; - } - if ((c == 'n') && (strncmp(value, "none", length) == 0)) { - *wrapPtr = TEXT_WRAPMODE_NONE; - return TCL_OK; - } - if ((c == 'w') && (strncmp(value, "word", length) == 0)) { - *wrapPtr = TEXT_WRAPMODE_WORD; - return TCL_OK; - } - Tcl_AppendResult(interp, "bad wrap mode \"", value, - "\": must be char, none, or word", - (char *) NULL); - *wrapPtr = TEXT_WRAPMODE_CHAR; - return TCL_ERROR; -} +static int SearchCore(Tcl_Interp *interp, + SearchSpec *searchSpecPtr, Tcl_Obj *patObj); +static int SearchPerform(Tcl_Interp *interp, + SearchSpec *searchSpecPtr, Tcl_Obj *patObj, + Tcl_Obj *fromPtr, Tcl_Obj *toPtr); /* - *-------------------------------------------------------------- - * - * WrapModePrintProc -- - * - * This procedure is invoked by the Tk configuration code - * to produce a printable string for the "-wrap" configuration - * option for canvas items. - * - * Results: - * The return value is a string describing the state for - * the item referred to by "widgRec". In addition, *freeProcPtr - * is filled in with the address of a procedure to call to free - * the result string when it's no longer needed (or NULL to - * indicate that the string doesn't need to be freed). - * - * Side effects: - * None. - * - *-------------------------------------------------------------- + * Boolean variable indicating whether or not special debugging code should be + * executed. */ -static char * -WrapModePrintProc(clientData, tkwin, widgRec, offset, freeProcPtr) - ClientData clientData; /* Ignored. */ - Tk_Window tkwin; /* Window containing canvas widget. */ - char *widgRec; /* Pointer to record for item. */ - int offset; /* Ignored. */ - Tcl_FreeProc **freeProcPtr; /* Pointer to variable to fill in with - * information about how to reclaim - * storage for return string. */ -{ - register TkWrapMode *wrapPtr = (TkWrapMode *) (widgRec + offset); - - if (*wrapPtr==TEXT_WRAPMODE_CHAR) { - return "char"; - } else if (*wrapPtr==TEXT_WRAPMODE_NONE) { - return "none"; - } else if (*wrapPtr==TEXT_WRAPMODE_WORD) { - return "word"; - } else { - return ""; - } -} +int tkTextDebug = 0; /* - * Forward declarations for procedures defined later in this file: + * Forward declarations for functions defined later in this file: */ -static int ConfigureText _ANSI_ARGS_((Tcl_Interp *interp, - TkText *textPtr, int argc, CONST char **argv, - int flags)); -static int DeleteChars _ANSI_ARGS_((TkText *textPtr, - CONST char *index1String, CONST char *index2String, - TkTextIndex *indexPtr1, TkTextIndex *indexPtr2)); -static void DestroyText _ANSI_ARGS_((char *memPtr)); -static void InsertChars _ANSI_ARGS_((TkText *textPtr, - TkTextIndex *indexPtr, CONST char *string)); -static void TextBlinkProc _ANSI_ARGS_((ClientData clientData)); -static void TextCmdDeletedProc _ANSI_ARGS_(( - ClientData clientData)); -static void TextEventProc _ANSI_ARGS_((ClientData clientData, - XEvent *eventPtr)); -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 TextSearchCmd _ANSI_ARGS_((TkText *textPtr, - Tcl_Interp *interp, int argc, CONST char **argv)); -static int TextEditCmd _ANSI_ARGS_((TkText *textPtr, - Tcl_Interp *interp, int argc, CONST char **argv)); -static int TextWidgetCmd _ANSI_ARGS_((ClientData clientData, - Tcl_Interp *interp, int argc, CONST char **argv)); -static void TextWorldChanged _ANSI_ARGS_(( - ClientData instanceData)); -static int TextDumpCmd _ANSI_ARGS_((TkText *textPtr, - Tcl_Interp *interp, int argc, CONST char **argv)); -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, - TkTextIndex *index, int what)); -static int TextEditUndo _ANSI_ARGS_((TkText *textPtr)); -static int TextEditRedo _ANSI_ARGS_((TkText *textPtr)); -static void TextGetText _ANSI_ARGS_((TkTextIndex * index1, - TkTextIndex * index2, Tcl_DString *dsPtr)); +static int ConfigureText(Tcl_Interp *interp, + TkText *textPtr, int objc, Tcl_Obj *const objv[]); +static int DeleteIndexRange(TkSharedText *sharedPtr, + TkText *textPtr, const TkTextIndex *indexPtr1, + const TkTextIndex *indexPtr2, int viewUpdate); +static int CountIndices(const TkText *textPtr, + const TkTextIndex *indexPtr1, + const TkTextIndex *indexPtr2, + TkTextCountType type); +static void DestroyText(TkText *textPtr); +static int InsertChars(TkSharedText *sharedTextPtr, + TkText *textPtr, TkTextIndex *indexPtr, + Tcl_Obj *stringPtr, int viewUpdate); +static void TextBlinkProc(ClientData clientData); +static void TextCmdDeletedProc(ClientData clientData); +static int CreateWidget(TkSharedText *sharedPtr, Tk_Window tkwin, + Tcl_Interp *interp, const TkText *parent, + int objc, Tcl_Obj *const objv[]); +static void TextEventProc(ClientData clientData, + XEvent *eventPtr); +static int TextFetchSelection(ClientData clientData, int offset, + char *buffer, int maxBytes); +static int TextIndexSortProc(const void *first, + const void *second); +static int TextInsertCmd(TkSharedText *sharedTextPtr, + TkText *textPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[], + const TkTextIndex *indexPtr, int viewUpdate); +static int TextReplaceCmd(TkText *textPtr, Tcl_Interp *interp, + const TkTextIndex *indexFromPtr, + const TkTextIndex *indexToPtr, + int objc, Tcl_Obj *const objv[], int viewUpdate); +static int TextSearchCmd(TkText *textPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int TextEditCmd(TkText *textPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int TextWidgetObjCmd(ClientData clientData, + Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int SharedTextObjCmd(ClientData clientData, + Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static void TextWorldChangedCallback(ClientData instanceData); +static void TextWorldChanged(TkText *textPtr, int mask); +static int TextDumpCmd(TkText *textPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static int DumpLine(Tcl_Interp *interp, TkText *textPtr, + int what, TkTextLine *linePtr, int start, int end, + int lineno, Tcl_Obj *command); +static int DumpSegment(TkText *textPtr, Tcl_Interp *interp, + const char *key, const char *value, + Tcl_Obj *command, const TkTextIndex *index, + int what); +static int TextEditUndo(TkText *textPtr); +static int TextEditRedo(TkText *textPtr); +static Tcl_Obj * TextGetText(const TkText *textPtr, + const TkTextIndex *index1, + const TkTextIndex *index2, int visibleOnly); static void GenerateModifiedEvent(TkText *textPtr); -static void updateDirtyFlag _ANSI_ARGS_((TkText *textPtr)); +static void UpdateDirtyFlag(TkSharedText *sharedPtr); +static void TextPushUndoAction(TkText *textPtr, + Tcl_Obj *undoString, int insert, + const TkTextIndex *index1Ptr, + const TkTextIndex *index2Ptr); +static int TextSearchIndexInLine(const SearchSpec *searchSpecPtr, + TkTextLine *linePtr, int byteIndex); +static int TextPeerCmd(TkText *textPtr, Tcl_Interp *interp, + int objc, Tcl_Obj *const objv[]); +static TkUndoProc TextUndoRedoCallback; + +/* + * Declarations of the three search procs required by the multi-line search + * routines. + */ + +static SearchMatchProc TextSearchFoundMatch; +static SearchAddLineProc TextSearchAddNextLine; +static SearchLineIndexProc TextSearchGetLineIndex; /* - * The structure below defines text class behavior by means of procedures - * that can be invoked from generic window code. + * The structure below defines text class behavior by means of functions that + * can be invoked from generic window code. */ static Tk_ClassProcs textClass = { sizeof(Tk_ClassProcs), /* size */ - TextWorldChanged, /* worldChangedProc */ + TextWorldChangedCallback, /* worldChangedProc */ + NULL, /* createProc */ + NULL /* modalProc */ }; - /* *-------------------------------------------------------------- * - * Tk_TextCmd -- + * Tk_TextObjCmd -- * - * This procedure is invoked to process the "text" Tcl command. - * See the user documentation for details on what it does. + * This function is invoked to process the "text" Tcl command. See the + * user documentation for details on what it does. * * Results: * A standard Tcl result. @@ -353,85 +437,201 @@ static Tk_ClassProcs textClass = { */ int -Tk_TextCmd(clientData, interp, argc, argv) - ClientData clientData; /* Main window associated with - * interpreter. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - CONST char **argv; /* Argument strings. */ +Tk_TextObjCmd( + ClientData clientData, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ { Tk_Window tkwin = (Tk_Window) clientData; - Tk_Window new; - register TkText *textPtr; - TkTextIndex startIndex; - if (argc < 2) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " pathName ?options?\"", (char *) NULL); + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "pathName ?options?"); return TCL_ERROR; } + return CreateWidget(NULL, tkwin, interp, NULL, objc, objv); +} + +/* + *-------------------------------------------------------------- + * + * CreateWidget -- + * + * This function is invoked to process the "text" Tcl command, (when + * called by Tk_TextObjCmd) and the "$text peer create" text widget + * sub-command (called from TextPeerCmd). + * + * See the user documentation for details on what it does. + * + * Results: + * A standard Tcl result, places the name of the widget created into the + * interp's result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +CreateWidget( + TkSharedText *sharedPtr, /* Shared widget info, or NULL. */ + Tk_Window tkwin, /* Main window associated with interpreter. */ + Tcl_Interp *interp, /* Current interpreter. */ + const TkText *parent, /* If non-NULL then take default start, end + * from this parent. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + register TkText *textPtr; + Tk_OptionTable optionTable; + TkTextIndex startIndex; + Tk_Window newWin; + /* * Create the window. */ - new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL); - if (new == NULL) { + newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]), + NULL); + if (newWin == NULL) { return TCL_ERROR; } /* - * Create the text widget and initialize everything to zero, - * then set the necessary initial (non-NULL) values. + * Create the text widget and initialize everything to zero, then set the + * necessary initial (non-NULL) values. It is important that the 'set' tag + * and 'insert', 'current' mark pointers are all NULL to start. */ textPtr = (TkText *) ckalloc(sizeof(TkText)); - memset((VOID *) textPtr, 0, sizeof(TkText)); + memset(textPtr, 0, sizeof(TkText)); - textPtr->tkwin = new; - textPtr->display = Tk_Display(new); + textPtr->tkwin = newWin; + textPtr->display = Tk_Display(newWin); textPtr->interp = interp; - textPtr->widgetCmd = Tcl_CreateCommand(interp, - Tk_PathName(textPtr->tkwin), TextWidgetCmd, + textPtr->widgetCmd = Tcl_CreateObjCommand(interp, + Tk_PathName(textPtr->tkwin), TextWidgetObjCmd, (ClientData) textPtr, TextCmdDeletedProc); - textPtr->tree = TkBTreeCreate(textPtr); - Tcl_InitHashTable(&textPtr->tagTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->markTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->windowTable, TCL_STRING_KEYS); - Tcl_InitHashTable(&textPtr->imageTable, TCL_STRING_KEYS); - textPtr->state = TK_STATE_NORMAL; + + if (sharedPtr == NULL) { + sharedPtr = (TkSharedText *) ckalloc(sizeof(TkSharedText)); + memset(sharedPtr, 0, sizeof(TkSharedText)); + + sharedPtr->refCount = 0; + sharedPtr->peers = NULL; + sharedPtr->tree = TkBTreeCreate(sharedPtr); + + Tcl_InitHashTable(&sharedPtr->tagTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->markTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->windowTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&sharedPtr->imageTable, TCL_STRING_KEYS); + sharedPtr->undoStack = TkUndoInitStack(interp,0); + sharedPtr->undo = 1; + sharedPtr->isDirty = 0; + sharedPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; + sharedPtr->autoSeparators = 1; + sharedPtr->lastEditMode = TK_TEXT_EDIT_OTHER; + sharedPtr->stateEpoch = 0; + } + + /* + * Add the new widget to the shared list. + */ + + textPtr->sharedTextPtr = sharedPtr; + sharedPtr->refCount++; + textPtr->next = sharedPtr->peers; + sharedPtr->peers = textPtr; + + /* + * This refCount will be held until DestroyText is called. Note also that + * the later call to 'TkTextCreateDInfo' will add more refCounts. + */ + + textPtr->refCount = 1; + + /* + * Specify start and end lines in the B-tree. The default is the same as + * the parent, but this can be adjusted to display more or less if the + * start, end where given as configuration options. + */ + + if (parent != NULL) { + textPtr->start = parent->start; + textPtr->end = parent->end; + } else { + textPtr->start = NULL; + textPtr->end = NULL; + } + + /* + * Register with the B-tree. In some sense it would be best if we could do + * this later (after configuration options), so that any changes to + * start,end do not require a total recalculation. + */ + + TkBTreeAddClient(sharedPtr->tree, textPtr, textPtr->charHeight); + + textPtr->state = TK_TEXT_STATE_NORMAL; 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); + textPtr->prevWidth = Tk_Width(newWin); + textPtr->prevHeight = Tk_Height(newWin); + + /* + * This will add refCounts to textPtr. + */ + TkTextCreateDInfo(textPtr); - TkTextMakeByteIndex(textPtr->tree, 0, 0, &startIndex); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, + &startIndex); TkTextSetYView(textPtr, &startIndex, 0); textPtr->exportSelection = 1; textPtr->pickEvent.type = LeaveNotify; - textPtr->undoStack = TkUndoInitStack(interp,0); - textPtr->undo = 1; - textPtr->isDirty = 0; - textPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; - textPtr->autoSeparators = 1; - textPtr->lastEditMode = TK_TEXT_EDIT_OTHER; + textPtr->undo = textPtr->sharedTextPtr->undo; + textPtr->maxUndo = textPtr->sharedTextPtr->maxUndo; + textPtr->autoSeparators = textPtr->sharedTextPtr->autoSeparators; + textPtr->tabOptionPtr = NULL; /* * Create the "sel" tag and the "current" and "insert" marks. */ - textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel"); - textPtr->selTagPtr->reliefString = - (char *) ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF)); + textPtr->selBorder = NULL; + textPtr->inactiveSelBorder = NULL; + textPtr->selBorderWidth = 0; + textPtr->selBorderWidthPtr = NULL; + textPtr->selFgColorPtr = NULL; + + /* + * Note: it is important that textPtr->selTagPtr is NULL before this + * initial call. + */ + + textPtr->selTagPtr = TkTextCreateTag(textPtr, "sel", NULL); + textPtr->selTagPtr->reliefString = (char *) + ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF)); strcpy(textPtr->selTagPtr->reliefString, DEF_TEXT_SELECT_RELIEF); - Tk_GetRelief(interp, DEF_TEXT_SELECT_RELIEF, &(textPtr->selTagPtr->relief)); + Tk_GetRelief(interp, DEF_TEXT_SELECT_RELIEF, &textPtr->selTagPtr->relief); textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex); textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &startIndex); + /* + * Create the option table for this widget class. If it has already been + * created, the cached pointer will be returned. + */ + + optionTable = Tk_CreateOptionTable(interp, optionSpecs); + Tk_SetClass(textPtr->tkwin, "Text"); Tk_SetClassProcs(textPtr->tkwin, &textClass, (ClientData) textPtr); + textPtr->optionTable = optionTable; + Tk_CreateEventHandler(textPtr->tkwin, ExposureMask|StructureNotifyMask|FocusChangeMask, TextEventProc, (ClientData) textPtr); @@ -441,23 +641,30 @@ Tk_TextCmd(clientData, interp, argc, argv) TkTextBindProc, (ClientData) textPtr); Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING, TextFetchSelection, (ClientData) textPtr, XA_STRING); - if (ConfigureText(interp, textPtr, argc-2, argv+2, 0) != TCL_OK) { + + if (Tk_InitOptions(interp, (char *) textPtr, optionTable, textPtr->tkwin) + != TCL_OK) { + Tk_DestroyWindow(textPtr->tkwin); + return TCL_ERROR; + } + if (ConfigureText(interp, textPtr, objc-2, objv+2) != TCL_OK) { Tk_DestroyWindow(textPtr->tkwin); return TCL_ERROR; } - Tcl_SetResult(interp, Tk_PathName(textPtr->tkwin), TCL_STATIC); + Tcl_SetObjResult(interp, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin),-1)); return TCL_OK; } /* *-------------------------------------------------------------- * - * TextWidgetCmd -- + * TextWidgetObjCmd -- * - * This procedure is invoked to process the Tcl command - * that corresponds to a text widget. See the user - * documentation for details on what it does. + * This function is invoked to process the Tcl command that corresponds + * to a text widget. See the user documentation for details on what it + * does. * * Results: * A standard Tcl result. @@ -469,88 +676,118 @@ Tk_TextCmd(clientData, interp, argc, argv) */ static int -TextWidgetCmd(clientData, interp, argc, argv) - ClientData clientData; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - CONST char **argv; /* Argument strings. */ +TextWidgetObjCmd( + ClientData clientData, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ { register TkText *textPtr = (TkText *) clientData; - int c, result = TCL_OK; - size_t length; - TkTextIndex index1, index2; + int result = TCL_OK; + int index; + + static const char *optionStrings[] = { + "bbox", "cget", "compare", "configure", "count", "debug", "delete", + "dlineinfo", "dump", "edit", "get", "image", "index", "insert", + "mark", "peer", "replace", "scan", "search", "see", "tag", "window", + "xview", "yview", NULL + }; + enum options { + 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_PEER, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH, TEXT_SEE, + TEXT_TAG, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } - if (argc < 2) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " option ?arg arg ...?\"", (char *) NULL); + if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, + &index) != TCL_OK) { return TCL_ERROR; } - Tcl_Preserve((ClientData) textPtr); - c = argv[1][0]; - length = strlen(argv[1]); - if ((c == 'b') && (strncmp(argv[1], "bbox", length) == 0)) { + textPtr->refCount++; + + switch ((enum options) index) { + case TEXT_BBOX: { int x, y, width, height; + const TkTextIndex *indexPtr; - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " bbox index\"", (char *) NULL); + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); result = TCL_ERROR; goto done; } - if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) { + indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexPtr == NULL) { result = TCL_ERROR; goto done; } - if (TkTextCharBbox(textPtr, &index1, &x, &y, &width, &height) == 0) { - char buf[TCL_INTEGER_SPACE * 4]; - - sprintf(buf, "%d %d %d %d", x, y, width, height); - Tcl_SetResult(interp, buf, TCL_VOLATILE); - } - } else if ((c == 'c') && (strncmp(argv[1], "cget", length) == 0) - && (length >= 2)) { - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " cget option\"", - (char *) NULL); + if (TkTextIndexBbox(textPtr, indexPtr, &x, &y, &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)); + + Tcl_SetObjResult(interp, listObj); + } + break; + } + case TEXT_CGET: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option"); result = TCL_ERROR; goto done; + } else { + Tcl_Obj *objPtr = Tk_GetOptionValue(interp, (char *) textPtr, + textPtr->optionTable, objv[2], textPtr->tkwin); + if (objPtr == NULL) { + result = TCL_ERROR; + goto done; + } else { + Tcl_SetObjResult(interp, objPtr); + result = TCL_OK; + } } - result = Tk_ConfigureValue(interp, textPtr->tkwin, configSpecs, - (char *) textPtr, argv[2], 0); - } else if ((c == 'c') && (strncmp(argv[1], "compare", length) == 0) - && (length >= 3)) { + break; + case TEXT_COMPARE: { int relation, value; - CONST char *p; + const char *p; + const TkTextIndex *index1Ptr, *index2Ptr; - if (argc != 5) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " compare index1 op index2\"", (char *) NULL); + if (objc != 5) { + Tcl_WrongNumArgs(interp, 2, objv, "index1 op index2"); result = TCL_ERROR; goto done; } - if ((TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) - || (TkTextGetIndex(interp, textPtr, argv[4], &index2) - != TCL_OK)) { + index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[4]); + if (index1Ptr == NULL || index2Ptr == NULL) { result = TCL_ERROR; goto done; } - relation = TkTextIndexCmp(&index1, &index2); - p = argv[3]; + relation = TkTextIndexCmp(index1Ptr, index2Ptr); + p = Tcl_GetString(objv[3]); if (p[0] == '<') { - value = (relation < 0); + value = (relation < 0); if ((p[1] == '=') && (p[2] == 0)) { value = (relation <= 0); } else if (p[1] != 0) { - compareError: + compareError: Tcl_AppendResult(interp, "bad comparison operator \"", - argv[3], "\": must be <, <=, ==, >=, >, or !=", - (char *) NULL); + Tcl_GetString(objv[3]), + "\": must be <, <=, ==, >=, >, or !=", NULL); result = TCL_ERROR; goto done; } } else if (p[0] == '>') { - value = (relation > 0); + value = (relation > 0); if ((p[1] == '=') && (p[2] == 0)) { value = (relation >= 0); } else if (p[1] != 0) { @@ -563,104 +800,347 @@ TextWidgetCmd(clientData, interp, argc, argv) } else { goto compareError; } - Tcl_SetResult(interp, ((value) ? "1" : "0"), TCL_STATIC); - } else if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0) - && (length >= 3)) { - if (argc == 2) { - result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) textPtr, (char *) NULL, 0); - } else if (argc == 3) { - result = Tk_ConfigureInfo(interp, textPtr->tkwin, configSpecs, - (char *) textPtr, argv[2], 0); + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value)); + break; + } + case TEXT_CONFIGURE: + if (objc <= 3) { + Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) textPtr, + textPtr->optionTable, ((objc == 3) ? objv[2] : NULL), + textPtr->tkwin); + if (objPtr == NULL) { + result = TCL_ERROR; + goto done; + } else { + Tcl_SetObjResult(interp, objPtr); + } } else { - result = ConfigureText(interp, textPtr, argc-2, argv+2, - TK_CONFIG_ARGV_ONLY); - } - } else if ((c == 'd') && (strncmp(argv[1], "debug", length) == 0) - && (length >= 3)) { - if (argc > 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " debug boolean\"", (char *) NULL); + result = ConfigureText(interp, textPtr, objc-2, objv+2); + } + 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; } - if (argc == 2) { - Tcl_SetResult(interp, ((tkBTreeDebug) ? "1" : "0"), TCL_STATIC); + + for (i = 2; i < objc-2; i++) { + int value, 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, (unsigned) length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_CHARS); + } else if (c == 'd' && (length > 8) + && !strncmp("-displaychars", option, (unsigned) length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_CHARS); + } else if (c == 'd' && (length > 8) + && !strncmp("-displayindices", option,(unsigned)length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_DISPLAY_INDICES); + } else if (c == 'd' && (length > 8) + && !strncmp("-displaylines", option, (unsigned) length)) { + 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->sharedTextPtr->tree, + textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree,textPtr)); + fromPtr = indexFromPtr->linePtr; + if (fromPtr == lastPtr) { + goto countDone; + } + + /* + * Caution: we must NEVER call TkTextUpdateOneLine with the + * last artificial line in the widget. + */ + + index = *indexFromPtr; + index.byteIndex = 0; + + /* + * We're going to count up all display lines in the logical + * line of 'indexFromPtr' up to, but not including the logical + * line of 'indexToPtr', and then subtract off what we didn't + * want from 'from' and add on what we didn't count from 'to. + */ + + while (index.linePtr != indexToPtr->linePtr) { + value += TkTextUpdateOneLine(textPtr, fromPtr,0,&index,0); + + /* + * We might have skipped past indexToPtr, if we have + * multiple logical lines in a single display line. + */ + if (TkTextIndexCmp(&index,indexToPtr) > 0) { + break; + } + } + + /* + * 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(textPtr, &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(textPtr, &index, 1, &index); + value++; + } + } + + if (compare > 0) { + value = -value; + } + } else if (c == 'i' + && !strncmp("-indices", option, (unsigned) length)) { + value = CountIndices(textPtr, indexFromPtr, indexToPtr, + COUNT_INDICES); + } else if (c == 'l' + && !strncmp("-lines", option, (unsigned) length)) { + value = TkBTreeLinesTo(textPtr, indexToPtr->linePtr) + - TkBTreeLinesTo(textPtr, indexFromPtr->linePtr); + } else if (c == 'u' + && !strncmp("-update", option, (unsigned) length)) { + update = 1; + continue; + } else if (c == 'x' + && !strncmp("-xpixels", option, (unsigned) 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, (unsigned) length)) { + if (update) { + TkTextUpdateLineMetrics(textPtr, + TkBTreeLinesTo(textPtr, indexFromPtr->linePtr), + TkBTreeLinesTo(textPtr, 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"); + result = TCL_ERROR; + goto done; + } + if (objc == 2) { + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(tkBTreeDebug)); } else { - if (Tcl_GetBoolean(interp, argv[2], &tkBTreeDebug) != TCL_OK) { + if (Tcl_GetBooleanFromObj(interp, objv[2], + &tkBTreeDebug) != TCL_OK) { result = TCL_ERROR; goto done; } tkTextDebug = tkBTreeDebug; } - } else if ((c == 'd') && (strncmp(argv[1], "delete", length) == 0) - && (length >= 3)) { - int i; - - if (argc < 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " delete index1 ?index2 ...?\"", (char *) NULL); + break; + case TEXT_DELETE: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?"); result = TCL_ERROR; goto done; } - if (textPtr->state == TK_STATE_NORMAL) { - if (argc < 5) { + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + if (objc < 5) { /* * Simple case requires no predetermination of indices. */ - result = DeleteChars(textPtr, argv[2], - (argc == 4) ? argv[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; + } + DeleteIndexRange(NULL, textPtr, indexPtr1, indexPtr2, 1); } else { /* * Multi-index pair case requires that we prevalidate the - * indices and sort from last to first so that deletes - * occur in the exact (unshifted) text. It also needs to - * handle partial and fully overlapping ranges. We have to - * do this with multiple passes. + * indices and sort from last to first so that deletes occur + * in the exact (unshifted) text. It also needs to handle + * partial and fully overlapping ranges. We have to do this + * with multiple passes. */ + TkTextIndex *indices, *ixStart, *ixEnd, *lastStart; char *useIdx; + int i; - argc -= 2; - argv += 2; + objc -= 2; + objv += 2; indices = (TkTextIndex *) - ckalloc((argc + 1) * sizeof(TkTextIndex)); + ckalloc((objc + 1) * sizeof(TkTextIndex)); /* * First pass verifies that all indices are valid. */ - for (i = 0; i < argc; i++) { - if (TkTextGetIndex(interp, textPtr, argv[i], - &indices[i]) != TCL_OK) { + + for (i = 0; i < objc; i++) { + const TkTextIndex *indexPtr = + TkTextGetIndexFromObj(interp, textPtr, objv[i]); + + if (indexPtr == NULL) { result = TCL_ERROR; ckfree((char *) indices); goto done; } + indices[i] = *indexPtr; } + /* * Pad out the pairs evenly to make later code easier. */ - if (argc & 1) { + + if (objc & 1) { indices[i] = indices[i-1]; - TkTextIndexForwChars(&indices[i], 1, &indices[i]); - argc++; + TkTextIndexForwChars(NULL, &indices[i], 1, &indices[i], + COUNT_INDICES); + objc++; } - useIdx = (char *) ckalloc((unsigned) argc); - memset(useIdx, 0, (unsigned) argc); + useIdx = (char *) ckalloc((unsigned) objc); + memset(useIdx, 0, (unsigned) objc); + /* - * Do a decreasing order sort so that we delete the end - * ranges first to maintain index consistency. + * Do a decreasing order sort so that we delete the end ranges + * first to maintain index consistency. */ - qsort((VOID *) indices, (unsigned) (argc / 2), + + qsort(indices, (unsigned) objc / 2, 2 * sizeof(TkTextIndex), TextIndexSortProc); lastStart = NULL; + /* * Second pass will handle bogus ranges (end < start) and * overlapping ranges. */ - for (i = 0; i < argc; i += 2) { + + for (i = 0; i < objc; i += 2) { ixStart = &indices[i]; - ixEnd = &indices[i+1]; + ixEnd = &indices[i+1]; if (TkTextIndexCmp(ixEnd, ixStart) <= 0) { continue; } @@ -670,13 +1150,15 @@ TextWidgetCmd(clientData, interp, argc, argv) * Start indices were equal, and the sort placed * the longest range first, so skip this one. */ + continue; } else if (TkTextIndexCmp(lastStart, ixEnd) < 0) { /* * The next pair has a start range before the end - * point of the last range. Constrain the delete + * point of the last range. Constrain the delete * range, but use the pointer values. */ + *ixEnd = *lastStart; if (TkTextIndexCmp(ixEnd, ixStart) <= 0) { continue; @@ -684,200 +1166,589 @@ TextWidgetCmd(clientData, interp, argc, argv) } } lastStart = ixStart; - useIdx[i] = 1; + useIdx[i] = 1; } + /* - * Final pass take the input from the previous and deletes - * the ranges which are flagged to be deleted. + * Final pass take the input from the previous and deletes the + * ranges which are flagged to be deleted. */ - for (i = 0; i < argc; i += 2) { + + for (i = 0; i < objc; i += 2) { if (useIdx[i]) { /* * We don't need to check the return value because all * indices are preparsed above. */ - DeleteChars(textPtr, NULL, NULL, - &indices[i], &indices[i+1]); + + DeleteIndexRange(NULL, textPtr, &indices[i], + &indices[i+1], 1); } } ckfree((char *) indices); } } - } else if ((c == 'd') && (strncmp(argv[1], "dlineinfo", length) == 0) - && (length >= 2)) { + break; + case TEXT_DLINEINFO: { int x, y, width, height, base; + const TkTextIndex *indexPtr; - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " dlineinfo index\"", (char *) NULL); + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); result = TCL_ERROR; goto done; } - if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) { + indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexPtr == NULL) { result = TCL_ERROR; goto done; } - if (TkTextDLineInfo(textPtr, &index1, &x, &y, &width, &height, &base) - == 0) { - char buf[TCL_INTEGER_SPACE * 5]; - - sprintf(buf, "%d %d %d %d %d", x, y, width, height, base); - Tcl_SetResult(interp, buf, TCL_VOLATILE); + if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height, + &base) == 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)); + Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(base)); + + Tcl_SetObjResult(interp, listObj); } - } else if ((c == 'e') && (strncmp(argv[1], "edit", length) == 0)) { - result = TextEditCmd(textPtr, interp, argc, argv); - } else if ((c == 'g') && (strncmp(argv[1], "get", length) == 0)) { + break; + } + case TEXT_DUMP: + result = TextDumpCmd(textPtr, interp, objc, objv); + break; + case TEXT_EDIT: + result = TextEditCmd(textPtr, interp, objc, objv); + break; + case TEXT_GET: { Tcl_Obj *objPtr = NULL; - Tcl_DString ds; - int i, found = 0; + int i, found = 0, visible = 0; + const char *name; + int length; - if (argc < 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " get index1 ?index2 ...?\"", (char *) NULL); + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, + "?-displaychars? ?--? index1 ?index2 ...?"); result = TCL_ERROR; goto done; } - for (i = 2; i < argc; i += 2) { - if (TkTextGetIndex(interp, textPtr, argv[i], &index1) != TCL_OK) { - result = TCL_ERROR; - goto done; + + /* + * 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, (unsigned)length)==0) { + i++; + visible = 1; + name = Tcl_GetStringFromObj(objv[i], &length); + } + if ((i < objc-1) && (length == 2) && !strcmp("--", name)) { + i++; + } } - if (i+1 == argc) { - index2 = index1; - TkTextIndexForwChars(&index2, 1, &index2); - } else if (TkTextGetIndex(interp, textPtr, argv[i+1], &index2) - != TCL_OK) { + } + + for (; i < objc; i += 2) { + const TkTextIndex *index1Ptr, *index2Ptr; + TkTextIndex index2; + + index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i]); + if (index1Ptr == NULL) { if (objPtr) { Tcl_DecrRefCount(objPtr); } result = TCL_ERROR; goto done; } - if (TkTextIndexCmp(&index1, &index2) < 0) { - /* - * Place the text in a DString and move it to the result. - * Since this could in principle be a megabyte or more, we - * want to do it efficiently! + + if (i+1 == objc) { + TkTextIndexForwChars(NULL, index1Ptr, 1, &index2, + COUNT_INDICES); + index2Ptr = &index2; + } else { + index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i+1]); + if (index2Ptr == NULL) { + if (objPtr) { + Tcl_DecrRefCount(objPtr); + } + result = TCL_ERROR; + goto done; + } + } + + if (TkTextIndexCmp(index1Ptr, index2Ptr) < 0) { + /* + * We want to move the text we get from the window into the + * result, but since this could in principle be a megabyte or + * more, we want to do it efficiently! */ - TextGetText(&index1, &index2, &ds); + + Tcl_Obj *get = TextGetText(textPtr, index1Ptr, index2Ptr, + visible); + found++; if (found == 1) { - Tcl_DStringResult(interp, &ds); + Tcl_SetObjResult(interp, get); } else { if (found == 2) { /* - * Move the first item we put into the result into - * the first element of the list object. + * 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_NewStringObj(Tcl_DStringValue(&ds), - Tcl_DStringLength(&ds))); + Tcl_ListObjAppendElement(NULL, objPtr, get); } - Tcl_DStringFree(&ds); } } if (found > 1) { Tcl_SetObjResult(interp, objPtr); } - } else if ((c == 'i') && (strncmp(argv[1], "index", length) == 0) - && (length >= 3)) { - char buf[200]; + break; + } + case TEXT_IMAGE: + result = TkTextImageCmd(textPtr, interp, objc, objv); + break; + case TEXT_INDEX: { + const TkTextIndex *indexPtr; - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " index index\"", - (char *) NULL); + if (objc != 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index"); result = TCL_ERROR; goto done; } - if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) { + + indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexPtr == NULL) { result = TCL_ERROR; goto done; } - TkTextPrintIndex(&index1, buf); - Tcl_SetResult(interp, buf, TCL_VOLATILE); - } else if ((c == 'i') && (strncmp(argv[1], "insert", length) == 0) - && (length >= 3)) { - int i, j, numTags; - CONST char **tagNames; - TkTextTag **oldTagArrayPtr; - - if (argc < 4) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], - " insert index chars ?tagList chars tagList ...?\"", - (char *) NULL); + Tcl_SetObjResult(interp, TkTextNewIndexObj(textPtr, indexPtr)); + break; + } + case TEXT_INSERT: { + const TkTextIndex *indexPtr; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "index chars ?tagList chars tagList ...?"); result = TCL_ERROR; goto done; } - if (TkTextGetIndex(interp, textPtr, argv[2], &index1) != TCL_OK) { + indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]); + if (indexPtr == NULL) { result = TCL_ERROR; goto done; } - if (textPtr->state == TK_STATE_NORMAL) { - for (j = 3; j < argc; j += 2) { - InsertChars(textPtr, &index1, argv[j]); - if (argc > (j+1)) { - TkTextIndexForwBytes(&index1, (int) strlen(argv[j]), - &index2); - oldTagArrayPtr = TkBTreeGetTags(&index1, &numTags); - if (oldTagArrayPtr != NULL) { - for (i = 0; i < numTags; i++) { - TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0); - } - ckfree((char *) oldTagArrayPtr); - } - if (Tcl_SplitList(interp, argv[j+1], &numTags, &tagNames) - != TCL_OK) { - result = TCL_ERROR; - goto done; - } - for (i = 0; i < numTags; i++) { - TkBTreeTag(&index1, &index2, - TkTextCreateTag(textPtr, tagNames[i]), 1); - } - ckfree((char *) tagNames); - index1 = index2; + if (textPtr->state == TK_TEXT_STATE_NORMAL) { + result = TextInsertCmd(NULL, textPtr, interp, objc-3, objv+3, + indexPtr, 1); + } + break; + } + case TEXT_MARK: + result = TkTextMarkCmd(textPtr, interp, objc, objv); + break; + case TEXT_PEER: + result = TextPeerCmd(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 = TkBTreeLinesTo(textPtr, 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, 0); + + if (result == TCL_OK) { + /* + * Move the insertion position to the correct place. + */ + + TkTextIndexForwChars(NULL, indexFromPtr, + deleteInsertOffset, &index, COUNT_INDICES); + TkBTreeUnlinkSegment(textPtr->insertMarkPtr, + textPtr->insertMarkPtr->body.mark.linePtr); + TkBTreeLinkSegment(textPtr->insertMarkPtr, &index); + } + } else { + result = TextReplaceCmd(textPtr, interp, indexFromPtr, + indexToPtr, objc, objv, 1); + } + if (result == TCL_OK) { + /* + * Now ensure the top-line is in the right place. + */ + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineNum, byteIndex, &index); + TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST); } } - } else if ((c == 'd') && (strncmp(argv[1], "dump", length) == 0)) { - result = TextDumpCmd(textPtr, interp, argc, argv); - } else if ((c == 'i') && (strncmp(argv[1], "image", length) == 0)) { - result = TkTextImageCmd(textPtr, interp, argc, argv); - } else if ((c == 'm') && (strncmp(argv[1], "mark", length) == 0)) { - result = TkTextMarkCmd(textPtr, interp, argc, argv); - } else if ((c == 's') && (strcmp(argv[1], "scan") == 0) && (length >= 2)) { - result = TkTextScanCmd(textPtr, interp, argc, argv); - } else if ((c == 's') && (strcmp(argv[1], "search") == 0) - && (length >= 3)) { - result = TextSearchCmd(textPtr, interp, argc, argv); - } else if ((c == 's') && (strcmp(argv[1], "see") == 0) && (length >= 3)) { - result = TkTextSeeCmd(textPtr, interp, argc, argv); - } else if ((c == 't') && (strcmp(argv[1], "tag") == 0)) { - result = TkTextTagCmd(textPtr, interp, argc, argv); - } else if ((c == 'w') && (strncmp(argv[1], "window", length) == 0)) { - result = TkTextWindowCmd(textPtr, interp, argc, argv); - } else if ((c == 'x') && (strncmp(argv[1], "xview", length) == 0)) { - result = TkTextXviewCmd(textPtr, interp, argc, argv); - } else if ((c == 'y') && (strncmp(argv[1], "yview", length) == 0) - && (length >= 2)) { - result = TkTextYviewCmd(textPtr, interp, argc, argv); - } else { - Tcl_AppendResult(interp, "bad option \"", argv[1], - "\": must be bbox, cget, compare, configure, debug, delete, ", - "dlineinfo, dump, edit, get, image, index, insert, mark, ", - "scan, search, see, tag, window, xview, or yview", - (char *) NULL); - result = TCL_ERROR; + break; + } + case TEXT_SCAN: + result = TkTextScanCmd(textPtr, interp, objc, objv); + break; + case TEXT_SEARCH: + result = TextSearchCmd(textPtr, interp, objc, objv); + break; + case TEXT_SEE: + result = TkTextSeeCmd(textPtr, interp, objc, objv); + break; + case TEXT_TAG: + result = TkTextTagCmd(textPtr, interp, objc, objv); + break; + case TEXT_WINDOW: + result = TkTextWindowCmd(textPtr, interp, objc, objv); + break; + case TEXT_XVIEW: + result = TkTextXviewCmd(textPtr, interp, objc, objv); + break; + case TEXT_YVIEW: + result = TkTextYviewCmd(textPtr, interp, objc, objv); + break; + } + + done: + textPtr->refCount--; + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } + return result; +} + +/* + *-------------------------------------------------------------- + * + * SharedTextObjCmd -- + * + * This function is invoked to process commands on the shared portion of + * a text widget. Currently it is not actually exported as a Tcl command, + * and is only used internally to process parts of undo/redo scripts. + * See the user documentation for 'text' for details on what it does - + * the only subcommands it currently supports are 'insert' and 'delete'. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation for "text". + * + *-------------------------------------------------------------- + */ + +static int +SharedTextObjCmd( + ClientData clientData, /* Information about shared test B-tree. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + register TkSharedText *sharedPtr = (TkSharedText *) clientData; + int result = TCL_OK; + int index; + + static const char *optionStrings[] = { + "delete", "insert", NULL + }; + enum options { + TEXT_DELETE, TEXT_INSERT + }; + + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum options) index) { + case TEXT_DELETE: + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?"); + return TCL_ERROR; + } + if (objc < 5) { + /* + * Simple case requires no predetermination of indices. + */ + + TkTextIndex index1; + + /* + * Parse the starting and stopping indices. + */ + + result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2], + &index1); + if (result != TCL_OK) { + return result; + } + if (objc == 4) { + TkTextIndex index2; + + result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[3], + &index2); + if (result != TCL_OK) { + return result; + } + DeleteIndexRange(sharedPtr, NULL, &index1, &index2, 1); + } else { + DeleteIndexRange(sharedPtr, NULL, &index1, NULL, 1); + } + return TCL_OK; + } else { + /* Too many arguments */ + return TCL_ERROR; + } + break; + case TEXT_INSERT: { + TkTextIndex index1; + + if (objc < 4) { + Tcl_WrongNumArgs(interp, 2, objv, + "index chars ?tagList chars tagList ...?"); + return TCL_ERROR; + } + result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2], + &index1); + if (result != TCL_OK) { + return result; + } + return TextInsertCmd(sharedPtr, NULL, interp, objc-3, objv+3, &index1, + 1); + } + default: + return TCL_OK; + } +} + +/* + *-------------------------------------------------------------- + * + * TextPeerCmd -- + * + * This function is invoked to process the "text peer" Tcl command. See + * the user documentation for details on what it does. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + *-------------------------------------------------------------- + */ + +static int +TextPeerCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tk_Window tkwin = textPtr->tkwin; + int index; + + static const char *peerOptionStrings[] = { + "create", "names", NULL + }; + enum peerOptions { + PEER_CREATE, PEER_NAMES + }; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); + return TCL_ERROR; + } + + if (Tcl_GetIndexFromObj(interp, objv[2], peerOptionStrings, + "peer option", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum peerOptions)index) { + case PEER_CREATE: + if (objc < 4) { + Tcl_WrongNumArgs(interp, 3, objv, "pathName ?options?"); + return TCL_ERROR; + } + return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr, + objc-2, objv+2); + case PEER_NAMES: { + TkText *tPtr = textPtr->sharedTextPtr->peers; + + if (objc > 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); + return TCL_ERROR; + } + while (tPtr != NULL) { + if (tPtr != textPtr) { + Tcl_AppendElement(interp, Tk_PathName(tPtr->tkwin)); + } + tPtr = tPtr->next; + } + } + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TextReplaceCmd -- + * + * This function is invoked to process part of the "replace" widget + * command for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + * If 'viewUpdate' is false, then textPtr->topIndex may no longer be a + * valid index after this function returns. The caller is responsible for + * ensuring a correct index is in place. + * + *---------------------------------------------------------------------- + */ + +static int +TextReplaceCmd( + 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 viewUpdate) /* Update vertical view if set. */ +{ + /* + * Perform the deletion and insertion, but ensure no undo-separator is + * placed between the two operations. Since we are using the helper + * functions 'DeleteIndexRange' 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->sharedTextPtr->autoSeparators; + int result, lineNumber; + TkTextIndex indexTmp; + + if (textPtr->sharedTextPtr->undo) { + textPtr->sharedTextPtr->autoSeparators = 0; + if (origAutoSep && + textPtr->sharedTextPtr->lastEditMode!=TK_TEXT_EDIT_REPLACE) { + TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack); + } + } + + /* + * Must save and restore line in indexFromPtr based on line number; can't + * keep the line itself as that might be eliminated/invalidated when + * deleting the range. [Bug 1602537] + */ + + indexTmp = *indexFromPtr; + lineNumber = TkBTreeLinesTo(textPtr, indexFromPtr->linePtr); + DeleteIndexRange(NULL, textPtr, indexFromPtr, indexToPtr, viewUpdate); + indexTmp.linePtr = TkBTreeFindLine(indexTmp.tree, textPtr, lineNumber); + result = TextInsertCmd(NULL, textPtr, interp, objc-4, objv+4, + &indexTmp, viewUpdate); + + if (textPtr->sharedTextPtr->undo) { + textPtr->sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE; + textPtr->sharedTextPtr->autoSeparators = origAutoSep; } - done: - Tcl_Release((ClientData) textPtr); return result; } @@ -886,13 +1757,13 @@ TextWidgetCmd(clientData, interp, argc, argv) * * TextIndexSortProc -- * - * This procedure is called by qsort when sorting an array of - * indices in *decreasing* order (last to first). + * This function is called by qsort when sorting an array of indices in + * *decreasing* order (last to first). * * Results: - * The return value is -1 if the first argument should be before - * the second element, 0 if it's equivalent, and 1 if it should be - * after the second element. + * The return value is -1 if the first argument should be before the + * second element, 0 if it's equivalent, and 1 if it should be after the + * second element. * * Side effects: * None. @@ -901,8 +1772,9 @@ TextWidgetCmd(clientData, interp, argc, argv) */ static int -TextIndexSortProc(first, second) - CONST VOID *first, *second; /* Elements to be compared. */ +TextIndexSortProc( + const void *first, /* Elements to be compared. */ + const void *second) { TkTextIndex *pair1 = (TkTextIndex *) first; TkTextIndex *pair2 = (TkTextIndex *) second; @@ -911,9 +1783,10 @@ TextIndexSortProc(first, second) if (cmp == 0) { /* * If the first indices were equal, we want the second index of the - * pair also to be the greater. Use pointer magic to access the - * second index pair. + * pair also to be the greater. Use pointer magic to access the second + * index pair. */ + cmp = TkTextIndexCmp(&pair1[0], &pair2[0]); } if (cmp > 0) { @@ -929,71 +1802,168 @@ 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 function 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( + TkText *textPtr) /* Info about text widget. */ { - register TkText *textPtr = (TkText *) memPtr; Tcl_HashSearch search; Tcl_HashEntry *hPtr; TkTextTag *tagPtr; + TkSharedText *sharedTextPtr = textPtr->sharedTextPtr; /* - * Free up all the stuff that requires special handling, then - * let Tk_FreeOptions handle all the standard option-related - * stuff. Special note: free up display-related information - * before deleting the B-tree, since display-related stuff - * may refer to stuff in the B-tree. + * Free up all the stuff that requires special handling. We have already + * called let Tk_FreeConfigOptions to handle all the standard + * option-related stuff (and so none of that exists when we are called). + * Special note: free up display-related information before deleting the + * B-tree, since display-related stuff may refer to stuff in the B-tree. */ TkTextFreeDInfo(textPtr); - TkBTreeDestroy(textPtr->tree); - for (hPtr = Tcl_FirstHashEntry(&textPtr->tagTable, &search); - hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); - TkTextFreeTag(textPtr, tagPtr); - } - Tcl_DeleteHashTable(&textPtr->tagTable); - for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search); - hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { - ckfree((char *) Tcl_GetHashValue(hPtr)); - } - Tcl_DeleteHashTable(&textPtr->markTable); + textPtr->dInfoPtr = NULL; + + /* + * Remove ourselves from the peer list. + */ + + if (sharedTextPtr->peers == textPtr) { + sharedTextPtr->peers = textPtr->next; + } else { + TkText *nextPtr = sharedTextPtr->peers; + while (nextPtr != NULL) { + if (nextPtr->next == textPtr) { + nextPtr->next = textPtr->next; + break; + } + nextPtr = nextPtr->next; + } + } + + /* + * Always clean up the widget-specific tags first. Common tags (i.e. most) + * will only be cleaned up when the shared structure is cleaned up. + * + * We also need to clean up widget-specific marks ('insert', 'current'), + * since otherwise marks will never disappear from the B-tree. + */ + + TkTextDeleteTag(textPtr, textPtr->selTagPtr); + TkBTreeUnlinkSegment(textPtr->insertMarkPtr, + textPtr->insertMarkPtr->body.mark.linePtr); + ckfree((char *) textPtr->insertMarkPtr); + TkBTreeUnlinkSegment(textPtr->currentMarkPtr, + textPtr->currentMarkPtr->body.mark.linePtr); + ckfree((char *) textPtr->currentMarkPtr); + + /* + * Now we've cleaned up everything of relevance to us in the B-tree, so we + * disassociate outselves from it. + * + * When the refCount reaches zero, it's time to clean up the shared + * portion of the text widget. + */ + + sharedTextPtr->refCount--; + + if (sharedTextPtr->refCount > 0) { + TkBTreeRemoveClient(sharedTextPtr->tree, textPtr); + + /* + * Free up any embedded windows which belong to this widget. + */ + + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + TkTextEmbWindowClient *loop; + TkTextSegment *ewPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr); + + loop = ewPtr->body.ew.clients; + if (loop->textPtr == textPtr) { + ewPtr->body.ew.clients = loop->next; + TkTextWinFreeClient(hPtr, loop); + } else { + TkTextEmbWindowClient *client = ewPtr->body.ew.clients; + + client = loop->next; + while (client != NULL) { + if (client->textPtr == textPtr) { + loop->next = client->next; + TkTextWinFreeClient(hPtr, client); + break; + } else { + loop = loop->next; + } + client = loop->next; + } + } + } + } else { + /* + * No need to call 'TkBTreeRemoveClient' first, since this will do + * everything in one go, more quickly. + */ + + TkBTreeDestroy(sharedTextPtr->tree); + + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); + + /* + * No need to use 'TkTextDeleteTag' since we've already removed + * the B-tree completely. + */ + + TkTextFreeTag(textPtr, tagPtr); + } + Tcl_DeleteHashTable(&sharedTextPtr->tagTable); + for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search); + hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + ckfree((char *) Tcl_GetHashValue(hPtr)); + } + Tcl_DeleteHashTable(&sharedTextPtr->markTable); + TkUndoFreeStack(sharedTextPtr->undoStack); + + Tcl_DeleteHashTable(&sharedTextPtr->windowTable); + Tcl_DeleteHashTable(&sharedTextPtr->imageTable); + + if (sharedTextPtr->bindingTable != NULL) { + Tk_DeleteBindingTable(sharedTextPtr->bindingTable); + } + ckfree((char *) sharedTextPtr); + } + if (textPtr->tabArrayPtr != NULL) { ckfree((char *) textPtr->tabArrayPtr); } if (textPtr->insertBlinkHandler != NULL) { Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler); } - if (textPtr->bindingTable != NULL) { - Tk_DeleteBindingTable(textPtr->bindingTable); - } - TkUndoFreeStack(textPtr->undoStack); - /* - * NOTE: do NOT free up selBorder, selBdString, or selFgColorPtr: - * they are duplicates of information in the "sel" tag, which was - * freed up as part of deleting the tags above. - */ - - textPtr->selBorder = NULL; - textPtr->selBdString = NULL; - textPtr->selFgColorPtr = NULL; - Tk_FreeOptions(configSpecs, (char *) textPtr, textPtr->display, 0); - ckfree((char *) textPtr); + textPtr->tkwin = NULL; + textPtr->refCount--; + Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd); + if (textPtr->refCount == 0) { + ckfree((char *) textPtr); + } } /* @@ -1001,47 +1971,165 @@ DestroyText(memPtr) * * ConfigureText -- * - * This procedure is called to process an argv/argc list, plus - * the Tk option database, in order to configure (or - * reconfigure) a text widget. + * This function is called to process an objv/objc list, plus the Tk + * option database, in order to configure (or reconfigure) a text widget. * * Results: - * The return value is a standard Tcl result. If TCL_ERROR is - * returned, then the interp's result contains an error message. + * The return value is a standard Tcl result. If TCL_ERROR is returned, + * then the interp's result contains an error message. * * Side effects: - * Configuration information, such as text string, colors, font, - * etc. get set for textPtr; old resources get freed, if there - * were any. + * Configuration information, such as text string, colors, font, etc. get + * set for textPtr; old resources get freed, if there were any. * *---------------------------------------------------------------------- */ static int -ConfigureText(interp, textPtr, argc, argv, flags) - Tcl_Interp *interp; /* Used for error reporting. */ - register TkText *textPtr; /* Information about widget; may or may - * not already have values for some fields. */ - int argc; /* Number of valid entries in argv. */ - CONST char **argv; /* Arguments. */ - int flags; /* Flags to pass to Tk_ConfigureWidget. */ +ConfigureText( + Tcl_Interp *interp, /* Used for error reporting. */ + register TkText *textPtr, /* Information about widget; may or may not + * already have values for some fields. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ { + Tk_SavedOptions savedOptions; int oldExport = textPtr->exportSelection; + int mask = 0; - if (Tk_ConfigureWidget(interp, textPtr->tkwin, configSpecs, - argc, argv, (char *) textPtr, flags) != TCL_OK) { + if (Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable, + objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) { return TCL_ERROR; } - TkUndoSetDepth(textPtr->undoStack, textPtr->maxUndo); + /* + * Copy down shared flags. + */ + + textPtr->sharedTextPtr->undo = textPtr->undo; + textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo; + textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators; + + TkUndoSetDepth(textPtr->sharedTextPtr->undoStack, + textPtr->sharedTextPtr->maxUndo); /* - * A few other options also need special processing, such as parsing - * the geometry and setting the background from a 3-D border. + * A few other options also need special processing, such as parsing the + * geometry and setting the background from a 3-D border. */ Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border); + if (mask & TK_TEXT_LINE_RANGE) { + int start, end, current; + TkTextIndex index1, index2, index3; + + /* + * Line start and/or end have been adjusted. We need to validate the + * first displayed line and arrange for re-layout. + */ + + TkBTreeClientRangeChanged(textPtr, textPtr->charHeight); + + if (textPtr->start != NULL) { + start = TkBTreeLinesTo(NULL, textPtr->start); + } else { + start = 0; + } + if (textPtr->end != NULL) { + end = TkBTreeLinesTo(NULL, textPtr->end); + } else { + end = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL); + } + if (start > end) { + Tcl_AppendResult(interp, + "-startline must be less than or equal to -endline", + NULL); + Tk_RestoreSavedOptions(&savedOptions); + return TCL_ERROR; + } + current = TkBTreeLinesTo(NULL, textPtr->topIndex.linePtr); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0, + &index1); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0, + &index2); + if (current < start || current > end) { + TkTextSearch search; + TkTextIndex first, last; + int selChanged = 0; + + TkTextSetYView(textPtr, &index1, 0); + + /* + * We may need to adjust the selection. So we have to check + * whether the "sel" tag was applied to anything outside the + * current start,end. + */ + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, 0, 0, + &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL), + 0, &last); + TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search); + if (!TkBTreeCharTagged(&first, textPtr->selTagPtr) + && !TkBTreeNextTag(&search)) { + /* Nothing tagged with "sel" */ + } else { + int line = TkBTreeLinesTo(NULL, search.curIndex.linePtr); + if (line < start) { + selChanged = 1; + } else { + TkTextLine *linePtr = search.curIndex.linePtr; + + while (TkBTreeNextTag(&search)) { + linePtr = search.curIndex.linePtr; + } + line = TkBTreeLinesTo(NULL, linePtr); + if (line >= end) { + selChanged = 1; + } + } + } + if (selChanged) { + /* + * Send an event that the selection has changed, and abort any + * partial-selections in progress. + */ + + TkTextSelectionEvent(textPtr); + textPtr->abortSelections = 1; + } + } + + /* Indices are potentially obsolete after changing -startline and/or + * -endline, therefore increase the epoch. + * Also, clamp the insert and current (unshared) marks to the new + * -startline/-endline range limits of the widget. All other (shared) + * marks are unchanged. + * The return value of TkTextMarkNameToIndex does not need to be + * checked: "insert" and "current" marks always exist, and the + * purpose of the code below precisely is to move them inside the + * -startline/-endline range. + */ + + textPtr->sharedTextPtr->stateEpoch++; + TkTextMarkNameToIndex(textPtr, "insert", &index3); + if (TkTextIndexCmp(&index3, &index1) < 0) { + textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index1); + } + if (TkTextIndexCmp(&index3, &index2) > 0) { + textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index2); + } + TkTextMarkNameToIndex(textPtr, "current", &index3); + if (TkTextIndexCmp(&index3, &index1) < 0) { + textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index1); + } + if (TkTextIndexCmp(&index3, &index2) > 0) { + textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index2); + } + } + /* * Don't allow negative spacings. */ @@ -1064,62 +2152,56 @@ ConfigureText(interp, textPtr, argc, argv, flags) ckfree((char *) textPtr->tabArrayPtr); textPtr->tabArrayPtr = NULL; } - if (textPtr->tabOptionString != NULL) { - textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr->tkwin, - textPtr->tabOptionString); + if (textPtr->tabOptionPtr != NULL) { + textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr, + textPtr->tabOptionPtr); if (textPtr->tabArrayPtr == NULL) { Tcl_AddErrorInfo(interp,"\n (while processing -tabs option)"); + Tk_RestoreSavedOptions(&savedOptions); return TCL_ERROR; } } /* - * Make sure that configuration options are properly mirrored - * between the widget record and the "sel" tags. NOTE: we don't - * have to free up information during the mirroring; old - * information was freed when it was replaced in the widget - * record. + * Make sure that configuration options are properly mirrored between the + * widget record and the "sel" tags. NOTE: we don't have to free up + * information during the mirroring; old information was freed when it was + * replaced in the widget record. */ textPtr->selTagPtr->border = textPtr->selBorder; - if (textPtr->selTagPtr->bdString != textPtr->selBdString) { - textPtr->selTagPtr->bdString = textPtr->selBdString; - if (textPtr->selBdString != NULL) { - if (Tk_GetPixels(interp, textPtr->tkwin, textPtr->selBdString, - &textPtr->selTagPtr->borderWidth) != TCL_OK) { - return TCL_ERROR; - } - if (textPtr->selTagPtr->borderWidth < 0) { - textPtr->selTagPtr->borderWidth = 0; - } - } + if (textPtr->selTagPtr->borderWidthPtr != textPtr->selBorderWidthPtr) { + textPtr->selTagPtr->borderWidthPtr = textPtr->selBorderWidthPtr; + textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth; } textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr; textPtr->selTagPtr->affectsDisplay = 0; - if ((textPtr->selTagPtr->border != NULL) - || (textPtr->selTagPtr->bdString != NULL) - || (textPtr->selTagPtr->reliefString != NULL) - || (textPtr->selTagPtr->bgStipple != None) - || (textPtr->selTagPtr->fgColor != NULL) + 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->tabString != NULL) - || (textPtr->selTagPtr->underlineString != NULL) - || (textPtr->selTagPtr->elideString != NULL) + || (textPtr->selTagPtr->tabStringPtr != NULL) || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) { textPtr->selTagPtr->affectsDisplay = 1; + textPtr->selTagPtr->affectsDisplayGeometry = 1; } - TkTextRedrawTag(textPtr, (TkTextIndex *) NULL, (TkTextIndex *) NULL, - textPtr->selTagPtr, 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(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, 1); /* * Claim the selection if we've suddenly started exporting it and there @@ -1130,9 +2212,11 @@ ConfigureText(interp, textPtr, argc, argv, flags) TkTextSearch search; TkTextIndex first, last; - TkTextMakeByteIndex(textPtr->tree, 0, 0, &first); - TkTextMakeByteIndex(textPtr->tree, - TkBTreeNumLines(textPtr->tree), 0, &last); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, + &first); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &last); TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search); if (TkBTreeCharTagged(&first, textPtr->selTagPtr) || TkBTreeNextTag(&search)) { @@ -1153,8 +2237,8 @@ ConfigureText(interp, textPtr, argc, argv, flags) } /* - * Register the desired geometry for the window, and arrange for - * the window to be redisplayed. + * Register the desired geometry for the window, and arrange for the + * window to be redisplayed. */ if (textPtr->width <= 0) { @@ -1163,60 +2247,97 @@ ConfigureText(interp, textPtr, argc, argv, flags) if (textPtr->height <= 0) { textPtr->height = 1; } - TextWorldChanged((ClientData) textPtr); + Tk_FreeSavedOptions(&savedOptions); + 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 - * and determine its new geometry. + * This function 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. + * None. * * Side effects: - * Configures all tags in the Text with a empty argc/argv, for - * the side effect of causing all the items to recompute their - * geometry and to be redisplayed. + * 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(instanceData) - ClientData instanceData; /* Information about widget. */ +TextWorldChangedCallback( + ClientData instanceData) /* Information about widget. */ { TkText *textPtr; - Tk_FontMetrics fm; textPtr = (TkText *) instanceData; + TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY); +} + +/* + *--------------------------------------------------------------------------- + * + * TextWorldChanged -- + * + * This function 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( + TkText *textPtr, /* Information about widget. */ + int mask) /* OR'd collection of bits showing what has + * changed. */ +{ + Tk_FontMetrics fm; + int border; 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; + } + border = textPtr->borderWidth + textPtr->highlightWidth; Tk_GeometryRequest(textPtr->tkwin, - textPtr->width * textPtr->charWidth + 2*textPtr->borderWidth - + 2*textPtr->padX + 2*textPtr->highlightWidth, - textPtr->height * (fm.linespace + textPtr->spacing1 - + textPtr->spacing3) + 2*textPtr->borderWidth - + 2*textPtr->padY + 2*textPtr->highlightWidth); - Tk_SetInternalBorder(textPtr->tkwin, - textPtr->borderWidth + textPtr->highlightWidth); + textPtr->width * textPtr->charWidth + 2*textPtr->padX + 2*border, + textPtr->height*(fm.linespace+textPtr->spacing1+textPtr->spacing3) + + 2*textPtr->padY + 2*border); + + Tk_SetInternalBorderEx(textPtr->tkwin, + border + textPtr->padX, border + textPtr->padX, + border + textPtr->padY, border + textPtr->padY); 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); } /* @@ -1224,24 +2345,24 @@ TextWorldChanged(instanceData) * * TextEventProc -- * - * This procedure is invoked by the Tk dispatcher on - * structure changes to a text. For texts with 3D - * borders, this procedure is also invoked for exposures. + * This function is invoked by the Tk dispatcher on structure changes to + * a text. For texts with 3D borders, this function is also invoked for + * exposures. * * Results: * None. * * Side effects: - * When the window gets deleted, internal structures get - * cleaned up. When it gets exposed, it is redisplayed. + * When the window gets deleted, internal structures get cleaned up. + * When it gets exposed, it is redisplayed. * *-------------------------------------------------------------- */ static void -TextEventProc(clientData, eventPtr) - ClientData clientData; /* Information about window. */ - register XEvent *eventPtr; /* Information about event. */ +TextEventProc( + ClientData clientData, /* Information about window. */ + register XEvent *eventPtr) /* Information about event. */ { register TkText *textPtr = (TkText *) clientData; TkTextIndex index, index2; @@ -1253,22 +2374,51 @@ 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 = TK_TEXT_LINE_GEOMETRY; + } + TkTextRelayoutWindow(textPtr, mask); textPtr->prevWidth = Tk_Width(textPtr->tkwin); textPtr->prevHeight = Tk_Height(textPtr->tkwin); } } else if (eventPtr->type == DestroyNotify) { - if (textPtr->tkwin != NULL) { - if (textPtr->setGrid) { - Tk_UnsetGrid(textPtr->tkwin); - } - textPtr->tkwin = NULL; - Tcl_DeleteCommandFromToken(textPtr->interp, - textPtr->widgetCmd); + /* + * NOTE: we must zero out selBorder, selBorderWidthPtr and + * selFgColorPtr: they are duplicates of information in the "sel" tag, + * which will be freed up when we delete all tags. Hence we don't want + * the automatic config options freeing process to delete them as + * well. + */ + + textPtr->selBorder = NULL; + textPtr->selBorderWidthPtr = NULL; + textPtr->selBorderWidth = 0; + textPtr->selFgColorPtr = NULL; + if (textPtr->setGrid) { + Tk_UnsetGrid(textPtr->tkwin); + textPtr->setGrid = 0; } - Tcl_EventuallyFree((ClientData) textPtr, DestroyText); + if (!(textPtr->flags & OPTIONS_FREED)) { + Tk_FreeConfigOptions((char *) textPtr, textPtr->optionTable, + textPtr->tkwin); + textPtr->flags |= OPTIONS_FREED; + } + textPtr->flags |= DESTROYED; + + /* + * 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. + */ + + DestroyText(textPtr); } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) { - if (eventPtr->xfocus.detail != NotifyInferior) { + if (eventPtr->xfocus.detail == NotifyInferior + || eventPtr->xfocus.detail == NotifyAncestor + || eventPtr->xfocus.detail == NotifyNonlinear) { Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler); if (eventPtr->type == FocusIn) { textPtr->flags |= GOT_FOCUS | INSERT_ON; @@ -1281,19 +2431,19 @@ TextEventProc(clientData, eventPtr) textPtr->flags &= ~(GOT_FOCUS | INSERT_ON); textPtr->insertBlinkHandler = (Tcl_TimerToken) NULL; } - if ( -#ifndef MAC_OSX_TK - !TkpAlwaysShowSelection(textPtr->tkwin) -#else - /* Don't show inactive selection in disabled widgets. */ - textPtr->state != TK_STATE_DISABLED -#endif - ) { - TkTextRedrawTag(textPtr, NULL, NULL, textPtr->selTagPtr, 1); + if (textPtr->inactiveSelBorder != textPtr->selBorder) { + TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, + 1); } TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index); - TkTextIndexForwChars(&index, 1, &index2); - TkTextChanged(textPtr, &index, &index2); + TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES); + + /* + * While we wish to redisplay, no heights have changed, so no need + * to call TkTextInvalidateLineMetrics. + */ + + TkTextChanged(NULL, textPtr, &index, &index2); if (textPtr->highlightWidth > 0) { TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, textPtr->highlightWidth); @@ -1307,9 +2457,9 @@ TextEventProc(clientData, eventPtr) * * TextCmdDeletedProc -- * - * This procedure is invoked when a widget command is deleted. If - * the widget isn't already in the process of being destroyed, - * this command destroys it. + * This function is invoked when a widget command is deleted. If the + * widget isn't already in the process of being destroyed, this command + * destroys it. * * Results: * None. @@ -1321,24 +2471,25 @@ TextEventProc(clientData, eventPtr) */ static void -TextCmdDeletedProc(clientData) - ClientData clientData; /* Pointer to widget record for widget. */ +TextCmdDeletedProc( + ClientData clientData) /* Pointer to widget record for widget. */ { TkText *textPtr = (TkText *) clientData; Tk_Window tkwin = textPtr->tkwin; /* - * This procedure could be invoked either because the window was - * destroyed and the command was then deleted (in which case tkwin - * is NULL) or because the command was deleted, and then this procedure - * destroys the widget. + * This function could be invoked either because the window was destroyed + * and the command was then deleted (in which this flag is already set) or + * because the command was deleted, and then this function destroys the + * widget. */ - if (tkwin != NULL) { + if (!(textPtr->flags & DESTROYED)) { if (textPtr->setGrid) { Tk_UnsetGrid(textPtr->tkwin); + textPtr->setGrid = 0; } - textPtr->tkwin = NULL; + textPtr->flags |= DESTROYED; Tk_DestroyWindow(tkwin); } } @@ -1348,205 +2499,483 @@ TextCmdDeletedProc(clientData) * * InsertChars -- * - * This procedure implements most of the functionality of the - * "insert" widget command. + * This function implements most of the functionality of the "insert" + * widget command. * * Results: - * None. + * The length of the inserted string. * * Side effects: - * The characters in "string" get added to the text just before - * the character indicated by "indexPtr". + * The characters in "stringPtr" get added to the text just before the + * character indicated by "indexPtr". + * + * If 'viewUpdate' is true, we may adjust the window contents' + * y-position, and scrollbar setting. * *---------------------------------------------------------------------- */ -static void -InsertChars(textPtr, indexPtr, string) - TkText *textPtr; /* Overall information about text widget. */ - TkTextIndex *indexPtr; /* Where to insert new characters. May be - * modified and/or invalidated. */ - CONST char *string; /* Null-terminated string containing new +static int +InsertChars( + TkSharedText *sharedTextPtr, + 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 viewUpdate) /* Update the view if set. */ { - int lineIndex, resetView, offset; - TkTextIndex newTop; - char indexBuffer[TK_POS_CHARS]; + int lineIndex, length; + TkText *tPtr; + int *lineAndByteIndex; + int resetViewCount; + int pixels[2*PIXEL_CLIENTS]; - /* - * Don't do anything for an empty string [Bug 1275237] - */ + const char *string = Tcl_GetStringFromObj(stringPtr, &length); - if (*string == '\0') { - return; + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; } /* - * Don't allow insertions on the last (dummy) line of the text. + * Don't allow insertions on the last (dummy) line of the text. This is + * the only place in this function where the indexPtr is modified. */ - lineIndex = TkBTreeLineIndex(indexPtr->linePtr); - if (lineIndex == TkBTreeNumLines(textPtr->tree)) { + lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (lineIndex == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) { lineIndex--; - TkTextMakeByteIndex(textPtr->tree, lineIndex, 1000000, indexPtr); + TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineIndex, 1000000, + indexPtr); } /* - * Notify the display module that lines are about to change, then do - * the insertion. If the insertion occurs on the top line of the - * widget (textPtr->topIndex), then we have to recompute topIndex - * after the insertion, since the insertion could invalidate it. + * Notify the display module that lines are about to change, then do the + * insertion. If the insertion occurs on the top line of the widget + * (textPtr->topIndex), then we have to recompute topIndex after the + * insertion, since the insertion could invalidate it. */ - resetView = offset = 0; - if (indexPtr->linePtr == textPtr->topIndex.linePtr) { - resetView = 1; - offset = textPtr->topIndex.byteIndex; - if (offset > indexPtr->byteIndex) { - offset += strlen(string); + resetViewCount = 0; + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + lineAndByteIndex = (int *) + ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount); + } else { + lineAndByteIndex = pixels; + } + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + lineAndByteIndex[resetViewCount] = -1; + if (indexPtr->linePtr == tPtr->topIndex.linePtr) { + lineAndByteIndex[resetViewCount] = + TkBTreeLinesTo(tPtr, indexPtr->linePtr); + lineAndByteIndex[resetViewCount+1] = tPtr->topIndex.byteIndex; + if (lineAndByteIndex[resetViewCount+1] > indexPtr->byteIndex) { + lineAndByteIndex[resetViewCount+1] += length; + } } + resetViewCount += 2; } - TkTextChanged(textPtr, indexPtr, indexPtr); - TkBTreeInsertChars(indexPtr, string); + + TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr); + + sharedTextPtr->stateEpoch++; + + TkBTreeInsertChars(sharedTextPtr->tree, indexPtr, string); /* - * Push the insertion on the undo stack + * Push the insertion on the undo stack, and update the modified status of + * the widget. */ - if (textPtr->undo) { - CONST char *cmdName; - TkTextIndex toIndex; + if (length > 0) { + if (sharedTextPtr->undo) { + TkTextIndex toIndex; + + if (sharedTextPtr->autoSeparators && + sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) { + TkUndoInsertUndoSeparator(sharedTextPtr->undoStack); + } - Tcl_DString actionCommand; - Tcl_DString revertCommand; + sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT; - if (textPtr->autoSeparators && - textPtr->lastEditMode != TK_TEXT_EDIT_INSERT) { - TkUndoInsertUndoSeparator(textPtr->undoStack); + TkTextIndexForwBytes(textPtr, indexPtr, length, &toIndex); + TextPushUndoAction(textPtr, stringPtr, 1, indexPtr, &toIndex); } - textPtr->lastEditMode = TK_TEXT_EDIT_INSERT; - cmdName = Tcl_GetCommandName(textPtr->interp, textPtr->widgetCmd); + UpdateDirtyFlag(sharedTextPtr); + } + + resetViewCount = 0; + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + if (lineAndByteIndex[resetViewCount] != -1) { + if ((tPtr != textPtr) || viewUpdate) { + TkTextIndex newTop; + + TkTextMakeByteIndex(sharedTextPtr->tree, tPtr, + lineAndByteIndex[resetViewCount], 0, &newTop); + TkTextIndexForwBytes(tPtr, &newTop, + lineAndByteIndex[resetViewCount+1], &newTop); + TkTextSetYView(tPtr, &newTop, 0); + } + } + resetViewCount += 2; + } + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + ckfree((char *) lineAndByteIndex); + } + + /* + * Invalidate any selection retrievals in progress. + */ + + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + tPtr->abortSelections = 1; + } - Tcl_DStringInit(&actionCommand); - Tcl_DStringInit(&revertCommand); + /* + * For convenience, return the length of the string. + */ - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " insert ", -1); - TkTextPrintIndex(indexPtr,indexBuffer); - Tcl_DStringAppend(&actionCommand, indexBuffer, -1); - Tcl_DStringAppend(&actionCommand, " ", -1); - Tcl_DStringAppendElement(&actionCommand, string); - Tcl_DStringAppend(&actionCommand, ";", -1); - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " mark set insert ", -1); - TkTextIndexForwBytes(indexPtr, (int) strlen(string), &toIndex); - TkTextPrintIndex(&toIndex, indexBuffer); - Tcl_DStringAppend(&actionCommand, indexBuffer,-1); - Tcl_DStringAppend(&actionCommand, "; ", -1); - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " see insert", -1); + return length; +} + +/* + *---------------------------------------------------------------------- + * + * TextPushUndoAction -- + * + * Shared by insert and delete actions. Stores the appropriate scripts + * into our undo stack. We will add a single refCount to the 'undoString' + * object, so, if it previously had a refCount of zero, the caller should + * not free it. + * + * Results: + * None. + * + * Side effects: + * Items pushed onto stack. + * + *---------------------------------------------------------------------- + */ - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand, " delete ", -1); - TkTextPrintIndex(indexPtr, indexBuffer); - Tcl_DStringAppend(&revertCommand, indexBuffer, -1); - Tcl_DStringAppend(&revertCommand, " ", -1); - TkTextPrintIndex(&toIndex, indexBuffer); - Tcl_DStringAppend(&revertCommand, indexBuffer, -1); - Tcl_DStringAppend(&revertCommand, " ;", -1); - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand, " mark set insert ", -1); - TkTextPrintIndex(indexPtr,indexBuffer); - Tcl_DStringAppend(&revertCommand, indexBuffer, -1); - Tcl_DStringAppend(&revertCommand, "; ", -1); - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand," see insert", -1); +static void +TextPushUndoAction( + TkText *textPtr, /* Overall information about text widget. */ + Tcl_Obj *undoString, /* New text. */ + int insert, /* 1 if insert, else delete. */ + const TkTextIndex *index1Ptr, + /* Index describing first location. */ + const TkTextIndex *index2Ptr) + /* Index describing second location. */ +{ + TkUndoSubAtom *iAtom, *dAtom; - TkUndoPushAction(textPtr->undoStack, &actionCommand, &revertCommand); + /* + * Create the helpers. + */ - Tcl_DStringFree(&actionCommand); - Tcl_DStringFree(&revertCommand); + Tcl_Obj *seeInsertObj = Tcl_NewObj(); + Tcl_Obj *markSet1InsertObj = Tcl_NewObj(); + Tcl_Obj *markSet2InsertObj = NULL; + Tcl_Obj *insertCmdObj = Tcl_NewObj(); + Tcl_Obj *deleteCmdObj = Tcl_NewObj(); + + /* + * Get the index positions. + */ + + Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr); + Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr); + + /* + * These need refCounts, because they are used more than once below. + */ + + Tcl_IncrRefCount(seeInsertObj); + Tcl_IncrRefCount(index1Obj); + Tcl_IncrRefCount(index2Obj); + + Tcl_ListObjAppendElement(NULL, seeInsertObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); + Tcl_ListObjAppendElement(NULL, seeInsertObj, Tcl_NewStringObj("see", 3)); + Tcl_ListObjAppendElement(NULL, seeInsertObj, + Tcl_NewStringObj("insert", 6)); + + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, + Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1)); + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, + Tcl_NewStringObj("mark", 4)); + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, + Tcl_NewStringObj("set", 3)); + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, + Tcl_NewStringObj("insert", 6)); + markSet2InsertObj = Tcl_DuplicateObj(markSet1InsertObj); + Tcl_ListObjAppendElement(NULL, markSet1InsertObj, index1Obj); + Tcl_ListObjAppendElement(NULL, markSet2InsertObj, index2Obj); + + Tcl_ListObjAppendElement(NULL, insertCmdObj, + Tcl_NewStringObj("insert", 6)); + Tcl_ListObjAppendElement(NULL, insertCmdObj, index1Obj); + + /* + * Only use of 'undoString' is here. + */ + + Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString); + + Tcl_ListObjAppendElement(NULL, deleteCmdObj, + Tcl_NewStringObj("delete", 6)); + Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj); + Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj); + + /* + * Note: we don't wish to use textPtr->widgetCmd in these callbacks + * because if we delete the textPtr, but peers still exist, we will then + * have references to a non-existent Tcl_Command in the undo stack, which + * will lead to crashes later. Also, the behaviour of the widget w.r.t. + * bindings (%W substitutions) always uses the widget path name, so there + * is no good reason the undo stack should do otherwise. + * + * For the 'insert' and 'delete' actions, we have to register a functional + * callback, because these actions are defined to operate on the + * underlying data shared by all peers. + */ + + iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, + (ClientData)textPtr->sharedTextPtr, insertCmdObj, NULL); + TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom); + TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom); + + dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, + (ClientData)textPtr->sharedTextPtr, deleteCmdObj, NULL); + TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom); + TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom); + + Tcl_DecrRefCount(seeInsertObj); + Tcl_DecrRefCount(index1Obj); + Tcl_DecrRefCount(index2Obj); + + /* + * Depending whether the action is to insert or delete, we provide the + * appropriate second and third arguments to TkUndoPushAction. (The first + * is the 'actionCommand', and the second the 'revertCommand'). + */ + + if (insert) { + TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom); + } else { + TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom); } - updateDirtyFlag(textPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TextUndoRedoCallback -- + * + * This function is registered with the generic undo/redo code to handle + * 'insert' and 'delete' actions on all text widgets. We cannot perform + * those actions on any particular text widget, because that text widget + * might have been deleted by the time we get here. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Will insert or delete text, depending on the first word contained in + * objPtr. + * + *---------------------------------------------------------------------- + */ - if (resetView) { - TkTextMakeByteIndex(textPtr->tree, lineIndex, 0, &newTop); - TkTextIndexForwBytes(&newTop, offset, &newTop); - TkTextSetYView(textPtr, &newTop, 0); +int +TextUndoRedoCallback( + Tcl_Interp *interp, /* Current interpreter. */ + ClientData clientData, /* Passed from undo code, but contains our + * shared text data structure. */ + Tcl_Obj *objPtr) /* Arguments of a command to be handled by the + * shared text data structure. */ +{ + TkSharedText *sharedPtr = (TkSharedText *) clientData; + int res, objc; + Tcl_Obj **objv; + TkText *textPtr; + + res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv); + if (res != TCL_OK) { + return res; } /* - * Invalidate any selection retrievals in progress. + * If possible, use a real text widget to perform the undo/redo action + * (i.e. insertion or deletion of text). This provides maximum + * compatibility with older versions of Tk, in which the user may rename + * the text widget to allow capture of undo or redo actions. + * + * In particular, this sorting of capture is useful in text editors based + * on the Tk text widget, which need to know which new text needs + * re-coloring. + * + * It would be better if the text widget provided some other mechanism to + * allow capture of this information ("What has just changed in the text + * widget?"). What we have here is not entirely satisfactory under all + * circumstances. + */ + + textPtr = sharedPtr->peers; + while (textPtr != NULL) { + if (textPtr->start == NULL && textPtr->end == NULL) { + Tcl_Obj *cmdNameObj, *evalObj; + + evalObj = Tcl_NewObj(); + Tcl_IncrRefCount(evalObj); + + /* + * We might wish to use the real, current command-name for the + * widget, but this will break any code that has over-ridden the + * widget, and is expecting to observe the insert/delete actions + * which are caused by undo/redo operations. + * + * cmdNameObj = Tcl_NewObj(); + * Tcl_GetCommandFullName(interp, textPtr->widgetCmd, cmdNameObj); + * + * While such interception is not explicitly documented as + * supported, it does occur, and so until we can provide some + * alternative mechanism for such code to do what it needs, we + * allow it to take place here. + */ + + cmdNameObj = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1); + Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj); + Tcl_ListObjAppendList(NULL, evalObj, objPtr); + res = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL); + Tcl_DecrRefCount(evalObj); + return res; + } + textPtr = textPtr->next; + } + + /* + * If there's no current text widget which shows everything, then we fall + * back on acting directly. This means there is no way to intercept from + * the Tcl level. */ - textPtr->abortSelections = 1; + return SharedTextObjCmd((ClientData)sharedPtr, interp, objc+1, objv-1); } /* *---------------------------------------------------------------------- * - * DeleteChars -- + * CountIndices -- + * + * This function implements most of the functionality of the "count" + * widget command. * - * This procedure implements most of the functionality of the - * "delete" 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 a standard Tcl result, and leaves an error message - * in textPtr->interp if there is an error. + * Returns the number of characters in the range. * * Side effects: - * Characters get deleted from the text. + * None. * *---------------------------------------------------------------------- */ static int -DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2) - TkText *textPtr; /* Overall information about text widget. */ - CONST char *index1String; /* String describing location of first - * character to delete. */ - CONST char *index2String; /* String describing location of last - * character to delete. NULL means just - * delete the one character given by - * index1String. */ - TkTextIndex *indexPtr1; /* index describing location of first +CountIndices( + const TkText *textPtr, /* Overall information about text widget. */ + const TkTextIndex *indexPtr1, + /* Index describing location of first * character to delete. */ - TkTextIndex *indexPtr2; /* index describing location of last - * character to delete. NULL means just - * delete the one character given by - * indexPtr1. */ + 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); + } +} + +/* + *---------------------------------------------------------------------- + * + * DeleteIndexRange -- + * + * This function implements most of the functionality of the "delete" + * widget command. + * + * Results: + * Returns a standard Tcl result, currently always TCL_OK. + * + * Side effects: + * Characters and other entities (windows, images) get deleted from the + * text. + * + * If 'viewUpdate' is true, we may adjust the window contents' + * y-position, and scrollbar setting. + * + * If 'viewUpdate' is false, true we can guarantee that textPtr->topIndex + * points to a valid TkTextLine after this function returns. However, if + * 'viewUpdate' is false, then there is no such guarantee (since + * topIndex.linePtr can be garbage). The caller is expected to take + * actions to ensure the topIndex is validated before laying out the + * window again. + * + *---------------------------------------------------------------------- + */ + +static int +DeleteIndexRange( + TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */ + TkText *textPtr, /* Overall information about text widget. */ + const TkTextIndex *indexPtr1, + /* Index describing location of first + * character (or other entity) to delete. */ + const TkTextIndex *indexPtr2, + /* Index describing location of last + * character (or other entity) to delete. + * NULL means just delete the one character + * given by indexPtr1. */ + int viewUpdate) /* Update vertical view if set. */ { - int line1, line2, line, byteIndex, resetView; + int line1, line2; TkTextIndex index1, index2; - char indexBuffer[TK_POS_CHARS]; + TkText *tPtr; + int *lineAndByteIndex; + int resetViewCount; + int pixels[2*PIXEL_CLIENTS]; + + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; + } /* - * Parse the starting and stopping indices. + * Prepare the starting and stopping indices. */ - if (index1String != NULL) { - if (TkTextGetIndex(textPtr->interp, textPtr, index1String, &index1) - != TCL_OK) { - return TCL_ERROR; - } - if (index2String != NULL) { - if (TkTextGetIndex(textPtr->interp, textPtr, index2String, &index2) - != TCL_OK) { - return TCL_ERROR; - } - } 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(NULL, &index2, 1, &index2, COUNT_INDICES); } /* @@ -1558,32 +2987,33 @@ DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2) } /* - * The code below is ugly, but it's needed to make sure there - * is always a dummy empty line at the end of the text. If the - * final newline of the file (just before the dummy line) is being - * deleted, then back up index to just before the newline. If - * there is a newline just before the first character being deleted, - * then back up the first index too, so that an even number of lines - * gets deleted. Furthermore, remove any tags that are present on - * the newline that isn't going to be deleted after all (this simulates - * deleting the newline and then adding a "clean" one back again). + * The code below is ugly, but it's needed to make sure there is always a + * dummy empty line at the end of the text. If the final newline of the + * file (just before the dummy line) is being deleted, then back up index + * to just before the newline. If there is a newline just before the first + * character being deleted, then back up the first index too, so that an + * even number of lines gets deleted. Furthermore, remove any tags that + * are present on the newline that isn't going to be deleted after all + * (this simulates deleting the newline and then adding a "clean" one back + * again). Note that index1 and index2 might now be equal again which + * means that no text will be deleted but tags might be removed. */ - line1 = TkBTreeLineIndex(index1.linePtr); - line2 = TkBTreeLineIndex(index2.linePtr); - if (line2 == TkBTreeNumLines(textPtr->tree)) { + line1 = TkBTreeLinesTo(textPtr, index1.linePtr); + line2 = TkBTreeLinesTo(textPtr, index2.linePtr); + if (line2 == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) { TkTextTag **arrayPtr; int arraySize, i; TkTextIndex oldIndex2; oldIndex2 = index2; - TkTextIndexBackChars(&oldIndex2, 1, &index2); + TkTextIndexBackChars(NULL, &oldIndex2, 1, &index2, COUNT_INDICES); line2--; if ((index1.byteIndex == 0) && (line1 != 0)) { - TkTextIndexBackChars(&index1, 1, &index1); + TkTextIndexBackChars(NULL, &index1, 1, &index1, COUNT_INDICES); line1--; } - arrayPtr = TkBTreeGetTags(&index2, &arraySize); + arrayPtr = TkBTreeGetTags(&index2, NULL, &arraySize); if (arrayPtr != NULL) { for (i = 0; i < arraySize; i++) { TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0); @@ -1597,144 +3027,206 @@ DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2) * 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. - * [Bug 1456342] */ Tcl_HashSearch search; Tcl_HashEntry *hPtr; + int i; - for (hPtr=Tcl_FirstHashEntry(&textPtr->tagTable, &search); - hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) { + for (i=0, hPtr=Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search); + hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) { TkTextTag *tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); TkBTreeTag(&index1, &index2, tagPtr, 0); } + + /* + * Special case for the sel tag which is not in the hash table. We + * need to do this once for each peer text widget. + */ + + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; + tPtr = tPtr->next) { + if (TkBTreeTag(&index1, &index2, tPtr->selTagPtr, 0)) { + /* + * Send an event that the selection changed. This is + * equivalent to: + * event generate $textWidget <<Selection>> + */ + + TkTextSelectionEvent(textPtr); + tPtr->abortSelections = 1; + } + } } /* - * Tell the display what's about to happen so it can discard - * obsolete display information, then do the deletion. Also, - * if the deletion involves the top line on the screen, then - * we have to reset the view (the deletion will invalidate - * textPtr->topIndex). Compute what the new first character - * will be, then do the deletion, then reset the view. + * Tell the display what's about to happen so it can discard obsolete + * display information, then do the deletion. Also, if the deletion + * involves the top line on the screen, then we have to reset the view + * (the deletion will invalidate textPtr->topIndex). Compute what the new + * first character will be, then do the deletion, then reset the view. */ - TkTextChanged(textPtr, &index1, &index2); - resetView = 0; - line = 0; - byteIndex = 0; - if (TkTextIndexCmp(&index2, &textPtr->topIndex) >= 0) { - if (TkTextIndexCmp(&index1, &textPtr->topIndex) <= 0) { - /* - * Deletion range straddles topIndex: use the beginning - * of the range as the new topIndex. - */ + TkTextChanged(sharedTextPtr, NULL, &index1, &index2); - resetView = 1; - line = line1; - byteIndex = index1.byteIndex; - } else if (index1.linePtr == textPtr->topIndex.linePtr) { + resetViewCount = 0; + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + lineAndByteIndex = (int *) + ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount); + } else { + lineAndByteIndex = pixels; + } + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + int line = 0; + int byteIndex = 0; + int resetView = 0; + + if (TkTextIndexCmp(&index2, &tPtr->topIndex) >= 0) { + if (TkTextIndexCmp(&index1, &tPtr->topIndex) <= 0) { + /* + * Deletion range straddles topIndex: use the beginning of the + * range as the new topIndex. + */ + + resetView = 1; + line = line1; + byteIndex = index1.byteIndex; + } else if (index1.linePtr == tPtr->topIndex.linePtr) { + /* + * Deletion range starts on top line but after topIndex. Use + * the current topIndex as the new one. + */ + + resetView = 1; + line = line1; + byteIndex = tPtr->topIndex.byteIndex; + } else { + /* + * Deletion range starts after the top line. This peers's view + * will not need to be reset. Nothing to do. + */ + } + } else if (index2.linePtr == tPtr->topIndex.linePtr) { /* - * Deletion range starts on top line but after topIndex. - * Use the current topIndex as the new one. + * Deletion range ends on top line but before topIndex. Figure out + * what will be the new character index for the character + * currently pointed to by topIndex. */ resetView = 1; - line = line1; - byteIndex = textPtr->topIndex.byteIndex; + line = line2; + byteIndex = tPtr->topIndex.byteIndex; + if (index1.linePtr != index2.linePtr) { + byteIndex -= index2.byteIndex; + } else { + byteIndex -= (index2.byteIndex - index1.byteIndex); + } + } else { + /* + * Deletion range ends before the top line. This peers's view + * will not need to be reset. Nothing to do. + */ } - } else if (index2.linePtr == textPtr->topIndex.linePtr) { - /* - * Deletion range ends on top line but before topIndex. - * Figure out what will be the new character index for - * the character currently pointed to by topIndex. - */ - - resetView = 1; - line = line2; - byteIndex = textPtr->topIndex.byteIndex; - if (index1.linePtr != index2.linePtr) { - byteIndex -= index2.byteIndex; + if (resetView) { + lineAndByteIndex[resetViewCount] = line; + lineAndByteIndex[resetViewCount+1] = byteIndex; } else { - byteIndex -= (index2.byteIndex - index1.byteIndex); + lineAndByteIndex[resetViewCount] = -1; } + resetViewCount+=2; } /* - * Push the deletion on the undo stack + * Push the deletion on the undo stack if something was actually deleted. */ - if (textPtr->undo) { - CONST char *cmdName; - Tcl_DString ds; - Tcl_DString actionCommand; - Tcl_DString revertCommand; - - if (textPtr->autoSeparators - && (textPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) { - TkUndoInsertUndoSeparator(textPtr->undoStack); - } - - textPtr->lastEditMode = TK_TEXT_EDIT_DELETE; - cmdName = Tcl_GetCommandName(textPtr->interp, textPtr->widgetCmd); + if (TkTextIndexCmp(&index1, &index2) < 0) { + if (sharedTextPtr->undo) { + Tcl_Obj *get; - Tcl_DStringInit(&actionCommand); - Tcl_DStringInit(&revertCommand); - - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " delete ", -1); - TkTextPrintIndex(&index1, indexBuffer); - Tcl_DStringAppend(&actionCommand, indexBuffer, -1); - Tcl_DStringAppend(&actionCommand, " ", -1); - TkTextPrintIndex(&index2, indexBuffer); - Tcl_DStringAppend(&actionCommand, indexBuffer, -1); - Tcl_DStringAppend(&actionCommand, "; ", -1); - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " mark set insert ", -1); - TkTextPrintIndex(&index1, indexBuffer); - Tcl_DStringAppend(&actionCommand,indexBuffer, -1); - - Tcl_DStringAppend(&actionCommand, "; ", -1); - Tcl_DStringAppendElement(&actionCommand, cmdName); - Tcl_DStringAppend(&actionCommand, " see insert", -1); + if (sharedTextPtr->autoSeparators + && (sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) { + TkUndoInsertUndoSeparator(sharedTextPtr->undoStack); + } - TextGetText(&index1, &index2, &ds); + sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE; - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand, " insert ", -1); - TkTextPrintIndex(&index1, indexBuffer); - Tcl_DStringAppend(&revertCommand, indexBuffer, -1); - Tcl_DStringAppend(&revertCommand, " ", -1); - Tcl_DStringAppendElement(&revertCommand, Tcl_DStringValue(&ds)); - Tcl_DStringAppend(&revertCommand, "; ", -1); - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand, " mark set insert ", -1); - TkTextPrintIndex(&index2, indexBuffer); - Tcl_DStringAppend(&revertCommand, indexBuffer, -1); - Tcl_DStringAppend(&revertCommand, "; ", -1); - Tcl_DStringAppendElement(&revertCommand, cmdName); - Tcl_DStringAppend(&revertCommand, " see insert", -1); + get = TextGetText(textPtr, &index1, &index2, 0); + TextPushUndoAction(textPtr, get, 0, &index1, &index2); + } + sharedTextPtr->stateEpoch++; - TkUndoPushAction(textPtr->undoStack, &actionCommand, &revertCommand); + TkBTreeDeleteIndexRange(sharedTextPtr->tree, &index1, &index2); - Tcl_DStringFree(&actionCommand); - Tcl_DStringFree(&revertCommand); + UpdateDirtyFlag(sharedTextPtr); } - TkBTreeDeleteChars(&index1, &index2); - - updateDirtyFlag(textPtr); - if (resetView) { - TkTextMakeByteIndex(textPtr->tree, line, byteIndex, &index1); - TkTextSetYView(textPtr, &index1, 0); + resetViewCount = 0; + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + int line = lineAndByteIndex[resetViewCount]; + + if (line != -1) { + int byteIndex = lineAndByteIndex[resetViewCount+1]; + TkTextIndex indexTmp; + + if (tPtr == textPtr) { + if (viewUpdate) { + /* + * line cannot be before -startline of textPtr because + * this line corresponds to an index which is necessarily + * between "1.0" and "end" relative to textPtr. + * Therefore no need to clamp line to the -start/-end + * range. + */ + + TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, line, + byteIndex, &indexTmp); + TkTextSetYView(tPtr, &indexTmp, 0); + } + } else { + TkTextMakeByteIndex(sharedTextPtr->tree, tPtr, line, + byteIndex, &indexTmp); + /* + * line may be before -startline of tPtr and must be + * clamped to -startline before providing it to + * TkTextSetYView otherwise lines before -startline + * would be displayed. + * There is no need to worry about -endline however, + * because the view will only be reset if the deletion + * involves the TOP line of the screen + */ + + if (tPtr->start != NULL) { + int start; + TkTextIndex indexStart; + + start = TkBTreeLinesTo(NULL, tPtr->start); + TkTextMakeByteIndex(sharedTextPtr->tree, NULL, start, + 0, &indexStart); + if (TkTextIndexCmp(&indexTmp, &indexStart) < 0) { + indexTmp = indexStart; + } + } + TkTextSetYView(tPtr, &indexTmp, 0); + } + } + resetViewCount += 2; + } + if (sharedTextPtr->refCount > PIXEL_CLIENTS) { + ckfree((char *) lineAndByteIndex); } - /* - * Invalidate any selection retrievals in progress. - */ + if (line1 >= line2) { + /* + * Invalidate any selection retrievals in progress, assuming we didn't + * check for this case above. + */ - textPtr->abortSelections = 1; + for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) { + tPtr->abortSelections = 1; + } + } return TCL_OK; } @@ -1744,15 +3236,15 @@ DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2) * * TextFetchSelection -- * - * This procedure is called back by Tk when the selection is - * requested by someone. It returns part or all of the selection - * in a buffer provided by the caller. + * This function is called back by Tk when the selection is requested by + * someone. It returns part or all of the selection in a buffer provided + * by the caller. * * Results: - * The return value is the number of non-NULL bytes stored - * at buffer. Buffer is filled (or partially filled) with a - * NULL-terminated string containing part or all of the selection, - * as given by offset and maxBytes. + * The return value is the number of non-NULL bytes stored at buffer. + * Buffer is filled (or partially filled) with a NULL-terminated string + * containing part or all of the selection, as given by offset and + * maxBytes. * * Side effects: * None. @@ -1761,15 +3253,14 @@ DeleteChars(textPtr, index1String, index2String, indexPtr1, indexPtr2) */ static int -TextFetchSelection(clientData, offset, buffer, maxBytes) - ClientData clientData; /* Information about text widget. */ - int offset; /* Offset within selection of first - * character to be returned. */ - char *buffer; /* Location in which to place - * selection. */ - int maxBytes; /* Maximum number of bytes to place - * at buffer, not including terminating - * NULL character. */ +TextFetchSelection( + ClientData clientData, /* Information about text widget. */ + int offset, /* Offset within selection of first character + * to be returned. */ + char *buffer, /* Location in which to place selection. */ + int maxBytes) /* Maximum number of bytes to place at buffer, + * not including terminating NULL + * character. */ { register TkText *textPtr = (TkText *) clientData; TkTextIndex eof; @@ -1782,20 +3273,21 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) } /* - * Find the beginning of the next range of selected text. Note: if - * the selection is being retrieved in multiple pieces (offset != 0) - * and some modification has been made to the text that affects the - * selection then reject the selection request (make 'em start over - * again). + * Find the beginning of the next range of selected text. Note: if the + * selection is being retrieved in multiple pieces (offset != 0) and some + * modification has been made to the text that affects the selection then + * reject the selection request (make 'em start over again). */ if (offset == 0) { - TkTextMakeByteIndex(textPtr->tree, 0, 0, &textPtr->selIndex); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0, + &textPtr->selIndex); textPtr->abortSelections = 0; } else if (textPtr->abortSelections) { return 0; } - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &eof); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), 0, &eof); TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, &search); if (!TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) { if (!TkBTreeNextTag(&search)) { @@ -1810,8 +3302,8 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) /* * Each iteration through the outer loop below scans one selected range. - * Each iteration through the inner loop scans one segment in the - * selected range. + * Each iteration through the inner loop scans one segment in the selected + * range. */ count = 0; @@ -1821,18 +3313,18 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) */ if (!TkBTreeNextTag(&search)) { - panic("TextFetchSelection couldn't find end of range"); + Tcl_Panic("TextFetchSelection couldn't find end of range"); } /* - * Copy information from character segments into the buffer - * until either we run out of space in the buffer or we get - * to the end of this range of text. + * Copy information from character segments into the buffer until + * either we run out of space in the buffer or we get to the end of + * this range of text. */ while (1) { if (maxBytes == 0) { - goto done; + goto fetchDone; } segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg); chunkSize = segPtr->size - offsetInSeg; @@ -1852,14 +3344,14 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) } } if ((segPtr->typePtr == &tkTextCharType) - && !TkTextIsElided(textPtr, &textPtr->selIndex)) { - memcpy((VOID *) buffer, (VOID *) (segPtr->body.chars - + offsetInSeg), (size_t) chunkSize); + && !TkTextIsElided(textPtr, &textPtr->selIndex, NULL)) { + memcpy(buffer, segPtr->body.chars + offsetInSeg, + (size_t) chunkSize); buffer += chunkSize; maxBytes -= chunkSize; count += chunkSize; } - TkTextIndexForwBytes(&textPtr->selIndex, chunkSize, + TkTextIndexForwBytes(textPtr, &textPtr->selIndex, chunkSize, &textPtr->selIndex); } @@ -1873,7 +3365,7 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) textPtr->selIndex = search.curIndex; } - done: + fetchDone: *buffer = 0; return count; } @@ -1883,11 +3375,10 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) * * TkTextLostSelection -- * - * This procedure is called back by Tk when the selection is - * grabbed away from a text widget. On Windows and Mac systems, we - * want to remember the selection for the next time the focus - * enters the window. On Unix, just remove the "sel" tag from - * everything in the widget. + * This function is called back by Tk when the selection is grabbed away + * from a text widget. On Windows and Mac systems, we want to remember + * the selection for the next time the focus enters the window. On Unix, + * just remove the "sel" tag from everything in the widget. * * Results: * None. @@ -1899,11 +3390,10 @@ TextFetchSelection(clientData, offset, buffer, maxBytes) */ void -TkTextLostSelection(clientData) - ClientData clientData; /* Information about text widget. */ +TkTextLostSelection( + ClientData clientData) /* Information about text widget. */ { register TkText *textPtr = (TkText *) clientData; - XEvent event; if (TkpAlwaysShowSelection(textPtr->tkwin)) { TkTextIndex start, end; @@ -1913,30 +3403,26 @@ TkTextLostSelection(clientData) } /* - * On Windows and Mac systems, we want to remember the selection - * for the next time the focus enters the window. On Unix, - * just remove the "sel" tag from everything in the widget. + * On Windows and Mac systems, we want to remember the selection for + * the next time the focus enters the window. On Unix, just remove the + * "sel" tag from everything in the widget. */ - TkTextMakeByteIndex(textPtr->tree, 0, 0, &start); - TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0, &end); - TkTextRedrawTag(textPtr, &start, &end, textPtr->selTagPtr, 1); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + 0, 0, &start); + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), + 0, &end); + TkTextRedrawTag(NULL, textPtr, &start, &end, textPtr->selTagPtr, 1); TkBTreeTag(&start, &end, textPtr->selTagPtr, 0); } /* - * Send an event that the selection changed. This is equivalent to - * "event generate $textWidget <<Selection>>" + * 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); + TkTextSelectionEvent(textPtr); textPtr->flags &= ~GOT_SELECTION; } @@ -1944,31 +3430,78 @@ TkTextLostSelection(clientData) /* *---------------------------------------------------------------------- * + * TkTextSelectionEvent -- + * + * When anything relevant to the "sel" tag has been changed, call this + * function to generate a <<Selection>> event. + * + * Results: + * None. + * + * Side effects: + * If <<Selection>> bindings are present, they will trigger. + * + *---------------------------------------------------------------------- + */ + +void +TkTextSelectionEvent( + TkText *textPtr) +{ + /* + * Send an event that the selection changed. This is equivalent to: + * event generate $textWidget <<Selection>> + */ + + union {XEvent general; XVirtualEvent virtual;} event; + + memset(&event, 0, sizeof(event)); + event.general.xany.type = VirtualEvent; + event.general.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.general.xany.send_event = False; + event.general.xany.window = Tk_WindowId(textPtr->tkwin); + event.general.xany.display = Tk_Display(textPtr->tkwin); + event.virtual.name = Tk_GetUid("Selection"); + Tk_HandleEvent(&event.general); +} + +/* + *---------------------------------------------------------------------- + * * TextBlinkProc -- * - * This procedure is called as a timer handler to blink the - * insertion cursor off and on. + * This function is called as a timer handler to blink the insertion + * cursor off and on. * * Results: * None. * * Side effects: - * The cursor gets turned on or off, redisplay gets invoked, - * and this procedure reschedules itself. + * The cursor gets turned on or off, redisplay gets invoked, and this + * function reschedules itself. * *---------------------------------------------------------------------- */ static void -TextBlinkProc(clientData) - ClientData clientData; /* Pointer to record describing text. */ +TextBlinkProc( + ClientData clientData) /* Pointer to record describing text. */ { register TkText *textPtr = (TkText *) clientData; TkTextIndex index; - int x, y, w, h; + int x, y, w, h, charWidth; - if ((textPtr->state == TK_STATE_DISABLED) || + if ((textPtr->state == TK_TEXT_STATE_DISABLED) || !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) { + if ((textPtr->insertOffTime == 0) && !(textPtr->flags & INSERT_ON)) { + /* + * The widget was configured to have zero offtime while the + * insertion point was not displayed. We have to display it once. + */ + + textPtr->flags |= INSERT_ON; + goto redrawInsert; + } return; } if (textPtr->flags & INSERT_ON) { @@ -1980,21 +3513,111 @@ TextBlinkProc(clientData) textPtr->insertBlinkHandler = Tcl_CreateTimerHandler( textPtr->insertOnTime, TextBlinkProc, (ClientData) textPtr); } + redrawInsert: 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 (TkTextIndexBbox(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 function is invoked to process the "insert" and "replace" widget + * commands for text widgets. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * See the user documentation. + * + * If 'viewUpdate' is true, we may adjust the window contents' + * y-position, and scrollbar setting. + * + *---------------------------------------------------------------------- + */ + +static int +TextInsertCmd( + TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */ + 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 viewUpdate) /* Update the view if set. */ +{ + TkTextIndex index1, index2; + int j; + + if (sharedTextPtr == NULL) { + sharedTextPtr = textPtr->sharedTextPtr; + } + + 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(sharedTextPtr, textPtr, &index1, objv[j], + viewUpdate); + + if (objc > (j+1)) { + Tcl_Obj **tagNamePtrs; + TkTextTag **oldTagArrayPtr; + int numTags; + + TkTextIndexForwBytes(textPtr, &index1, length, &index2); + oldTagArrayPtr = TkBTreeGetTags(&index1, NULL, &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++) { + const char *strTag = Tcl_GetString(tagNamePtrs[i]); + + TkBTreeTag(&index1, &index2, + TkTextCreateTag(textPtr, strTag, NULL), 1); + } + index1 = index2; + } + } + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TextSearchCmd -- * - * This procedure is invoked to process the "search" widget command - * for text widgets. See the user documentation for details on what - * it does. + * This function is invoked to process the "search" widget command for + * text widgets. See the user documentation for details on what it does. * * Results: * A standard Tcl result. @@ -2006,393 +3629,680 @@ TextBlinkProc(clientData) */ static int -TextSearchCmd(textPtr, interp, argc, argv) - TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - CONST char **argv; /* Argument strings. */ +TextSearchCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ { - int backwards, exact, searchElide, c, i, argsLeft, noCase, leftToScan; - size_t length; - int numLines, startingLine, startingByte, lineNum, firstByte, lastByte; - int code, matchLength, matchByte, passes, stopLine, searchWholeText; - int patLength; - CONST char *arg, *pattern, *varName, *p, *startOfLine; - char buffer[20]; - TkTextIndex index, stopIndex; - Tcl_DString line, patDString; - TkTextSegment *segPtr; - TkTextLine *linePtr; - TkTextIndex curIndex; - Tcl_Obj *patObj = NULL; - Tcl_RegExp regexp = NULL; /* Initialization needed only to - * prevent compiler warning. */ + int i, argsLeft, code; + SearchSpec searchSpec; + + static const char *switchStrings[] = { + "--", "-all", "-backwards", "-count", "-elide", "-exact", "-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_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP, SEARCH_STRICTLIMITS + }; + + /* + * Set up the search specification, including the last 4 fields which are + * text widget specific. + */ + + searchSpec.exact = 1; + searchSpec.noCase = 0; + searchSpec.all = 0; + searchSpec.backwards = 0; + searchSpec.varPtr = NULL; + searchSpec.countPtr = NULL; + searchSpec.resPtr = NULL; + searchSpec.searchElide = 0; + searchSpec.noLineStop = 0; + searchSpec.overlap = 0; + searchSpec.strictLimits = 0; + searchSpec.numLines = + TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr); + searchSpec.clientData = (ClientData)textPtr; + searchSpec.addLineProc = &TextSearchAddNextLine; + searchSpec.foundMatchProc = &TextSearchFoundMatch; + searchSpec.lineIndexProc = &TextSearchGetLineIndex; /* * Parse switches and other arguments. */ - exact = 1; - searchElide = 0; - curIndex.tree = textPtr->tree; - backwards = 0; - noCase = 0; - varName = NULL; - for (i = 2; i < argc; i++) { - arg = argv[i]; - if (arg[0] != '-') { + for (i=2 ; i<objc ; i++) { + int index; + if (Tcl_GetString(objv[i])[0] != '-') { break; } - length = strlen(arg); - if (length < 2) { - badSwitch: - Tcl_AppendResult(interp, "bad switch \"", arg, - "\": must be --, -backward, -count, -elide, -exact, ", - "-forward, -nocase, or -regexp", (char *) NULL); + + if (Tcl_GetIndexFromObj(interp, objv[i], switchStrings, "switch", 0, + &index) != TCL_OK) { + /* + * Hide the -hidden option. + */ + + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "bad switch \"", Tcl_GetString(objv[i]), + "\": must be --, -all, -backward, -count, -elide, ", + "-exact, -forward, -nocase, -nolinestop, -overlap, ", + "-regexp, or -strictlimits", NULL); return TCL_ERROR; } - c = arg[1]; - if ((c == 'b') && (strncmp(argv[i], "-backwards", length) == 0)) { - backwards = 1; - } else if ((c == 'c') && (strncmp(argv[i], "-count", length) == 0)) { - if (i >= (argc-1)) { + + switch ((enum SearchSwitches) index) { + case SEARCH_END: + i++; + goto endOfSwitchProcessing; + case SEARCH_ALL: + searchSpec.all = 1; + break; + case SEARCH_BACK: + searchSpec.backwards = 1; + break; + case SEARCH_COUNT: + if (i >= objc-1) { Tcl_SetResult(interp, "no value given for \"-count\" option", TCL_STATIC); return TCL_ERROR; } i++; - varName = argv[i]; - } else if ((c == 'e') && (length > 2) - && (strncmp(argv[i], "-exact", length) == 0)) { - exact = 1; - } else if ((c == 'e') && (length > 2) - && (strncmp(argv[i], "-elide", length) == 0)) { - searchElide = 1; - } else if ((c == 'h') && (strncmp(argv[i], "-hidden", length) == 0)) { + /* - * -hidden is kept around for backwards compatibility with - * the dash patch, but -elide is the official option + * Assumption objv[i] isn't going to disappear on us during this + * function, which is fair. */ - searchElide = 1; - } else if ((c == 'f') && (strncmp(argv[i], "-forwards", length) == 0)) { - backwards = 0; - } else if ((c == 'n') && (strncmp(argv[i], "-nocase", length) == 0)) { - noCase = 1; - } else if ((c == 'r') && (strncmp(argv[i], "-regexp", length) == 0)) { - exact = 0; - } else if ((c == '-') && (strncmp(argv[i], "--", length) == 0)) { - i++; + + searchSpec.varPtr = objv[i]; break; - } else { - goto badSwitch; + case SEARCH_ELIDE: + case SEARCH_HIDDEN: + searchSpec.searchElide = 1; + break; + case SEARCH_EXACT: + searchSpec.exact = 1; + break; + case SEARCH_FWD: + searchSpec.backwards = 0; + break; + case SEARCH_NOCASE: + searchSpec.noCase = 1; + break; + 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; + default: + Tcl_Panic("unexpected switch fallthrough"); } } - argsLeft = argc - (i+2); + endOfSwitchProcessing: + + argsLeft = objc - (i+2); if ((argsLeft != 0) && (argsLeft != 1)) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " search ?switches? pattern index ?stopIndex?\"", - (char *) NULL); + Tcl_WrongNumArgs(interp, 2, objv, + "?switches? pattern index ?stopIndex?"); + return TCL_ERROR; + } + + if (searchSpec.noLineStop && searchSpec.exact) { + Tcl_SetResult(interp, "the \"-nolinestop\" option requires the " + "\"-regexp\" option to be present", TCL_STATIC); + 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; } - pattern = argv[i]; /* - * Convert the pattern to lower-case if we're supposed to ignore case. + * 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. */ - if (noCase && exact) { - Tcl_DStringInit(&patDString); - Tcl_DStringAppend(&patDString, pattern, -1); - Tcl_UtfToLower(Tcl_DStringValue(&patDString)); - pattern = Tcl_DStringValue(&patDString); + code = SearchPerform(interp, &searchSpec, objv[i], objv[i+1], + (argsLeft == 1 ? objv[i+2] : NULL)); + if (code != TCL_OK) { + goto cleanup; } - Tcl_DStringInit(&line); - if (TkTextGetIndex(interp, textPtr, argv[i+1], &index) != TCL_OK) { - code = TCL_ERROR; - goto done; - } - numLines = TkBTreeNumLines(textPtr->tree); - startingLine = TkBTreeLineIndex(index.linePtr); - startingByte = index.byteIndex; - if (startingLine >= numLines) { - if (backwards) { - startingLine = TkBTreeNumLines(textPtr->tree) - 1; - startingByte = TkBTreeBytesInLine(TkBTreeFindLine(textPtr->tree, - startingLine)); - } else { - startingLine = 0; - startingByte = 0; - } - } - if (argsLeft == 1) { - if (TkTextGetIndex(interp, textPtr, argv[i+2], &stopIndex) != TCL_OK) { + /* + * Set the '-count' variable, if given. + */ + + if (searchSpec.varPtr != NULL && searchSpec.countPtr != NULL) { + Tcl_IncrRefCount(searchSpec.countPtr); + if (Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL, + searchSpec.countPtr, TCL_LEAVE_ERR_MSG) == NULL) { code = TCL_ERROR; - goto done; - } - stopLine = TkBTreeLineIndex(stopIndex.linePtr); - if (!backwards && (stopLine == numLines)) { - stopLine = numLines-1; + goto cleanup; } - searchWholeText = 0; - } else { - stopLine = 0; - searchWholeText = 1; } /* - * Scan through all of the lines of the text circularly, starting - * at the given index. + * Set the result. */ - matchLength = patLength = 0; /* Only needed to prevent compiler - * warnings. */ - if (exact) { - patLength = strlen(pattern); - } else { - patObj = Tcl_NewStringObj(pattern, -1); - Tcl_IncrRefCount(patObj); - regexp = Tcl_GetRegExpFromObj(interp, patObj, - (noCase ? TCL_REG_NOCASE : 0) | TCL_REG_ADVANCED); - if (regexp == NULL) { - code = TCL_ERROR; - goto done; - } + if (searchSpec.resPtr != NULL) { + Tcl_SetObjResult(interp, searchSpec.resPtr); + searchSpec.resPtr = NULL; } - lineNum = startingLine; - code = TCL_OK; - for (passes = 0; passes < 2; ) { - if (lineNum >= numLines) { - /* - * Don't search the dummy last line of the text. - */ - goto nextLine; - } + cleanup: + if (searchSpec.countPtr != NULL) { + Tcl_DecrRefCount(searchSpec.countPtr); + } + if (searchSpec.resPtr != NULL) { + Tcl_DecrRefCount(searchSpec.resPtr); + } + return code; +} + +/* + *---------------------------------------------------------------------- + * + * TextSearchGetLineIndex -- + * + * Extract a row, text offset index position from an objPtr + * + * This means we ignore any embedded windows/images and elidden text + * (unless we are searching that). + * + * Results: + * Standard Tcl error code (with a message in the interpreter on error + * conditions). + * + * The offset placed in offsetPosPtr is a utf-8 char* byte index for + * exact searches, and a Unicode character index for regexp searches. + * + * The line number should start at zero (searches which wrap around + * assume the first line is numbered 0). + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TextSearchGetLineIndex( + Tcl_Interp *interp, /* For error messages. */ + Tcl_Obj *objPtr, /* Contains a textual index like "1.2" */ + SearchSpec *searchSpecPtr, /* Contains other search parameters. */ + int *linePosPtr, /* For returning the line number. */ + int *offsetPosPtr) /* For returning the text offset in the + * line. */ +{ + const TkTextIndex *indexPtr; + int line; + TkText *textPtr = (TkText *) searchSpecPtr->clientData; + + indexPtr = TkTextGetIndexFromObj(interp, textPtr, objPtr); + if (indexPtr == NULL) { + return TCL_ERROR; + } + + line = TkBTreeLinesTo(textPtr, indexPtr->linePtr); + if (line >= searchSpecPtr->numLines) { + TkTextLine *linePtr; + int count = 0; + TkTextSegment *segPtr; + + line = searchSpecPtr->numLines-1; + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, line); /* - * Extract the text from the line. If we're doing regular - * expression matching, drop the newline from the line, so - * that "$" can be used to match the end of the line. + * Count the number of bytes in this line. */ - linePtr = TkBTreeFindLine(textPtr->tree, lineNum); - curIndex.linePtr = linePtr; curIndex.byteIndex = 0; - for (segPtr = linePtr->segPtr; segPtr != NULL; + for (segPtr=linePtr->segPtr ; segPtr!=NULL ; segPtr=segPtr->nextPtr) { + count += segPtr->size; + } + *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, count); + } else { + *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, + indexPtr->linePtr, indexPtr->byteIndex); + } + + *linePosPtr = line; + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TextSearchIndexInLine -- + * + * Find textual index of 'byteIndex' in the searchable characters of + * 'linePtr'. + * + * This means we ignore any embedded windows/images and elidden text + * (unless we are searching that). + * + * Results: + * The returned index is a utf-8 char* byte index for exact searches, and + * a Unicode character index for regexp searches. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TextSearchIndexInLine( + const SearchSpec *searchSpecPtr, + /* Search parameters. */ + TkTextLine *linePtr, /* The line we're looking at. */ + int byteIndex) /* Index into the line. */ +{ + TkTextSegment *segPtr; + TkTextIndex curIndex; + int index, leftToScan; + TkText *textPtr = (TkText *) searchSpecPtr->clientData; + + index = 0; + curIndex.tree = textPtr->sharedTextPtr->tree; + curIndex.linePtr = linePtr; curIndex.byteIndex = 0; + for (segPtr = linePtr->segPtr, leftToScan = byteIndex; + leftToScan > 0; + curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) { + if ((segPtr->typePtr == &tkTextCharType) && + (searchSpecPtr->searchElide + || !TkTextIsElided(textPtr, &curIndex, NULL))) { + if (leftToScan < segPtr->size) { + if (searchSpecPtr->exact) { + index += leftToScan; + } else { + index += Tcl_NumUtfChars(segPtr->body.chars, leftToScan); + } + } else if (searchSpecPtr->exact) { + index += segPtr->size; + } else { + index += Tcl_NumUtfChars(segPtr->body.chars, -1); + } + } + leftToScan -= segPtr->size; + } + return index; +} + +/* + *---------------------------------------------------------------------- + * + * TextSearchAddNextLine -- + * + * Adds a line from the text widget to the object 'theLine'. + * + * Results: + * A pointer to the TkTextLine corresponding to the given line, or NULL + * if there was no available line. + * + * Also 'lenPtr' (if non-NULL) is filled in with the total length of + * 'theLine' (not just what we added to it, but the length including what + * was already in there). This is in bytes for an exact search and in + * chars for a regexp search. + * + * Also 'extraLinesPtr' (if non-NULL) will have its value incremented by + * 1 for each additional logical line we have added because a newline is + * elided (this will only ever happen if we have chosen not to search + * elided text, of course). + * + * Side effects: + * Memory may be allocated or re-allocated for theLine's string + * representation. + * + *---------------------------------------------------------------------- + */ + +static ClientData +TextSearchAddNextLine( + int lineNum, /* Line we must add. */ + SearchSpec *searchSpecPtr, /* Search parameters. */ + Tcl_Obj *theLine, /* Object to append to. */ + int *lenPtr, /* For returning the total length. */ + int *extraLinesPtr) /* If non-NULL, will have its value + * incremented by the number of additional + * logical lines which are merged into this + * one by newlines being elided. */ +{ + TkTextLine *linePtr, *thisLinePtr; + TkTextIndex curIndex; + TkTextSegment *segPtr; + TkText *textPtr = (TkText *) searchSpecPtr->clientData; + int nothingYet = 1; + + /* + * Extract the text from the line. + */ + + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum); + if (linePtr == NULL) { + return NULL; + } + curIndex.tree = textPtr->sharedTextPtr->tree; + thisLinePtr = linePtr; + + while (thisLinePtr != NULL) { + int elideWraps = 0; + + curIndex.linePtr = thisLinePtr; + curIndex.byteIndex = 0; + for (segPtr = thisLinePtr->segPtr; segPtr != NULL; curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) { - if ((segPtr->typePtr != &tkTextCharType) - || (!searchElide && TkTextIsElided(textPtr, &curIndex))) { + if (!searchSpecPtr->searchElide + && TkTextIsElided(textPtr, &curIndex, NULL)) { + /* + * If we reach the end of the logical line, and if we have at + * least one character in the string, then we continue + * wrapping to the next logical line. If there are no + * characters yet, then the entire line of characters is + * elided and there's no need to complicate matters by + * wrapping - we'll look at the next line in due course. + */ + + if (segPtr->nextPtr == NULL && !nothingYet) { + elideWraps = 1; + } + continue; + } + if (segPtr->typePtr != &tkTextCharType) { continue; } - Tcl_DStringAppend(&line, segPtr->body.chars, segPtr->size); + Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size); + nothingYet = 0; } - if (!exact) { - Tcl_DStringSetLength(&line, Tcl_DStringLength(&line)-1); + if (!elideWraps) { + break; + } + lineNum++; + if (lineNum >= searchSpecPtr->numLines) { + break; } - startOfLine = Tcl_DStringValue(&line); + thisLinePtr = TkBTreeNextLine(textPtr, thisLinePtr); + if (thisLinePtr != NULL && extraLinesPtr != NULL) { + /* + * Tell our caller we have an extra line merged in. + */ - /* - * If we're ignoring case, convert the line to lower case. - */ + *extraLinesPtr = (*extraLinesPtr) + 1; + } + } + + /* + * If we're ignoring case, convert the line to lower case. There is no + * need to do this for regexp searches, since they handle a flag for this + * purpose. + */ - if (noCase) { - Tcl_DStringSetLength(&line, - Tcl_UtfToLower(Tcl_DStringValue(&line))); + if (searchSpecPtr->exact && searchSpecPtr->noCase) { + Tcl_SetObjLength(theLine, Tcl_UtfToLower(Tcl_GetString(theLine))); + } + + if (lenPtr != NULL) { + if (searchSpecPtr->exact) { + Tcl_GetStringFromObj(theLine, lenPtr); + } else { + *lenPtr = Tcl_GetCharLength(theLine); } + } + return (ClientData)linePtr; +} + +/* + *---------------------------------------------------------------------- + * + * TextSearchFoundMatch -- + * + * Stores information from a successful search. + * + * Results: + * 1 if the information was stored, 0 if the position at which the match + * was found actually falls outside the allowable search region (and + * therefore the search is actually complete). + * + * Side effects: + * Memory may be allocated in the 'countPtr' and 'resPtr' fields of + * 'searchSpecPtr'. Each of those objects will have refCount zero and + * must eventually be freed or stored elsewhere as appropriate. + * + *---------------------------------------------------------------------- + */ +static int +TextSearchFoundMatch( + int lineNum, /* Line on which match was found. */ + SearchSpec *searchSpecPtr, /* Search parameters. */ + ClientData clientData, /* Token returned by the 'addNextLineProc', + * 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. */ + int matchLength) /* Length also in bytes/chars as per search + * type. */ +{ + int numChars; + int leftToScan; + TkTextIndex curIndex, foundIndex; + TkTextSegment *segPtr; + TkTextLine *linePtr; + TkText *textPtr = (TkText *) searchSpecPtr->clientData; + + if (lineNum == searchSpecPtr->stopLine) { /* - * Check for matches within the current line. If so, and if we're - * searching backwards, repeat the search to find the last match - * in the line. (Note: The lastByte should include the NULL char - * so we can handle searching for end of line easier.) + * If the current index is on the wrong side of the stopIndex, then + * the item we just found is actually outside the acceptable range, + * and the search is over. */ - matchByte = -1; - firstByte = 0; - lastByte = Tcl_DStringLength(&line) + 1; - if (lineNum == startingLine) { - int indexInDString; + if (searchSpecPtr->backwards ^ + (matchOffset >= searchSpecPtr->stopOffset)) { + return 0; + } + } - /* - * The starting line is tricky: the first time we see it - * we check one part of the line, and the second pass through - * we check the other part of the line. We have to be very - * careful here because there could be embedded windows or - * other things that are not in the extracted line. Rescan - * the original line to compute the index in it of the first - * character. - */ + /* + * Calculate the character count, which may need augmenting if there are + * embedded windows or elidden text. + */ + + if (searchSpecPtr->exact) { + const char *startOfLine = Tcl_GetString(theLine); + + numChars = Tcl_NumUtfChars(startOfLine + matchOffset, matchLength); + } else { + numChars = matchLength; + } + + /* + * If we're using strict limits checking, ensure that the match with its + * full length fits inside the given range. + */ + + if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) { + if (searchSpecPtr->backwards ^ + ((matchOffset + numChars) > searchSpecPtr->stopOffset)) { + return 0; + } + } + + /* + * The index information returned by the regular expression parser only + * considers textual information: it doesn't account for embedded windows, + * elided text (when we are not searching elided text) or any other + * non-textual info. Scan through the line's segments again to adjust both + * matchChar and matchCount. + * + * We will walk through the segments of this line until we have either + * reached the end of the match or we have reached the end of the line. + */ + + linePtr = (TkTextLine *)clientData; + if (linePtr == NULL) { + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, + lineNum); + } + + curIndex.tree = textPtr->sharedTextPtr->tree; + + /* + * Find the starting point. + */ + + leftToScan = matchOffset; + while (1) { + curIndex.linePtr = linePtr; + curIndex.byteIndex = 0; - indexInDString = startingByte; - for (segPtr = linePtr->segPtr, leftToScan = startingByte; - leftToScan > 0; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr != &tkTextCharType) { - indexInDString -= segPtr->size; + /* + * Note that we allow leftToScan to be zero because we want to skip + * over any preceding non-textual items. + */ + + for (segPtr = linePtr->segPtr; leftToScan >= 0 && segPtr; + segPtr = segPtr->nextPtr) { + if (segPtr->typePtr != &tkTextCharType) { + matchOffset += segPtr->size; + } else if (!searchSpecPtr->searchElide + && TkTextIsElided(textPtr, &curIndex, NULL)) { + if (searchSpecPtr->exact) { + matchOffset += segPtr->size; + } else { + matchOffset += Tcl_NumUtfChars(segPtr->body.chars, -1); } + } else { leftToScan -= segPtr->size; } + curIndex.byteIndex += segPtr->size; + } + if (segPtr == NULL && leftToScan >= 0) { + /* + * This will only happen if we are eliding newlines. + */ - passes++; - if ((passes == 1) ^ backwards) { + linePtr = TkBTreeNextLine(textPtr, linePtr); + if (linePtr == NULL) { /* - * Only use the last part of the line. + * If we reach the end of the text, we have a serious problem, + * unless there's actually nothing left to look for. */ - firstByte = indexInDString; - if ((firstByte >= Tcl_DStringLength(&line)) - && !((Tcl_DStringLength(&line) == 0) && !exact)) { - goto nextLine; + if (leftToScan == 0) { + break; + } else { + Tcl_Panic("Reached end of text in a match"); } - } else { - /* - * Use only the first part of the line. - */ - - lastByte = indexInDString; } + + /* + * We've wrapped to the beginning of the next logical line, which + * has been merged with the previous one whose newline was elided. + */ + + lineNum++; + matchOffset = 0; + } else { + break; } - do { - int thisLength; - Tcl_UniChar ch; + } - if (exact) { - p = strstr(startOfLine + firstByte, /* INTL: Native. */ - pattern); - if (p == NULL) { - break; - } - i = p - startOfLine; - thisLength = patLength; - } else { - CONST char *start, *end; - int match; + /* + * Calculate and store the found index in the result. + */ - match = Tcl_RegExpExec(interp, regexp, - startOfLine + firstByte, startOfLine); - if (match < 0) { - code = TCL_ERROR; - goto done; - } - if (!match) { - break; - } - Tcl_RegExpRange(regexp, 0, &start, &end); - i = start - startOfLine; - thisLength = end - start; - } - if (i >= lastByte) { - break; - } - matchByte = i; - matchLength = thisLength; - firstByte = i + Tcl_UtfToUniChar(startOfLine + matchByte, &ch); - } while (backwards); + if (searchSpecPtr->exact) { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, + matchOffset, &foundIndex); + } else { + TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, + matchOffset, &foundIndex); + } - /* - * If we found a match then we're done. Make sure that - * the match occurred before the stopping index, if one was - * specified. - */ + if (searchSpecPtr->all) { + if (searchSpecPtr->resPtr == NULL) { + searchSpecPtr->resPtr = Tcl_NewObj(); + } + Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr, + TkTextNewIndexObj(textPtr, &foundIndex)); + } else { + searchSpecPtr->resPtr = TkTextNewIndexObj(textPtr, &foundIndex); + } - if (matchByte >= 0) { - int numChars; + /* + * Find the end point. Here 'leftToScan' could be negative already as a + * result of the above loop if the segment we reached spanned the start of + * the string. When we add matchLength it will become non-negative. + */ + for (leftToScan += matchLength; leftToScan > 0; + curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) { + if (segPtr == NULL) { /* - * Convert the byte length to a character count. + * We are on the next line - this of course should only ever + * happen with searches which have matched across multiple lines. */ - numChars = Tcl_NumUtfChars(startOfLine + matchByte, - matchLength); - + linePtr = TkBTreeNextLine(textPtr, linePtr); + segPtr = linePtr->segPtr; + curIndex.linePtr = linePtr; curIndex.byteIndex = 0; + } + if (segPtr->typePtr != &tkTextCharType) { /* - * The index information returned by the regular expression - * parser only considers textual information: it doesn't - * account for embedded windows, elided text (when we are not - * searching elided text) or any other non-textual info. - * Scan through the line's segments again to adjust both - * matchChar and matchCount. - * - * We will walk through the segments of this line until we have - * either reached the end of the match or we have reached the end - * of the line. + * Anything we didn't count in the search needs adding. */ - curIndex.linePtr = linePtr; curIndex.byteIndex = 0; - for (segPtr = linePtr->segPtr, leftToScan = matchByte; - leftToScan >= 0 && segPtr; segPtr = segPtr->nextPtr) { - if (segPtr->typePtr != &tkTextCharType || \ - (!searchElide && TkTextIsElided(textPtr, &curIndex))) { - matchByte += segPtr->size; - } else { - leftToScan -= segPtr->size; - } - curIndex.byteIndex += segPtr->size; - } - for (leftToScan += matchLength; leftToScan > 0; - segPtr = segPtr->nextPtr) { - if (segPtr->typePtr != &tkTextCharType) { - numChars += segPtr->size; - continue; - } - leftToScan -= segPtr->size; - } - TkTextMakeByteIndex(textPtr->tree, lineNum, matchByte, &index); - if (!searchWholeText) { - if (!backwards && (TkTextIndexCmp(&index, &stopIndex) >= 0)) { - goto done; - } - if (backwards && (TkTextIndexCmp(&index, &stopIndex) < 0)) { - goto done; - } - } - if (varName != NULL) { - sprintf(buffer, "%d", numChars); - if (Tcl_SetVar(interp, varName, buffer, TCL_LEAVE_ERR_MSG) - == NULL) { - code = TCL_ERROR; - goto done; - } - } - TkTextPrintIndex(&index, buffer); - Tcl_SetResult(interp, buffer, TCL_VOLATILE); - goto done; + numChars += segPtr->size; + continue; + } else if (!searchSpecPtr->searchElide + && TkTextIsElided(textPtr, &curIndex, NULL)) { + numChars += Tcl_NumUtfChars(segPtr->body.chars, -1); + continue; + } + if (searchSpecPtr->exact) { + leftToScan -= segPtr->size; + } else { + leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1); } + } - /* - * Go to the next (or previous) line; - */ + /* + * Now store the count result, if it is wanted. + */ - nextLine: - if (backwards) { - lineNum--; - if (!searchWholeText) { - if (lineNum < stopLine) { - break; - } - } else if (lineNum < 0) { - lineNum = numLines-1; + if (searchSpecPtr->varPtr != NULL) { + Tcl_Obj *tmpPtr = Tcl_NewIntObj(numChars); + if (searchSpecPtr->all) { + if (searchSpecPtr->countPtr == NULL) { + searchSpecPtr->countPtr = Tcl_NewObj(); } + Tcl_ListObjAppendElement(NULL, searchSpecPtr->countPtr, tmpPtr); } else { - lineNum++; - if (!searchWholeText) { - if (lineNum > stopLine) { - break; - } - } else if (lineNum >= numLines) { - lineNum = 0; - } + searchSpecPtr->countPtr = tmpPtr; } - Tcl_DStringSetLength(&line, 0); - } - done: - Tcl_DStringFree(&line); - if (noCase && exact) { - Tcl_DStringFree(&patDString); } - if (patObj != NULL) { - Tcl_DecrRefCount(patObj); - } - return code; + return 1; } /* @@ -2403,105 +4313,158 @@ TextSearchCmd(textPtr, interp, argc, argv) * Parses a string description of a set of tab stops. * * Results: - * The return value is a pointer to a malloc'ed structure holding - * parsed information about the tab stops. If an error occurred - * then the return value is NULL and an error message is left in - * the interp's result. + * The return value is a pointer to a malloc'ed structure holding parsed + * information about the tab stops. If an error occurred then the return + * value is NULL and an error message is left in the interp's result. * * Side effects: - * Memory is allocated for the structure that is returned. It is - * up to the caller to free this structure when it is no longer - * needed. + * Memory is allocated for the structure that is returned. It is up to + * the caller to free this structure when it is no longer needed. * *---------------------------------------------------------------------- */ TkTextTabArray * -TkTextGetTabs(interp, tkwin, string) - Tcl_Interp *interp; /* Used for error reporting. */ - Tk_Window tkwin; /* Window in which the tabs will be - * used. */ - char *string; /* Description of the tab stops. See - * the text manual entry for details. */ +TkTextGetTabs( + Tcl_Interp *interp, /* Used for error reporting. */ + TkText *textPtr, /* Information about the text widget. */ + Tcl_Obj *stringPtr) /* Description of the tab stops. See the text + * manual entry for details. */ { - int argc, i, count, c; - CONST char **argv; + int objc, i, count; + Tcl_Obj **objv; TkTextTabArray *tabArrayPtr; TkTextTab *tabPtr; Tcl_UniChar ch; + double prevStop, lastStop; + /* + * Map these strings to TkTextTabAlign values. + */ + static const char *tabOptionStrings[] = { + "left", "right", "center", "numeric", NULL + }; - if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) { + if (Tcl_ListObjGetElements(interp, stringPtr, &objc, &objv) != TCL_OK) { return NULL; } /* - * First find out how many entries we need to allocate in the - * tab array. + * First find out how many entries we need to allocate in the tab array. */ count = 0; - for (i = 0; i < argc; i++) { - c = argv[i][0]; + for (i = 0; i < objc; i++) { + char c = Tcl_GetString(objv[i])[0]; if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) { count++; } } /* - * Parse the elements of the list one at a time to fill in the - * array. + * Parse the elements of the list one at a time to fill in the array. */ tabArrayPtr = (TkTextTabArray *) ckalloc((unsigned) (sizeof(TkTextTabArray) + (count-1)*sizeof(TkTextTab))); tabArrayPtr->numTabs = 0; - for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i < argc; i++, tabPtr++) { - if (Tk_GetPixels(interp, tkwin, argv[i], &tabPtr->location) - != TCL_OK) { + prevStop = 0.0; + lastStop = 0.0; + for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i < objc; i++, tabPtr++) { + int index; + + /* + * This will round fractional pixels above 0.5 upwards, and otherwise + * downwards, to find the right integer pixel position. + */ + + if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i], + &tabPtr->location) != TCL_OK) { + goto error; + } + + if (tabPtr->location <= 0) { + Tcl_AppendResult(interp, "tab stop \"", Tcl_GetString(objv[i]), + "\" is not at a positive distance", NULL); + goto error; + } + + prevStop = lastStop; + if (Tk_GetDoublePixelsFromObj (interp, textPtr->tkwin, objv[i], + &lastStop) != TCL_OK) { goto error; } + + if (i > 0 && (tabPtr->location <= (tabPtr-1)->location)) { + /* + * This tab is actually to the left of the previous one, which is + * illegal. + */ + +#ifdef _TK_ALLOW_DECREASING_TABS + /* + * Force the tab to be a typical character width to the right of + * the previous one, and update the 'lastStop' with the changed + * position. + */ + + if (textPtr->charWidth > 0) { + tabPtr->location = (tabPtr-1)->location + textPtr->charWidth; + } else { + tabPtr->location = (tabPtr-1)->location + 8; + } + lastStop = tabPtr->location; +#else + Tcl_AppendResult(interp, + "tabs must be monotonically increasing, but \"", + Tcl_GetString(objv[i]), + "\" is smaller than or equal to the previous tab", + NULL); + goto error; +#endif /* _TK_ALLOW_DECREASING_TABS */ + } + tabArrayPtr->numTabs++; /* - * See if there is an explicit alignment in the next list - * element. Otherwise just use "left". + * See if there is an explicit alignment in the next list element. + * Otherwise just use "left". */ tabPtr->alignment = LEFT; - if ((i+1) == argc) { + if ((i+1) == objc) { continue; } - Tcl_UtfToUniChar(argv[i+1], &ch); + + /* + * There may be a more efficient way of getting this. + */ + + Tcl_UtfToUniChar(Tcl_GetString(objv[i+1]), &ch); if (!Tcl_UniCharIsAlpha(ch)) { continue; } i += 1; - c = argv[i][0]; - if ((c == 'l') && (strncmp(argv[i], "left", - strlen(argv[i])) == 0)) { - tabPtr->alignment = LEFT; - } else if ((c == 'r') && (strncmp(argv[i], "right", - strlen(argv[i])) == 0)) { - tabPtr->alignment = RIGHT; - } else if ((c == 'c') && (strncmp(argv[i], "center", - strlen(argv[i])) == 0)) { - tabPtr->alignment = CENTER; - } else if ((c == 'n') && (strncmp(argv[i], - "numeric", strlen(argv[i])) == 0)) { - tabPtr->alignment = NUMERIC; - } else { - Tcl_AppendResult(interp, "bad tab alignment \"", - argv[i], "\": must be left, right, center, or numeric", - (char *) NULL); + + if (Tcl_GetIndexFromObj(interp, objv[i], tabOptionStrings, + "tab alignment", 0, &index) != TCL_OK) { goto error; } + tabPtr->alignment = ((TkTextTabAlign)index); } - ckfree((char *) argv); + + /* + * For when we need to interpolate tab stops, store these two so we know + * the tab stop size to very high precision. With the above checks, we can + * guarantee that tabIncrement is strictly positive here. + */ + + tabArrayPtr->lastTab = lastStop; + tabArrayPtr->tabIncrement = lastStop - prevStop; + return tabArrayPtr; - error: + error: ckfree((char *) tabArrayPtr); - ckfree((char *) argv); return NULL; } @@ -2511,8 +4474,8 @@ TkTextGetTabs(interp, tkwin, string) * TextDumpCmd -- * * Return information about the text, tags, marks, and embedded windows - * and images in a text widget. See the man page for the description - * of the text dump operation for all the details. + * and images in a text widget. See the man page for the description of + * the text dump operation for all the details. * * Results: * A standard Tcl result. @@ -2525,21 +4488,21 @@ TkTextGetTabs(interp, tkwin, string) */ static int -TextDumpCmd(textPtr, interp, argc, argv) - register TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - CONST char **argv; /* Argument strings. Someone else has already +TextDumpCmd( + register 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 - * argv[1] is "dump". */ + * objv[1] is "dump". */ { TkTextIndex index1, index2; int arg; - int lineno; /* Current line number */ - int what = 0; /* bitfield to select segment types */ - int atEnd; /* True if dumping up to logical end */ + int lineno; /* Current line number. */ + int what = 0; /* bitfield to select segment types. */ + int atEnd; /* True if dumping up to logical end. */ TkTextLine *linePtr; - CONST char *command = NULL; /* Script callback to apply to segments */ + Tcl_Obj *command = NULL; /* Script callback to apply to segments. */ #define TK_DUMP_TEXT 0x1 #define TK_DUMP_MARK 0x2 #define TK_DUMP_TAG 0x4 @@ -2547,116 +4510,189 @@ TextDumpCmd(textPtr, interp, argc, argv) #define TK_DUMP_IMG 0x10 #define TK_DUMP_ALL (TK_DUMP_TEXT|TK_DUMP_MARK|TK_DUMP_TAG| \ TK_DUMP_WIN|TK_DUMP_IMG) - - for (arg=2 ; argv[arg] != (char *) NULL ; arg++) { - size_t len; - if (argv[arg][0] != '-') { + static const char *optStrings[] = { + "-all", "-command", "-image", "-mark", "-tag", "-text", "-window", + NULL + }; + enum opts { + DUMP_ALL, DUMP_CMD, DUMP_IMG, DUMP_MARK, DUMP_TAG, DUMP_TXT, DUMP_WIN + }; + + for (arg=2 ; arg < objc ; arg++) { + int index; + if (Tcl_GetString(objv[arg])[0] != '-') { break; } - len = strlen(argv[arg]); - if (strncmp("-all", argv[arg], len) == 0) { + if (Tcl_GetIndexFromObj(interp, objv[arg], optStrings, "option", 0, + &index) != TCL_OK) { + return TCL_ERROR; + } + switch ((enum opts) index) { + case DUMP_ALL: what = TK_DUMP_ALL; - } else if (strncmp("-text", argv[arg], len) == 0) { + break; + case DUMP_TXT: what |= TK_DUMP_TEXT; - } else if (strncmp("-tag", argv[arg], len) == 0) { + break; + case DUMP_TAG: what |= TK_DUMP_TAG; - } else if (strncmp("-mark", argv[arg], len) == 0) { + break; + case DUMP_MARK: what |= TK_DUMP_MARK; - } else if (strncmp("-image", argv[arg], len) == 0) { + break; + case DUMP_IMG: what |= TK_DUMP_IMG; - } else if (strncmp("-window", argv[arg], len) == 0) { + break; + case DUMP_WIN: what |= TK_DUMP_WIN; - } else if (strncmp("-command", argv[arg], len) == 0) { + break; + case DUMP_CMD: arg++; - if (arg >= argc) { - Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL); + if (arg >= objc) { + Tcl_AppendResult(interp, "Usage: ", Tcl_GetString(objv[0]), + " dump ?-all -image -text -mark -tag -window? ", + "?-command script? index ?index2?", NULL); return TCL_ERROR; } - command = argv[arg]; - } else { - Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL); - return TCL_ERROR; + command = objv[arg]; + break; + default: + Tcl_Panic("unexpected switch fallthrough"); } } - if (arg >= argc) { - Tcl_AppendResult(interp, "Usage: ", argv[0], " dump ?-all -image -text -mark -tag -window? ?-command script? index ?index2?", NULL); + if (arg >= objc || arg+2 < objc) { + Tcl_AppendResult(interp, "Usage: ", Tcl_GetString(objv[0]), + " dump ?-all -image -text -mark -tag -window? ", + "?-command script? index ?index2?", NULL); return TCL_ERROR; } if (what == 0) { what = TK_DUMP_ALL; } - if (TkTextGetIndex(interp, textPtr, argv[arg], &index1) != TCL_OK) { + if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index1) != TCL_OK) { return TCL_ERROR; } - lineno = TkBTreeLineIndex(index1.linePtr); arg++; atEnd = 0; - if (argc == arg) { - TkTextIndexForwChars(&index1, 1, &index2); + if (objc == arg) { + TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES); } else { - if (TkTextGetIndex(interp, textPtr, argv[arg], &index2) != TCL_OK) { + int length; + char *str; + + if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) { return TCL_ERROR; } - if (strncmp(argv[arg], "end", strlen(argv[arg])) == 0) { + str = Tcl_GetStringFromObj(objv[arg], &length); + if (strncmp(str, "end", (unsigned)length) == 0) { atEnd = 1; } } if (TkTextIndexCmp(&index1, &index2) >= 0) { return TCL_OK; } + lineno = TkBTreeLinesTo(textPtr, index1.linePtr); if (index1.linePtr == index2.linePtr) { DumpLine(interp, textPtr, what, index1.linePtr, - index1.byteIndex, index2.byteIndex, lineno, command); + index1.byteIndex, index2.byteIndex, lineno, command); } else { - DumpLine(interp, textPtr, what, index1.linePtr, + int textChanged; + int lineend = TkBTreeLinesTo(textPtr, index2.linePtr); + int endByteIndex = index2.byteIndex; + + textChanged = DumpLine(interp, textPtr, what, index1.linePtr, index1.byteIndex, 32000000, lineno, command); - linePtr = index1.linePtr; - while ((linePtr = TkBTreeNextLine(linePtr)) != (TkTextLine *)NULL) { + if (textChanged) { + if (textPtr->flags & DESTROYED) { + return TCL_OK; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineno); + textChanged = 0; + } else { + linePtr = index1.linePtr; + } + while ((linePtr = TkBTreeNextLine(textPtr, linePtr)) != NULL) { lineno++; - if (linePtr == index2.linePtr) { + if (lineno == lineend) { break; } - DumpLine(interp, textPtr, what, linePtr, 0, 32000000, + textChanged = DumpLine(interp, textPtr, what, linePtr, 0, 32000000, lineno, command); + if (textChanged) { + if (textPtr->flags & DESTROYED) { + return TCL_OK; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineno); + textChanged = 0; + } + } + if (linePtr != NULL) { + DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno, + command); + if (textPtr->flags & DESTROYED) { + return TCL_OK; + } } - DumpLine(interp, textPtr, what, index2.linePtr, 0, - index2.byteIndex, lineno, command); } + /* * Special case to get the leftovers hiding at the end mark. */ + if (atEnd) { - DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr, - 0, 1, lineno, command); + if (textPtr->flags & DESTROYED) { + return TCL_OK; + } + + /* + * Re-get the end index, in case it has changed. + */ + if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) { + return TCL_ERROR; + } + DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr, + 0, 1, lineno, command); } return TCL_OK; } /* + *---------------------------------------------------------------------- + * * DumpLine - * Return information about a given text line from character - * position "start" up to, but not including, "end". + * + * Return information about a given text line from character position + * "start" up to, but not including, "end". * * Results: - * A standard Tcl result. + * Returns 1 if the command callback made any changes to the text widget + * which will have invalidated internal structures such as TkTextSegment, + * TkTextIndex, pointers. Our caller can then take action to recompute + * such entities. Returns 0 otherwise. * * Side effects: - * None, but see DumpSegment. + * None, but see DumpSegment which can have arbitrary side-effects + * + *---------------------------------------------------------------------- */ -static void -DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) - Tcl_Interp *interp; - TkText *textPtr; - int what; /* bit flags to select segment types */ - TkTextLine *linePtr; /* The current line */ - int startByte, endByte; /* Byte range to dump */ - int lineno; /* Line number for indices dump */ - CONST char *command; /* Script to apply to the segment */ + +static int +DumpLine( + Tcl_Interp *interp, + TkText *textPtr, + int what, /* Bit flags to select segment types. */ + TkTextLine *linePtr, /* The current line. */ + int startByte, int endByte, /* Byte range to dump. */ + int lineno, /* Line number for indices dump. */ + Tcl_Obj *command) /* Script to apply to the segment. */ { - int offset; - TkTextSegment *segPtr, *nextPtr; + TkTextSegment *segPtr; TkTextIndex index; + int offset = 0, textChanged = 0; + /* * Must loop through line looking at its segments. * character @@ -2666,335 +4702,485 @@ DumpLine(interp, textPtr, what, linePtr, startByte, endByte, lineno, command) * window */ - for (offset = 0, segPtr = linePtr->segPtr ; - (offset < endByte) && (segPtr != (TkTextSegment *)NULL) ; - offset += segPtr->size, segPtr = nextPtr) { - nextPtr = segPtr->nextPtr; + segPtr = linePtr->segPtr; + while ((offset < endByte) && (segPtr != NULL)) { + int lineChanged = 0; + int currentSize = segPtr->size; + if ((what & TK_DUMP_TEXT) && (segPtr->typePtr == &tkTextCharType) && - (offset + segPtr->size > startByte)) { - char savedChar; /* Last char used in the seg */ - int last = segPtr->size; /* Index of savedChar */ - int first = 0; /* Index of first char in seg */ - if (offset + segPtr->size > endByte) { + (offset + currentSize > startByte)) { + int last = currentSize; /* Index of last char in seg. */ + int first = 0; /* Index of first char in seg. */ + + if (offset + currentSize > endByte) { last = endByte - offset; } if (startByte > offset) { first = startByte - offset; } - savedChar = segPtr->body.chars[last]; - segPtr->body.chars[last] = '\0'; - - TkTextMakeByteIndex(textPtr->tree, lineno, offset + first, &index); - DumpSegment(interp, "text", segPtr->body.chars + first, - command, &index, what); - segPtr->body.chars[last] = savedChar; + if (last != currentSize) { + /* + * To avoid modifying the string in place we copy over just + * the segment that we want. Since DumpSegment can modify the + * text, we could not confidently revert the modification + * here. + */ + + int length = last - first; + char *range = ckalloc((length + 1) * sizeof(char)); + + memcpy(range, segPtr->body.chars + first, + length * sizeof(char)); + range[length] = '\0'; + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset + first, &index); + lineChanged = DumpSegment(textPtr, interp, "text", range, + command, &index, what); + ckfree(range); + } else { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset + first, &index); + lineChanged = DumpSegment(textPtr, interp, "text", + segPtr->body.chars + first, command, &index, what); + } } else if ((offset >= startByte)) { if ((what & TK_DUMP_MARK) && (segPtr->typePtr->name[0] == 'm')) { - TkTextMark *markPtr = (TkTextMark *)&segPtr->body; - char *name = Tcl_GetHashKey(&textPtr->markTable, markPtr->hPtr); - - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "mark", name, command, &index, what); + char *name; + TkTextMark *markPtr = (TkTextMark *) &segPtr->body; + + if (segPtr == textPtr->insertMarkPtr) { + name = "insert"; + } else if (segPtr == textPtr->currentMarkPtr) { + name = "current"; + } else if (markPtr->hPtr == NULL) { + name = NULL; + lineChanged = 0; + } else { + name = Tcl_GetHashKey(&textPtr->sharedTextPtr->markTable, + markPtr->hPtr); + } + if (name != NULL) { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + lineChanged = DumpSegment(textPtr, interp, "mark", name, + command, &index, what); + } } else if ((what & TK_DUMP_TAG) && - (segPtr->typePtr == &tkTextToggleOnType)) { - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "tagon", - segPtr->body.toggle.tagPtr->name, - command, &index, what); - } else if ((what & TK_DUMP_TAG) && - (segPtr->typePtr == &tkTextToggleOffType)) { - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "tagoff", - segPtr->body.toggle.tagPtr->name, - command, &index, what); - } else if ((what & TK_DUMP_IMG) && - (segPtr->typePtr->name[0] == 'i')) { + (segPtr->typePtr == &tkTextToggleOnType)) { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + lineChanged = DumpSegment(textPtr, interp, "tagon", + segPtr->body.toggle.tagPtr->name, command, &index, + what); + } else if ((what & TK_DUMP_TAG) && + (segPtr->typePtr == &tkTextToggleOffType)) { + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + lineChanged = DumpSegment(textPtr, interp, "tagoff", + segPtr->body.toggle.tagPtr->name, command, &index, + what); + } else if ((what & TK_DUMP_IMG) && + (segPtr->typePtr->name[0] == 'i')) { TkTextEmbImage *eiPtr = (TkTextEmbImage *)&segPtr->body; - char *name = (eiPtr->name == NULL) ? "" : eiPtr->name; - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "image", name, + char *name = (eiPtr->name == NULL) ? "" : eiPtr->name; + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + lineChanged = DumpSegment(textPtr, interp, "image", name, command, &index, what); - } else if ((what & TK_DUMP_WIN) && - (segPtr->typePtr->name[0] == 'w')) { + } else if ((what & TK_DUMP_WIN) && + (segPtr->typePtr->name[0] == 'w')) { TkTextEmbWindow *ewPtr = (TkTextEmbWindow *)&segPtr->body; char *pathname; + if (ewPtr->tkwin == (Tk_Window) NULL) { pathname = ""; } else { pathname = Tk_PathName(ewPtr->tkwin); } - TkTextMakeByteIndex(textPtr->tree, lineno, offset, &index); - DumpSegment(interp, "window", pathname, + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, + lineno, offset, &index); + lineChanged = DumpSegment(textPtr, interp, "window", pathname, command, &index, what); } } - if (nextPtr != segPtr->nextPtr) { + offset += currentSize; + if (lineChanged) { + TkTextSegment *newSegPtr; + int newOffset = 0; + + textChanged = 1; + /* - * Someone modified the text widget while we were dumping. - * Just stop dumping. [Bug 1414171] + * Our indices are no longer valid. */ - break; + + if (textPtr->flags & DESTROYED) { + return textChanged; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, + textPtr, lineno); + newSegPtr = linePtr->segPtr; + if (segPtr == newSegPtr) { + segPtr = segPtr->nextPtr; + } else { + while ((newOffset < endByte) && (newOffset < offset) + && (newSegPtr != NULL)) { + newOffset += currentSize; + newSegPtr = newSegPtr->nextPtr; + if (segPtr == newSegPtr) { + break; + } + } + if (segPtr != newSegPtr && newOffset == offset + && currentSize == 0) { + TkTextSegment *searchPtr = newSegPtr; + + while (searchPtr != NULL && searchPtr->size == 0) { + if (searchPtr == segPtr) { + newSegPtr = searchPtr; + break; + } + searchPtr = searchPtr->nextPtr; + } + } + segPtr = newSegPtr; + if (segPtr != NULL) { + segPtr = segPtr->nextPtr; + } + } + } else { + segPtr = segPtr->nextPtr; } } + return textChanged; } /* + *---------------------------------------------------------------------- + * * DumpSegment - * Either append information about the current segment to the result, - * or make a script callback with that information as arguments. + * + * Either append information about the current segment to the result, or + * make a script callback with that information as arguments. * * Results: - * None + * Returns 1 if the command callback made any changes to the text widget + * which will have invalidated internal structures such as TkTextSegment, + * TkTextIndex, pointers. Our caller can then take action to recompute + * such entities. Returns 0 otherwise. * * Side effects: * Either evals the callback or appends elements to the result string. + * The callback can have arbitrary side-effects. + * + *---------------------------------------------------------------------- */ + static int -DumpSegment(interp, key, value, command, index, what) - Tcl_Interp *interp; - char *key; /* Segment type key */ - char *value; /* Segment value */ - CONST char *command; /* Script callback */ - TkTextIndex *index; /* index with line/byte position info */ - int what; /* Look for TK_DUMP_INDEX bit */ +DumpSegment( + TkText *textPtr, + Tcl_Interp *interp, + const char *key, /* Segment type key. */ + const char *value, /* Segment value. */ + Tcl_Obj *command, /* Script callback. */ + const TkTextIndex *index, /* index with line/byte position info. */ + int what) /* Look for TK_DUMP_INDEX bit. */ { - char buffer[TCL_INTEGER_SPACE*2]; - TkTextPrintIndex(index, buffer); + char buffer[TK_POS_CHARS]; + + TkTextPrintIndex(textPtr, index, buffer); if (command == NULL) { Tcl_AppendElement(interp, key); Tcl_AppendElement(interp, value); Tcl_AppendElement(interp, buffer); - return TCL_OK; + return 0; } else { - CONST char *argv[4]; + const char *argv[4]; char *list; - int result; + int oldStateEpoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree); + argv[0] = key; argv[1] = value; argv[2] = buffer; argv[3] = NULL; list = Tcl_Merge(3, argv); - result = Tcl_VarEval(interp, command, " ", list, (char *) NULL); + Tcl_VarEval(interp, Tcl_GetString(command), " ", list, NULL); ckfree(list); - return result; + if ((textPtr->flags & DESTROYED) || + TkBTreeEpoch(textPtr->sharedTextPtr->tree) != oldStateEpoch) { + return 1; + } else { + return 0; + } } } /* + *---------------------------------------------------------------------- + * * TextEditUndo -- - * undo the last change. + * + * Undo the last change. * * Results: - * None + * None. * * Side effects: - * None. + * Apart from manipulating the undo and redo stacks, the state of the + * rest of the widget may also change (due to whatever is being undone). + * + *---------------------------------------------------------------------- */ static int -TextEditUndo(textPtr) - TkText *textPtr; /* Overall information about text widget. */ +TextEditUndo( + TkText *textPtr) /* Overall information about text widget. */ { int status; - if (!textPtr->undo) { + if (!textPtr->sharedTextPtr->undo) { return TCL_OK; } - /* Turn off the undo feature */ - textPtr->undo = 0; + /* + * Turn off the undo feature while we revert a compound action, setting + * the dirty handling mode to undo for the duration (unless it is + * 'fixed'). + */ - /* Set dirty mode to undo, unless it is fixed */ - if (textPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { - textPtr->dirtyMode = TK_TEXT_DIRTY_UNDO; + textPtr->sharedTextPtr->undo = 0; + if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO; } - /* revert one compound action */ - status = TkUndoRevert(textPtr->undoStack); + status = TkUndoRevert(textPtr->sharedTextPtr->undoStack); - /* Restore dirty mode */ - if (textPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { - textPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; + if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; } - - /* Turn back on the undo feature */ - textPtr->undo = 1; + textPtr->sharedTextPtr->undo = 1; return status; } /* + *---------------------------------------------------------------------- + * * TextEditRedo -- - * redo the last undone change. + * + * Redo the last undone change. * * Results: - * None + * None. * * Side effects: - * None. + * Apart from manipulating the undo and redo stacks, the state of the + * rest of the widget may also change (due to whatever is being redone). + * + *---------------------------------------------------------------------- */ static int -TextEditRedo(textPtr) - TkText *textPtr; /* Overall information about text widget. */ +TextEditRedo( + TkText *textPtr) /* Overall information about text widget. */ { int status; - if (!textPtr->undo) { + if (!textPtr->sharedTextPtr->undo) { return TCL_OK; } - /* Turn off the undo feature temporarily */ - textPtr->undo = 0; + /* + * Turn off the undo feature temporarily while we revert a previously + * undone compound action, setting the dirty handling mode to redo for the + * duration (unless it is 'fixed'). + */ - /* Set dirty mode to redo, unless it is fixed */ - if (textPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { - textPtr->dirtyMode = TK_TEXT_DIRTY_REDO; + textPtr->sharedTextPtr->undo = 0; + if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO; } - /* reapply one compound action */ - status = TkUndoApply(textPtr->undoStack); + status = TkUndoApply(textPtr->sharedTextPtr->undoStack); - /* Restore dirty mode */ - if (textPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { - textPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; + if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) { + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; } - - /* Turn back on the undo feature */ - textPtr->undo = 1; - + textPtr->sharedTextPtr->undo = 1; return status; } /* + *---------------------------------------------------------------------- + * * TextEditCmd -- * - * Handle the subcommands to "$text edit ...". - * See documentation for details. + * Handle the subcommands to "$text edit ...". See documentation for + * details. * * Results: - * None + * None * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ static int -TextEditCmd(textPtr, interp, argc, argv) - TkText *textPtr; /* Information about text widget. */ - Tcl_Interp *interp; /* Current interpreter. */ - int argc; /* Number of arguments. */ - CONST char **argv; /* Argument strings. */ +TextEditCmd( + TkText *textPtr, /* Information about text widget. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ { - int c; - size_t length; + int index; - if (argc < 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit option ?arg arg ...?\"", (char *) NULL); + static const char *editOptionStrings[] = { + "modified", "redo", "reset", "separator", "undo", NULL + }; + enum editOptions { + EDIT_MODIFIED, EDIT_REDO, EDIT_RESET, EDIT_SEPARATOR, EDIT_UNDO + }; + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 2, objv, "option ?arg arg ...?"); return TCL_ERROR; } - c = argv[2][0]; - length = strlen(argv[2]); - if ((c == 'm') && (strncmp(argv[2], "modified", length) == 0)) { - if (argc == 3) { - Tcl_SetObjResult(interp, Tcl_NewBooleanObj(textPtr->isDirty)); - } else if (argc != 4) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit modified ?boolean?\"", (char *) NULL); + + if (Tcl_GetIndexFromObj(interp, objv[2], editOptionStrings, + "edit option", 0, &index) != TCL_OK) { + return TCL_ERROR; + } + + switch ((enum editOptions) index) { + case EDIT_MODIFIED: + if (objc == 3) { + Tcl_SetObjResult(interp, + Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty)); + } else if (objc != 4) { + Tcl_WrongNumArgs(interp, 3, objv, "?boolean?"); return TCL_ERROR; } else { - int setModified, wasModified = textPtr->isDirty; + int setModified, oldModified; - if (Tcl_GetBoolean(interp, argv[3], &setModified) != TCL_OK) { + if (Tcl_GetBooleanFromObj(interp, objv[3], + &setModified) != TCL_OK) { return TCL_ERROR; } /* - * Set or reset the dirty info, and trigger a Modified event (. + * Set or reset the dirty info, and trigger a Modified event. */ setModified = setModified ? 1 : 0; - textPtr->isDirty = setModified; + oldModified = textPtr->sharedTextPtr->isDirty; + textPtr->sharedTextPtr->isDirty = setModified; if (setModified) { - textPtr->dirtyMode = TK_TEXT_DIRTY_FIXED; + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED; } else { - textPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; + textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL; } - if ((!wasModified) != (!setModified)) { + + /* + * Only issue the <<Modified>> event if the flag actually changed. + * However, degree of modified-ness doesn't matter. [Bug 1799782] + */ + + if ((!oldModified) != (!setModified)) { GenerateModifiedEvent(textPtr); } - } - } else if ((c == 'r') && (strncmp(argv[2], "redo", length) == 0) - && (length >= 3)) { - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit redo\"", (char *) NULL); + } + break; + case EDIT_REDO: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - if ( TextEditRedo(textPtr) ) { - Tcl_AppendResult(interp, "nothing to redo", (char *) NULL); + if (TextEditRedo(textPtr)) { + Tcl_AppendResult(interp, "nothing to redo", NULL); return TCL_ERROR; - } - } else if ((c == 'r') && (strncmp(argv[2], "reset", length) == 0) - && (length >= 3)) { - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit reset\"", (char *) NULL); + } + break; + case EDIT_RESET: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - TkUndoClearStacks(textPtr->undoStack); - } else if ((c == 's') && (strncmp(argv[2], "separator", length) == 0)) { - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit separator\"", (char *) NULL); + TkUndoClearStacks(textPtr->sharedTextPtr->undoStack); + break; + case EDIT_SEPARATOR: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - TkUndoInsertUndoSeparator(textPtr->undoStack); - } else if ((c == 'u') && (strncmp(argv[2], "undo", length) == 0)) { - if (argc != 3) { - Tcl_AppendResult(interp, "wrong # args: should be \"", - argv[0], " edit undo\"", (char *) NULL); + TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack); + break; + case EDIT_UNDO: + if (objc != 3) { + Tcl_WrongNumArgs(interp, 3, objv, NULL); return TCL_ERROR; } - if ( TextEditUndo(textPtr) ) { - Tcl_AppendResult(interp, "nothing to undo", - (char *) NULL); + if (TextEditUndo(textPtr)) { + Tcl_AppendResult(interp, "nothing to undo", NULL); return TCL_ERROR; - } - } else { - Tcl_AppendResult(interp, "bad edit option \"", argv[2], - "\": must be modified, redo, reset, separator or undo", - (char *) NULL); - return TCL_ERROR; + } + break; } - return TCL_OK; } /* + *---------------------------------------------------------------------- + * * TextGetText -- - * Returns the text from indexPtr1 to indexPtr2, placing that text - * in the Tcl_DString given. That DString should be free or uninitialized. + * + * Returns the text from indexPtr1 to indexPtr2, placing that text in a + * string object which is returned with a refCount of zero. + * + * Since the amount of text may potentially be several megabytes (e.g. + * in text editors built on the text widget), efficiency is very + * important. We may want to investigate the efficiency of the + * Tcl_AppendToObj more carefully (e.g. if we know we are going to be + * appending several thousand lines, we could attempt to pre-allocate a + * larger space). + * + * Also the result is built up as a utf-8 string, but, if we knew we + * wanted it as Unicode, we could potentially save a huge conversion by + * building it up as Unicode directly. This could be as simple as + * replacing Tcl_NewObj by Tcl_NewUnicodeObj. * * Results: - * None. + * 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 DString. Remember to free it. + * Memory will be allocated for the new object. Remember to free it if it + * isn't going to be stored appropriately. + * + *---------------------------------------------------------------------- */ -static void -TextGetText(indexPtr1,indexPtr2, dsPtr) - TkTextIndex *indexPtr1; - TkTextIndex *indexPtr2; - Tcl_DString *dsPtr; +static Tcl_Obj * +TextGetText( + 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_DStringInit(dsPtr); - - TkTextMakeByteIndex(indexPtr1->tree, TkBTreeLineIndex(indexPtr1->linePtr), + Tcl_Obj *resultPtr = Tcl_NewObj(); + + TkTextMakeByteIndex(indexPtr1->tree, textPtr, + TkBTreeLinesTo(textPtr, indexPtr1->linePtr), indexPtr1->byteIndex, &tmpIndex); if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) { @@ -3005,26 +5191,38 @@ TextGetText(indexPtr1,indexPtr2, dsPtr) segPtr = TkTextIndexToSeg(&tmpIndex, &offset); last = segPtr->size; if (tmpIndex.linePtr == indexPtr2->linePtr) { - int last2; + /* + * The last line that was requested must be handled carefully, + * because we may need to break out of this loop in the middle + * of the line. + */ if (indexPtr2->byteIndex == tmpIndex.byteIndex) { break; - } - last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset; - if (last2 < last) { - last = last2; + } else { + int last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + + offset; + + if (last2 < last) { + last = last2; + } } } if (segPtr->typePtr == &tkTextCharType) { - Tcl_DStringAppend(dsPtr, segPtr->body.chars + offset, - last - offset); + if (!visibleOnly || !TkTextIsElided(textPtr,&tmpIndex,NULL)) { + Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset, + last - offset); + } } - TkTextIndexForwBytes(&tmpIndex, last-offset, &tmpIndex); + TkTextIndexForwBytes(textPtr, &tmpIndex, last-offset, &tmpIndex); } } + return resultPtr; } /* + *---------------------------------------------------------------------- + * * GenerateModifiedEvent -- * * Send an event that the text was modified. This is equivalent to @@ -3035,68 +5233,1464 @@ TextGetText(indexPtr1,indexPtr2, dsPtr) * * Side effects: * May force the text window into existence. + * + *---------------------------------------------------------------------- */ static void GenerateModifiedEvent( TkText *textPtr) /* Information about text widget. */ { - XEvent event; + union {XEvent general; XVirtualEvent virtual;} event; Tk_MakeWindowExist(textPtr->tkwin); memset(&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("Modified"); - Tk_HandleEvent(&event); + event.general.xany.type = VirtualEvent; + event.general.xany.serial = NextRequest(Tk_Display(textPtr->tkwin)); + event.general.xany.send_event = False; + event.general.xany.window = Tk_WindowId(textPtr->tkwin); + event.general.xany.display = Tk_Display(textPtr->tkwin); + event.virtual.name = Tk_GetUid("Modified"); + Tk_HandleEvent(&event.general); } /* - * updateDirtyFlag -- - * updates the dirtyness of the text widget + *---------------------------------------------------------------------- + * + * UpdateDirtyFlag -- + * + * Updates the dirtyness of the text widget * * Results: - * None + * None * * Side effects: - * None. + * None. + * + *---------------------------------------------------------------------- */ static void -updateDirtyFlag(textPtr) - TkText *textPtr; /* Information about text widget. */ +UpdateDirtyFlag( + TkSharedText *sharedTextPtr)/* Information about text widget. */ { int oldDirtyFlag; + TkText *textPtr; + + /* + * If we've been forced to be dirty, we stay dirty (until explicitly + * reset, of course). + */ + + if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) { + return; + } + + if (sharedTextPtr->isDirty < 0 + && sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_NORMAL) { + /* + * If dirty flag is negative, only redo operations can make it zero + * again. If we do a normal operation, it can never become zero any + * more (other than by explicit reset). + */ - if (textPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) { + sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED; return; } + oldDirtyFlag = sharedTextPtr->isDirty; + if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) { + sharedTextPtr->isDirty--; + } else { + sharedTextPtr->isDirty++; + } + + if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) { + for (textPtr = sharedTextPtr->peers; textPtr != NULL; + textPtr = textPtr->next) { + GenerateModifiedEvent(textPtr); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * SearchPerform -- + * + * Overall control of search process. Is given a pattern, a starting + * index and an ending index, and attempts to perform a search. This + * function is actually completely independent of Tk, and could in the + * future be split off. + * + * Results: + * Standard Tcl result code. In particular, if fromPtr or toPtr are not + * considered valid by the 'lineIndexProc', an error will be thrown and + * no search performed. + * + * Side effects: + * See 'SearchCore'. + * + *---------------------------------------------------------------------- + */ + +static int +SearchPerform( + Tcl_Interp *interp, /* For error messages. */ + SearchSpec *searchSpecPtr, /* Search parameters. */ + Tcl_Obj *patObj, /* Contains an exact string or a regexp + * pattern. Must have a refCount > 0. */ + Tcl_Obj *fromPtr, /* Contains information describing the first + * index. */ + Tcl_Obj *toPtr) /* NULL or information describing the last + * index. */ +{ /* - * If dirty flag is negative, only redo operations can make it zero again. - * If we do a normal operation, it can never become zero anymore. + * Find the starting line and starting offset (measured in Unicode chars + * for regexp search, utf-8 bytes for exact search). */ - if (textPtr->isDirty < 0 && textPtr->dirtyMode == TK_TEXT_DIRTY_NORMAL) { - textPtr->dirtyMode = TK_TEXT_DIRTY_FIXED; - return; + + if ((*searchSpecPtr->lineIndexProc)(interp, fromPtr, searchSpecPtr, + &searchSpecPtr->startLine, + &searchSpecPtr->startOffset) != TCL_OK) { + return TCL_ERROR; } - oldDirtyFlag = textPtr->isDirty; + /* + * Find the optional end location, similarly. + */ - switch (textPtr->dirtyMode) { - case TK_TEXT_DIRTY_UNDO: - textPtr->isDirty--; - break; - default: - textPtr->isDirty++; - break; + if (toPtr != NULL) { + const TkTextIndex *indexToPtr, *indexFromPtr; + TkText *textPtr = (TkText *) searchSpecPtr->clientData; + + indexToPtr = TkTextGetIndexFromObj(interp, textPtr, toPtr); + if (indexToPtr == NULL) { + return TCL_ERROR; + } + indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, fromPtr); + + /* + * Check for any empty search range here. It might be better in the + * future to embed that in SearchCore (whose default behaviour is to + * wrap when given a negative search range). + */ + + if (searchSpecPtr->backwards) { + if (TkTextIndexCmp(indexFromPtr, indexToPtr) == -1) { + return TCL_OK; + } + } else { + if (TkTextIndexCmp(indexFromPtr, indexToPtr) == 1) { + return TCL_OK; + } + } + + if ((*searchSpecPtr->lineIndexProc)(interp, toPtr, searchSpecPtr, + &searchSpecPtr->stopLine, + &searchSpecPtr->stopOffset) != TCL_OK) { + return TCL_ERROR; + } + } else { + searchSpecPtr->stopLine = -1; + } + + /* + * Scan through all of the lines of the text circularly, starting 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); +} + +/* + *---------------------------------------------------------------------- + * + * SearchCore -- + * + * The core of the search function. This function is actually completely + * independent of Tk, and could in the future be split off. + * + * The function assumes regexp-based searches operate on Unicode strings, + * and exact searches on utf-8 strings. Therefore the 'foundMatchProc' + * and 'addLineProc' need to be aware of this distinction. + * + * Results: + * Standard Tcl result code. + * + * Side effects: + * Only those of the 'searchSpecPtr->foundMatchProc' which is called + * whenever a match is found. + * + * Note that the way matching across multiple lines is implemented, we + * start afresh with each line we have available, even though we may + * already have examined the contents of that line (and further ones) if + * we were attempting a multi-line match using the previous line. This + * means there may be ways to speed this up a lot by not throwing away + * all the multi-line information one has accumulated. Profiling should + * be done to see where the bottlenecks lie before attempting this, + * however. We would also need to be very careful such optimisation keep + * within the specified search bounds. + * + *---------------------------------------------------------------------- + */ + +static int +SearchCore( + Tcl_Interp *interp, /* For error messages. */ + SearchSpec *searchSpecPtr, /* Search parameters. */ + Tcl_Obj *patObj) /* Contains an exact string or a regexp + * pattern. Must have a refCount > 0. */ +{ + /* + * For exact searches these are utf-8 char* offsets, for regexp searches + * they are Unicode char offsets. + */ + + int firstOffset, lastOffset, matchOffset, matchLength; + int passes; + int lineNum = searchSpecPtr->startLine; + int code = TCL_OK; + Tcl_Obj *theLine; + int alreadySearchOffset = -1; + + const char *pattern = NULL; /* For exact searches only. */ + int firstNewLine = -1; /* For exact searches only. */ + Tcl_RegExp regexp = NULL; /* For regexp 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; + + if (searchSpecPtr->exact) { + /* + * Convert the pattern to lower-case if we're supposed to ignore case. + */ + + if (searchSpecPtr->noCase) { + patObj = Tcl_DuplicateObj(patObj); + + /* + * This can change the length of the string behind the object's + * back, so ensure it is correctly synchronised. + */ + + Tcl_SetObjLength(patObj, Tcl_UtfToLower(Tcl_GetString(patObj))); + } + } else { + /* + * Compile the regular expression. We want '^$' to match after and + * before \n respectively, so use the TCL_REG_NLANCH flag. + */ + + regexp = Tcl_GetRegExpFromObj(interp, patObj, + (searchSpecPtr->noCase ? TCL_REG_NOCASE : 0) + | (searchSpecPtr->noLineStop ? 0 : TCL_REG_NLSTOP) + | TCL_REG_ADVANCED | TCL_REG_CANMATCH | TCL_REG_NLANCH); + if (regexp == NULL) { + return TCL_ERROR; + } + } + + /* + * For exact strings, we want to know where the first newline is, and we + * will also use this as a flag to test whether it is even possible to + * match the pattern on a single line. If not we will have to search + * across multiple lines. + */ + + if (searchSpecPtr->exact) { + const char *nl; + + /* + * We only need to set the matchLength once for exact searches, and we + * do it here. It is also used below as the actual pattern length, so + * it has dual purpose. + */ + + pattern = Tcl_GetStringFromObj(patObj, &matchLength); + nl = strchr(pattern, '\n'); + + /* + * If there is no newline, or it is the very end of the string, then + * we don't need any special treatment, since single-line matching + * will work fine. + */ + + if (nl != NULL && nl[1] != '\0') { + firstNewLine = (nl - pattern); + } + } else { + matchLength = 0; /* Only needed to prevent compiler warnings. */ + } + + /* + * Keep a reference here, so that we can be sure the object doesn't + * disappear behind our backs and invalidate its contents which we are + * using. + */ + + Tcl_IncrRefCount(patObj); + + /* + * For building up the current line being checked. + */ + + theLine = Tcl_NewObj(); + Tcl_IncrRefCount(theLine); + + for (passes = 0; passes < 2; ) { + ClientData lineInfo; + int linesSearched = 1; + int extraLinesSearched = 0; + + if (lineNum >= searchSpecPtr->numLines) { + /* + * Don't search the dummy last line of the text. + */ + + goto nextLine; + } + + /* + * Extract the text from the line, storing its length in '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 'lastOffset' represents. + */ + + lineInfo = (*searchSpecPtr->addLineProc)(lineNum, searchSpecPtr, + theLine, &lastOffset, &linesSearched); + + if (lineInfo == NULL) { + /* + * This should not happen, since 'lineNum' should be valid in the + * call above. However, let's try to be flexible and not cause a + * crash below. + */ + + goto nextLine; + } + + 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 we check + * one part of the line, and the second pass through we check the + * other part of the line. + */ + + passes++; + if ((passes == 1) ^ searchSpecPtr->backwards) { + /* + * Forward search and first pass, or backward search and + * second pass. + * + * Only use the last part of the line. + */ + + if (searchSpecPtr->startOffset > firstOffset) { + firstOffset = searchSpecPtr->startOffset; + } + if ((firstOffset >= lastOffset) + && ((lastOffset != 0) || searchSpecPtr->exact)) { + goto nextLine; + } + } else { + /* + * Use only the first part of the line. + */ + + if (searchSpecPtr->startOffset < lastOffset) { + lastOffset = searchSpecPtr->startOffset; + } + } + } + + /* + * Check for matches within the current line 'lineNum'. If so, and if + * we're searching backwards or for all matches, repeat the search + * until we find the last match in the line. The 'lastOffset' is one + * beyond the last position in the line at which a match is allowed to + * begin. + */ + + matchOffset = -1; + + if (searchSpecPtr->exact) { + int maxExtraLines = 0; + const char *startOfLine = Tcl_GetString(theLine); + + do { + Tcl_UniChar ch; + const char *p; + int lastFullLine = lastOffset; + + 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, + (unsigned)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. The + * following 'while' block handles multi-line exact + * searches. + */ + + while (1) { + lastFullLine = lastTotal; + + if (lineNum+extraLines>=searchSpecPtr->numLines) { + p = NULL; + break; + } + + /* + * Only add the line if we haven't already done so + * already. + */ + + if (extraLines > maxExtraLines) { + if ((*searchSpecPtr->addLineProc)(lineNum + + extraLines, searchSpecPtr, theLine, + &lastTotal, &extraLines) == NULL) { + p = NULL; + if (!searchSpecPtr->backwards) { + extraLinesSearched = extraLines; + } + break; + } + maxExtraLines = extraLines; + } + + startOfLine = Tcl_GetString(theLine); + p = startOfLine + skipFirst; + + /* + * Use the fact that 'matchLength = patLength' for + * exact searches. + */ + + if ((lastTotal - skipFirst) >= matchLength) { + /* + * We now have enough text to match, so we + * make a final test and break whatever the + * result. + */ + + if (strncmp(p,pattern,(unsigned)matchLength)) { + p = NULL; + } + break; + } else { + /* + * Not enough text yet, but check the prefix. + */ + + if (strncmp(p, pattern, + (unsigned)(lastTotal - skipFirst))) { + p = NULL; + break; + } + + /* + * The prefix matches, so keep looking. + */ + } + extraLines++; + } + /* + * If we reach here, with p != NULL, we've found a + * multi-line match, else we started a multi-match but + * didn't finish it off, so we go to the next line. + */ + + if (p == NULL) { + break; + } + + /* + * We've found a multi-line match. + */ + + if (extraLines > 0) { + extraLinesSearched = extraLines - 1; + } + } + } + + backwardsMatch: + if ((p - startOfLine) >= lastOffset) { + break; + } + + /* + * Remember the match. + */ + + matchOffset = p - startOfLine; + + if (searchSpecPtr->all && + !(*searchSpecPtr->foundMatchProc)(lineNum, + searchSpecPtr, lineInfo, theLine, matchOffset, + matchLength)) { + /* + * We reached the end of the search. + */ + + goto searchDone; + } + + 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) { + code = TCL_ERROR; + goto searchDone; + } + Tcl_RegExpGetInfo(regexp, &info); + + /* + * 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-firstOffset))) { + int extraLines = 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 regexp + * searches. + */ + + while (1) { + prevFullLine = lastTotal; + + /* + * Move firstOffset to first possible start. + */ + + if (!match) { + firstOffset += info.extendStart; + } + if (firstOffset >= lastOffset) { + /* + * We're being told that the only possible new + * match is starting after the end of the line. + * But, that is the next line which we will handle + * when we look at that line. + */ + + if (!match && !searchSpecPtr->backwards + && (firstOffset == 0)) { + extraLinesSearched = extraLines; + } + break; + } + + if (lineNum + extraLines >= searchSpecPtr->numLines) { + break; + } + + /* + * Add next line, provided we haven't already done so. + */ + + if (extraLines > maxExtraLines) { + if ((*searchSpecPtr->addLineProc)(lineNum + + extraLines, searchSpecPtr, theLine, + &lastTotal, &extraLines) == NULL) { + /* + * There are no more acceptable lines, so we + * can say we have searched all of these. + */ + + if (!match && !searchSpecPtr->backwards) { + extraLinesSearched = extraLines; + } + break; + } + + maxExtraLines = extraLines; + if ((lastBackwardsLineMatch != -1) + && (lastBackwardsLineMatch + == (lineNum + extraLines + 1))) { + lastNonOverlap = lastTotal; + } + } + + match = Tcl_RegExpExecObj(interp, regexp, theLine, + firstOffset, 1, + ((firstOffset > 0) ? TCL_REG_NOTBOL : 0)); + if (match < 0) { + code = TCL_ERROR; + goto searchDone; + } + Tcl_RegExpGetInfo(regexp, &info); + + /* + * 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 there is a match, but that match starts after + * the end of the first line, then we'll handle that + * next time around, when we're actually looking at + * that line. + */ + + if (match && (info.matches[0].start >= lastOffset)) { + break; + } + if (match && ((firstOffset + info.matches[0].end) + >= prevFullLine)) { + if (extraLines > 0) { + extraLinesSearched = extraLines - 1; + } + lastFullLine = prevFullLine; + } + + /* + * The prefix matches, so keep looking. + */ + + extraLines++; + } + + /* + * 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. + */ + + if (!match) { + /* + * 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 + extraLinesSearched) + == 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+extraLinesSearched + < 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; + } + + /* + * 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. + */ + + 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 = (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 on, in case we are doing repeated + * or backwards searches (for the latter, we actually do + * repeated forward searches). + */ + + firstOffset++; + } 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. + */ + + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, + lineInfo, theLine, matchOffset, matchLength); + } else { + lastBackwardsLineMatch = lineNum; + lastBackwardsMatchOffset = matchOffset; + } + } + } + + /* + * If the 'all' flag is set, we will already have stored all matches, + * so we just proceed to the next line. + * + * If not, and there is a match we need to store that information and + * we are done. + */ + + if ((lastBackwardsLineMatch == -1) && (matchOffset >= 0) + && !searchSpecPtr->all) { + (*searchSpecPtr->foundMatchProc)(lineNum, searchSpecPtr, lineInfo, + theLine, matchOffset, matchLength); + goto searchDone; + } + + /* + * Go to the next (or previous) line; + */ + + nextLine: + linesSearched += extraLinesSearched; + + while (linesSearched-- > 0) { + /* + * If we have just completed the 'stopLine', we are done. + */ + + if (lineNum == searchSpecPtr->stopLine) { + goto searchDone; + } + + 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 (textPtr->isDirty == 0 || oldDirtyFlag == 0) { - GenerateModifiedEvent(textPtr); + 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; } + +/* + *---------------------------------------------------------------------- + * + * GetLineStartEnd - + * + * Converts an internal TkTextLine ptr into a Tcl string obj containing + * the line number. (Handler for the 'line' configuration option type.) + * + * Results: + * Tcl_Obj containing the string representation of the line value. + * + * Side effects: + * Creates a new Tcl_Obj. + * + *---------------------------------------------------------------------- + */ + +static Tcl_Obj * +GetLineStartEnd( + ClientData clientData, + Tk_Window tkwin, + char *recordPtr, /* Pointer to widget record. */ + int internalOffset) /* Offset within *recordPtr containing the + * line value. */ +{ + TkTextLine *linePtr = *(TkTextLine **)(recordPtr + internalOffset); + + if (linePtr == NULL) { + return Tcl_NewObj(); + } else { + return Tcl_NewIntObj(1+TkBTreeLinesTo(NULL, linePtr)); + } +} + +/* + *---------------------------------------------------------------------- + * + * SetLineStartEnd -- + * + * Converts a Tcl_Obj representing a widget's (start or end) line into a + * TkTextLine* value. (Handler for the 'line' configuration option type.) + * + * Results: + * Standard Tcl result. + * + * Side effects: + * May store the TkTextLine* value into the internal representation + * pointer. May change the pointer to the Tcl_Obj to NULL to indicate + * that the specified string was empty and that is acceptable. + * + *---------------------------------------------------------------------- + */ + +static int +SetLineStartEnd( + ClientData clientData, + Tcl_Interp *interp, /* Current interp; may be used for errors. */ + Tk_Window tkwin, /* Window for which option is being set. */ + Tcl_Obj **value, /* Pointer to the pointer to the value object. + * We use a pointer to the pointer because we + * may need to return a value (NULL). */ + char *recordPtr, /* Pointer to storage for the widget record. */ + int internalOffset, /* Offset within *recordPtr at which the + * internal value is to be stored. */ + char *oldInternalPtr, /* Pointer to storage for the old value. */ + int flags) /* Flags for the option, set Tk_SetOptions. */ +{ + TkTextLine *linePtr = NULL; + char *internalPtr; + TkText *textPtr = (TkText *) recordPtr; + + if (internalOffset >= 0) { + internalPtr = recordPtr + internalOffset; + } else { + internalPtr = NULL; + } + + if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) { + *value = NULL; + } else { + int line; + + if (Tcl_GetIntFromObj(interp, *value, &line) != TCL_OK) { + return TCL_ERROR; + } + linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line-1); + } + + if (internalPtr != NULL) { + *((TkTextLine **) oldInternalPtr) = *((TkTextLine **) internalPtr); + *((TkTextLine **) internalPtr) = linePtr; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * RestoreLineStartEnd -- + * + * Restore a line option value from a saved value. (Handler for the + * 'line' configuration option type.) + * + * Results: + * None. + * + * Side effects: + * Restores the old value. + * + *---------------------------------------------------------------------- + */ + +static void +RestoreLineStartEnd( + ClientData clientData, + Tk_Window tkwin, + char *internalPtr, /* Pointer to storage for value. */ + char *oldInternalPtr) /* Pointer to old value. */ +{ + *(TkTextLine **)internalPtr = *(TkTextLine **)oldInternalPtr; +} + +/* + *---------------------------------------------------------------------- + * + * ObjectIsEmpty -- + * + * This function tests whether the string value of an object is empty. + * + * Results: + * The return value is 1 if the string value of objPtr has length zero, + * and 0 otherwise. + * + * Side effects: + * May cause object shimmering, since this function can force a + * conversion to a string object. + * + *---------------------------------------------------------------------- + */ + +static int +ObjectIsEmpty( + Tcl_Obj *objPtr) /* Object to test. May be NULL. */ +{ + int length; + + if (objPtr == NULL) { + return 1; + } + if (objPtr->bytes != NULL) { + return (objPtr->length == 0); + } + Tcl_GetStringFromObj(objPtr, &length); + return (length == 0); +} + +/* + *---------------------------------------------------------------------- + * + * TkpTesttextCmd -- + * + * This function implements the "testtext" command. It provides a set of + * functions for testing text widgets and the associated functions in + * tkText*.c. + * + * Results: + * A standard Tcl result. + * + * Side effects: + * Depends on option; see below. + * + *---------------------------------------------------------------------- + */ + +int +TkpTesttextCmd( + ClientData clientData, /* Main window for application. */ + Tcl_Interp *interp, /* Current interpreter. */ + int argc, /* Number of arguments. */ + const char **argv) /* Argument strings. */ +{ + TkText *textPtr; + size_t len; + int lineIndex, byteIndex, byteOffset; + TkTextIndex index; + char buf[64]; + Tcl_CmdInfo info; + + if (argc < 3) { + return TCL_ERROR; + } + + if (Tcl_GetCommandInfo(interp, argv[1], &info) == 0) { + return TCL_ERROR; + } + if (info.isNativeObjectProc) { + textPtr = (TkText *) info.objClientData; + } else { + textPtr = (TkText *) info.clientData; + } + len = strlen(argv[2]); + if (strncmp(argv[2], "byteindex", len) == 0) { + if (argc != 5) { + return TCL_ERROR; + } + lineIndex = atoi(argv[3]) - 1; + byteIndex = atoi(argv[4]); + + TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex, + byteIndex, &index); + } else if (strncmp(argv[2], "forwbytes", len) == 0) { + if (argc != 5) { + return TCL_ERROR; + } + if (TkTextGetIndex(interp, textPtr, argv[3], &index) != TCL_OK) { + return TCL_ERROR; + } + byteOffset = atoi(argv[4]); + TkTextIndexForwBytes(textPtr, &index, byteOffset, &index); + } else if (strncmp(argv[2], "backbytes", len) == 0) { + if (argc != 5) { + return TCL_ERROR; + } + if (TkTextGetIndex(interp, textPtr, argv[3], &index) != TCL_OK) { + return TCL_ERROR; + } + byteOffset = atoi(argv[4]); + TkTextIndexBackBytes(textPtr, &index, byteOffset, &index); + } else { + return TCL_ERROR; + } + + TkTextSetMark(textPtr, "insert", &index); + TkTextPrintIndex(textPtr, &index, buf); + sprintf(buf + strlen(buf), " %d", index.byteIndex); + Tcl_AppendResult(interp, buf, NULL); + + return TCL_OK; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |