summaryrefslogtreecommitdiffstats
path: root/generic/tkText.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tkText.c')
-rw-r--r--generic/tkText.c10901
1 files changed, 7982 insertions, 2919 deletions
diff --git a/generic/tkText.c b/generic/tkText.c
index ba1e9ab..d215fa6 100644
--- a/generic/tkText.c
+++ b/generic/tkText.c
@@ -9,6 +9,7 @@
* Copyright (c) 1992-1994 The Regents of the University of California.
* Copyright (c) 1994-1996 Sun Microsystems, Inc.
* Copyright (c) 1999 by Scriptics Corporation.
+ * Copyright (c) 2015-2017 Gregor Cramer
*
* See the file "license.terms" for information on usage and redistribution of
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
@@ -16,11 +17,71 @@
#include "default.h"
#include "tkInt.h"
-#include "tkUndo.h"
+#include "tkText.h"
+#include "tkTextUndo.h"
+#include "tkTextTagSet.h"
+#include "tkBitField.h"
+#if TCL_MAJOR_VERSION > 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 7)
+#include "tkFont.h"
+#endif
+#include "tkAlloc.h"
+#include <stdlib.h>
+#include <assert.h>
+
+/* needed for strncasecmp */
+#if defined(_WIN32) && !defined(__GNUC__)
+# define strncasecmp _strnicmp
+#else
+# include <strings.h>
+#endif
+
+#ifndef TK_C99_INLINE_SUPPORT
+# define _TK_NEED_IMPLEMENTATION
+# include "tkTextPriv.h"
+#endif
-#if defined(MAC_OSX_TK)
-#define Style TkStyle
-#define DInfo TkDInfo
+#if defined(_MSC_VER ) && _MSC_VER < 1500
+/* suppress wrong warnings to support ancient compilers */
+# pragma warning (disable : 4305)
+#endif
+
+#ifndef MAX
+# define MAX(a,b) ((a) < (b) ? b : a)
+#endif
+#ifndef MIN
+# define MIN(a,b) ((a) < (b) ? a : b)
+#endif
+
+#ifdef NDEBUG
+# define DEBUG(expr)
+#else
+# define DEBUG(expr) expr
+#endif
+
+/*
+ * Support of tk8.5.
+ */
+#ifdef CONST
+# undef CONST
+#endif
+#if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION == 5
+# define CONST
+#else
+# define CONST const
+#endif
+
+/*
+ * Support of tk8.6/8.5.
+ */
+#ifndef DEF_TEXT_INACTIVE_SELECT_FG_COLOR
+# if defined(MAC_OSX_TK)
+# define DEF_TEXT_INACTIVE_SELECT_FG_COLOR "systemDialogActiveText"
+# elif defined(_WIN32)
+# define DEF_TEXT_INACTIVE_SELECT_FG_COLOR NULL
+# else /* X11 */
+# define DEF_TEXT_INACTIVE_SELECT_FG_COLOR BLACK
+# endif
+# define DEF_TEXT_INACTIVE_SELECT_BG_COLOR DEF_TEXT_INACTIVE_SELECT_COLOR
#endif
/*
@@ -29,18 +90,16 @@
* which are the equivalent of at least a character width apart.
*/
-#if (TK_MAJOR_VERSION < 9)
-#define _TK_ALLOW_DECREASING_TABS
+#if TK_MAJOR_VERSION < 9
+# define _TK_ALLOW_DECREASING_TABS
#endif
-#include "tkText.h"
-
/*
* Used to avoid having to allocate and deallocate arrays on the fly for
* commonly used functions. Must be > 0.
*/
-#define PIXEL_CLIENTS 5
+#define PIXEL_CLIENTS 8
/*
* The 'TkTextState' enum in tkText.h is used to define a type for the -state
@@ -48,8 +107,26 @@
* table below.
*/
-static const char *const stateStrings[] = {
- "disabled", "normal", NULL
+static const char *CONST stateStrings[] = {
+ "disabled", "normal", "readonly", NULL
+};
+
+/*
+ * The 'TkTextTagging' enum in tkText.h is used to define a type for the -tagging
+ * option of the Text widget. These values are used as indices into the string table below.
+ */
+
+static const char *CONST taggingStrings[] = {
+ "within", "gravity", "none", NULL
+};
+
+/*
+ * The 'TkTextJustify' enum in tkText.h is used to define a type for the -justify option of
+ * the Text widget. These values are used as indices into the string table below.
+ */
+
+static const char *CONST justifyStrings[] = {
+ "left", "right", "full", "center", NULL
};
/*
@@ -58,8 +135,18 @@ static const char *const stateStrings[] = {
* table below.
*/
-static const char *const wrapStrings[] = {
- "char", "none", "word", NULL
+static const char *CONST wrapStrings[] = {
+ "char", "none", "word", "codepoint", NULL
+};
+
+/*
+ * The 'TkSpacing' enum in tkText.h is used to define a type for the -spacing
+ * option of the Text widget. These values are used as indices into the string
+ * table below.
+ */
+
+static const char *CONST spaceModeStrings[] = {
+ "none", "exact", "trim", NULL
};
/*
@@ -68,7 +155,7 @@ static const char *const wrapStrings[] = {
* the string table below.
*/
-static const char *const tabStyleStrings[] = {
+static const char *CONST tabStyleStrings[] = {
"tabular", "wordprocessor", NULL
};
@@ -78,11 +165,27 @@ static const char *const tabStyleStrings[] = {
* indice into the string table below.
*/
-static const char *const insertUnfocussedStrings[] = {
+static const char *CONST insertUnfocussedStrings[] = {
"hollow", "none", "solid", NULL
};
/*
+ * The 'TkTextHyphenRule' enum in tkText.h is used to define a type for the
+ * -hyphenrules option of the Text widget. These values are used for applying
+ * hyphen rules to soft hyphens.
+ *
+ * NOTE: Don't forget to update function ParseHyphens() if this array will be
+ * modified.
+ */
+
+static const char *const hyphenRuleStrings[] = {
+ "ck", "doubledigraph", "doublevowel", "gemination", "repeathyphen", "trema",
+ "tripleconsonant" /* don't append a trailing NULL */
+};
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+/*
* 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.
@@ -93,20 +196,15 @@ static const char *const insertUnfocussedStrings[] = {
* freeing.
*/
-static int SetLineStartEnd(ClientData clientData,
- Tcl_Interp *interp, Tk_Window tkwin,
- Tcl_Obj **value, char *recordPtr,
- int internalOffset, char *oldInternalPtr,
+static int SetLineStartEnd(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
+ Tcl_Obj **value, char *recordPtr, int internalOffset, char *oldInternalPtr,
int flags);
-static Tcl_Obj * GetLineStartEnd(ClientData clientData,
- Tk_Window tkwin, char *recordPtr,
+static Tcl_Obj * GetLineStartEnd(ClientData clientData, Tk_Window tkwin, char *recordPtr,
int internalOffset);
-static void RestoreLineStartEnd(ClientData clientData,
- Tk_Window tkwin, char *internalPtr,
+static void RestoreLineStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr,
char *oldInternalPtr);
-static int ObjectIsEmpty(Tcl_Obj *objPtr);
-static const Tk_ObjCustomOption lineOption = {
+static CONST Tk_ObjCustomOption lineOption = {
"line", /* name */
SetLineStartEnd, /* setProc */
GetLineStartEnd, /* getProc */
@@ -115,146 +213,186 @@ static const Tk_ObjCustomOption lineOption = {
0
};
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+/*
+ * The following functions and custom option type are used to define the
+ * "index" option type, and thereby handle the text widget '-startindex',
+ * '-endindex' configuration options which are of that type.
+ */
+
+static int SetTextStartEnd(ClientData clientData, Tcl_Interp *interp, Tk_Window tkwin,
+ Tcl_Obj **value, char *recordPtr, int internalOffset, char *oldInternalPtr,
+ int flags);
+static Tcl_Obj * GetTextStartEnd(ClientData clientData, Tk_Window tkwin, char *recordPtr,
+ int internalOffset);
+static void RestoreTextStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr,
+ char *oldInternalPtr);
+static void FreeTextStartEnd(ClientData clientData, Tk_Window tkwin, char *internalPtr);
+
+static CONST Tk_ObjCustomOption startEndMarkOption = {
+ "index", /* name */
+ SetTextStartEnd, /* setProc */
+ GetTextStartEnd, /* getProc */
+ RestoreTextStartEnd, /* restoreProc */
+ FreeTextStartEnd, /* freeProc */
+ 0
+};
+
/*
* Information used to parse text configuration options:
*/
static const Tk_OptionSpec optionSpecs[] = {
{TK_OPTION_BOOLEAN, "-autoseparators", "autoSeparators",
- "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, -1,
- Tk_Offset(TkText, autoSeparators),
+ "AutoSeparators", DEF_TEXT_AUTO_SEPARATORS, -1, Tk_Offset(TkText, autoSeparators),
TK_OPTION_DONT_SET_DEFAULT, 0, 0},
{TK_OPTION_BORDER, "-background", "background", "Background",
- DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border),
- 0, DEF_TEXT_BG_MONO, 0},
+ DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, border), 0, DEF_TEXT_BG_MONO, TK_TEXT_LINE_REDRAW},
{TK_OPTION_SYNONYM, "-bd", NULL, NULL,
- NULL, 0, -1, 0, "-borderwidth",
- TK_TEXT_LINE_GEOMETRY},
+ NULL, 0, -1, 0, "-borderwidth", TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_SYNONYM, "-bg", NULL, NULL,
- NULL, 0, -1, 0, "-background", 0},
+ NULL, 0, -1, 0, "-background", TK_TEXT_LINE_REDRAW},
{TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor",
- "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1,
- Tk_Offset(TkText, insertCursorType), 0, 0, 0},
+ "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1, Tk_Offset(TkText, blockCursorType), 0, 0, 0},
{TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
- DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth),
- 0, 0, TK_TEXT_LINE_GEOMETRY},
+ 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},
+ DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor), TK_OPTION_NULL_OK, 0, 0},
+ {TK_OPTION_CUSTOM, "-endindex", NULL, NULL,
+ NULL, -1, Tk_Offset(TkText, newEndIndex), TK_OPTION_NULL_OK, &startEndMarkOption, TK_TEXT_INDEX_RANGE},
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
{TK_OPTION_CUSTOM, "-endline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, end), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
+ NULL, -1, Tk_Offset(TkText, endLine), TK_OPTION_NULL_OK, &lineOption, TK_TEXT_LINE_RANGE},
+#endif
+ {TK_OPTION_STRING, "-eolchar", "eolChar", "EolChar",
+ NULL, Tk_Offset(TkText, eolCharPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_COLOR, "-eolcolor", "eolColor", "EolColor",
+ NULL, -1, Tk_Offset(TkText, eolColor), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_REDRAW},
+ {TK_OPTION_STRING, "-eotchar", "eotChar", "EotChar",
+ NULL, Tk_Offset(TkText, eotCharPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_COLOR, "-eotcolor", "eotColor", "EotColor",
+ NULL, -1, Tk_Offset(TkText, eotColor), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_REDRAW},
{TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
- "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1,
- Tk_Offset(TkText, exportSelection), 0, 0, 0},
+ "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1, Tk_Offset(TkText, exportSelection), 0, 0, 0},
{TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
- NULL, 0, -1, 0, "-foreground", 0},
+ NULL, 0, -1, 0, "-foreground", TK_TEXT_LINE_REDRAW},
{TK_OPTION_FONT, "-font", "font", "Font",
- DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ 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},
+ DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0, 0, TK_TEXT_LINE_REDRAW},
{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, "-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),
+ 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_STRING, "-hyphenrules", NULL, NULL,
+ NULL, Tk_Offset(TkText, hyphenRulesPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_COLOR, "-hyphencolor", "hyphenColor", "HyphenColor",
+ DEF_TEXT_FG, -1, Tk_Offset(TkText, hyphenColor), TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_REDRAW},
+ {TK_OPTION_BOOLEAN, "-hyphens", "hyphens", "Hyphens",
+ "0", -1, Tk_Offset(TkText, useHyphenSupport), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_BORDER, "-inactiveselectbackground", "inactiveSelectBackground", "Foreground",
+ DEF_TEXT_INACTIVE_SELECT_BG_COLOR, -1, Tk_Offset(TkText, selAttrs.inactiveBorder),
TK_OPTION_NULL_OK, DEF_TEXT_SELECT_MONO, 0},
+ {TK_OPTION_COLOR, "-inactiveselectforeground", "inactiveSelectForeground", "Background",
+ DEF_TEXT_INACTIVE_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selAttrs.inactiveFgColor),
+ TK_OPTION_NULL_OK, DEF_TEXT_SELECT_FG_MONO, 0},
{TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
- DEF_TEXT_INSERT_BG,
- -1, Tk_Offset(TkText, insertBorder),
- 0, 0, 0},
+ 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,
+ "BorderWidth", DEF_TEXT_INSERT_BD_COLOR, -1, Tk_Offset(TkText, insertBorderWidth), 0,
(ClientData) DEF_TEXT_INSERT_BD_MONO, 0},
+ {TK_OPTION_COLOR, "-insertforeground", "insertForeground", "InsertForeground",
+ DEF_TEXT_BG_COLOR, -1, Tk_Offset(TkText, insertFgColor), 0, 0, 0},
{TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
- DEF_TEXT_INSERT_OFF_TIME, -1, Tk_Offset(TkText, insertOffTime),
- 0, 0, 0},
+ 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},
+ DEF_TEXT_INSERT_ON_TIME, -1, Tk_Offset(TkText, insertOnTime), 0, 0, 0},
{TK_OPTION_STRING_TABLE,
"-insertunfocussed", "insertUnfocussed", "InsertUnfocussed",
DEF_TEXT_INSERT_UNFOCUSSED, -1, Tk_Offset(TkText, insertUnfocussed),
0, insertUnfocussedStrings, 0},
{TK_OPTION_PIXELS, "-insertwidth", "insertWidth", "InsertWidth",
- DEF_TEXT_INSERT_WIDTH, -1, Tk_Offset(TkText, insertWidth),
- 0, 0, 0},
+ DEF_TEXT_INSERT_WIDTH, -1, Tk_Offset(TkText, insertWidth), 0, 0, 0},
+ {TK_OPTION_STRING_TABLE, "-justify", "justify", "Justify",
+ "left", -1, Tk_Offset(TkText, justify), 0, justifyStrings, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_STRING, "-lang", "lang", "Lang",
+ NULL, Tk_Offset(TkText, langPtr), -1, TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo",
- DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo),
- TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndoDepth), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-maxundosize", "maxUndoSize", "MaxUndoSize",
+ DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndoSize), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-maxredo", "maxRedo", "MaxRedo",
+ "-1", -1, Tk_Offset(TkText, maxRedoDepth), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
{TK_OPTION_PIXELS, "-padx", "padX", "Pad",
- DEF_TEXT_PADX, -1, Tk_Offset(TkText, padX), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ 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_INT, "-responsiveness", "responsiveness", "Responsiveness",
+ "50", -1, Tk_Offset(TkText, responsiveness), 0, 0, 0},
{TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
- DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selBorder),
+ DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selAttrs.border),
0, 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, DEF_TEXT_SELECT_BD_MONO, 0},
+ {TK_OPTION_PIXELS, "-selectborderwidth", "selectBorderWidth", "BorderWidth",
+ DEF_TEXT_SELECT_BD_COLOR, Tk_Offset(TkText, selAttrs.borderWidthPtr),
+ Tk_Offset(TkText, selAttrs.borderWidth), TK_OPTION_NULL_OK, DEF_TEXT_SELECT_BD_MONO, 0},
{TK_OPTION_COLOR, "-selectforeground", "selectForeground", "Background",
- DEF_TEXT_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selFgColorPtr),
+ DEF_TEXT_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selAttrs.fgColor),
TK_OPTION_NULL_OK, 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_BOOLEAN, "-showendofline", "showEndOfLine", "ShowEndOfLine",
+ "0", -1, Tk_Offset(TkText, showEndOfLine), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_BOOLEAN, "-showendoftext", "showEndOfText", "ShowEndOfText",
+ "0", -1, Tk_Offset(TkText, showEndOfText), 0, 0, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_BOOLEAN, "-showinsertforeground", "showInsertForeground", "ShowInsertForeground",
+ "0", -1, Tk_Offset(TkText, showInsertFgColor), 0, 0, 0},
+ {TK_OPTION_STRING_TABLE, "-spacemode", "spaceMode", "SpaceMode",
+ "none", -1, Tk_Offset(TkText, spaceMode), 0, spaceModeStrings, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing1", "spacing1", "Spacing",
- DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING1, -1, Tk_Offset(TkText, spacing1), 0, 0 , TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing2", "spacing2", "Spacing",
- DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING2, -1, Tk_Offset(TkText, spacing2), 0, 0 , TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing",
- DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
+ DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3), 0, 0 , TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_CUSTOM, "-startindex", NULL, NULL,
+ NULL, -1, Tk_Offset(TkText, newStartIndex), TK_OPTION_NULL_OK, &startEndMarkOption, TK_TEXT_INDEX_RANGE},
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
{TK_OPTION_CUSTOM, "-startline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, start), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
+ NULL, -1, Tk_Offset(TkText, startLine), TK_OPTION_NULL_OK, &lineOption, TK_TEXT_LINE_RANGE},
+#endif
{TK_OPTION_STRING_TABLE, "-state", "state", "State",
- DEF_TEXT_STATE, -1, Tk_Offset(TkText, state),
- 0, stateStrings, 0},
+ DEF_TEXT_STATE, -1, Tk_Offset(TkText, state), 0, stateStrings, 0},
+ {TK_OPTION_BOOLEAN, "-steadymarks", "steadyMarks", "SteadyMarks",
+ "0", -1, Tk_Offset(TkText, steadyMarks), TK_OPTION_DONT_SET_DEFAULT, 0, 0},
+ {TK_OPTION_INT, "-synctime", "syncTime", "SyncTime", "150", -1, Tk_Offset(TkText, syncTime),
+ 0, 0, TK_TEXT_SYNCHRONIZE},
{TK_OPTION_STRING, "-tabs", "tabs", "Tabs",
- DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1,
- TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
+ 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, tabStyleStrings, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_TABSTYLE, -1, Tk_Offset(TkText, tabStyle), 0, tabStyleStrings, TK_TEXT_LINE_GEOMETRY},
+ {TK_OPTION_STRING_TABLE, "-tagging", "tagging", "Tagging",
+ "within", -1, Tk_Offset(TkText, tagging), 0, taggingStrings, 0},
{TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
- DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus),
- TK_OPTION_NULL_OK, 0, 0},
+ 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),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0},
+ DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo), TK_OPTION_DONT_SET_DEFAULT, 0 ,0},
+ {TK_OPTION_BOOLEAN, "-useunibreak", "useUniBreak", "UseUniBreak",
+ "0", -1, Tk_Offset(TkText, useUniBreak), 0, 0, TK_TEXT_LINE_GEOMETRY},
{TK_OPTION_INT, "-width", "width", "Width",
- DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
+ 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, wrapStrings, TK_TEXT_LINE_GEOMETRY},
+ DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode), 0, 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},
+ 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},
+ 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}
};
@@ -272,55 +410,43 @@ static const Tk_OptionSpec optionSpecs[] = {
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 ClientData SearchAddLineProc(int lineNum, struct SearchSpec *searchSpecPtr,
+ Tcl_Obj *theLine, int *lenPtr, int *extraLinesPtr);
+typedef bool 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. */
+ TkText *textPtr; /* Information about widget. */
+ bool exact; /* Whether search is exact or regexp. */
+ bool noCase; /* Case-insenstivive? */
+ bool noLineStop; /* If not set, a regexp search will use the TCL_REG_NLSTOP flag. */
+ bool overlap; /* If set, results from multiple searches (-all) are allowed to
+ * overlap each other. */
+ bool strictLimits; /* If set, matches must be completely inside the from,to range.
+ * Otherwise the limits only apply to the start of each match. */
+ bool all; /* Whether all or the first match should be reported. */
+ bool backwards; /* Searching forwards or backwards. */
+ bool searchElide; /* Search in hidden text as well. */
+ bool searchHyphens; /* Search in soft hyhens as well. */
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 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 *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 */
+ /* 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. */
+ /* 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. */
+ /* Function to call when we have found a match. */
+ ClientData clientData; /* Information about structure being searched, in this case a text
+ * widget. */
} SearchSpec;
/*
@@ -328,98 +454,160 @@ typedef struct SearchSpec {
* handling both regexp and exact searches.
*/
-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);
+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);
+
+/*
+ * We need a simple linked list for strings:
+ */
+
+typedef struct TkTextStringList {
+ struct TkTextStringList *nextPtr;
+ Tcl_Obj *strObjPtr;
+} TkTextStringList;
/*
- * Boolean variable indicating whether or not special debugging code should be
- * executed.
+ * Boolean variable indicating whether or not special debugging code should be executed.
*/
-int tkTextDebug = 0;
+int tkTextDebug = false;
+
+typedef const TkTextUndoAtom * (*InspectUndoStackProc)(TkTextUndoStack stack);
/*
* Forward declarations for functions defined later in this file:
*/
-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 bool DeleteIndexRange(TkSharedText *sharedTextPtr, TkText *textPtr,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2, int flags,
+ bool viewUpdate, bool triggerWatchDelete, bool triggerWatchInsert,
+ bool userFlag, bool final);
+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 ClearText(TkText *textPtr, bool clearTags);
+static void FireWidgetViewSyncEvent(ClientData clientData);
+static void FreeEmbeddedWindows(TkText *textPtr);
+static void InsertChars(TkText *textPtr, TkTextIndex *index1Ptr, TkTextIndex *index2Ptr,
+ char const *string, unsigned length, bool viewUpdate,
+ TkTextTagSet *tagInfoPtr, TkTextTag *hyphenTagPtr, bool parseHyphens);
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 CreateWidget(TkSharedText *sharedTextPtr, Tk_Window tkwin, Tcl_Interp *interp,
+ const TkText *parent, int objc, Tcl_Obj *const objv[]);
+static void TextEventProc(ClientData clientData, XEvent *eventPtr);
+static void ProcessConfigureNotify(TkText *textPtr, bool updateLineGeometry);
+static int TextFetchSelection(ClientData clientData, int offset, char *buffer,
+ int maxBytes);
+static int TextIndexSortProc(const void *first, const void *second);
+static int TextInsertCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[], const TkTextIndex *indexPtr,
+ bool viewUpdate, bool triggerWatchDelete, bool triggerWatchInsert,
+ bool userFlag, bool parseHyphens);
static int TextReplaceCmd(TkText *textPtr, Tcl_Interp *interp,
- const TkTextIndex *indexFromPtr,
- const TkTextIndex *indexToPtr,
- int objc, Tcl_Obj *const objv[], int viewUpdate);
+ const TkTextIndex *indexFromPtr, const TkTextIndex *indexToPtr,
+ int objc, Tcl_Obj *const objv[], bool viewUpdate, bool triggerWatch,
+ bool userFlag, bool parseHyphens);
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[]);
+ Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]);
static void TextWorldChangedCallback(ClientData instanceData);
static void TextWorldChanged(TkText *textPtr, int mask);
+static void UpdateLineMetrics(TkText *textPtr, unsigned lineNum, unsigned endLine);
+static int TextChecksumCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
static int TextDumpCmd(TkText *textPtr, Tcl_Interp *interp,
int objc, Tcl_Obj *const objv[]);
-static int DumpLine(Tcl_Interp *interp, TkText *textPtr,
+static int TextInspectCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static bool 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 GenerateUndoStackEvent(TkText *textPtr);
-static void UpdateDirtyFlag(TkSharedText *sharedPtr);
+ int lineno, Tcl_Obj *command, TkTextTag **prevTagPtr);
+static bool DumpSegment(TkText *textPtr, Tcl_Interp *interp, const char *key,
+ const char *value, Tcl_Obj *command, const TkTextIndex *index, int what);
+static void InspectUndoStack(const TkSharedText *sharedTextPtr,
+ InspectUndoStackProc firstAtomProc, InspectUndoStackProc nextAtomProc,
+ Tcl_Obj *objPtr);
+static void InspectRetainedUndoItems(const TkSharedText *sharedTextPtr, Tcl_Obj *objPtr);
+static Tcl_Obj * TextGetText(TkText *textPtr, const TkTextIndex *index1,
+ const TkTextIndex *index2, TkTextIndex *lastIndexPtr, Tcl_Obj *resultPtr,
+ unsigned maxBytes, bool visibleOnly, bool includeHyphens);
+static void GenerateEvent(TkSharedText *sharedTextPtr, const char *type);
static void RunAfterSyncCmd(ClientData clientData);
-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 void UpdateModifiedFlag(TkSharedText *sharedTextPtr, bool flag);
+static Tcl_Obj * MakeEditInfo(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *arrayPtr);
+static unsigned 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;
+static int TextWatchCmd(TkText *textPtr, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static bool TriggerWatchEdit(TkText *textPtr, bool userFlag, const char *operation,
+ const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2,
+ const char *info, bool final);
+static void TriggerUndoStackEvent(TkSharedText *sharedTextPtr);
+static void PushRetainedUndoTokens(TkSharedText *sharedTextPtr);
+static void PushUndoSeparatorIfNeeded(TkSharedText *sharedTextPtr, bool autoSeparators,
+ TkTextEditMode currentEditMode);
+static bool IsEmpty(const TkSharedText *sharedTextPtr, const TkText *textPtr);
+static bool IsClean(const TkSharedText *sharedTextPtr, const TkText *textPtr,
+ bool discardSelection);
+static TkTextUndoPerformProc TextUndoRedoCallback;
+static TkTextUndoFreeProc TextUndoFreeCallback;
+static TkTextUndoStackContentChangedProc TextUndoStackContentChangedCallback;
+
+/*
+ * Some definitions for controlling "dump", "inspect", and "checksum".
+ */
+
+enum {
+ TK_DUMP_TEXT = SEG_GROUP_CHAR,
+ TK_DUMP_CHARS = TK_DUMP_TEXT|SEG_GROUP_HYPHEN,
+ TK_DUMP_MARK = SEG_GROUP_MARK,
+ TK_DUMP_ELIDE = SEG_GROUP_BRANCH,
+ TK_DUMP_TAG = SEG_GROUP_TAG,
+ TK_DUMP_WIN = SEG_GROUP_WINDOW,
+ TK_DUMP_IMG = SEG_GROUP_IMAGE,
+ TK_DUMP_NODE = 1 << 18,
+ TK_DUMP_DUMP_ALL = TK_DUMP_TEXT|TK_DUMP_CHARS|TK_DUMP_MARK|TK_DUMP_TAG|
+ TK_DUMP_WIN|TK_DUMP_IMG,
+
+ TK_DUMP_DISPLAY = 1 << 19,
+ TK_DUMP_DISPLAY_CHARS = TK_DUMP_CHARS|TK_DUMP_DISPLAY,
+ TK_DUMP_DISPLAY_TEXT = TK_DUMP_TEXT|TK_DUMP_DISPLAY,
+ TK_DUMP_CRC_DFLT = TK_DUMP_TEXT|SEG_GROUP_WINDOW|SEG_GROUP_IMAGE,
+ TK_DUMP_CRC_ALL = TK_DUMP_TEXT|TK_DUMP_CHARS|TK_DUMP_DISPLAY_TEXT|SEG_GROUP_WINDOW|
+ SEG_GROUP_IMAGE|TK_DUMP_MARK|TK_DUMP_TAG,
+
+ TK_DUMP_NESTED = 1 << 20,
+ TK_DUMP_TEXT_CONFIGS = 1 << 21,
+ TK_DUMP_TAG_CONFIGS = 1 << 22,
+ TK_DUMP_TAG_BINDINGS = 1 << 23,
+ TK_DUMP_INSERT_MARK = 1 << 24,
+ TK_DUMP_INCLUDE_SEL = 1 << 25,
+ TK_DUMP_DONT_RESOLVE_COLORS = 1 << 26,
+ TK_DUMP_DONT_RESOLVE_FONTS = 1 << 27,
+ TK_DUMP_INCLUDE_DATABASE_CONFIG = 1 << 28,
+ TK_DUMP_INCLUDE_SYSTEM_CONFIG = 1 << 29,
+ TK_DUMP_INCLUDE_DEFAULT_CONFIG = 1 << 30,
+ TK_DUMP_INCLUDE_SYSTEM_COLORS = 1 << 31,
+ TK_DUMP_INSPECT_DFLT = TK_DUMP_DUMP_ALL,
+ TK_DUMP_INSPECT_COMPLETE = TK_DUMP_INSPECT_DFLT|TK_DUMP_TAG_BINDINGS|TK_DUMP_TEXT_CONFIGS|
+ TK_DUMP_TAG_CONFIGS|TK_DUMP_INCLUDE_SEL|TK_DUMP_INSERT_MARK|
+ TK_DUMP_INCLUDE_DATABASE_CONFIG|TK_DUMP_INCLUDE_SYSTEM_CONFIG|
+ TK_DUMP_INCLUDE_DEFAULT_CONFIG|TK_DUMP_ELIDE|
+ TK_DUMP_INCLUDE_SYSTEM_COLORS,
+ TK_DUMP_INSPECT_ALL = TK_DUMP_INSPECT_COMPLETE|TK_DUMP_DISPLAY_TEXT|
+ TK_DUMP_DONT_RESOLVE_COLORS|TK_DUMP_DONT_RESOLVE_FONTS|
+ TK_DUMP_NESTED,
+};
/*
- * Declarations of the three search procs required by the multi-line search
- * routines.
+ * Declarations of the three search procs required by the multi-line search routines.
*/
static SearchMatchProc TextSearchFoundMatch;
@@ -431,13 +619,219 @@ static SearchLineIndexProc TextSearchGetLineIndex;
* can be invoked from generic window code.
*/
-static const Tk_ClassProcs textClass = {
+static CONST Tk_ClassProcs textClass = {
sizeof(Tk_ClassProcs), /* size */
TextWorldChangedCallback, /* worldChangedProc */
- NULL, /* createProc */
- NULL /* modalProc */
+ NULL, /* createProc */
+ NULL /* modalProc */
};
+#if TK_CHECK_ALLOCS
+
+/*
+ * Some stuff for memory checks, and allocation statistic.
+ */
+
+unsigned tkTextCountNewShared = 0;
+unsigned tkTextCountDestroyShared = 0;
+unsigned tkTextCountNewPeer = 0;
+unsigned tkTextCountDestroyPeer = 0;
+unsigned tkTextCountNewPixelInfo = 0;
+unsigned tkTextCountDestroyPixelInfo = 0;
+unsigned tkTextCountNewSegment = 0;
+unsigned tkTextCountDestroySegment = 0;
+unsigned tkTextCountNewTag = 0;
+unsigned tkTextCountDestroyTag = 0;
+unsigned tkTextCountNewUndoToken = 0;
+unsigned tkTextCountDestroyUndoToken = 0;
+unsigned tkTextCountNewNode = 0;
+unsigned tkTextCountDestroyNode = 0;
+unsigned tkTextCountNewLine = 0;
+unsigned tkTextCountDestroyLine = 0;
+unsigned tkTextCountNewSection = 0;
+unsigned tkTextCountDestroySection = 0;
+
+extern unsigned tkIntSetCountDestroy;
+extern unsigned tkIntSetCountNew;
+extern unsigned tkBitCountNew;
+extern unsigned tkBitCountDestroy;
+
+typedef struct WatchShared {
+ TkSharedText *sharedTextPtr;
+ struct WatchShared *nextPtr;
+} WatchShared;
+
+static unsigned widgetNumber = 0;
+static WatchShared *watchShared;
+
+static void
+AllocStatistic()
+{
+ const WatchShared *wShared;
+
+ if (!tkBTreeDebug) {
+ return;
+ }
+
+ for (wShared = watchShared; wShared; wShared = wShared->nextPtr) {
+ const TkText *peer;
+
+ for (peer = wShared->sharedTextPtr->peers; peer; peer = peer->next) {
+ fprintf(stderr, "Unreleased text widget %d\n", peer->widgetNumber);
+ }
+ }
+
+ fprintf(stderr, "---------------------------------\n");
+ fprintf(stderr, "ALLOCATION: new destroy\n");
+ fprintf(stderr, "---------------------------------\n");
+ fprintf(stderr, "Shared: %8u - %8u\n", tkTextCountNewShared, tkTextCountDestroyShared);
+ fprintf(stderr, "Peer: %8u - %8u\n", tkTextCountNewPeer, tkTextCountDestroyPeer);
+ fprintf(stderr, "Segment: %8u - %8u\n", tkTextCountNewSegment, tkTextCountDestroySegment);
+ fprintf(stderr, "Tag: %8u - %8u\n", tkTextCountNewTag, tkTextCountDestroyTag);
+ fprintf(stderr, "UndoToken: %8u - %8u\n", tkTextCountNewUndoToken, tkTextCountDestroyUndoToken);
+ fprintf(stderr, "Node: %8u - %8u\n", tkTextCountNewNode, tkTextCountDestroyNode);
+ fprintf(stderr, "Line: %8u - %8u\n", tkTextCountNewLine, tkTextCountDestroyLine);
+ fprintf(stderr, "Section: %8u - %8u\n", tkTextCountNewSection, tkTextCountDestroySection);
+ fprintf(stderr, "PixelInfo: %8u - %8u\n", tkTextCountNewPixelInfo, tkTextCountDestroyPixelInfo);
+ fprintf(stderr, "BitField: %8u - %8u\n", tkBitCountNew, tkBitCountDestroy);
+ fprintf(stderr, "IntSet: %8u - %8u\n", tkIntSetCountNew, tkIntSetCountDestroy);
+ fprintf(stderr, "--------------------------------\n");
+
+ if (tkTextCountNewShared != tkTextCountDestroyShared
+ || tkTextCountNewPeer != tkTextCountDestroyPeer
+ || tkTextCountNewSegment != tkTextCountDestroySegment
+ || tkTextCountNewTag != tkTextCountDestroyTag
+ || tkTextCountNewUndoToken != tkTextCountDestroyUndoToken
+ || tkTextCountNewNode != tkTextCountDestroyNode
+ || tkTextCountNewLine != tkTextCountDestroyLine
+ || tkTextCountNewSection != tkTextCountDestroySection
+ || tkTextCountNewPixelInfo != tkTextCountDestroyPixelInfo
+ || tkBitCountNew != tkBitCountDestroy
+ || tkIntSetCountNew != tkIntSetCountDestroy) {
+ fprintf(stderr, "*** memory leak detected ***\n");
+ fprintf(stderr, "----------------------------\n");
+ /* TkBitCheckAllocs(); */
+ }
+}
+#endif /* TK_CHECK_ALLOCS */
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+/*
+ * Some helpers.
+ */
+
+static void WarnAboutDeprecatedStartLineOption() {
+ static bool printWarning = true;
+ if (printWarning) {
+ fprintf(stderr, "tk::text: Option \"-startline\" is deprecated, "
+ "please use option \"-startindex\".\n");
+ printWarning = false;
+ }
+}
+static void WarnAboutDeprecatedEndLineOption() {
+ static bool printWarning = true;
+ if (printWarning) {
+ fprintf(stderr, "tk::text: Option \"-endline\" is deprecated, "
+ "please use option \"-endindex\".\n");
+ printWarning = false;
+ }
+}
+
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+/*
+ * Helper for guarded release of objects.
+ */
+
+static void
+Tcl_GuardedDecrRefCount(Tcl_Obj *objPtr)
+{
+#ifndef NDEBUG
+ /*
+ * Tcl does not provide any function for querying the reference count.
+ * So we need a work-around. Why does Tcl not provide a guarded version
+ * for such a dangerous function?
+ */
+ assert(objPtr);
+ Tcl_IncrRefCount(objPtr);
+ assert(Tcl_IsShared(objPtr));
+ Tcl_DecrRefCount(objPtr);
+#endif
+ Tcl_DecrRefCount(objPtr);
+}
+
+/*
+ * Wee need a helper for sending virtual events, because in newer Tk version
+ * the footprint of TkSendVirtualEvent has changed. (Note that this source has
+ * backports for 8.5, and older versions of 8.6).
+ */
+
+static void
+SendVirtualEvent(
+ Tk_Window tkwin,
+ char const *eventName,
+ Tcl_Obj *detail)
+{
+#if TK_MAJOR_VERSION > 8 \
+ || (TK_MAJOR_VERSION == 8 \
+ && (TK_MINOR_VERSION > 6 || (TK_MINOR_VERSION == 6 && TK_RELEASE_SERIAL >= 6)))
+ /* new footprint since 8.6.6 */
+ TkSendVirtualEvent(tkwin, eventName, detail);
+#else
+# if TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION >= 6
+ if (!detail) {
+ /* new function since 8.6.0, and valid until 8.6.5 */
+ TkSendVirtualEvent(tkwin, eventName);
+ return;
+ }
+# endif
+ {
+ /* backport to 8.5 */
+ union { XEvent general; XVirtualEvent virtual; } event;
+
+ memset(&event, 0, sizeof(event));
+ event.general.xany.type = VirtualEvent;
+ event.general.xany.serial = NextRequest(Tk_Display(tkwin));
+ event.general.xany.send_event = False;
+ event.general.xany.window = Tk_WindowId(tkwin);
+ event.general.xany.display = Tk_Display(tkwin);
+ event.virtual.name = Tk_GetUid(eventName);
+ event.virtual.user_data = detail;
+ Tk_QueueWindowEvent(&event.general, TCL_QUEUE_TAIL);
+ }
+#endif
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * GetByteLength --
+ *
+ * This function should be defined by Tcl, but it isn't defined,
+ * so we are doing this.
+ *
+ * Results:
+ * The length of the string.
+ *
+ * Side effects:
+ * Calls Tcl_GetString(objPtr) if objPtr->bytes is not yet resolved.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+GetByteLength(
+ Tcl_Obj *objPtr)
+{
+ assert(objPtr);
+
+ if (!objPtr->bytes) {
+ Tcl_GetString(objPtr);
+ }
+ return objPtr->length;
+}
+
/*
*--------------------------------------------------------------
*
@@ -469,10 +863,122 @@ Tk_TextObjCmd(
return TCL_ERROR;
}
+ if (!tkwin) {
+ tkwin = Tk_MainWindow(interp);
+ }
return CreateWidget(NULL, tkwin, interp, NULL, objc, objv);
}
/*
+ *----------------------------------------------------------------------
+ *
+ * PushRetainedUndoTokens --
+ *
+ * Push the retained undo tokens onto the stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextPushUndoToken, additionaly 'undoTagList' and
+ * 'undoMarkList' will be cleared.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PushRetainedUndoTokens(
+ TkSharedText *sharedTextPtr)
+{
+ unsigned i;
+
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextPushUndoTagTokens(sharedTextPtr, sharedTextPtr->undoTagList[i]);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextPushUndoMarkTokens(sharedTextPtr, &sharedTextPtr->undoMarkList[i]);
+ }
+
+ sharedTextPtr->undoTagListCount = 0;
+ sharedTextPtr->undoMarkListCount = 0;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushUndoToken --
+ *
+ * This function is pushing the given undo/redo token. Don't use
+ * TkTextUndoPushItem, because some of the prepared undo tokens
+ * are retained.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextUndoPushItem, furthermore all retained items
+ * will be pushed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushUndoToken(
+ TkSharedText *sharedTextPtr,
+ void *token,
+ unsigned byteSize)
+{
+ TkTextUndoAction action;
+
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+ assert(token);
+
+ action = ((TkTextUndoToken *) token)->undoType->action;
+
+ if (action == TK_TEXT_UNDO_INSERT || action == TK_TEXT_UNDO_DELETE) {
+ sharedTextPtr->insertDeleteUndoTokenCount += 1;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushItem(sharedTextPtr->undoStack, token, byteSize);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextPushRedoToken --
+ *
+ * This function is pushing the given redo token. This function
+ * is useful only for the reconstruction of the undo stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Same as TkTextUndoPushRedoItem.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkTextPushRedoToken(
+ TkSharedText *sharedTextPtr,
+ void *token,
+ unsigned byteSize)
+{
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->undoStack);
+ assert(token);
+
+ TkTextUndoPushRedoItem(sharedTextPtr->undoStack, token, byteSize);
+}
+
+/*
*--------------------------------------------------------------
*
* CreateWidget --
@@ -495,7 +1001,7 @@ Tk_TextObjCmd(
static int
CreateWidget(
- TkSharedText *sharedPtr, /* Shared widget info, or NULL. */
+ TkSharedText *sharedTextPtr,/* 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
@@ -503,7 +1009,7 @@ CreateWidget(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkText *textPtr;
+ TkText *textPtr;
Tk_OptionTable optionTable;
TkTextIndex startIndex;
Tk_Window newWin;
@@ -512,57 +1018,100 @@ CreateWidget(
* Create the window.
*/
- newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]),
- NULL);
- if (newWin == NULL) {
+ if (!(newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]), NULL))) {
return TCL_ERROR;
}
+ if (!sharedTextPtr) {
+ sharedTextPtr = calloc(1, sizeof(TkSharedText));
+
+ Tcl_InitHashTable(&sharedTextPtr->tagTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->markTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->windowTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->imageTable, TCL_STRING_KEYS);
+ sharedTextPtr->usedTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->elisionTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->selectionTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->dontUndoTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->affectDisplayTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->notAffectDisplayTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->affectDisplayNonSelTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->affectGeometryTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->affectGeometryNonSelTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->affectLineHeightTags = TkBitResize(NULL, TK_TEXT_SET_MAX_BIT_SIZE);
+ sharedTextPtr->tagLookup = malloc(TK_TEXT_SET_MAX_BIT_SIZE*sizeof(TkTextTag *));
+ sharedTextPtr->emptyTagInfoPtr = TkTextTagSetResize(NULL, 0);
+ sharedTextPtr->maxRedoDepth = -1;
+ sharedTextPtr->autoSeparators = true;
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+ sharedTextPtr->startMarker = TkTextMakeStartEndMark(NULL, &tkTextLeftMarkType);
+ sharedTextPtr->endMarker = TkTextMakeStartEndMark(NULL, &tkTextRightMarkType);
+ sharedTextPtr->protectionMark[0] = TkTextMakeMark(NULL, NULL);
+ sharedTextPtr->protectionMark[1] = TkTextMakeMark(NULL, NULL);
+ sharedTextPtr->protectionMark[0]->typePtr = &tkTextProtectionMarkType;
+ sharedTextPtr->protectionMark[1]->typePtr = &tkTextProtectionMarkType;
+
+ DEBUG(memset(sharedTextPtr->tagLookup, 0, TK_TEXT_SET_MAX_BIT_SIZE*sizeof(TkTextTag *)));
+
+ sharedTextPtr->mainPeer = calloc(1, sizeof(TkText));
+ sharedTextPtr->mainPeer->startMarker = sharedTextPtr->startMarker;
+ sharedTextPtr->mainPeer->endMarker = sharedTextPtr->endMarker;
+ sharedTextPtr->mainPeer->sharedTextPtr = sharedTextPtr;
+
+#if TK_CHECK_ALLOCS
+ if (tkTextCountNewShared++ == 0) {
+ atexit(AllocStatistic);
+ }
+ /*
+ * Add this shared resource to global list.
+ */
+ {
+ WatchShared *wShared = malloc(sizeof(WatchShared));
+ wShared->sharedTextPtr = sharedTextPtr;
+ wShared->nextPtr = watchShared;
+ watchShared = wShared;
+ }
+#endif
+
+ /*
+ * The construction of the tree requires a valid setup of the shared resource.
+ */
+
+ sharedTextPtr->tree = TkBTreeCreate(sharedTextPtr, 1);
+ }
+
+ DEBUG_ALLOC(tkTextCountNewPeer++);
+
/*
* 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 = ckalloc(sizeof(TkText));
- memset(textPtr, 0, sizeof(TkText));
-
+ textPtr = calloc(1, sizeof(TkText));
textPtr->tkwin = newWin;
textPtr->display = Tk_Display(newWin);
textPtr->interp = interp;
- textPtr->widgetCmd = Tcl_CreateObjCommand(interp,
- Tk_PathName(textPtr->tkwin), TextWidgetObjCmd,
- textPtr, TextCmdDeletedProc);
-
- if (sharedPtr == NULL) {
- sharedPtr = 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 = 0;
- sharedPtr->isDirty = 0;
- sharedPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
- sharedPtr->autoSeparators = 1;
- sharedPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
- sharedPtr->stateEpoch = 0;
- }
+ textPtr->widgetCmd = Tcl_CreateObjCommand(interp, Tk_PathName(textPtr->tkwin),
+ TextWidgetObjCmd, textPtr, TextCmdDeletedProc);
+ DEBUG_ALLOC(textPtr->widgetNumber = ++widgetNumber);
/*
* Add the new widget to the shared list.
*/
- textPtr->sharedTextPtr = sharedPtr;
- sharedPtr->refCount++;
- textPtr->next = sharedPtr->peers;
- sharedPtr->peers = textPtr;
+ textPtr->sharedTextPtr = sharedTextPtr;
+ sharedTextPtr->refCount += 1;
+ textPtr->next = sharedTextPtr->peers;
+ sharedTextPtr->peers = textPtr;
+
+ /*
+ * Clear the indices, do this after the shared widget is created.
+ */
+
+ TkTextIndexClear(&textPtr->topIndex, textPtr);
+ TkTextIndexClear(&textPtr->selIndex, textPtr);
/*
* This refCount will be held until DestroyText is called. Note also that
@@ -577,68 +1126,68 @@ CreateWidget(
* start, end where given as configuration options.
*/
- if (parent != NULL) {
- textPtr->start = parent->start;
- textPtr->end = parent->end;
+ if (parent) {
+ (textPtr->startMarker = parent->startMarker)->refCount += 1;
+ (textPtr->endMarker = parent->endMarker)->refCount += 1;
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+ textPtr->startLine = parent->startLine;
+ textPtr->endLine = parent->endLine;
+#endif
} else {
- textPtr->start = NULL;
- textPtr->end = NULL;
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
}
- 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(newWin);
- textPtr->prevHeight = Tk_Height(newWin);
-
/*
* 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);
+ TkBTreeAddClient(sharedTextPtr->tree, textPtr, textPtr->lineHeight);
+
+ textPtr->state = TK_TEXT_STATE_NORMAL;
+ textPtr->relief = TK_RELIEF_FLAT;
+ textPtr->cursor = None;
+ textPtr->charWidth = 1;
+ textPtr->spaceWidth = 1;
+ textPtr->lineHeight = -1;
+ textPtr->prevWidth = Tk_Width(newWin);
+ textPtr->prevHeight = Tk_Height(newWin);
+ textPtr->useHyphenSupport = -1;
+ textPtr->hyphenRules = TK_TEXT_HYPHEN_MASK;
+ textPtr->prevSyncState = -1;
+ textPtr->lastLineY = TK_TEXT_NEARBY_IS_UNDETERMINED;
+ TkTextTagSetIncrRefCount(textPtr->curTagInfoPtr = sharedTextPtr->emptyTagInfoPtr);
/*
* This will add refCounts to textPtr.
*/
TkTextCreateDInfo(textPtr);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &startIndex);
+ TkTextIndexSetupToStartOfText(&startIndex, textPtr, sharedTextPtr->tree);
TkTextSetYView(textPtr, &startIndex, 0);
- textPtr->exportSelection = 1;
+ textPtr->exportSelection = true;
textPtr->pickEvent.type = LeaveNotify;
+ textPtr->steadyMarks = textPtr->sharedTextPtr->steadyMarks;
textPtr->undo = textPtr->sharedTextPtr->undo;
- textPtr->maxUndo = textPtr->sharedTextPtr->maxUndo;
+ textPtr->maxUndoDepth = textPtr->sharedTextPtr->maxUndoDepth;
+ textPtr->maxRedoDepth = textPtr->sharedTextPtr->maxRedoDepth;
+ textPtr->maxUndoSize = textPtr->sharedTextPtr->maxUndoSize;
textPtr->autoSeparators = textPtr->sharedTextPtr->autoSeparators;
- textPtr->tabOptionPtr = NULL;
/*
* Create the "sel" tag and the "current" and "insert" marks.
- */
-
- 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 =
- ckalloc(sizeof(DEF_TEXT_SELECT_RELIEF));
- strcpy(textPtr->selTagPtr->reliefString, DEF_TEXT_SELECT_RELIEF);
- Tk_GetRelief(interp, DEF_TEXT_SELECT_RELIEF, &textPtr->selTagPtr->relief);
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &startIndex);
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &startIndex);
+ textPtr->currentMarkIndex = startIndex;
+
+ sharedTextPtr->numPeers += 1;
/*
* Create the option table for this widget class. If it has already been
@@ -652,21 +1201,21 @@ CreateWidget(
textPtr->optionTable = optionTable;
Tk_CreateEventHandler(textPtr->tkwin,
- ExposureMask|StructureNotifyMask|FocusChangeMask,
- TextEventProc, textPtr);
+ ExposureMask|StructureNotifyMask|FocusChangeMask, TextEventProc, textPtr);
Tk_CreateEventHandler(textPtr->tkwin, KeyPressMask|KeyReleaseMask
|ButtonPressMask|ButtonReleaseMask|EnterWindowMask
|LeaveWindowMask|PointerMotionMask|VirtualEventMask,
TkTextBindProc, textPtr);
- Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING,
- TextFetchSelection, textPtr, XA_STRING);
+ Tk_CreateSelHandler(textPtr->tkwin, XA_PRIMARY, XA_STRING, TextFetchSelection, textPtr, XA_STRING);
- if (Tk_InitOptions(interp, (char *) textPtr, optionTable, textPtr->tkwin)
- != 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) {
+ textPtr->textConfigAttrs = textPtr->selAttrs;
+ textPtr->selTagPtr->attrs = textPtr->selAttrs;
+
+ if (TkConfigureText(interp, textPtr, objc - 2, objv + 2) != TCL_OK) {
Tk_DestroyWindow(textPtr->tkwin);
return TCL_ERROR;
}
@@ -678,6 +1227,112 @@ CreateWidget(
/*
*--------------------------------------------------------------
*
+ * UpdateLineMetrics --
+ *
+ * This function updates the pixel height calculations of a range of
+ * lines in the widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Line heights may be recalculated.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+UpdateLineMetrics(
+ TkText *textPtr, /* Information about widget. */
+ unsigned startLine, /* Start at this line. */
+ unsigned endLine) /* Go no further than this line. */
+{
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ TkTextUpdateLineMetrics(textPtr, startLine, endLine);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextAttemptToModifyDisabledWidget --
+ *
+ * The GUI tries to modify a disabled text widget, so an
+ * error will be thrown.
+ *
+ * Results:
+ * Returns TCL_ERROR.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static void
+ErrorNotAllowed(
+ Tcl_Interp *interp,
+ const char *text)
+{
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(text, -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NOT_ALLOWED", NULL);
+}
+
+int
+TkTextAttemptToModifyDisabledWidget(
+ Tcl_Interp *interp)
+{
+#if SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ static bool showWarning = true;
+ if (showWarning) {
+ fprintf(stderr, "tk::text: Attempt to modify a disabled widget is deprecated.\n");
+ showWarning = false;
+ }
+ return TCL_OK;
+#else /* if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET */
+ ErrorNotAllowed(interp, "attempt to modify disabled widget");
+ return TCL_ERROR;
+#endif
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextAttemptToModifyDeadWidget --
+ *
+ * The GUI tries to modify a dead text widget, so an
+ * error will be thrown.
+ *
+ * Results:
+ * Returns TCL_ERROR.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+int
+TkTextAttemptToModifyDeadWidget(
+ Tcl_Interp *interp)
+{
+#if SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET
+ static bool showWarning = true;
+ if (showWarning) {
+ fprintf(stderr, "tk::text: Attempt to modify a dead widget is deprecated.\n");
+ showWarning = false;
+ }
+ return TCL_OK;
+#else /* if !SUPPORT_DEPRECATED_MODS_OF_DISABLED_WIDGET */
+ ErrorNotAllowed(interp, "attempt to modify dead widget");
+ return TCL_ERROR;
+#endif
+}
+
+/*
+ *--------------------------------------------------------------
+ *
* TextWidgetObjCmd --
*
* This function is invoked to process the Tcl command that corresponds
@@ -693,6 +1348,118 @@ CreateWidget(
*--------------------------------------------------------------
*/
+static bool
+TestIfTriggerUserMod(
+ TkSharedText *sharedTextPtr,
+ Tcl_Obj *indexObjPtr)
+{
+ return sharedTextPtr->triggerWatchCmd && strcmp(Tcl_GetString(indexObjPtr), "insert") == 0;
+}
+
+static bool
+TestIfPerformingUndoRedo(
+ Tcl_Interp *interp,
+ const TkSharedText *sharedTextPtr,
+ int *result)
+{
+ if (sharedTextPtr->undoStack && TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ ErrorNotAllowed(interp, "cannot modify inside undo/redo operation");
+ if (result) {
+ *result = TCL_ERROR;
+ }
+ return true;
+ }
+ return false;
+}
+
+static bool
+TestIfDisabled(
+ Tcl_Interp *interp,
+ const TkText *textPtr,
+ int *result)
+{
+ assert(result);
+
+ if (textPtr->state != TK_TEXT_STATE_DISABLED) {
+ return false;
+ }
+ *result = TkTextAttemptToModifyDisabledWidget(interp);
+ return true;
+}
+
+static bool
+TestIfDead(
+ Tcl_Interp *interp,
+ const TkText *textPtr,
+ int *result)
+{
+ assert(result);
+
+ if (!TkTextIsDeadPeer(textPtr)) {
+ return false;
+ }
+ *result = TkTextAttemptToModifyDeadWidget(interp);
+ return true;
+}
+
+static Tcl_Obj *
+AppendScript(
+ const char *oldScript,
+ const char *script)
+{
+ char buffer[1024];
+ int lenOfNew = strlen(script);
+ int lenOfOld = strlen(oldScript);
+ size_t totalLen = lenOfOld + lenOfNew + 1;
+ char *newScript = buffer;
+ Tcl_Obj *newScriptObj;
+
+ if (totalLen + 2 > sizeof(buffer)) {
+ newScript = malloc(totalLen + 1);
+ }
+
+ memcpy(newScript, oldScript, lenOfOld);
+ newScript[lenOfOld] = '\n';
+ memcpy(newScript + lenOfOld + 1, script, lenOfNew + 1);
+ newScriptObj = Tcl_NewStringObj(newScript, totalLen);
+ if (newScript != buffer) { free(newScript); }
+ return newScriptObj;
+}
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+static bool
+MatchOpt(
+ const char *opt,
+ const char *pattern,
+ unsigned minMatchLen)
+{
+ if (strncmp(opt, pattern, minMatchLen) != 0) {
+ return false;
+ }
+ opt += minMatchLen;
+ pattern += minMatchLen;
+ while (true) {
+ if (*opt == '\0') {
+ return true;
+ }
+ if (*pattern == '\0') {
+ return false;
+ }
+ if (*opt != *pattern) {
+ return false;
+ }
+ opt += 1;
+ pattern += 1;
+ }
+ return false; /* never reached */
+}
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
static int
TextWidgetObjCmd(
ClientData clientData, /* Information about text widget. */
@@ -700,23 +1467,27 @@ TextWidgetObjCmd(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
+ TkSharedText *sharedTextPtr;
int result = TCL_OK;
- int index;
+ int commandIndex = -1;
+ bool oldUndoStackEvent;
static const char *const optionStrings[] = {
- "bbox", "cget", "compare", "configure", "count", "debug", "delete",
- "dlineinfo", "dump", "edit", "get", "image", "index", "insert",
- "mark", "peer", "pendingsync", "replace", "scan", "search",
- "see", "sync", "tag", "window", "xview", "yview", NULL
+ "tk_bindvar", "tk_textInsert", "tk_textReplace",
+ "bbox", "brks", "checksum", "cget", "clear", "compare", "configure",
+ "count", "debug", "delete", "dlineinfo", "dump", "edit", "get", "image",
+ "index", "insert", "inspect", "isclean", "isdead", "isempty", "lineno",
+ "load", "mark", "peer", "pendingsync", "replace", "scan", "search",
+ "see", "sync", "tag", "watch", "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_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN,
- TEXT_SEARCH, TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WINDOW,
- TEXT_XVIEW, TEXT_YVIEW
+ TEXT_TK_BINDVAR, TEXT_TK_TEXTINSERT, TEXT_TK_TEXTREPLACE,
+ TEXT_BBOX, TEXT_BRKS, TEXT_CHECKSUM, TEXT_CGET, TEXT_CLEAR, 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_INSPECT, TEXT_ISCLEAN, TEXT_ISDEAD, TEXT_ISEMPTY, TEXT_LINENO,
+ TEXT_LOAD, TEXT_MARK, TEXT_PEER, TEXT_PENDINGSYNC, TEXT_REPLACE, TEXT_SCAN, TEXT_SEARCH,
+ TEXT_SEE, TEXT_SYNC, TEXT_TAG, TEXT_WATCH, TEXT_WINDOW, TEXT_XVIEW, TEXT_YVIEW
};
if (objc < 2) {
@@ -725,29 +1496,93 @@ TextWidgetObjCmd(
}
if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
+ sizeof(char *), "option", 0, &commandIndex) != TCL_OK) {
+ /*
+ * Hide the first three options, generating the error description with
+ * the side effects of Tcl_GetIndexFromObjStruct.
+ */
+
+ (void) Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings + 3,
+ sizeof(char *), "option", 0, &commandIndex);
return TCL_ERROR;
}
- textPtr->refCount++;
- switch ((enum options) index) {
- case TEXT_BBOX: {
- int x, y, width, height;
- const TkTextIndex *indexPtr;
+ textPtr->refCount += 1;
+ sharedTextPtr = textPtr->sharedTextPtr;
+ oldUndoStackEvent = sharedTextPtr->undoStackEvent;
+ sharedTextPtr->undoStackEvent = false;
+
+ /*
+ * Clear saved insert cursor position.
+ */
+
+ TkTextIndexClear(&textPtr->insertIndex, textPtr);
+
+ /*
+ * Check if we need to update the "current" mark segment.
+ */
+
+ if (sharedTextPtr->haveToSetCurrentMark) {
+ TkTextUpdateCurrentMark(sharedTextPtr);
+ }
+
+ if (CATCH_ASSERTION_FAILED) {
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ switch ((enum options) commandIndex) {
+ case TEXT_TK_BINDVAR: {
+ TkTextStringList *listPtr;
+
+ /*
+ * Bind a variable to this widget, this variable will be released (Tcl_UnsetVar2)
+ * when the widget will be destroyed.
+ *
+ * I suggest to provide a general support for binding variables to widgets in a
+ * future Tk version.
+ */
if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index");
+ Tcl_WrongNumArgs(interp, 2, objv, "varname");
+ result = TCL_ERROR;
+ goto done;
+ }
+
+ listPtr = malloc(sizeof(TkTextStringList));
+ Tcl_IncrRefCount(listPtr->strObjPtr = objv[2]);
+ listPtr->nextPtr = textPtr->varBindingList;
+ textPtr->varBindingList = listPtr;
+ break;
+ }
+ case TEXT_BBOX: {
+ int x, y, width, height, argc = 2;
+ bool extents = false;
+ TkTextIndex index;
+
+ if (objc == 4) {
+ const char* option = Tcl_GetString(objv[2]);
+
+ if (strcmp(option, "-extents") == 0) {
+ extents = true;
+ argc += 1;
+ } else if (*option == '-') {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -extents", option));
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+ if (objc - argc + 2 != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-extents? index");
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[argc], &index)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextIndexBbox(textPtr, indexPtr, &x, &y, &width, &height,
- NULL) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+ if (TkTextIndexBbox(textPtr, &index, extents, &x, &y, &width, &height, NULL, NULL)) {
+ Tcl_Obj *listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
@@ -758,16 +1593,98 @@ TextWidgetObjCmd(
}
break;
}
+ case TEXT_BRKS: {
+ Tcl_Obj *arrPtr;
+ unsigned length, i;
+ char const *lang = NULL;
+ char buf[1];
+
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (objc == 4) {
+ if (!TkTextTestLangCode(interp, objv[3])) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!TkTextComputeBreakLocations(interp, "", 0, "en", buf)) {
+#if TCL_UTF_MAX > 4
+# ifdef __unix__
+# error "The use of external libraries with a proprietary pseudo UTF-8 encoding is safety-endagering and may result in invalid computationial results. This means: TCL_UTF_MAX > 4 cannot be supported here."
+#endif
+ ErrorNotAllowed(interp, "external library libunibreak/liblinebreak cannot "
+ "be used with non-standard encodings");
+#else
+ ErrorNotAllowed(interp, "external library libunibreak/liblinebreak is not available");
+#endif
+ result = TCL_ERROR;
+ goto done;
+ }
+ lang = Tcl_GetString(objv[3]);
+ }
+ if ((length = GetByteLength(objv[2])) < textPtr->brksBufferSize) {
+ textPtr->brksBufferSize = MAX(length, textPtr->brksBufferSize + 512);
+ textPtr->brksBuffer = realloc(textPtr->brksBuffer, textPtr->brksBufferSize);
+ }
+ TkTextComputeBreakLocations(interp, Tcl_GetString(objv[2]), length, lang, textPtr->brksBuffer);
+ arrPtr = Tcl_NewObj();
+
+ for (i = 0; i < length; ++i) {
+ int value;
+
+ switch (textPtr->brksBuffer[i]) {
+ case LINEBREAK_INSIDEACHAR: continue;
+ case LINEBREAK_MUSTBREAK: value = 2; break;
+ case LINEBREAK_ALLOWBREAK: value = 1; break;
+ default: value = 0; break;
+ }
+ Tcl_ListObjAppendElement(interp, arrPtr, Tcl_NewIntObj(value));
+ }
+
+ Tcl_SetObjResult(interp, arrPtr);
+ break;
+ }
+ case TEXT_CHECKSUM:
+ result = TextChecksumCmd(textPtr, interp, objc, objv);
+ 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,
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+ Tcl_Obj *objPtr, *optionObj = NULL;
+ const char *opt = Tcl_GetString(objv[2]);
+
+ if (strcmp(opt, "-start") == 0) {
+ optionObj = Tcl_NewStringObj(textPtr->startLine ? "-startline" : "-startindex", -1);
+ } else if (MatchOpt(opt, "-startline", 7)) {
+ optionObj = Tcl_NewStringObj("-startline", -1);
+ } else if (strcmp(opt, "-end") == 0) {
+ optionObj = Tcl_NewStringObj(textPtr->endLine ? "-endline" : "-endindex", -1);
+ } else if (MatchOpt(opt, "-endline", 5)) {
+ optionObj = Tcl_NewStringObj("-endline", -1);
+ } else {
+ Tcl_IncrRefCount(optionObj = objv[2]);
+ }
+
+ Tcl_IncrRefCount(optionObj);
+ objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
+ textPtr->optionTable, optionObj, textPtr->tkwin);
+ Tcl_GuardedDecrRefCount(optionObj);
+
+#else /* if !SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
textPtr->optionTable, objv[2], textPtr->tkwin);
- if (objPtr == NULL) {
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ if (!objPtr) {
result = TCL_ERROR;
goto done;
}
@@ -775,250 +1692,202 @@ TextWidgetObjCmd(
result = TCL_OK;
}
break;
+ case TEXT_CLEAR:
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ ClearText(textPtr, true);
+ TkTextRelayoutWindow(textPtr, TK_TEXT_LINE_GEOMETRY);
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+ break;
case TEXT_COMPARE: {
int relation, value;
- const char *p;
- const TkTextIndex *index1Ptr, *index2Ptr;
+ TkTextIndex index1, index2;
if (objc != 5) {
Tcl_WrongNumArgs(interp, 2, objv, "index1 op index2");
result = TCL_ERROR;
goto done;
}
- index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[4]);
- if (index1Ptr == NULL || index2Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index1)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[4], &index2)) {
result = TCL_ERROR;
goto done;
}
- relation = TkTextIndexCmp(index1Ptr, index2Ptr);
- p = Tcl_GetString(objv[3]);
- if (p[0] == '<') {
- value = (relation < 0);
- if ((p[1] == '=') && (p[2] == 0)) {
- value = (relation <= 0);
- } else if (p[1] != 0) {
- goto compareError;
- }
- } else if (p[0] == '>') {
- value = (relation > 0);
- if ((p[1] == '=') && (p[2] == 0)) {
- value = (relation >= 0);
- } else if (p[1] != 0) {
- goto compareError;
- }
- } else if ((p[0] == '=') && (p[1] == '=') && (p[2] == 0)) {
- value = (relation == 0);
- } else if ((p[0] == '!') && (p[1] == '=') && (p[2] == 0)) {
- value = (relation != 0);
+ relation = TkTextIndexCompare(&index1, &index2);
+ value = TkTextTestRelation(interp, relation, Tcl_GetString(objv[3]));
+ if (value == -1) {
+ result = TCL_ERROR;
} else {
- goto compareError;
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
}
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(value));
break;
-
- compareError:
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad comparison operator \"%s\": must be"
- " <, <=, ==, >=, >, or !=", Tcl_GetString(objv[3])));
- Tcl_SetErrorCode(interp, "TK", "VALUE", "COMPARISON", NULL);
- result = TCL_ERROR;
- goto done;
}
case TEXT_CONFIGURE:
if (objc <= 3) {
Tcl_Obj *objPtr = Tk_GetOptionInfo(interp, (char *) textPtr,
- textPtr->optionTable, ((objc == 3) ? objv[2] : NULL),
- textPtr->tkwin);
+ textPtr->optionTable, objc == 3 ? objv[2] : NULL, textPtr->tkwin);
- if (objPtr == NULL) {
+ if (!objPtr) {
result = TCL_ERROR;
goto done;
}
Tcl_SetObjResult(interp, objPtr);
} else {
- result = ConfigureText(interp, textPtr, objc-2, objv+2);
+ result = TkConfigureText(interp, textPtr, objc - 2, objv + 2);
}
break;
case TEXT_COUNT: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
- int i, found = 0, update = 0;
+ TkTextIndex indexFrom, indexTo;
Tcl_Obj *objPtr = NULL;
+ bool update = false;
+ int i, found = 0;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-option value ...? index1 index2");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-option value ...? 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) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[objc - 2], &indexFrom)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[objc - 1], &indexTo)) {
result = TCL_ERROR;
goto done;
}
- for (i = 2; i < objc-2; i++) {
- int value, length;
+ for (i = 2; i < objc - 2; i++) {
+ int length;
+ int value = INT_MIN;
const char *option = Tcl_GetString(objv[i]);
- char c;
- length = objv[i]->length;
+ length = GetByteLength(objv[i]);
if (length < 2 || option[0] != '-') {
goto badOption;
}
- 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, index2;
-
- int compare = TkTextIndexCmp(indexFromPtr, indexToPtr);
- value = 0;
-
- if (compare == 0) {
- goto countDone;
+ switch (option[1]) {
+ case 'c':
+ if (strncmp("-chars", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_CHARS);
}
+ break;
+ case 'd':
+ if (length > 8 && strncmp("-display", option, 8) == 0) {
+ switch (option[8]) {
+ case 'c':
+ if (strcmp("chars", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_CHARS);
+ }
+ break;
+ case 'h':
+ if (strcmp("hyphens", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_HYPHENS);
+ }
+ break;
+ case 'i':
+ if (strcmp("indices", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_INDICES);
+ }
+ break;
+ case 'l':
+ if (strcmp("lines", option + 8) == 0) {
+ int compare = TkTextIndexCompare(&indexFrom, &indexTo);
- if (compare > 0) {
- const TkTextIndex *tmpPtr = indexFromPtr;
+ if (compare == 0) {
+ value = 0;
+ } else {
+ const TkTextIndex *indexPtr1;
+ const TkTextIndex *indexPtr2;
- indexFromPtr = indexToPtr;
- indexToPtr = tmpPtr;
+ if (compare < 0) {
+ indexPtr1 = &indexFrom;
+ indexPtr2 = &indexTo;
+ } else {
+ indexPtr1 = &indexTo;
+ indexPtr2 = &indexFrom;
+ }
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ value = TkTextCountDisplayLines(textPtr, indexPtr1, indexPtr2);
+ if (compare > 0) {
+ value = -value;
+ }
+ }
+ }
+ break;
+ case 't':
+ if (strcmp("text", option + 8) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_DISPLAY_TEXT);
+ }
+ break;
+ }
}
-
- lastPtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree,textPtr));
- fromPtr = indexFromPtr->linePtr;
- if (fromPtr == lastPtr) {
- goto countDone;
+ break;
+ case 'h':
+ if (strncmp("-hyphens", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_HYPHENS);
}
-
- /*
- * 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' (except if this line is elided), and
- * then subtract off what came in too much from elided lines,
- * also subtract off what we didn't want from 'from' and add
- * on what we didn't count from 'to'.
- */
-
- while (TkTextIndexCmp(&index,indexToPtr) < 0) {
- value += TkTextUpdateOneLine(textPtr, index.linePtr,
- 0, &index, 0);
+ break;
+ case 'i':
+ if (strncmp("-indices", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_INDICES);
}
-
- index2 = index;
-
- /*
- * Now we need to adjust the count to:
- * - subtract off the number of display lines between
- * indexToPtr and index2, since we might have skipped past
- * indexToPtr, if we have several logical lines in a
- * single display line
- * - subtract off the number of display lines overcounted
- * in the first logical line
- * - add on the number of display lines in the last logical
- * line
- * This logic is still ok if both indexFromPtr and indexToPtr
- * are in the same logical line.
- */
-
- index = *indexToPtr;
- index.byteIndex = 0;
- while (TkTextIndexCmp(&index,&index2) < 0) {
- value -= TkTextUpdateOneLine(textPtr, index.linePtr,
- 0, &index, 0);
+ break;
+ case 'l':
+ if (strncmp("-lines", option, length) == 0) {
+ TkTextBTree tree = sharedTextPtr->tree;
+ value = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&indexTo), NULL)
+ - TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&indexFrom), NULL);
}
- index.linePtr = indexFromPtr->linePtr;
- index.byteIndex = 0;
- while (1) {
- TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
- if (TkTextIndexCmp(&index,indexFromPtr) >= 0) {
- break;
- }
- TkTextIndexForwBytes(textPtr, &index, 1, &index);
- value--;
-
+ break;
+ case 't':
+ if (strncmp("-text", option, length) == 0) {
+ value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_TEXT);
}
- if (indexToPtr->linePtr != lastPtr) {
- index.linePtr = indexToPtr->linePtr;
- index.byteIndex = 0;
- while (1) {
- TkTextFindDisplayLineEnd(textPtr, &index, 1, NULL);
- if (TkTextIndexCmp(&index,indexToPtr) >= 0) {
- break;
- }
- TkTextIndexForwBytes(textPtr, &index, 1, &index);
- value++;
- }
+ break;
+ case 'u':
+ if (strncmp("-update", option, length) == 0) {
+ update = true;
+ continue;
}
-
- if (compare > 0) {
- value = -value;
+ break;
+ case 'x':
+ if (strncmp("-xpixels", option, length) == 0) {
+ int x1, x2;
+ TkTextIndex index;
+
+ index = indexFrom;
+ TkTextFindDisplayIndex(textPtr, &index, 0, &x1);
+ index = indexTo;
+ TkTextFindDisplayIndex(textPtr, &index, 0, &x2);
+ value = x2 - x1;
}
- } 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);
+ break;
+ case 'y':
+ if (strncmp("-ypixels", option, length) == 0) {
+ int from, to;
+
+ if (update) {
+ from = TkTextIndexGetLineNumber(&indexFrom, textPtr);
+ to = TkTextIndexGetLineNumber(&indexTo, textPtr);
+ if (from != to) {
+ if (from > to) {
+ int tmp = from; from = to; to = tmp;
+ }
+ UpdateLineMetrics(textPtr, from, to);
+ }
+ }
+ from = TkTextIndexYPixels(textPtr, &indexFrom);
+ to = TkTextIndexYPixels(textPtr, &indexTo);
+ value = to - from;
}
- value = TkTextIndexYPixels(textPtr, indexToPtr)
- - TkTextIndexYPixels(textPtr, indexFromPtr);
- } else {
+ break;
+ }
+ if (value == INT_MIN) {
goto badOption;
}
- countDone:
- found++;
+ found += 1;
if (found == 1) {
Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
} else {
@@ -1041,9 +1910,7 @@ TextWidgetObjCmd(
* Use the default '-indices'.
*/
- int value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_INDICES);
-
+ int value = CountIndices(textPtr, &indexFrom, &indexTo, COUNT_INDICES);
Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
} else if (found > 1) {
Tcl_SetObjResult(interp, objPtr);
@@ -1052,9 +1919,9 @@ TextWidgetObjCmd(
badOption:
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad option \"%s\" must be -chars, -displaychars, "
- "-displayindices, -displaylines, -indices, -lines, -update, "
- "-xpixels, or -ypixels", Tcl_GetString(objv[i])));
+ "bad option \"%s\": must be -chars, -displaychars, -displayhyphens, -displayindices, "
+ "-displaylines, -displaytext, -hyphens, -indices, -lines, -text, -update, -xpixels, "
+ "or -ypixels", Tcl_GetString(objv[i])));
Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
result = TCL_ERROR;
goto done;
@@ -1068,177 +1935,229 @@ TextWidgetObjCmd(
if (objc == 2) {
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(tkBTreeDebug));
} else {
- if (Tcl_GetBooleanFromObj(interp, objv[2],
- &tkBTreeDebug) != TCL_OK) {
+ if (Tcl_GetBooleanFromObj(interp, objv[2], &tkBTreeDebug) != TCL_OK) {
result = TCL_ERROR;
goto done;
}
tkTextDebug = tkBTreeDebug;
}
break;
- case TEXT_DELETE:
+ case TEXT_DELETE: {
+ int i, flags = 0;
+ bool ok = true;
+
+ for (i = 2; i < objc - 1; i++) {
+ const char *option = Tcl_GetString(objv[i]);
+ int length;
+
+ if (option[0] != '-') {
+ break;
+ }
+ length = GetByteLength(objv[i]);
+ if (strncmp("-marks", option, length) == 0) {
+ flags |= DELETE_MARKS;
+ } else if (strncmp("-inclusive", option, length) == 0) {
+ flags |= DELETE_INCLUSIVE;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -marks, or -inclusive", Tcl_GetString(objv[i])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ objv += i - 2;
+ objc -= i - 2;
+
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-marks? ?-inclusive? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
+ if (TestIfDisabled(interp, textPtr, &result)
+ || TestIfDead(interp, textPtr, &result)
+ || TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ if (objc < 5) {
+ /*
+ * Simple case requires no predetermination of indices.
+ */
- const TkTextIndex *indexPtr1, *indexPtr2;
+ TkTextIndex index1, index2, *index2Ptr;
+ bool triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ bool triggerWatch = triggerUserMod || sharedTextPtr->triggerAlways;
- /*
- * Parse the starting and stopping indices.
- */
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
+
+ /*
+ * Parse the starting and stopping indices.
+ */
- indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr,
- objv[2]);
- if (indexPtr1 == NULL) {
+ if (!TkTextGetIndexFromObj(textPtr->interp, textPtr, objv[2], &index1)) {
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (objc == 4) {
+ if (!TkTextGetIndexFromObj(textPtr->interp, textPtr, objv[3], index2Ptr = &index2)) {
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.
- */
-
- TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
- char *useIdx;
- int i;
+ index2Ptr = NULL;
+ }
+ ok = DeleteIndexRange(NULL, textPtr, &index1, index2Ptr, flags, true,
+ triggerWatch, triggerWatch, triggerUserMod, true);
+ } 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.
+ */
- objc -= 2;
- objv += 2;
- indices = ckalloc((objc + 1) * sizeof(TkTextIndex));
+ TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
+ char *useIdx;
+ int lastUsed, i;
- /*
- * First pass verifies that all indices are valid.
- */
+ objc -= 2;
+ objv += 2;
+ indices = malloc((objc + 1)*sizeof(TkTextIndex));
- for (i = 0; i < objc; i++) {
- const TkTextIndex *indexPtr =
- TkTextGetIndexFromObj(interp, textPtr, objv[i]);
+ /*
+ * First pass verifies that all indices are valid.
+ */
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- ckfree(indices);
- goto done;
- }
- indices[i] = *indexPtr;
+ for (i = 0; i < objc; i++) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &indices[i])) {
+ result = TCL_ERROR;
+ free(indices);
+ goto done;
}
+ }
- /*
- * Pad out the pairs evenly to make later code easier.
- */
+ /*
+ * Pad out the pairs evenly to make later code easier.
+ */
- if (objc & 1) {
- indices[i] = indices[i-1];
- TkTextIndexForwChars(NULL, &indices[i], 1, &indices[i],
- COUNT_INDICES);
- objc++;
- }
- useIdx = ckalloc(objc);
- memset(useIdx, 0, (unsigned) objc);
+ if (objc & 1) {
+ indices[i] = indices[i - 1];
+ TkTextIndexForwChars(textPtr, &indices[i], 1, &indices[i], COUNT_INDICES);
+ objc += 1;
+ }
+ useIdx = malloc(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(indices, (unsigned) objc / 2,
- 2 * sizeof(TkTextIndex), TextIndexSortProc);
- lastStart = NULL;
+ qsort(indices, (unsigned) objc/2, 2*sizeof(TkTextIndex), TextIndexSortProc);
+ lastStart = NULL;
+ lastUsed = 0; /* otherwise GCC complains */
- /*
- * Second pass will handle bogus ranges (end < start) and
- * overlapping ranges.
- */
+ /*
+ * Second pass will handle bogus ranges (end < start) and
+ * overlapping ranges.
+ */
+
+ for (i = 0; i < objc; i += 2) {
+ ixStart = &indices[i];
+ ixEnd = &indices[i + 1];
+ if (TkTextIndexCompare(ixEnd, ixStart) <= 0) {
+ continue;
+ }
+ if (lastStart) {
+ if (TkTextIndexCompare(ixStart, lastStart) == 0) {
+ /*
+ * Start indices were equal, and the sort placed
+ * the longest range first, so skip this one.
+ */
- for (i = 0; i < objc; i += 2) {
- ixStart = &indices[i];
- ixEnd = &indices[i+1];
- if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
continue;
- }
- if (lastStart) {
- if (TkTextIndexCmp(ixStart, lastStart) == 0) {
- /*
- * Start indices were equal, and the sort placed
- * the longest range first, so skip this one.
- */
+ } else if (TkTextIndexCompare(lastStart, ixEnd) < 0) {
+ /*
+ * The next pair has a start range before the end
+ * point of the last range. Constrain the delete
+ * range, but use the pointer values.
+ */
+ *ixEnd = *lastStart;
+ if (TkTextIndexCompare(ixEnd, ixStart) <= 0) {
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
- * range, but use the pointer values.
- */
-
- *ixEnd = *lastStart;
- if (TkTextIndexCmp(ixEnd, ixStart) <= 0) {
- continue;
- }
}
}
- lastStart = ixStart;
- useIdx[i] = 1;
}
+ lastStart = ixStart;
+ useIdx[i] = 1;
+ lastUsed = i;
+ }
- /*
- * 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 < objc; i += 2) {
- if (useIdx[i]) {
- /*
- * We don't need to check the return value because all
- * indices are preparsed above.
- */
+ for (i = 0; i < objc && ok; i += 2) {
+ if (useIdx[i]) {
+ bool triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[i]);
+ bool triggerWatch = triggerUserMod || sharedTextPtr->triggerAlways;
- DeleteIndexRange(NULL, textPtr, &indices[i],
- &indices[i+1], 1);
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
}
+
+ /*
+ * We don't need to check the return value because all
+ * indices are preparsed above.
+ */
+
+ ok = DeleteIndexRange(NULL, textPtr, &indices[i], &indices[i + 1],
+ flags, true, triggerWatch, triggerWatch, triggerUserMod, i == lastUsed);
}
- ckfree(indices);
}
+ free(indices);
+ free(useIdx);
+ }
+
+ if (!ok) {
+ return TCL_OK; /* widget has been destroyed */
}
break;
+ }
case TEXT_DLINEINFO: {
- int x, y, width, height, base;
- const TkTextIndex *indexPtr;
+ int x, y, width, height, base, argc = 2;
+ bool extents = false;
+ TkTextIndex index;
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index");
+ if (objc == 4) {
+ const char* option = Tcl_GetString(objv[2]);
+
+ if (strcmp(option, "-extents") == 0) {
+ extents = true;
+ argc += 1;
+ } else if (*option == '-') {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -extents", option));
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+ if (objc - argc + 2 != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-extents? index");
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[argc], &index)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height,
- &base) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
+ if (TkTextGetDLineInfo(textPtr, &index, extents, &x, &y, &width, &height, &base)) {
+ Tcl_Obj *listObj = Tcl_NewObj();
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
@@ -1257,94 +2176,126 @@ TextWidgetObjCmd(
result = TextEditCmd(textPtr, interp, objc, objv);
break;
case TEXT_GET: {
- Tcl_Obj *objPtr = NULL;
- int i, found = 0, visible = 0;
- const char *name;
- int length;
+ Tcl_Obj *objPtr;
+ int i, found;
+ bool includeHyphens;
+ bool visibleOnly;
+ unsigned countOptions;
+ const char *option;
if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-displaychars? ?--? index1 ?index2 ...?");
+ Tcl_WrongNumArgs(interp, 2, objv, "?-option? ?--? index1 ?index2 ...?");
result = TCL_ERROR;
goto done;
}
- /*
- * Simple, restrictive argument parsing. The only options are -- and
- * -displaychars (or any unique prefix).
- */
-
+ objPtr = NULL;
+ found = 0;
+ includeHyphens = true;
+ visibleOnly = false;
+ countOptions = 0;
i = 2;
- if (objc > 3) {
- name = Tcl_GetString(objv[i]);
- length = objv[i]->length;
- if (length > 1 && name[0] == '-') {
- if (strncmp("-displaychars", name, (unsigned) length) == 0) {
- i++;
- visible = 1;
- name = Tcl_GetString(objv[i]);
- length = objv[i]->length;
+
+ while (objc > i + 1 && (option = Tcl_GetString(objv[i]))[0] == '-') {
+ bool badOption = false;
+
+ i += 1;
+
+ if (option[1] == '-') {
+ if (option[2] == '\0') {
+ break;
}
- if ((i < objc-1) && (length == 2) && !strcmp("--", name)) {
- i++;
+ badOption = true;
+ } else if (++countOptions > 1) {
+ i -= 1;
+ break;
+ } else {
+ switch (option[1]) {
+ case 'c':
+ if (strcmp("-chars", option) != 0) {
+ badOption = true;
+ }
+ break;
+ case 't':
+ if (strcmp("-text", option) != 0) {
+ badOption = true;
+ }
+ includeHyphens = false;
+ break;
+ case 'd':
+ if (strcmp("-displaychars", option) == 0) {
+ visibleOnly = true;
+ } else if (strcmp("-displaytext", option) == 0) {
+ visibleOnly = true;
+ includeHyphens = false;
+ } else {
+ badOption = true;
+ }
+ break;
+ default:
+ badOption = true;
+ break;
}
}
+
+ if (badOption) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": "
+ "must be -chars, -displaychars, -displaytext, or -text", option));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
}
for (; i < objc; i += 2) {
- const TkTextIndex *index1Ptr, *index2Ptr;
- TkTextIndex index2;
+ TkTextIndex index1, index2;
+ Tcl_Obj *get;
- index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i]);
- if (index1Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i], &index1)) {
if (objPtr) {
- Tcl_DecrRefCount(objPtr);
+ Tcl_GuardedDecrRefCount(objPtr);
}
result = TCL_ERROR;
goto done;
}
- if (i+1 == objc) {
- TkTextIndexForwChars(NULL, index1Ptr, 1, &index2,
- COUNT_INDICES);
- index2Ptr = &index2;
+ if (i + 1 == objc) {
+ TkTextIndexForwChars(textPtr, &index1, 1, &index2, COUNT_INDICES);
} else {
- index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i+1]);
- if (index2Ptr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[i + 1], &index2)) {
if (objPtr) {
- Tcl_DecrRefCount(objPtr);
+ Tcl_GuardedDecrRefCount(objPtr);
}
result = TCL_ERROR;
goto done;
}
+ if (TkTextIndexCompare(&index1, &index2) >= 0) {
+ 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!
- */
+ /*
+ * 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!
+ */
- Tcl_Obj *get = TextGetText(textPtr, index1Ptr, index2Ptr,
- visible);
+ get = TextGetText(textPtr, &index1, &index2, NULL, NULL, UINT_MAX,
+ visibleOnly, includeHyphens);
- found++;
- if (found == 1) {
- 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.
- */
+ if (++found == 1) {
+ 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.
+ */
- objPtr = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, objPtr,
- Tcl_GetObjResult(interp));
- }
- Tcl_ListObjAppendElement(NULL, objPtr, get);
+ objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, Tcl_GetObjResult(interp));
}
+ Tcl_ListObjAppendElement(NULL, objPtr, get);
}
}
if (found > 1) {
@@ -1356,78 +2307,187 @@ TextWidgetObjCmd(
result = TkTextImageCmd(textPtr, interp, objc, objv);
break;
case TEXT_INDEX: {
- const TkTextIndex *indexPtr;
+ TkTextIndex index;
if (objc != 3) {
Tcl_WrongNumArgs(interp, 2, objv, "index");
result = TCL_ERROR;
goto done;
}
-
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- Tcl_SetObjResult(interp, TkTextNewIndexObj(textPtr, indexPtr));
+ Tcl_SetObjResult(interp, TkTextNewIndexObj(&index));
break;
}
- case TEXT_INSERT: {
- const TkTextIndex *indexPtr;
+ case TEXT_INSERT:
+ case TEXT_TK_TEXTINSERT: {
+ TkTextIndex index;
+ bool triggerUserMod, triggerWatch;
if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index chars ?tagList chars tagList ...?");
+ const char *args = (commandIndex == TEXT_TK_TEXTINSERT) ?
+ "?-hyphentags tags? index chars ?tagList chars tagList ...?" :
+ "index chars ?tagList chars tagList ...?";
+ Tcl_WrongNumArgs(interp, 2, objv, args);
result = TCL_ERROR;
goto done;
}
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- result = TextInsertCmd(NULL, textPtr, interp, objc-3, objv+3,
- indexPtr, 1);
+ if (TestIfDisabled(interp, textPtr, &result)
+ || TestIfDead(interp, textPtr, &result)
+ || TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
}
+
+ triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ triggerWatch = triggerUserMod || sharedTextPtr->triggerAlways;
+
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
+ result = TextInsertCmd(textPtr, interp, objc - 3, objv + 3, &index, true, triggerWatch,
+ triggerWatch, triggerUserMod, commandIndex == TEXT_TK_TEXTINSERT);
break;
}
- case TEXT_MARK:
- result = TkTextMarkCmd(textPtr, interp, objc, objv);
+ case TEXT_INSPECT:
+ result = TextInspectCmd(textPtr, interp, objc, objv);
break;
- case TEXT_PEER:
- result = TextPeerCmd(textPtr, interp, objc, objv);
+ case TEXT_ISCLEAN: {
+ bool discardSelection = false;
+ const TkText *myTextPtr = textPtr;
+ int i;
+
+ for (i = 2; i < objc; ++i) {
+ char const * opt = Tcl_GetString(objv[i]);
+
+ if (strcmp(opt, "-overall") == 0) {
+ myTextPtr = NULL;
+ } else if (strcmp(opt, "-discardselection") == 0) {
+ discardSelection = true;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -overall", opt));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(IsClean(sharedTextPtr, myTextPtr, discardSelection)));
break;
- case TEXT_PENDINGSYNC: {
- if (objc != 2) {
- Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ }
+ case TEXT_ISDEAD:
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TkTextIsDeadPeer(textPtr)));
+ break;
+ case TEXT_ISEMPTY: {
+ bool overall = false;
+ int i;
+
+ for (i = 2; i < objc; ++i) {
+ char const * opt = Tcl_GetString(objv[i]);
+
+ if (strcmp(opt, "-overall") == 0) {
+ overall = true;
+ } else {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad option \"%s\": must be -overall", opt));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ }
+
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(IsEmpty(sharedTextPtr, overall ? NULL : textPtr)));
+ break;
+ }
+ case TEXT_LINENO: {
+ TkTextIndex index;
+ int lineno;
+
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index");
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &index)) {
result = TCL_ERROR;
goto done;
}
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(TkTextPendingsync(textPtr)));
+ lineno = TkTextIsDeadPeer(textPtr) ? 0 : TkTextIndexGetLineNumber(&index, textPtr) + 1;
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(lineno));
break;
}
- case TEXT_REPLACE: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
+ case TEXT_LOAD: {
+ Tcl_Obj *contentObjPtr;
+ bool validOptions = false;
- if (objc < 5) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index1 index2 chars ?tagList chars tagList ...?");
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "textcontent");
result = TCL_ERROR;
goto done;
}
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexFromPtr == NULL) {
+ if (objc == 4) {
+ const char *opt = Tcl_GetString(objv[2]);
+
+ if (strcmp(opt, "-validconfig") != 0) {
+ Tcl_SetObjResult(interp,
+ Tcl_ObjPrintf("bad option \"%s\": must be -validconfig", opt));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "BAD_OPTION", NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ validOptions = true;
+ contentObjPtr = objv[3];
+ } else {
+ contentObjPtr = objv[2];
+ }
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, &result)) {
+ goto done;
+ }
+ ClearText(textPtr, false);
+ TkTextRelayoutWindow(textPtr, TK_TEXT_LINE_GEOMETRY);
+ if ((result = TkBTreeLoad(textPtr, contentObjPtr, validOptions)) != TCL_OK) {
+ ClearText(textPtr, false);
+ }
+ break;
+ }
+ case TEXT_MARK:
+ result = TkTextMarkCmd(textPtr, interp, objc, objv);
+ break;
+ case TEXT_PEER:
+ result = TextPeerCmd(textPtr, interp, objc, objv);
+ break;
+ case TEXT_PENDINGSYNC: {
+ if (objc != 2) {
+ Tcl_WrongNumArgs(interp, 2, objv, NULL);
+ result = TCL_ERROR;
+ goto done;
+ }
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(TkTextPendingSync(textPtr)));
+ break;
+ }
+ case TEXT_REPLACE:
+ case TEXT_TK_TEXTREPLACE: {
+ TkTextIndex indexFrom, indexTo, index;
+ bool triggerUserMod, triggerWatch;
+
+ if (objc < 5) {
+ Tcl_WrongNumArgs(interp, 2, objv, "index1 index2 chars ?tagList chars tagList ...?");
result = TCL_ERROR;
goto done;
}
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]);
- if (indexToPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[2], &indexFrom)
+ || !TkTextGetIndexFromObj(interp, textPtr, objv[3], &indexTo)) {
result = TCL_ERROR;
goto done;
}
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) {
+ if (TkTextIndexCompare(&indexFrom, &indexTo) > 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
"index \"%s\" before \"%s\" in the text",
Tcl_GetString(objv[3]), Tcl_GetString(objv[2])));
@@ -1435,81 +2495,88 @@ TextWidgetObjCmd(
result = TCL_ERROR;
goto done;
}
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- int lineNum, byteIndex;
- TkTextIndex index;
+ if (TestIfDisabled(interp, textPtr, &result) || TestIfDead(interp, textPtr, &result)) {
+ goto done;
+ }
- /*
- * 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.
- */
+ triggerUserMod = TestIfTriggerUserMod(sharedTextPtr, objv[2]);
+ triggerWatch = triggerUserMod || sharedTextPtr->triggerAlways;
- byteIndex = textPtr->topIndex.byteIndex;
- lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
+ /*
+ * 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.
+ */
- 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.
- */
+ TkTextIndexSave(&textPtr->topIndex);
+ if (triggerWatch) {
+ TkTextSaveCursorIndex(textPtr);
+ }
- int deleteInsertOffset, insertLength, j;
+ TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- insertLength = 0;
- for (j = 4; j < objc; j += 2) {
- insertLength += Tcl_GetCharLength(objv[j]);
- }
+ if (TkTextIndexCompare(&indexFrom, &index) < 0 && TkTextIndexCompare(&index, &indexTo) <= 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.
+ */
- /*
- * Calculate 'deleteInsertOffset' as an offset we will apply
- * to the insertion point after this operation.
- */
+ int deleteInsertOffset, insertLength, j;
- deleteInsertOffset = CountIndices(textPtr, indexFromPtr,
- &index, COUNT_CHARS);
- if (deleteInsertOffset > insertLength) {
- deleteInsertOffset = insertLength;
- }
+ insertLength = 0;
+ for (j = 4; j < objc; j += 2) {
+ insertLength += Tcl_GetCharLength(objv[j]);
+ }
- result = TextReplaceCmd(textPtr, interp, indexFromPtr,
- indexToPtr, objc, objv, 0);
+ /*
+ * Calculate 'deleteInsertOffset' as an offset we will apply
+ * to the insertion point after this operation.
+ */
- if (result == TCL_OK) {
- /*
- * Move the insertion position to the correct place.
- */
+ deleteInsertOffset = CountIndices(textPtr, &indexFrom, &index, COUNT_CHARS);
+ if (deleteInsertOffset > insertLength) {
+ deleteInsertOffset = insertLength;
+ }
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- 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);
+ result = TextReplaceCmd(textPtr, interp, &indexFrom, &indexTo, objc, objv, false,
+ triggerWatch, triggerUserMod, commandIndex == TEXT_TK_TEXTREPLACE);
+ if (textPtr->flags & DESTROYED) {
+ return result;
}
+
if (result == TCL_OK) {
/*
- * Now ensure the top-line is in the right place.
+ * Move the insertion position to the correct place.
*/
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineNum, byteIndex, &index);
- TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST);
+ TkTextIndexForwChars(textPtr, &indexFrom, deleteInsertOffset, &index, COUNT_INDICES);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->insertMarkPtr);
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->insertMarkPtr, &index);
+ textPtr->insertIndex = index;
+ }
+ } else {
+ result = TextReplaceCmd(textPtr, interp, &indexFrom, &indexTo, objc, objv, false,
+ triggerWatch, triggerUserMod, commandIndex == TEXT_TK_TEXTREPLACE);
+ if (textPtr->flags & DESTROYED) {
+ return result;
+ }
+ }
+ if (result == TCL_OK) {
+ /*
+ * Now ensure the top-line is in the right place.
+ */
+
+ if (!TkTextIndexRebuild(&textPtr->topIndex)) {
+ TkTextSetYView(textPtr, &textPtr->topIndex, TK_TEXT_NOPIXELADJUST);
}
}
break;
@@ -1524,41 +2591,98 @@ TextWidgetObjCmd(
result = TkTextSeeCmd(textPtr, interp, objc, objv);
break;
case TEXT_SYNC: {
- if (objc == 4) {
- Tcl_Obj *cmd = objv[3];
+ bool wrongNumberOfArgs = false;
+
+ if (objc == 3 || objc == 4) {
const char *option = Tcl_GetString(objv[2]);
- if (strncmp(option, "-command", objv[2]->length)) {
- Tcl_AppendResult(interp, "wrong option \"", option, "\": should be \"-command\"", NULL);
+ if (*option != '-') {
+ wrongNumberOfArgs = true;
+ } else if (strncmp(option, "-command", objv[2]->length) != 0) {
+ Tcl_AppendResult(interp, "wrong option \"", option,
+ "\": should be \"-command\"", NULL);
result = TCL_ERROR;
goto done;
}
- Tcl_IncrRefCount(cmd);
- if (TkTextPendingsync(textPtr)) {
- if (textPtr->afterSyncCmd) {
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- }
- textPtr->afterSyncCmd = cmd;
- } else {
- textPtr->afterSyncCmd = cmd;
- Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
- }
- break;
} else if (objc != 2) {
- Tcl_WrongNumArgs(interp, 2, objv, "?-command command?");
+ wrongNumberOfArgs = true;
+ }
+ if (wrongNumberOfArgs) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?-command ?command??");
result = TCL_ERROR;
goto done;
}
- if (textPtr->afterSyncCmd) {
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
+ if (!sharedTextPtr->allowUpdateLineMetrics) {
+ ProcessConfigureNotify(textPtr, true);
+ }
+ if (objc == 3) {
+ if (textPtr->afterSyncCmd) {
+ Tcl_SetObjResult(interp, textPtr->afterSyncCmd);
+ }
+ } else if (objc == 4) {
+ Tcl_Obj *cmd = objv[3];
+ const char *script = Tcl_GetString(cmd);
+ bool append = false;
+
+ if (*script == '+') {
+ script += 1;
+ append = true;
+ }
+
+ if (!textPtr->afterSyncCmd) {
+ if (append) {
+ cmd = Tcl_NewStringObj(script, -1);
+ }
+ Tcl_IncrRefCount(textPtr->afterSyncCmd = cmd);
+ } else {
+ if (!append && *script == '\0') {
+ if (textPtr->pendingAfterSync) {
+ Tcl_CancelIdleCall(RunAfterSyncCmd, (ClientData) textPtr);
+ textPtr->pendingAfterSync = false;
+ }
+ cmd = NULL;
+ } else {
+ if (append) {
+ cmd = AppendScript(Tcl_GetString(textPtr->afterSyncCmd), script);
+ }
+ Tcl_IncrRefCount(cmd);
+ }
+ Tcl_GuardedDecrRefCount(textPtr->afterSyncCmd);
+ textPtr->afterSyncCmd = cmd;
+ }
+ if (!textPtr->pendingAfterSync) {
+ textPtr->pendingAfterSync = true;
+ if (!TkTextPendingSync(textPtr)) {
+ Tcl_DoWhenIdle(RunAfterSyncCmd, (ClientData) textPtr);
+ }
+ }
+ } else {
+ textPtr->sendSyncEvent = true;
+
+ if (!TkTextPendingSync(textPtr)) {
+ /*
+ * There is nothing to sync, so fire the <<WidgetViewSync>> event,
+ * because nobody else will do this when no update is pending.
+ */
+ TkTextGenerateWidgetViewSyncEvent(textPtr, false);
+ } else {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ }
}
- textPtr->afterSyncCmd = NULL;
- TkTextUpdateLineMetrics(textPtr, 1,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr), -1);
break;
}
case TEXT_TAG:
result = TkTextTagCmd(textPtr, interp, objc, objv);
break;
+ case TEXT_WATCH: {
+ Tcl_Obj *cmd = textPtr->watchCmd;
+
+ result = TextWatchCmd(textPtr, interp, objc, objv);
+ if (cmd) {
+ Tcl_SetObjResult(interp, cmd);
+ Tcl_GuardedDecrRefCount(cmd);
+ }
+ break;
+ }
case TEXT_WINDOW:
result = TkTextWindowCmd(textPtr, interp, objc, objv);
break;
@@ -1571,119 +2695,325 @@ TextWidgetObjCmd(
}
done:
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+ if (--textPtr->refCount == 0) {
+ bool sharedIsReleased = textPtr->sharedIsReleased;
+
+ assert(textPtr->flags & MEM_RELEASED);
+ free(textPtr);
+ DEBUG_ALLOC(tkTextCountDestroyPeer++);
+ if (sharedIsReleased) {
+ return result;
+ }
+ textPtr = NULL;
+ } else if (textPtr->watchCmd) {
+ TkTextTriggerWatchCursor(textPtr);
+ }
+ if (sharedTextPtr->undoStackEvent) {
+ TriggerUndoStackEvent(sharedTextPtr);
+ }
+ sharedTextPtr->undoStackEvent = oldUndoStackEvent;
+
+ if (textPtr && textPtr->syncTime == 0) {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
}
+
return result;
}
/*
*--------------------------------------------------------------
*
- * SharedTextObjCmd --
+ * IsEmpty --
+ *
+ * Test whether this widget is empty. The widget is empty
+ * if it contains exact two single newline characters.
+ *
+ * Results:
+ * Returns true if the widget is empty, and false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+DoesNotContainTextSegments(
+ const TkTextSegment *segPtr1,
+ const TkTextSegment *segPtr2)
+{
+ for ( ; segPtr1 != segPtr2; segPtr1 = segPtr1->nextPtr) {
+ if (segPtr1->size > 0) {
+ return !segPtr1->nextPtr; /* ignore trailing newline */
+ }
+ }
+
+ return true;
+}
+
+static bool
+IsEmpty(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr) /* Can be NULL. */
+{
+ TkTextSegment *startMarker;
+ TkTextSegment *endMarker;
+
+ assert(sharedTextPtr);
+
+ if (TkBTreeNumLines(sharedTextPtr->tree, textPtr) > 1) {
+ return false;
+ }
+
+ if (textPtr) {
+ startMarker = textPtr->startMarker;
+ endMarker = textPtr->endMarker;
+ } else {
+ startMarker = sharedTextPtr->startMarker;
+ endMarker = sharedTextPtr->endMarker;
+ }
+
+ return DoesNotContainTextSegments(startMarker, endMarker);
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * IsClean --
*
- * 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'.
+ * Test whether this widget is clean. The widget is clean
+ * if it is empty, if no mark is set, and if the solely
+ * newline of this widget is untagged.
+ *
+ * Results:
+ * Returns true if the widget is clean, and false otherwise.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static bool
+ContainsAnySegment(
+ const TkTextSegment *segPtr1,
+ const TkTextSegment *segPtr2)
+{
+ for ( ; segPtr1 != segPtr2; segPtr1 = segPtr1->nextPtr) {
+ if (segPtr1->size > 0 || segPtr1->normalMarkFlag) {
+ return !!segPtr1->nextPtr; /* ignore trailing newline */
+ }
+ }
+
+ return false;
+}
+
+static bool
+IsClean(
+ const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, /* Can be NULL. */
+ bool discardSelection)
+{
+ const TkTextTagSet *tagInfoPtr;
+ const TkTextSegment *startMarker;
+ const TkTextSegment *endMarker;
+ const TkTextLine *endLine;
+
+ assert(sharedTextPtr);
+
+ if (TkBTreeNumLines(sharedTextPtr->tree, textPtr) > 1) {
+ return false;
+ }
+
+ if (textPtr) {
+ startMarker = textPtr->startMarker;
+ endMarker = textPtr->endMarker;
+ } else {
+ startMarker = sharedTextPtr->startMarker;
+ endMarker = sharedTextPtr->endMarker;
+ }
+
+ if (ContainsAnySegment(startMarker, endMarker)) {
+ return false;
+ }
+
+ endLine = endMarker->sectionPtr->linePtr;
+
+ if (!textPtr && ContainsAnySegment(endLine->segPtr, NULL)) {
+ /* This widget contains any mark on very last line. */
+ return false;
+ }
+
+ tagInfoPtr = endLine->prevPtr->lastPtr->tagInfoPtr;
+
+ return discardSelection ?
+ TkTextTagBitContainsSet(sharedTextPtr->selectionTags, tagInfoPtr) :
+ tagInfoPtr == sharedTextPtr->emptyTagInfoPtr;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextTestRelation --
+ *
+ * Given a relation (>0 for greater, =0 for equal, and <0 for
+ * less), this function computes whether the given operator
+ * satisfies this relation.
+ *
+ * Results:
+ * Returns 1 if the relation will be satsified, 0 if it will
+ * not be satisifed, and -1 if the operator is invalid.
+ *
+ * Side effects:
+ * None.
+ *
+ *--------------------------------------------------------------
+ */
+
+static int
+BadComparisonOperator(
+ Tcl_Interp *interp,
+ char const *op)
+{
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad comparison operator \"%s\": must be <, <=, ==, >=, >, or !=", op));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "COMPARISON", NULL);
+ return -1;
+}
+
+int
+TkTextTestRelation(
+ Tcl_Interp *interp, /* Current interpreter. */
+ int relation, /* Test this relation... */
+ char const *op) /* ...whether it will be satisifed by this operator. */
+{
+ int value;
+
+ if (op[0] == '<') {
+ value = (relation < 0);
+ if (op[1] == '=' && op[2] == 0) {
+ value = (relation <= 0);
+ } else if (op[1] != 0) {
+ return BadComparisonOperator(interp, op);
+ }
+ } else if (op[0] == '>') {
+ value = (relation > 0);
+ if (op[1] == '=' && op[2] == 0) {
+ value = (relation >= 0);
+ } else if (op[1] != 0) {
+ return BadComparisonOperator(interp, op);
+ }
+ } else if (op[0] == '=' && op[1] == '=' && op[2] == 0) {
+ value = (relation == 0);
+ } else if (op[0] == '!' && op[1] == '=' && op[2] == 0) {
+ value = (relation != 0);
+ } else {
+ return BadComparisonOperator(interp, op);
+ }
+
+ return value;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TextWatchCmd --
+ *
+ * This function is invoked to process the "text watch" Tcl command. See
+ * the user documentation for details on what it does.
*
* Results:
* A standard Tcl result.
*
* Side effects:
- * See the user documentation for "text".
+ * See the user documentation.
*
*--------------------------------------------------------------
*/
static int
-SharedTextObjCmd(
- ClientData clientData, /* Information about shared test B-tree. */
+TextWatchCmd(
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- register TkSharedText *sharedPtr = clientData;
- int result = TCL_OK;
- int index;
-
- static const char *const optionStrings[] = {
- "delete", "insert", NULL
- };
- enum options {
- TEXT_DELETE, TEXT_INSERT
- };
+ TkSharedText *sharedTextPtr;
- if (objc < 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
+ if (objc > 4) {
+ /* NOTE: avoid trigraph "??-" in string. */
+ Tcl_WrongNumArgs(interp, 4, objv, "\?\?-always? commandPrefix?");
return TCL_ERROR;
}
- if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
- return TCL_ERROR;
- }
+ sharedTextPtr = textPtr->sharedTextPtr;
- switch ((enum options) index) {
- case TEXT_DELETE:
- if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
- return TCL_ERROR;
+ if (objc <= 2) {
+ TkText *tPtr;
+
+ if (textPtr->watchCmd) {
+ textPtr->triggerAlways = false;
+ textPtr->watchCmd = NULL;
}
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
- TkTextIndex index1;
+ sharedTextPtr->triggerWatchCmd = false; /* do not trigger recursively */
+ sharedTextPtr->triggerAlways = false;
- /*
- * Parse the starting and stopping indices.
- */
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->watchCmd) {
+ sharedTextPtr->triggerWatchCmd = true;
+ if (tPtr->triggerAlways) {
+ sharedTextPtr->triggerAlways = true;
+ }
+ }
+ }
+ } else {
+ const char *script;
+ Tcl_Obj *cmd;
+ int argnum = 2;
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2],
- &index1);
- if (result != TCL_OK) {
- return result;
+ if (objc == 4) {
+ if (strcmp(Tcl_GetString(objv[2]), "-always") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -always", Tcl_GetString(objv[2])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "WATCH_OPTION", NULL);
+ return TCL_ERROR;
}
- if (objc == 4) {
- TkTextIndex index2;
+ textPtr->triggerAlways = true;
+ textPtr->sharedTextPtr->triggerAlways = true;
+ argnum = 3;
+ }
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[3],
- &index2);
- if (result != TCL_OK) {
- return result;
- }
- DeleteIndexRange(sharedPtr, NULL, &index1, &index2, 1);
+ cmd = objv[argnum];
+ script = Tcl_GetString(cmd);
+
+ if (*script == '+') {
+ script += 1;
+ if (textPtr->watchCmd) {
+ cmd = AppendScript(Tcl_GetString(textPtr->watchCmd), script);
} else {
- DeleteIndexRange(sharedPtr, NULL, &index1, NULL, 1);
+ cmd = Tcl_NewStringObj(script, -1);
}
- return TCL_OK;
- } else {
- /* Too many arguments */
- return TCL_ERROR;
- }
- break;
- case TEXT_INSERT: {
- TkTextIndex index1;
+ } else if (argnum == 2) {
+ TkText *tPtr;
- 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;
+ textPtr->triggerAlways = false;
+ textPtr->sharedTextPtr->triggerAlways = false;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->triggerAlways) {
+ assert(tPtr->watchCmd);
+ sharedTextPtr->triggerWatchCmd = true;
+ }
+ }
}
- return TextInsertCmd(sharedPtr, NULL, interp, objc-3, objv+3, &index1,
- 1);
- }
- default:
- return TCL_OK;
+
+ textPtr->sharedTextPtr->triggerWatchCmd = true;
+ Tcl_IncrRefCount(textPtr->watchCmd = cmd);
}
+
+ return TCL_OK;
}
/*
@@ -1713,18 +3043,13 @@ TextPeerCmd(
Tk_Window tkwin = textPtr->tkwin;
int index;
- static const char *const peerOptionStrings[] = {
- "create", "names", NULL
- };
- enum peerOptions {
- PEER_CREATE, PEER_NAMES
- };
+ static const char *const peerOptionStrings[] = { "create", "names", NULL };
+ enum peerOptions { PEER_CREATE, PEER_NAMES };
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
return TCL_ERROR;
}
-
if (Tcl_GetIndexFromObjStruct(interp, objv[2], peerOptionStrings,
sizeof(char *), "peer option", 0, &index) != TCL_OK) {
return TCL_ERROR;
@@ -1736,8 +3061,7 @@ TextPeerCmd(
Tcl_WrongNumArgs(interp, 3, objv, "pathName ?-option value ...?");
return TCL_ERROR;
}
- return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr,
- objc-2, objv+2);
+ return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr, objc - 2, objv + 2);
case PEER_NAMES: {
TkText *tPtr = textPtr->sharedTextPtr->peers;
Tcl_Obj *peersObj;
@@ -1747,10 +3071,9 @@ TextPeerCmd(
return TCL_ERROR;
}
peersObj = Tcl_NewObj();
- while (tPtr != NULL) {
+ while (tPtr) {
if (tPtr != textPtr) {
- Tcl_ListObjAppendElement(NULL, peersObj,
- TkNewWindowObj(tPtr->tkwin));
+ Tcl_ListObjAppendElement(NULL, peersObj, TkNewWindowObj(tPtr->tkwin));
}
tPtr = tPtr->next;
}
@@ -1764,6 +3087,39 @@ TextPeerCmd(
/*
*----------------------------------------------------------------------
*
+ * PushUndoSeparatorIfNeeded --
+ *
+ * Push undo separator if needed.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May push a separator onto undo stack.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+PushUndoSeparatorIfNeeded(
+ TkSharedText *sharedTextPtr,
+ bool autoSeparators,
+ TkTextEditMode currentEditMode)
+{
+ assert(sharedTextPtr->undoStack);
+
+ if (sharedTextPtr->pushSeparator
+ || (autoSeparators && sharedTextPtr->lastEditMode != currentEditMode)) {
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoPushSeparator(sharedTextPtr->undoStack, true);
+ sharedTextPtr->pushSeparator = false;
+ sharedTextPtr->lastUndoTokenType = -1;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TextReplaceCmd --
*
* This function is invoked to process part of the "replace" widget
@@ -1792,8 +3148,21 @@ TextReplaceCmd(
/* Index to which to replace. */
int objc, /* Number of arguments. */
Tcl_Obj *const objv[], /* Argument objects. */
- int viewUpdate) /* Update vertical view if set. */
+ bool viewUpdate, /* Update vertical view if set. */
+ bool triggerWatch, /* Should we trigger the watch command? */
+ bool userFlag, /* Trigger due to user modification? */
+ bool parseHyphens) /* Should we parse hyphens (tk_textReplace)? */
{
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ int origAutoSep = sharedTextPtr->autoSeparators;
+ int result = TCL_OK;
+ TkTextIndex indexTmp;
+ bool notDestroyed;
+
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ textPtr->refCount += 1;
+
/*
* Perform the deletion and insertion, but ensure no undo-separator is
* placed between the two operations. Since we are using the helper
@@ -1802,36 +3171,30 @@ TextReplaceCmd(
* 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);
- }
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->autoSeparators = false;
+ PushUndoSeparatorIfNeeded(sharedTextPtr, origAutoSep, TK_TEXT_EDIT_REPLACE);
}
- /*
- * 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]
- */
-
+ /* The line and segment storage may change when deleting. */
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);
+ TkTextIndexSave(&indexTmp);
+
+ notDestroyed = DeleteIndexRange(NULL, textPtr, indexFromPtr, indexToPtr,
+ 0, viewUpdate, triggerWatch, false, userFlag, true);
+
+ if (notDestroyed) {
+ TkTextIndexRebuild(&indexTmp);
+ result = TextInsertCmd(textPtr, interp, objc - 4, objv + 4, &indexTmp,
+ viewUpdate, false, triggerWatch, userFlag, parseHyphens);
+ }
- if (textPtr->sharedTextPtr->undo) {
- textPtr->sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
- textPtr->sharedTextPtr->autoSeparators = origAutoSep;
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
+ sharedTextPtr->autoSeparators = origAutoSep;
}
+ TkTextDecrRefCountAndTestIfDestroyed(textPtr);
return result;
}
@@ -1844,9 +3207,9 @@ TextReplaceCmd(
* *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 less than zero if the first argument should be before
+ * the second element, 0 if it's equivalent, and greater than zero if it should
+ * be after the second element.
*
* Side effects:
* None.
@@ -1861,7 +3224,7 @@ TextIndexSortProc(
{
TkTextIndex *pair1 = (TkTextIndex *) first;
TkTextIndex *pair2 = (TkTextIndex *) second;
- int cmp = TkTextIndexCmp(&pair1[1], &pair2[1]);
+ int cmp = TkTextIndexCompare(&pair1[1], &pair2[1]);
if (cmp == 0) {
/*
@@ -1870,14 +3233,240 @@ TextIndexSortProc(
* index pair.
*/
- cmp = TkTextIndexCmp(&pair1[0], &pair2[0]);
+ cmp = TkTextIndexCompare(&pair1[0], &pair2[0]);
}
- if (cmp > 0) {
- return -1;
- } else if (cmp < 0) {
- return 1;
+
+ return -cmp;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeEmbeddedWindows --
+ *
+ * Free up any embedded windows which belong to this widget.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * All embedded windows of this widget will be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeEmbeddedWindows(
+ TkText *textPtr) /* The concerned text widget. */
+{
+ Tcl_HashSearch search;
+ Tcl_HashEntry *hPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+
+ for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search);
+ hPtr;
+ hPtr = Tcl_NextHashEntry(&search)) {
+ TkTextSegment *ewPtr = Tcl_GetHashValue(hPtr);
+ TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
+ TkTextEmbWindowClient **prev = &ewPtr->body.ew.clients;
+
+ while (client) {
+ TkTextEmbWindowClient *next = client->next;
+ if (client->textPtr == textPtr && client->hPtr == hPtr) {
+ TkTextWinFreeClient(hPtr, client);
+ *prev = next;
+ } else {
+ prev = &client->next;
+ }
+ client = next;
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * ClearText --
+ *
+ * This function is invoked when we reset a text widget to it's intitial
+ * state, but without resetting options. We will free up many of the
+ * internal structure.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Almost everything associated with the text content is cleared.
+ * Note that all the peers of the shared structure will be cleared.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+ClearRetainedUndoTokens(
+ TkSharedText *sharedTextPtr)
+{
+ unsigned i;
+
+ assert(sharedTextPtr);
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextReleaseUndoTagToken(sharedTextPtr, sharedTextPtr->undoTagList[i]);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextReleaseUndoMarkTokens(sharedTextPtr, &sharedTextPtr->undoMarkList[i]);
+ }
+
+ sharedTextPtr->undoTagListCount = 0;
+ sharedTextPtr->undoMarkListCount = 0;
+}
+
+static void
+ClearText(
+ TkText *textPtr, /* Clean up this text widget. */
+ bool clearTags) /* Also clear all tags? */
+{
+ TkTextSegment *retainedMarks;
+ TkTextIndex startIndex;
+ TkText *tPtr;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ unsigned oldEpoch = TkBTreeEpoch(sharedTextPtr->tree);
+ bool steadyMarks = textPtr->sharedTextPtr->steadyMarks;
+ bool debug = tkBTreeDebug;
+
+ tkBTreeDebug = false; /* debugging is not wanted here */
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->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.
+ *
+ * Do not clear the after sync commands, otherwise the widget may hang.
+ */
+
+ tPtr->refCount += 1;
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->insertMarkPtr);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->currentMarkPtr);
+ if (clearTags) {
+ TkTextFreeAllTags(tPtr);
+ }
+ FreeEmbeddedWindows(tPtr);
+ TkTextFreeDInfo(tPtr);
+ textPtr->dInfoPtr = NULL;
+ textPtr->dontRepick = false;
+ tPtr->abortSelections = true;
+ textPtr->lastLineY = TK_TEXT_NEARBY_IS_UNDETERMINED;
+ tPtr->refCount -= 1;
+ tPtr->startLine = NULL;
+ tPtr->endLine = NULL;
+
+ if (tPtr->startMarker->refCount == 1) {
+ assert(textPtr->startMarker != textPtr->sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->startMarker);
+ FREE_SEGMENT(tPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ (tPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ }
+ if (tPtr->endMarker->refCount == 1) {
+ assert(textPtr->endMarker != textPtr->sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, tPtr->endMarker);
+ FREE_SEGMENT(tPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ (tPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ }
+ }
+
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkBTreeUnlinkSegment(sharedTextPtr, sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, sharedTextPtr->endMarker);
+ sharedTextPtr->startMarker->nextPtr = sharedTextPtr->startMarker->prevPtr = NULL;
+ sharedTextPtr->endMarker->nextPtr = sharedTextPtr->endMarker->prevPtr = NULL;
+ TkBTreeDestroy(sharedTextPtr->tree);
+ retainedMarks = TkTextFreeMarks(sharedTextPtr, true);
+ Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
+
+ if (clearTags) {
+ Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
+ if (sharedTextPtr->tagBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->tagBindingTable);
+ }
+ sharedTextPtr->numMotionEventBindings = 0;
+ sharedTextPtr->numElisionTags = 0;
+ }
+
+ /*
+ * Rebuild the internal structures.
+ */
+
+ Tcl_InitHashTable(&sharedTextPtr->windowTable, TCL_STRING_KEYS);
+ Tcl_InitHashTable(&sharedTextPtr->imageTable, TCL_STRING_KEYS);
+ TkTextUndoResetStack(sharedTextPtr->undoStack);
+ TkBitClear(sharedTextPtr->elisionTags);
+ TkBitClear(sharedTextPtr->selectionTags);
+ TkBitClear(sharedTextPtr->dontUndoTags);
+ TkBitClear(sharedTextPtr->affectDisplayTags);
+ TkBitClear(sharedTextPtr->notAffectDisplayTags);
+ TkBitClear(sharedTextPtr->affectDisplayNonSelTags);
+ TkBitClear(sharedTextPtr->affectGeometryTags);
+ TkBitClear(sharedTextPtr->affectGeometryNonSelTags);
+ TkBitClear(sharedTextPtr->affectLineHeightTags);
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isModified = false;
+ sharedTextPtr->isIrreversible = false;
+ sharedTextPtr->userHasSetModifiedFlag = false;
+ sharedTextPtr->haveToSetCurrentMark = false;
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->pushSeparator = false;
+ sharedTextPtr->imageCount = 0;
+ sharedTextPtr->tree = TkBTreeCreate(sharedTextPtr, oldEpoch + 1);
+ sharedTextPtr->insertDeleteUndoTokenCount = 0;
+
+ if (clearTags) {
+ sharedTextPtr->tagInfoSize = 0;
+ sharedTextPtr->tagBindingTable = NULL;
+ sharedTextPtr->numTags = 0;
+ sharedTextPtr->numEnabledTags = sharedTextPtr->numPeers; /* because the "sel" tag will survive */
+ Tcl_InitHashTable(&sharedTextPtr->tagTable, TCL_STRING_KEYS);
+ TkBitClear(sharedTextPtr->usedTags);
+ DEBUG(memset(sharedTextPtr->tagLookup, 0,
+ TkBitSize(sharedTextPtr->usedTags)*sizeof(TkTextTag *)));
+ }
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ TkTextCreateDInfo(tPtr);
+ TkBTreeAddClient(sharedTextPtr->tree, tPtr, tPtr->lineHeight);
+ TkTextIndexSetupToStartOfText(&startIndex, tPtr, sharedTextPtr->tree);
+ TkTextSetYView(tPtr, &startIndex, 0);
+ sharedTextPtr->tagLookup[tPtr->selTagPtr->index] = tPtr->selTagPtr;
+ TkBitSet(sharedTextPtr->usedTags, tPtr->selTagPtr->index);
+ tPtr->haveToSetCurrentMark = false;
+ TkBTreeLinkSegment(sharedTextPtr, tPtr->insertMarkPtr, &startIndex);
+ TkBTreeLinkSegment(sharedTextPtr, tPtr->currentMarkPtr, &startIndex);
+ tPtr->currentMarkIndex = startIndex;
+ }
+
+ sharedTextPtr->steadyMarks = false;
+ while (retainedMarks) {
+ TkTextSegment *nextPtr = retainedMarks->nextPtr;
+ TkTextIndexSetupToStartOfText(&startIndex, NULL, sharedTextPtr->tree);
+ TkBTreeLinkSegment(sharedTextPtr, retainedMarks, &startIndex);
+ retainedMarks = nextPtr;
+ }
+ sharedTextPtr->steadyMarks = steadyMarks;
+
+ TkTextResetDInfo(textPtr);
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+
+ if (debug) {
+ tkBTreeDebug = true;
+ TkBTreeCheck(sharedTextPtr->tree);
}
- return 0;
}
/*
@@ -1907,153 +3496,342 @@ static void
DestroyText(
TkText *textPtr) /* Info about text widget. */
{
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
- TkTextTag *tagPtr;
TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextStringList *listPtr;
+ bool debug = tkBTreeDebug;
+
+ tkBTreeDebug = false; /* debugging is not wanted here */
+
+ /*
+ * Firstly, remove pending idle commands, and free the array.
+ */
+
+ if (textPtr->pendingAfterSync) {
+ Tcl_CancelIdleCall(RunAfterSyncCmd, (ClientData) textPtr);
+ textPtr->pendingAfterSync = false;
+ }
+ if (textPtr->pendingFireEvent) {
+ Tcl_CancelIdleCall(FireWidgetViewSyncEvent, (ClientData) textPtr);
+ textPtr->pendingFireEvent = false;
+ }
+ if (textPtr->afterSyncCmd) {
+ Tcl_GuardedDecrRefCount(textPtr->afterSyncCmd);
+ }
/*
* 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);
textPtr->dInfoPtr = NULL;
+ textPtr->undo = false;
/*
- * Remove ourselves from the peer list.
+ * 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.
+ *
+ * Firstly unset all the variables bound to this widget.
*/
- 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;
- }
+ listPtr = textPtr->varBindingList;
+ while (listPtr) {
+ TkTextStringList *nextPtr = listPtr->nextPtr;
+
+ Tcl_UnsetVar2(textPtr->interp, Tcl_GetString(listPtr->strObjPtr), NULL, TCL_GLOBAL_ONLY);
+ Tcl_GuardedDecrRefCount(listPtr->strObjPtr);
+ free(listPtr);
+ listPtr = nextPtr;
}
/*
- * 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.
- *
+ * Unset the watch command.
+ */
+
+ if (textPtr->watchCmd) {
+ Tcl_GuardedDecrRefCount(textPtr->watchCmd);
+ }
+ TextWatchCmd(textPtr, NULL, 0, NULL);
+
+ /*
* 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(textPtr->insertMarkPtr);
- TkBTreeUnlinkSegment(textPtr->currentMarkPtr,
- textPtr->currentMarkPtr->body.mark.linePtr);
- ckfree(textPtr->currentMarkPtr);
+ TkTextDeleteTag(textPtr, textPtr->selTagPtr, NULL);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->insertMarkPtr);
+ FREE_SEGMENT(textPtr->insertMarkPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->currentMarkPtr);
+ FREE_SEGMENT(textPtr->currentMarkPtr);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FreeEmbeddedWindows(textPtr);
+
+ /*
+ * Clean up the -start/-end markers, do this after cleanup of other segments (not before).
+ */
+
+ if (textPtr->startMarker->refCount == 1) {
+ assert(textPtr->startMarker != sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ } else {
+ DEBUG(textPtr->startMarker->refCount -= 1);
+ }
+ if (textPtr->endMarker->refCount == 1) {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ } else {
+ DEBUG(textPtr->endMarker->refCount -= 1);
+ }
/*
* Now we've cleaned up everything of relevance to us in the B-tree, so we
- * disassociate outselves from it.
+ * disassociate ourselves from it.
*
* When the refCount reaches zero, it's time to clean up the shared
* portion of the text widget.
*/
- if (sharedTextPtr->refCount-- > 1) {
- TkBTreeRemoveClient(sharedTextPtr->tree, textPtr);
+ sharedTextPtr->refCount -= 1;
+
+ if (sharedTextPtr->refCount > 0) {
+ sharedTextPtr->numPeers -= 1;
/*
- * Free up any embedded windows which belong to this widget.
+ * No need to call 'TkBTreeRemoveClient' first, since this will do
+ * everything in one go, more quickly.
*/
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- TkTextEmbWindowClient *loop;
- TkTextSegment *ewPtr = Tcl_GetHashValue(hPtr);
+ TkBTreeRemoveClient(sharedTextPtr->tree, textPtr);
- 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;
+ /*
+ * Remove ourselves from the peer list.
+ */
- 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;
+ if (sharedTextPtr->peers == textPtr) {
+ sharedTextPtr->peers = textPtr->next;
+ } else {
+ TkText *nextPtr = sharedTextPtr->peers;
+ while (nextPtr) {
+ if (nextPtr->next == textPtr) {
+ nextPtr->next = textPtr->next;
+ break;
}
+ nextPtr = nextPtr->next;
}
}
+
+ if (textPtr->refCount == 1) {
+ /* Don't forget to release the current tag info. */
+ TkTextTagSetDecrRefCount(textPtr->curTagInfoPtr);
+ }
} else {
- /*
- * No need to call 'TkBTreeRemoveClient' first, since this will do
- * everything in one go, more quickly.
- */
+ /* Prevent that this resource will be released too early. */
+ textPtr->refCount += 1;
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDestroyStack(&sharedTextPtr->undoStack);
+ free(sharedTextPtr->undoTagList);
+ free(sharedTextPtr->undoMarkList);
TkBTreeDestroy(sharedTextPtr->tree);
+ assert(sharedTextPtr->startMarker->refCount == 1);
+ FREE_SEGMENT(sharedTextPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ assert(sharedTextPtr->endMarker->refCount == 1);
+ FREE_SEGMENT(sharedTextPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FREE_SEGMENT(sharedTextPtr->protectionMark[0]);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ FREE_SEGMENT(sharedTextPtr->protectionMark[1]);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ TkTextFreeAllTags(textPtr);
+ Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
+ TkTextFreeMarks(sharedTextPtr, false);
+ TkBitDestroy(&sharedTextPtr->usedTags);
+ TkBitDestroy(&sharedTextPtr->elisionTags);
+ TkBitDestroy(&sharedTextPtr->selectionTags);
+ TkBitDestroy(&sharedTextPtr->dontUndoTags);
+ TkBitDestroy(&sharedTextPtr->affectDisplayTags);
+ TkBitDestroy(&sharedTextPtr->notAffectDisplayTags);
+ TkBitDestroy(&sharedTextPtr->affectDisplayNonSelTags);
+ TkBitDestroy(&sharedTextPtr->affectGeometryTags);
+ TkBitDestroy(&sharedTextPtr->affectGeometryNonSelTags);
+ TkBitDestroy(&sharedTextPtr->affectLineHeightTags);
+ TkTextTagSetDestroy(&sharedTextPtr->emptyTagInfoPtr);
+ Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ TkTextDeleteBreakInfoTableEntries(&sharedTextPtr->breakInfoTable);
+ Tcl_DeleteHashTable(&sharedTextPtr->breakInfoTable);
+ free(sharedTextPtr->mainPeer);
+ free(sharedTextPtr->tagLookup);
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- tagPtr = Tcl_GetHashValue(hPtr);
+ if (sharedTextPtr->tagBindingTable) {
+ Tk_DeleteBindingTable(sharedTextPtr->tagBindingTable);
+ }
+ free(sharedTextPtr);
+ DEBUG_ALLOC(tkTextCountDestroyShared++);
- /*
- * No need to use 'TkTextDeleteTag' since we've already removed
- * the B-tree completely.
- */
+ textPtr->sharedIsReleased = true;
+ textPtr->refCount -= 1;
- TkTextFreeTag(textPtr, tagPtr);
- }
- Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- ckfree(Tcl_GetHashValue(hPtr));
- }
- Tcl_DeleteHashTable(&sharedTextPtr->markTable);
- TkUndoFreeStack(sharedTextPtr->undoStack);
+#if TK_CHECK_ALLOCS
+ /*
+ * Remove this shared resource from global list.
+ */
+ {
+ WatchShared *thisPtr = watchShared;
+ WatchShared *prevPtr = NULL;
+
+ while (thisPtr->sharedTextPtr != sharedTextPtr) {
+ prevPtr = thisPtr;
+ thisPtr = thisPtr->nextPtr;
+ assert(thisPtr);
+ }
- Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
- Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
+ if (prevPtr) {
+ prevPtr->nextPtr = thisPtr->nextPtr;
+ } else {
+ watchShared = thisPtr->nextPtr;
+ }
- if (sharedTextPtr->bindingTable != NULL) {
- Tk_DeleteBindingTable(sharedTextPtr->bindingTable);
+ free(thisPtr);
}
- ckfree(sharedTextPtr);
+#endif
}
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
+ if (textPtr->tabArrayPtr) {
+ free(textPtr->tabArrayPtr);
}
- if (textPtr->insertBlinkHandler != NULL) {
+ if (textPtr->insertBlinkHandler) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
}
textPtr->tkwin = NULL;
Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
- if (textPtr->afterSyncCmd){
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
+ assert(textPtr->flags & DESTROYED);
+ DEBUG(textPtr->flags |= MEM_RELEASED);
+ TkTextReleaseIfDestroyed(textPtr);
+ tkBTreeDebug = debug;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextDecrRefCountAndTestIfDestroyed --
+ *
+ * This function is decrementing the reference count of the text
+ * widget and destroys the widget if the reference count has been
+ * gone to zero.
+ *
+ * Results:
+ * Returns whether the widget has been destroyed.
+ *
+ * Side effects:
+ * Memory might be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextDecrRefCountAndTestIfDestroyed(
+ TkText *textPtr)
+{
+ if (--textPtr->refCount == 0) {
+ assert(textPtr->flags & DESTROYED);
+ assert(textPtr->flags & MEM_RELEASED);
+ free(textPtr);
+ DEBUG_ALLOC(tkTextCountDestroyPeer++);
+ return true;
+ }
+ return !!(textPtr->flags & DESTROYED);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextReleaseIfDestroyed --
+ *
+ * This function is decrementing the reference count of the text
+ * widget if it has been destroyed. In this case also the memory
+ * will be released.
+ *
+ * Results:
+ * Returns whether the widget was already destroyed.
+ *
+ * Side effects:
+ * Memory might be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextReleaseIfDestroyed(
+ TkText *textPtr)
+{
+ if (!(textPtr->flags & DESTROYED)) {
+ assert(textPtr->refCount > 0);
+ return false;
}
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
+ if (--textPtr->refCount == 0) {
+ assert(textPtr->flags & MEM_RELEASED);
+ free(textPtr);
+ DEBUG_ALLOC(tkTextCountDestroyPeer++);
}
+ return true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTestLangCode --
+ *
+ * Test the given language code, whether it satsifies ISO 539-1,
+ * and set an error message if the code is invalid.
+ *
+ * Results:
+ * The return value is 'tue' if given language code will be accepted,
+ * otherwise 'false' will be returned.
+ *
+ * Side effects:
+ * An error message in the interpreter may be set.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTestLangCode(
+ Tcl_Interp *interp,
+ Tcl_Obj *langCodePtr)
+{
+ char const *lang = Tcl_GetString(langCodePtr);
+
+ if (UCHAR(lang[0]) >= 0x80
+ || UCHAR(lang[1]) >= 0x80
+ || !isalpha(lang[0])
+ || !isalpha(lang[1])
+ || !islower(lang[0])
+ || !islower(lang[1])
+ || lang[2] != '\0') {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("bad lang \"%s\": "
+ "must have the form of an ISO 639-1 language code, or empty", lang));
+ Tcl_SetErrorCode(interp, "TK", "VALUE", "LANG", NULL);
+ return false;
+ }
+ return true;
}
/*
*----------------------------------------------------------------------
*
- * ConfigureText --
+ * TkConfigureText --
*
* This function is called to process an objv/objc list, plus the Tk
* option database, in order to configure (or reconfigure) a text widget.
@@ -2069,33 +3847,342 @@ DestroyText(
*----------------------------------------------------------------------
*/
-static int
-ConfigureText(
+static bool
+IsNumberOrEmpty(
+ const char *str)
+{
+ for ( ; *str; ++str) {
+ if (!isdigit(*str)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+int
+TkConfigureText(
Tcl_Interp *interp, /* Used for error reporting. */
- register TkText *textPtr, /* Information about widget; may or may not
+ 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;
+ TkTextIndex start, end, current;
+ unsigned currentEpoch;
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextBTree tree = sharedTextPtr->tree;
+ bool oldExport = textPtr->exportSelection;
+ bool oldTextDebug = tkTextDebug;
+ bool didHyphenate = textPtr->hyphenate;
+ int oldHyphenRules = textPtr->hyphenRules;
int mask = 0;
+ bool copyDownFlags = false;
+
+ tkTextDebug = false; /* debugging is not useful here */
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+
+ /*
+ * We want also to support the "-start", and "-end" abbreviations. The thing that
+ * Tcl supports abbreviated options is a real crux.
+ */
+
+ {
+ Tcl_Obj **myObjv;
+ Tcl_Obj *startLineObj = NULL;
+ Tcl_Obj *endLineObj = NULL;
+ Tcl_Obj *startIndexObj = NULL;
+ Tcl_Obj *endIndexObj = NULL;
+ int i, rc;
+
+ myObjv = malloc(objc * sizeof(Tcl_Obj *));
+
+ for (i = 0; i < objc; ++i) {
+ Tcl_Obj *obj = objv[i];
+
+ if (!(i & 1)) {
+ if (strcmp(Tcl_GetString(objv[i]), "-start") == 0) {
+ if (i + 1 < objc && IsNumberOrEmpty(Tcl_GetString(objv[i + 1]))) {
+ if (!startLineObj) {
+ Tcl_IncrRefCount(startLineObj = Tcl_NewStringObj("-startline", -1));
+ }
+ obj = startLineObj;
+ WarnAboutDeprecatedStartLineOption();
+ } else {
+ if (!startIndexObj) {
+ Tcl_IncrRefCount(startIndexObj = Tcl_NewStringObj("-startindex", -1));
+ }
+ obj = startIndexObj;
+ }
+ } else if (MatchOpt(Tcl_GetString(objv[i]), "-startline", 7)) {
+ if (!startLineObj) {
+ Tcl_IncrRefCount(startLineObj = Tcl_NewStringObj("-startline", -1));
+ }
+ obj = startLineObj;
+ WarnAboutDeprecatedStartLineOption();
+ } else if (MatchOpt(Tcl_GetString(objv[i]), "-startindex", 7)) {
+ if (!startIndexObj) {
+ Tcl_IncrRefCount(startIndexObj = Tcl_NewStringObj("-startindex", -1));
+ }
+ obj = startIndexObj;
+ } else if (strcmp(Tcl_GetString(objv[i]), "-end") == 0) {
+ if (i + 1 < objc && IsNumberOrEmpty(Tcl_GetString(objv[i + 1]))) {
+ if (!endLineObj) {
+ Tcl_IncrRefCount(endLineObj = Tcl_NewStringObj("-endline", -1));
+ }
+ obj = endLineObj;
+ WarnAboutDeprecatedEndLineOption();
+ } else {
+ if (!endIndexObj) {
+ Tcl_IncrRefCount(endIndexObj = Tcl_NewStringObj("-endindex", -1));
+ }
+ obj = endIndexObj;
+ }
+ } else if (MatchOpt(Tcl_GetString(objv[i]), "-endline", 5)) {
+ if (!endLineObj) {
+ Tcl_IncrRefCount(endLineObj = Tcl_NewStringObj("-endline", -1));
+ }
+ obj = endLineObj;
+ WarnAboutDeprecatedEndLineOption();
+ } else if (MatchOpt(Tcl_GetString(objv[i]), "-endindex", 5)) {
+ if (!endIndexObj) {
+ Tcl_IncrRefCount(endIndexObj = Tcl_NewStringObj("-endindex", -1));
+ }
+ obj = endIndexObj;
+ }
+ }
+ myObjv[i] = obj;
+ }
+
+ textPtr->selAttrs = textPtr->textConfigAttrs;
+ rc = Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable,
+ objc, myObjv, textPtr->tkwin, &savedOptions, &mask);
+
+ if (rc != TCL_OK) {
+ if (startLineObj && startIndexObj) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "cannot use both, -startindex, and deprecated -startline", -1));
+ rc = TCL_ERROR;
+ }
+ if (endLineObj && endIndexObj) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "cannot use both, -endindex, and deprecated -endline", -1));
+ rc = TCL_ERROR;
+ }
+ }
+
+ if (startLineObj) { Tcl_GuardedDecrRefCount(startLineObj); }
+ if (endLineObj) { Tcl_GuardedDecrRefCount(endLineObj); }
+ if (startIndexObj) { Tcl_GuardedDecrRefCount(startIndexObj); }
+ if (endIndexObj) { Tcl_GuardedDecrRefCount(endIndexObj); }
+
+ free(myObjv);
+
+ if (rc != TCL_OK) {
+ goto error;
+ }
+ }
+
+ if ((mask & TK_TEXT_INDEX_RANGE) == TK_TEXT_LINE_RANGE) {
+ TkTextIndexClear2(&start, NULL, tree);
+ TkTextIndexClear2(&end, NULL, tree);
+ TkTextIndexSetToStartOfLine2(&start, textPtr->startLine ?
+ textPtr->startLine : TkBTreeGetStartLine(textPtr));
+ TkTextIndexSetToStartOfLine2(&end, textPtr->endLine ?
+ textPtr->endLine : TkBTreeGetLastLine(textPtr));
+ if (textPtr->endLine && textPtr->startLine != textPtr->endLine) {
+ TkTextIndexBackChars(textPtr, &end, 1, &end, COUNT_INDICES);
+ }
+
+ if (TkTextIndexCompare(&start, &end) > 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(
+ "-startline must be less than or equal to -endline", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
+ goto error;
+ }
+
+ if (textPtr->endLine && textPtr->endLine != sharedTextPtr->endMarker->sectionPtr->linePtr) {
+ if (textPtr->endMarker->refCount > 1) {
+ textPtr->endMarker->refCount -= 1;
+ textPtr->endMarker = TkTextMakeStartEndMark(textPtr, &tkTextRightMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->endMarker, &end);
+ } else if (textPtr->endMarker != sharedTextPtr->endMarker) {
+ if (--textPtr->endMarker->refCount == 0) {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ }
+ if (textPtr->startLine
+ && textPtr->startLine != sharedTextPtr->startMarker->sectionPtr->linePtr) {
+ if (textPtr->startMarker->refCount > 1) {
+ textPtr->startMarker->refCount -= 1;
+ textPtr->startMarker = TkTextMakeStartEndMark(textPtr, &tkTextLeftMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->startMarker, &start);
+ } else if (textPtr->startMarker != sharedTextPtr->startMarker) {
+ if (--textPtr->startMarker->refCount == 0) {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ }
+ }
+
+#else /* if !SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+ textPtr->selAttrs = textPtr->textConfigAttrs;
if (Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable,
objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) {
+ textPtr->selAttrs = textPtr->selTagPtr->attrs;
+ tkTextDebug = oldTextDebug;
return TCL_ERROR;
}
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
+
+ if (sharedTextPtr->steadyMarks != textPtr->steadyMarks) {
+ if (!IsClean(sharedTextPtr, NULL, true)) {
+ ErrorNotAllowed(interp, "setting this option is possible only if the widget "
+ "is overall clean");
+ goto error;
+ }
+ }
+
+ /*
+ * Copy up shared flags.
+ */
+
+ /* This flag cannot alter if we have peers. */
+ sharedTextPtr->steadyMarks = textPtr->steadyMarks;
+
+ if (sharedTextPtr->autoSeparators != textPtr->autoSeparators) {
+ sharedTextPtr->autoSeparators = textPtr->autoSeparators;
+ copyDownFlags = true;
+ }
+
+ if (textPtr->undo != sharedTextPtr->undo) {
+ if (TestIfPerformingUndoRedo(interp, sharedTextPtr, NULL)) {
+ goto error;
+ }
+
+ assert(sharedTextPtr->undo == !!sharedTextPtr->undoStack);
+ sharedTextPtr->undo = textPtr->undo;
+ copyDownFlags = true;
+
+ if (sharedTextPtr->undo) {
+ sharedTextPtr->undoStack = TkTextUndoCreateStack(
+ sharedTextPtr->maxUndoDepth,
+ sharedTextPtr->maxRedoDepth,
+ sharedTextPtr->maxUndoSize,
+ TextUndoRedoCallback,
+ TextUndoFreeCallback,
+ TextUndoStackContentChangedCallback);
+ TkTextUndoSetContext(sharedTextPtr->undoStack, sharedTextPtr);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->pushSeparator = false;
+ sharedTextPtr->isIrreversible = false;
+ sharedTextPtr->isAltered = false;
+ } else {
+ sharedTextPtr->isIrreversible = TkTextUndoContentIsModified(sharedTextPtr->undoStack);
+ ClearRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDestroyStack(&sharedTextPtr->undoStack);
+ }
+ }
+
+ /* normalize values */
+ textPtr->maxUndoDepth = MAX(textPtr->maxUndoDepth, 0);
+ textPtr->maxRedoDepth = MAX(-1, textPtr->maxRedoDepth);
+ textPtr->maxUndoSize = MAX(textPtr->maxUndoSize, 0);
+
+ if (sharedTextPtr->maxUndoDepth != textPtr->maxUndoDepth
+ || sharedTextPtr->maxRedoDepth != textPtr->maxRedoDepth
+ || sharedTextPtr->maxUndoSize != textPtr->maxUndoSize) {
+ if (sharedTextPtr->undoStack) {
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack,
+ textPtr->maxUndoDepth, textPtr->maxRedoDepth);
+ TkTextUndoSetMaxStackSize(sharedTextPtr->undoStack, textPtr->maxUndoSize, false);
+ }
+ sharedTextPtr->maxUndoDepth = textPtr->maxUndoDepth;
+ sharedTextPtr->maxRedoDepth = textPtr->maxRedoDepth;
+ sharedTextPtr->maxUndoSize = textPtr->maxUndoSize;
+ copyDownFlags = true;
+ }
+
+ if (copyDownFlags) {
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ tPtr->autoSeparators = sharedTextPtr->autoSeparators;
+ tPtr->maxUndoDepth = sharedTextPtr->maxUndoDepth;
+ tPtr->maxRedoDepth = sharedTextPtr->maxRedoDepth;
+ tPtr->maxUndoSize = sharedTextPtr->maxUndoSize;
+ tPtr->undo = sharedTextPtr->undo;
+ }
+ }
+
+ /*
+ * Check soft hyphen support.
+ */
+
+ textPtr->hyphenate = textPtr->useHyphenSupport
+ && textPtr->state != TK_TEXT_STATE_NORMAL
+ && (textPtr->wrapMode == TEXT_WRAPMODE_WORD || textPtr->wrapMode == TEXT_WRAPMODE_CODEPOINT);
+ if (didHyphenate != textPtr->hyphenate) {
+ mask |= TK_TEXT_LINE_GEOMETRY;
+ }
+
+ /*
+ * Parse hyphen rules.
+ */
+
+ if (textPtr->hyphenRulesPtr) {
+ if (TkTextParseHyphenRules(textPtr, textPtr->hyphenRulesPtr, &textPtr->hyphenRules) != TCL_OK) {
+ goto error;
+ }
+ } else {
+ textPtr->hyphenRules = TK_TEXT_HYPHEN_MASK;
+ }
+ if (oldHyphenRules != textPtr->hyphenRules && textPtr->hyphenate) {
+ mask |= TK_TEXT_LINE_GEOMETRY;
+ }
+
/*
- * Copy down shared flags.
+ * Parse tab stops.
*/
- textPtr->sharedTextPtr->undo = textPtr->undo;
- textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo;
- textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators;
+ if (textPtr->tabArrayPtr) {
+ free(textPtr->tabArrayPtr);
+ textPtr->tabArrayPtr = NULL;
+ }
+ if (textPtr->tabOptionPtr) {
+ textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr, textPtr->tabOptionPtr);
+ if (!textPtr->tabArrayPtr) {
+ Tcl_AddErrorInfo(interp, "\n (while processing -tabs option)");
+ goto error;
+ }
+ }
- TkUndoSetMaxDepth(textPtr->sharedTextPtr->undoStack,
- textPtr->sharedTextPtr->maxUndo);
+ /*
+ * Check language support.
+ */
+
+ if (textPtr->langPtr) {
+ if (!TkTextTestLangCode(interp, textPtr->langPtr)) {
+ goto error;
+ }
+ memcpy(textPtr->lang, Tcl_GetString(textPtr->langPtr), 3);
+ } else {
+ memset(textPtr->lang, 0, 3);
+ }
/*
* A few other options also need special processing, such as parsing the
@@ -2104,45 +4191,98 @@ ConfigureText(
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);
+ /*
+ * Now setup the -startindex/-setindex range. This step cannot be restored,
+ * so this function must not return with an error code after this processing.
+ */
- if (textPtr->start != NULL) {
- start = TkBTreeLinesTo(NULL, textPtr->start);
+ if (mask & TK_TEXT_INDEX_RANGE) {
+ if (textPtr->newStartIndex) {
+ if (!TkTextGetIndexFromObj(interp, sharedTextPtr->mainPeer,
+ textPtr->newStartIndex, &start)) {
+ goto error;
+ }
} else {
- start = 0;
+ TkTextIndexClear(&start, textPtr);
+ TkTextIndexSetSegment(&start, textPtr->startMarker);
}
- if (textPtr->end != NULL) {
- end = TkBTreeLinesTo(NULL, textPtr->end);
+ if (textPtr->newEndIndex) {
+ if (!TkTextGetIndexFromObj(interp, sharedTextPtr->mainPeer, textPtr->newEndIndex, &end)) {
+ goto error;
+ }
} else {
- end = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
+ TkTextIndexClear(&end, textPtr);
+ TkTextIndexSetSegment(&end, textPtr->endMarker);
}
- if (start > end) {
+ if (TkTextIndexCompare(&start, &end) > 0) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "-startline must be less than or equal to -endline", -1));
+ "-startindex must be less than or equal to -endindex", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
- Tk_RestoreSavedOptions(&savedOptions);
- return TCL_ERROR;
+ goto error;
+ }
+
+ start.textPtr = NULL;
+ end.textPtr = NULL;
+
+ if (textPtr->newEndIndex) {
+ if (TkTextIndexIsEndOfText(&end)) {
+ if (--textPtr->endMarker->refCount == 0) {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ FREE_SEGMENT(textPtr->endMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->endMarker = sharedTextPtr->endMarker)->refCount += 1;
+ } else {
+ if (textPtr->endMarker->refCount > 1) {
+ textPtr->endMarker->refCount -= 1;
+ textPtr->endMarker = TkTextMakeStartEndMark(textPtr, &tkTextRightMarkType);
+ } else {
+ assert(textPtr->endMarker != sharedTextPtr->endMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->endMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->endMarker, &end);
+ }
+ Tcl_GuardedDecrRefCount(textPtr->newEndIndex);
+ textPtr->newEndIndex = NULL;
+ }
+
+ if (textPtr->newStartIndex) {
+ if (TkTextIndexIsStartOfText(&start)) {
+ if (--textPtr->startMarker->refCount == 0) {
+ assert(textPtr->startMarker != sharedTextPtr->startMarker);
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ FREE_SEGMENT(textPtr->startMarker);
+ DEBUG_ALLOC(tkTextCountDestroySegment++);
+ }
+ (textPtr->startMarker = sharedTextPtr->startMarker)->refCount += 1;
+ } else {
+ if (textPtr->startMarker->refCount > 1) {
+ textPtr->startMarker->refCount -= 1;
+ textPtr->startMarker = TkTextMakeStartEndMark(textPtr, &tkTextLeftMarkType);
+ } else {
+ TkBTreeUnlinkSegment(sharedTextPtr, textPtr->startMarker);
+ }
+ TkBTreeLinkSegment(sharedTextPtr, textPtr->startMarker, &start);
+ }
+ Tcl_GuardedDecrRefCount(textPtr->newStartIndex);
+ textPtr->newStartIndex = NULL;
}
- 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) {
+
+ /*
+ * Line start and/or end have been adjusted. We need to validate the
+ * first displayed line and arrange for re-layout.
+ */
+
+ TkBTreeClientRangeChanged(textPtr, MAX(0, textPtr->lineHeight));
+ TkTextMakeByteIndex(tree, NULL, TkTextIndexGetLineNumber(&textPtr->topIndex, NULL), 0, &current);
+
+ if (TkTextIndexCompare(&current, &start) < 0 || TkTextIndexCompare(&end, &current) < 0) {
TkTextSearch search;
TkTextIndex first, last;
- int selChanged = 0;
+ bool selChanged = false;
- TkTextSetYView(textPtr, &index1, 0);
+ TkTextSetYView(textPtr, &start, 0);
/*
* We may need to adjust the selection. So we have to check
@@ -2150,30 +4290,16 @@ ConfigureText(
* 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" */
+ TkTextMakeByteIndex(tree, NULL, 0, 0, &first);
+ TkBTreeStartSearch(&first, &start, textPtr->selTagPtr, &search, SEARCH_NEXT_TAGON);
+ if (TkBTreeNextTag(&search)) {
+ selChanged = true;
} 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;
- }
+ TkTextMakeByteIndex(tree, NULL, TkBTreeNumLines(tree, NULL), 0, &last);
+ TkBTreeStartSearchBack(&end, &last, textPtr->selTagPtr,
+ &search, SEARCH_EITHER_TAGON_TAGOFF);
+ if (TkBTreePrevTag(&search)) {
+ selChanged = true;
}
}
if (selChanged) {
@@ -2183,144 +4309,88 @@ ConfigureText(
*/
TkTextSelectionEvent(textPtr);
- textPtr->abortSelections = 1;
+ textPtr->abortSelections = true;
}
}
- /* Indices are potentially obsolete after changing -startline and/or
- * -endline, therefore increase the epoch.
+ /* Indices are potentially obsolete after changing -start and/or
+ * -end, 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)
+ * -start/-end 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.
+ * 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
+ * -start/-end 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);
+ currentEpoch = TkBTreeIncrEpoch(tree);
+ start.textPtr = textPtr;
+ end.textPtr = textPtr;
+
+ TkTextMarkNameToIndex(textPtr, "current", &current);
+ if (TkTextIndexCompare(&current, &start) < 0) {
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &start);
+ } else if (TkTextIndexCompare(&current, &end) > 0) {
+ textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &end);
}
+ } else {
+ currentEpoch = TkBTreeEpoch(tree);
}
/*
- * Don't allow negative spacings.
+ * Don't allow negative values for specific attributes.
*/
- if (textPtr->spacing1 < 0) {
- textPtr->spacing1 = 0;
- }
- if (textPtr->spacing2 < 0) {
- textPtr->spacing2 = 0;
- }
- if (textPtr->spacing3 < 0) {
- textPtr->spacing3 = 0;
- }
+ textPtr->spacing1 = MAX(textPtr->spacing1, 0);
+ textPtr->spacing2 = MAX(textPtr->spacing2, 0);
+ textPtr->spacing3 = MAX(textPtr->spacing3, 0);
+ textPtr->highlightWidth = MAX(textPtr->highlightWidth, 0);
+ textPtr->borderWidth = MAX(textPtr->borderWidth, 0);
+ textPtr->insertWidth = MAX(textPtr->insertWidth, 0);
+ textPtr->syncTime = MAX(0, textPtr->syncTime);
+ textPtr->selAttrs.borderWidth = MAX(textPtr->selAttrs.borderWidth, 0);
/*
- * Parse tab stops.
+ * Make sure that configuration options are properly mirrored between the
+ * widget record and the "sel" tags.
*/
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
- textPtr->tabArrayPtr = NULL;
+ if (textPtr->selAttrs.border != textPtr->textConfigAttrs.border) {
+ textPtr->selTagPtr->attrs.border = textPtr->selAttrs.border;
}
- 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;
- }
+ if (textPtr->selAttrs.inactiveBorder != textPtr->textConfigAttrs.inactiveBorder) {
+ textPtr->selTagPtr->attrs.inactiveBorder = textPtr->selAttrs.inactiveBorder;
}
-
- /*
- * 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.
- */
-
- if (textPtr->selTagPtr->selBorder == NULL) {
- textPtr->selTagPtr->border = textPtr->selBorder;
- } else {
- textPtr->selTagPtr->selBorder = textPtr->selBorder;
+ if (textPtr->selAttrs.fgColor != textPtr->textConfigAttrs.fgColor) {
+ textPtr->selTagPtr->attrs.fgColor = textPtr->selAttrs.fgColor;
}
- if (textPtr->selTagPtr->borderWidthPtr != textPtr->selBorderWidthPtr) {
- textPtr->selTagPtr->borderWidthPtr = textPtr->selBorderWidthPtr;
- textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
+ if (textPtr->selAttrs.inactiveFgColor != textPtr->textConfigAttrs.inactiveFgColor) {
+ textPtr->selTagPtr->attrs.inactiveFgColor = textPtr->selAttrs.inactiveFgColor;
}
- if (textPtr->selTagPtr->selFgColor == NULL) {
- textPtr->selTagPtr->fgColor = textPtr->selFgColorPtr;
- } else {
- textPtr->selTagPtr->selFgColor = textPtr->selFgColorPtr;
- }
- textPtr->selTagPtr->affectsDisplay = 0;
- textPtr->selTagPtr->affectsDisplayGeometry = 0;
- if ((textPtr->selTagPtr->elideString != NULL)
- || (textPtr->selTagPtr->tkfont != None)
- || (textPtr->selTagPtr->justifyString != NULL)
- || (textPtr->selTagPtr->lMargin1String != NULL)
- || (textPtr->selTagPtr->lMargin2String != NULL)
- || (textPtr->selTagPtr->offsetString != NULL)
- || (textPtr->selTagPtr->rMarginString != NULL)
- || (textPtr->selTagPtr->spacing1String != NULL)
- || (textPtr->selTagPtr->spacing2String != NULL)
- || (textPtr->selTagPtr->spacing3String != NULL)
- || (textPtr->selTagPtr->tabStringPtr != NULL)
- || (textPtr->selTagPtr->wrapMode != TEXT_WRAPMODE_NULL)) {
- textPtr->selTagPtr->affectsDisplay = 1;
- textPtr->selTagPtr->affectsDisplayGeometry = 1;
- }
- if ((textPtr->selTagPtr->border != NULL)
- || (textPtr->selTagPtr->selBorder != NULL)
- || (textPtr->selTagPtr->reliefString != NULL)
- || (textPtr->selTagPtr->bgStipple != None)
- || (textPtr->selTagPtr->fgColor != NULL)
- || (textPtr->selTagPtr->selFgColor != NULL)
- || (textPtr->selTagPtr->fgStipple != None)
- || (textPtr->selTagPtr->overstrikeString != NULL)
- || (textPtr->selTagPtr->overstrikeColor != NULL)
- || (textPtr->selTagPtr->underlineString != NULL)
- || (textPtr->selTagPtr->underlineColor != NULL)
- || (textPtr->selTagPtr->lMarginColor != NULL)
- || (textPtr->selTagPtr->rMarginColor != NULL)) {
- textPtr->selTagPtr->affectsDisplay = 1;
- }
- TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, 1);
+ if (textPtr->selAttrs.borderWidthPtr != textPtr->textConfigAttrs.borderWidthPtr) {
+ textPtr->selTagPtr->attrs.borderWidthPtr = textPtr->selAttrs.borderWidthPtr;
+ textPtr->selTagPtr->attrs.borderWidth = textPtr->selAttrs.borderWidth;
+ }
+ textPtr->textConfigAttrs = textPtr->selAttrs;
+ textPtr->selAttrs = textPtr->selTagPtr->attrs;
+ TkTextUpdateTagDisplayFlags(textPtr->selTagPtr);
+ TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, false);
/*
* Claim the selection if we've suddenly started exporting it and there
* are tagged characters.
*/
- if (textPtr->exportSelection && (!oldExport)) {
+ if (textPtr->exportSelection && !oldExport) {
TkTextSearch search;
TkTextIndex first, 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)) {
- Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection,
- textPtr);
+ TkTextIndexSetupToStartOfText(&first, textPtr, tree);
+ TkTextIndexSetupToEndOfText(&last, textPtr, tree);
+ TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search, SEARCH_NEXT_TAGON);
+ if (TkBTreeNextTag(&search)) {
+ Tk_OwnSelection(textPtr->tkwin, XA_PRIMARY, TkTextLostSelection, textPtr);
textPtr->flags |= GOT_SELECTION;
}
}
@@ -2329,7 +4399,7 @@ ConfigureText(
* Account for state changes that would reenable blinking cursor state.
*/
- if (textPtr->flags & GOT_FOCUS) {
+ if (textPtr->flags & HAVE_FOCUS) {
Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
textPtr->insertBlinkHandler = NULL;
TextBlinkProc(textPtr);
@@ -2340,14 +4410,106 @@ ConfigureText(
* window to be redisplayed.
*/
- if (textPtr->width <= 0) {
- textPtr->width = 1;
- }
- if (textPtr->height <= 0) {
- textPtr->height = 1;
- }
+ textPtr->width = MAX(textPtr->width, 1);
+ textPtr->height = MAX(textPtr->height, 1);
+
Tk_FreeSavedOptions(&savedOptions);
TextWorldChanged(textPtr, mask);
+
+ if (textPtr->syncTime == 0 && (mask & TK_TEXT_SYNCHRONIZE)) {
+ UpdateLineMetrics(textPtr, 0, TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ }
+
+ /*
+ * At least handle the "watch" command, and set the insert cursor.
+ */
+
+ if (mask & TK_TEXT_INDEX_RANGE) {
+ /*
+ * Setting the "insert" mark must be done at the end, because the "watch" command
+ * will be triggered. Be sure to use the actual range, mind the epoch.
+ */
+
+ TkTextMarkNameToIndex(textPtr, "insert", &current);
+
+ if (start.stateEpoch != currentEpoch) {
+ /*
+ * The "watch" command did change the content.
+ */
+ TkTextIndexSetupToStartOfText(&start, textPtr, tree);
+ TkTextIndexSetupToEndOfText(&end, textPtr, tree);
+ }
+
+ start.textPtr = textPtr;
+ end.textPtr = textPtr;
+
+ if (TkTextIndexCompare(&current, &start) < 0) {
+ textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &start);
+ } else if (TkTextIndexCompare(&current, &end) >= 0) {
+ textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &end);
+ }
+ }
+
+ tkTextDebug = oldTextDebug;
+ TK_BTREE_DEBUG(TkBTreeCheck(sharedTextPtr->tree));
+
+ return TCL_OK;
+
+error:
+ Tk_RestoreSavedOptions(&savedOptions);
+ textPtr->selAttrs = textPtr->selTagPtr->attrs;
+ tkTextDebug = oldTextDebug;
+ return TCL_ERROR;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextParseHyphenRules --
+ *
+ * This function is parsing the object containing the hyphen rules.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TkTextParseHyphenRules(
+ TkText *textPtr,
+ Tcl_Obj *objPtr,
+ int *rulesPtr)
+{
+ int rules = 0;
+ Tcl_Obj **argv;
+ int argc, i;
+ unsigned k;
+
+ assert(rulesPtr);
+
+ if (Tcl_ListObjGetElements(textPtr->interp, objPtr, &argc, &argv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ for (i = 0; i < argc; ++i) {
+ char const *rule = Tcl_GetString(argv[i]);
+ int r = rules;
+
+ for (k = 0; k < sizeof(hyphenRuleStrings)/sizeof(hyphenRuleStrings[0]); ++k) {
+ if (strcmp(rule, hyphenRuleStrings[k]) == 0) {
+ rules |= (1 << k);
+ }
+ }
+ if (r == rules) {
+ Tcl_SetObjResult(textPtr->interp, Tcl_ObjPrintf("unknown hyphen rule \"%s\"", rule));
+ Tcl_SetErrorCode(textPtr->interp, "TK", "TEXT", "VALUE", NULL);
+ return TCL_ERROR;
+ }
+ }
+ *rulesPtr = rules;
return TCL_OK;
}
@@ -2375,9 +4537,7 @@ static void
TextWorldChangedCallback(
ClientData instanceData) /* Information about widget. */
{
- TkText *textPtr = instanceData;
-
- TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY);
+ TextWorldChanged((TkText *) instanceData, TK_TEXT_LINE_GEOMETRY);
}
/*
@@ -2403,30 +4563,25 @@ TextWorldChangedCallback(
static void
TextWorldChanged(
TkText *textPtr, /* Information about widget. */
- int mask) /* OR'd collection of bits showing what has
- * changed. */
+ int mask) /* OR'd collection of bits showing what has changed. */
{
Tk_FontMetrics fm;
int border;
- int oldCharHeight = textPtr->charHeight;
+ int oldLineHeight = textPtr->lineHeight;
- textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
- if (textPtr->charWidth <= 0) {
- textPtr->charWidth = 1;
- }
Tk_GetFontMetrics(textPtr->tkfont, &fm);
+ textPtr->lineHeight = MAX(1, fm.linespace);
+ textPtr->charWidth = MAX(1, Tk_TextWidth(textPtr->tkfont, "0", 1));
+ textPtr->spaceWidth = MAX(1, Tk_TextWidth(textPtr->tkfont, " ", 1));
- textPtr->charHeight = fm.linespace;
- if (textPtr->charHeight <= 0) {
- textPtr->charHeight = 1;
- }
- if (textPtr->charHeight != oldCharHeight) {
- TkBTreeClientRangeChanged(textPtr, textPtr->charHeight);
+ if (oldLineHeight != textPtr->lineHeight) {
+ TkTextFontHeightChanged(textPtr);
}
+
border = textPtr->borderWidth + textPtr->highlightWidth;
Tk_GeometryRequest(textPtr->tkwin,
- textPtr->width * textPtr->charWidth + 2*textPtr->padX + 2*border,
- textPtr->height*(fm.linespace+textPtr->spacing1+textPtr->spacing3)
+ 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,
@@ -2434,12 +4589,13 @@ TextWorldChanged(
border + textPtr->padY, border + textPtr->padY);
if (textPtr->setGrid) {
Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
- textPtr->charWidth, textPtr->charHeight);
+ textPtr->charWidth, textPtr->lineHeight);
} else {
Tk_UnsetGrid(textPtr->tkwin);
}
TkTextRelayoutWindow(textPtr, mask);
+ TK_BTREE_DEBUG(TkBTreeCheck(textPtr->sharedTextPtr->tree));
}
/*
@@ -2462,94 +4618,147 @@ TextWorldChanged(
*/
static void
-TextEventProc(
- ClientData clientData, /* Information about window. */
- register XEvent *eventPtr) /* Information about event. */
+ProcessConfigureNotify(
+ TkText *textPtr,
+ bool updateLineGeometry)
{
- register TkText *textPtr = clientData;
- TkTextIndex index, index2;
+ int mask = updateLineGeometry ? TK_TEXT_LINE_GEOMETRY : 0;
- if (eventPtr->type == Expose) {
- TkTextRedrawRegion(textPtr, eventPtr->xexpose.x,
- eventPtr->xexpose.y, eventPtr->xexpose.width,
- eventPtr->xexpose.height);
- } else if (eventPtr->type == ConfigureNotify) {
- if ((textPtr->prevWidth != Tk_Width(textPtr->tkwin))
- || (textPtr->prevHeight != Tk_Height(textPtr->tkwin))) {
- int mask = 0;
+ /*
+ * Do not allow line height computations before we accept the first
+ * ConfigureNotify event. The problem is the very poor performance
+ * in CalculateDisplayLineHeight() with very small widget width.
+ */
- 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) {
- /*
- * 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.
- */
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ textPtr->sharedTextPtr->allowUpdateLineMetrics = true;
+ updateLineGeometry = true;
+ TkTextEventuallyRepick(textPtr);
+ }
- textPtr->selBorder = NULL;
- textPtr->selBorderWidthPtr = NULL;
- textPtr->selBorderWidth = 0;
- textPtr->selFgColorPtr = NULL;
- if (textPtr->setGrid) {
- Tk_UnsetGrid(textPtr->tkwin);
- textPtr->setGrid = 0;
- }
- if (!(textPtr->flags & OPTIONS_FREED)) {
- Tk_FreeConfigOptions((char *) textPtr, textPtr->optionTable,
- textPtr->tkwin);
- textPtr->flags |= OPTIONS_FREED;
- }
- textPtr->flags |= DESTROYED;
+ if (textPtr->prevHeight != Tk_Height(textPtr->tkwin)
+ || textPtr->prevWidth != Tk_Width(textPtr->tkwin)) {
+ mask |= TK_TEXT_LINE_REDRAW_BOTTOM_LINE;
+ }
+ TkTextRelayoutWindow(textPtr, mask);
+ TK_BTREE_DEBUG(TkBTreeCheck(textPtr->sharedTextPtr->tree));
- /*
- * 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.
- */
+ textPtr->prevWidth = Tk_Width(textPtr->tkwin);
+ textPtr->prevHeight = Tk_Height(textPtr->tkwin);
+}
+
+static void
+ProcessDestroyNotify(
+ TkText *textPtr)
+{
+ if (textPtr->setGrid) {
+ Tk_UnsetGrid(textPtr->tkwin);
+ textPtr->setGrid = false;
+ }
+ if (!(textPtr->flags & OPTIONS_FREED)) {
+ /* Restore the original attributes. */
+ textPtr->selAttrs = textPtr->textConfigAttrs;
+ 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);
+}
+
+static void
+ProcessFocusInOut(
+ TkText *textPtr,
+ XEvent *eventPtr)
+{
+ TkTextIndex index, index2;
- DestroyText(textPtr);
- } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
- 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;
- if (textPtr->insertOffTime != 0) {
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOnTime, TextBlinkProc, textPtr);
+ if (eventPtr->xfocus.detail == NotifyInferior
+ || eventPtr->xfocus.detail == NotifyAncestor
+ || eventPtr->xfocus.detail == NotifyNonlinear) {
+ if (eventPtr->type == FocusIn) {
+ textPtr->flags |= HAVE_FOCUS | INSERT_ON;
+ } else {
+ textPtr->flags &= ~(HAVE_FOCUS | INSERT_ON);
+ }
+ if (textPtr->state == TK_TEXT_STATE_NORMAL) {
+ if (eventPtr->type == FocusOut) {
+ if (textPtr->insertBlinkHandler) {
+ Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
+ textPtr->insertBlinkHandler = NULL;
}
- } else {
- textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
- textPtr->insertBlinkHandler = NULL;
- }
- if (textPtr->inactiveSelBorder != textPtr->selBorder) {
- TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr,
- 1);
+ } else if (textPtr->insertOffTime && !textPtr->insertBlinkHandler) {
+ textPtr->insertBlinkHandler =
+ Tcl_CreateTimerHandler(textPtr->insertOnTime, TextBlinkProc, textPtr);
}
TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
+ TkTextIndexForwChars(textPtr, &index, 1, &index2, COUNT_INDICES);
+ TkTextChanged(NULL, textPtr, &index, &index2);
+ }
+ if (textPtr->selAttrs.inactiveBorder != textPtr->selAttrs.border
+ || textPtr->selAttrs.inactiveFgColor != textPtr->selAttrs.fgColor) {
+ TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr, false);
+ }
+ if (textPtr->highlightWidth > 0) {
+ TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth, textPtr->highlightWidth);
+ }
+ }
+}
+
+static void
+TextEventProc(
+ ClientData clientData, /* Information about window. */
+ XEvent *eventPtr) /* Information about event. */
+{
+ TkText *textPtr = clientData;
+ switch (eventPtr->type) {
+ case ConfigureNotify:
+ if (textPtr->prevWidth != Tk_Width(textPtr->tkwin)
+ || textPtr->prevHeight != Tk_Height(textPtr->tkwin)) {
/*
- * While we wish to redisplay, no heights have changed, so no need
- * to call TkTextInvalidateLineMetrics.
+ * We don't need display computations until the widget is mapped
+ * or as long as the width seems to be unrealistic (not yet expanded
+ * by the geometry manager), see ProcessConfigureNotify() for more
+ * information.
*/
- TkTextChanged(NULL, textPtr, &index, &index2);
- if (textPtr->highlightWidth > 0) {
- TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
- textPtr->highlightWidth);
+ if (Tk_IsMapped(textPtr->tkwin)
+ || (Tk_Width(textPtr->tkwin) >
+ MAX(1, 2*(textPtr->highlightWidth + textPtr->borderWidth + textPtr->padX)))) {
+ ProcessConfigureNotify(textPtr, textPtr->prevWidth != Tk_Width(textPtr->tkwin));
}
}
+ break;
+ case DestroyNotify:
+ ProcessDestroyNotify(textPtr);
+ break;
+ default:
+ if (!textPtr->sharedTextPtr->allowUpdateLineMetrics) {
+ /*
+ * I don't know whether this can happen, but we want to be sure,
+ * probably we have rejected all ConfigureNotify events before
+ * first Expose arrives.
+ */
+ ProcessConfigureNotify(textPtr, true);
+ }
+ switch (eventPtr->type) {
+ case Expose:
+ TkTextRedrawRegion(textPtr, eventPtr->xexpose.x, eventPtr->xexpose.y,
+ eventPtr->xexpose.width, eventPtr->xexpose.height);
+ break;
+ case FocusIn:
+ case FocusOut:
+ ProcessFocusInOut(textPtr, eventPtr);
+ break;
+ }
}
}
@@ -2588,7 +4797,7 @@ TextCmdDeletedProc(
if (!(textPtr->flags & DESTROYED)) {
if (textPtr->setGrid) {
Tk_UnsetGrid(textPtr->tkwin);
- textPtr->setGrid = 0;
+ textPtr->setGrid = false;
}
textPtr->flags |= DESTROYED;
Tk_DestroyWindow(tkwin);
@@ -2616,39 +4825,321 @@ TextCmdDeletedProc(
*----------------------------------------------------------------------
*/
-static int
+static void
+InitPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkTextPosition *positions) /* Initialise this position array. */
+{
+ unsigned i;
+
+ for (i = 0; i < sharedTextPtr->numPeers; ++i, ++positions) {
+ positions->lineIndex = -1;
+ positions->byteIndex = 0;
+ }
+}
+
+static void
+FindNewTopPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkTextPosition *positions, /* Fill this position array. */
+ const TkTextIndex *index1Ptr, /* Start position of this insert/delete. */
+ const TkTextIndex *index2Ptr, /* End position of this delete, is NULL in case of insert. */
+ unsigned lengthOfInsertion) /* Length of inserted string, is zero in case of delete. */
+{
+ TkTextBTree tree = sharedTextPtr->tree;
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next, ++positions) {
+ int lineIndex = -1;
+ int byteIndex = 0;
+
+ if (index2Ptr == NULL) {
+ if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex);
+ if (byteIndex > TkTextIndexGetByteIndex(index1Ptr)) {
+ byteIndex += lengthOfInsertion;
+ }
+ }
+ } else if (TkTextIndexCompare(index2Ptr, &tPtr->topIndex) >= 0) {
+ if (TkTextIndexCompare(index1Ptr, &tPtr->topIndex) <= 0) {
+ /*
+ * Deletion range straddles topIndex: use the beginning of the
+ * range as the new topIndex.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(index1Ptr);
+ } else if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ /*
+ * Deletion range starts on top line but after topIndex. Use
+ * the current topIndex as the new one.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index1Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex);
+ } else {
+ /*
+ * Deletion range starts after the top line. This peers's view
+ * will not need to be reset. Nothing to do.
+ */
+ }
+ } else if (TkTextIndexGetLine(index2Ptr) == TkTextIndexGetLine(&tPtr->topIndex)) {
+ /*
+ * 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.
+ */
+
+ lineIndex = TkBTreeLinesTo(tree, NULL, TkTextIndexGetLine(index2Ptr), NULL);
+ byteIndex = TkTextIndexGetByteIndex(&tPtr->topIndex) - TkTextIndexGetByteIndex(index2Ptr);
+ if (TkTextIndexGetLine(index1Ptr) == TkTextIndexGetLine(index2Ptr)) {
+ byteIndex += TkTextIndexGetByteIndex(index1Ptr);
+ }
+ } else {
+ /*
+ * Deletion range ends before the top line. This peers's view
+ * will not need to be reset. Nothing to do.
+ */
+ }
+
+ if (lineIndex != -1) {
+ if (lineIndex == positions->lineIndex) {
+ positions->byteIndex = MAX(positions->byteIndex, byteIndex);
+ } else {
+ positions->lineIndex = MAX(positions->lineIndex, lineIndex);
+ positions->byteIndex = byteIndex;
+ }
+ }
+ }
+}
+
+static void
+SetNewTopPosition(
+ TkSharedText *sharedTextPtr, /* Shared portion of peer widgets. */
+ TkText *textPtr, /* Current peer widget, can be NULL. */
+ const TkTextPosition *positions, /* New top positions. */
+ bool viewUpdate) /* Update the view of current widget if set. */
+{
+ TkText *tPtr;
+
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next, ++positions) {
+ if (positions->lineIndex != -1) {
+ TkTextIndex index;
+
+ if (tPtr == textPtr && !viewUpdate) {
+ continue;
+ }
+
+ TkTextMakeByteIndex(sharedTextPtr->tree, NULL, positions->lineIndex, 0, &index);
+ TkTextIndexForwBytes(tPtr, &index, positions->byteIndex, &index);
+
+ if (tPtr == textPtr) {
+ /*
+ * Line cannot be before -startindex of textPtr because this line
+ * corresponds to an index which is necessarily between "begin"
+ * and "end" relative to textPtr. Therefore no need to clamp line
+ * to the -start/-end range.
+ */
+ } else {
+ TkTextIndex start;
+
+ /*
+ * Line may be before -startindex of tPtr and must be clamped to -startindex
+ * before providing it to TkTextSetYView otherwise lines before -startindex
+ * 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.
+ */
+
+ TkTextIndexClear2(&start, tPtr, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&start, tPtr->startMarker);
+ if (TkTextIndexCompare(&index, &start) < 0) {
+ index = start;
+ }
+ }
+
+ TkTextSetYView(tPtr, &index, 0);
+ }
+ }
+}
+
+static void
+ParseHyphens(
+ const char *string,
+ const char *end,
+ char *buffer)
+{
+#if TCL_UTF_MAX > 4
+# error "The text widget is designed for UTF-8, this applies also to the legacy code. Undocumented pseudo UTF-8 strings cannot be processed with this function, because it relies on the UTF-8 specification."
+#endif
+
+ assert(TK_TEXT_HYPHEN_MASK < 256); /* otherwise does not fit into char */
+
+ /*
+ * Preparing a string for hyphenation support. Note that 0xff is not allowed in
+ * UTF-8 strings, so we can use this value for special purposes.
+ */
+
+ while (string != end) {
+ if (*string == '\\') {
+ switch (*++string) {
+ case '\0':
+ *buffer++ = '\\';
+ break;
+ case '-':
+ *buffer++ = 0xff;
+ *buffer++ = '-';
+ string += 1;
+ break;
+ case '+':
+ *buffer++ = 0xff;
+ *buffer++ = '+';
+ string += 1;
+ break;
+ case ':':
+ switch (string[1]) {
+ case 'c':
+ if (strncmp(string, ":ck:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_CK);
+ string += 4;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'd':
+ if (strncmp(string, ":dd:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":dv:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":doubledigraph:", 15) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_DOUBLE_DIGRAPH);
+ string += 15;
+ break;
+ }
+ if (strncmp(string, ":doublevowel:", 13) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_DOUBLE_VOWEL);
+ string += 13;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'g':
+ if (strncmp(string, ":ge:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_GEMINATION);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":gemination:", 12) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_GEMINATION);
+ string += 12;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 'r':
+ if (strncmp(string, ":rh:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_REPEAT);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":repeathyphen:", 14) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_REPEAT);
+ string += 14;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ case 't':
+ if (strncmp(string, ":tr:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_TREMA);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":tc:", 4) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT);
+ string += 4;
+ break;
+ }
+ if (strncmp(string, ":trema:", 7) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_TREMA);
+ string += 7;
+ break;
+ }
+ if (strncmp(string, ":tripleconsonant:", 17) == 0) {
+ *buffer++ = 0xff;
+ *buffer++ = (char) (1 << TK_TEXT_HYPHEN_TRIPLE_CONSONANT);
+ string += 17;
+ break;
+ }
+ *buffer++ = *string++;
+ break;
+ default:
+ *buffer++ = *string++;
+ break;
+ }
+ }
+ } else {
+ *buffer++ = *string++;
+ }
+ }
+ *buffer = '\0';
+}
+
+static void
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. */
+ TkTextIndex *index1Ptr, /* Where to insert new characters. May be modified if the index
+ * is not valid for insertion (e.g. if at "end"). */
+ TkTextIndex *index2Ptr, /* Out: Index at the end of the inserted text. */
+ char const *string, /* Null-terminated string containing new information to add to text. */
+ unsigned length, /* Length of string content. */
+ bool viewUpdate, /* Update the view if set. */
+ TkTextTagSet *tagInfoPtr, /* Add these tags to the inserted text, can be NULL. */
+ TkTextTag *hyphenTagPtr, /* Associate this tag with soft hyphens, can be NULL. */
+ bool parseHyphens) /* Should we parse hyphens (tk_textInsert)? */
{
- int lineIndex, length;
+ TkSharedText *sharedTextPtr;
TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
- const char *string = Tcl_GetString(stringPtr);
+ TkTextPosition *textPosition;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+ TkTextIndex startIndex;
+ const char *text = string;
+ char textBuf[4096];
- length = stringPtr->length;
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
- }
+ assert(textPtr);
+ assert(length > 0);
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ sharedTextPtr = textPtr->sharedTextPtr;
/*
* 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.
+ * the only place in this function where the index1Ptr is modified.
*/
- lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
- if (lineIndex == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- lineIndex--;
- TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineIndex, 1000000,
- indexPtr);
+ if (TkTextIndexGetLine(index1Ptr) == TkBTreeGetLastLine(textPtr)) {
+ TkTextIndexBackChars(textPtr, index1Ptr, 1, index1Ptr, COUNT_INDICES);
}
/*
@@ -2658,368 +5149,415 @@ InsertChars(
* insertion, since the insertion could invalidate it.
*/
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
+ if (sharedTextPtr->numPeers > sizeof(textPosition)/sizeof(textPosition[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
} 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;
+ textPosition = textPosBuf;
}
+ InitPosition(sharedTextPtr, textPosition);
+ FindNewTopPosition(sharedTextPtr, textPosition, index1Ptr, NULL, length);
- TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr);
+ TkTextChanged(sharedTextPtr, NULL, index1Ptr, index1Ptr);
+ undoInfoPtr = TkTextUndoStackIsFull(sharedTextPtr->undoStack) ? NULL : &undoInfo;
+ startIndex = *index1Ptr;
+ TkTextIndexToByteIndex(&startIndex); /* we need the byte position after insertion */
- sharedTextPtr->stateEpoch++;
+ if (parseHyphens) {
+ text = (length >= sizeof(textBuf)) ? malloc(length + 1) : textBuf;
+ ParseHyphens(string, string + length, (char *) text);
+ }
- TkBTreeInsertChars(sharedTextPtr->tree, indexPtr, string);
+ TkBTreeInsertChars(sharedTextPtr->tree, index1Ptr, text, tagInfoPtr, hyphenTagPtr, undoInfoPtr);
/*
- * Push the insertion on the undo stack, and update the modified status of
- * the widget.
+ * Push the insertion on the undo stack, and update the modified status of the widget.
+ * Try to join with previously pushed undo token, if possible.
*/
- if (length > 0) {
- if (sharedTextPtr->undo) {
- TkTextIndex toIndex;
-
- if (sharedTextPtr->autoSeparators &&
- sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
+ if (undoInfoPtr) {
+ const TkTextUndoSubAtom *subAtom;
+ bool triggerStackEvent = false;
+ bool pushToken;
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
+ assert(undoInfo.byteSize == 0);
- TkTextIndexForwBytes(textPtr, indexPtr, length, &toIndex);
- TextPushUndoAction(textPtr, stringPtr, 1, indexPtr, &toIndex);
- }
+ PushUndoSeparatorIfNeeded(sharedTextPtr, sharedTextPtr->autoSeparators, TK_TEXT_EDIT_INSERT);
- UpdateDirtyFlag(sharedTextPtr);
- }
+ pushToken = sharedTextPtr->lastUndoTokenType != TK_TEXT_UNDO_INSERT
+ || !((subAtom = TkTextUndoGetLastUndoSubAtom(sharedTextPtr->undoStack))
+ && (triggerStackEvent = TkBTreeJoinUndoInsert(
+ subAtom->item, subAtom->size, undoInfo.token, undoInfo.byteSize)));
- resetViewCount = 0;
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- if (lineAndByteIndex[resetViewCount] != -1) {
- if ((tPtr != textPtr) || viewUpdate) {
- TkTextIndex newTop;
+ assert(undoInfo.token->undoType->rangeProc);
+ sharedTextPtr->prevUndoStartIndex = ((TkTextUndoTokenRange *) undoInfo.token)->startIndex;
+ sharedTextPtr->prevUndoEndIndex = ((TkTextUndoTokenRange *) undoInfo.token)->endIndex;
+ sharedTextPtr->lastUndoTokenType = TK_TEXT_UNDO_INSERT;
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
- TkTextMakeByteIndex(sharedTextPtr->tree, tPtr,
- lineAndByteIndex[resetViewCount], 0, &newTop);
- TkTextIndexForwBytes(tPtr, &newTop,
- lineAndByteIndex[resetViewCount+1], &newTop);
- TkTextSetYView(tPtr, &newTop, 0);
- }
+ if (pushToken) {
+ TkTextPushUndoToken(sharedTextPtr, undoInfo.token, undoInfo.byteSize);
+ } else {
+ assert(!undoInfo.token->undoType->destroyProc);
+ free(undoInfo.token);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
+ }
+ if (triggerStackEvent) {
+ sharedTextPtr->undoStackEvent = true; /* TkBTreeJoinUndoInsert didn't trigger */
}
- resetViewCount += 2;
}
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- ckfree(lineAndByteIndex);
+
+ *index2Ptr = *index1Ptr;
+ *index1Ptr = startIndex;
+ UpdateModifiedFlag(sharedTextPtr, true);
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ SetNewTopPosition(sharedTextPtr, textPtr, textPosition, viewUpdate);
+
+ if (textPosition != textPosBuf) {
+ free(textPosition);
}
/*
* Invalidate any selection retrievals in progress.
*/
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ tPtr->abortSelections = true;
}
- /*
- * For convenience, return the length of the string.
- */
-
- return length;
+ if (parseHyphens && text != textBuf) {
+ free((char *) text);
+ }
}
/*
*----------------------------------------------------------------------
*
- * TextPushUndoAction --
+ * TextUndoRedoCallback --
*
- * 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.
+ * 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:
* None.
*
* Side effects:
- * Items pushed onto stack.
+ * Will change anything, depending on the undo token.
*
*----------------------------------------------------------------------
*/
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. */
+TriggerWatchUndoRedo(
+ TkSharedText *sharedTextPtr,
+ TkTextUndoToken *token,
+ bool isRedo,
+ bool isFinal,
+ TkText **peers,
+ int numPeers)
{
- TkUndoSubAtom *iAtom, *dAtom;
- int canUndo, canRedo;
- char lMarkName[20] = "tk::undoMarkL";
- char rMarkName[20] = "tk::undoMarkR";
- char stringUndoMarkId[7] = "";
+ TkTextIndex index1, index2;
+ Tcl_Obj *cmdPtr;
+ char buf[100];
+ int i;
- /*
- * Create the helpers.
- */
+ assert(sharedTextPtr->triggerWatchCmd);
+ assert(token->undoType->rangeProc);
+ assert(token->undoType->commandProc);
- Tcl_Obj *seeInsertObj = Tcl_NewObj();
- Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
- Tcl_Obj *markSet2InsertObj = NULL;
- Tcl_Obj *insertCmdObj = Tcl_NewObj();
- Tcl_Obj *deleteCmdObj = Tcl_NewObj();
- Tcl_Obj *markSetLUndoMarkCmdObj = Tcl_NewObj();
- Tcl_Obj *markSetRUndoMarkCmdObj = NULL;
- Tcl_Obj *markGravityLUndoMarkCmdObj = Tcl_NewObj();
- Tcl_Obj *markGravityRUndoMarkCmdObj = NULL;
+ sharedTextPtr->triggerWatchCmd = false; /* do not trigger recursively */
+ token->undoType->rangeProc(sharedTextPtr, token, &index1, &index2);
+ Tcl_IncrRefCount(cmdPtr = token->undoType->commandProc(sharedTextPtr, token));
+ snprintf(buf, sizeof(buf), "%s", isFinal ? "yes" : "no");
- /*
- * Get the index positions.
- */
+ for (i = 0; i < numPeers; ++i) {
+ TkText *tPtr = peers[i];
- Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr);
- Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr);
+ if (tPtr->watchCmd && !(tPtr->flags & DESTROYED)) {
+ char idx[2][TK_POS_CHARS];
+ const char *info = isRedo ? "redo" : "undo";
- /*
- * These need refCounts, because they are used more than once below.
- */
+ TkTextPrintIndex(tPtr, &index1, idx[0]);
+ TkTextPrintIndex(tPtr, &index2, idx[1]);
+ TkTextTriggerWatchCmd(tPtr, info, idx[0], idx[1], Tcl_GetString(cmdPtr), buf, NULL, false);
+ }
+ }
- 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);
+ Tcl_GuardedDecrRefCount(cmdPtr);
+ sharedTextPtr->triggerWatchCmd = true;
+}
- /*
- * Only use of 'undoString' is here.
- */
+void
+TextUndoRedoCallback(
+ TkTextUndoStack stack,
+ const TkTextUndoAtom *atom)
+{
+ TkSharedText *sharedTextPtr = (TkSharedText *) TkTextUndoGetContext(stack);
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo redoInfo;
+ TkTextUndoInfo *redoInfoPtr;
+ TkTextPosition *textPosition = NULL;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ bool eventuallyRepick = false;
+ TkText *peerArr[20];
+ TkText **peers = peerArr;
+ TkText *tPtr;
+ int i, k, countPeers = 0;
- Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString);
-
- Tcl_ListObjAppendElement(NULL, deleteCmdObj,
- Tcl_NewStringObj("delete", 6));
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj("set", 3));
- markSetRUndoMarkCmdObj = Tcl_DuplicateObj(markSetLUndoMarkCmdObj);
- textPtr->sharedTextPtr->undoMarkId++;
- sprintf(stringUndoMarkId, "%d", textPtr->sharedTextPtr->undoMarkId);
- strcat(lMarkName, stringUndoMarkId);
- strcat(rMarkName, stringUndoMarkId);
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj,
- Tcl_NewStringObj(lMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj,
- Tcl_NewStringObj(rMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markSetLUndoMarkCmdObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, markSetRUndoMarkCmdObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("gravity", 7));
- markGravityRUndoMarkCmdObj = Tcl_DuplicateObj(markGravityLUndoMarkCmdObj);
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj(lMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj,
- Tcl_NewStringObj(rMarkName, -1));
- Tcl_ListObjAppendElement(NULL, markGravityLUndoMarkCmdObj,
- Tcl_NewStringObj("left", 4));
- Tcl_ListObjAppendElement(NULL, markGravityRUndoMarkCmdObj,
- Tcl_NewStringObj("right", 5));
+ assert(stack);
- /*
- * 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.
- */
+ if (sharedTextPtr->triggerWatchCmd) {
+ if (sharedTextPtr->numPeers > sizeof(peerArr) / sizeof(peerArr[0])) {
+ peers = malloc(sharedTextPtr->numPeers * sizeof(peerArr[0]));
+ }
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr->watchCmd) {
+ TkTextSaveCursorIndex(tPtr);
+ peers[countPeers++] = tPtr;
+ tPtr->refCount += 1;
+ }
+ }
+ }
- iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- insertCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, iAtom);
-
- dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- deleteCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetLUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markSetRUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityLUndoMarkCmdObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, markGravityRUndoMarkCmdObj, dAtom);
-
- Tcl_DecrRefCount(seeInsertObj);
- Tcl_DecrRefCount(index1Obj);
- Tcl_DecrRefCount(index2Obj);
-
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
+ memset(&undoInfo, 0, sizeof(undoInfo));
+ redoInfoPtr = TkTextUndoStackIsFull(stack) ? NULL : &redoInfo;
- /*
- * 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').
- */
+ for (i = atom->arraySize - 1; i >= 0; --i) {
+ TkTextIndex index1, index2;
+ const TkTextUndoSubAtom *subAtom = atom->array + i;
+ TkTextUndoToken *token = subAtom->item;
+ bool isDelete = token->undoType->action == TK_TEXT_UNDO_INSERT
+ || token->undoType->action == TK_TEXT_REDO_DELETE;
+ bool isInsert = token->undoType->action == TK_TEXT_UNDO_DELETE
+ || token->undoType->action == TK_TEXT_REDO_INSERT;
- if (insert) {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom);
- } else {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom);
+ if (isInsert || isDelete) {
+ const TkTextUndoTokenRange *range = (const TkTextUndoTokenRange *) token;
+
+ if (isDelete && sharedTextPtr->triggerWatchCmd) {
+ TriggerWatchUndoRedo(sharedTextPtr, token, subAtom->redo, i == 0, peers, countPeers);
+ }
+ if (!textPosition) {
+ if (sharedTextPtr->numPeers > sizeof(textPosBuf)/sizeof(textPosBuf[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
+ } else {
+ textPosition = textPosBuf;
+ }
+ InitPosition(sharedTextPtr, textPosition);
+ }
+ if (isInsert) {
+ TkBTreeUndoIndexToIndex(sharedTextPtr, &range->startIndex, &index1);
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index1);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, NULL, subAtom->size);
+ } else {
+ token->undoType->rangeProc(sharedTextPtr, token, &index1, &index2);
+ TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, &index2, 0);
+ }
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (!tPtr->abortSelections) {
+ if (isInsert) {
+ tPtr->abortSelections = true;
+ } else {
+ if (range->startIndex.lineIndex < range->endIndex.lineIndex
+ && TkBTreeTag(sharedTextPtr, NULL, &index1, &index2,
+ tPtr->selTagPtr, false, NULL, TkTextRedrawTag)) {
+ TkTextSelectionEvent(tPtr);
+ tPtr->abortSelections = true;
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Now perform the undo/redo action.
+ */
+
+ if (redoInfoPtr) {
+ memset(redoInfoPtr, 0, sizeof(redoInfo));
+ }
+ undoInfo.token = token;
+ undoInfo.byteSize = atom->size;
+ token->undoType->undoProc(sharedTextPtr, &undoInfo, redoInfoPtr, atom->redo);
+
+ if (token->undoType->action == TK_TEXT_UNDO_TAG) {
+ eventuallyRepick = true;
+ }
+ if (redoInfoPtr) {
+ if (redoInfo.token == token) {
+ /*
+ * We are re-using a token, this is possible because the current undo token
+ * will expire after this action.
+ */
+ if (!subAtom->redo) {
+ if (token->undoType->action == TK_TEXT_UNDO_INSERT
+ || token->undoType->action == TK_TEXT_UNDO_DELETE) {
+ assert(sharedTextPtr->insertDeleteUndoTokenCount > 0);
+ sharedTextPtr->insertDeleteUndoTokenCount -= 1;
+ }
+ }
+ if (token->undoType->destroyProc) {
+ /* We need a balanced call of perform/destroy. */
+ token->undoType->destroyProc(sharedTextPtr, subAtom->item, true);
+ }
+ /*
+ * Do not free this item.
+ */
+ ((TkTextUndoSubAtom *) subAtom)->item = NULL;
+ }
+ TkTextPushUndoToken(sharedTextPtr, redoInfo.token, redoInfo.byteSize);
+ }
+ if (!isDelete && sharedTextPtr->triggerWatchCmd) {
+ TriggerWatchUndoRedo(sharedTextPtr, token, subAtom->redo, i == 0, peers, countPeers);
+ }
+ }
+
+ if (eventuallyRepick) {
+ for (k = 0; k < countPeers; ++k) {
+ TkText *tPtr = peers[k];
+
+ if (!(tPtr->flags & DESTROYED)) {
+ TkTextEventuallyRepick(tPtr);
+ }
+ }
}
- if (!canUndo || canRedo) {
- GenerateUndoStackEvent(textPtr);
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_OTHER;
+ sharedTextPtr->lastUndoTokenType = -1;
+ UpdateModifiedFlag(sharedTextPtr, false);
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+
+ if (textPosition) {
+ SetNewTopPosition(sharedTextPtr, NULL, textPosition, true);
+ if (textPosition != textPosBuf) {
+ free(textPosition);
+ }
+ }
+
+ if (sharedTextPtr->triggerWatchCmd) {
+ for (i = 0; i < countPeers; ++i) {
+ TkText *tPtr = peers[i];
+
+ if (!(tPtr->flags & DESTROYED)) {
+ TkTextIndexClear(&tPtr->insertIndex, tPtr);
+ TkTextTriggerWatchCursor(tPtr);
+ }
+ TkTextDecrRefCountAndTestIfDestroyed(tPtr);
+ }
+ }
+
+ /*
+ * Freeing the peer array has to be done even if sharedTextPtr->triggerWatchCmd
+ * is false, possibly the user has cleared the watch command inside the trigger
+ * callback.
+ */
+
+ if (peers != peerArr) {
+ free(peers);
}
}
/*
*----------------------------------------------------------------------
*
- * TextUndoRedoCallback --
+ * TextUndoStackContentChangedCallback --
*
* 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.
+ * undo/redo stack changes.
*
* Results:
- * A standard Tcl result.
+ * None.
*
* Side effects:
- * Will insert or delete text, depending on the first word contained in
- * objPtr.
+ * None.
*
*----------------------------------------------------------------------
*/
-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. */
+static void
+TextUndoStackContentChangedCallback(
+ const TkTextUndoStack stack)
+{
+ ((TkSharedText *) TkTextUndoGetContext(stack))->undoStackEvent = true;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TriggerUndoStackEvent --
+ *
+ * This function is triggering the <<UndoStack>> event for all peers.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * May force the text window (and all peers) into existence.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TriggerUndoStackEvent(
+ TkSharedText *sharedTextPtr)
{
- TkSharedText *sharedPtr = clientData;
- int res, objc;
- Tcl_Obj **objv;
TkText *textPtr;
- res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv);
- if (res != TCL_OK) {
- return res;
+ assert(sharedTextPtr->undoStackEvent);
+ sharedTextPtr->undoStackEvent = false;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
+ if (!(textPtr->flags & DESTROYED)) {
+ Tk_MakeWindowExist(textPtr->tkwin);
+ SendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
+ }
}
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextUndoFreeCallback --
+ *
+ * This function is registered with the generic undo/redo code to handle
+ * the freeing operation of undo/redo items.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Some memory will be freed.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+TextUndoFreeCallback(
+ const TkTextUndoStack stack,
+ const TkTextUndoSubAtom *subAtom) /* Destroy this token. */
+{
+ TkTextUndoToken *token = (TkTextUndoToken *) subAtom->item;
/*
- * 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.
+ * Consider that the token is possibly null.
*/
- textPtr = sharedPtr->peers;
- while (textPtr != NULL) {
- if (textPtr->start == NULL && textPtr->end == NULL) {
- Tcl_Obj *cmdNameObj, *evalObj;
-
- evalObj = Tcl_NewObj();
- Tcl_IncrRefCount(evalObj);
+ if (token) {
+ TkTextUndoAction action = token->undoType->action;
- /*
- * 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;
+ if (action == TK_TEXT_UNDO_INSERT || action == TK_TEXT_UNDO_DELETE) {
+ TkSharedText *sharedTextPtr = (TkSharedText *) TkTextUndoGetContext(stack);
+ assert(sharedTextPtr->insertDeleteUndoTokenCount > 0);
+ sharedTextPtr->insertDeleteUndoTokenCount -= 1;
}
- textPtr = textPtr->next;
+ if (token->undoType->destroyProc) {
+ token->undoType->destroyProc(TkTextUndoGetContext(stack), subAtom->item, false);
+ }
+ free(subAtom->item);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
}
-
- /*
- * 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.
- */
-
- return SharedTextObjCmd(sharedPtr, interp, objc+1, objv-1);
}
/*
@@ -3047,27 +5585,117 @@ static int
CountIndices(
const TkText *textPtr, /* Overall information about text widget. */
const TkTextIndex *indexPtr1,
- /* Index describing location of first
- * character to delete. */
+ /* Index describing location of first character to delete. */
const TkTextIndex *indexPtr2,
- /* Index describing location of last character
- * to delete. NULL means just delete the one
- * character given by indexPtr1. */
+ /* 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);
+ int compare = TkTextIndexCompare(indexPtr1, indexPtr2);
if (compare == 0) {
return 0;
- } else if (compare > 0) {
- return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type);
- } else {
- return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
}
+ if (compare > 0) {
+ return -((int) TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type));
+ }
+ return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGetUndeletableNewline --
+ *
+ * Return pointer to undeletable newline. The search will start at
+ * start of deletion. See comments in function about the properties
+ * of an undeletable newline.
+ *
+ * Note that this functions expects that the deletions end on very
+ * last line in B-Tree, otherwise the newline is always deletable.
+ *
+ * Results:
+ * Returns the undeletable newline, or NULL.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const TkTextSegment *
+TkTextGetUndeletableNewline(
+ const TkTextLine *lastLinePtr) /* last line of deletion, must be last line of B-Tree */
+{
+ assert(lastLinePtr);
+ assert(!lastLinePtr->nextPtr);
+
+#if 0 /* THIS IS OLD IMPLEMENTATION */
+ const TkTextSegment *segPtr = TkTextIndexGetContentSegment(&index1, NULL);
+
+ /*
+ * Advance to next character.
+ */
+
+ while (segPtr->size == 0) {
+ segPtr = segPtr->nextPtr;
+ assert(segPtr);
+ }
+
+ /*
+ * Assume the following text content:
+ *
+ * {"1" "\n"} {"\n"} {"2" "\n"} {"\n"}
+ * A B C D E F
+ *
+ * Segment E is the last newline (F belongs to addtional empty line).
+ * We have two cases where the last newline has to be preserved.
+ *
+ * 1. Deletion is starting in first line, then we have to preserve the
+ * last newline, return segment E.
+ *
+ * 2. Deletion is not starting at the first character in this line, then
+ * we have to preserve the last newline, return segment E.
+ *
+ * In all other cases return NULL.
+ */
+
+ if (segPtr->sectionPtr->linePtr->prevPtr && SegIsAtStartOfLine(segPtr)) {
+ return NULL;
+ }
+#endif
+
+ /*
+ * The old implementation is erroneous, and has been changed:
+ *
+ * 1. Test the following script with old implementation:
+ * text .t
+ * .t insert end "1\n2"
+ * .t delete 2.0 end
+ * .t insert end "2"
+ * The result of [.t get begin end] -> "12\n" is unexpected, the expected result is "1\n2\n".
+ *
+ * 2. The mathematical consistency now will be preserved:
+ * - The newly created text widget is clean and contains "\e"
+ * (\e is the always existing final newline in last line).
+ * - After insertion of "1\n2" at 'begin' we have "1\n2\e".
+ * - After [.t delete 2.0 end] the deletion starts with inserted character "2",
+ * and not with the inserted newline. Thus from mathematical point of view
+ * the result must be "1\n\e" (this means: the always existing final newline
+ * will never be deleted).
+ * - After [.t insert end "2"] the string "2" has been inserted at end, this means
+ * before "\e", so the new result is "1\n2\e".
+ *
+ * 3. It's a clean concept if the artificial newline is undeletable, the old concept is
+ * hard to understand for a user, and error-prone.
+ */
+
+ assert(lastLinePtr->prevPtr);
+ return lastLinePtr->prevPtr->lastPtr; /* return final newline \e */
}
/*
@@ -3079,7 +5707,7 @@ CountIndices(
* widget command.
*
* Results:
- * Returns a standard Tcl result, currently always TCL_OK.
+ * Returns whether the widget hasn't been destroyed.
*
* Side effects:
* Characters and other entities (windows, images) get deleted from the
@@ -3088,7 +5716,7 @@ CountIndices(
* If 'viewUpdate' is true, we may adjust the window contents'
* y-position, and scrollbar setting.
*
- * If 'viewUpdate' is true we can guarantee that textPtr->topIndex
+ * If 'viewUpdate' is true, 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
@@ -3098,130 +5726,165 @@ CountIndices(
*----------------------------------------------------------------------
*/
-static int
+static bool
+DeleteOnLastLine(
+ TkSharedText *sharedTextPtr,
+ const TkTextLine *lastLinePtr,
+ int flags) /* deletion flags */
+{
+ assert(lastLinePtr);
+ assert(!lastLinePtr->nextPtr);
+
+ if (flags & DELETE_MARKS) {
+ const TkTextSegment *segPtr = lastLinePtr->segPtr;
+
+ while (segPtr->size == 0) {
+ if ((flags & DELETE_MARKS) && TkTextIsNormalMark(segPtr)) {
+ return true;
+ }
+ segPtr = segPtr->nextPtr;
+ }
+ }
+
+ return false;
+}
+
+static bool
+DeleteEndMarker(
+ const TkTextIndex *indexPtr,
+ int flags)
+{
+ const TkTextSegment *segPtr;
+
+ return (flags & DELETE_MARKS)
+ && (segPtr = TkTextIndexGetSegment(indexPtr))
+ && TkTextIsNormalMark(segPtr);
+}
+
+static bool
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. */
+ /* 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. */
+ /* Index describing location of last character (or other entity)
+ * to delete. NULL means just delete the one character given by
+ * indexPtr1. */
+ int flags, /* Flags controlling the deletion. */
+ bool viewUpdate, /* Update vertical view if set. */
+ bool triggerWatchDelete, /* Should we trigger the watch command for deletion? */
+ bool triggerWatchInsert, /* Should we trigger the watch command for insertion? */
+ bool userFlag, /* Trigger user modification? */
+ bool final) /* This is the final call in a sequence of ranges. */
{
- int line1, line2;
- TkTextIndex index1, index2;
- TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
-
- if (sharedTextPtr == NULL) {
+ TkTextIndex index1, index2, index3;
+ TkTextPosition *textPosition;
+ TkTextPosition textPosBuf[PIXEL_CLIENTS];
+ TkTextUndoInfo undoInfo;
+ TkTextUndoInfo *undoInfoPtr;
+ TkTextLine *lastLinePtr;
+
+ if (!sharedTextPtr) {
sharedTextPtr = textPtr->sharedTextPtr;
}
+ if (triggerWatchInsert) {
+ TkTextIndexToByteIndex((TkTextIndex *) indexPtr1); /* mutable due to concept */
+ }
+
+ if (TkTextIndexIsEndOfText(indexPtr1)) {
+ return true; /* nothing to delete */
+ }
+
/*
* Prepare the starting and stopping indices.
*/
- index1 = *indexPtr1;
- if (indexPtr2 != NULL) {
+ if (indexPtr2) {
+ if (TkTextIndexCompare(indexPtr1, indexPtr2) >= 0) {
+ return true; /* there is nothing to delete */
+ }
+ index1 = *indexPtr1;
index2 = *indexPtr2;
+ } else if (!TkTextIndexForwChars(textPtr, indexPtr1, 1, &index2, COUNT_INDICES)) {
+ return true;
} else {
- index2 = index1;
- TkTextIndexForwChars(NULL, &index2, 1, &index2, COUNT_INDICES);
+ index1 = *indexPtr1;
}
- /*
- * Make sure there's really something to delete.
- */
-
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
- return TCL_OK;
- }
+ index3 = index2;
- /*
- * 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. The idea is
- * that a deletion involving a range starting at a line start and
- * including the final \n (i.e. index2 is "end") is an attempt to delete
- * complete lines, so the \n before the deleted block shall become the new
- * final \n. 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.
- */
+ if (!TkTextIndexGetLine(&index2)->nextPtr
+ && !DeleteEndMarker(&index2, flags)
+ && TkTextGetUndeletableNewline(lastLinePtr = TkTextIndexGetLine(&index2))
+ && !DeleteOnLastLine(sharedTextPtr, lastLinePtr, flags)) {
+ /*
+ * This is a very special case. If the last newline is undeletable, we do not
+ * have a deletable marker at end of range, and there is no deletable mark on
+ * last line, then decrement the end of range.
+ */
- line1 = TkBTreeLinesTo(textPtr, index1.linePtr);
- line2 = TkBTreeLinesTo(textPtr, index2.linePtr);
- if (line2 == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- TkTextTag **arrayPtr;
- int arraySize, i;
- TkTextIndex oldIndex2;
+ TkTextIndexBackBytes(textPtr, &index2, 1, &index2);
- oldIndex2 = index2;
- TkTextIndexBackChars(NULL, &oldIndex2, 1, &index2, COUNT_INDICES);
- line2--;
- if ((index1.byteIndex == 0) && (line1 != 0)) {
- TkTextIndexBackChars(NULL, &index1, 1, &index1, COUNT_INDICES);
- line1--;
+ if (TkTextIndexIsEqual(&index1, &index2)) {
+ if (lastLinePtr->prevPtr) {
+ if (lastLinePtr->prevPtr->lastPtr->tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ /* we have to delete tags on previous newline, that's all */
+ TkTextClearSelection(sharedTextPtr, &index1, &index3);
+ TkTextClearTags(sharedTextPtr, textPtr, &index1, &index3, false);
+ } else {
+ assert(TkTextTagSetIsEmpty(lastLinePtr->prevPtr->lastPtr->tagInfoPtr));
+ }
+ }
+ return true; /* nothing to do */
}
- arrayPtr = TkBTreeGetTags(&index2, NULL, &arraySize);
- if (arrayPtr != NULL) {
- for (i = 0; i < arraySize; i++) {
- TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
+
+ if (lastLinePtr->prevPtr->lastPtr->tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ if (!TkTextTagBitContainsSet(sharedTextPtr->selectionTags,
+ lastLinePtr->prevPtr->lastPtr->tagInfoPtr)) {
+ /*
+ * Last newline is tagged with any non-selection tag, so we have to
+ * re-include this character.
+ */
+ flags |= DELETE_LASTLINE;
+ index2 = index3;
}
- ckfree(arrayPtr);
}
}
- if (line1 < line2) {
- /*
- * We are deleting more than one line. For speed, we remove all tags
- * from the range first. If we don't do this, the code below can (when
- * there are many tags) grow non-linearly in execution time.
- */
-
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
- int i;
+ /*
+ * Call the "watch" command for deletion. Take into account that the
+ * receiver might change the text content inside the callback, although
+ * he shouldn't do this.
+ */
- for (i=0, hPtr=Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
- TkTextTag *tagPtr = Tcl_GetHashValue(hPtr);
+ if (triggerWatchDelete) {
+ Tcl_Obj *delObj = TextGetText(textPtr, &index1, &index2, NULL, NULL, UINT_MAX, false, true);
+ char const *deleted = Tcl_GetString(delObj);
+ bool unchanged;
+ bool rc;
- TkBTreeTag(&index1, &index2, tagPtr, 0);
- }
+ TkTextIndexSave(&index1);
+ TkTextIndexSave(&index2);
+ Tcl_IncrRefCount(delObj);
+ rc = TriggerWatchEdit(textPtr, userFlag, "delete", &index1, &index2, deleted, final);
+ Tcl_GuardedDecrRefCount(delObj);
+ unchanged = TkTextIndexRebuild(&index1) && TkTextIndexRebuild(&index2);
- /*
- * 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>>
- */
+ if (!rc) { return false; } /* the receiver has destroyed this widget */
- TkTextSelectionEvent(textPtr);
- tPtr->abortSelections = 1;
- }
+ if (!unchanged && TkTextIndexCompare(&index1, &index2) >= 0) {
+ /* This can only happen if the receiver of the trigger command did any modification. */
+ return true;
}
}
+ TkTextClearSelection(sharedTextPtr, &index1, &index3);
+
/*
- * Tell the display what's about to happen so it can discard obsolete
+ * 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
@@ -3230,164 +5893,72 @@ DeleteIndexRange(
TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
+ if (sharedTextPtr->numPeers > sizeof(textPosBuf)/sizeof(textPosBuf[0])) {
+ textPosition = malloc(sizeof(textPosition[0])*sharedTextPtr->numPeers);
} else {
- lineAndByteIndex = pixels;
+ textPosition = textPosBuf;
}
- 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 ends on top line but before topIndex. Figure out
- * what will be the new character index for the character
- * currently pointed to by topIndex.
- */
+ InitPosition(sharedTextPtr, textPosition);
+ FindNewTopPosition(sharedTextPtr, textPosition, &index1, &index2, 0);
- resetView = 1;
- 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.
- */
- }
- if (resetView) {
- lineAndByteIndex[resetViewCount] = line;
- lineAndByteIndex[resetViewCount+1] = byteIndex;
- } else {
- lineAndByteIndex[resetViewCount] = -1;
- }
- resetViewCount += 2;
- }
+ undoInfoPtr = TkTextUndoStackIsFull(sharedTextPtr->undoStack) ? NULL : &undoInfo;
+ TkBTreeDeleteIndexRange(sharedTextPtr, &index1, &index2, flags, undoInfoPtr);
/*
- * Push the deletion on the undo stack if something was actually deleted.
+ * Push the deletion onto the undo stack, and update the modified status of the widget.
+ * Try to join with previously pushed undo token, if possible.
*/
- if (TkTextIndexCmp(&index1, &index2) < 0) {
- if (sharedTextPtr->undo) {
- Tcl_Obj *get;
+ if (undoInfoPtr) {
+ const TkTextUndoSubAtom *subAtom;
- if (sharedTextPtr->autoSeparators
- && (sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
-
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
+ PushUndoSeparatorIfNeeded(sharedTextPtr, sharedTextPtr->autoSeparators, TK_TEXT_EDIT_DELETE);
- get = TextGetText(textPtr, &index1, &index2, 0);
- TextPushUndoAction(textPtr, get, 0, &index1, &index2);
+ if (TkTextUndoGetMaxSize(sharedTextPtr->undoStack) == 0
+ || TkTextUndoGetCurrentSize(sharedTextPtr->undoStack) + undoInfo.byteSize
+ <= TkTextUndoGetMaxSize(sharedTextPtr->undoStack)) {
+ if (sharedTextPtr->lastUndoTokenType != TK_TEXT_UNDO_DELETE
+ || !((subAtom = TkTextUndoGetLastUndoSubAtom(sharedTextPtr->undoStack))
+ && TkBTreeJoinUndoDelete(subAtom->item, subAtom->size,
+ undoInfo.token, undoInfo.byteSize))) {
+ TkTextPushUndoToken(sharedTextPtr, undoInfo.token, undoInfo.byteSize);
+ }
+ sharedTextPtr->lastUndoTokenType = TK_TEXT_UNDO_DELETE;
+ sharedTextPtr->prevUndoStartIndex =
+ ((TkTextUndoTokenRange *) undoInfo.token)->startIndex;
+ sharedTextPtr->prevUndoEndIndex = ((TkTextUndoTokenRange *) undoInfo.token)->endIndex;
+ /* stack has changed anyway, but TkBTreeJoinUndoDelete didn't trigger */
+ sharedTextPtr->undoStackEvent = true;
+ } else {
+ assert(undoInfo.token->undoType->destroyProc);
+ undoInfo.token->undoType->destroyProc(sharedTextPtr, undoInfo.token, false);
+ free(undoInfo.token);
+ DEBUG_ALLOC(tkTextCountDestroyUndoToken++);
}
- sharedTextPtr->stateEpoch++;
-
- TkBTreeDeleteIndexRange(sharedTextPtr->tree, &index1, &index2);
- UpdateDirtyFlag(sharedTextPtr);
+ sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
}
- 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;
+ UpdateModifiedFlag(sharedTextPtr, true);
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ SetNewTopPosition(sharedTextPtr, textPtr, textPosition, viewUpdate);
- 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(lineAndByteIndex);
+ if (textPosition != textPosBuf) {
+ free(textPosition);
}
- if (line1 >= line2) {
- /*
- * Invalidate any selection retrievals in progress, assuming we didn't
- * check for this case above.
- */
+ /*
+ * Lastly, trigger the "watch" command for insertion. This must be the last action,
+ * probably the receiver is calling some widget commands inside the callback.
+ */
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
+ if (triggerWatchInsert) {
+ if (!TriggerWatchEdit(textPtr, userFlag, "insert", indexPtr1, indexPtr1, NULL, final)) {
+ return false; /* widget has been destroyed */
}
}
- return TCL_OK;
+ return true;
}
/*
@@ -3414,18 +5985,15 @@ DeleteIndexRange(
static int
TextFetchSelection(
ClientData clientData, /* Information about text widget. */
- int offset, /* Offset within selection of first character
- * to be returned. */
+ 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. */
+ int maxBytes) /* Maximum number of bytes to place at buffer, not including
+ * terminating NULL character. */
{
- register TkText *textPtr = clientData;
- TkTextIndex eof;
- int count, chunkSize, offsetInSeg;
- TkTextSearch search;
- TkTextSegment *segPtr;
+ TkText *textPtr = clientData;
+ TkTextSearch *searchPtr;
+ Tcl_Obj *selTextPtr;
+ int numBytes;
if (!textPtr->exportSelection) {
return -1;
@@ -3439,94 +6007,110 @@ TextFetchSelection(
*/
if (offset == 0) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &textPtr->selIndex);
- textPtr->abortSelections = 0;
+ TkTextIndexSetupToStartOfText(&textPtr->selIndex, textPtr, textPtr->sharedTextPtr->tree);
+ textPtr->abortSelections = false;
} else if (textPtr->abortSelections) {
return 0;
}
- 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)) {
- if (offset == 0) {
- return -1;
- } else {
- return 0;
- }
- }
- textPtr->selIndex = search.curIndex;
- }
- /*
- * Each iteration through the outer loop below scans one selected range.
- * Each iteration through the inner loop scans one segment in the selected
- * range.
- */
+ searchPtr = &textPtr->selSearch;
+
+ if (offset == 0 || !TkBTreeCharTagged(&textPtr->selIndex, textPtr->selTagPtr)) {
+ TkTextIndex eof;
+
+ TkTextIndexSetupToEndOfText(&eof, textPtr, textPtr->sharedTextPtr->tree);
+ TkBTreeStartSearch(&textPtr->selIndex, &eof, textPtr->selTagPtr, searchPtr, SEARCH_NEXT_TAGON);
+ if (!TkBTreeNextTag(searchPtr)) {
+ return offset == 0 ? -1 : 0;
+ }
+ textPtr->selIndex = searchPtr->curIndex;
- count = 0;
- while (1) {
/*
* Find the end of the current range of selected text.
*/
- if (!TkBTreeNextTag(&search)) {
- Tcl_Panic("TextFetchSelection couldn't find end of range");
+ if (!TkBTreeNextTag(searchPtr)) {
+ assert(!"TextFetchSelection couldn't find end of range");
}
+ } else {
+ /* we are still inside tagged 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.
- */
+ /*
+ * Iterate through the the selected ranges and collect the text content.
+ *
+ * NOTE:
+ * The crux with TextFetchSelection is the old interface of this callback function,
+ * it does not fit with the object design (Tcl_Obj), otherwise it would expect an
+ * object as the result. Thus the actual "natural" implementation is a bit
+ * ineffecient, because we are collecting the data with an object (we are using the
+ * "get" mechanism), and afterwards the content of this object will be copied into
+ * the buffer, and the object will be destroyed. Hopefully some day function
+ * TextFetchSelection will be changed to new object design.
+ */
- while (1) {
- if (maxBytes == 0) {
- goto fetchDone;
- }
- segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg);
- chunkSize = segPtr->size - offsetInSeg;
- if (chunkSize > maxBytes) {
- chunkSize = maxBytes;
- }
- if (textPtr->selIndex.linePtr == search.curIndex.linePtr) {
- int leftInRange;
+ Tcl_IncrRefCount(selTextPtr = Tcl_NewObj());
- leftInRange = search.curIndex.byteIndex
- - textPtr->selIndex.byteIndex;
- if (leftInRange < chunkSize) {
- chunkSize = leftInRange;
- if (chunkSize <= 0) {
- break;
- }
- }
- }
- if ((segPtr->typePtr == &tkTextCharType)
- && !TkTextIsElided(textPtr, &textPtr->selIndex, NULL)) {
- memcpy(buffer, segPtr->body.chars + offsetInSeg,
- (size_t) chunkSize);
- buffer += chunkSize;
- maxBytes -= chunkSize;
- count += chunkSize;
- }
- TkTextIndexForwBytes(textPtr, &textPtr->selIndex, chunkSize,
- &textPtr->selIndex);
+ while (true) {
+ TextGetText(textPtr, &textPtr->selIndex, &searchPtr->curIndex, &textPtr->selIndex,
+ selTextPtr, maxBytes - GetByteLength(selTextPtr), true, false);
+
+ if (GetByteLength(selTextPtr) == maxBytes) {
+ break;
}
/*
* Find the beginning of the next range of selected text.
*/
- if (!TkBTreeNextTag(&search)) {
+ if (!TkBTreeNextTag(searchPtr)) {
break;
}
- textPtr->selIndex = search.curIndex;
+
+ textPtr->selIndex = searchPtr->curIndex;
+
+ /*
+ * Find the end of the current range of selected text.
+ */
+
+ if (!TkBTreeNextTag(searchPtr)) {
+ assert(!"TextFetchSelection couldn't find end of range");
+ }
}
- fetchDone:
- *buffer = 0;
- return count;
+ numBytes = GetByteLength(selTextPtr);
+ memcpy(buffer, Tcl_GetString(selTextPtr), numBytes);
+ Tcl_GuardedDecrRefCount(selTextPtr);
+ return numBytes;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * 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>>
+ */
+
+ SendVirtualEvent(textPtr->tkwin, "Selection", NULL);
}
/*
@@ -3552,7 +6136,7 @@ void
TkTextLostSelection(
ClientData clientData) /* Information about text widget. */
{
- register TkText *textPtr = clientData;
+ TkText *textPtr = clientData;
if (TkpAlwaysShowSelection(textPtr->tkwin)) {
TkTextIndex start, end;
@@ -3567,13 +6151,10 @@ TkTextLostSelection(
* "sel" tag from everything in the widget.
*/
- 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);
+ TkTextIndexSetupToStartOfText(&start, textPtr, textPtr->sharedTextPtr->tree);
+ TkTextIndexSetupToEndOfText(&end, textPtr, textPtr->sharedTextPtr->tree);
+ TkBTreeTag(textPtr->sharedTextPtr, textPtr, &start, &end, textPtr->selTagPtr,
+ false, NULL, TkTextRedrawTag);
}
/*
@@ -3589,35 +6170,6 @@ TkTextLostSelection(
/*
*----------------------------------------------------------------------
*
- * 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>>
- */
-
- TkSendVirtualEvent(textPtr->tkwin, "Selection", NULL);
-}
-
-/*
- *----------------------------------------------------------------------
- *
* TextBlinkProc --
*
* This function is called as a timer handler to blink the insertion
@@ -3637,53 +6189,45 @@ static void
TextBlinkProc(
ClientData clientData) /* Pointer to record describing text. */
{
- register TkText *textPtr = clientData;
- TkTextIndex index;
- int x, y, w, h, charWidth;
+ TkText *textPtr = clientData;
+ unsigned oldFlags = textPtr->flags;
- if ((textPtr->state == TK_TEXT_STATE_DISABLED) ||
- !(textPtr->flags & GOT_FOCUS) || (textPtr->insertOffTime == 0)) {
- if (!(textPtr->flags & GOT_FOCUS) &&
- (textPtr->insertUnfocussed != TK_TEXT_INSERT_NOFOCUS_NONE)) {
+ if (textPtr->state == TK_TEXT_STATE_DISABLED
+ || !(textPtr->flags & HAVE_FOCUS)
+ || textPtr->insertOffTime == 0) {
+ if (!(textPtr->flags & HAVE_FOCUS) && textPtr->insertUnfocussed != TK_TEXT_INSERT_NOFOCUS_NONE) {
/*
* The widget doesn't have the focus yet it is configured to
* display the cursor when it doesn't have the focus. Act now!
*/
textPtr->flags |= INSERT_ON;
- goto redrawInsert;
- }
- if ((textPtr->insertOffTime == 0) && !(textPtr->flags & INSERT_ON)) {
+ } else if (textPtr->insertOffTime == 0) {
/*
* 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) {
- textPtr->flags &= ~INSERT_ON;
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOffTime, TextBlinkProc, textPtr);
} else {
- textPtr->flags |= INSERT_ON;
- textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
- textPtr->insertOnTime, TextBlinkProc, textPtr);
- }
- redrawInsert:
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- 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);
+ if (textPtr->flags & INSERT_ON) {
+ textPtr->flags &= ~INSERT_ON;
+ textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
+ textPtr->insertOffTime, TextBlinkProc, textPtr);
} else {
- /* I-beam cursor */
- TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
- textPtr->insertWidth, h);
+ textPtr->flags |= INSERT_ON;
+ textPtr->insertBlinkHandler = Tcl_CreateTimerHandler(
+ textPtr->insertOnTime, TextBlinkProc, textPtr);
+ }
+ }
+
+ if (oldFlags != textPtr->flags) {
+ int x, y, w, h;
+
+ if (TkTextGetCursorBbox(textPtr, &x, &y, &w, &h)) {
+ int inset = textPtr->borderWidth + textPtr->highlightWidth;
+ TkTextRedrawRegion(textPtr, x + inset, y + inset, w, h);
}
}
}
@@ -3710,65 +6254,133 @@ TextBlinkProc(
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. */
+ bool viewUpdate, /* Update the view if set. */
+ bool triggerWatchDelete, /* Should we trigger the watch command for deletion? */
+ bool triggerWatchInsert, /* Should we trigger the watch command for insertion? */
+ bool userFlag, /* Trigger user modification? */
+ bool parseHyphens) /* Should we parse hyphens? (tk_textInsert) */
{
TkTextIndex index1, index2;
+ TkSharedText *sharedTextPtr;
+ TkTextTag *hyphenTagPtr = NULL;
+ int rc = TCL_OK;
int j;
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
+ assert(textPtr);
+ assert(!TkTextIsDeadPeer(textPtr));
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+
+ if (parseHyphens && objc > 1 && *Tcl_GetString(objv[0]) == '-') {
+ int argc;
+ Tcl_Obj **argv;
+
+ if (strcmp(Tcl_GetString(objv[0]), "-hyphentags") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -hyphentags", Tcl_GetString(objv[0])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ return TCL_ERROR;
+ }
+ if (Tcl_ListObjGetElements(interp, objv[1], &argc, &argv) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ for (j = 0; j < argc; ++j) {
+ TkTextTag *tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(argv[j]), NULL);
+ tagPtr->nextPtr = hyphenTagPtr;
+ hyphenTagPtr = tagPtr;
+ }
+ objc -= 2;
+ objv += 2;
}
+ for (j = 0; j < objc && GetByteLength(objv[j]) == 0; j += 2) {
+ /* empty loop body */
+ }
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);
+ while (j < objc) {
+ Tcl_Obj *stringPtr = objv[j];
+ Tcl_Obj *tagPtr = (j + 1 < objc) ? objv[j + 1] : NULL;
+ char const *string = Tcl_GetString(stringPtr);
+ unsigned length = GetByteLength(stringPtr);
+ int k = j + 2;
+ bool final;
- if (objc > (j+1)) {
- Tcl_Obj **tagNamePtrs;
- TkTextTag **oldTagArrayPtr;
- int numTags;
+ while (k < objc && GetByteLength(objv[k]) == 0) {
+ k += 2;
+ }
+ final = objc <= k;
- TkTextIndexForwBytes(textPtr, &index1, length, &index2);
- oldTagArrayPtr = TkBTreeGetTags(&index1, NULL, &numTags);
- if (oldTagArrayPtr != NULL) {
- int i;
+ if (length > 0) {
+ int numTags = 0;
+ Tcl_Obj **tagNamePtrs = NULL;
+ TkTextTagSet *tagInfoPtr = NULL;
+
+ /*
+ * Call the "watch" command for deletion. Take into account that the
+ * receiver might change the text content, although he shouldn't do this.
+ */
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
+ if (triggerWatchDelete) {
+ TkTextIndexSave(&index1);
+ if (!TriggerWatchEdit(textPtr, userFlag, "delete", &index1, &index1, NULL, final)) {
+ return rc;
}
- ckfree(oldTagArrayPtr);
+ TkTextIndexRebuild(&index1);
}
- if (Tcl_ListObjGetElements(interp, objv[j+1], &numTags,
- &tagNamePtrs) != TCL_OK) {
- return TCL_ERROR;
- } else {
+
+ if (tagPtr) {
int i;
- for (i = 0; i < numTags; i++) {
- const char *strTag = Tcl_GetString(tagNamePtrs[i]);
+ if (Tcl_ListObjGetElements(interp, tagPtr, &numTags, &tagNamePtrs) != TCL_OK) {
+ rc = TCL_ERROR;
+ } else if (numTags > 0) {
+ TkTextTag *tagPtr;
+
+ tagInfoPtr = TkTextTagSetResize(NULL, sharedTextPtr->tagInfoSize);
- TkBTreeTag(&index1, &index2,
- TkTextCreateTag(textPtr, strTag, NULL), 1);
+ for (i = 0; i < numTags; ++i) {
+ tagPtr = TkTextCreateTag(textPtr, Tcl_GetString(tagNamePtrs[i]), NULL);
+ if (tagPtr->index >= TkTextTagSetSize(tagInfoPtr)) {
+ tagInfoPtr = TkTextTagSetResize(tagInfoPtr, sharedTextPtr->tagInfoSize);
+ }
+ tagInfoPtr = TkTextTagSetAddToThis(tagInfoPtr, tagPtr->index);
+ }
}
- index1 = index2;
}
+
+ InsertChars(textPtr, &index1, &index2, string, length,
+ viewUpdate, tagInfoPtr, hyphenTagPtr, parseHyphens);
+ if (tagInfoPtr) {
+ TkTextTagSetDecrRefCount(tagInfoPtr);
+ }
+
+ /*
+ * Lastly, trigger the "watch" command for insertion. This must be the last action,
+ * probably the receiver is calling some widget commands inside the callback.
+ */
+
+ if (triggerWatchInsert) {
+ if (!TriggerWatchEdit(textPtr, userFlag, "insert", &index1, &index2, string, final)) {
+ return rc;
+ }
+ }
+
+ if (rc != TCL_OK) {
+ return rc;
+ }
+ index1 = index2;
}
+
+ j = k;
}
- return TCL_OK;
+
+ return rc;
}
/*
@@ -3800,14 +6412,15 @@ TextSearchCmd(
static const char *const switchStrings[] = {
"-hidden",
- "--", "-all", "-backwards", "-count", "-elide", "-exact", "-forwards",
- "-nocase", "-nolinestop", "-overlap", "-regexp", "-strictlimits", NULL
+ "--", "-all", "-backwards", "-count", "-discardhyphens", "-elide",
+ "-exact", "-forwards", "-nocase", "-nolinestop", "-overlap", "-regexp",
+ "-strictlimits", NULL
};
enum SearchSwitches {
SEARCH_HIDDEN,
- SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_ELIDE,
- SEARCH_EXACT, SEARCH_FWD, SEARCH_NOCASE,
- SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP, SEARCH_STRICTLIMITS
+ SEARCH_END, SEARCH_ALL, SEARCH_BACK, SEARCH_COUNT, SEARCH_DISCARDHYPHENS, SEARCH_ELIDE,
+ SEARCH_EXACT, SEARCH_FWD, SEARCH_NOCASE, SEARCH_NOLINESTOP, SEARCH_OVERLAP, SEARCH_REGEXP,
+ SEARCH_STRICTLIMITS
};
/*
@@ -3815,19 +6428,20 @@ TextSearchCmd(
* text widget specific.
*/
- searchSpec.exact = 1;
- searchSpec.noCase = 0;
- searchSpec.all = 0;
- searchSpec.backwards = 0;
+ searchSpec.textPtr = textPtr;
+ searchSpec.exact = true;
+ searchSpec.noCase = false;
+ searchSpec.all = false;
+ searchSpec.backwards = false;
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.searchElide = false;
+ searchSpec.searchHyphens = true;
+ searchSpec.noLineStop = false;
+ searchSpec.overlap = false;
+ searchSpec.strictLimits = false;
+ searchSpec.numLines = TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
searchSpec.clientData = textPtr;
searchSpec.addLineProc = &TextSearchAddNextLine;
searchSpec.foundMatchProc = &TextSearchFoundMatch;
@@ -3837,7 +6451,7 @@ TextSearchCmd(
* Parse switches and other arguments.
*/
- for (i=2 ; i<objc ; i++) {
+ for (i = 2; i < objc; ++i) {
int index;
if (Tcl_GetString(objv[i])[0] != '-') {
@@ -3851,29 +6465,28 @@ TextSearchCmd(
* the side effects of T_GIFO.
*/
- (void) Tcl_GetIndexFromObjStruct(interp, objv[i], switchStrings+1,
+ (void) Tcl_GetIndexFromObjStruct(interp, objv[i], switchStrings + 1,
sizeof(char *), "switch", 0, &index);
return TCL_ERROR;
}
switch ((enum SearchSwitches) index) {
case SEARCH_END:
- i++;
+ i += 1;
goto endOfSwitchProcessing;
case SEARCH_ALL:
- searchSpec.all = 1;
+ searchSpec.all = true;
break;
case SEARCH_BACK:
- searchSpec.backwards = 1;
+ searchSpec.backwards = true;
break;
case SEARCH_COUNT:
- if (i >= objc-1) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "no value given for \"-count\" option", -1));
+ if (i >= objc - 1) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("no value given for \"-count\" option", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "VALUE", NULL);
return TCL_ERROR;
}
- i++;
+ i += 1;
/*
* Assumption objv[i] isn't going to disappear on us during this
@@ -3882,56 +6495,56 @@ TextSearchCmd(
searchSpec.varPtr = objv[i];
break;
+ case SEARCH_DISCARDHYPHENS:
+ searchSpec.searchHyphens = false;
+ break;
case SEARCH_ELIDE:
case SEARCH_HIDDEN:
- searchSpec.searchElide = 1;
+ searchSpec.searchElide = true;
break;
case SEARCH_EXACT:
- searchSpec.exact = 1;
+ searchSpec.exact = true;
break;
case SEARCH_FWD:
- searchSpec.backwards = 0;
+ searchSpec.backwards = false;
break;
case SEARCH_NOCASE:
- searchSpec.noCase = 1;
+ searchSpec.noCase = true;
break;
case SEARCH_NOLINESTOP:
- searchSpec.noLineStop = 1;
+ searchSpec.noLineStop = true;
break;
case SEARCH_OVERLAP:
- searchSpec.overlap = 1;
+ searchSpec.overlap = true;
break;
case SEARCH_STRICTLIMITS:
- searchSpec.strictLimits = 1;
+ searchSpec.strictLimits = true;
break;
case SEARCH_REGEXP:
- searchSpec.exact = 0;
+ searchSpec.exact = false;
break;
default:
- Tcl_Panic("unexpected switch fallthrough");
+ assert(!"unexpected switch fallthrough");
}
}
endOfSwitchProcessing:
- argsLeft = objc - (i+2);
- if ((argsLeft != 0) && (argsLeft != 1)) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?switches? pattern index ?stopIndex?");
+ argsLeft = objc - (i + 2);
+ if (argsLeft != 0 && argsLeft != 1) {
+ Tcl_WrongNumArgs(interp, 2, objv, "?switches? pattern index ?stopIndex?");
return TCL_ERROR;
}
if (searchSpec.noLineStop && searchSpec.exact) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "the \"-nolinestop\" option requires the \"-regexp\" option"
- " to be present", -1));
+ "the \"-nolinestop\" option requires the \"-regexp\" option to be present", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "SEARCH_USAGE", NULL);
return TCL_ERROR;
}
if (searchSpec.overlap && !searchSpec.all) {
Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "the \"-overlap\" option requires the \"-all\" option"
- " to be present", -1));
+ "the \"-overlap\" option requires the \"-all\" option to be present", -1));
Tcl_SetErrorCode(interp, "TK", "TEXT", "SEARCH_USAGE", NULL);
return TCL_ERROR;
}
@@ -3942,8 +6555,7 @@ TextSearchCmd(
* regexp pattern depending on the flags set above.
*/
- code = SearchPerform(interp, &searchSpec, objv[i], objv[i+1],
- (argsLeft == 1 ? objv[i+2] : NULL));
+ code = SearchPerform(interp, &searchSpec, objv[i], objv[i + 1], argsLeft == 1 ? objv[i + 2] : NULL);
if (code != TCL_OK) {
goto cleanup;
}
@@ -3952,10 +6564,9 @@ TextSearchCmd(
* Set the '-count' variable, if given.
*/
- if (searchSpec.varPtr != NULL && searchSpec.countPtr != NULL) {
+ if (searchSpec.varPtr && searchSpec.countPtr) {
Tcl_IncrRefCount(searchSpec.countPtr);
- if (Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL,
- searchSpec.countPtr, TCL_LEAVE_ERR_MSG) == NULL) {
+ if (!Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL, searchSpec.countPtr, TCL_LEAVE_ERR_MSG)) {
code = TCL_ERROR;
goto cleanup;
}
@@ -3965,17 +6576,16 @@ TextSearchCmd(
* Set the result.
*/
- if (searchSpec.resPtr != NULL) {
+ if (searchSpec.resPtr) {
Tcl_SetObjResult(interp, searchSpec.resPtr);
- searchSpec.resPtr = NULL;
}
cleanup:
- if (searchSpec.countPtr != NULL) {
- Tcl_DecrRefCount(searchSpec.countPtr);
+ if (searchSpec.countPtr) {
+ Tcl_GuardedDecrRefCount(searchSpec.countPtr);
}
- if (searchSpec.resPtr != NULL) {
- Tcl_DecrRefCount(searchSpec.resPtr);
+ if (searchSpec.resPtr) {
+ Tcl_GuardedDecrRefCount(searchSpec.resPtr);
}
return code;
}
@@ -3985,7 +6595,7 @@ TextSearchCmd(
*
* TextSearchGetLineIndex --
*
- * Extract a row, text offset index position from an objPtr
+ * 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).
@@ -4012,40 +6622,35 @@ TextSearchGetLineIndex(
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. */
+ int *offsetPosPtr) /* For returning the text offset in the line. */
{
- const TkTextIndex *indexPtr;
- int line;
+ TkTextIndex index;
+ int line, byteIndex;
TkText *textPtr = searchSpecPtr->clientData;
+ TkTextLine *linePtr;
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objPtr);
- if (indexPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objPtr, &index)) {
return TCL_ERROR;
}
- line = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
+ assert(textPtr);
+ line = TkBTreeLinesTo(textPtr->sharedTextPtr->tree, textPtr, TkTextIndexGetLine(&index), NULL);
if (line >= searchSpecPtr->numLines) {
- TkTextLine *linePtr;
- int count = 0;
- TkTextSegment *segPtr;
-
- line = searchSpecPtr->numLines-1;
+ line = searchSpecPtr->numLines - 1;
linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, line);
-
- /*
- * Count the number of bytes in this line.
- */
-
- for (segPtr=linePtr->segPtr ; segPtr!=NULL ; segPtr=segPtr->nextPtr) {
- count += segPtr->size;
+ assert(linePtr); /* this may only fail with dead peers */
+ if (textPtr->endMarker == textPtr->sharedTextPtr->endMarker
+ || textPtr->endMarker->sectionPtr->linePtr != TkTextIndexGetLine(&index)) {
+ byteIndex = linePtr->size;
+ } else {
+ byteIndex = TkTextSegToIndex(textPtr->endMarker);
}
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, count);
} else {
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr,
- indexPtr->linePtr, indexPtr->byteIndex);
+ linePtr = TkTextIndexGetLine(&index);
+ byteIndex = TkTextIndexGetByteIndex(&index);
}
+ *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, byteIndex);
*linePosPtr = line;
return TCL_OK;
@@ -4072,7 +6677,15 @@ TextSearchGetLineIndex(
*----------------------------------------------------------------------
*/
-static int
+static unsigned
+CountCharsInSeg(
+ const TkTextSegment *segPtr)
+{
+ assert(segPtr->typePtr == &tkTextCharType);
+ return Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
+}
+
+static unsigned
TextSearchIndexInLine(
const SearchSpec *searchSpecPtr,
/* Search parameters. */
@@ -4080,33 +6693,40 @@ TextSearchIndexInLine(
int byteIndex) /* Index into the line. */
{
TkTextSegment *segPtr;
- TkTextIndex curIndex;
- int index, leftToScan;
+ int leftToScan;
+ unsigned index = 0;
TkText *textPtr = searchSpecPtr->clientData;
+ TkTextLine *startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
+ bool isCharSeg;
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;
+ segPtr = (startLinePtr == linePtr) ? textPtr->startMarker : linePtr->segPtr;
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for (leftToScan = byteIndex; leftToScan > 0; segPtr = segPtr->nextPtr) {
+ if ((isCharSeg = segPtr->typePtr == &tkTextCharType)
+ || (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType)) {
+ if (searchSpecPtr->searchElide || !TkTextSegmentIsElided(textPtr, segPtr)) {
+ if (leftToScan < segPtr->size) {
+ if (searchSpecPtr->exact) {
+ index += leftToScan;
+ } else {
+ index += isCharSeg ? Tcl_NumUtfChars(segPtr->body.chars, leftToScan) : 1;
+ }
+ } else if (searchSpecPtr->exact) {
+ index += isCharSeg ? segPtr->size : 2;
} else {
- index += Tcl_NumUtfChars(segPtr->body.chars, leftToScan);
+ index += isCharSeg ? CountCharsInSeg(segPtr) : 1;
}
- } else if (searchSpecPtr->exact) {
- index += segPtr->size;
- } else {
- index += Tcl_NumUtfChars(segPtr->body.chars, -1);
}
}
leftToScan -= segPtr->size;
}
+
return index;
}
@@ -4150,65 +6770,71 @@ TextSearchAddNextLine(
* one by newlines being elided. */
{
TkTextLine *linePtr, *thisLinePtr;
- TkTextIndex curIndex;
- TkTextSegment *segPtr;
+ TkTextSegment *segPtr, *lastPtr;
TkText *textPtr = searchSpecPtr->clientData;
- int nothingYet = 1;
+ TkTextLine *startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
+ TkTextLine *endLinePtr = textPtr->endMarker->sectionPtr->linePtr;
+ bool nothingYet = true;
/*
* Extract the text from the line.
*/
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
- if (linePtr == NULL) {
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum))) {
return NULL;
}
- curIndex.tree = textPtr->sharedTextPtr->tree;
thisLinePtr = linePtr;
- while (thisLinePtr != NULL) {
- int elideWraps = 0;
+ while (thisLinePtr) {
+ bool elideWraps = false;
- curIndex.linePtr = thisLinePtr;
- curIndex.byteIndex = 0;
- for (segPtr = thisLinePtr->segPtr; segPtr != NULL;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- 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.
- */
+ segPtr = (startLinePtr == thisLinePtr) ? textPtr->startMarker : thisLinePtr->segPtr;
+ lastPtr = (endLinePtr == thisLinePtr) ? textPtr->endMarker : NULL;
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
- if (segPtr->nextPtr == NULL && !nothingYet) {
- elideWraps = 1;
+ for ( ; segPtr != lastPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType
+ || (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType)) {
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ /*
+ * 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 && !nothingYet) {
+ elideWraps = true;
+ }
+ } else if (segPtr->typePtr == &tkTextCharType) {
+ Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size);
+ nothingYet = false;
+ } else {
+ Tcl_AppendToObj(theLine, "\xc2\xad", 2); /* U+00AD */
+ nothingYet = false;
}
- continue;
}
- if (segPtr->typePtr != &tkTextCharType) {
- continue;
- }
- Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size);
- nothingYet = 0;
}
if (!elideWraps) {
break;
}
- lineNum++;
+ lineNum += 1;
if (lineNum >= searchSpecPtr->numLines) {
break;
}
thisLinePtr = TkBTreeNextLine(textPtr, thisLinePtr);
- if (thisLinePtr != NULL && extraLinesPtr != NULL) {
+ if (thisLinePtr && extraLinesPtr) {
/*
* Tell our caller we have an extra line merged in.
*/
- *extraLinesPtr = (*extraLinesPtr) + 1;
+ *extraLinesPtr = *extraLinesPtr + 1;
}
}
@@ -4222,13 +6848,8 @@ TextSearchAddNextLine(
Tcl_SetObjLength(theLine, Tcl_UtfToLower(Tcl_GetString(theLine)));
}
- if (lenPtr != NULL) {
- if (searchSpecPtr->exact) {
- (void)Tcl_GetString(theLine);
- *lenPtr = theLine->length;
- } else {
- *lenPtr = Tcl_GetCharLength(theLine);
- }
+ if (lenPtr) {
+ *lenPtr = searchSpecPtr->exact ? GetByteLength(theLine) : Tcl_GetCharLength(theLine);
}
return linePtr;
}
@@ -4241,9 +6862,9 @@ TextSearchAddNextLine(
* 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).
+ * 'true' if the information was stored, 'false' 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
@@ -4253,28 +6874,25 @@ TextSearchAddNextLine(
*----------------------------------------------------------------------
*/
-static int
+static bool
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. */
+ 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;
+ TkTextIndex foundIndex;
TkTextSegment *segPtr;
- TkTextLine *linePtr;
+ TkTextLine *linePtr, *startLinePtr;
TkText *textPtr = searchSpecPtr->clientData;
+ int byteIndex;
if (lineNum == searchSpecPtr->stopLine) {
/*
@@ -4283,9 +6901,8 @@ TextSearchFoundMatch(
* and the search is over.
*/
- if (searchSpecPtr->backwards ^
- (matchOffset >= searchSpecPtr->stopOffset)) {
- return 0;
+ if (searchSpecPtr->backwards ^ (matchOffset >= searchSpecPtr->stopOffset)) {
+ return false;
}
}
@@ -4295,9 +6912,7 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->exact) {
- const char *startOfLine = Tcl_GetString(theLine);
-
- numChars = Tcl_NumUtfChars(startOfLine + matchOffset, matchLength);
+ numChars = Tcl_NumUtfChars(Tcl_GetString(theLine) + matchOffset, matchLength);
} else {
numChars = matchLength;
}
@@ -4308,9 +6923,8 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) {
- if (searchSpecPtr->backwards ^
- ((matchOffset + numChars) > searchSpecPtr->stopOffset)) {
- return 0;
+ if (searchSpecPtr->backwards ^ (matchOffset + numChars > searchSpecPtr->stopOffset)) {
+ return false;
}
}
@@ -4326,76 +6940,70 @@ TextSearchFoundMatch(
*/
linePtr = clientData;
- if (linePtr == NULL) {
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- lineNum);
+ if (!linePtr) {
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
}
-
- curIndex.tree = textPtr->sharedTextPtr->tree;
+ startLinePtr = textPtr->startMarker->sectionPtr->linePtr;
/*
* Find the starting point.
*/
leftToScan = matchOffset;
- while (1) {
- curIndex.linePtr = linePtr;
- curIndex.byteIndex = 0;
-
+ while (true) {
/*
* 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 {
- if (searchSpecPtr->exact) {
- leftToScan -= segPtr->size;
+ segPtr = (linePtr == startLinePtr) ? textPtr->startMarker : linePtr->segPtr;
+ byteIndex = TkTextSegToIndex(segPtr);
+
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for ( ; leftToScan >= 0 && segPtr; segPtr = segPtr->nextPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ int size = searchSpecPtr->exact ? segPtr->size : (int) CountCharsInSeg(segPtr);
+
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ matchOffset += size;
} else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
+ leftToScan -= size;
}
- }
- curIndex.byteIndex += segPtr->size;
- }
- if (segPtr == NULL && leftToScan >= 0) {
- /*
- * This will only happen if we are eliding newlines.
- */
-
- linePtr = TkBTreeNextLine(textPtr, linePtr);
- if (linePtr == NULL) {
- /*
- * If we reach the end of the text, we have a serious problem,
- * unless there's actually nothing left to look for.
- */
+ } else if (searchSpecPtr->searchHyphens && segPtr->typePtr == &tkTextHyphenType) {
+ int size = searchSpecPtr->exact ? 2 : 1;
- if (leftToScan == 0) {
- break;
+ if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ matchOffset += size;
} else {
- Tcl_Panic("Reached end of text in a match");
+ leftToScan -= size;
}
+ } else {
+ assert(segPtr->size <= 1);
+ matchOffset += segPtr->size;
}
+ byteIndex += segPtr->size;
+ }
- /*
- * We've wrapped to the beginning of the next logical line, which
- * has been merged with the previous one whose newline was elided.
- */
+ assert(!segPtr || leftToScan < 0 || TkBTreeNextLine(textPtr, linePtr));
- lineNum++;
- matchOffset = 0;
- } else {
+ if (segPtr || leftToScan < 0) {
break;
}
+
+ /*
+ * This will only happen if we are eliding newlines.
+ *
+ * We've wrapped to the beginning of the next logical line, which
+ * has been merged with the previous one whose newline was elided.
+ */
+
+ linePtr = linePtr->nextPtr;
+ lineNum += 1;
+ matchOffset = 0;
}
/*
@@ -4403,21 +7011,18 @@ TextSearchFoundMatch(
*/
if (searchSpecPtr->exact) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex);
} else {
- TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
+ TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum, matchOffset, &foundIndex);
}
if (searchSpecPtr->all) {
- if (searchSpecPtr->resPtr == NULL) {
- searchSpecPtr->resPtr = Tcl_NewObj();
+ if (!searchSpecPtr->resPtr) {
+ Tcl_IncrRefCount(searchSpecPtr->resPtr = Tcl_NewObj());
}
- Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr,
- TkTextNewIndexObj(textPtr, &foundIndex));
+ Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr, TkTextNewIndexObj(&foundIndex));
} else {
- searchSpecPtr->resPtr = TkTextNewIndexObj(textPtr, &foundIndex);
+ Tcl_IncrRefCount(searchSpecPtr->resPtr = TkTextNewIndexObj(&foundIndex));
}
/*
@@ -4426,34 +7031,41 @@ TextSearchFoundMatch(
* 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) {
+ /*
+ * TODO: Use new elide structure, but this requires a redesign of the whole
+ * search algorithm.
+ */
+
+ for (leftToScan += matchLength; leftToScan > 0; segPtr = segPtr->nextPtr) {
+ bool isCharSeg;
+
+ if (!segPtr) {
/*
* We are on the next line - this of course should only ever
* happen with searches which have matched across multiple lines.
*/
- linePtr = TkBTreeNextLine(textPtr, linePtr);
+ assert(TkBTreeNextLine(textPtr, linePtr));
+ linePtr = linePtr->nextPtr;
segPtr = linePtr->segPtr;
- curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
+ byteIndex = 0;
}
- if (segPtr->typePtr != &tkTextCharType) {
+
+ isCharSeg = (segPtr->typePtr == &tkTextCharType);
+
+ if (!isCharSeg && (!searchSpecPtr->searchHyphens || segPtr->typePtr != &tkTextHyphenType)) {
/*
* Anything we didn't count in the search needs adding.
*/
+ assert(segPtr->size <= 1);
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 if (!searchSpecPtr->searchElide && TkTextSegmentIsElided(textPtr, segPtr)) {
+ numChars += isCharSeg ? CountCharsInSeg(segPtr) : 1;
+ } else if (searchSpecPtr->exact) {
+ leftToScan -= isCharSeg ? segPtr->size : 2;
} else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
+ leftToScan -= isCharSeg ? CountCharsInSeg(segPtr) : 1;
}
}
@@ -4461,10 +7073,10 @@ TextSearchFoundMatch(
* Now store the count result, if it is wanted.
*/
- if (searchSpecPtr->varPtr != NULL) {
+ if (searchSpecPtr->varPtr) {
Tcl_Obj *tmpPtr = Tcl_NewIntObj(numChars);
if (searchSpecPtr->all) {
- if (searchSpecPtr->countPtr == NULL) {
+ if (!searchSpecPtr->countPtr) {
searchSpecPtr->countPtr = Tcl_NewObj();
}
Tcl_ListObjAppendElement(NULL, searchSpecPtr->countPtr, tmpPtr);
@@ -4472,7 +7084,8 @@ TextSearchFoundMatch(
searchSpecPtr->countPtr = tmpPtr;
}
}
- return 1;
+
+ return true;
}
/*
@@ -4505,7 +7118,6 @@ TkTextGetTabs(
Tcl_Obj **objv;
TkTextTabArray *tabArrayPtr;
TkTextTab *tabPtr;
- int ch;
double prevStop, lastStop;
/*
* Map these strings to TkTextTabAlign values.
@@ -4526,8 +7138,8 @@ TkTextGetTabs(
for (i = 0; i < objc; i++) {
char c = Tcl_GetString(objv[i])[0];
- if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
- count++;
+ if (c != 'l' && c != 'r' && c != 'c' && c != 'n') {
+ count += 1;
}
}
@@ -4535,8 +7147,7 @@ TkTextGetTabs(
* Parse the elements of the list one at a time to fill in the array.
*/
- tabArrayPtr = ckalloc(sizeof(TkTextTabArray)
- + (count - 1) * sizeof(TkTextTab));
+ tabArrayPtr = malloc(sizeof(TkTextTabArray) + (count - 1)*sizeof(TkTextTab));
tabArrayPtr->numTabs = 0;
prevStop = 0.0;
lastStop = 0.0;
@@ -4548,26 +7159,23 @@ TkTextGetTabs(
* downwards, to find the right integer pixel position.
*/
- if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i],
- &tabPtr->location) != TCL_OK) {
+ if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i], &tabPtr->location) != TCL_OK) {
goto error;
}
if (tabPtr->location <= 0) {
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "tab stop \"%s\" is not at a positive distance",
- Tcl_GetString(objv[i])));
+ "tab stop \"%s\" is not at a positive distance", Tcl_GetString(objv[i])));
Tcl_SetErrorCode(interp, "TK", "VALUE", "TAB_STOP", NULL);
goto error;
}
prevStop = lastStop;
- if (Tk_GetDoublePixelsFromObj(interp, textPtr->tkwin, objv[i],
- &lastStop) != TCL_OK) {
+ if (Tk_GetDoublePixelsFromObj(interp, textPtr->tkwin, objv[i], &lastStop) != TCL_OK) {
goto error;
}
- if (i > 0 && (tabPtr->location <= (tabPtr-1)->location)) {
+ if (i > 0 && tabPtr->location <= (tabPtr - 1)->location) {
/*
* This tab is actually to the left of the previous one, which is
* illegal.
@@ -4580,11 +7188,8 @@ TkTextGetTabs(
* position.
*/
- if (textPtr->charWidth > 0) {
- tabPtr->location = (tabPtr-1)->location + textPtr->charWidth;
- } else {
- tabPtr->location = (tabPtr-1)->location + 8;
- }
+ tabPtr->location = (tabPtr - 1)->location;
+ tabPtr->location += (textPtr->charWidth > 0 ? textPtr->charWidth : 8);
lastStop = tabPtr->location;
#else
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
@@ -4596,7 +7201,7 @@ TkTextGetTabs(
#endif /* _TK_ALLOW_DECREASING_TABS */
}
- tabArrayPtr->numTabs++;
+ tabArrayPtr->numTabs += 1;
/*
* See if there is an explicit alignment in the next list element.
@@ -4604,7 +7209,7 @@ TkTextGetTabs(
*/
tabPtr->alignment = LEFT;
- if ((i+1) == objc) {
+ if (i + 1 == objc) {
continue;
}
@@ -4612,9 +7217,28 @@ TkTextGetTabs(
* There may be a more efficient way of getting this.
*/
- TkUtfToUniChar(Tcl_GetString(objv[i+1]), &ch);
- if (!Tcl_UniCharIsAlpha(ch)) {
- continue;
+ { /* local scope */
+#if TCL_UTF_MAX > 4
+ /*
+ * HACK: Support of pseudo UTF-8 strings. Needed because of this
+ * bad hack with TCL_UTF_MAX > 4, the whole thing is amateurish.
+ * (See function GetLineBreakFunc() about the very severe problems
+ * with TCL_UTF_MAX > 4).
+ */
+
+ int ch;
+ TkUtfToUniChar(Tcl_GetString(objv[i + 1]), &ch);
+#else
+ /*
+ * Proper implementation for UTF-8 strings:
+ */
+
+ Tcl_UniChar ch;
+ Tcl_UtfToUniChar(Tcl_GetString(objv[i + 1]), &ch);
+#endif
+ if (!Tcl_UniCharIsAlpha(ch)) {
+ continue;
+ }
}
i += 1;
@@ -4637,7 +7261,7 @@ TkTextGetTabs(
return tabArrayPtr;
error:
- ckfree(tabArrayPtr);
+ free(tabArrayPtr);
return NULL;
}
@@ -4660,175 +7284,318 @@ TkTextGetTabs(
*----------------------------------------------------------------------
*/
+static void
+AppendOption(
+ char *result,
+ const char *str,
+ const char *delim)
+{
+ unsigned len = strlen(result);
+
+ if (delim && len > 0 && result[len - 1] != ' ' && result[len - 1] != '?') {
+ strcpy(result + len, delim);
+ len += strlen(delim);
+ }
+ strcpy(result + len, str);
+}
+
static int
-TextDumpCmd(
- register TkText *textPtr, /* Information about text widget. */
+GetDumpFlags(
+ TkText *textPtr, /* Information about text widget. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "dump". */
+ Tcl_Obj *const objv[], /* Argument objects. */
+ unsigned allowed, /* Which options are allowed? */
+ unsigned dflt, /* Default options (-all) */
+ unsigned complete, /* Complete options (-complete) */
+ unsigned *what, /* Store flags here. */
+ int *lastArg, /* Store index of last used argument, can be NULL. */
+ TkTextIndex *index1, /* Store first index here. */
+ TkTextIndex *index2, /* Store second index here. */
+ Tcl_Obj **command) /* Store command here, can be NULL. */
{
- 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. */
- TkTextLine *linePtr;
- 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
-#define TK_DUMP_WIN 0x8
-#define TK_DUMP_IMG 0x10
-#define TK_DUMP_ALL (TK_DUMP_TEXT|TK_DUMP_MARK|TK_DUMP_TAG| \
- TK_DUMP_WIN|TK_DUMP_IMG)
static const char *const optStrings[] = {
- "-all", "-command", "-image", "-mark", "-tag", "-text", "-window",
+ "-all", "-bindings", "-chars", "-command", "-complete", "-configurations",
+ "-displaychars", "-displaytext", "-dontresolvecolors",
+ "-dontresolvefonts", "-elide", "-image", "-includedbconfig",
+ "-includedefaultconfig", "-includeselection", "-includesyscolors",
+ "-includesysconfig", "-insertmark", "-mark", "-nested", "-node",
+ "-setup", "-tag", "-text", "-window",
NULL
};
enum opts {
- DUMP_ALL, DUMP_CMD, DUMP_IMG, DUMP_MARK, DUMP_TAG, DUMP_TXT, DUMP_WIN
+ DUMP_ALL, DUMP_TAG_BINDINGS, DUMP_CHARS, DUMP_CMD, DUMP_COMPLETE, DUMP_TAG_CONFIGS,
+ DUMP_DISPLAY_CHARS, DUMP_DISPLAY_TEXT, DUMP_DONT_RESOLVE_COLORS,
+ DUMP_DONT_RESOLVE_FONTS, DUMP_ELIDE, DUMP_IMG, DUMP_INCLUDE_DATABASE_CONFIG,
+ DUMP_INCLUDE_DEFAULT_CONFIG, DUMP_INCLUDE_SEL, DUMP_INCLUDE_SYSTEM_COLORS,
+ DUMP_INCLUDE_SYSTEM_CONFIG, DUMP_INSERT_MARK, DUMP_MARK, DUMP_NESTED, DUMP_NODE,
+ DUMP_TEXT_CONFIGS, DUMP_TAG, DUMP_TEXT, DUMP_WIN
+ };
+ static const unsigned dumpFlags[] = {
+ 0, TK_DUMP_TAG_BINDINGS, TK_DUMP_CHARS, 0, TK_DUMP_INSPECT_COMPLETE, TK_DUMP_TAG_CONFIGS,
+ TK_DUMP_DISPLAY_CHARS, TK_DUMP_DISPLAY_TEXT, TK_DUMP_DONT_RESOLVE_COLORS,
+ TK_DUMP_DONT_RESOLVE_FONTS, TK_DUMP_ELIDE, TK_DUMP_IMG, TK_DUMP_INCLUDE_DATABASE_CONFIG,
+ TK_DUMP_INCLUDE_DEFAULT_CONFIG, TK_DUMP_INCLUDE_SEL, TK_DUMP_INCLUDE_SYSTEM_COLORS,
+ TK_DUMP_INCLUDE_SYSTEM_CONFIG, TK_DUMP_INSERT_MARK, TK_DUMP_MARK, TK_DUMP_NESTED, TK_DUMP_NODE,
+ TK_DUMP_TEXT_CONFIGS, TK_DUMP_TAG, TK_DUMP_TEXT, TK_DUMP_WIN
};
- for (arg=2 ; arg < objc ; arg++) {
+ int arg;
+ unsigned i;
+ unsigned flags = 0;
+ const char *myOptStrings[sizeof(optStrings)/sizeof(optStrings[0])];
+ int myOptIndices[sizeof(optStrings)/sizeof(optStrings[0])];
+ int myOptCount;
+
+ assert(what);
+ assert(!index1 == !index2);
+ assert(DUMP_ALL == 0); /* otherwise next loop is wrong */
+ assert(!complete || (complete & dflt) == dflt);
+
+ /* We know that option -all is allowed in any case. */
+ myOptStrings[0] = optStrings[DUMP_ALL];
+ myOptIndices[0] = DUMP_ALL;
+ myOptCount = 1;
+
+ for (i = 1; i < sizeof(optStrings)/sizeof(optStrings[0]) - 1; ++i) {
+ if (i == DUMP_CMD ? !!command : (allowed & dumpFlags[i]) == dumpFlags[i]) {
+ myOptStrings[myOptCount] = optStrings[i];
+ myOptIndices[myOptCount] = i;
+ myOptCount += 1;
+ }
+ }
+ myOptStrings[myOptCount] = NULL;
+
+ if (lastArg) {
+ *lastArg = 0;
+ }
+ *what = 0;
+
+ for (arg = 2; arg < objc && Tcl_GetString(objv[arg])[0] == '-'; ++arg) {
int index;
- if (Tcl_GetString(objv[arg])[0] != '-') {
- break;
+
+ if (Tcl_GetString(objv[arg])[1] == '-'
+ && Tcl_GetString(objv[arg])[2] == '\0'
+ && (arg < objc - 1 || Tcl_GetString(objv[arg + 1])[0] != '-')) {
+ continue;
}
- if (Tcl_GetIndexFromObjStruct(interp, objv[arg], optStrings,
+
+ if (Tcl_GetIndexFromObjStruct(interp, objv[arg], myOptStrings,
sizeof(char *), "option", 0, &index) != TCL_OK) {
return TCL_ERROR;
}
- switch ((enum opts) index) {
+
+ switch ((enum opts) myOptIndices[index]) {
+#define CASE(Flag) case DUMP_##Flag: *what |= TK_DUMP_##Flag; flags |= TK_DUMP_##Flag; break;
+ CASE(CHARS);
+ CASE(TEXT);
+ CASE(DISPLAY_CHARS);
+ CASE(DISPLAY_TEXT);
+ CASE(TAG);
+ CASE(MARK);
+ CASE(ELIDE);
+ CASE(NESTED);
+ CASE(NODE);
+ CASE(INCLUDE_SEL);
+ CASE(INSERT_MARK);
+ CASE(TEXT_CONFIGS);
+ CASE(TAG_BINDINGS);
+ CASE(TAG_CONFIGS);
+ CASE(DONT_RESOLVE_COLORS);
+ CASE(DONT_RESOLVE_FONTS);
+ CASE(INCLUDE_DEFAULT_CONFIG);
+ CASE(INCLUDE_DATABASE_CONFIG);
+ CASE(INCLUDE_SYSTEM_CONFIG);
+ CASE(INCLUDE_SYSTEM_COLORS);
+ CASE(IMG);
+ CASE(WIN);
+#undef CASE
case DUMP_ALL:
- what = TK_DUMP_ALL;
+ *what = dflt;
break;
- case DUMP_TXT:
- what |= TK_DUMP_TEXT;
- break;
- case DUMP_TAG:
- what |= TK_DUMP_TAG;
- break;
- case DUMP_MARK:
- what |= TK_DUMP_MARK;
- break;
- case DUMP_IMG:
- what |= TK_DUMP_IMG;
- break;
- case DUMP_WIN:
- what |= TK_DUMP_WIN;
+ case DUMP_COMPLETE:
+ if (!complete)
+ goto wrongArgs;
+ *what = complete;
break;
case DUMP_CMD:
- arg++;
- if (arg >= objc) {
+ arg += 1;
+ if (!command || arg >= objc) {
goto wrongArgs;
}
- command = objv[arg];
+ *command = objv[arg];
break;
- default:
- Tcl_Panic("unexpected switch fallthrough");
+ }
+ if (~allowed & flags) {
+ goto wrongArgs;
}
}
- if (arg >= objc || arg+2 < objc) {
- wrongArgs:
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "Usage: %s dump ?-all -image -text -mark -tag -window? "
- "?-command script? index ?index2?", Tcl_GetString(objv[0])));
- Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
- return TCL_ERROR;
+ if (!(*what & dflt)) {
+ *what |= dflt;
+ }
+ if (!index1) {
+ if (arg < objc) {
+ goto wrongArgs;
+ }
+ return TCL_OK;
}
- if (what == 0) {
- what = TK_DUMP_ALL;
+ if (arg >= objc || arg + 2 < objc) {
+ goto wrongArgs;
}
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index1) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[arg], index1)) {
return TCL_ERROR;
}
- arg++;
- atEnd = 0;
+ arg += 1;
+ if (lastArg) {
+ *lastArg = arg;
+ }
if (objc == arg) {
- TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES);
- } else {
- int length;
- const char *str;
+ TkTextIndexForwChars(textPtr, index1, 1, index2, COUNT_INDICES);
+ } else if (!TkTextGetIndexFromObj(interp, textPtr, objv[arg], index2)) {
+ return TCL_ERROR;
+ }
+ return TCL_OK;
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) {
- return TCL_ERROR;
- }
- str = Tcl_GetString(objv[arg]);
- length = objv[arg]->length;
- if (strncmp(str, "end", (unsigned) length) == 0) {
- atEnd = 1;
+wrongArgs:
+ {
+ char result[500];
+ unsigned i;
+
+ result[0] = 0;
+ AppendOption(result, "?", NULL);
+
+ for (i = 0; myOptStrings[i]; ++i) {
+ if (myOptIndices[i] != DUMP_CMD) {
+ AppendOption(result, myOptStrings[i], " ");
+ }
}
+ AppendOption(result, "? ?", NULL);
+ if (command) { AppendOption(result, "-command script", NULL); }
+ AppendOption(result, "?", NULL);
+ if (index1) { AppendOption(result, " index ?index2?", NULL); }
+
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("Usage: %s %s %s",
+ Tcl_GetString(objv[0]), Tcl_GetString(objv[1]), result));
+ Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL);
}
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
+ return TCL_ERROR;
+}
+
+static int
+TextDumpCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "dump". */
+{
+ TkTextIndex index1, index2;
+ TkTextBTree tree;
+ TkTextTag *tagPtr, *tPtr;
+ int lineno; /* Current line number. */
+ unsigned what; /* bitfield to select segment types. */
+ int lastArg; /* Index of last argument. */
+ TkTextLine *linePtr;
+ TkTextIndex prevByteIndex;
+ Tcl_Obj *command = NULL; /* Script callback to apply to segments. */
+ TkTextTag *prevTagPtr = NULL;
+ int result;
+
+ assert(textPtr);
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_DUMP_ALL|TK_DUMP_NODE, TK_DUMP_DUMP_ALL,
+ 0, &what, &lastArg, &index1, &index2, &command);
+ if (result != TCL_OK) {
+ return result;
+ }
+ if (TkTextIndexCompare(&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);
+ tree = textPtr->sharedTextPtr->tree;
+ textPtr->sharedTextPtr->inspectEpoch += 1;
+ lineno = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&index1), NULL);
+ prevByteIndex = index1;
+ if (TkTextIndexBackBytes(textPtr, &index1, 1, &prevByteIndex) == 0) {
+ unsigned epoch = textPtr->sharedTextPtr->inspectEpoch + 1;
+ tagPtr = TkBTreeGetTags(&prevByteIndex, TK_TEXT_SORT_NONE, NULL);
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) { tPtr->epoch = epoch; }
+ } else {
+ tagPtr = NULL;
+ }
+ if (TkTextIndexGetLine(&index1) == TkTextIndexGetLine(&index2)) {
+ /* we are at the end, so we can ignore the return code of DumpLine */
+ DumpLine(interp, textPtr, what, TkTextIndexGetLine(&index1),
+ TkTextIndexGetByteIndex(&index1), TkTextIndexGetByteIndex(&index2),
+ lineno, command, &prevTagPtr);
} else {
- int textChanged;
- int lineend = TkBTreeLinesTo(textPtr, index2.linePtr);
- int endByteIndex = index2.byteIndex;
+ int lineend = TkBTreeLinesTo(tree, textPtr, TkTextIndexGetLine(&index2), NULL);
+ int endByteIndex = TkTextIndexGetByteIndex(&index2);
- textChanged = DumpLine(interp, textPtr, what, index1.linePtr,
- index1.byteIndex, 32000000, lineno, command);
- if (textChanged) {
+ if (!DumpLine(interp, textPtr, what, TkTextIndexGetLine(&index1),
+ TkTextIndexGetByteIndex(&index1), INT_MAX, lineno, command, &prevTagPtr)) {
if (textPtr->flags & DESTROYED) {
return TCL_OK;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ goto textChanged;
+ }
} else {
- linePtr = index1.linePtr;
+ linePtr = TkTextIndexGetLine(&index1);
}
- while ((linePtr = TkBTreeNextLine(textPtr, linePtr)) != NULL) {
- lineno++;
- if (lineno == lineend) {
+ while ((linePtr = TkBTreeNextLine(textPtr, linePtr))) {
+ if (++lineno == lineend) {
break;
}
- textChanged = DumpLine(interp, textPtr, what, linePtr, 0,
- 32000000, lineno, command);
- if (textChanged) {
+ if (!DumpLine(interp, textPtr, what, linePtr, 0, INT_MAX, lineno, command, &prevTagPtr)) {
if (textPtr->flags & DESTROYED) {
return TCL_OK;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ goto textChanged;
+ }
}
}
- if (linePtr != NULL) {
- DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno,
- command);
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
+ if (linePtr) {
+ /* we are at the end, so we can ignore the return code of DumpLine */
+ DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno, command, &prevTagPtr);
}
}
+ textChanged:
+
/*
* Special case to get the leftovers hiding at the end mark.
*/
- if (atEnd) {
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
+ if (!(textPtr->flags & DESTROYED)) {
+ if (lastArg < objc
+ && strncmp(Tcl_GetString(objv[lastArg]), "end", GetByteLength(objv[lastArg])) == 0) {
+ /*
+ * Re-get the end index, in case it has changed.
+ */
+
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[lastArg], &index2)) {
+ return TCL_ERROR;
+ }
+ if (!DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, TkTextIndexGetLine(&index2), 0, 1,
+ lineno, command, &prevTagPtr)) {
+ prevTagPtr = NULL; /* the tags are no longer valid */
+ }
}
- /*
- * Re-get the end index, in case it has changed.
- */
+ if (prevTagPtr && TkTextIndexIsEndOfText(&index2)) {
+ /*
+ * Finally print "tagoff" information, if at end of text.
+ */
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) {
- return TCL_ERROR;
+ for ( ; prevTagPtr; prevTagPtr = prevTagPtr->succPtr) {
+ if (!DumpSegment(textPtr, interp, "tagoff", prevTagPtr->name, command, &index2, what)) {
+ break;
+ }
+ }
}
- DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr,
- 0, 1, lineno, command);
}
+
return TCL_OK;
}
@@ -4841,10 +7608,10 @@ TextDumpCmd(
* "start" up to, but not including, "end".
*
* Results:
- * Returns 1 if the command callback made any changes to the text widget
+ * Returns false 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.
+ * such entities, or he aborts with an error. Returns true otherwise.
*
* Side effects:
* None, but see DumpSegment which can have arbitrary side-effects
@@ -4852,7 +7619,7 @@ TextDumpCmd(
*----------------------------------------------------------------------
*/
-static int
+static bool
DumpLine(
Tcl_Interp *interp,
TkText *textPtr,
@@ -4860,172 +7627,447 @@ DumpLine(
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. */
+ Tcl_Obj *command, /* Script to apply to the segment. */
+ TkTextTag **prevTagPtr) /* Tag information from previous segment. */
{
+ TkSharedText *sharedTextPtr;
+ TkTextSegment *sPtr;
TkTextSegment *segPtr;
+ TkTextSegment *endPtr;
+ TkTextSegment *newSegPtr;
TkTextIndex index;
- int offset = 0, textChanged = 0;
+ int offset = 0;
+ int currentSize = 0;
+ int bufSize = 0;
+ bool textChanged = false;
+ char *buffer = NULL;
+ bool eol;
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+
+ if (!*prevTagPtr && (startByte > 0 || linePtr != TkBTreeGetStartLine(textPtr))) {
+ /*
+ * If this is the first line to dump, and we are not at start of line,
+ * then we need the preceding tag information.
+ */
+
+ TkTextIndex index;
+ TkTextTag *tagPtr, *tPtr;
+ unsigned epoch = sharedTextPtr->inspectEpoch;
+
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetByteIndex2(&index, linePtr, startByte);
+ TkBTreeMoveBackward(&index, 1);
+ segPtr = TkTextIndexGetContentSegment(&index, NULL);
+ assert(segPtr);
+ tagPtr = TkBTreeGetSegmentTags(textPtr->sharedTextPtr, segPtr, textPtr, TK_TEXT_SORT_NONE, NULL);
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ tPtr->flag = epoch; /* mark as open */
+ }
+ }
/*
- * Must loop through line looking at its segments.
- * character
- * toggleOn, toggleOff
- * mark
- * image
- * window
+ * Must loop through line looking at its segments: character, hyphen, mark, image, window.
*/
segPtr = linePtr->segPtr;
- while ((offset < endByte) && (segPtr != NULL)) {
- int lineChanged = 0;
- int currentSize = segPtr->size;
+ endPtr = textPtr->endMarker;
+ eol = !segPtr->nextPtr;
- if ((what & TK_DUMP_TEXT) && (segPtr->typePtr == &tkTextCharType) &&
- (offset + currentSize > startByte)) {
- int last = currentSize; /* Index of last char in seg. */
- int first = 0; /* Index of first char in seg. */
+ if ((what & TK_DUMP_NODE)
+ && startByte == 0
+ && (!linePtr->prevPtr || linePtr->prevPtr->parentPtr != linePtr->parentPtr)) {
+ char buf[20];
+ unsigned depth, number;
- if (offset + currentSize > endByte) {
- last = endByte - offset;
- }
- if (startByte > offset) {
- first = startByte - offset;
- }
- 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.
- */
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetToStartOfLine2(&index, linePtr);
+ number = TkBTreeChildNumber(sharedTextPtr->tree, linePtr, &depth);
+ snprintf(buf, sizeof(buf), "%d:%d", number, depth);
- int length = last - first;
- char *range = ckalloc(length + 1);
+ if (!DumpSegment(textPtr, interp, "node", buf, command, &index, what)) {
+ goto textChanged;
+ }
+ }
- memcpy(range, segPtr->body.chars + first, length);
- range[length] = '\0';
+ while (segPtr && offset < endByte) {
+ currentSize = segPtr->size;
- 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 == &tkTextLeftMarkType
- || segPtr->typePtr == &tkTextRightMarkType)) {
- const char *name;
- TkTextMark *markPtr = &segPtr->body.mark;
-
- 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 (offset + MAX(1, currentSize) > startByte) {
+ if ((what & TK_DUMP_TAG) && segPtr->tagInfoPtr) {
+ TkTextTag *tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr,
+ TK_TEXT_SORT_ASCENDING, NULL);
+ unsigned epoch = sharedTextPtr->inspectEpoch;
+ unsigned nextEpoch = epoch + 1;
+ TkTextTag *tPtr;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag == epoch) {
+ tPtr->flag = nextEpoch; /* mark as still open */
+ }
}
- if (name != NULL) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "mark", name,
- command, &index, what);
+
+ if (*prevTagPtr) {
+ /*
+ * Print "tagoff" information.
+ */
+
+ for (tPtr = *prevTagPtr; tPtr; tPtr = tPtr->succPtr) {
+ if (tPtr->flag == epoch) { /* should be closed? */
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, "tagoff",
+ tPtr->name, command, &index, what)) {
+ goto textChanged;
+ }
+ tPtr->flag = 0; /* mark as closed */
+ }
+ }
}
- } else if ((what & TK_DUMP_TAG) &&
- (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 == &tkTextEmbImageType)) {
- TkTextEmbImage *eiPtr = &segPtr->body.ei;
- const 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 == &tkTextEmbWindowType)) {
- TkTextEmbWindow *ewPtr = &segPtr->body.ew;
- const char *pathname;
-
- if (ewPtr->tkwin == (Tk_Window) NULL) {
- pathname = "";
- } else {
- pathname = Tk_PathName(ewPtr->tkwin);
+
+ /*
+ * Print "tagon" information.
+ */
+
+ sharedTextPtr->inspectEpoch = ++epoch;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag != epoch) {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, "tagon", tPtr->name, command, &index, what)) {
+ goto textChanged;
+ }
+ tPtr->flag = epoch; /* mark as open */
+ }
+ tPtr->succPtr = tPtr->nextPtr;
}
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "window", pathname,
- command, &index, what);
+
+ *prevTagPtr = tagPtr;
}
- }
- offset += currentSize;
- if (lineChanged) {
- TkTextSegment *newSegPtr;
- int newOffset = 0;
+ if (what & segPtr->typePtr->group) {
+ assert(segPtr->typePtr->group != SEG_GROUP_BRANCH);
- textChanged = 1;
+ if (segPtr->typePtr->group == SEG_GROUP_CHAR) {
+ int last = currentSize; /* Index of last char in seg. */
+ int first = 0; /* Index of first char in seg. */
- /*
- * Our indices are no longer valid.
- */
+ if (offset + currentSize > endByte) {
+ last = endByte - offset;
+ }
+ if (startByte > offset) {
+ first = startByte - offset;
+ }
+ 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.
+ */
- if (textPtr->flags & DESTROYED) {
- return textChanged;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- newSegPtr = linePtr->segPtr;
- if (segPtr != newSegPtr) {
- while ((newOffset < endByte) && (newOffset < offset)
- && (newSegPtr != NULL)) {
- newOffset += currentSize;
- newSegPtr = newSegPtr->nextPtr;
- if (segPtr == newSegPtr) {
- break;
+ int length = last - first;
+
+ if (length >= bufSize) {
+ bufSize = MAX(length + 1, 2*length);
+ buffer = realloc(buffer, bufSize);
+ }
+
+ memcpy(buffer, segPtr->body.chars + first, length);
+ buffer[length] = '\0';
+
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno,
+ offset + first, &index);
+ if (!DumpSegment(textPtr, interp, "text", buffer, command, &index, what)) {
+ goto textChanged;
+ }
+ } else {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno,
+ offset + first, &index);
+ if (!DumpSegment(textPtr, interp, "text",
+ segPtr->body.chars + first, command, &index, what)) {
+ goto textChanged;
+ }
}
- }
- if (segPtr != newSegPtr && newOffset == offset
- && currentSize == 0) {
- TkTextSegment *searchPtr = newSegPtr;
+ } else if (segPtr == endPtr) {
+ if (linePtr == TkBTreeGetLastLine(textPtr)) {
+ break; /* finished */
+ }
+ /* print final newline in next iteration */
+ currentSize = linePtr->size - offset - 1;
+ startByte = offset + currentSize + linePtr->lastPtr->size - 1;
+ segPtr = linePtr->lastPtr->prevPtr;
+ } else {
+ char const *value = NULL;
- while (searchPtr != NULL && searchPtr->size == 0) {
- if (searchPtr == segPtr) {
- newSegPtr = searchPtr;
- break;
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_MARK:
+ value = TkTextMarkName(sharedTextPtr, textPtr, segPtr);
+ break;
+ case SEG_GROUP_IMAGE: {
+ TkTextEmbImage *eiPtr = &segPtr->body.ei;
+ value = eiPtr->name ? eiPtr->name : "";
+ break;
+ }
+ case SEG_GROUP_WINDOW: {
+ TkTextEmbWindow *ewPtr = &segPtr->body.ew;
+ value = ewPtr->tkwin ? Tk_PathName(ewPtr->tkwin) : "";
+ break;
+ }
+ case SEG_GROUP_HYPHEN:
+ value = "";
+ break;
+ }
+ if (value) {
+ TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineno, offset, &index);
+ if (!DumpSegment(textPtr, interp, segPtr->typePtr->name, value, command,
+ &index, what)) {
+ goto textChanged;
}
- searchPtr = searchPtr->nextPtr;
}
}
- segPtr = newSegPtr;
}
}
- if (segPtr != NULL) {
+
+ offset += currentSize;
+ segPtr = segPtr->nextPtr;
+ continue;
+
+ textChanged:
+
+ /*
+ * Our indices, segments, and tag chains are no longer valid. It's a bad
+ * idea to do changes while the dump is running, it's impossible to
+ * synchronize in any case, but we will try the best.
+ */
+
+ *prevTagPtr = NULL;
+ textChanged = true;
+
+ if (eol || (textPtr->flags & DESTROYED)) {
+ break;
+ }
+
+ offset += currentSize;
+ if (!(linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineno))) {
+ break;
+ }
+ TkTextIndexClear(&index, textPtr);
+ TkTextIndexSetByteIndex2(&index, linePtr, MIN(offset, linePtr->size - 1));
+
+ sPtr = newSegPtr = TkTextIndexGetFirstSegment(&index, NULL);
+ while (sPtr && sPtr != segPtr) {
+ sPtr = sPtr->nextPtr;
+ }
+ if (sPtr != segPtr) {
+ segPtr = newSegPtr;
+ } else if (offset >= segPtr->size) {
segPtr = segPtr->nextPtr;
}
}
- return textChanged;
+
+ free(buffer);
+ return !textChanged;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TextChecksumCmd --
+ *
+ * Return the checksum over the whole content.
+ * About the format see documentation.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
+ *
+ *----------------------------------------------------------------------
+ */
+
+static uint32_t
+ComputeChecksum(
+ uint32_t crc,
+ const char *buf,
+ unsigned len)
+{
+ static const uint32_t crcTable[256] = {
+ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
+ 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
+ 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+ 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
+ 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
+ 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+ 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
+ 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
+ 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+ 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
+ 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
+ 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+ 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
+ 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
+ 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+ 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
+ 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
+ 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+ 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
+ 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
+ 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+ 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
+ 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
+ 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+ 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
+ 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
+ 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+ 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
+ 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
+ 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+ 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
+ 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+ };
+
+ assert(buf);
+
+ /* basic algorithm stolen from zlib/crc32.c (public domain) */
+
+#define DO1(buf) crc = crcTable[((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8);
+#define DO2(buf) DO1(buf); DO1(buf);
+#define DO4(buf) DO2(buf); DO2(buf);
+#define DO8(buf) DO4(buf); DO4(buf);
+
+ crc = crc ^ 0xffffffff;
+
+ if (len == 0) {
+ while (*buf) {
+ DO1(buf);
+ }
+ } else {
+ while (len >= 8) {
+ DO8(buf);
+ len -= 8;
+ }
+ while (len--) {
+ DO1(buf);
+ }
+ }
+ return crc ^ 0xffffffff;
+}
+
+static int
+TextChecksumCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. Someone else has already parsed this command
+ * enough to know that objv[1] is "checksum". */
+{
+ const TkSharedText *sharedTextPtr;
+ const TkTextSegment *segPtr;
+ const TkTextSegment *endPtr;
+ const TkTextLine *linePtr;
+ TkTextTag **tagArrPtr = NULL; /* avoid compiler warning */
+ unsigned what;
+ unsigned crc;
+ int result;
+
+ assert(textPtr);
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_CRC_ALL, TK_DUMP_CRC_DFLT, 0,
+ &what, NULL, NULL, NULL, NULL);
+
+ if (result != TCL_OK) {
+ return result;
+ }
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ segPtr = sharedTextPtr->startMarker;
+ endPtr = sharedTextPtr->endMarker;
+ linePtr = segPtr->sectionPtr->linePtr;
+ if (endPtr->sectionPtr->linePtr != linePtr) {
+ endPtr = NULL;
+ }
+ crc = 0;
+
+ if ((what & SEG_GROUP_TAG)) {
+ tagArrPtr = malloc(sizeof(tagArrPtr[0])*sharedTextPtr->numTags);
+ }
+
+ /*
+ * Note that 0xff cannot occur in UTF-8 strings, so we can use this value as a separator.
+ */
+
+ while (segPtr != endPtr) {
+ if (segPtr->tagInfoPtr
+ && (what & SEG_GROUP_TAG)
+ && segPtr->tagInfoPtr != sharedTextPtr->emptyTagInfoPtr) {
+ unsigned i = TkTextTagSetFindFirst(segPtr->tagInfoPtr);
+ unsigned n = 0;
+
+ for ( ; i != TK_TEXT_TAG_SET_NPOS; i = TkTextTagSetFindNext(segPtr->tagInfoPtr, i)) {
+ assert(sharedTextPtr->tagLookup[i]);
+ tagArrPtr[n++] = sharedTextPtr->tagLookup[i];
+ }
+
+ TkTextSortTags(n, tagArrPtr);
+
+ for (i = 0; i < n; ++i) {
+ crc = ComputeChecksum(crc, "\xff\x00", 2);
+ crc = ComputeChecksum(crc, tagArrPtr[i]->name, 0);
+ }
+ }
+ switch ((int) segPtr->typePtr->group) {
+ case SEG_GROUP_CHAR:
+ if (what & SEG_GROUP_CHAR) {
+ crc = ComputeChecksum(crc, "\xff\x01", 2);
+ crc = ComputeChecksum(crc, segPtr->body.chars, segPtr->size);
+ }
+ break;
+ case SEG_GROUP_HYPHEN:
+ if (what & SEG_GROUP_HYPHEN) {
+ crc = ComputeChecksum(crc, "\xff\x02", 2);
+ }
+ break;
+ case SEG_GROUP_WINDOW:
+ if ((what & SEG_GROUP_WINDOW)) {
+ crc = ComputeChecksum(crc, "\xff\x03", 2);
+ crc = ComputeChecksum(crc, Tk_PathName(segPtr->body.ew.tkwin), 0);
+ }
+ break;
+ case SEG_GROUP_IMAGE:
+ if ((what & SEG_GROUP_IMAGE) && segPtr->body.ei.name) {
+ crc = ComputeChecksum(crc, "\xff\x04", 2);
+ crc = ComputeChecksum(crc, segPtr->body.ei.name, 0);
+ }
+ break;
+ case SEG_GROUP_MARK:
+ if ((what & SEG_GROUP_MARK) && TkTextIsNormalMark(segPtr)) {
+ const char *name;
+ const char *signature;
+
+ name = TkTextMarkName(sharedTextPtr, NULL, segPtr);
+ signature = (segPtr->typePtr == &tkTextRightMarkType) ? "\xff\x05" : "\xff\x06";
+ crc = ComputeChecksum(crc, signature, 2);
+ crc = ComputeChecksum(crc, name, 0);
+ }
+ break;
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextBranchType && (what & TK_DUMP_DISPLAY)) {
+ segPtr = segPtr->body.branch.nextPtr;
+ }
+ break;
+ }
+ if (!(segPtr = segPtr->nextPtr)) {
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ }
+ }
+
+ if ((what & SEG_GROUP_TAG)) {
+ free(tagArrPtr);
+ }
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(crc));
+ return TCL_OK;
}
/*
@@ -5037,10 +8079,10 @@ DumpLine(
* make a script callback with that information as arguments.
*
* Results:
- * Returns 1 if the command callback made any changes to the text widget
+ * Returns 'false' 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.
+ * such entities, or he aborts with an error. Returns 'true' otherwise.
*
* Side effects:
* Either evals the callback or appends elements to the result string.
@@ -5049,7 +8091,7 @@ DumpLine(
*----------------------------------------------------------------------
*/
-static int
+static bool
DumpSegment(
TkText *textPtr,
Tcl_Interp *interp,
@@ -5066,13 +8108,13 @@ DumpSegment(
values[0] = Tcl_NewStringObj(key, -1);
values[1] = Tcl_NewStringObj(value, -1);
values[2] = Tcl_NewStringObj(buffer, -1);
- tuple = Tcl_NewListObj(3, values);
- if (command == NULL) {
+ Tcl_IncrRefCount(tuple = Tcl_NewListObj(3, values));
+ if (!command) {
Tcl_ListObjAppendList(NULL, Tcl_GetObjResult(interp), tuple);
- Tcl_DecrRefCount(tuple);
- return 0;
+ Tcl_GuardedDecrRefCount(tuple);
+ return true;
} else {
- int oldStateEpoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
+ unsigned oldStateEpoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
Tcl_DString buf;
int code;
@@ -5083,146 +8125,826 @@ DumpSegment(
code = Tcl_EvalEx(interp, Tcl_DStringValue(&buf), -1, 0);
Tcl_DStringFree(&buf);
if (code != TCL_OK) {
- Tcl_AddErrorInfo(interp,
- "\n (segment dumping command executed by text)");
+ Tcl_AddErrorInfo(interp, "\n (segment dumping command executed by text)");
Tcl_BackgroundException(interp, code);
}
- Tcl_DecrRefCount(tuple);
- return ((textPtr->flags & DESTROYED) ||
- TkBTreeEpoch(textPtr->sharedTextPtr->tree) != oldStateEpoch);
+ Tcl_GuardedDecrRefCount(tuple);
+ return !(textPtr->flags & DESTROYED)
+ && TkBTreeEpoch(textPtr->sharedTextPtr->tree) == oldStateEpoch;
}
}
/*
*----------------------------------------------------------------------
*
- * TextEditUndo --
+ * TkTextInspectOptions --
*
- * Undo the last change.
+ * Build information from option table for "inspect".
*
* Results:
* None.
*
* Side effects:
- * 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).
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
*
*----------------------------------------------------------------------
*/
-static int
-TextEditUndo(
- TkText *textPtr) /* Overall information about text widget. */
+static bool
+MatchColors(
+ const char *name,
+ int len,
+ const char *hexColor,
+ const char *colorName)
{
- int status;
- Tcl_Obj *cmdObj;
- int code;
+ assert(strlen(hexColor) == 13);
+ assert(strlen(colorName) == 5);
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
+ switch (len) {
+ case 5: return strncasecmp(name, colorName, 5) == 0;
+ case 7: return strncasecmp(name, hexColor, 7) == 0;
+ case 13: return strncasecmp(name, hexColor, 13) == 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').
- */
+ return false;
+}
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO;
+static bool
+TestIfEqual(
+ const char *opt1,
+ int opt1Len,
+ const char *opt2,
+ int opt2Len)
+{
+ int i;
+
+ if (MatchColors(opt1, opt1Len, "#ffffffffffff", "white")) {
+ return MatchColors(opt2, opt2Len, "#ffffffffffff", "white");
+ }
+ if (MatchColors(opt1, opt1Len, "#000000000000", "black")) {
+ return MatchColors(opt2, opt2Len, "#000000000000", "black");
}
+ if (opt1Len != opt2Len) {
+ return false;
+ }
+ for (i = 0; i < opt1Len; ++i) {
+ if (opt1[i] != opt2[i]) {
+ return false;
+ }
+ }
+ return true;
+}
- status = TkUndoRevert(textPtr->sharedTextPtr->undoStack);
+#if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7)
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
- }
- textPtr->sharedTextPtr->undo = 1;
+static Tcl_Obj *
+GetFontAttrs(
+ TkText *textPtr,
+ int argc,
+ Tcl_Obj **args)
+{
+ Tcl_Interp *interp = textPtr->interp;
+ Tcl_Obj *objPtr = NULL;
+
+ if (Tk_FontObjCmd(textPtr->tkwin, interp, argc, args) == TCL_OK) {
+ Tcl_Obj *result = Tcl_GetObjResult(interp);
+ Tcl_Obj *family = NULL;
+ Tcl_Obj *size = NULL;
+ Tcl_Obj *slant = NULL;
+ Tcl_Obj *weight = NULL;
+ Tcl_Obj *underline = NULL;
+ Tcl_Obj *overstrike = NULL;
+ Tcl_Obj **objv;
+ int objc, i;
+
+ if (Tcl_ListObjGetElements(interp, result, &objc, &objv) == TCL_OK) {
+ for (i = 0; i < objc - 1; ++i) {
+ if (Tcl_GetString(objv[i])[0] == '-') {
+ switch (Tcl_GetString(objv[i])[1]) {
+ case 'f': /* -family */
+ family = objv[i + 1];
+ break;
+ case 'o': /* -overstrike */
+ overstrike = objv[i + 1];
+ break;
+ case 's':
+ switch (Tcl_GetString(objv[i])[2]) {
+ case 'i': /* -size */
+ size = objv[i + 1];
+ break;
+ case 'l': /* -slant */
+ slant = objv[i + 1];
+ break;
+ }
+ break;
+ case 'u': /* -underline */
+ underline = objv[i + 1];
+ break;
+ case 'w': /* -weight */
+ weight = objv[i + 1];
+ break;
+ }
+ }
+ }
+ }
- /*
- * Convert undo/redo temporary marks set by TkUndoRevert() into
- * indices left in the interp result.
- */
+ if (family && size) {
+ Tcl_DString str;
+ int boolean;
- cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s",
- Tk_PathName(textPtr->tkwin));
- Tcl_IncrRefCount(cmdObj);
- code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(textPtr->interp,
- "\n (on undoing)");
- Tcl_BackgroundException(textPtr->interp, code);
+ Tcl_DStringInit(&str);
+ Tcl_DStringAppendElement(&str, Tcl_GetString(family));
+ Tcl_DStringAppendElement(&str, Tcl_GetString(size));
+ if (weight && strcmp(Tcl_GetString(weight), "normal") != 0) {
+ Tcl_DStringAppendElement(&str, Tcl_GetString(weight));
+ }
+ if (slant && strcmp(Tcl_GetString(slant), "roman") != 0) {
+ Tcl_DStringAppendElement(&str, Tcl_GetString(slant));
+ }
+ if (underline && Tcl_GetBooleanFromObj(NULL, underline, &boolean) == TCL_OK && boolean) {
+ Tcl_DStringAppendElement(&str, "underline");
+ }
+ if (overstrike && Tcl_GetBooleanFromObj(NULL, overstrike, &boolean) == TCL_OK && boolean) {
+ Tcl_DStringAppendElement(&str, "overstrike");
+ }
+
+ objPtr = Tcl_NewStringObj(Tcl_DStringValue(&str), Tcl_DStringLength(&str));
+ Tcl_DStringFree(&str);
+ }
+
+ Tcl_ResetResult(interp);
}
- Tcl_DecrRefCount(cmdObj);
- return status;
+ return objPtr;
+}
+
+#endif /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+
+static bool
+IsPossibleColorOption(
+ const char *s)
+{
+ unsigned len = strlen(s);
+
+ assert(s[0] == '-');
+
+ return (len >= 6 && strcmp(s + len - 5, "color") == 0)
+ || (len >= 7 && strcmp(s + len - 6, "ground") == 0);
+}
+
+void
+TkTextInspectOptions(
+ TkText *textPtr,
+ const void *recordPtr,
+ Tk_OptionTable optionTable,
+ Tcl_DString *result, /* should be already initialized */
+ int flags)
+{
+ Tcl_Obj *objPtr;
+ Tcl_Interp *interp = textPtr->interp;
+
+ Tcl_DStringSetLength(result, 0);
+
+ if ((objPtr = Tk_GetOptionInfo(interp, (char *) recordPtr, optionTable, NULL, textPtr->tkwin))) {
+#if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7)
+ Tcl_Obj *font = NULL; /* shut up compiler */
+ Tcl_Obj *actual = NULL; /* shut up compiler */
+#endif /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+ Tcl_Obj **objv;
+ int objc = 0;
+ int i;
+
+ Tcl_ListObjGetElements(interp, objPtr, &objc, &objv);
+
+#if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7)
+ if (!(flags & INSPECT_DONT_RESOLVE_FONTS)) {
+ Tcl_IncrRefCount(font = Tcl_NewStringObj("font", -1));
+ Tcl_IncrRefCount(actual = Tcl_NewStringObj("actual", -1));
+ }
+#endif /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+
+ for (i = 0; i < objc; ++i) {
+ Tcl_Obj **argv;
+ int argc = 0;
+
+ Tcl_ListObjGetElements(interp, objv[i], &argc, &argv);
+
+ if (argc >= 5) { /* only if this option has a non-default value */
+ Tcl_Obj *valObj = argv[4];
+ Tcl_Obj *myValObj;
+ Tcl_Obj *nameObj;
+ int myFlags = flags;
+
+ if (GetByteLength(valObj) == 0) {
+ continue;
+ }
+
+ if (!(myFlags & INSPECT_INCLUDE_DATABASE_CONFIG)
+ || myFlags & (INSPECT_INCLUDE_SYSTEM_CONFIG|INSPECT_INCLUDE_DEFAULT_CONFIG)) {
+ const char *name = Tcl_GetString(argv[1]);
+ const char *cls = Tcl_GetString(argv[2]);
+ Tk_Uid dfltUid = Tk_GetOption(textPtr->tkwin, name, cls);
+
+ if (dfltUid) {
+ const char *value = Tcl_GetString(valObj);
+ int valueLen = GetByteLength(valObj);
+
+ if (TestIfEqual(dfltUid, strlen(dfltUid), value, valueLen)) {
+ if (!(myFlags & INSPECT_INCLUDE_DATABASE_CONFIG)) {
+ continue;
+ }
+ myFlags |= INSPECT_INCLUDE_SYSTEM_CONFIG|INSPECT_INCLUDE_DEFAULT_CONFIG;
+ }
+ }
+ }
+
+ if (!(myFlags & INSPECT_INCLUDE_SYSTEM_CONFIG)
+ || myFlags & INSPECT_INCLUDE_DEFAULT_CONFIG) {
+ const char *name = Tcl_GetString(argv[1]);
+ const char *cls = Tcl_GetString(argv[2]);
+ Tcl_Obj *dfltObj;
+
+ dfltObj = TkpGetSystemDefault(textPtr->tkwin, name, cls);
+
+ if (dfltObj) {
+ const char *dflt = Tcl_GetString(dfltObj);
+ const char *value = Tcl_GetString(valObj);
+ int dfltLen = GetByteLength(dfltObj);
+ int valueLen = GetByteLength(valObj);
+
+ if (TestIfEqual(dflt, dfltLen, value, valueLen)) {
+ if (!(myFlags & INSPECT_INCLUDE_SYSTEM_CONFIG)) {
+ continue;
+ }
+ myFlags |= INSPECT_INCLUDE_DEFAULT_CONFIG;
+ }
+ }
+ }
+
+ if (!(myFlags & INSPECT_INCLUDE_DEFAULT_CONFIG)) {
+ const char *dflt = Tcl_GetString(argv[3]);
+ const char *value = Tcl_GetString(valObj);
+ int dfltLen = GetByteLength(argv[3]);
+ int valueLen = GetByteLength(valObj);
+
+ if (TestIfEqual(dflt, dfltLen, value, valueLen)) {
+ continue;
+ }
+ }
+
+ myValObj = valObj;
+ nameObj = argv[0];
+ if (Tcl_DStringLength(result) > 0) {
+ Tcl_DStringAppend(result, " ", 1);
+ }
+ Tcl_DStringAppend(result, Tcl_GetString(nameObj), GetByteLength(nameObj));
+ Tcl_DStringAppend(result, " ", 1);
+
+ if (!(flags & INSPECT_DONT_RESOLVE_FONTS)
+ && strcmp(Tcl_GetString(nameObj), "-font") == 0) {
+ const char *s = Tcl_GetString(valObj);
+ unsigned len = GetByteLength(valObj);
+
+ /*
+ * Don't resolve font names like TkFixedFont, TkTextFont, etc.
+ */
+
+ if (len < 7
+ || strncmp(s, "Tk", 2) != 0
+ || strncmp(s + len - 4, "Font", 4) != 0) {
+#if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7)
+ Tcl_Obj *args[3];
+ Tcl_Obj *result;
+
+ /*
+ * Try to resolve the font name to the actual font attributes.
+ */
+
+ args[0] = font;
+ args[1] = actual;
+ args[2] = valObj;
+
+ if ((result = GetFontAttrs(textPtr, 3, args))) {
+ myValObj = result;
+ }
+#else /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+ Tk_Font tkfont = Tk_AllocFontFromObj(interp, textPtr->tkwin, valObj);
+
+ if (tkfont) {
+ Tcl_IncrRefCount(myValObj = TkFontGetDescription(tkfont));
+ Tk_FreeFont(tkfont);
+ }
+#endif /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+ }
+ } else if ((flags & (INSPECT_DONT_RESOLVE_COLORS|INSPECT_INCLUDE_SYSTEM_COLORS)) !=
+ (INSPECT_DONT_RESOLVE_COLORS|INSPECT_INCLUDE_SYSTEM_COLORS)
+ && IsPossibleColorOption(Tcl_GetString(nameObj))) {
+ const char *colorName = Tcl_GetString(valObj);
+
+ if (strncasecmp(colorName, "system", 6) == 0) {
+ XColor *col;
+
+ if (!(flags & INSPECT_INCLUDE_SYSTEM_COLORS)) {
+ continue;
+ }
+
+ /*
+ * The color lookup expects a lowercase "system", but the defaults
+ * are providing the uppercase form "System", so we need to build
+ * a lowercase form.
+ */
+
+ col = Tk_GetColor(interp, textPtr->tkwin, colorName);
+
+ if (col) {
+ myValObj = Tcl_ObjPrintf("#%02x%02x%02x", col->red, col->green, col->blue);
+ Tcl_IncrRefCount(myValObj);
+ Tk_FreeColor(col);
+ } else {
+ /*
+ * This should not happen. We will clear the error result, and
+ * print a warning.
+ */
+ Tcl_SetObjResult(interp, Tcl_NewObj());
+ Tcl_SetObjErrorCode(interp, Tcl_NewObj());
+ fprintf(stderr, "tk::text: couldn't resolve system color '%s'\n", colorName);
+ }
+ }
+ }
+
+ Tcl_DStringAppendElement(result, Tcl_GetString(myValObj));
+
+ if (myValObj != valObj) {
+ Tcl_GuardedDecrRefCount(myValObj);
+ }
+ }
+ }
+
+#if TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7)
+ if (!(flags & INSPECT_DONT_RESOLVE_FONTS)) {
+ Tcl_GuardedDecrRefCount(actual);
+ Tcl_GuardedDecrRefCount(font);
+ }
+#endif /* TCL_MAJOR_VERSION < 8 || (TCL_MAJOR_VERSION == 8 && TCL_MINOR_VERSION < 7) */
+ }
}
/*
*----------------------------------------------------------------------
*
- * TextEditRedo --
+ * TextInspectCmd --
*
- * Redo the last undone change.
+ * Return information about text and the associated tags.
+ * About the format see documentation.
*
* Results:
- * None.
+ * A standard Tcl result.
*
* Side effects:
- * 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).
+ * Memory is allocated for the result, if needed (standard Tcl result
+ * side effects).
*
*----------------------------------------------------------------------
*/
-static int
-TextEditRedo(
- TkText *textPtr) /* Overall information about text widget. */
+static void
+GetBindings(
+ TkText *textPtr,
+ const char *name,
+ Tk_BindingTable bindingTable,
+ Tcl_DString *str)
{
- int status;
- Tcl_Obj *cmdObj;
- int code;
+ Tcl_Interp *interp = textPtr->interp;
+ Tcl_DString str2;
+ Tcl_Obj **argv;
+ int argc, i;
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
+ Tk_GetAllBindings(interp, bindingTable, (ClientData) name);
+ Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &argc, &argv);
+ Tcl_DStringInit(&str2);
+
+ for (i = 0; i < argc; ++i) {
+ const char *event = Tcl_GetString(argv[i]);
+ const char *binding = Tk_GetBinding(interp, bindingTable, (ClientData) name, event);
+ char *p;
+
+ Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp), &argc, &argv);
+
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "bind");
+ Tcl_DStringAppendElement(str, name);
+ Tcl_DStringAppendElement(str, event);
+
+ Tcl_DStringSetLength(&str2, 0);
+ p = strchr(binding, '\n');
+ while (p) {
+ Tcl_DStringAppend(&str2, binding, p - binding);
+ Tcl_DStringAppend(&str2, "; ", 2);
+ binding = p + 1;
+ p = strchr(binding, '\n');
+ }
+ Tcl_DStringAppend(&str2, binding, -1);
+
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(&str2));
+ Tcl_DStringEndSublist(str);
}
- /*
- * 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').
- */
+ Tcl_DStringFree(&str2);
+ Tcl_ResetResult(interp);
+}
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO;
+static int
+TextInspectCmd(
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Interp *interp, /* Current interpreter. */
+ int objc, /* Number of arguments. */
+ Tcl_Obj *const objv[]) /* Argument objects. */
+{
+ TkSharedText *sharedTextPtr;
+ TkTextTag *prevTagPtr;
+ TkTextSegment *nextPtr;
+ TkTextSegment *prevPtr;
+ Tcl_DString buf[2];
+ Tcl_DString *str = &buf[0];
+ Tcl_DString *opts = &buf[1];
+ TkTextTag **tagArray;
+ TkTextTag *tagPtr;
+ TkTextTag *tPtr;
+ unsigned tagArrSize;
+ unsigned epoch;
+ unsigned what;
+ bool closeSubList;
+ int result;
+ int flags;
+
+ result = GetDumpFlags(textPtr, interp, objc, objv, TK_DUMP_INSPECT_ALL, TK_DUMP_INSPECT_DFLT,
+ TK_DUMP_INSPECT_COMPLETE, &what, NULL, NULL, NULL, NULL);
+ if (result != TCL_OK) {
+ return result;
+ }
+
+ Tcl_DStringInit(str);
+ Tcl_DStringInit(opts);
+ sharedTextPtr = textPtr->sharedTextPtr;
+ epoch = sharedTextPtr->inspectEpoch;
+ tagPtr = textPtr->selTagPtr; /* any non-null value */
+ nextPtr = textPtr->startMarker;
+ closeSubList = false;
+ prevTagPtr = NULL;
+ prevPtr = NULL;
+ tagArrSize = 128;
+ tagArray = malloc(tagArrSize * sizeof(tagArray[0]));
+ flags = 0;
+
+ if (what & TK_DUMP_DONT_RESOLVE_FONTS) { flags |= INSPECT_DONT_RESOLVE_FONTS; }
+ if (what & TK_DUMP_DONT_RESOLVE_COLORS) { flags |= INSPECT_DONT_RESOLVE_COLORS; }
+ if (what & TK_DUMP_INCLUDE_DATABASE_CONFIG) { flags |= INSPECT_INCLUDE_DATABASE_CONFIG; }
+ if (what & TK_DUMP_INCLUDE_SYSTEM_CONFIG) { flags |= INSPECT_INCLUDE_SYSTEM_CONFIG; }
+ if (what & TK_DUMP_INCLUDE_DEFAULT_CONFIG) { flags |= INSPECT_INCLUDE_DEFAULT_CONFIG; }
+ if (what & TK_DUMP_INCLUDE_SYSTEM_COLORS) { flags |= INSPECT_INCLUDE_SYSTEM_COLORS; }
+
+ assert(textPtr->selTagPtr->textPtr == textPtr);
+
+ if (!(what & TK_DUMP_INCLUDE_SEL)) {
+ /* this little trick is discarding the "sel" tag */
+ textPtr->selTagPtr->textPtr = (TkText *) textPtr->selTagPtr;
+ }
+
+ if (what & TK_DUMP_TEXT_CONFIGS) {
+ assert(textPtr->optionTable);
+ TkTextInspectOptions(textPtr, textPtr, textPtr->optionTable, opts, flags);
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "setup");
+ Tcl_DStringAppendElement(str, Tk_PathName(textPtr->tkwin));
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(opts));
+ Tcl_DStringEndSublist(str);
+ }
+
+ if (what & TK_DUMP_TAG_CONFIGS) {
+ TkTextTag **tags = textPtr->sharedTextPtr->tagLookup;
+ unsigned n = textPtr->sharedTextPtr->numTags;
+ unsigned i;
+
+ for (i = 0; i < n; ++i) {
+ TkTextTag *tagPtr = tags[i];
+
+ if (tagPtr && ((what & TK_DUMP_INCLUDE_SEL) || !tagPtr->isSelTag)) {
+ assert(tagPtr->optionTable);
+ TkTextInspectOptions(textPtr, tagPtr, tagPtr->optionTable, opts, flags);
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, "configure");
+ Tcl_DStringAppendElement(str, tagPtr->name);
+ if (Tcl_DStringLength(opts) > 2) {
+ Tcl_DStringAppendElement(str, Tcl_DStringValue(opts));
+ }
+ Tcl_DStringEndSublist(str);
+ }
+ }
}
- status = TkUndoApply(textPtr->sharedTextPtr->undoStack);
+ if (what & TK_DUMP_TAG_BINDINGS) {
+ TkTextTag **tags = textPtr->sharedTextPtr->tagLookup;
+ unsigned n = textPtr->sharedTextPtr->numTags;
+ unsigned i;
+
+ for (i = 0; i < n; ++i) {
+ TkTextTag *tagPtr = tags[i];
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
+ if (tagPtr
+ && sharedTextPtr->tagBindingTable
+ && ((what & TK_DUMP_INCLUDE_SEL) || !tagPtr->isSelTag)) {
+ GetBindings(textPtr, tagPtr->name, sharedTextPtr->tagBindingTable, str);
+ }
+ }
}
- textPtr->sharedTextPtr->undo = 1;
- /*
- * Convert undo/redo temporary marks set by TkUndoApply() into
- * indices left in the interp result.
- */
+ do {
+ TkTextSegment *segPtr = nextPtr;
+ unsigned group = segPtr->typePtr->group;
+ const char *value = NULL;
+ const char *type = NULL;
+ bool printTags = false;
- cmdObj = Tcl_ObjPrintf("::tk::TextUndoRedoProcessMarks %s",
- Tk_PathName(textPtr->tkwin));
- Tcl_IncrRefCount(cmdObj);
- code = Tcl_EvalObjEx(textPtr->interp, cmdObj, TCL_EVAL_GLOBAL);
- if (code != TCL_OK) {
- Tcl_AddErrorInfo(textPtr->interp,
- "\n (on undoing)");
- Tcl_BackgroundException(textPtr->interp, code);
+ nextPtr = segPtr->nextPtr;
+
+ switch (group) {
+ case SEG_GROUP_BRANCH:
+ if (segPtr->typePtr == &tkTextBranchType && (what & TK_DUMP_DISPLAY)) {
+ segPtr = segPtr->body.branch.nextPtr;
+ nextPtr = segPtr->nextPtr;
+ }
+ if (!(what & SEG_GROUP_BRANCH)) {
+ continue;
+ }
+ type = "elide";
+ value = (segPtr->typePtr == &tkTextBranchType) ? "on" : "off";
+ break;
+ case SEG_GROUP_IMAGE:
+ if (!(what & SEG_GROUP_IMAGE) || !segPtr->body.ei.name) {
+ continue;
+ }
+ type = "image";
+ assert(segPtr->body.ei.optionTable);
+ TkTextInspectOptions(textPtr, &segPtr->body.ei, segPtr->body.ei.optionTable, opts, 0);
+ value = Tcl_DStringValue(opts);
+ printTags = !!(what & TK_DUMP_TAG);
+ break;
+ case SEG_GROUP_WINDOW:
+ if (!(what & SEG_GROUP_WINDOW)) {
+ continue;
+ }
+ type = "window";
+ assert(segPtr->body.ew.optionTable);
+ TkTextInspectOptions(textPtr, &segPtr->body.ew, segPtr->body.ew.optionTable, opts, 0);
+ value = Tcl_DStringValue(opts);
+ printTags = !!(what & TK_DUMP_TAG);
+ break;
+ case SEG_GROUP_MARK:
+ if (segPtr == textPtr->endMarker) {
+ if (prevPtr != segPtr
+ && (what & SEG_GROUP_CHAR)
+ && segPtr->sectionPtr->linePtr != TkBTreeGetLastLine(textPtr)) {
+ /* print newline before finishing */
+ type = "break";
+ printTags = !!(what & TK_DUMP_TAG);
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr->sectionPtr->linePtr->lastPtr,
+ textPtr, TK_TEXT_SORT_ASCENDING, NULL);
+ nextPtr = segPtr; /* repeat this mark */
+ } else {
+ nextPtr = NULL; /* finished */
+ }
+ } else if (!(what & SEG_GROUP_MARK)) {
+ continue;
+ } else if (!TkTextIsNormalMark(segPtr)
+ && (!(what & TK_DUMP_INSERT_MARK) || segPtr != textPtr->insertMarkPtr)) {
+ continue;
+ } else {
+ type = (segPtr->typePtr == &tkTextLeftMarkType ? "left" : "right");
+ value = TkTextMarkName(sharedTextPtr, textPtr, segPtr);
+ }
+ break;
+ case SEG_GROUP_HYPHEN:
+ if (!(what & SEG_GROUP_HYPHEN)) {
+ continue;
+ }
+ printTags = !!(what & TK_DUMP_TAG);
+ type = "hyphen";
+ break;
+ case SEG_GROUP_CHAR:
+ if (what & SEG_GROUP_CHAR) {
+ printTags = !!(what & TK_DUMP_TAG);
+ if (prevPtr == segPtr || *segPtr->body.chars == '\n') {
+ type = "break";
+ nextPtr = segPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ if (prevPtr == segPtr) {
+ tagPtr = prevTagPtr;
+ segPtr->body.chars[segPtr->size - 1] = '\n';
+ } else if (type && printTags) {
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr,
+ TK_TEXT_SORT_ASCENDING, NULL);
+ }
+ } else {
+ type = "text";
+ if (segPtr->size > 1 && segPtr->body.chars[segPtr->size - 1] == '\n') {
+ nextPtr = segPtr; /* repeat this char segment */
+ segPtr->body.chars[segPtr->size - 1] = '\0';
+ }
+ value = segPtr->body.chars;
+ if (printTags) {
+ tagPtr = TkBTreeGetSegmentTags(sharedTextPtr, segPtr, textPtr,
+ TK_TEXT_SORT_ASCENDING, NULL);
+ }
+ }
+ } else if (!nextPtr) {
+ nextPtr = segPtr->sectionPtr->linePtr->nextPtr->segPtr;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ if (closeSubList) {
+ if (what & TK_DUMP_NESTED) {
+ unsigned nextEpoch = epoch + 1;
+ unsigned numTags = 0;
+ unsigned i;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag == epoch) {
+ tPtr->flag = nextEpoch; /* mark as still open */
+ }
+ }
+
+ for ( ; prevTagPtr; prevTagPtr = prevTagPtr->succPtr) {
+ if (prevTagPtr->flag == epoch) { /* should be closed? */
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = prevTagPtr;
+ prevTagPtr->flag = 0; /* mark as closed */
+ }
+ }
+
+ Tcl_DStringStartSublist(str);
+ for (i = 0; i < numTags; ++i) {
+ Tcl_DStringAppendElement(str, tagArray[i]->name);
+ }
+ Tcl_DStringEndSublist(str);
+ }
+
+ prevTagPtr = NULL;
+ closeSubList = false;
+ Tcl_DStringEndSublist(str);
+ }
+
+ if (type) {
+ Tcl_DStringStartSublist(str);
+ Tcl_DStringAppendElement(str, type);
+ if (value) {
+ Tcl_DStringAppendElement(str, value);
+ }
+ closeSubList = true;
+
+ if (printTags) {
+ unsigned numTags = 0;
+ unsigned i;
+
+ prevTagPtr = tagPtr;
+
+ if (what & TK_DUMP_NESTED) {
+ epoch += 1;
+
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (tPtr->flag != epoch) { /* should be opened? */
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = tPtr;
+ tPtr->flag = epoch; /* mark as open */
+ }
+ tPtr->succPtr = tPtr->nextPtr;
+ }
+ } else {
+ for (tPtr = tagPtr; tPtr; tPtr = tPtr->nextPtr) {
+ if (numTags == tagArrSize) {
+ tagArrSize *= 2;
+ tagArray = realloc(tagArray, tagArrSize * sizeof(tagArray[0]));
+ }
+ tagArray[numTags++] = tPtr;
+ }
+ }
+
+ Tcl_DStringStartSublist(str);
+ for (i = 0; i < numTags; ++i) {
+ Tcl_DStringAppendElement(str, tagArray[i]->name);
+ }
+ Tcl_DStringEndSublist(str);
+ }
+ }
+
+ prevPtr = segPtr;
+ } while (nextPtr);
+
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(Tcl_DStringValue(str), Tcl_DStringLength(str)));
+ Tcl_DStringFree(str);
+ Tcl_DStringFree(opts);
+ free(tagArray);
+
+ textPtr->selTagPtr->textPtr = textPtr; /* restore */
+ sharedTextPtr->inspectEpoch = epoch;
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InspectRetainedUndoItems --
+ *
+ * Return information about content of retained undo items, these
+ * items are not yet pushed onto undo stack.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InspectRetainedUndoItems(
+ const TkSharedText *sharedTextPtr,
+ Tcl_Obj *objPtr)
+{
+ if (sharedTextPtr->undoTagListCount > 0 || sharedTextPtr->undoMarkListCount > 0) {
+ Tcl_Obj *resultPtr = Tcl_NewObj();
+ unsigned i;
+ int len;
+
+ for (i = 0; i < sharedTextPtr->undoTagListCount; ++i) {
+ TkTextInspectUndoTagItem(sharedTextPtr, sharedTextPtr->undoTagList[i], resultPtr);
+ }
+
+ for (i = 0; i < sharedTextPtr->undoMarkListCount; ++i) {
+ TkTextInspectUndoMarkItem(sharedTextPtr, &sharedTextPtr->undoMarkList[i], resultPtr);
+ }
+
+ Tcl_ListObjLength(NULL, resultPtr, &len);
+ if (len == 0) {
+ Tcl_DecrRefCount(resultPtr);
+ } else {
+ Tcl_ListObjAppendElement(NULL, objPtr, resultPtr);
+ }
}
- Tcl_DecrRefCount(cmdObj);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * InspectUndoStack --
+ *
+ * Return information about content of undo/redo stack.
+ *
+ * Results:
+ * A Tcl object.
+ *
+ * Side effects:
+ * Memory is allocated for the result.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+InspectUndoStack(
+ const TkSharedText *sharedTextPtr,
+ InspectUndoStackProc firstAtomProc,
+ InspectUndoStackProc nextAtomProc,
+ Tcl_Obj *objPtr)
+{
+ TkTextUndoStack undoStack;
+ const TkTextUndoAtom *atom;
+ Tcl_Obj *atomPtr;
+ unsigned i;
+
+ assert(sharedTextPtr->undoStack);
+
+ undoStack = sharedTextPtr->undoStack;
+
+ for (atom = firstAtomProc(undoStack); atom; atom = nextAtomProc(undoStack)) {
+ atomPtr = Tcl_NewObj();
+
+ for (i = 0; i < atom->arraySize; ++i) {
+ const TkTextUndoToken *token = (const TkTextUndoToken *) atom->array[i].item;
+ Tcl_Obj *subAtomPtr = token->undoType->inspectProc(sharedTextPtr, token);
+ Tcl_ListObjAppendElement(NULL, atomPtr, subAtomPtr);
+ }
- return status;
+ Tcl_ListObjAppendElement(NULL, objPtr, atomPtr);
+ }
}
/*
@@ -5242,6 +8964,15 @@ TextEditRedo(
*----------------------------------------------------------------------
*/
+static Tcl_Obj *
+GetCommand(
+ const TkSharedText *sharedTextPtr,
+ const TkTextUndoToken *token)
+{
+ assert(token->undoType->commandProc);
+ return token->undoType->commandProc(sharedTextPtr, token);
+}
+
static int
TextEditCmd(
TkText *textPtr, /* Information about text widget. */
@@ -5249,75 +8980,168 @@ TextEditCmd(
int objc, /* Number of arguments. */
Tcl_Obj *const objv[]) /* Argument objects. */
{
- int index, setModified, oldModified;
- int canRedo = 0;
- int canUndo = 0;
-
+ int index;
+ int setModified;
+ bool oldModified;
+ TkSharedText *sharedTextPtr;
static const char *const editOptionStrings[] = {
- "canundo", "canredo", "modified", "redo", "reset", "separator",
- "undo", NULL
+ "altered",
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ "canredo", "canundo",
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ "info", "inspect", "irreversible", "modified", "recover", "redo", "reset",
+ "separator", "undo", NULL
};
enum editOptions {
- EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
+ EDIT_ALTERED,
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ EDIT_CANREDO, EDIT_CANUNDO,
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ EDIT_INFO, EDIT_INSPECT, EDIT_IRREVERSIBLE, EDIT_MODIFIED, EDIT_RECOVER, EDIT_REDO, EDIT_RESET,
EDIT_SEPARATOR, EDIT_UNDO
};
+ sharedTextPtr = textPtr->sharedTextPtr;
+
if (objc < 3) {
Tcl_WrongNumArgs(interp, 2, objv, "option ?arg ...?");
return TCL_ERROR;
}
-
if (Tcl_GetIndexFromObjStruct(interp, objv[2], editOptionStrings,
sizeof(char *), "edit option", 0, &index) != TCL_OK) {
return TCL_ERROR;
}
switch ((enum editOptions) index) {
- case EDIT_CANREDO:
+ case EDIT_ALTERED:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isAltered));
+ return TCL_OK;
+ break;
+#if SUPPORT_DEPRECATED_CANUNDO_REDO
+ case EDIT_CANREDO: {
+ static bool warnDeprecated = true;
+ bool canRedo = false;
+
+ if (warnDeprecated) {
+ warnDeprecated = false;
+ fprintf(stderr, "tk::text: Command \"edit canredo\" is deprecated, "
+ "please use \"edit info\".\n");
+ }
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- if (textPtr->sharedTextPtr->undo) {
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
+ if (textPtr->sharedTextPtr->undoStack) {
+ canRedo = TkTextUndoGetCurrentRedoStackDepth(textPtr->sharedTextPtr->undoStack) > 0;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canRedo));
break;
- case EDIT_CANUNDO:
+ }
+ case EDIT_CANUNDO: {
+ static bool warnDeprecated = true;
+ bool canUndo = false;
+
+ if (warnDeprecated) {
+ warnDeprecated = false;
+ fprintf(stderr, "tk::text: Command \"edit canundo\" is deprecated, "
+ "please use \"edit info\".\n");
+ }
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
if (textPtr->sharedTextPtr->undo) {
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
+ canUndo = TkTextUndoGetCurrentUndoStackDepth(textPtr->sharedTextPtr->undoStack) > 0;
}
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canUndo));
break;
+ }
+#endif /* SUPPORT_DEPRECATED_CANUNDO_REDO */
+ case EDIT_INFO:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?array?");
+ return TCL_ERROR;
+ } else {
+ Tcl_SetObjResult(textPtr->interp, MakeEditInfo(interp, textPtr, objc == 4 ? objv[3] : NULL));
+ }
+ break;
+ case EDIT_INSPECT:
+ if (objc != 3 && objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?stack?");
+ return TCL_ERROR;
+ } else {
+ char const *stack = (objc == 4) ? Tcl_GetString(objv[3]) : NULL;
+
+ if (stack && strcmp(stack, "undo") != 0 && strcmp(stack, "redo") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad stack argument \"%s\": must be \"undo\" or \"redo\"", stack));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "STACK_VALUE", NULL);
+ return TCL_ERROR;
+ }
+ if (sharedTextPtr->undoStack) {
+ Tcl_Obj *undoResultPtr = NULL;
+ Tcl_Obj *redoResultPtr = NULL;
+
+ if (!stack || stack[0] == 'u') {
+ undoResultPtr = Tcl_NewObj();
+ InspectRetainedUndoItems(sharedTextPtr, undoResultPtr);
+ InspectUndoStack(sharedTextPtr, TkTextUndoFirstUndoAtom,
+ TkTextUndoNextUndoAtom, undoResultPtr);
+ }
+ if (!stack || stack[0] == 'r') {
+ redoResultPtr = Tcl_NewObj();
+ InspectUndoStack(sharedTextPtr, TkTextUndoFirstRedoAtom,
+ TkTextUndoNextRedoAtom, redoResultPtr);
+ }
+ if (!stack) {
+ Tcl_Obj *objPtr = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, objPtr, undoResultPtr);
+ Tcl_ListObjAppendElement(NULL, objPtr, redoResultPtr);
+ Tcl_SetObjResult(interp, objPtr);
+ } else if (stack[0] == 'u') {
+ Tcl_SetObjResult(interp, undoResultPtr);
+ } else {
+ Tcl_SetObjResult(interp, redoResultPtr);
+ }
+ }
+ }
+ break;
+ case EDIT_IRREVERSIBLE:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
+ return TCL_ERROR;
+ }
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isIrreversible));
+ break;
case EDIT_MODIFIED:
if (objc == 3) {
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty));
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(sharedTextPtr->isModified));
return TCL_OK;
} else if (objc != 4) {
Tcl_WrongNumArgs(interp, 3, objv, "?boolean?");
return TCL_ERROR;
- } else if (Tcl_GetBooleanFromObj(interp, objv[3],
- &setModified) != TCL_OK) {
+ } else 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 modified status, and trigger a <<Modified>> event.
*/
- setModified = setModified ? 1 : 0;
+ oldModified = sharedTextPtr->isModified;
+ sharedTextPtr->isModified = setModified;
- oldModified = textPtr->sharedTextPtr->isDirty;
- textPtr->sharedTextPtr->isDirty = setModified;
- if (setModified) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED;
- } else {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
+ /*
+ * Setting the flag to 'false' is clearing the user's decision.
+ */
+
+ sharedTextPtr->userHasSetModifiedFlag = setModified;
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->undoLevel = TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack);
}
/*
@@ -5325,59 +9149,170 @@ TextEditCmd(
* However, degree of modified-ness doesn't matter. [Bug 1799782]
*/
- if ((!oldModified) != (!setModified)) {
- GenerateModifiedEvent(textPtr);
+ assert(setModified == true || setModified == false);
+
+ if (oldModified != setModified) {
+ GenerateEvent(textPtr->sharedTextPtr, "Modified");
}
break;
- case EDIT_REDO:
+ case EDIT_RECOVER:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- if (TextEditRedo(textPtr)) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
+ if (sharedTextPtr->undoStack) {
+ int redoDepth;
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot recover inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ redoDepth = TkTextUndoGetMaxRedoDepth(sharedTextPtr->undoStack);
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack, textPtr->maxUndoDepth, 0);
+
+ while (TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) > 0) {
+ TkTextUndoDoUndo(sharedTextPtr->undoStack);
+ }
+
+ TkTextUndoSetMaxStackDepth(sharedTextPtr->undoStack, textPtr->maxUndoDepth, redoDepth);
+ }
+ break;
+ case EDIT_REDO:
+ if (objc != 3) {
+ Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- if (!canUndo || !canRedo) {
- GenerateUndoStackEvent(textPtr);
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot redo inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ if (TkTextUndoGetCurrentRedoStackDepth(sharedTextPtr->undoStack) == 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to redo", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_REDO", NULL);
+ return TCL_ERROR;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+ TkTextUndoDoRedo(sharedTextPtr->undoStack);
}
break;
case EDIT_RESET:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
+ if (objc == 3) {
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot reset stack inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ TkTextUndoClearStack(sharedTextPtr->undoStack);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->pushSeparator = false;
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isIrreversible = false;
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ }
+ return TCL_OK;
+ } else if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 3, objv, "?stack?");
+ return TCL_ERROR;
+ } else {
+ char const *stack = Tcl_GetString(objv[3]);
+
+ if (strcmp(stack, "undo") != 0 && strcmp(stack, "redo") != 0) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad stack argument \"%s\": must be \"undo\" or \"redo\"", stack));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "STACK_VALUE", NULL);
+ return TCL_ERROR;
+ }
+ if (sharedTextPtr->undoStack) {
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ /*
+ * It's possible that this command command will be invoked inside
+ * the "watch" callback, but this is not allowed when performing
+ * undo/redo.
+ */
+
+ ErrorNotAllowed(interp, "cannot reset stack inside undo/redo operation");
+ return TCL_ERROR;
+ }
+ if (stack[0] == 'u') {
+ TkTextUndoClearUndoStack(sharedTextPtr->undoStack);
+ sharedTextPtr->undoLevel = 0;
+ sharedTextPtr->pushSeparator = false;
+ sharedTextPtr->isAltered = false;
+ sharedTextPtr->isIrreversible = false;
+ TkTextUpdateAlteredFlag(sharedTextPtr);
+ } else {
+ TkTextUndoClearRedoStack(sharedTextPtr->undoStack);
+ }
+ }
return TCL_ERROR;
- }
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- TkUndoClearStacks(textPtr->sharedTextPtr->undoStack);
- if (canUndo || canRedo) {
- GenerateUndoStackEvent(textPtr);
}
break;
- case EDIT_SEPARATOR:
- if (objc != 3) {
+ case EDIT_SEPARATOR: {
+ bool immediately = false;
+
+ if (objc == 4) {
+ if (strcmp(Tcl_GetString(objv[3]), "-immediately")) {
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+ "bad option \"%s\": must be -immediately", Tcl_GetString(objv[3])));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
+ return TCL_ERROR;
+ }
+ immediately = true;
+ } else if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
+ if (sharedTextPtr->undoStack) {
+ sharedTextPtr->pushSeparator = true;
+ if (immediately) {
+ /* last two args are meaningless here */
+ PushUndoSeparatorIfNeeded(sharedTextPtr, sharedTextPtr->autoSeparators,
+ TK_TEXT_EDIT_OTHER);
+ }
+ }
break;
+ }
case EDIT_UNDO:
if (objc != 3) {
Tcl_WrongNumArgs(interp, 3, objv, NULL);
return TCL_ERROR;
}
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- if (TextEditUndo(textPtr)) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
- return TCL_ERROR;
- }
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- if (!canRedo || !canUndo) {
- GenerateUndoStackEvent(textPtr);
+ if (sharedTextPtr->undoStack) {
+ /*
+ * It's possible that this command command will be invoked inside the "watch" callback,
+ * but this is not allowed when performing undo/redo.
+ */
+
+ if (TkTextUndoIsPerformingUndoRedo(sharedTextPtr->undoStack)) {
+ ErrorNotAllowed(interp, "cannot undo inside undo/redo operation");
+ return TCL_ERROR;
+ }
+
+ PushRetainedUndoTokens(sharedTextPtr);
+
+ if (TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) == 0) {
+ Tcl_SetObjResult(interp, Tcl_NewStringObj("nothing to undo", -1));
+ Tcl_SetErrorCode(interp, "TK", "TEXT", "NO_UNDO", NULL);
+ return TCL_ERROR;
+ }
+
+ TkTextUndoDoUndo(sharedTextPtr->undoStack);
}
break;
}
@@ -5387,6 +9322,162 @@ TextEditCmd(
/*
*----------------------------------------------------------------------
*
+ * MakeEditInfo --
+ *
+ * Returns the array containing the "edit info" information.
+ *
+ * Results:
+ * Tcl_Obj of list type containing the required information.
+ *
+ * Side effects:
+ * Some memory will be allocated:
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+MakeEditInfo(
+ Tcl_Interp *interp, /* Current interpreter. */
+ TkText *textPtr, /* Information about text widget. */
+ Tcl_Obj *arrayPtr) /* Name of array, may be NULL. */
+{
+ enum {
+ INFO_UNDOSTACKSIZE, INFO_REDOSTACKSIZE, INFO_UNDODEPTH, INFO_REDODEPTH,
+ INFO_UNDOBYTESIZE, INFO_REDOBYTESIZE, INFO_UNDOCOMMANDS, INFO_REDOCOMMANDS,
+ INFO_BYTESIZE, INFO_TOTALBYTESIZE, INFO_LINES, INFO_TOTALLINES, INFO_IMAGES,
+ INFO_WINDOWS, INFO_DISPIMAGES, INFO_DISPWINDOWS, INFO_TAGS, INFO_USEDTAGS,
+ INFO_MARKS, INFO_GENERATEDMARKS, INFO_LINESPERNODE,
+ INFO_LAST /* must be last item */
+ };
+
+ TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ TkTextUndoStack st = sharedTextPtr->undoStack;
+ Tcl_Obj *var = arrayPtr ? arrayPtr : Tcl_NewStringObj("", 0);
+ Tcl_Obj *name[INFO_LAST];
+ Tcl_Obj *value[INFO_LAST];
+ int usedTags, i;
+ unsigned k;
+
+ name[INFO_UNDOSTACKSIZE ] = Tcl_NewStringObj("undostacksize", -1);
+ name[INFO_REDOSTACKSIZE ] = Tcl_NewStringObj("redostacksize", -1);
+ name[INFO_UNDODEPTH ] = Tcl_NewStringObj("undodepth", -1);
+ name[INFO_REDODEPTH ] = Tcl_NewStringObj("redodepth", -1);
+ name[INFO_UNDOBYTESIZE ] = Tcl_NewStringObj("undobytesize", -1);
+ name[INFO_REDOBYTESIZE ] = Tcl_NewStringObj("redobytesize", -1);
+ name[INFO_UNDOCOMMANDS ] = Tcl_NewStringObj("undocommands", -1);
+ name[INFO_REDOCOMMANDS ] = Tcl_NewStringObj("redocommands", -1);
+ name[INFO_BYTESIZE ] = Tcl_NewStringObj("bytesize", -1);
+ name[INFO_TOTALBYTESIZE ] = Tcl_NewStringObj("totalbytesize", -1);
+ name[INFO_LINES ] = Tcl_NewStringObj("lines", -1);
+ name[INFO_TOTALLINES ] = Tcl_NewStringObj("totallines", -1);
+ name[INFO_IMAGES ] = Tcl_NewStringObj("images", -1);
+ name[INFO_WINDOWS ] = Tcl_NewStringObj("windows", -1);
+ name[INFO_DISPIMAGES ] = Tcl_NewStringObj("visibleimages", -1);
+ name[INFO_DISPWINDOWS ] = Tcl_NewStringObj("visiblewindows", -1);
+ name[INFO_TAGS ] = Tcl_NewStringObj("tags", -1);
+ name[INFO_USEDTAGS ] = Tcl_NewStringObj("usedtags", -1);
+ name[INFO_MARKS ] = Tcl_NewStringObj("marks", -1);
+ name[INFO_GENERATEDMARKS] = Tcl_NewStringObj("generatedmarks", -1);
+ name[INFO_LINESPERNODE ] = Tcl_NewStringObj("linespernode", -1);
+
+ if (st) {
+ const TkTextUndoAtom *atom;
+ Tcl_Obj *listPtr;
+
+ value[INFO_UNDOSTACKSIZE] = Tcl_NewIntObj(TkTextUndoCountUndoItems(st));
+ value[INFO_REDOSTACKSIZE] = Tcl_NewIntObj(TkTextUndoCountRedoItems(st));
+ value[INFO_UNDODEPTH ] = Tcl_NewIntObj(TkTextUndoGetCurrentUndoStackDepth(st));
+ value[INFO_REDODEPTH ] = Tcl_NewIntObj(TkTextUndoGetCurrentRedoStackDepth(st));
+ value[INFO_UNDOBYTESIZE ] = Tcl_NewIntObj(TkTextUndoGetCurrentUndoSize(st));
+ value[INFO_REDOBYTESIZE ] = Tcl_NewIntObj(TkTextUndoGetCurrentRedoSize(st));
+ value[INFO_UNDOCOMMANDS ] = Tcl_NewObj();
+ value[INFO_REDOCOMMANDS ] = Tcl_NewObj();
+
+ listPtr = value[TkTextUndoIsPerformingUndo(st) ? INFO_REDOCOMMANDS : INFO_UNDOCOMMANDS];
+
+ for (i = sharedTextPtr->undoTagListCount - 1; i >= 0; --i) {
+ const TkTextTag *tagPtr = sharedTextPtr->undoTagList[i];
+
+ if (tagPtr->recentTagAddRemoveToken && !tagPtr->recentTagAddRemoveTokenIsNull) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, tagPtr->recentTagAddRemoveToken));
+ }
+ if (tagPtr->recentChangePriorityToken && tagPtr->savedPriority != tagPtr->priority) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, tagPtr->recentTagAddRemoveToken));
+ }
+ }
+
+ for (i = sharedTextPtr->undoMarkListCount - 1; i >= 0; --i) {
+ const TkTextMarkChange *changePtr = &sharedTextPtr->undoMarkList[i];
+
+ if (changePtr->setMark) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->setMark));
+ }
+ if (changePtr->moveMark) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->moveMark));
+ }
+ if (changePtr->toggleGravity) {
+ Tcl_ListObjAppendElement(interp, listPtr,
+ GetCommand(sharedTextPtr, changePtr->toggleGravity));
+ }
+ }
+
+ atom = TkTextUndoIsPerformingUndo(st) ?
+ TkTextUndoCurrentRedoAtom(st) : TkTextUndoCurrentUndoAtom(st);
+
+ if (atom) {
+ for (i = atom->arraySize - 1; i >= 0; --i) {
+ const TkTextUndoSubAtom *subAtom = atom->array + i;
+ TkTextUndoToken *token = subAtom->item;
+
+ Tcl_ListObjAppendElement(interp, listPtr, GetCommand(sharedTextPtr, token));
+ }
+ }
+ } else {
+ value[INFO_UNDOSTACKSIZE] =
+ value[INFO_REDOSTACKSIZE] =
+ value[INFO_UNDODEPTH] =
+ value[INFO_REDODEPTH] =
+ value[INFO_UNDOBYTESIZE] =
+ value[INFO_REDOBYTESIZE] = Tcl_NewIntObj(0);
+ value[INFO_UNDOCOMMANDS] = value[INFO_REDOCOMMANDS] = Tcl_NewObj();
+ }
+
+ usedTags = TkTextTagSetCount(TkBTreeRootTagInfo(sharedTextPtr->tree));
+
+ /* Related to this widget */
+
+ value[INFO_BYTESIZE ] = Tcl_NewIntObj(TkBTreeSize(sharedTextPtr->tree, textPtr));
+ value[INFO_LINES ] = Tcl_NewIntObj(TkBTreeNumLines(sharedTextPtr->tree, textPtr));
+ value[INFO_DISPIMAGES ] = Tcl_NewIntObj(TkTextCountVisibleImages(textPtr));
+ value[INFO_DISPWINDOWS ] = Tcl_NewIntObj(TkTextCountVisibleWindows(textPtr));
+
+ /* Related to shared resource */
+
+ value[INFO_TOTALBYTESIZE ] = Tcl_NewIntObj(TkBTreeSize(sharedTextPtr->tree, NULL));
+ value[INFO_TOTALLINES ] = Tcl_NewIntObj(TkBTreeNumLines(sharedTextPtr->tree, NULL));
+ value[INFO_IMAGES ] = Tcl_NewIntObj(sharedTextPtr->numImages);
+ value[INFO_WINDOWS ] = Tcl_NewIntObj(sharedTextPtr->numWindows);
+ value[INFO_TAGS ] = Tcl_NewIntObj(sharedTextPtr->numTags);
+ value[INFO_USEDTAGS ] = Tcl_NewIntObj(usedTags);
+ value[INFO_MARKS ] = Tcl_NewIntObj(sharedTextPtr->numMarks);
+ value[INFO_GENERATEDMARKS] = Tcl_NewIntObj(sharedTextPtr->numPrivateMarks);
+ value[INFO_LINESPERNODE ] = Tcl_NewIntObj(TkBTreeLinesPerNode(sharedTextPtr->tree));
+
+ Tcl_UnsetVar(interp, Tcl_GetString(var), 0);
+ for (k = 0; k < sizeof(name)/sizeof(name[0]); ++k) {
+ Tcl_ObjSetVar2(interp, var, name[k], value[k], 0);
+ }
+
+ return var;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TextGetText --
*
* Returns the text from indexPtr1 to indexPtr2, placing that text in a
@@ -5406,8 +9497,8 @@ TextEditCmd(
*
* Results:
* 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
+ * visibleOnly flag is set to true, then only those characters which are not
+ * elided will be returned. Otherwise (flag is false) all characters in the
* given range are returned.
*
* Side effects:
@@ -5419,61 +9510,455 @@ TextEditCmd(
static Tcl_Obj *
TextGetText(
- const TkText *textPtr, /* Information about text widget. */
+ 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 *lastIndexPtr, /* Position before last character of the result, can be NULL. */
+ Tcl_Obj *resultPtr, /* Append text to this object, can be NULL. */
+ unsigned maxBytes, /* Maximal number of bytes. */
+ bool visibleOnly, /* If true, then only return non-elided characters. */
+ bool includeHyphens) /* If true, then also include soft hyphens. */
{
- TkTextIndex tmpIndex;
- Tcl_Obj *resultPtr = Tcl_NewObj();
+ TkTextSegment *segPtr, *lastPtr;
+ TkTextIndex index;
+ int offset1, offset2;
- TkTextMakeByteIndex(indexPtr1->tree, textPtr,
- TkBTreeLinesTo(textPtr, indexPtr1->linePtr),
- indexPtr1->byteIndex, &tmpIndex);
+ assert(textPtr);
+ assert(TkTextIndexCompare(indexPtr1, indexPtr2) <= 0);
- if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) {
- while (1) {
- int offset;
- TkTextSegment *segPtr = TkTextIndexToSeg(&tmpIndex, &offset);
- int last = segPtr->size, last2;
+ if (!resultPtr) {
+ resultPtr = Tcl_NewObj();
+ }
- if (tmpIndex.linePtr == indexPtr2->linePtr) {
- /*
- * 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.
- */
+ segPtr = TkTextIndexGetContentSegment(indexPtr1, &offset1);
+ if (lastIndexPtr) {
+ *lastIndexPtr = *indexPtr2;
+ }
- if (indexPtr2->byteIndex == tmpIndex.byteIndex) {
- break;
+ if (visibleOnly && TkTextSegmentIsElided(textPtr, segPtr)) {
+ index = *indexPtr1;
+ if (!TkTextSkipElidedRegion(&index) || TkTextIndexCompare(&index, indexPtr2) >= 0) {
+ return resultPtr; /* end of text reached */
+ }
+ segPtr = TkTextIndexGetContentSegment(&index, &offset1);
+ }
+
+ lastPtr = TkTextIndexGetContentSegment(indexPtr2, &offset2);
+
+ if (segPtr == lastPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset1,
+ MIN(maxBytes, (unsigned) (offset2 - offset1)));
+ }
+ } else {
+ TkTextLine *linePtr = segPtr->sectionPtr->linePtr;
+
+ TkTextIndexClear(&index, textPtr);
+
+ if (segPtr->typePtr == &tkTextCharType) {
+ unsigned nbytes = MIN(maxBytes, (unsigned) segPtr->size - offset1);
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset1, nbytes);
+ if ((maxBytes -= nbytes) == 0) {
+ return resultPtr;
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (includeHyphens) {
+ if (maxBytes < 2) {
+ return resultPtr;
+ }
+ Tcl_AppendToObj(resultPtr, "\xc2\xad", 2); /* U+00AD */
+ if ((maxBytes -= 2u) == 0) {
+ return resultPtr;
+ }
+ }
+ } else if (segPtr->typePtr == &tkTextBranchType) {
+ if (visibleOnly) {
+ TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexRestrictToEndRange(&index) >= 0) {
+ return resultPtr; /* end of text reached */
}
- last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset;
- if (last2 < last) {
- last = last2;
+ linePtr = segPtr->sectionPtr->linePtr;
+ }
+ }
+ if (!(segPtr = segPtr->nextPtr)) {
+ assert(linePtr->nextPtr);
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
+ }
+ while (segPtr != lastPtr) {
+ if (segPtr->typePtr == &tkTextCharType) {
+ unsigned nbytes = MIN(maxBytes, (unsigned) segPtr->size);
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars, nbytes);
+ if ((maxBytes -= nbytes) == 0) {
+ if (lastIndexPtr) {
+ TkTextIndexSetSegment(lastIndexPtr, segPtr);
+ TkTextIndexAddToByteIndex(lastIndexPtr, nbytes);
+ }
+ return resultPtr; /* end of text reached */
+ }
+ } else if (segPtr->typePtr == &tkTextHyphenType) {
+ if (includeHyphens) {
+ if (maxBytes < 2) {
+ return resultPtr;
+ }
+ Tcl_AppendToObj(resultPtr, "\xc2\xad", 2); /* U+00AD */
+ if ((maxBytes -= 2) == 0) {
+ return resultPtr;
+ }
+ }
+ } else if (segPtr->typePtr == &tkTextBranchType) {
+ if (visibleOnly) {
+ TkTextIndexSetSegment(&index, segPtr = segPtr->body.branch.nextPtr);
+ if (TkTextIndexRestrictToEndRange(&index) >= 0) {
+ return resultPtr; /* end of text reached */
+ }
+ linePtr = segPtr->sectionPtr->linePtr;
}
}
- if (segPtr->typePtr == &tkTextCharType &&
- !(visibleOnly && TkTextIsElided(textPtr,&tmpIndex,NULL))){
- Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
- last - offset);
+ if (!(segPtr = segPtr->nextPtr)) {
+ assert(linePtr->nextPtr);
+ linePtr = linePtr->nextPtr;
+ segPtr = linePtr->segPtr;
}
- TkTextIndexForwBytes(textPtr, &tmpIndex, last-offset, &tmpIndex);
+ }
+ if (offset2 > 0) {
+ Tcl_AppendToObj(resultPtr, segPtr->body.chars, MIN(maxBytes, (unsigned) offset2));
}
}
+
return resultPtr;
}
/*
*----------------------------------------------------------------------
*
- * GenerateModifiedEvent --
+ * TriggerWatchEdit --
+ *
+ * Trigger the watch command for delete/insert operations, see the
+ * documentation for details on what it does.
+ *
+ * Results:
+ * Returns 'false' if the referenced widget has been destroyed, otherwise
+ * 'true' will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+AppendTags(
+ Tcl_DString *buf,
+ TkTextTag *tagPtr)
+{
+ Tcl_DStringStartSublist(buf);
+ for ( ; tagPtr; tagPtr = tagPtr->nextPtr) {
+ Tcl_DStringAppendElement(buf, tagPtr->name);
+ }
+ Tcl_DStringEndSublist(buf);
+}
+
+static bool
+TriggerWatchEdit(
+ TkText *textPtr, /* Information about text widget. */
+ bool userFlag, /* Trigger due to user modification? */
+ const char *operation, /* The triggering operation. */
+ const TkTextIndex *indexPtr1, /* Start index for deletion / insert. */
+ const TkTextIndex *indexPtr2, /* End index after insert / before deletion. */
+ const char *string, /* Deleted/inserted chars. */
+ bool final) /* Flag indicating whether this is a final part. */
+{
+ TkSharedText *sharedTextPtr;
+ TkText *peerArr[20];
+ TkText **peers = peerArr;
+ TkText *tPtr;
+ unsigned i, n = 0;
+ unsigned numPeers;
+ bool rc = true;
+
+ assert(textPtr->sharedTextPtr->triggerWatchCmd);
+ assert(!indexPtr1 == !indexPtr2);
+ assert(strcmp(operation, "insert") == 0 || strcmp(operation, "delete") == 0);
+
+ sharedTextPtr = textPtr->sharedTextPtr;
+ sharedTextPtr->triggerWatchCmd = false; /* do not trigger recursively */
+ numPeers = sharedTextPtr->numPeers;
+
+ if (sharedTextPtr->numPeers > sizeof(peerArr) / sizeof(peerArr[0])) {
+ peers = malloc(sharedTextPtr->numPeers * sizeof(peerArr[0]));
+ }
+
+ /*
+ * Firstly save all peers, we have to take into account that the list of
+ * peers is changing when executing the "watch" command.
+ */
+
+ peers[n++] = textPtr;
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr != textPtr) {
+ peers[n++] = tPtr;
+ }
+ tPtr->refCount += 1;
+ }
+
+ for (i = 0; i < sharedTextPtr->numPeers; ++i) {
+ TkText *tPtr = peers[i];
+
+ if (tPtr->watchCmd && (userFlag || tPtr->triggerAlways) && !(tPtr->flags & DESTROYED)) {
+ TkTextIndex index[4];
+
+ if (indexPtr1) {
+ TkTextSegment *startMarker;
+ TkTextSegment *endMarker;
+ int cmp;
+
+ index[0] = *indexPtr1;
+ index[1] = *indexPtr2;
+
+ startMarker = tPtr->startMarker;
+ endMarker = tPtr->endMarker;
+
+ if (startMarker != sharedTextPtr->startMarker) {
+ TkTextIndex start;
+ TkTextIndexClear(&start, tPtr);
+ TkTextIndexSetSegment(&start, startMarker);
+ if (TkTextIndexCompare(&start, &index[0]) > 0) {
+ index[0] = start;
+ }
+ }
+ if (endMarker != sharedTextPtr->endMarker) {
+ TkTextIndex end;
+ TkTextIndexClear(&end, tPtr);
+ TkTextIndexSetSegment(&end, endMarker);
+ if (TkTextIndexCompare(&end, &index[1]) < 0) {
+ index[1] = end;
+ }
+ }
+
+ if ((cmp = TkTextIndexCompare(&index[0], &index[1])) <= 0) {
+ TkTextTag *tagPtr;
+ TkTextIndex myIndex;
+ Tcl_DString buf;
+ char idx[2][TK_POS_CHARS];
+ char const *arg;
+
+ TkTextPrintIndex(tPtr, &index[0], idx[0]);
+ TkTextPrintIndex(tPtr, &index[1], idx[1]);
+
+ Tcl_DStringInit(&buf);
+ Tcl_DStringAppendElement(&buf, string);
+
+ tagPtr = NULL;
+ if (TkTextIndexBackChars(tPtr, &index[0], 1, &myIndex, COUNT_CHARS)) {
+ tagPtr = TkBTreeGetTags(&myIndex, TK_TEXT_SORT_ASCENDING, NULL);
+ }
+ AppendTags(&buf, tagPtr);
+ AppendTags(&buf, TkBTreeGetTags(&index[1], TK_TEXT_SORT_ASCENDING, NULL));
+ AppendTags(&buf, cmp == 0 ? NULL :
+ TkBTreeGetTags(&index[0], TK_TEXT_SORT_ASCENDING, NULL));
+ if (*operation == 'd') {
+ tagPtr = NULL;
+ if (cmp && TkTextIndexBackChars(tPtr, &index[1], 1, &myIndex, COUNT_CHARS)) {
+ tagPtr = TkBTreeGetTags(&myIndex, TK_TEXT_SORT_ASCENDING, NULL);
+ }
+ AppendTags(&buf, tagPtr);
+ }
+ Tcl_DStringAppendElement(&buf, final ? "yes" : "no");
+ arg = Tcl_DStringValue(&buf);
+
+ if (!TkTextTriggerWatchCmd(tPtr, operation, idx[0], idx[1],
+ arg, NULL, NULL, userFlag)
+ && tPtr == textPtr) {
+ rc = false; /* this widget has been destroyed */
+ }
+
+ Tcl_DStringFree(&buf);
+ }
+ } else {
+ if (!TkTextTriggerWatchCmd(textPtr, operation, NULL, NULL, NULL, NULL, NULL, userFlag)
+ && tPtr == textPtr) {
+ rc = false; /* this widget has been destroyed */
+ }
+ }
+ }
+
+ if (TkTextDecrRefCountAndTestIfDestroyed(tPtr)) {
+ numPeers -= 1;
+ }
+ }
+
+ if (peers != peerArr) {
+ free(peers);
+ }
+ if (numPeers > 0) { /* otherwise sharedTextPtr is not valid anymore */
+ sharedTextPtr->triggerWatchCmd = true;
+ }
+
+ return rc;
+}
+
+/*
+ *--------------------------------------------------------------
+ *
+ * TkTextPerformWatchCmd --
+ *
+ * This function is performs triggering of the watch
+ * command for all peers.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * See function TkTextTriggerWatchCmd.
+ *
+ *--------------------------------------------------------------
+ */
+
+void
+TkTextPerformWatchCmd(
+ TkSharedText *sharedTextPtr,
+ TkText *textPtr, /* Firstly trigger watch command of this peer, can be NULL. */
+ const char *operation, /* The trigger operation. */
+ TkTextWatchGetIndexProc index1Proc, /* Function pointer for fst index, can be NULL. */
+ ClientData index1ProcData, /* Client data for index1Proc. */
+ TkTextWatchGetIndexProc index2Proc, /* Function pointer for snd index, can be NULL. */
+ ClientData index2ProcData, /* Client data for index2Proc. */
+ const char *arg1, /* 3rd argument for watch command, can be NULL. */
+ const char *arg2, /* 3rd argument for watch command, can be NULL. */
+ const char *arg3, /* 3rd argument for watch command, can be NULL. */
+ bool userFlag) /* 4rd argument for watch command. */
+{
+ TkText *peerArr[20];
+ TkText **peers = peerArr;
+ TkText *tPtr;
+ unsigned numPeers = 0;
+ unsigned i;
+
+ assert(sharedTextPtr);
+ assert(sharedTextPtr->triggerWatchCmd);
+ assert(operation);
+ assert(!index2Proc || index1Proc);
+
+ sharedTextPtr->triggerWatchCmd = false; /* do not trigger recursively */
+
+ if (sharedTextPtr->numPeers > sizeof(peerArr) / sizeof(peerArr[0])) {
+ peers = malloc(sharedTextPtr->numPeers * sizeof(peerArr[0]));
+ }
+ if (textPtr) {
+ peers[numPeers++] = textPtr;
+ textPtr->refCount += 1;
+ }
+ for (tPtr = sharedTextPtr->peers; tPtr; tPtr = tPtr->next) {
+ if (tPtr != textPtr && tPtr->watchCmd) {
+ peers[numPeers++] = tPtr;
+ tPtr->refCount += 1;
+ }
+ }
+ for (i = 0; i < numPeers; ++i) {
+ tPtr = peers[i];
+
+ if (!(tPtr->flags & DESTROYED)) {
+ char idx[2][TK_POS_CHARS];
+ TkTextIndex index[2];
+
+ if (index1Proc) {
+ index1Proc(tPtr, &index[0], index1ProcData);
+ TkTextPrintIndex(tPtr, &index[0], idx[0]);
+
+ if (index2Proc) {
+ index2Proc(tPtr, &index[1], index2ProcData);
+ TkTextPrintIndex(tPtr, &index[1], idx[1]);
+ } else {
+ memcpy(idx[1], idx[0], TK_POS_CHARS);
+ }
+ }
+
+ TkTextTriggerWatchCmd(tPtr, operation, idx[0], idx[1], arg1, arg2, arg3, false);
+ }
+ }
+
+ sharedTextPtr->triggerWatchCmd = true;
+
+ for (i = 0; i < numPeers; ++i) {
+ TkTextDecrRefCountAndTestIfDestroyed(peers[i]);
+ }
+ if (peers != peerArr) {
+ free(peers);
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextTriggerWatchCmd --
*
- * Send an event that the text was modified. This is equivalent to:
- * event generate $textWidget <<Modified>>
- * for all peers of $textWidget.
+ * Trigger the watch command, see the documentation for details on
+ * what it does.
+ *
+ * Results:
+ * Returns 'false' if this peer has been destroyed, otherwise 'true'
+ * will be returned.
+ *
+ * Side effects:
+ * It might happen that the receiver of the "watch" command is destroying the widget.
+ *
+ *----------------------------------------------------------------------
+ */
+
+bool
+TkTextTriggerWatchCmd(
+ TkText *textPtr, /* Information about text widget. */
+ const char *operation, /* The trigger operation. */
+ const char *index1, /* 1st argument for watch command, can be NULL. */
+ const char *index2, /* 2nd argument for watch command, can be NULL. */
+ const char *arg1, /* 3rd argument for watch command, can be NULL. */
+ const char *arg2, /* 3rd argument for watch command, can be NULL. */
+ const char *arg3, /* 3rd argument for watch command, can be NULL. */
+ bool userFlag) /* 4rd argument for watch command. */
+{
+ Tcl_DString cmd;
+
+ assert(textPtr);
+ assert(textPtr->watchCmd);
+ assert(operation);
+
+ Tcl_DStringInit(&cmd);
+ Tcl_DStringAppend(&cmd, Tcl_GetString(textPtr->watchCmd), -1);
+ Tcl_DStringAppendElement(&cmd, Tk_PathName(textPtr->tkwin));
+ Tcl_DStringAppendElement(&cmd, operation);
+ Tcl_DStringAppendElement(&cmd, index1 ? index1 : "");
+ Tcl_DStringAppendElement(&cmd, index2 ? index2 : "");
+ Tcl_DStringStartSublist(&cmd);
+ if (arg1) { Tcl_DStringAppendElement(&cmd, arg1); }
+ if (arg2) { Tcl_DStringAppendElement(&cmd, arg2); }
+ if (arg3) { Tcl_DStringAppendElement(&cmd, arg3); }
+ Tcl_DStringEndSublist(&cmd);
+ Tcl_DStringAppendElement(&cmd, userFlag ? "yes" : "no");
+
+ textPtr->refCount += 1;
+
+ Tcl_Preserve((ClientData) textPtr->interp);
+ if (Tcl_EvalEx(textPtr->interp, Tcl_DStringValue(&cmd), Tcl_DStringLength(&cmd), 0) != TCL_OK) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (triggering the \"watch\" command failed)");
+ Tcl_BackgroundException(textPtr->interp, TCL_ERROR);
+ }
+ Tcl_Release((ClientData) textPtr->interp);
+
+ Tcl_DStringFree(&cmd);
+ return !TkTextDecrRefCountAndTestIfDestroyed(textPtr);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * GenerateEvent --
+ *
+ * Send an event about a new state. This is equivalent to:
+ * event generate $textWidget <<TYPE>>
+ * for all peers of this text widget.
*
* Results:
* None
@@ -5485,52 +9970,64 @@ TextGetText(
*/
static void
-GenerateModifiedEvent(
- TkText *textPtr) /* Information about text widget. */
+GenerateEvent(
+ TkSharedText *sharedTextPtr,
+ const char *type)
{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
+ TkText *textPtr;
+
+ for (textPtr = sharedTextPtr->peers; textPtr; textPtr = textPtr->next) {
Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "Modified", NULL);
+ SendVirtualEvent(textPtr->tkwin, type, NULL);
}
}
/*
*----------------------------------------------------------------------
*
- * GenerateUndoStackEvent --
+ * UpdateModifiedFlag --
*
- * Send an event that the undo or redo stack became empty or unempty.
- * This is equivalent to:
- * event generate $textWidget <<UndoStack>>
- * for all peers of $textWidget.
+ * Updates the modified flag of the text widget.
*
* Results:
* None
*
* Side effects:
- * May force the text window (and all peers) into existence.
+ * None.
*
*----------------------------------------------------------------------
*/
static void
-GenerateUndoStackEvent(
- TkText *textPtr) /* Information about text widget. */
+UpdateModifiedFlag(
+ TkSharedText *sharedTextPtr,
+ bool flag)
{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
- Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
+ bool oldModifiedFlag = sharedTextPtr->isModified;
+
+ if (flag) {
+ sharedTextPtr->isModified = true;
+ } else if (sharedTextPtr->undoStack && !sharedTextPtr->userHasSetModifiedFlag) {
+ if (sharedTextPtr->insertDeleteUndoTokenCount > 0) {
+ sharedTextPtr->isModified = true;
+ } else {
+ unsigned undoDepth = TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack);
+ sharedTextPtr->isModified = (undoDepth > 0 && undoDepth == sharedTextPtr->undoLevel);
+ }
+ }
+
+ if (oldModifiedFlag != sharedTextPtr->isModified) {
+ sharedTextPtr->userHasSetModifiedFlag = false;
+ GenerateEvent(sharedTextPtr, "Modified");
}
}
/*
*----------------------------------------------------------------------
*
- * UpdateDirtyFlag --
+ * TkTextUpdateAlteredFlag --
*
- * Updates the dirtyness of the text widget
+ * Updates the "altered" flag of the text widget.
*
* Results:
* None
@@ -5541,43 +10038,89 @@ GenerateUndoStackEvent(
*----------------------------------------------------------------------
*/
-static void
-UpdateDirtyFlag(
+void
+TkTextUpdateAlteredFlag(
TkSharedText *sharedTextPtr)/* Information about text widget. */
{
- int oldDirtyFlag;
-
- /*
- * If we've been forced to be dirty, we stay dirty (until explicitly
- * reset, of course).
- */
+ bool oldIsAlteredFlag = sharedTextPtr->isAltered;
+ bool oldIsIrreversibleFlag = sharedTextPtr->isIrreversible;
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) {
- return;
+ if (sharedTextPtr->undoStack) {
+ if (TkTextUndoContentIsIrreversible(sharedTextPtr->undoStack)) {
+ sharedTextPtr->isIrreversible = true;
+ }
+ if (!sharedTextPtr->isIrreversible) {
+ sharedTextPtr->isAltered = sharedTextPtr->undoTagListCount > 0
+ || sharedTextPtr->undoMarkListCount > 0
+ || TkTextUndoGetCurrentUndoStackDepth(sharedTextPtr->undoStack) > 0;
+ }
+ } else {
+ sharedTextPtr->isIrreversible = true;
+ }
+ if (sharedTextPtr->isIrreversible) {
+ sharedTextPtr->isAltered = true;
+ }
+ if (oldIsAlteredFlag != sharedTextPtr->isAltered) {
+ GenerateEvent(sharedTextPtr, "Altered");
}
+ if (oldIsIrreversibleFlag != sharedTextPtr->isIrreversible) {
+ GenerateEvent(sharedTextPtr, "Irreversible");
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextRunAfterSyncCmd --
+ *
+ * This function executes the command scheduled by
+ * [.text sync -command $cmd], if any.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Anything may happen, depending on $cmd contents.
+ *
+ *----------------------------------------------------------------------
+ */
- 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).
- */
+void
+TkTextRunAfterSyncCmd(
+ TkText *textPtr) /* Information about text widget. */
+{
+ int code;
+ bool error = false;
+ Tcl_Obj *afterSyncCmd;
- sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED;
+ assert(!TkTextPendingSync(textPtr));
+
+ textPtr->pendingAfterSync = false;
+ afterSyncCmd = textPtr->afterSyncCmd;
+
+ if (!afterSyncCmd) {
return;
}
- oldDirtyFlag = sharedTextPtr->isDirty;
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) {
- sharedTextPtr->isDirty--;
- } else {
- sharedTextPtr->isDirty++;
- }
+ /*
+ * We have to expect nested calls, futhermore the receiver might destroy the widget.
+ */
- if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {
- GenerateModifiedEvent(sharedTextPtr->peers);
+ textPtr->afterSyncCmd = NULL;
+ textPtr->refCount += 1;
+
+ Tcl_Preserve((ClientData) textPtr->interp);
+ if (!(textPtr->flags & DESTROYED)) {
+ code = Tcl_EvalObjEx(textPtr->interp, afterSyncCmd, TCL_EVAL_GLOBAL);
+ if (code == TCL_ERROR && !error) {
+ Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
+ Tcl_BackgroundError(textPtr->interp);
+ error = true;
+ }
}
+ Tcl_GuardedDecrRefCount(afterSyncCmd);
+ Tcl_Release((ClientData) textPtr->interp);
+ TkTextDecrRefCountAndTestIfDestroyed(textPtr);
}
/*
@@ -5599,31 +10142,143 @@ UpdateDirtyFlag(
static void
RunAfterSyncCmd(
- ClientData clientData) /* Information about text widget. */
+ ClientData clientData) /* Information about text widget. */
{
- register TkText *textPtr = (TkText *) clientData;
- int code;
+ TkText *textPtr = (TkText *) clientData;
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
+ if (!(textPtr->flags & DESTROYED)) {
+ if (TkTextPendingSync(textPtr)) {
+ /* Too late here, the widget is not in sync, so we have to wait. */
+ } else {
+ TkTextRunAfterSyncCmd(textPtr);
+ }
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkTextGenerateWidgetViewSyncEvent --
+ *
+ * Send the <<WidgetViewSync>> event related to the text widget
+ * line metrics asynchronous update.
+ * This is equivalent to:
+ * event generate $textWidget <<WidgetViewSync>> -detail $s
+ * where $s is the sync status: true (when the widget view is in
+ * sync with its internal data) or false (when it is not).
+ *
+ * Note that this has to be done in the idle loop, otherwise vwait
+ * will not return.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * If corresponding bindings are present, they will trigger.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FireWidgetViewSyncEvent(
+ ClientData clientData) /* Information about text widget. */
+{
+ TkText *textPtr = (TkText *) clientData;
+ Tcl_Interp *interp;
+ bool syncState;
+
+ textPtr->pendingFireEvent = false;
+
+ if (textPtr->flags & DESTROYED) {
+ return;
+ }
+
+ syncState = !TkTextPendingSync(textPtr);
+
+ if (textPtr->sendSyncEvent && syncState) {
/*
- * The widget has been deleted. Don't do anything.
- */
+ * The user is waiting for sync state 'true', so we must send it.
+ */
+
+ textPtr->prevSyncState = false;
+ }
+
+ if (textPtr->prevSyncState == syncState) {
+ /*
+ * Do not send "WidgetViewSync" with same sync state as before
+ * (except if we must send it because the user is waiting for it).
+ */
- if (textPtr->refCount-- <= 1) {
- ckfree((char *) textPtr);
- }
return;
}
- Tcl_Preserve((ClientData) textPtr->interp);
- code = Tcl_EvalObjEx(textPtr->interp, textPtr->afterSyncCmd, TCL_EVAL_GLOBAL);
- if (code == TCL_ERROR) {
- Tcl_AddErrorInfo(textPtr->interp, "\n (text sync)");
- Tcl_BackgroundError(textPtr->interp);
+ if ((textPtr->sendSyncEvent || textPtr->pendingAfterSync) && !syncState) {
+ /*
+ * Do not send "WidgetViewSync" with sync state "false" as long as
+ * we have a pending sync command.
+ */
+
+ return;
}
- Tcl_Release((ClientData) textPtr->interp);
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
+
+ if (syncState) {
+ textPtr->sendSyncEvent = false;
+ }
+ textPtr->prevSyncState = syncState;
+
+ interp = textPtr->interp;
+ Tcl_Preserve((ClientData) interp);
+ SendVirtualEvent(textPtr->tkwin, "WidgetViewSync", Tcl_NewBooleanObj(syncState));
+ Tcl_Release((ClientData) interp);
+}
+
+void
+TkTextGenerateWidgetViewSyncEvent(
+ TkText *textPtr, /* Information about text widget. */
+ bool sendImmediately)
+{
+ if (!textPtr->pendingFireEvent) {
+ textPtr->pendingFireEvent = true;
+ if (sendImmediately) {
+ FireWidgetViewSyncEvent((ClientData) textPtr);
+ } else {
+ Tcl_DoWhenIdle(FireWidgetViewSyncEvent, (ClientData) textPtr);
+ }
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TkTextPrintIndex --
+ *
+ * This function generates a string description of an index, suitable for
+ * reading in again later.
+ *
+ * Results:
+ * The characters pointed to by string are modified. Returns the number
+ * of characters in the string.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+/*
+ * NOTE: this function has external linkage (declared in a common header file)
+ * and cannot be inlined.
+ */
+
+int
+TkTextPrintIndex(
+ const TkText *textPtr,
+ const TkTextIndex *indexPtr,/* Pointer to index. */
+ char *string) /* Place to store the position. Must have at least TK_POS_CHARS
+ * characters. */
+{
+ assert(textPtr);
+ return TkTextIndexPrint(textPtr->sharedTextPtr, textPtr, indexPtr, string);
}
/*
@@ -5653,19 +10308,22 @@ SearchPerform(
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. */
+ Tcl_Obj *fromPtr, /* Contains information describing the first index. */
+ Tcl_Obj *toPtr) /* NULL or information describing the last index. */
{
+ TkText *textPtr = searchSpecPtr->clientData;
+
+ if (TkTextIsDeadPeer(textPtr)) {
+ return TCL_OK;
+ }
+
/*
* Find the starting line and starting offset (measured in Unicode chars
* for regexp search, utf-8 bytes for exact search).
*/
if (searchSpecPtr->lineIndexProc(interp, fromPtr, searchSpecPtr,
- &searchSpecPtr->startLine,
- &searchSpecPtr->startOffset) != TCL_OK) {
+ &searchSpecPtr->startLine, &searchSpecPtr->startOffset) != TCL_OK) {
return TCL_ERROR;
}
@@ -5673,15 +10331,13 @@ SearchPerform(
* Find the optional end location, similarly.
*/
- if (toPtr != NULL) {
- const TkTextIndex *indexToPtr, *indexFromPtr;
- TkText *textPtr = searchSpecPtr->clientData;
+ if (toPtr) {
+ TkTextIndex indexTo, indexFrom;
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, toPtr);
- if (indexToPtr == NULL) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, toPtr, &indexTo)
+ || !TkTextGetIndexFromObj(interp, textPtr, fromPtr, &indexFrom)) {
return TCL_ERROR;
}
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, fromPtr);
/*
* Check for any empty search range here. It might be better in the
@@ -5689,14 +10345,12 @@ SearchPerform(
* wrap when given a negative search range).
*/
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) ==
- (searchSpecPtr->backwards ? -1 : 1)) {
+ if (TkTextIndexCompare(&indexFrom, &indexTo) == (searchSpecPtr->backwards ? -1 : 1)) {
return TCL_OK;
}
if (searchSpecPtr->lineIndexProc(interp, toPtr, searchSpecPtr,
- &searchSpecPtr->stopLine,
- &searchSpecPtr->stopOffset) != TCL_OK) {
+ &searchSpecPtr->stopLine, &searchSpecPtr->stopOffset) != TCL_OK) {
return TCL_ERROR;
}
} else {
@@ -5777,9 +10431,9 @@ SearchCore(
#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;
+ int32_t smArray[2 * LOTS_OF_MATCHES];
+ int32_t *storeMatch = smArray;
+ int32_t *storeLength = smArray + LOTS_OF_MATCHES;
int lastBackwardsLineMatch = -1;
int lastBackwardsMatchOffset = -1;
@@ -5808,7 +10462,7 @@ SearchCore(
(searchSpecPtr->noCase ? TCL_REG_NOCASE : 0)
| (searchSpecPtr->noLineStop ? 0 : TCL_REG_NLSTOP)
| TCL_REG_ADVANCED | TCL_REG_CANMATCH | TCL_REG_NLANCH);
- if (regexp == NULL) {
+ if (!regexp) {
return TCL_ERROR;
}
}
@@ -5830,7 +10484,7 @@ SearchCore(
*/
pattern = Tcl_GetString(patObj);
- matchLength = patObj->length;
+ matchLength = GetByteLength(patObj);
nl = strchr(pattern, '\n');
/*
@@ -5839,7 +10493,7 @@ SearchCore(
* will work fine.
*/
- if (nl != NULL && nl[1] != '\0') {
+ if (nl && nl[1] != '\0') {
firstNewLine = (nl - pattern);
}
} else {
@@ -5884,7 +10538,7 @@ SearchCore(
lineInfo = searchSpecPtr->addLineProc(lineNum, searchSpecPtr, theLine,
&lastOffset, &linesSearched);
- if (lineInfo == NULL) {
+ if (!lineInfo) {
/*
* This should not happen, since 'lineNum' should be valid in the
* call above. However, let's try to be flexible and not cause a
@@ -5920,7 +10574,7 @@ SearchCore(
* other part of the line.
*/
- passes++;
+ passes += 1;
if ((passes == 1) ^ searchSpecPtr->backwards) {
/*
* Forward search and first pass, or backward search and
@@ -5932,8 +10586,7 @@ SearchCore(
if (searchSpecPtr->startOffset > firstOffset) {
firstOffset = searchSpecPtr->startOffset;
}
- if ((firstOffset >= lastOffset)
- && ((lastOffset != 0) || searchSpecPtr->exact)) {
+ if (firstOffset >= lastOffset && (lastOffset != 0 || searchSpecPtr->exact)) {
goto nextLine;
}
} else {
@@ -5961,15 +10614,13 @@ SearchCore(
int maxExtraLines = 0;
const char *startOfLine = Tcl_GetString(theLine);
- CLANG_ASSERT(pattern);
+ assert(pattern);
do {
- int ch;
const char *p;
int lastFullLine = lastOffset;
if (firstNewLine == -1) {
- if (searchSpecPtr->strictLimits
- && (firstOffset + matchLength > lastOffset)) {
+ if (searchSpecPtr->strictLimits && (firstOffset + matchLength > lastOffset)) {
/*
* Not enough characters to match.
*/
@@ -5991,31 +10642,31 @@ SearchCore(
const char c = pattern[0];
+ p = startOfLine;
if (alreadySearchOffset != -1) {
- p = startOfLine + alreadySearchOffset;
+ p += alreadySearchOffset;
alreadySearchOffset = -1;
} else {
- p = startOfLine + lastOffset -1;
+ p += lastOffset - 1;
}
while (p >= startOfLine + firstOffset) {
- if (p[0] == c && !strncmp(p, pattern,
- (unsigned) matchLength)) {
+ if (p[0] == c && strncmp(p, pattern, matchLength) == 0) {
goto backwardsMatch;
}
- p--;
+ p -= 1;
}
break;
} else {
p = strstr(startOfLine + firstOffset, pattern);
}
- if (p == NULL) {
+ if (!p) {
/*
* Single line match failed.
*/
break;
}
- } else if (firstNewLine >= (lastOffset - firstOffset)) {
+ } else if (firstNewLine >= lastOffset - firstOffset) {
/*
* Multi-line match, but not enough characters to match.
*/
@@ -6028,7 +10679,7 @@ SearchCore(
*/
p = startOfLine + lastOffset - firstNewLine - 1;
- if (strncmp(p, pattern, (unsigned) firstNewLine + 1)) {
+ if (strncmp(p, pattern, firstNewLine + 1) != 0) {
/*
* No match.
*/
@@ -6045,7 +10696,7 @@ SearchCore(
*/
int lastTotal = lastOffset;
- int skipFirst = lastOffset - firstNewLine -1;
+ int skipFirst = lastOffset - firstNewLine - 1;
/*
* We may be able to match if given more text. The
@@ -6056,7 +10707,7 @@ SearchCore(
while (1) {
lastFullLine = lastTotal;
- if (lineNum+extraLines>=searchSpecPtr->numLines) {
+ if (lineNum + extraLines >= searchSpecPtr->numLines) {
p = NULL;
break;
}
@@ -6067,9 +10718,8 @@ SearchCore(
*/
if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
+ if (!searchSpecPtr->addLineProc(lineNum + extraLines, searchSpecPtr,
+ theLine, &lastTotal, &extraLines)) {
p = NULL;
if (!searchSpecPtr->backwards) {
extraLinesSearched = extraLines;
@@ -6087,14 +10737,14 @@ SearchCore(
* exact searches.
*/
- if ((lastTotal - skipFirst) >= matchLength) {
+ 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)) {
+ if (strncmp(p, pattern, matchLength) != 0) {
p = NULL;
}
break;
@@ -6103,8 +10753,7 @@ SearchCore(
* Not enough text yet, but check the prefix.
*/
- if (strncmp(p, pattern,
- (unsigned)(lastTotal - skipFirst))) {
+ if (strncmp(p, pattern, lastTotal - skipFirst) != 0) {
p = NULL;
break;
}
@@ -6113,7 +10762,7 @@ SearchCore(
* The prefix matches, so keep looking.
*/
}
- extraLines++;
+ extraLines += 1;
}
/*
* If we reach here, with p != NULL, we've found a
@@ -6121,7 +10770,7 @@ SearchCore(
* didn't finish it off, so we go to the next line.
*/
- if (p == NULL) {
+ if (!p) {
break;
}
@@ -6136,7 +10785,7 @@ SearchCore(
}
backwardsMatch:
- if ((p - startOfLine) >= lastOffset) {
+ if (p - startOfLine >= lastOffset) {
break;
}
@@ -6179,8 +10828,7 @@ SearchCore(
*/
if (!searchSpecPtr->backwards) {
- alreadySearchOffset =
- firstOffset - lastFullLine;
+ alreadySearchOffset = firstOffset - lastFullLine;
break;
}
}
@@ -6192,8 +10840,28 @@ SearchCore(
break;
}
} else {
- firstOffset = p - startOfLine +
- TkUtfToUniChar(startOfLine+matchOffset,&ch);
+ int len;
+ const char *s = startOfLine + matchOffset;
+
+#if TCL_UTF_MAX > 4
+ /*
+ * HACK: Support of pseudo UTF-8 strings. Needed because of this
+ * bad hack with TCL_UTF_MAX > 4, the whole thing is amateurish.
+ * (See function GetLineBreakFunc() about the very severe problems
+ * with TCL_UTF_MAX > 4).
+ */
+
+ int ch;
+ len = TkUtfToUniChar(s, &ch);
+#else
+ /*
+ * Proper implementation for UTF-8 strings:
+ */
+
+ Tcl_UniChar ch;
+ len = Tcl_UtfToUniChar(s, &ch);
+#endif
+ firstOffset = (p - startOfLine) + len;
}
}
} while (searchSpecPtr->all);
@@ -6208,7 +10876,7 @@ SearchCore(
int lastFullLine = lastOffset;
match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1, (firstOffset>0 ? TCL_REG_NOTBOL : 0));
+ firstOffset, 1, firstOffset > 0 ? TCL_REG_NOTBOL : 0);
if (match < 0) {
code = TCL_ERROR;
goto searchDone;
@@ -6221,9 +10889,9 @@ SearchCore(
* full greedy match.
*/
- if (!match ||
- ((info.extendStart == info.matches[0].start)
- && (info.matches[0].end == lastOffset-firstOffset))) {
+ if (!match
+ || (info.extendStart == info.matches[0].start
+ && info.matches[0].end == lastOffset - firstOffset)) {
int extraLines = 0;
int prevFullLine;
@@ -6236,8 +10904,7 @@ SearchCore(
int lastTotal = lastOffset;
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch == (lineNum + 1))) {
+ if (lastBackwardsLineMatch != -1 && lastBackwardsLineMatch == lineNum + 1) {
lastNonOverlap = lastTotal;
}
@@ -6273,8 +10940,7 @@ SearchCore(
* when we look at that line.
*/
- if (!match && !searchSpecPtr->backwards
- && (firstOffset == 0)) {
+ if (!match && !searchSpecPtr->backwards && firstOffset == 0) {
extraLinesSearched = extraLines;
}
break;
@@ -6289,9 +10955,8 @@ SearchCore(
*/
if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
+ if (!searchSpecPtr->addLineProc(lineNum + extraLines, searchSpecPtr,
+ theLine, &lastTotal, &extraLines)) {
/*
* There are no more acceptable lines, so we
* can say we have searched all of these.
@@ -6304,16 +10969,14 @@ SearchCore(
}
maxExtraLines = extraLines;
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch
- == (lineNum + extraLines + 1))) {
+ if (lastBackwardsLineMatch != -1
+ && lastBackwardsLineMatch == lineNum + extraLines + 1) {
lastNonOverlap = lastTotal;
}
}
match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1,
- ((firstOffset > 0) ? TCL_REG_NOTBOL : 0));
+ firstOffset, 1, firstOffset > 0 ? TCL_REG_NOTBOL : 0);
if (match < 0) {
code = TCL_ERROR;
goto searchDone;
@@ -6337,9 +11000,8 @@ SearchCore(
* circumstances.
*/
- if ((match &&
- firstOffset+info.matches[0].end != lastTotal &&
- firstOffset+info.matches[0].end < prevFullLine)
+ if ((match && firstOffset + info.matches[0].end != lastTotal
+ && firstOffset + info.matches[0].end < prevFullLine)
|| info.extendStart < 0) {
break;
}
@@ -6351,11 +11013,10 @@ SearchCore(
* that line.
*/
- if (match && (info.matches[0].start >= lastOffset)) {
+ if (match && info.matches[0].start >= lastOffset) {
break;
}
- if (match && ((firstOffset + info.matches[0].end)
- >= prevFullLine)) {
+ if (match && firstOffset + info.matches[0].end >= prevFullLine) {
if (extraLines > 0) {
extraLinesSearched = extraLines - 1;
}
@@ -6366,7 +11027,7 @@ SearchCore(
* The prefix matches, so keep looking.
*/
- extraLines++;
+ extraLines += 1;
}
/*
@@ -6394,30 +11055,27 @@ SearchCore(
}
if (lastBackwardsLineMatch != -1) {
- if ((lineNum + linesSearched + extraLinesSearched)
- == lastBackwardsLineMatch) {
+ if (lineNum + linesSearched + extraLinesSearched == lastBackwardsLineMatch) {
/*
* Possible overlap or inclusion.
*/
- int thisOffset = firstOffset + info.matches[0].end
- - info.matches[0].start;
+ int thisOffset = firstOffset + info.matches[0].end - info.matches[0].start;
if (lastNonOverlap != -1) {
/*
* Possible overlap or enclosure.
*/
- if (thisOffset-lastNonOverlap >=
- lastBackwardsMatchOffset+matchLength){
+ if (thisOffset - lastNonOverlap >=
+ lastBackwardsMatchOffset + matchLength) {
/*
* Totally encloses previous match, so
* forget the previous match.
*/
lastBackwardsLineMatch = -1;
- } else if ((thisOffset - lastNonOverlap)
- > lastBackwardsMatchOffset) {
+ } else if (thisOffset - lastNonOverlap > lastBackwardsMatchOffset) {
/*
* Overlap. Previous match is ok, and the
* current match is only ok if we are
@@ -6445,7 +11103,7 @@ SearchCore(
goto recordBackwardsMatch;
}
- } else if (lineNum+linesSearched+extraLinesSearched
+ } else if (lineNum + linesSearched + extraLinesSearched
< lastBackwardsLineMatch) {
/*
* No overlap.
@@ -6469,8 +11127,7 @@ SearchCore(
if (lastBackwardsLineMatch != -1) {
recordBackwardsMatch:
searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
+ searchSpecPtr, NULL, NULL, lastBackwardsMatchOffset, matchLength);
lastBackwardsLineMatch = -1;
if (!searchSpecPtr->all) {
goto searchDone;
@@ -6492,10 +11149,9 @@ SearchCore(
if (matchOffset == -1 ||
((searchSpecPtr->all || searchSpecPtr->backwards)
- && ((firstOffset < matchOffset)
- || ((firstOffset + info.matches[0].end
- - info.matches[0].start)
- > (matchOffset + matchLength))))) {
+ && (firstOffset < matchOffset
+ || firstOffset + info.matches[0].end - info.matches[0].start
+ > matchOffset + matchLength))) {
matchOffset = firstOffset;
matchLength = info.matches[0].end - info.matches[0].start;
@@ -6513,13 +11169,12 @@ SearchCore(
* matches on the heap.
*/
- int *newArray =
- ckalloc(4 * matchNum * sizeof(int));
- memcpy(newArray, storeMatch, matchNum*sizeof(int));
- memcpy(newArray + 2*matchNum, storeLength,
- matchNum * sizeof(int));
+ int matchNumSize = matchNum * sizeof(int32_t);
+ int32_t *newArray = malloc(4*matchNumSize);
+ memcpy(newArray, storeMatch, matchNumSize);
+ memcpy(newArray + 2*matchNum, storeLength, matchNumSize);
if (storeMatch != smArray) {
- ckfree(storeMatch);
+ free((char *) storeMatch);
}
matchNum *= 2;
storeMatch = newArray;
@@ -6527,7 +11182,7 @@ SearchCore(
}
storeMatch[matches] = matchOffset;
storeLength[matches] = matchLength;
- matches++;
+ matches += 1;
} else {
/*
* Now actually record the match, but only if we are
@@ -6536,8 +11191,7 @@ SearchCore(
if (searchSpecPtr->all &&
!searchSpecPtr->foundMatchProc(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
+ searchSpecPtr, lineInfo, theLine, matchOffset, matchLength)) {
/*
* We reached the end of the search.
*/
@@ -6552,8 +11206,7 @@ SearchCore(
* explicitly disallow overlapping matches.
*/
- if (matchLength > 0 && !searchSpecPtr->overlap
- && !searchSpecPtr->backwards) {
+ if (matchLength > 0 && !searchSpecPtr->overlap && !searchSpecPtr->backwards) {
firstOffset += matchLength;
if (firstOffset >= lastOffset) {
/*
@@ -6575,7 +11228,7 @@ SearchCore(
* We'll add this on again just below.
*/
- firstOffset --;
+ firstOffset -= 1;
}
}
@@ -6585,7 +11238,7 @@ SearchCore(
* repeated forward searches).
*/
- firstOffset++;
+ firstOffset += 1;
} while (searchSpecPtr->backwards || searchSpecPtr->all);
if (matches > 0) {
@@ -6594,7 +11247,7 @@ SearchCore(
* with 'foundMatchProc' yet.
*/
- matches--;
+ matches -= 1;
matchOffset = storeMatch[matches];
matchLength = storeLength[matches];
while (--matches >= 0) {
@@ -6609,8 +11262,7 @@ SearchCore(
* found which would exercise such a problem.
*/
}
- if (storeMatch[matches] + storeLength[matches]
- >= matchOffset + matchLength) {
+ if (storeMatch[matches] + storeLength[matches] >= matchOffset + matchLength) {
/*
* The new match totally encloses the previous one, so
* we overwrite the previous one.
@@ -6621,8 +11273,7 @@ SearchCore(
continue;
}
if (!searchSpecPtr->overlap) {
- if (storeMatch[matches] + storeLength[matches]
- > matchOffset) {
+ if (storeMatch[matches] + storeLength[matches] > matchOffset) {
continue;
}
}
@@ -6658,8 +11309,7 @@ SearchCore(
* we are done.
*/
- if ((lastBackwardsLineMatch == -1) && (matchOffset >= 0)
- && !searchSpecPtr->all) {
+ if (lastBackwardsLineMatch == -1 && matchOffset >= 0 && !searchSpecPtr->all) {
searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr, lineInfo,
theLine, matchOffset, matchLength);
goto searchDone;
@@ -6682,14 +11332,12 @@ SearchCore(
}
if (searchSpecPtr->backwards) {
- lineNum--;
+ lineNum -= 1;
if (lastBackwardsLineMatch != -1
- && ((lineNum < 0)
- || (lineNum + 2 < lastBackwardsLineMatch))) {
+ && (lineNum < 0 || lineNum + 2 < lastBackwardsLineMatch)) {
searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
+ searchSpecPtr, NULL, NULL, lastBackwardsMatchOffset, matchLength);
lastBackwardsLineMatch = -1;
if (!searchSpecPtr->all) {
goto searchDone;
@@ -6697,7 +11345,7 @@ SearchCore(
}
if (lineNum < 0) {
- lineNum = searchSpecPtr->numLines-1;
+ lineNum = searchSpecPtr->numLines - 1;
}
if (!searchSpecPtr->exact) {
/*
@@ -6712,7 +11360,7 @@ SearchCore(
break;
}
} else {
- lineNum++;
+ lineNum += 1;
if (lineNum >= searchSpecPtr->numLines) {
lineNum = 0;
}
@@ -6741,15 +11389,15 @@ SearchCore(
* Free up the cached line and pattern.
*/
- Tcl_DecrRefCount(theLine);
- Tcl_DecrRefCount(patObj);
+ Tcl_GuardedDecrRefCount(theLine);
+ Tcl_GuardedDecrRefCount(patObj);
/*
* Free up any extra space we allocated.
*/
if (storeMatch != smArray) {
- ckfree(storeMatch);
+ free((char *) storeMatch);
}
return code;
@@ -6758,6 +11406,175 @@ SearchCore(
/*
*----------------------------------------------------------------------
*
+ * GetTextStartEnd -
+ *
+ * Converts an internal TkTextSegment ptr into a Tcl string obj containing
+ * the representation of the index. (Handler for the 'startEndMark' configuration
+ * option type.)
+ *
+ * Results:
+ * Tcl_Obj containing the string representation of the index position.
+ *
+ * Side effects:
+ * Creates a new Tcl_Obj.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+GetTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *recordPtr, /* Pointer to widget record. */
+ int internalOffset) /* Offset within *recordPtr containing the start object. */
+{
+ TkTextIndex index;
+ char buf[TK_POS_CHARS] = { '\0' };
+ const TkText *textPtr = (const TkText *) recordPtr;
+ const TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
+ Tcl_Obj **objPtr = (Tcl_Obj **) (recordPtr + internalOffset);
+ const TkTextSegment *sharedMarker;
+ TkTextSegment *marker;
+
+ if (objPtr == &textPtr->newStartIndex) {
+ marker = textPtr->startMarker;
+ sharedMarker = sharedTextPtr->startMarker;
+ } else {
+ marker = textPtr->endMarker;
+ sharedMarker = sharedTextPtr->endMarker;
+ }
+ if (marker != sharedMarker) {
+ TkTextIndexClear2(&index, NULL, sharedTextPtr->tree);
+ TkTextIndexSetSegment(&index, marker);
+ TkTextIndexPrint(sharedTextPtr, NULL, &index, buf);
+ }
+ return Tcl_NewStringObj(buf, -1);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * SetTextStartEnd --
+ *
+ * Converts a Tcl_Obj representing a widget's (start or end) index into a
+ * TkTextSegment* value. (Handler for the 'startEndMark' configuration option type.)
+ *
+ * Results:
+ * Standard Tcl result.
+ *
+ * Side effects:
+ * May store the TkTextSegment* 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
+ObjectIsEmpty(
+ Tcl_Obj *objPtr) /* Object to test. May be NULL. */
+{
+ return objPtr ? GetByteLength(objPtr) == 0 : true;
+}
+
+static int
+SetTextStartEnd(
+ 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. */
+{
+ Tcl_Obj **objPtr = (Tcl_Obj **) (recordPtr + internalOffset);
+ Tcl_Obj **oldObjPtr = (Tcl_Obj **) oldInternalPtr;
+ const TkText *textPtr = (const TkText *) recordPtr;
+
+ assert(!*objPtr);
+ *oldObjPtr = NULL;
+
+ if ((flags & TK_OPTION_NULL_OK) && ObjectIsEmpty(*value)) {
+ *value = NULL;
+ *objPtr = Tcl_NewStringObj((objPtr == &textPtr->newStartIndex) ? "begin" : "end", -1);
+ } else {
+ *objPtr = *value;
+ }
+ Tcl_IncrRefCount(*objPtr);
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * RestoreTextStartEnd --
+ *
+ * Restore an index option value from a saved value. (Handler for the
+ * 'index' configuration option type.)
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Restores the old value.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+RestoreTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *internalPtr, /* Pointer to storage for value. */
+ char *oldInternalPtr) /* Pointer to old value. */
+{
+ Tcl_Obj **newValue = (Tcl_Obj **) internalPtr;
+ Tcl_Obj **oldValue = (Tcl_Obj **) oldInternalPtr;
+
+ if (*oldValue) {
+ Tcl_IncrRefCount(*oldValue);
+ }
+ *newValue = *oldValue;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FreeTextStartEnd --
+ *
+ * Free an index option value from a saved value. (Handler for the
+ * 'index' configuration option type.)
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Releases some memory.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FreeTextStartEnd(
+ ClientData clientData,
+ Tk_Window tkwin,
+ char *internalPtr)
+{
+ Tcl_Obj *objPtr = *(Tcl_Obj **) internalPtr;
+
+ if (objPtr) {
+ Tcl_GuardedDecrRefCount(objPtr);
+ }
+}
+
+#if SUPPORT_DEPRECATED_STARTLINE_ENDLINE
+/*
+ *----------------------------------------------------------------------
+ *
* GetLineStartEnd -
*
* Converts an internal TkTextLine ptr into a Tcl string obj containing
@@ -6777,15 +11594,16 @@ GetLineStartEnd(
ClientData clientData,
Tk_Window tkwin,
char *recordPtr, /* Pointer to widget record. */
- int internalOffset) /* Offset within *recordPtr containing the
- * line value. */
+ int internalOffset) /* Offset within *recordPtr containing the line value. */
{
+ TkText *textPtr;
TkTextLine *linePtr = *(TkTextLine **)(recordPtr + internalOffset);
- if (linePtr == NULL) {
+ if (!linePtr) {
return Tcl_NewObj();
}
- return Tcl_NewIntObj(1 + TkBTreeLinesTo(NULL, linePtr));
+ textPtr = (TkText *) recordPtr;
+ return Tcl_NewIntObj(1 + TkBTreeLinesTo(textPtr->sharedTextPtr->tree, NULL, linePtr, NULL));
}
/*
@@ -6825,13 +11643,9 @@ SetLineStartEnd(
char *internalPtr;
TkText *textPtr = (TkText *) recordPtr;
- if (internalOffset >= 0) {
- internalPtr = recordPtr + internalOffset;
- } else {
- internalPtr = NULL;
- }
+ internalPtr = internalOffset >= 0 ? recordPtr + internalOffset : NULL;
- if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) {
+ if ((flags & TK_OPTION_NULL_OK) && ObjectIsEmpty(*value)) {
*value = NULL;
} else {
int line;
@@ -6839,10 +11653,10 @@ SetLineStartEnd(
if (Tcl_GetIntFromObj(interp, *value, &line) != TCL_OK) {
return TCL_ERROR;
}
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line-1);
+ linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line - 1);
}
- if (internalPtr != NULL) {
+ if (internalPtr) {
*((TkTextLine **) oldInternalPtr) = *((TkTextLine **) internalPtr);
*((TkTextLine **) internalPtr) = linePtr;
}
@@ -6873,40 +11687,10 @@ RestoreLineStartEnd(
char *internalPtr, /* Pointer to storage for value. */
char *oldInternalPtr) /* Pointer to old value. */
{
- *(TkTextLine **)internalPtr = *(TkTextLine **)oldInternalPtr;
+ *(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. */
-{
- if (objPtr == NULL) {
- return 1;
- }
- if (objPtr->bytes != NULL) {
- return (objPtr->length == 0);
- }
- (void)Tcl_GetString(objPtr);
- return (objPtr->length == 0);
-}
+#endif /* SUPPORT_DEPRECATED_STARTLINE_ENDLINE */
/*
*----------------------------------------------------------------------
@@ -6926,19 +11710,22 @@ ObjectIsEmpty(
*----------------------------------------------------------------------
*/
+#if TK_MAJOR_VERSION > 8 || (TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION > 5)
+
int
TkpTesttextCmd(
ClientData clientData, /* Main window for application. */
Tcl_Interp *interp, /* Current interpreter. */
int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument strings. */
+ Tcl_Obj *const objv[]) /* Argument strings. */
{
TkText *textPtr;
size_t len;
int lineIndex, byteIndex, byteOffset;
- TkTextIndex index;
- char buf[64];
+ TkTextIndex index, insIndex;
+ char buf[TK_POS_CHARS];
Tcl_CmdInfo info;
+ Tcl_Obj *watchCmd;
if (objc < 3) {
return TCL_ERROR;
@@ -6956,13 +11743,12 @@ TkpTesttextCmd(
lineIndex = atoi(Tcl_GetString(objv[3])) - 1;
byteIndex = atoi(Tcl_GetString(objv[4]));
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex,
- byteIndex, &index);
+ TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineIndex, byteIndex, &index);
} else if (strncmp(Tcl_GetString(objv[2]), "forwbytes", len) == 0) {
if (objc != 5) {
return TCL_ERROR;
}
- if (TkTextGetIndex(interp, textPtr, Tcl_GetString(objv[3]), &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
byteOffset = atoi(Tcl_GetString(objv[4]));
@@ -6971,7 +11757,7 @@ TkpTesttextCmd(
if (objc != 5) {
return TCL_ERROR;
}
- if (TkTextGetIndex(interp, textPtr, Tcl_GetString(objv[3]), &index) != TCL_OK) {
+ if (!TkTextGetIndexFromObj(interp, textPtr, objv[3], &index)) {
return TCL_ERROR;
}
byteOffset = atoi(Tcl_GetString(objv[4]));
@@ -6980,16 +11766,293 @@ TkpTesttextCmd(
return TCL_ERROR;
}
+ /*
+ * Avoid triggering of the "watch" command.
+ */
+
+ watchCmd = textPtr->watchCmd;
+ textPtr->watchCmd = NULL;
+ insIndex = index; /* because TkTextSetMark may modify position */
+ TkTextSetMark(textPtr, "insert", &insIndex);
+ textPtr->watchCmd = watchCmd;
+
+ TkTextPrintIndex(textPtr, &index, buf);
+ Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s %d", buf, TkTextIndexGetByteIndex(&index)));
+ return TCL_OK;
+}
+
+#else /* backport to Tk 8.5 */
+
+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];
+ unsigned offs;
+ 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);
- Tcl_SetObjResult(interp, Tcl_ObjPrintf("%s %d", buf, index.byteIndex));
+ offs = strlen(buf);
+ snprintf(buf + offs, sizeof(buf) - offs, " %d", TkTextIndexGetByteIndex(&index));
+ Tcl_AppendResult(interp, buf, NULL);
+
return TCL_OK;
}
+
+#endif /* TK_MAJOR_VERSION > 8 || (TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION > 5) */
+
+#ifndef NDEBUG
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkpTextInspect --
+ *
+ * This function is for debugging only, printing the text content
+ * on stdout.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TkpTextInspect(
+ TkText *textPtr)
+{
+ Tcl_Obj *resultPtr;
+ Tcl_Obj *objv[8];
+ Tcl_Obj **argv;
+ int argc, i;
+
+ Tcl_IncrRefCount(resultPtr = Tcl_GetObjResult(textPtr->interp));
+ Tcl_ResetResult(textPtr->interp);
+ Tcl_IncrRefCount(objv[0] = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
+ Tcl_IncrRefCount(objv[1] = Tcl_NewStringObj("inspect", -1));
+ Tcl_IncrRefCount(objv[2] = Tcl_NewStringObj("-elide", -1));
+ Tcl_IncrRefCount(objv[3] = Tcl_NewStringObj("-chars", -1));
+ Tcl_IncrRefCount(objv[4] = Tcl_NewStringObj("-image", -1));
+ Tcl_IncrRefCount(objv[5] = Tcl_NewStringObj("-window", -1));
+ Tcl_IncrRefCount(objv[6] = Tcl_NewStringObj("-mark", -1));
+ Tcl_IncrRefCount(objv[7] = Tcl_NewStringObj("-tag", -1));
+ TextInspectCmd(textPtr, textPtr->interp, sizeof(objv)/sizeof(objv[0]), objv);
+ for (i = 0; i < (int) (sizeof(objv)/sizeof(objv[0])); ++i) {
+ Tcl_GuardedDecrRefCount(objv[i]);
+ }
+ Tcl_ListObjGetElements(textPtr->interp, Tcl_GetObjResult(textPtr->interp), &argc, &argv);
+ for (i = 0; i < argc; ++i) {
+ fprintf(stdout, "%s\n", Tcl_GetString(argv[i]));
+ }
+ Tcl_SetObjResult(textPtr->interp, resultPtr);
+ Tcl_GuardedDecrRefCount(resultPtr);
+}
+
+#endif /* NDEBUG */
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * TkpTextDump --
+ *
+ * This function is for debugging only, printing the text content
+ * on stdout.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+#ifndef NDEBUG
+
+void
+TkpTextDump(
+ TkText *textPtr)
+{
+ Tcl_Obj *resultPtr;
+ Tcl_Obj *objv[4];
+ Tcl_Obj **argv;
+ int argc, i;
+
+ Tcl_IncrRefCount(resultPtr = Tcl_GetObjResult(textPtr->interp));
+ Tcl_ResetResult(textPtr->interp);
+
+ Tcl_IncrRefCount(objv[0] = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
+ Tcl_IncrRefCount(objv[1] = Tcl_NewStringObj("dump", -1));
+ Tcl_IncrRefCount(objv[2] = Tcl_NewStringObj("begin", -1));
+ Tcl_IncrRefCount(objv[3] = Tcl_NewStringObj("end", -1));
+ TextDumpCmd(textPtr, textPtr->interp, sizeof(objv)/sizeof(objv[0]), objv);
+ for (i = 0; i < (int) (sizeof(objv)/sizeof(objv[0])); ++i) {
+ Tcl_GuardedDecrRefCount(objv[i]);
+ }
+
+ Tcl_ListObjGetElements(textPtr->interp, Tcl_GetObjResult(textPtr->interp), &argc, &argv);
+ for (i = 0; i < argc; i += 3) {
+ char const *type = Tcl_GetString(argv[i]);
+ char const *text = Tcl_GetString(argv[i + 1]);
+ char const *indx = Tcl_GetString(argv[i + 2]);
+
+ fprintf(stdout, "%s ", indx);
+ fprintf(stdout, "%s ", type);
+
+ if (strcmp(type, "text") == 0) {
+ int len = strlen(text), i;
+
+ fprintf(stdout, "\"");
+ for (i = 0; i < len; ++i) {
+ char c = text[i];
+
+ switch (c) {
+ case '\t': fprintf(stdout, "\\t"); break;
+ case '\n': fprintf(stdout, "\\n"); break;
+ case '\v': fprintf(stdout, "\\v"); break;
+ case '\f': fprintf(stdout, "\\f"); break;
+ case '\r': fprintf(stdout, "\\r"); break;
+
+ default:
+ if (UCHAR(c) < 0x80 && isprint(c)) {
+ fprintf(stdout, "%c", c);
+ } else {
+ fprintf(stdout, "\\x%02u", (unsigned) UCHAR(c));
+ }
+ break;
+ }
+ }
+ fprintf(stdout, "\"\n");
+ } else if (strcmp(type, "mark") == 0) {
+ Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&textPtr->sharedTextPtr->markTable, text);
+ const TkTextSegment *markPtr = NULL;
+
+ if (hPtr) {
+ markPtr = Tcl_GetHashValue(hPtr);
+ } else {
+ if (strcmp(text, "insert") == 0) { markPtr = textPtr->insertMarkPtr; }
+ if (strcmp(text, "current") == 0) { markPtr = textPtr->currentMarkPtr; }
+ }
+ if (markPtr) {
+ fprintf(stdout, "%s (%s)\n", text,
+ markPtr->typePtr == &tkTextLeftMarkType ? "left" : "right");
+ }
+ } else {
+ fprintf(stdout, "%s\n", text);
+ }
+ }
+
+ Tcl_SetObjResult(textPtr->interp, resultPtr);
+ Tcl_GuardedDecrRefCount(resultPtr);
+}
+
+#endif /* NDEBUG */
+
+
+#ifdef TK_C99_INLINE_SUPPORT
+/* Additionally we need stand-alone object code. */
+extern TkSharedText * TkBTreeGetShared(TkTextBTree tree);
+extern int TkBTreeGetNumberOfDisplayLines(const TkTextPixelInfo *pixelInfo);
+extern TkTextPixelInfo *TkBTreeLinePixelInfo(const TkText *textPtr, TkTextLine *linePtr);
+extern unsigned TkBTreeEpoch(TkTextBTree tree);
+extern unsigned TkBTreeIncrEpoch(TkTextBTree tree);
+extern struct Node *TkBTreeGetRoot(TkTextBTree tree);
+extern TkTextLine * TkBTreePrevLogicalLine(const TkSharedText *sharedTextPtr,
+ const TkText *textPtr, TkTextLine *linePtr);
+extern TkTextTag * TkBTreeGetTags(const TkTextIndex *indexPtr, TkTextSortMethod sortMeth,
+ int *flags);
+extern TkTextLine * TkBTreeGetStartLine(const TkText *textPtr);
+extern TkTextLine * TkBTreeGetLastLine(const TkText *textPtr);
+extern TkTextLine * TkBTreeNextLine(const TkText *textPtr, TkTextLine *linePtr);
+extern TkTextLine * TkBTreePrevLine(const TkText *textPtr, TkTextLine *linePtr);
+extern unsigned TkBTreeCountLines(const TkTextBTree tree, const TkTextLine *linePtr1,
+ const TkTextLine *linePtr2);
+extern bool TkTextGetIndexFromObj(Tcl_Interp *interp, TkText *textPtr, Tcl_Obj *objPtr,
+ TkTextIndex *indexPtr);
+extern bool TkTextIsDeadPeer(const TkText *textPtr);
+extern bool TkTextIsMark(const TkTextSegment *segPtr);
+extern bool TkTextIsStartEndMarker(const TkTextSegment *segPtr);
+extern bool TkTextIsSpecialMark(const TkTextSegment *segPtr);
+extern bool TkTextIsPrivateMark(const TkTextSegment *segPtr);
+extern bool TkTextIsSpecialOrPrivateMark(const TkTextSegment *segPtr);
+extern bool TkTextIsNormalOrSpecialMark(const TkTextSegment *segPtr);
+extern bool TkTextIsNormalMark(const TkTextSegment *segPtr);
+extern bool TkTextIsStableMark(const TkTextSegment *segPtr);
+extern const TkTextDispChunk *TkTextGetFirstChunkOfNextDispLine(const TkTextDispChunk *chunkPtr);
+extern const TkTextDispChunk *TkTextGetLastChunkOfPrevDispLine(const TkTextDispChunk *chunkPtr);
+extern void TkTextIndexSetEpoch(TkTextIndex *indexPtr, unsigned epoch);
+extern void TkTextIndexSetPeer(TkTextIndex *indexPtr, TkText *textPtr);
+extern void TkTextIndexSetToLastChar2(TkTextIndex *indexPtr, TkTextLine *linePtr);
+extern void TkTextIndexInvalidate(TkTextIndex *indexPtr);
+extern void TkTextIndexMakePersistent(TkTextIndex *indexPtr);
+extern TkTextLine * TkTextIndexGetLine(const TkTextIndex *indexPtr);
+extern TkTextSegment * TkTextIndexGetSegment(const TkTextIndex *indexPtr);
+extern TkSharedText * TkTextIndexGetShared(const TkTextIndex *indexPtr);
+extern bool TkTextIndexSameLines(const TkTextIndex *indexPtr1, const TkTextIndex *indexPtr2);
+extern void TkTextIndexSave(TkTextIndex *indexPtr);
+# if TK_MAJOR_VERSION == 8 && TK_MINOR_VERSION < 7 && TCL_UTF_MAX <= 4
+extern int TkUtfToUniChar(const char *src, int *chPtr);
+# endif
+#endif /* TK_C99_INLINE_SUPPORT */
+
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
- * fill-column: 78
+ * fill-column: 105
* End:
+ * vi:set ts=8 sw=4:
*/