summaryrefslogtreecommitdiffstats
path: root/generic/tkText.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tkText.c')
-rw-r--r--generic/tkText.c6918
1 files changed, 5256 insertions, 1662 deletions
diff --git a/generic/tkText.c b/generic/tkText.c
index 54f148d..6156dd4 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -1,347 +1,431 @@
-/*
+/*
* 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"
-#if defined(MAC_TCL) || defined(MAC_OSX_TK)
+#if defined(MAC_OSX_TK)
#define Style TkStyle
#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 *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 *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 *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 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 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];
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:
+ */