diff options
Diffstat (limited to 'generic/tkText.c')
-rw-r--r-- | generic/tkText.c | 10901 |
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, ¤t); + + if (TkTextIndexCompare(¤t, &start) < 0 || TkTextIndexCompare(&end, ¤t) < 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", ¤t); + if (TkTextIndexCompare(¤t, &start) < 0) { + textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &start); + } else if (TkTextIndexCompare(¤t, &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", ¤t); + + 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(¤t, &start) < 0) { + textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &start); + } else if (TkTextIndexCompare(¤t, &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: */ |