summaryrefslogtreecommitdiffstats
path: root/tk8.6/generic/tkText.c
diff options
context:
space:
mode:
Diffstat (limited to 'tk8.6/generic/tkText.c')
-rw-r--r--tk8.6/generic/tkText.c6909
1 files changed, 0 insertions, 6909 deletions
diff --git a/tk8.6/generic/tkText.c b/tk8.6/generic/tkText.c
deleted file mode 100644
index e0dcc50..0000000
--- a/tk8.6/generic/tkText.c
+++ /dev/null
@@ -1,6909 +0,0 @@
-/*
- * tkText.c --
- *
- * This module provides a big chunk of the implementation of multi-line
- * editable text widgets for Tk. Among other things, it provides the Tcl
- * command interfaces to text widgets. The B-tree representation of text
- * and its actual display are implemented elsewhere.
- *
- * Copyright (c) 1992-1994 The Regents of the University of California.
- * Copyright (c) 1994-1996 Sun Microsystems, Inc.
- * Copyright (c) 1999 by Scriptics Corporation.
- *
- * See the file "license.terms" for information on usage and redistribution of
- * this file, and for a DISCLAIMER OF ALL WARRANTIES.
- */
-
-#include "default.h"
-#include "tkInt.h"
-#include "tkUndo.h"
-
-#if defined(MAC_OSX_TK)
-#define Style TkStyle
-#define DInfo TkDInfo
-#endif
-
-/*
- * For compatibility with Tk 4.0 through 8.4.x, we allow tabs to be
- * mis-specified with non-increasing values. These are converted into tabs
- * which are the equivalent of at least a character width apart.
- */
-
-#if (TK_MAJOR_VERSION < 9)
-#define _TK_ALLOW_DECREASING_TABS
-#endif
-
-#include "tkText.h"
-
-/*
- * Used to avoid having to allocate and deallocate arrays on the fly for
- * commonly used functions. Must be > 0.
- */
-
-#define PIXEL_CLIENTS 5
-
-/*
- * The 'TkTextState' enum in tkText.h is used to define a type for the -state
- * option of the Text widget. These values are used as indices into the string
- * table below.
- */
-
-static const char *const stateStrings[] = {
- "disabled", "normal", NULL
-};
-
-/*
- * The 'TkWrapMode' enum in tkText.h is used to define a type for the -wrap
- * option of the Text widget. These values are used as indices into the string
- * table below.
- */
-
-static const char *const wrapStrings[] = {
- "char", "none", "word", NULL
-};
-
-/*
- * The 'TkTextTabStyle' enum in tkText.h is used to define a type for the
- * -tabstyle option of the Text widget. These values are used as indices into
- * the string table below.
- */
-
-static const char *const tabStyleStrings[] = {
- "tabular", "wordprocessor", NULL
-};
-
-/*
- * The 'TkTextInsertUnfocussed' enum in tkText.h is used to define a type for
- * the -insertunfocussed option of the Text widget. These values are used as
- * indice into the string table below.
- */
-
-static const char *const insertUnfocussedStrings[] = {
- "hollow", "none", "solid", NULL
-};
-
-/*
- * The following functions and custom option type are used to define the
- * "line" option type, and thereby handle the text widget '-startline',
- * '-endline' configuration options which are of that type.
- *
- * We do not need a 'freeProc' because all changes to these two options are
- * handled through the TK_TEXT_LINE_RANGE flag in the optionSpecs list, and
- * the internal storage is just a pointer, which therefore doesn't need
- * freeing.
- */
-
-static int 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,
- int internalOffset);
-static void RestoreLineStartEnd(ClientData clientData,
- Tk_Window tkwin, char *internalPtr,
- char *oldInternalPtr);
-static int ObjectIsEmpty(Tcl_Obj *objPtr);
-
-static const Tk_ObjCustomOption lineOption = {
- "line", /* name */
- SetLineStartEnd, /* setProc */
- GetLineStartEnd, /* getProc */
- RestoreLineStartEnd, /* restoreProc */
- NULL, /* freeProc */
- 0
-};
-
-/*
- * 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),
- 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},
- {TK_OPTION_SYNONYM, "-bd", NULL, NULL,
- NULL, 0, -1, 0, "-borderwidth",
- TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_SYNONYM, "-bg", NULL, NULL,
- NULL, 0, -1, 0, "-background", 0},
- {TK_OPTION_BOOLEAN, "-blockcursor", "blockCursor",
- "BlockCursor", DEF_TEXT_BLOCK_CURSOR, -1,
- Tk_Offset(TkText, insertCursorType), 0, 0, 0},
- {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
- DEF_TEXT_BORDER_WIDTH, -1, Tk_Offset(TkText, borderWidth),
- 0, 0, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_CURSOR, "-cursor", "cursor", "Cursor",
- DEF_TEXT_CURSOR, -1, Tk_Offset(TkText, cursor),
- TK_OPTION_NULL_OK, 0, 0},
- {TK_OPTION_CUSTOM, "-endline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, end), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
- {TK_OPTION_BOOLEAN, "-exportselection", "exportSelection",
- "ExportSelection", DEF_TEXT_EXPORT_SELECTION, -1,
- Tk_Offset(TkText, exportSelection), 0, 0, 0},
- {TK_OPTION_SYNONYM, "-fg", "foreground", NULL,
- NULL, 0, -1, 0, "-foreground", 0},
- {TK_OPTION_FONT, "-font", "font", "Font",
- DEF_TEXT_FONT, -1, Tk_Offset(TkText, tkfont), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_COLOR, "-foreground", "foreground", "Foreground",
- DEF_TEXT_FG, -1, Tk_Offset(TkText, fgColor), 0,
- 0, 0},
- {TK_OPTION_PIXELS, "-height", "height", "Height",
- DEF_TEXT_HEIGHT, -1, Tk_Offset(TkText, height), 0, 0, 0},
- {TK_OPTION_COLOR, "-highlightbackground", "highlightBackground",
- "HighlightBackground", DEF_TEXT_HIGHLIGHT_BG,
- -1, Tk_Offset(TkText, highlightBgColorPtr),
- 0, 0, 0},
- {TK_OPTION_COLOR, "-highlightcolor", "highlightColor", "HighlightColor",
- DEF_TEXT_HIGHLIGHT, -1, Tk_Offset(TkText, highlightColorPtr),
- 0, 0, 0},
- {TK_OPTION_PIXELS, "-highlightthickness", "highlightThickness",
- "HighlightThickness", DEF_TEXT_HIGHLIGHT_WIDTH, -1,
- Tk_Offset(TkText, highlightWidth), 0, 0, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_BORDER, "-inactiveselectbackground","inactiveSelectBackground",
- "Foreground",
- DEF_TEXT_INACTIVE_SELECT_COLOR,
- -1, Tk_Offset(TkText, inactiveSelBorder),
- TK_OPTION_NULL_OK, DEF_TEXT_SELECT_MONO, 0},
- {TK_OPTION_BORDER, "-insertbackground", "insertBackground", "Foreground",
- DEF_TEXT_INSERT_BG,
- -1, Tk_Offset(TkText, insertBorder),
- 0, 0, 0},
- {TK_OPTION_PIXELS, "-insertborderwidth", "insertBorderWidth",
- "BorderWidth", DEF_TEXT_INSERT_BD_COLOR, -1,
- Tk_Offset(TkText, insertBorderWidth), 0,
- (ClientData) DEF_TEXT_INSERT_BD_MONO, 0},
- {TK_OPTION_INT, "-insertofftime", "insertOffTime", "OffTime",
- DEF_TEXT_INSERT_OFF_TIME, -1, Tk_Offset(TkText, insertOffTime),
- 0, 0, 0},
- {TK_OPTION_INT, "-insertontime", "insertOnTime", "OnTime",
- DEF_TEXT_INSERT_ON_TIME, -1, Tk_Offset(TkText, insertOnTime),
- 0, 0, 0},
- {TK_OPTION_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},
- {TK_OPTION_INT, "-maxundo", "maxUndo", "MaxUndo",
- DEF_TEXT_MAX_UNDO, -1, Tk_Offset(TkText, maxUndo),
- 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},
- {TK_OPTION_PIXELS, "-pady", "padY", "Pad",
- DEF_TEXT_PADY, -1, Tk_Offset(TkText, padY), 0, 0, 0},
- {TK_OPTION_RELIEF, "-relief", "relief", "Relief",
- DEF_TEXT_RELIEF, -1, Tk_Offset(TkText, relief), 0, 0, 0},
- {TK_OPTION_BORDER, "-selectbackground", "selectBackground", "Foreground",
- DEF_TEXT_SELECT_COLOR, -1, Tk_Offset(TkText, selBorder),
- 0, 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_COLOR, "-selectforeground", "selectForeground", "Background",
- DEF_TEXT_SELECT_FG_COLOR, -1, Tk_Offset(TkText, selFgColorPtr),
- 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_PIXELS, "-spacing1", "spacing1", "Spacing",
- 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 },
- {TK_OPTION_PIXELS, "-spacing3", "spacing3", "Spacing",
- DEF_TEXT_SPACING3, -1, Tk_Offset(TkText, spacing3),
- 0, 0 , TK_TEXT_LINE_GEOMETRY },
- {TK_OPTION_CUSTOM, "-startline", NULL, NULL,
- NULL, -1, Tk_Offset(TkText, start), TK_OPTION_NULL_OK,
- &lineOption, TK_TEXT_LINE_RANGE},
- {TK_OPTION_STRING_TABLE, "-state", "state", "State",
- DEF_TEXT_STATE, -1, Tk_Offset(TkText, state),
- 0, stateStrings, 0},
- {TK_OPTION_STRING, "-tabs", "tabs", "Tabs",
- DEF_TEXT_TABS, Tk_Offset(TkText, tabOptionPtr), -1,
- TK_OPTION_NULL_OK, 0, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_STRING_TABLE, "-tabstyle", "tabStyle", "TabStyle",
- DEF_TEXT_TABSTYLE, -1, Tk_Offset(TkText, tabStyle),
- 0, tabStyleStrings, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_STRING, "-takefocus", "takeFocus", "TakeFocus",
- DEF_TEXT_TAKE_FOCUS, -1, Tk_Offset(TkText, takeFocus),
- TK_OPTION_NULL_OK, 0, 0},
- {TK_OPTION_BOOLEAN, "-undo", "undo", "Undo",
- DEF_TEXT_UNDO, -1, Tk_Offset(TkText, undo),
- TK_OPTION_DONT_SET_DEFAULT, 0 , 0},
- {TK_OPTION_INT, "-width", "width", "Width",
- DEF_TEXT_WIDTH, -1, Tk_Offset(TkText, width), 0, 0,
- TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_STRING_TABLE, "-wrap", "wrap", "Wrap",
- DEF_TEXT_WRAP, -1, Tk_Offset(TkText, wrapMode),
- 0, wrapStrings, TK_TEXT_LINE_GEOMETRY},
- {TK_OPTION_STRING, "-xscrollcommand", "xScrollCommand", "ScrollCommand",
- DEF_TEXT_XSCROLL_COMMAND, -1, Tk_Offset(TkText, xScrollCmd),
- TK_OPTION_NULL_OK, 0, 0},
- {TK_OPTION_STRING, "-yscrollcommand", "yScrollCommand", "ScrollCommand",
- DEF_TEXT_YSCROLL_COMMAND, -1, Tk_Offset(TkText, yScrollCmd),
- TK_OPTION_NULL_OK, 0, 0},
- {TK_OPTION_END, NULL, NULL, NULL, 0, 0, 0, 0, 0, 0}
-};
-
-/*
- * These three typedefs, the structure and the SearchPerform, SearchCore
- * functions below are used for line-based searches of the text widget, and,
- * in particular, to handle multi-line matching even though the text widget is
- * a single-line based data structure. They are completely abstracted away
- * from the Text widget internals, however, so could easily be re-used with
- * any line-based entity to provide multi-line matching.
- *
- * We have abstracted this code away from the text widget to try to keep Tk as
- * modular as possible.
- */
-
-struct SearchSpec; /* Forward declaration. */
-
-typedef ClientData SearchAddLineProc(int lineNum,
- struct SearchSpec *searchSpecPtr,
- Tcl_Obj *theLine, int *lenPtr,
- int *extraLinesPtr);
-typedef int SearchMatchProc(int lineNum,
- struct SearchSpec *searchSpecPtr,
- ClientData clientData, Tcl_Obj *theLine,
- int matchOffset, int matchLength);
-typedef int SearchLineIndexProc(Tcl_Interp *interp,
- Tcl_Obj *objPtr, struct SearchSpec *searchSpecPtr,
- int *linePosPtr, int *offsetPosPtr);
-
-typedef struct SearchSpec {
- int exact; /* Whether search is exact or regexp. */
- int noCase; /* Case-insenstivive? */
- int noLineStop; /* If not set, a regexp search will use the
- * TCL_REG_NLSTOP flag. */
- int overlap; /* If set, results from multiple searches
- * (-all) are allowed to overlap each
- * other. */
- int strictLimits; /* If set, matches must be completely inside
- * the from,to range. Otherwise the limits
- * only apply to the start of each match. */
- int all; /* Whether all or the first match should be
- * reported. */
- int startLine; /* First line to examine. */
- int startOffset; /* Index in first line to start at. */
- int stopLine; /* Last line to examine, or -1 when we search
- * all available text. */
- int stopOffset; /* Index to stop at, provided stopLine is not
- * -1. */
- int numLines; /* Total lines which are available. */
- int backwards; /* Searching forwards or backwards. */
- Tcl_Obj *varPtr; /* If non-NULL, store length(s) of match(es)
- * in this variable. */
- Tcl_Obj *countPtr; /* Keeps track of currently found lengths. */
- Tcl_Obj *resPtr; /* Keeps track of currently found locations */
- int searchElide; /* Search in hidden text as well. */
- SearchAddLineProc *addLineProc;
- /* Function to call when we need to add
- * another line to the search string so far */
- SearchMatchProc *foundMatchProc;
- /* Function to call when we have found a
- * match. */
- SearchLineIndexProc *lineIndexProc;
- /* Function to call when we have found a
- * match. */
- ClientData clientData; /* Information about structure being searched,
- * in this case a text widget. */
-} SearchSpec;
-
-/*
- * The text-widget-independent functions which actually perform the search,
- * 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);
-
-/*
- * Boolean variable indicating whether or not special debugging code should be
- * executed.
- */
-
-int tkTextDebug = 0;
-
-/*
- * 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 void DestroyText(TkText *textPtr);
-static int InsertChars(TkSharedText *sharedTextPtr,
- TkText *textPtr, TkTextIndex *indexPtr,
- Tcl_Obj *stringPtr, int viewUpdate);
-static void TextBlinkProc(ClientData clientData);
-static void TextCmdDeletedProc(ClientData clientData);
-static int CreateWidget(TkSharedText *sharedPtr, Tk_Window tkwin,
- Tcl_Interp *interp, const TkText *parent,
- int objc, Tcl_Obj *const objv[]);
-static void TextEventProc(ClientData clientData,
- XEvent *eventPtr);
-static int TextFetchSelection(ClientData clientData, int offset,
- char *buffer, int maxBytes);
-static int TextIndexSortProc(const void *first,
- const void *second);
-static int TextInsertCmd(TkSharedText *sharedTextPtr,
- TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[],
- const TkTextIndex *indexPtr, int viewUpdate);
-static int TextReplaceCmd(TkText *textPtr, Tcl_Interp *interp,
- const TkTextIndex *indexFromPtr,
- const TkTextIndex *indexToPtr,
- int objc, Tcl_Obj *const objv[], int viewUpdate);
-static int TextSearchCmd(TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static int TextEditCmd(TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static int TextWidgetObjCmd(ClientData clientData,
- Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static int SharedTextObjCmd(ClientData clientData,
- Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static void TextWorldChangedCallback(ClientData instanceData);
-static void TextWorldChanged(TkText *textPtr, int mask);
-static int TextDumpCmd(TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static int DumpLine(Tcl_Interp *interp, TkText *textPtr,
- int what, TkTextLine *linePtr, int start, int end,
- int lineno, Tcl_Obj *command);
-static int DumpSegment(TkText *textPtr, Tcl_Interp *interp,
- const char *key, const char *value,
- Tcl_Obj *command, const TkTextIndex *index,
- int what);
-static int TextEditUndo(TkText *textPtr);
-static int TextEditRedo(TkText *textPtr);
-static Tcl_Obj * TextGetText(const TkText *textPtr,
- const TkTextIndex *index1,
- const TkTextIndex *index2, int visibleOnly);
-static void GenerateModifiedEvent(TkText *textPtr);
-static void GenerateUndoStackEvent(TkText *textPtr);
-static void UpdateDirtyFlag(TkSharedText *sharedPtr);
-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 int TextPeerCmd(TkText *textPtr, Tcl_Interp *interp,
- int objc, Tcl_Obj *const objv[]);
-static TkUndoProc TextUndoRedoCallback;
-
-/*
- * Declarations of the three search procs required by the multi-line search
- * routines.
- */
-
-static SearchMatchProc TextSearchFoundMatch;
-static SearchAddLineProc TextSearchAddNextLine;
-static SearchLineIndexProc TextSearchGetLineIndex;
-
-/*
- * The structure below defines text class behavior by means of functions that
- * can be invoked from generic window code.
- */
-
-static const Tk_ClassProcs textClass = {
- sizeof(Tk_ClassProcs), /* size */
- TextWorldChangedCallback, /* worldChangedProc */
- NULL, /* createProc */
- NULL /* modalProc */
-};
-
-/*
- *--------------------------------------------------------------
- *
- * Tk_TextObjCmd --
- *
- * This function is invoked to process the "text" Tcl command. See the
- * user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- *--------------------------------------------------------------
- */
-
-int
-Tk_TextObjCmd(
- ClientData clientData, /* Main window associated with interpreter. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- Tk_Window tkwin = clientData;
-
- if (objc < 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "pathName ?-option value ...?");
- return TCL_ERROR;
- }
-
- return CreateWidget(NULL, tkwin, interp, NULL, objc, objv);
-}
-
-/*
- *--------------------------------------------------------------
- *
- * CreateWidget --
- *
- * This function is invoked to process the "text" Tcl command, (when
- * called by Tk_TextObjCmd) and the "$text peer create" text widget
- * sub-command (called from TextPeerCmd).
- *
- * See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result, places the name of the widget created into the
- * interp's result.
- *
- * Side effects:
- * See the user documentation.
- *
- *--------------------------------------------------------------
- */
-
-static int
-CreateWidget(
- TkSharedText *sharedPtr, /* Shared widget info, or NULL. */
- Tk_Window tkwin, /* Main window associated with interpreter. */
- Tcl_Interp *interp, /* Current interpreter. */
- const TkText *parent, /* If non-NULL then take default start, end
- * from this parent. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- register TkText *textPtr;
- Tk_OptionTable optionTable;
- TkTextIndex startIndex;
- Tk_Window newWin;
-
- /*
- * Create the window.
- */
-
- newWin = Tk_CreateWindowFromPath(interp, tkwin, Tcl_GetString(objv[1]),
- NULL);
- if (newWin == NULL) {
- return TCL_ERROR;
- }
-
- /*
- * Create the text widget and initialize everything to zero, then set the
- * necessary initial (non-NULL) values. 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->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;
- }
-
- /*
- * Add the new widget to the shared list.
- */
-
- textPtr->sharedTextPtr = sharedPtr;
- sharedPtr->refCount++;
- textPtr->next = sharedPtr->peers;
- sharedPtr->peers = textPtr;
-
- /*
- * This refCount will be held until DestroyText is called. Note also that
- * the later call to 'TkTextCreateDInfo' will add more refCounts.
- */
-
- textPtr->refCount = 1;
-
- /*
- * Specify start and end lines in the B-tree. The default is the same as
- * the parent, but this can be adjusted to display more or less if the
- * start, end where given as configuration options.
- */
-
- if (parent != NULL) {
- textPtr->start = parent->start;
- textPtr->end = parent->end;
- } else {
- textPtr->start = NULL;
- textPtr->end = NULL;
- }
-
- 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);
-
- /*
- * This will add refCounts to textPtr.
- */
-
- TkTextCreateDInfo(textPtr);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &startIndex);
- TkTextSetYView(textPtr, &startIndex, 0);
- textPtr->exportSelection = 1;
- textPtr->pickEvent.type = LeaveNotify;
- textPtr->undo = textPtr->sharedTextPtr->undo;
- textPtr->maxUndo = textPtr->sharedTextPtr->maxUndo;
- textPtr->autoSeparators = textPtr->sharedTextPtr->autoSeparators;
- textPtr->tabOptionPtr = NULL;
-
- /*
- * Create the "sel" tag and the "current" and "insert" marks.
- */
-
- textPtr->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);
-
- /*
- * Create the option table for this widget class. If it has already been
- * created, the cached pointer will be returned.
- */
-
- optionTable = Tk_CreateOptionTable(interp, optionSpecs);
-
- Tk_SetClass(textPtr->tkwin, "Text");
- Tk_SetClassProcs(textPtr->tkwin, &textClass, textPtr);
- textPtr->optionTable = optionTable;
-
- Tk_CreateEventHandler(textPtr->tkwin,
- 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);
-
- if (Tk_InitOptions(interp, (char *) textPtr, optionTable, textPtr->tkwin)
- != TCL_OK) {
- Tk_DestroyWindow(textPtr->tkwin);
- return TCL_ERROR;
- }
- if (ConfigureText(interp, textPtr, objc-2, objv+2) != TCL_OK) {
- Tk_DestroyWindow(textPtr->tkwin);
- return TCL_ERROR;
- }
-
- Tcl_SetObjResult(interp, TkNewWindowObj(textPtr->tkwin));
- return TCL_OK;
-}
-
-/*
- *--------------------------------------------------------------
- *
- * TextWidgetObjCmd --
- *
- * This function is invoked to process the Tcl command that corresponds
- * to a text widget. See the user documentation for details on what it
- * does.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- *--------------------------------------------------------------
- */
-
-static int
-TextWidgetObjCmd(
- ClientData clientData, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- register TkText *textPtr = clientData;
- int result = TCL_OK;
- int index;
-
- 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
- };
- 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
- };
-
- if (objc < 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
- return TCL_ERROR;
- }
-
- if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
- return TCL_ERROR;
- }
- textPtr->refCount++;
-
- switch ((enum options) index) {
- case TEXT_BBOX: {
- int x, y, width, height;
- const TkTextIndex *indexPtr;
-
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index");
- result = TCL_ERROR;
- goto done;
- }
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- if (TkTextIndexBbox(textPtr, indexPtr, &x, &y, &width, &height,
- NULL) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
-
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(width));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(height));
-
- Tcl_SetObjResult(interp, listObj);
- }
- break;
- }
- case TEXT_CGET:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "option");
- result = TCL_ERROR;
- goto done;
- } else {
- Tcl_Obj *objPtr = Tk_GetOptionValue(interp, (char *) textPtr,
- textPtr->optionTable, objv[2], textPtr->tkwin);
-
- if (objPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- Tcl_SetObjResult(interp, objPtr);
- result = TCL_OK;
- }
- break;
- case TEXT_COMPARE: {
- int relation, value;
- const char *p;
- const TkTextIndex *index1Ptr, *index2Ptr;
-
- 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) {
- 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);
- } else {
- goto compareError;
- }
- 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);
-
- if (objPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- Tcl_SetObjResult(interp, objPtr);
- } else {
- result = ConfigureText(interp, textPtr, objc-2, objv+2);
- }
- break;
- case TEXT_COUNT: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
- int i, found = 0, update = 0;
- Tcl_Obj *objPtr = NULL;
-
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-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) {
- result = TCL_ERROR;
- goto done;
- }
-
- for (i = 2; i < objc-2; i++) {
- int value, length;
- const char *option = Tcl_GetString(objv[i]);
- char c;
-
- length = objv[i]->length;
- 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;
- }
-
- if (compare > 0) {
- const TkTextIndex *tmpPtr = indexFromPtr;
-
- indexFromPtr = indexToPtr;
- indexToPtr = tmpPtr;
- }
-
- lastPtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree,textPtr));
- fromPtr = indexFromPtr->linePtr;
- if (fromPtr == lastPtr) {
- goto countDone;
- }
-
- /*
- * Caution: we must NEVER call TkTextUpdateOneLine with the
- * last artificial line in the widget.
- */
-
- index = *indexFromPtr;
- index.byteIndex = 0;
-
- /*
- * We're going to count up all display lines in the logical
- * line of 'indexFromPtr' up to, but not including the logical
- * line of 'indexToPtr' (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);
- }
-
- 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);
- }
- 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--;
-
- }
- 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++;
- }
- }
-
- if (compare > 0) {
- value = -value;
- }
- } else if (c == 'i'
- && !strncmp("-indices", option, (unsigned) length)) {
- value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_INDICES);
- } else if (c == 'l'
- && !strncmp("-lines", option, (unsigned) length)) {
- value = TkBTreeLinesTo(textPtr, indexToPtr->linePtr)
- - TkBTreeLinesTo(textPtr, indexFromPtr->linePtr);
- } else if (c == 'u'
- && !strncmp("-update", option, (unsigned) length)) {
- update = 1;
- continue;
- } else if (c == 'x'
- && !strncmp("-xpixels", option, (unsigned) length)) {
- int x1, x2;
- TkTextIndex index;
-
- index = *indexFromPtr;
- TkTextFindDisplayLineEnd(textPtr, &index, 0, &x1);
- index = *indexToPtr;
- TkTextFindDisplayLineEnd(textPtr, &index, 0, &x2);
- value = x2 - x1;
- } else if (c == 'y'
- && !strncmp("-ypixels", option, (unsigned) length)) {
- if (update) {
- TkTextUpdateLineMetrics(textPtr,
- TkBTreeLinesTo(textPtr, indexFromPtr->linePtr),
- TkBTreeLinesTo(textPtr, indexToPtr->linePtr), -1);
- }
- value = TkTextIndexYPixels(textPtr, indexToPtr)
- - TkTextIndexYPixels(textPtr, indexFromPtr);
- } else {
- goto badOption;
- }
-
- countDone:
- found++;
- if (found == 1) {
- Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
- } else {
- if (found == 2) {
- /*
- * Move the first item we put into the result into the
- * first element of the list object.
- */
-
- objPtr = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, objPtr,
- Tcl_GetObjResult(interp));
- }
- Tcl_ListObjAppendElement(NULL, objPtr, Tcl_NewIntObj(value));
- }
- }
-
- if (found == 0) {
- /*
- * Use the default '-indices'.
- */
-
- int value = CountIndices(textPtr, indexFromPtr, indexToPtr,
- COUNT_INDICES);
-
- Tcl_SetObjResult(interp, Tcl_NewIntObj(value));
- } else if (found > 1) {
- Tcl_SetObjResult(interp, objPtr);
- }
- break;
-
- 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])));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_OPTION", NULL);
- result = TCL_ERROR;
- goto done;
- }
- case TEXT_DEBUG:
- if (objc > 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "boolean");
- result = TCL_ERROR;
- goto done;
- }
- if (objc == 2) {
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(tkBTreeDebug));
- } else {
- if (Tcl_GetBooleanFromObj(interp, objv[2],
- &tkBTreeDebug) != TCL_OK) {
- result = TCL_ERROR;
- goto done;
- }
- tkTextDebug = tkBTreeDebug;
- }
- break;
- case TEXT_DELETE:
- if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
- result = TCL_ERROR;
- goto done;
- }
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
-
- const TkTextIndex *indexPtr1, *indexPtr2;
-
- /*
- * Parse the starting and stopping indices.
- */
-
- indexPtr1 = TkTextGetIndexFromObj(textPtr->interp, textPtr,
- objv[2]);
- if (indexPtr1 == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- if (objc == 4) {
- indexPtr2 = TkTextGetIndexFromObj(textPtr->interp,
- textPtr, objv[3]);
- if (indexPtr2 == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- } else {
- indexPtr2 = NULL;
- }
- DeleteIndexRange(NULL, textPtr, indexPtr1, indexPtr2, 1);
- } else {
- /*
- * Multi-index pair case requires that we prevalidate the
- * indices and sort from last to first so that deletes occur
- * in the exact (unshifted) text. It also needs to handle
- * partial and fully overlapping ranges. We have to do this
- * with multiple passes.
- */
-
- TkTextIndex *indices, *ixStart, *ixEnd, *lastStart;
- char *useIdx;
- int i;
-
- objc -= 2;
- objv += 2;
- indices = ckalloc((objc + 1) * sizeof(TkTextIndex));
-
- /*
- * First pass verifies that all indices are valid.
- */
-
- for (i = 0; i < objc; i++) {
- const TkTextIndex *indexPtr =
- TkTextGetIndexFromObj(interp, textPtr, objv[i]);
-
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- ckfree(indices);
- goto done;
- }
- indices[i] = *indexPtr;
- }
-
- /*
- * 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);
-
- /*
- * 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;
-
- /*
- * 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 (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.
- */
-
- 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;
- }
-
- /*
- * 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.
- */
-
- DeleteIndexRange(NULL, textPtr, &indices[i],
- &indices[i+1], 1);
- }
- }
- ckfree(indices);
- }
- }
- break;
- case TEXT_DLINEINFO: {
- int x, y, width, height, base;
- const TkTextIndex *indexPtr;
-
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index");
- result = TCL_ERROR;
- goto done;
- }
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- if (TkTextDLineInfo(textPtr, indexPtr, &x, &y, &width, &height,
- &base) == 0) {
- Tcl_Obj *listObj = Tcl_NewListObj(0, NULL);
-
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(x));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(y));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(width));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(height));
- Tcl_ListObjAppendElement(interp, listObj, Tcl_NewIntObj(base));
-
- Tcl_SetObjResult(interp, listObj);
- }
- break;
- }
- case TEXT_DUMP:
- result = TextDumpCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_EDIT:
- result = TextEditCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_GET: {
- Tcl_Obj *objPtr = NULL;
- int i, found = 0, visible = 0;
- const char *name;
- int length;
-
- if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "?-displaychars? ?--? index1 ?index2 ...?");
- result = TCL_ERROR;
- goto done;
- }
-
- /*
- * Simple, restrictive argument parsing. The only options are -- and
- * -displaychars (or any unique prefix).
- */
-
- i = 2;
- if (objc > 3) {
- name = Tcl_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;
- }
- if ((i < objc-1) && (length == 2) && !strcmp("--", name)) {
- i++;
- }
- }
- }
-
- for (; i < objc; i += 2) {
- const TkTextIndex *index1Ptr, *index2Ptr;
- TkTextIndex index2;
-
- index1Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i]);
- if (index1Ptr == NULL) {
- if (objPtr) {
- Tcl_DecrRefCount(objPtr);
- }
- result = TCL_ERROR;
- goto done;
- }
-
- if (i+1 == objc) {
- TkTextIndexForwChars(NULL, index1Ptr, 1, &index2,
- COUNT_INDICES);
- index2Ptr = &index2;
- } else {
- index2Ptr = TkTextGetIndexFromObj(interp, textPtr, objv[i+1]);
- if (index2Ptr == NULL) {
- if (objPtr) {
- Tcl_DecrRefCount(objPtr);
- }
- result = TCL_ERROR;
- goto done;
- }
- }
-
- if (TkTextIndexCmp(index1Ptr, index2Ptr) < 0) {
- /*
- * We want to move the text we get from the window into the
- * result, but since this could in principle be a megabyte or
- * more, we want to do it efficiently!
- */
-
- Tcl_Obj *get = TextGetText(textPtr, index1Ptr, index2Ptr,
- visible);
-
- 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.
- */
-
- objPtr = Tcl_NewObj();
- Tcl_ListObjAppendElement(NULL, objPtr,
- Tcl_GetObjResult(interp));
- }
- Tcl_ListObjAppendElement(NULL, objPtr, get);
- }
- }
- }
- if (found > 1) {
- Tcl_SetObjResult(interp, objPtr);
- }
- break;
- }
- case TEXT_IMAGE:
- result = TkTextImageCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_INDEX: {
- const TkTextIndex *indexPtr;
-
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index");
- result = TCL_ERROR;
- goto done;
- }
-
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- Tcl_SetObjResult(interp, TkTextNewIndexObj(textPtr, indexPtr));
- break;
- }
- case TEXT_INSERT: {
- const TkTextIndex *indexPtr;
-
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index chars ?tagList chars tagList ...?");
- result = TCL_ERROR;
- goto done;
- }
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- result = TextInsertCmd(NULL, textPtr, interp, objc-3, objv+3,
- indexPtr, 1);
- }
- break;
- }
- case TEXT_MARK:
- result = TkTextMarkCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_PEER:
- result = TextPeerCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_PENDINGSYNC: {
- if (objc != 2) {
- Tcl_WrongNumArgs(interp, 2, objv, NULL);
- result = TCL_ERROR;
- goto done;
- }
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(TkTextPendingsync(textPtr)));
- break;
- }
- case TEXT_REPLACE: {
- const TkTextIndex *indexFromPtr, *indexToPtr;
-
- if (objc < 5) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index1 index2 chars ?tagList chars tagList ...?");
- result = TCL_ERROR;
- goto done;
- }
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, objv[2]);
- if (indexFromPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, objv[3]);
- if (indexToPtr == NULL) {
- result = TCL_ERROR;
- goto done;
- }
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) > 0) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "index \"%s\" before \"%s\" in the text",
- Tcl_GetString(objv[3]), Tcl_GetString(objv[2])));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
- result = TCL_ERROR;
- goto done;
- }
- if (textPtr->state == TK_TEXT_STATE_NORMAL) {
- int lineNum, byteIndex;
- TkTextIndex index;
-
- /*
- * The 'replace' operation is quite complex to do correctly,
- * because we want a number of criteria to hold:
- *
- * 1. The insertion point shouldn't move, unless it is within the
- * deleted range. In this case it should end up after the new
- * text.
- *
- * 2. The window should not change the text it shows - should not
- * scroll vertically - unless the result of the replace is
- * that the insertion position which used to be on-screen is
- * now off-screen.
- */
-
- byteIndex = textPtr->topIndex.byteIndex;
- lineNum = TkBTreeLinesTo(textPtr, textPtr->topIndex.linePtr);
-
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- if ((TkTextIndexCmp(indexFromPtr, &index) < 0)
- && (TkTextIndexCmp(indexToPtr, &index) > 0)) {
- /*
- * The insertion point is inside the range to be replaced, so
- * we have to do some calculations to ensure it doesn't move
- * unnecessarily.
- */
-
- int deleteInsertOffset, insertLength, j;
-
- insertLength = 0;
- for (j = 4; j < objc; j += 2) {
- insertLength += Tcl_GetCharLength(objv[j]);
- }
-
- /*
- * Calculate 'deleteInsertOffset' as an offset we will apply
- * to the insertion point after this operation.
- */
-
- deleteInsertOffset = CountIndices(textPtr, indexFromPtr,
- &index, COUNT_CHARS);
- if (deleteInsertOffset > insertLength) {
- deleteInsertOffset = insertLength;
- }
-
- result = TextReplaceCmd(textPtr, interp, indexFromPtr,
- indexToPtr, objc, objv, 0);
-
- if (result == TCL_OK) {
- /*
- * Move the insertion position to the correct place.
- */
-
- 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);
- }
- if (result == TCL_OK) {
- /*
- * Now ensure the top-line is in the right place.
- */
-
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineNum, byteIndex, &index);
- TkTextSetYView(textPtr, &index, TK_TEXT_NOPIXELADJUST);
- }
- }
- break;
- }
- case TEXT_SCAN:
- result = TkTextScanCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_SEARCH:
- result = TextSearchCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_SEE:
- result = TkTextSeeCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_SYNC: {
- if (objc == 4) {
- Tcl_Obj *cmd = objv[3];
- const char *option = Tcl_GetString(objv[2]);
- if (strncmp(option, "-command", objv[2]->length)) {
- 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?");
- result = TCL_ERROR;
- goto done;
- }
- if (textPtr->afterSyncCmd) {
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- }
- 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_WINDOW:
- result = TkTextWindowCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_XVIEW:
- result = TkTextXviewCmd(textPtr, interp, objc, objv);
- break;
- case TEXT_YVIEW:
- result = TkTextYviewCmd(textPtr, interp, objc, objv);
- break;
- }
-
- done:
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
- }
- return result;
-}
-
-/*
- *--------------------------------------------------------------
- *
- * SharedTextObjCmd --
- *
- * This function is invoked to process commands on the shared portion of
- * a text widget. Currently it is not actually exported as a Tcl command,
- * and is only used internally to process parts of undo/redo scripts.
- * See the user documentation for 'text' for details on what it does -
- * the only subcommands it currently supports are 'insert' and 'delete'.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation for "text".
- *
- *--------------------------------------------------------------
- */
-
-static int
-SharedTextObjCmd(
- ClientData clientData, /* Information about shared test B-tree. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- register TkSharedText *sharedPtr = clientData;
- int result = TCL_OK;
- int index;
-
- static const char *const optionStrings[] = {
- "delete", "insert", NULL
- };
- enum options {
- TEXT_DELETE, TEXT_INSERT
- };
-
- if (objc < 2) {
- Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
- return TCL_ERROR;
- }
-
- if (Tcl_GetIndexFromObjStruct(interp, objv[1], optionStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
- return TCL_ERROR;
- }
-
- switch ((enum options) index) {
- case TEXT_DELETE:
- if (objc < 3) {
- Tcl_WrongNumArgs(interp, 2, objv, "index1 ?index2 ...?");
- return TCL_ERROR;
- }
- if (objc < 5) {
- /*
- * Simple case requires no predetermination of indices.
- */
-
- TkTextIndex index1;
-
- /*
- * Parse the starting and stopping indices.
- */
-
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2],
- &index1);
- if (result != TCL_OK) {
- return result;
- }
- if (objc == 4) {
- TkTextIndex index2;
-
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[3],
- &index2);
- if (result != TCL_OK) {
- return result;
- }
- DeleteIndexRange(sharedPtr, NULL, &index1, &index2, 1);
- } else {
- DeleteIndexRange(sharedPtr, NULL, &index1, NULL, 1);
- }
- return TCL_OK;
- } else {
- /* Too many arguments */
- return TCL_ERROR;
- }
- break;
- case TEXT_INSERT: {
- TkTextIndex index1;
-
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 2, objv,
- "index chars ?tagList chars tagList ...?");
- return TCL_ERROR;
- }
- result = TkTextSharedGetObjIndex(interp, sharedPtr, objv[2],
- &index1);
- if (result != TCL_OK) {
- return result;
- }
- return TextInsertCmd(sharedPtr, NULL, interp, objc-3, objv+3, &index1,
- 1);
- }
- default:
- return TCL_OK;
- }
-}
-
-/*
- *--------------------------------------------------------------
- *
- * TextPeerCmd --
- *
- * This function is invoked to process the "text peer" Tcl command. See
- * the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- *--------------------------------------------------------------
- */
-
-static int
-TextPeerCmd(
- TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- Tk_Window tkwin = textPtr->tkwin;
- int index;
-
- static const char *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;
- }
-
- switch ((enum peerOptions) index) {
- case PEER_CREATE:
- if (objc < 4) {
- Tcl_WrongNumArgs(interp, 3, objv, "pathName ?-option value ...?");
- return TCL_ERROR;
- }
- return CreateWidget(textPtr->sharedTextPtr, tkwin, interp, textPtr,
- objc-2, objv+2);
- case PEER_NAMES: {
- TkText *tPtr = textPtr->sharedTextPtr->peers;
- Tcl_Obj *peersObj;
-
- if (objc > 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
- return TCL_ERROR;
- }
- peersObj = Tcl_NewObj();
- while (tPtr != NULL) {
- if (tPtr != textPtr) {
- Tcl_ListObjAppendElement(NULL, peersObj,
- TkNewWindowObj(tPtr->tkwin));
- }
- tPtr = tPtr->next;
- }
- Tcl_SetObjResult(interp, peersObj);
- }
- }
-
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextReplaceCmd --
- *
- * This function is invoked to process part of the "replace" widget
- * command for text widgets.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- * If 'viewUpdate' is false, then textPtr->topIndex may no longer be a
- * valid index after this function returns. The caller is responsible for
- * ensuring a correct index is in place.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextReplaceCmd(
- TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- const TkTextIndex *indexFromPtr,
- /* Index from which to replace. */
- const TkTextIndex *indexToPtr,
- /* Index to which to replace. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[], /* Argument objects. */
- int viewUpdate) /* Update vertical view if set. */
-{
- /*
- * Perform the deletion and insertion, but ensure no undo-separator is
- * placed between the two operations. Since we are using the helper
- * functions 'DeleteIndexRange' and 'TextInsertCmd' we have to pretend
- * that the autoSeparators setting is off, so that we don't get an
- * undo-separator between the delete and insert.
- */
-
- int origAutoSep = textPtr->sharedTextPtr->autoSeparators;
- int result, lineNumber;
- TkTextIndex indexTmp;
-
- if (textPtr->sharedTextPtr->undo) {
- textPtr->sharedTextPtr->autoSeparators = 0;
- if (origAutoSep &&
- textPtr->sharedTextPtr->lastEditMode!=TK_TEXT_EDIT_REPLACE) {
- TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
- }
- }
-
- /*
- * Must save and restore line in indexFromPtr based on line number; can't
- * keep the line itself as that might be eliminated/invalidated when
- * deleting the range. [Bug 1602537]
- */
-
- indexTmp = *indexFromPtr;
- lineNumber = TkBTreeLinesTo(textPtr, indexFromPtr->linePtr);
- DeleteIndexRange(NULL, textPtr, indexFromPtr, indexToPtr, viewUpdate);
- indexTmp.linePtr = TkBTreeFindLine(indexTmp.tree, textPtr, lineNumber);
- result = TextInsertCmd(NULL, textPtr, interp, objc-4, objv+4,
- &indexTmp, viewUpdate);
-
- if (textPtr->sharedTextPtr->undo) {
- textPtr->sharedTextPtr->lastEditMode = TK_TEXT_EDIT_REPLACE;
- textPtr->sharedTextPtr->autoSeparators = origAutoSep;
- }
-
- return result;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextIndexSortProc --
- *
- * This function is called by qsort when sorting an array of indices in
- * *decreasing* order (last to first).
- *
- * Results:
- * The return value is -1 if the first argument should be before the
- * second element, 0 if it's equivalent, and 1 if it should be after the
- * second element.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextIndexSortProc(
- const void *first, /* Elements to be compared. */
- const void *second)
-{
- TkTextIndex *pair1 = (TkTextIndex *) first;
- TkTextIndex *pair2 = (TkTextIndex *) second;
- int cmp = TkTextIndexCmp(&pair1[1], &pair2[1]);
-
- if (cmp == 0) {
- /*
- * If the first indices were equal, we want the second index of the
- * pair also to be the greater. Use pointer magic to access the second
- * index pair.
- */
-
- cmp = TkTextIndexCmp(&pair1[0], &pair2[0]);
- }
- if (cmp > 0) {
- return -1;
- } else if (cmp < 0) {
- return 1;
- }
- return 0;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * DestroyText --
- *
- * This function is invoked when we receive a destroy event to clean up
- * the internal structure of a text widget. We will free up most of the
- * internal structure and delete the associated Tcl command. If there are
- * no outstanding references to the widget, we also free up the textPtr
- * itself.
- *
- * The widget has already been flagged as deleted.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Either everything or almost everything associated with the text is
- * freed up.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-DestroyText(
- TkText *textPtr) /* Info about text widget. */
-{
- Tcl_HashSearch search;
- Tcl_HashEntry *hPtr;
- TkTextTag *tagPtr;
- TkSharedText *sharedTextPtr = textPtr->sharedTextPtr;
-
- /*
- * 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;
-
- /*
- * Remove ourselves from the peer list.
- */
-
- if (sharedTextPtr->peers == textPtr) {
- sharedTextPtr->peers = textPtr->next;
- } else {
- TkText *nextPtr = sharedTextPtr->peers;
- while (nextPtr != NULL) {
- if (nextPtr->next == textPtr) {
- nextPtr->next = textPtr->next;
- break;
- }
- nextPtr = nextPtr->next;
- }
- }
-
- /*
- * Always clean up the widget-specific tags first. Common tags (i.e. most)
- * will only be cleaned up when the shared structure is cleaned up.
- *
- * We also need to clean up widget-specific marks ('insert', 'current'),
- * since otherwise marks will never disappear from the B-tree.
- */
-
- TkTextDeleteTag(textPtr, textPtr->selTagPtr);
- TkBTreeUnlinkSegment(textPtr->insertMarkPtr,
- textPtr->insertMarkPtr->body.mark.linePtr);
- ckfree(textPtr->insertMarkPtr);
- TkBTreeUnlinkSegment(textPtr->currentMarkPtr,
- textPtr->currentMarkPtr->body.mark.linePtr);
- ckfree(textPtr->currentMarkPtr);
-
- /*
- * Now we've cleaned up everything of relevance to us in the B-tree, so we
- * disassociate outselves from it.
- *
- * When the refCount reaches zero, it's time to clean up the shared
- * portion of the text widget.
- */
-
- if (sharedTextPtr->refCount-- > 1) {
- TkBTreeRemoveClient(sharedTextPtr->tree, textPtr);
-
- /*
- * Free up any embedded windows which belong to this widget.
- */
-
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->windowTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- TkTextEmbWindowClient *loop;
- TkTextSegment *ewPtr = Tcl_GetHashValue(hPtr);
-
- loop = ewPtr->body.ew.clients;
- if (loop->textPtr == textPtr) {
- ewPtr->body.ew.clients = loop->next;
- TkTextWinFreeClient(hPtr, loop);
- } else {
- TkTextEmbWindowClient *client = ewPtr->body.ew.clients;
-
- client = loop->next;
- while (client != NULL) {
- if (client->textPtr == textPtr) {
- loop->next = client->next;
- TkTextWinFreeClient(hPtr, client);
- break;
- } else {
- loop = loop->next;
- }
- client = loop->next;
- }
- }
- }
- } else {
- /*
- * No need to call 'TkBTreeRemoveClient' first, since this will do
- * everything in one go, more quickly.
- */
-
- TkBTreeDestroy(sharedTextPtr->tree);
-
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- tagPtr = Tcl_GetHashValue(hPtr);
-
- /*
- * No need to use 'TkTextDeleteTag' since we've already removed
- * the B-tree completely.
- */
-
- TkTextFreeTag(textPtr, tagPtr);
- }
- Tcl_DeleteHashTable(&sharedTextPtr->tagTable);
- for (hPtr = Tcl_FirstHashEntry(&sharedTextPtr->markTable, &search);
- hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
- ckfree(Tcl_GetHashValue(hPtr));
- }
- Tcl_DeleteHashTable(&sharedTextPtr->markTable);
- TkUndoFreeStack(sharedTextPtr->undoStack);
-
- Tcl_DeleteHashTable(&sharedTextPtr->windowTable);
- Tcl_DeleteHashTable(&sharedTextPtr->imageTable);
-
- if (sharedTextPtr->bindingTable != NULL) {
- Tk_DeleteBindingTable(sharedTextPtr->bindingTable);
- }
- ckfree(sharedTextPtr);
- }
-
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
- }
- if (textPtr->insertBlinkHandler != NULL) {
- Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
- }
-
- textPtr->tkwin = NULL;
- Tcl_DeleteCommandFromToken(textPtr->interp, textPtr->widgetCmd);
- if (textPtr->afterSyncCmd){
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
- }
- if (textPtr->refCount-- <= 1) {
- ckfree(textPtr);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ConfigureText --
- *
- * This function is called to process an objv/objc list, plus the Tk
- * option database, in order to configure (or reconfigure) a text widget.
- *
- * Results:
- * The return value is a standard Tcl result. If TCL_ERROR is returned,
- * then the interp's result contains an error message.
- *
- * Side effects:
- * Configuration information, such as text string, colors, font, etc. get
- * set for textPtr; old resources get freed, if there were any.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-ConfigureText(
- Tcl_Interp *interp, /* Used for error reporting. */
- register TkText *textPtr, /* Information about widget; may or may not
- * already have values for some fields. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- Tk_SavedOptions savedOptions;
- int oldExport = textPtr->exportSelection;
- int mask = 0;
-
- if (Tk_SetOptions(interp, (char *) textPtr, textPtr->optionTable,
- objc, objv, textPtr->tkwin, &savedOptions, &mask) != TCL_OK) {
- return TCL_ERROR;
- }
-
- /*
- * Copy down shared flags.
- */
-
- textPtr->sharedTextPtr->undo = textPtr->undo;
- textPtr->sharedTextPtr->maxUndo = textPtr->maxUndo;
- textPtr->sharedTextPtr->autoSeparators = textPtr->autoSeparators;
-
- TkUndoSetMaxDepth(textPtr->sharedTextPtr->undoStack,
- textPtr->sharedTextPtr->maxUndo);
-
- /*
- * A few other options also need special processing, such as parsing the
- * geometry and setting the background from a 3-D border.
- */
-
- Tk_SetBackgroundFromBorder(textPtr->tkwin, textPtr->border);
-
- if (mask & TK_TEXT_LINE_RANGE) {
- int start, end, current;
- TkTextIndex index1, index2, index3;
-
- /*
- * Line start and/or end have been adjusted. We need to validate the
- * first displayed line and arrange for re-layout.
- */
-
- TkBTreeClientRangeChanged(textPtr, textPtr->charHeight);
-
- if (textPtr->start != NULL) {
- start = TkBTreeLinesTo(NULL, textPtr->start);
- } else {
- start = 0;
- }
- if (textPtr->end != NULL) {
- end = TkBTreeLinesTo(NULL, textPtr->end);
- } else {
- end = TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL);
- }
- if (start > end) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "-startline must be less than or equal to -endline", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "INDEX_ORDER", NULL);
- Tk_RestoreSavedOptions(&savedOptions);
- return TCL_ERROR;
- }
- current = TkBTreeLinesTo(NULL, textPtr->topIndex.linePtr);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, start, 0,
- &index1);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, end, 0,
- &index2);
- if (current < start || current > end) {
- TkTextSearch search;
- TkTextIndex first, last;
- int selChanged = 0;
-
- TkTextSetYView(textPtr, &index1, 0);
-
- /*
- * We may need to adjust the selection. So we have to check
- * whether the "sel" tag was applied to anything outside the
- * current start,end.
- */
-
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL, 0, 0,
- &first);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, NULL,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, NULL),
- 0, &last);
- TkBTreeStartSearch(&first, &last, textPtr->selTagPtr, &search);
- if (!TkBTreeCharTagged(&first, textPtr->selTagPtr)
- && !TkBTreeNextTag(&search)) {
- /* Nothing tagged with "sel" */
- } else {
- int line = TkBTreeLinesTo(NULL, search.curIndex.linePtr);
-
- if (line < start) {
- selChanged = 1;
- } else {
- TkTextLine *linePtr = search.curIndex.linePtr;
-
- while (TkBTreeNextTag(&search)) {
- linePtr = search.curIndex.linePtr;
- }
- line = TkBTreeLinesTo(NULL, linePtr);
- if (line >= end) {
- selChanged = 1;
- }
- }
- }
- if (selChanged) {
- /*
- * Send an event that the selection has changed, and abort any
- * partial-selections in progress.
- */
-
- TkTextSelectionEvent(textPtr);
- textPtr->abortSelections = 1;
- }
- }
-
- /* Indices are potentially obsolete after changing -startline and/or
- * -endline, therefore increase the epoch.
- * Also, clamp the insert and current (unshared) marks to the new
- * -startline/-endline range limits of the widget. All other (shared)
- * marks are unchanged.
- * The return value of TkTextMarkNameToIndex does not need to be
- * checked: "insert" and "current" marks always exist, and the
- * purpose of the code below precisely is to move them inside the
- * -startline/-endline range.
- */
-
- textPtr->sharedTextPtr->stateEpoch++;
- TkTextMarkNameToIndex(textPtr, "insert", &index3);
- if (TkTextIndexCmp(&index3, &index1) < 0) {
- textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index1);
- }
- if (TkTextIndexCmp(&index3, &index2) > 0) {
- textPtr->insertMarkPtr = TkTextSetMark(textPtr, "insert", &index2);
- }
- TkTextMarkNameToIndex(textPtr, "current", &index3);
- if (TkTextIndexCmp(&index3, &index1) < 0) {
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index1);
- }
- if (TkTextIndexCmp(&index3, &index2) > 0) {
- textPtr->currentMarkPtr = TkTextSetMark(textPtr, "current", &index2);
- }
- }
-
- /*
- * Don't allow negative spacings.
- */
-
- if (textPtr->spacing1 < 0) {
- textPtr->spacing1 = 0;
- }
- if (textPtr->spacing2 < 0) {
- textPtr->spacing2 = 0;
- }
- if (textPtr->spacing3 < 0) {
- textPtr->spacing3 = 0;
- }
-
- /*
- * Parse tab stops.
- */
-
- if (textPtr->tabArrayPtr != NULL) {
- ckfree(textPtr->tabArrayPtr);
- textPtr->tabArrayPtr = NULL;
- }
- if (textPtr->tabOptionPtr != NULL) {
- textPtr->tabArrayPtr = TkTextGetTabs(interp, textPtr,
- textPtr->tabOptionPtr);
- if (textPtr->tabArrayPtr == NULL) {
- Tcl_AddErrorInfo(interp,"\n (while processing -tabs option)");
- Tk_RestoreSavedOptions(&savedOptions);
- return TCL_ERROR;
- }
- }
-
- /*
- * Make sure that configuration options are properly mirrored between the
- * widget record and the "sel" tags. NOTE: we don't have to free up
- * information during the mirroring; old information was freed when it was
- * replaced in the widget record.
- */
-
- if (textPtr->selTagPtr->selBorder == NULL) {
- textPtr->selTagPtr->border = textPtr->selBorder;
- } else {
- textPtr->selTagPtr->selBorder = textPtr->selBorder;
- }
- if (textPtr->selTagPtr->borderWidthPtr != textPtr->selBorderWidthPtr) {
- textPtr->selTagPtr->borderWidthPtr = textPtr->selBorderWidthPtr;
- textPtr->selTagPtr->borderWidth = textPtr->selBorderWidth;
- }
- 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);
-
- /*
- * Claim the selection if we've suddenly started exporting it and there
- * are tagged characters.
- */
-
- 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);
- textPtr->flags |= GOT_SELECTION;
- }
- }
-
- /*
- * Account for state changes that would reenable blinking cursor state.
- */
-
- if (textPtr->flags & GOT_FOCUS) {
- Tcl_DeleteTimerHandler(textPtr->insertBlinkHandler);
- textPtr->insertBlinkHandler = NULL;
- TextBlinkProc(textPtr);
- }
-
- /*
- * Register the desired geometry for the window, and arrange for the
- * window to be redisplayed.
- */
-
- if (textPtr->width <= 0) {
- textPtr->width = 1;
- }
- if (textPtr->height <= 0) {
- textPtr->height = 1;
- }
- Tk_FreeSavedOptions(&savedOptions);
- TextWorldChanged(textPtr, mask);
- return TCL_OK;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * TextWorldChangedCallback --
- *
- * This function is called when the world has changed in some way and the
- * widget needs to recompute all its graphics contexts and determine its
- * new geometry.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Configures all tags in the Text with a empty objc/objv, for the side
- * effect of causing all the items to recompute their geometry and to be
- * redisplayed.
- *
- *---------------------------------------------------------------------------
- */
-
-static void
-TextWorldChangedCallback(
- ClientData instanceData) /* Information about widget. */
-{
- TkText *textPtr = instanceData;
-
- TextWorldChanged(textPtr, TK_TEXT_LINE_GEOMETRY);
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * TextWorldChanged --
- *
- * This function is called when the world has changed in some way and the
- * widget needs to recompute all its graphics contexts and determine its
- * new geometry.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Configures all tags in the Text with a empty objc/objv, for the side
- * effect of causing all the items to recompute their geometry and to be
- * redisplayed.
- *
- *---------------------------------------------------------------------------
- */
-
-static void
-TextWorldChanged(
- TkText *textPtr, /* Information about widget. */
- int mask) /* OR'd collection of bits showing what has
- * changed. */
-{
- Tk_FontMetrics fm;
- int border;
- int oldCharHeight = textPtr->charHeight;
-
- textPtr->charWidth = Tk_TextWidth(textPtr->tkfont, "0", 1);
- if (textPtr->charWidth <= 0) {
- textPtr->charWidth = 1;
- }
- Tk_GetFontMetrics(textPtr->tkfont, &fm);
-
- textPtr->charHeight = fm.linespace;
- if (textPtr->charHeight <= 0) {
- textPtr->charHeight = 1;
- }
- if (textPtr->charHeight != oldCharHeight) {
- TkBTreeClientRangeChanged(textPtr, textPtr->charHeight);
- }
- 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)
- + 2*textPtr->padY + 2*border);
-
- Tk_SetInternalBorderEx(textPtr->tkwin,
- border + textPtr->padX, border + textPtr->padX,
- border + textPtr->padY, border + textPtr->padY);
- if (textPtr->setGrid) {
- Tk_SetGrid(textPtr->tkwin, textPtr->width, textPtr->height,
- textPtr->charWidth, textPtr->charHeight);
- } else {
- Tk_UnsetGrid(textPtr->tkwin);
- }
-
- TkTextRelayoutWindow(textPtr, mask);
-}
-
-/*
- *--------------------------------------------------------------
- *
- * TextEventProc --
- *
- * This function is invoked by the Tk dispatcher on structure changes to
- * a text. For texts with 3D borders, this function is also invoked for
- * exposures.
- *
- * Results:
- * None.
- *
- * Side effects:
- * When the window gets deleted, internal structures get cleaned up.
- * When it gets exposed, it is redisplayed.
- *
- *--------------------------------------------------------------
- */
-
-static void
-TextEventProc(
- ClientData clientData, /* Information about window. */
- register XEvent *eventPtr) /* Information about event. */
-{
- register TkText *textPtr = clientData;
- TkTextIndex index, index2;
-
- 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;
-
- 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.
- */
-
- 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;
-
- /*
- * Call 'DestroyTest' to handle the deletion for us. The actual
- * textPtr may still exist after this, if there are some outstanding
- * references. But we have flagged it as DESTROYED just above, so
- * nothing will try to make use of it very extensively.
- */
-
- DestroyText(textPtr);
- } else if ((eventPtr->type == FocusIn) || (eventPtr->type == FocusOut)) {
- if (eventPtr->xfocus.detail == NotifyInferior
- || 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);
- }
- } else {
- textPtr->flags &= ~(GOT_FOCUS | INSERT_ON);
- textPtr->insertBlinkHandler = NULL;
- }
- if (textPtr->inactiveSelBorder != textPtr->selBorder) {
- TkTextRedrawTag(NULL, textPtr, NULL, NULL, textPtr->selTagPtr,
- 1);
- }
- TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
- TkTextIndexForwChars(NULL, &index, 1, &index2, COUNT_INDICES);
-
- /*
- * While we wish to redisplay, no heights have changed, so no need
- * to call TkTextInvalidateLineMetrics.
- */
-
- TkTextChanged(NULL, textPtr, &index, &index2);
- if (textPtr->highlightWidth > 0) {
- TkTextRedrawRegion(textPtr, 0, 0, textPtr->highlightWidth,
- textPtr->highlightWidth);
- }
- }
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextCmdDeletedProc --
- *
- * This function is invoked when a widget command is deleted. If the
- * widget isn't already in the process of being destroyed, this command
- * destroys it.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The widget is destroyed.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-TextCmdDeletedProc(
- ClientData clientData) /* Pointer to widget record for widget. */
-{
- TkText *textPtr = clientData;
- Tk_Window tkwin = textPtr->tkwin;
-
- /*
- * This function could be invoked either because the window was destroyed
- * and the command was then deleted (in which this flag is already set) or
- * because the command was deleted, and then this function destroys the
- * widget.
- */
-
- if (!(textPtr->flags & DESTROYED)) {
- if (textPtr->setGrid) {
- Tk_UnsetGrid(textPtr->tkwin);
- textPtr->setGrid = 0;
- }
- textPtr->flags |= DESTROYED;
- Tk_DestroyWindow(tkwin);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * InsertChars --
- *
- * This function implements most of the functionality of the "insert"
- * widget command.
- *
- * Results:
- * The length of the inserted string.
- *
- * Side effects:
- * The characters in "stringPtr" get added to the text just before the
- * character indicated by "indexPtr".
- *
- * If 'viewUpdate' is true, we may adjust the window contents'
- * y-position, and scrollbar setting.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-InsertChars(
- TkSharedText *sharedTextPtr,
- TkText *textPtr, /* Overall information about text widget. */
- TkTextIndex *indexPtr, /* Where to insert new characters. May be
- * modified if the index is not valid for
- * insertion (e.g. if at "end"). */
- Tcl_Obj *stringPtr, /* Null-terminated string containing new
- * information to add to text. */
- int viewUpdate) /* Update the view if set. */
-{
- int lineIndex, length;
- TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
- const char *string = Tcl_GetString(stringPtr);
-
- length = stringPtr->length;
- if (sharedTextPtr == NULL) {
- 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.
- */
-
- lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
- if (lineIndex == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- lineIndex--;
- TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, lineIndex, 1000000,
- indexPtr);
- }
-
- /*
- * Notify the display module that lines are about to change, then do the
- * insertion. If the insertion occurs on the top line of the widget
- * (textPtr->topIndex), then we have to recompute topIndex after the
- * insertion, since the insertion could invalidate it.
- */
-
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
- } else {
- lineAndByteIndex = pixels;
- }
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- lineAndByteIndex[resetViewCount] = -1;
- if (indexPtr->linePtr == tPtr->topIndex.linePtr) {
- lineAndByteIndex[resetViewCount] =
- TkBTreeLinesTo(tPtr, indexPtr->linePtr);
- lineAndByteIndex[resetViewCount+1] = tPtr->topIndex.byteIndex;
- if (lineAndByteIndex[resetViewCount+1] > indexPtr->byteIndex) {
- lineAndByteIndex[resetViewCount+1] += length;
- }
- }
- resetViewCount += 2;
- }
-
- TkTextChanged(sharedTextPtr, NULL, indexPtr, indexPtr);
-
- sharedTextPtr->stateEpoch++;
-
- TkBTreeInsertChars(sharedTextPtr->tree, indexPtr, string);
-
- /*
- * Push the insertion on the undo stack, and update the modified status of
- * the widget.
- */
-
- if (length > 0) {
- if (sharedTextPtr->undo) {
- TkTextIndex toIndex;
-
- if (sharedTextPtr->autoSeparators &&
- sharedTextPtr->lastEditMode != TK_TEXT_EDIT_INSERT) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
-
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_INSERT;
-
- TkTextIndexForwBytes(textPtr, indexPtr, length, &toIndex);
- TextPushUndoAction(textPtr, stringPtr, 1, indexPtr, &toIndex);
- }
-
- UpdateDirtyFlag(sharedTextPtr);
- }
-
- resetViewCount = 0;
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- if (lineAndByteIndex[resetViewCount] != -1) {
- if ((tPtr != textPtr) || viewUpdate) {
- TkTextIndex newTop;
-
- TkTextMakeByteIndex(sharedTextPtr->tree, tPtr,
- lineAndByteIndex[resetViewCount], 0, &newTop);
- TkTextIndexForwBytes(tPtr, &newTop,
- lineAndByteIndex[resetViewCount+1], &newTop);
- TkTextSetYView(tPtr, &newTop, 0);
- }
- }
- resetViewCount += 2;
- }
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- ckfree(lineAndByteIndex);
- }
-
- /*
- * Invalidate any selection retrievals in progress.
- */
-
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
- }
-
- /*
- * For convenience, return the length of the string.
- */
-
- return length;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextPushUndoAction --
- *
- * Shared by insert and delete actions. Stores the appropriate scripts
- * into our undo stack. We will add a single refCount to the 'undoString'
- * object, so, if it previously had a refCount of zero, the caller should
- * not free it.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Items pushed onto stack.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-TextPushUndoAction(
- TkText *textPtr, /* Overall information about text widget. */
- Tcl_Obj *undoString, /* New text. */
- int insert, /* 1 if insert, else delete. */
- const TkTextIndex *index1Ptr,
- /* Index describing first location. */
- const TkTextIndex *index2Ptr)
- /* Index describing second location. */
-{
- TkUndoSubAtom *iAtom, *dAtom;
- int canUndo, canRedo;
-
- /*
- * Create the helpers.
- */
-
- Tcl_Obj *seeInsertObj = Tcl_NewObj();
- Tcl_Obj *markSet1InsertObj = Tcl_NewObj();
- Tcl_Obj *markSet2InsertObj = NULL;
- Tcl_Obj *insertCmdObj = Tcl_NewObj();
- Tcl_Obj *deleteCmdObj = Tcl_NewObj();
-
- /*
- * Get the index positions.
- */
-
- Tcl_Obj *index1Obj = TkTextNewIndexObj(NULL, index1Ptr);
- Tcl_Obj *index2Obj = TkTextNewIndexObj(NULL, index2Ptr);
-
- /*
- * These need refCounts, because they are used more than once below.
- */
-
- Tcl_IncrRefCount(seeInsertObj);
- Tcl_IncrRefCount(index1Obj);
- Tcl_IncrRefCount(index2Obj);
-
- Tcl_ListObjAppendElement(NULL, seeInsertObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, seeInsertObj, Tcl_NewStringObj("see", 3));
- Tcl_ListObjAppendElement(NULL, seeInsertObj,
- Tcl_NewStringObj("insert", 6));
-
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("mark", 4));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("set", 3));
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj,
- Tcl_NewStringObj("insert", 6));
- markSet2InsertObj = Tcl_DuplicateObj(markSet1InsertObj);
- Tcl_ListObjAppendElement(NULL, markSet1InsertObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, markSet2InsertObj, index2Obj);
-
- Tcl_ListObjAppendElement(NULL, insertCmdObj,
- Tcl_NewStringObj("insert", 6));
- Tcl_ListObjAppendElement(NULL, insertCmdObj, index1Obj);
-
- /*
- * Only use of 'undoString' is here.
- */
-
- Tcl_ListObjAppendElement(NULL, insertCmdObj, undoString);
-
- Tcl_ListObjAppendElement(NULL, deleteCmdObj,
- Tcl_NewStringObj("delete", 6));
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index1Obj);
- Tcl_ListObjAppendElement(NULL, deleteCmdObj, index2Obj);
-
- /*
- * Note: we don't wish to use textPtr->widgetCmd in these callbacks
- * because if we delete the textPtr, but peers still exist, we will then
- * have references to a non-existent Tcl_Command in the undo stack, which
- * will lead to crashes later. Also, the behaviour of the widget w.r.t.
- * bindings (%W substitutions) always uses the widget path name, so there
- * is no good reason the undo stack should do otherwise.
- *
- * For the 'insert' and 'delete' actions, we have to register a functional
- * callback, because these actions are defined to operate on the
- * underlying data shared by all peers.
- */
-
- iAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- insertCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet2InsertObj, iAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, iAtom);
-
- dAtom = TkUndoMakeSubAtom(&TextUndoRedoCallback, textPtr->sharedTextPtr,
- deleteCmdObj, NULL);
- TkUndoMakeCmdSubAtom(NULL, markSet1InsertObj, dAtom);
- TkUndoMakeCmdSubAtom(NULL, seeInsertObj, dAtom);
-
- Tcl_DecrRefCount(seeInsertObj);
- Tcl_DecrRefCount(index1Obj);
- Tcl_DecrRefCount(index2Obj);
-
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
-
- /*
- * Depending whether the action is to insert or delete, we provide the
- * appropriate second and third arguments to TkUndoPushAction. (The first
- * is the 'actionCommand', and the second the 'revertCommand').
- */
-
- if (insert) {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, iAtom, dAtom);
- } else {
- TkUndoPushAction(textPtr->sharedTextPtr->undoStack, dAtom, iAtom);
- }
-
- if (!canUndo || canRedo) {
- GenerateUndoStackEvent(textPtr);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextUndoRedoCallback --
- *
- * This function is registered with the generic undo/redo code to handle
- * 'insert' and 'delete' actions on all text widgets. We cannot perform
- * those actions on any particular text widget, because that text widget
- * might have been deleted by the time we get here.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * Will insert or delete text, depending on the first word contained in
- * objPtr.
- *
- *----------------------------------------------------------------------
- */
-
-int
-TextUndoRedoCallback(
- Tcl_Interp *interp, /* Current interpreter. */
- ClientData clientData, /* Passed from undo code, but contains our
- * shared text data structure. */
- Tcl_Obj *objPtr) /* Arguments of a command to be handled by the
- * shared text data structure. */
-{
- TkSharedText *sharedPtr = clientData;
- int res, objc;
- Tcl_Obj **objv;
- TkText *textPtr;
-
- res = Tcl_ListObjGetElements(interp, objPtr, &objc, &objv);
- if (res != TCL_OK) {
- return res;
- }
-
- /*
- * If possible, use a real text widget to perform the undo/redo action
- * (i.e. insertion or deletion of text). This provides maximum
- * compatibility with older versions of Tk, in which the user may rename
- * the text widget to allow capture of undo or redo actions.
- *
- * In particular, this sorting of capture is useful in text editors based
- * on the Tk text widget, which need to know which new text needs
- * re-coloring.
- *
- * It would be better if the text widget provided some other mechanism to
- * allow capture of this information ("What has just changed in the text
- * widget?"). What we have here is not entirely satisfactory under all
- * circumstances.
- */
-
- textPtr = sharedPtr->peers;
- while (textPtr != NULL) {
- if (textPtr->start == NULL && textPtr->end == NULL) {
- Tcl_Obj *cmdNameObj, *evalObj;
-
- evalObj = Tcl_NewObj();
- Tcl_IncrRefCount(evalObj);
-
- /*
- * We might wish to use the real, current command-name for the
- * widget, but this will break any code that has over-ridden the
- * widget, and is expecting to observe the insert/delete actions
- * which are caused by undo/redo operations.
- *
- * cmdNameObj = Tcl_NewObj();
- * Tcl_GetCommandFullName(interp, textPtr->widgetCmd, cmdNameObj);
- *
- * While such interception is not explicitly documented as
- * supported, it does occur, and so until we can provide some
- * alternative mechanism for such code to do what it needs, we
- * allow it to take place here.
- */
-
- cmdNameObj = Tcl_NewStringObj(Tk_PathName(textPtr->tkwin), -1);
- Tcl_ListObjAppendElement(NULL, evalObj, cmdNameObj);
- Tcl_ListObjAppendList(NULL, evalObj, objPtr);
- res = Tcl_EvalObjEx(interp, evalObj, TCL_EVAL_GLOBAL);
- Tcl_DecrRefCount(evalObj);
- return res;
- }
- textPtr = textPtr->next;
- }
-
- /*
- * If there's no current text widget which shows everything, then we fall
- * back on acting directly. This means there is no way to intercept from
- * the Tcl level.
- */
-
- return SharedTextObjCmd(sharedPtr, interp, objc+1, objv-1);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * CountIndices --
- *
- * This function implements most of the functionality of the "count"
- * widget command.
- *
- * Note that 'textPtr' is only used if we need to check for elided
- * attributes, i.e. if type is COUNT_DISPLAY_INDICES or
- * COUNT_DISPLAY_CHARS
- *
- * Results:
- * Returns the number of characters in the range.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-CountIndices(
- const TkText *textPtr, /* Overall information about text widget. */
- const TkTextIndex *indexPtr1,
- /* Index describing location of first
- * character to delete. */
- const TkTextIndex *indexPtr2,
- /* Index describing location of last character
- * to delete. NULL means just delete the one
- * character given by indexPtr1. */
- TkTextCountType type) /* The kind of indices to count. */
-{
- /*
- * Order the starting and stopping indices.
- */
-
- int compare = TkTextIndexCmp(indexPtr1, indexPtr2);
-
- if (compare == 0) {
- return 0;
- } else if (compare > 0) {
- return -TkTextIndexCount(textPtr, indexPtr2, indexPtr1, type);
- } else {
- return TkTextIndexCount(textPtr, indexPtr1, indexPtr2, type);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * DeleteIndexRange --
- *
- * This function implements most of the functionality of the "delete"
- * widget command.
- *
- * Results:
- * Returns a standard Tcl result, currently always TCL_OK.
- *
- * Side effects:
- * Characters and other entities (windows, images) get deleted from the
- * text.
- *
- * If 'viewUpdate' is true, we may adjust the window contents'
- * y-position, and scrollbar setting.
- *
- * If 'viewUpdate' is true we can guarantee that textPtr->topIndex
- * points to a valid TkTextLine after this function returns. However, if
- * 'viewUpdate' is false, then there is no such guarantee (since
- * topIndex.linePtr can be garbage). The caller is expected to take
- * actions to ensure the topIndex is validated before laying out the
- * window again.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-DeleteIndexRange(
- TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */
- TkText *textPtr, /* Overall information about text widget. */
- const TkTextIndex *indexPtr1,
- /* Index describing location of first
- * character (or other entity) to delete. */
- const TkTextIndex *indexPtr2,
- /* Index describing location of last
- * character (or other entity) to delete.
- * NULL means just delete the one character
- * given by indexPtr1. */
- int viewUpdate) /* Update vertical view if set. */
-{
- int line1, line2;
- TkTextIndex index1, index2;
- TkText *tPtr;
- int *lineAndByteIndex;
- int resetViewCount;
- int pixels[2*PIXEL_CLIENTS];
-
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
- }
-
- /*
- * Prepare the starting and stopping indices.
- */
-
- index1 = *indexPtr1;
- if (indexPtr2 != NULL) {
- index2 = *indexPtr2;
- } else {
- index2 = index1;
- TkTextIndexForwChars(NULL, &index2, 1, &index2, COUNT_INDICES);
- }
-
- /*
- * Make sure there's really something to delete.
- */
-
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
- return TCL_OK;
- }
-
- /*
- * 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.
- */
-
- line1 = TkBTreeLinesTo(textPtr, index1.linePtr);
- line2 = TkBTreeLinesTo(textPtr, index2.linePtr);
- if (line2 == TkBTreeNumLines(sharedTextPtr->tree, textPtr)) {
- TkTextTag **arrayPtr;
- int arraySize, i;
- TkTextIndex oldIndex2;
-
- oldIndex2 = index2;
- TkTextIndexBackChars(NULL, &oldIndex2, 1, &index2, COUNT_INDICES);
- line2--;
- if ((index1.byteIndex == 0) && (line1 != 0)) {
- TkTextIndexBackChars(NULL, &index1, 1, &index1, COUNT_INDICES);
- line1--;
- }
- arrayPtr = TkBTreeGetTags(&index2, NULL, &arraySize);
- if (arrayPtr != NULL) {
- for (i = 0; i < arraySize; i++) {
- TkBTreeTag(&index2, &oldIndex2, arrayPtr[i], 0);
- }
- 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;
-
- for (i=0, hPtr=Tcl_FirstHashEntry(&sharedTextPtr->tagTable, &search);
- hPtr != NULL; i++, hPtr = Tcl_NextHashEntry(&search)) {
- TkTextTag *tagPtr = Tcl_GetHashValue(hPtr);
-
- TkBTreeTag(&index1, &index2, tagPtr, 0);
- }
-
- /*
- * Special case for the sel tag which is not in the hash table. We
- * need to do this once for each peer text widget.
- */
-
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ;
- tPtr = tPtr->next) {
- if (TkBTreeTag(&index1, &index2, tPtr->selTagPtr, 0)) {
- /*
- * Send an event that the selection changed. This is
- * equivalent to:
- * event generate $textWidget <<Selection>>
- */
-
- TkTextSelectionEvent(textPtr);
- tPtr->abortSelections = 1;
- }
- }
- }
-
- /*
- * Tell the display what's about to happen so it can discard obsolete
- * display information, then do the deletion. Also, if the deletion
- * involves the top line on the screen, then we have to reset the view
- * (the deletion will invalidate textPtr->topIndex). Compute what the new
- * first character will be, then do the deletion, then reset the view.
- */
-
- TkTextChanged(sharedTextPtr, NULL, &index1, &index2);
-
- resetViewCount = 0;
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- lineAndByteIndex = ckalloc(sizeof(int) * 2 * sharedTextPtr->refCount);
- } else {
- lineAndByteIndex = pixels;
- }
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- int line = 0;
- int byteIndex = 0;
- int resetView = 0;
-
- if (TkTextIndexCmp(&index2, &tPtr->topIndex) >= 0) {
- if (TkTextIndexCmp(&index1, &tPtr->topIndex) <= 0) {
- /*
- * Deletion range straddles topIndex: use the beginning of the
- * range as the new topIndex.
- */
-
- resetView = 1;
- line = line1;
- byteIndex = index1.byteIndex;
- } else if (index1.linePtr == tPtr->topIndex.linePtr) {
- /*
- * Deletion range starts on top line but after topIndex. Use
- * the current topIndex as the new one.
- */
-
- resetView = 1;
- line = line1;
- byteIndex = tPtr->topIndex.byteIndex;
- } else {
- /*
- * Deletion range starts after the top line. This peers's view
- * will not need to be reset. Nothing to do.
- */
- }
- } else if (index2.linePtr == tPtr->topIndex.linePtr) {
- /*
- * Deletion range ends on top line but before topIndex. Figure out
- * what will be the new character index for the character
- * currently pointed to by topIndex.
- */
-
- resetView = 1;
- line = line2;
- byteIndex = 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;
- }
-
- /*
- * Push the deletion on the undo stack if something was actually deleted.
- */
-
- if (TkTextIndexCmp(&index1, &index2) < 0) {
- if (sharedTextPtr->undo) {
- Tcl_Obj *get;
-
- if (sharedTextPtr->autoSeparators
- && (sharedTextPtr->lastEditMode != TK_TEXT_EDIT_DELETE)) {
- TkUndoInsertUndoSeparator(sharedTextPtr->undoStack);
- }
-
- sharedTextPtr->lastEditMode = TK_TEXT_EDIT_DELETE;
-
- get = TextGetText(textPtr, &index1, &index2, 0);
- TextPushUndoAction(textPtr, get, 0, &index1, &index2);
- }
- sharedTextPtr->stateEpoch++;
-
- TkBTreeDeleteIndexRange(sharedTextPtr->tree, &index1, &index2);
-
- UpdateDirtyFlag(sharedTextPtr);
- }
-
- resetViewCount = 0;
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- int line = lineAndByteIndex[resetViewCount];
-
- if (line != -1) {
- int byteIndex = lineAndByteIndex[resetViewCount+1];
- TkTextIndex indexTmp;
-
- if (tPtr == textPtr) {
- if (viewUpdate) {
- /*
- * line cannot be before -startline of textPtr because
- * this line corresponds to an index which is necessarily
- * between "1.0" and "end" relative to textPtr.
- * Therefore no need to clamp line to the -start/-end
- * range.
- */
-
- TkTextMakeByteIndex(sharedTextPtr->tree, textPtr, line,
- byteIndex, &indexTmp);
- TkTextSetYView(tPtr, &indexTmp, 0);
- }
- } else {
- TkTextMakeByteIndex(sharedTextPtr->tree, tPtr, line,
- byteIndex, &indexTmp);
- /*
- * line may be before -startline of tPtr and must be
- * clamped to -startline before providing it to
- * TkTextSetYView otherwise lines before -startline
- * would be displayed.
- * There is no need to worry about -endline however,
- * because the view will only be reset if the deletion
- * involves the TOP line of the screen
- */
-
- if (tPtr->start != NULL) {
- int start;
- TkTextIndex indexStart;
-
- start = TkBTreeLinesTo(NULL, tPtr->start);
- TkTextMakeByteIndex(sharedTextPtr->tree, NULL, start,
- 0, &indexStart);
- if (TkTextIndexCmp(&indexTmp, &indexStart) < 0) {
- indexTmp = indexStart;
- }
- }
- TkTextSetYView(tPtr, &indexTmp, 0);
- }
- }
- resetViewCount += 2;
- }
- if (sharedTextPtr->refCount > PIXEL_CLIENTS) {
- ckfree(lineAndByteIndex);
- }
-
- if (line1 >= line2) {
- /*
- * Invalidate any selection retrievals in progress, assuming we didn't
- * check for this case above.
- */
-
- for (tPtr = sharedTextPtr->peers; tPtr != NULL ; tPtr = tPtr->next) {
- tPtr->abortSelections = 1;
- }
- }
-
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextFetchSelection --
- *
- * This function is called back by Tk when the selection is requested by
- * someone. It returns part or all of the selection in a buffer provided
- * by the caller.
- *
- * Results:
- * The return value is the number of non-NULL bytes stored at buffer.
- * Buffer is filled (or partially filled) with a NULL-terminated string
- * containing part or all of the selection, as given by offset and
- * maxBytes.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextFetchSelection(
- ClientData clientData, /* Information about text widget. */
- int offset, /* Offset within selection of first character
- * to be returned. */
- char *buffer, /* Location in which to place selection. */
- int maxBytes) /* Maximum number of bytes to place at buffer,
- * not including terminating NULL
- * character. */
-{
- register TkText *textPtr = clientData;
- TkTextIndex eof;
- int count, chunkSize, offsetInSeg;
- TkTextSearch search;
- TkTextSegment *segPtr;
-
- if (!textPtr->exportSelection) {
- return -1;
- }
-
- /*
- * Find the beginning of the next range of selected text. Note: if the
- * selection is being retrieved in multiple pieces (offset != 0) and some
- * modification has been made to the text that affects the selection then
- * reject the selection request (make 'em start over again).
- */
-
- if (offset == 0) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, 0, 0,
- &textPtr->selIndex);
- textPtr->abortSelections = 0;
- } 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.
- */
-
- 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");
- }
-
- /*
- * Copy information from character segments into the buffer until
- * either we run out of space in the buffer or we get to the end of
- * this range of text.
- */
-
- while (1) {
- if (maxBytes == 0) {
- goto fetchDone;
- }
- segPtr = TkTextIndexToSeg(&textPtr->selIndex, &offsetInSeg);
- chunkSize = segPtr->size - offsetInSeg;
- if (chunkSize > maxBytes) {
- chunkSize = maxBytes;
- }
- if (textPtr->selIndex.linePtr == search.curIndex.linePtr) {
- int leftInRange;
-
- 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);
- }
-
- /*
- * Find the beginning of the next range of selected text.
- */
-
- if (!TkBTreeNextTag(&search)) {
- break;
- }
- textPtr->selIndex = search.curIndex;
- }
-
- fetchDone:
- *buffer = 0;
- return count;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TkTextLostSelection --
- *
- * This function is called back by Tk when the selection is grabbed away
- * from a text widget. On Windows and Mac systems, we want to remember
- * the selection for the next time the focus enters the window. On Unix,
- * just remove the "sel" tag from everything in the widget.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The "sel" tag is cleared from the window.
- *
- *----------------------------------------------------------------------
- */
-
-void
-TkTextLostSelection(
- ClientData clientData) /* Information about text widget. */
-{
- register TkText *textPtr = clientData;
-
- if (TkpAlwaysShowSelection(textPtr->tkwin)) {
- TkTextIndex start, end;
-
- if (!textPtr->exportSelection) {
- return;
- }
-
- /*
- * On Windows and Mac systems, we want to remember the selection for
- * the next time the focus enters the window. On Unix, just remove the
- * "sel" tag from everything in the widget.
- */
-
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- 0, 0, &start);
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr),
- 0, &end);
- TkTextRedrawTag(NULL, textPtr, &start, &end, textPtr->selTagPtr, 1);
- TkBTreeTag(&start, &end, textPtr->selTagPtr, 0);
- }
-
- /*
- * Send an event that the selection changed. This is equivalent to:
- * event generate $textWidget <<Selection>>
- */
-
- TkTextSelectionEvent(textPtr);
-
- textPtr->flags &= ~GOT_SELECTION;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * 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
- * cursor off and on.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The cursor gets turned on or off, redisplay gets invoked, and this
- * function reschedules itself.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-TextBlinkProc(
- ClientData clientData) /* Pointer to record describing text. */
-{
- register TkText *textPtr = clientData;
- TkTextIndex index;
- int x, y, w, h, charWidth;
-
- 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)) {
- /*
- * 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)) {
- /*
- * 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);
- } else {
- /* I-beam cursor */
- TkTextRedrawRegion(textPtr, x - textPtr->insertWidth / 2, y,
- textPtr->insertWidth, h);
- }
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextInsertCmd --
- *
- * This function is invoked to process the "insert" and "replace" widget
- * commands for text widgets.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- * If 'viewUpdate' is true, we may adjust the window contents'
- * y-position, and scrollbar setting.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextInsertCmd(
- TkSharedText *sharedTextPtr,/* Shared portion of peer widgets. */
- TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[], /* Argument objects. */
- const TkTextIndex *indexPtr,/* Index at which to insert. */
- int viewUpdate) /* Update the view if set. */
-{
- TkTextIndex index1, index2;
- int j;
-
- if (sharedTextPtr == NULL) {
- sharedTextPtr = textPtr->sharedTextPtr;
- }
-
- index1 = *indexPtr;
- for (j = 0; j < objc; j += 2) {
- /*
- * Here we rely on this call to modify index1 if it is outside the
- * acceptable range. In particular, if index1 is "end", it must be set
- * to the last allowable index for insertion, otherwise subsequent tag
- * insertions will fail.
- */
-
- int length = InsertChars(sharedTextPtr, textPtr, &index1, objv[j],
- viewUpdate);
-
- if (objc > (j+1)) {
- Tcl_Obj **tagNamePtrs;
- TkTextTag **oldTagArrayPtr;
- int numTags;
-
- TkTextIndexForwBytes(textPtr, &index1, length, &index2);
- oldTagArrayPtr = TkBTreeGetTags(&index1, NULL, &numTags);
- if (oldTagArrayPtr != NULL) {
- int i;
-
- for (i = 0; i < numTags; i++) {
- TkBTreeTag(&index1, &index2, oldTagArrayPtr[i], 0);
- }
- ckfree(oldTagArrayPtr);
- }
- if (Tcl_ListObjGetElements(interp, objv[j+1], &numTags,
- &tagNamePtrs) != TCL_OK) {
- return TCL_ERROR;
- } else {
- int i;
-
- for (i = 0; i < numTags; i++) {
- const char *strTag = Tcl_GetString(tagNamePtrs[i]);
-
- TkBTreeTag(&index1, &index2,
- TkTextCreateTag(textPtr, strTag, NULL), 1);
- }
- index1 = index2;
- }
- }
- }
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextSearchCmd --
- *
- * This function is invoked to process the "search" widget command for
- * text widgets. See the user documentation for details on what it does.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * See the user documentation.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextSearchCmd(
- TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- int i, argsLeft, code;
- SearchSpec searchSpec;
-
- static const char *const switchStrings[] = {
- "-hidden",
- "--", "-all", "-backwards", "-count", "-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
- };
-
- /*
- * Set up the search specification, including the last 4 fields which are
- * text widget specific.
- */
-
- searchSpec.exact = 1;
- searchSpec.noCase = 0;
- searchSpec.all = 0;
- searchSpec.backwards = 0;
- searchSpec.varPtr = NULL;
- searchSpec.countPtr = NULL;
- searchSpec.resPtr = NULL;
- searchSpec.searchElide = 0;
- searchSpec.noLineStop = 0;
- searchSpec.overlap = 0;
- searchSpec.strictLimits = 0;
- searchSpec.numLines =
- TkBTreeNumLines(textPtr->sharedTextPtr->tree, textPtr);
- searchSpec.clientData = textPtr;
- searchSpec.addLineProc = &TextSearchAddNextLine;
- searchSpec.foundMatchProc = &TextSearchFoundMatch;
- searchSpec.lineIndexProc = &TextSearchGetLineIndex;
-
- /*
- * Parse switches and other arguments.
- */
-
- for (i=2 ; i<objc ; i++) {
- int index;
-
- if (Tcl_GetString(objv[i])[0] != '-') {
- break;
- }
-
- if (Tcl_GetIndexFromObjStruct(NULL, objv[i], switchStrings,
- sizeof(char *), "switch", 0, &index) != TCL_OK) {
- /*
- * Hide the -hidden option, generating the error description with
- * the side effects of T_GIFO.
- */
-
- (void) Tcl_GetIndexFromObjStruct(interp, objv[i], switchStrings+1,
- sizeof(char *), "switch", 0, &index);
- return TCL_ERROR;
- }
-
- switch ((enum SearchSwitches) index) {
- case SEARCH_END:
- i++;
- goto endOfSwitchProcessing;
- case SEARCH_ALL:
- searchSpec.all = 1;
- break;
- case SEARCH_BACK:
- searchSpec.backwards = 1;
- break;
- case SEARCH_COUNT:
- if (i >= objc-1) {
- Tcl_SetObjResult(interp, Tcl_NewStringObj(
- "no value given for \"-count\" option", -1));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "VALUE", NULL);
- return TCL_ERROR;
- }
- i++;
-
- /*
- * Assumption objv[i] isn't going to disappear on us during this
- * function, which is fair.
- */
-
- searchSpec.varPtr = objv[i];
- break;
- case SEARCH_ELIDE:
- case SEARCH_HIDDEN:
- searchSpec.searchElide = 1;
- break;
- case SEARCH_EXACT:
- searchSpec.exact = 1;
- break;
- case SEARCH_FWD:
- searchSpec.backwards = 0;
- break;
- case SEARCH_NOCASE:
- searchSpec.noCase = 1;
- break;
- case SEARCH_NOLINESTOP:
- searchSpec.noLineStop = 1;
- break;
- case SEARCH_OVERLAP:
- searchSpec.overlap = 1;
- break;
- case SEARCH_STRICTLIMITS:
- searchSpec.strictLimits = 1;
- break;
- case SEARCH_REGEXP:
- searchSpec.exact = 0;
- break;
- default:
- Tcl_Panic("unexpected switch fallthrough");
- }
- }
- endOfSwitchProcessing:
-
- 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));
- 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));
- Tcl_SetErrorCode(interp, "TK", "TEXT", "SEARCH_USAGE", NULL);
- return TCL_ERROR;
- }
-
- /*
- * Scan through all of the lines of the text circularly, starting at the
- * given index. 'objv[i]' is the pattern which may be an exact string or a
- * regexp pattern depending on the flags set above.
- */
-
- code = SearchPerform(interp, &searchSpec, objv[i], objv[i+1],
- (argsLeft == 1 ? objv[i+2] : NULL));
- if (code != TCL_OK) {
- goto cleanup;
- }
-
- /*
- * Set the '-count' variable, if given.
- */
-
- if (searchSpec.varPtr != NULL && searchSpec.countPtr != NULL) {
- Tcl_IncrRefCount(searchSpec.countPtr);
- if (Tcl_ObjSetVar2(interp, searchSpec.varPtr, NULL,
- searchSpec.countPtr, TCL_LEAVE_ERR_MSG) == NULL) {
- code = TCL_ERROR;
- goto cleanup;
- }
- }
-
- /*
- * Set the result.
- */
-
- if (searchSpec.resPtr != NULL) {
- Tcl_SetObjResult(interp, searchSpec.resPtr);
- searchSpec.resPtr = NULL;
- }
-
- cleanup:
- if (searchSpec.countPtr != NULL) {
- Tcl_DecrRefCount(searchSpec.countPtr);
- }
- if (searchSpec.resPtr != NULL) {
- Tcl_DecrRefCount(searchSpec.resPtr);
- }
- return code;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextSearchGetLineIndex --
- *
- * Extract a row, text offset index position from an objPtr
- *
- * This means we ignore any embedded windows/images and elidden text
- * (unless we are searching that).
- *
- * Results:
- * Standard Tcl error code (with a message in the interpreter on error
- * conditions).
- *
- * The offset placed in offsetPosPtr is a utf-8 char* byte index for
- * exact searches, and a Unicode character index for regexp searches.
- *
- * The line number should start at zero (searches which wrap around
- * assume the first line is numbered 0).
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextSearchGetLineIndex(
- Tcl_Interp *interp, /* For error messages. */
- Tcl_Obj *objPtr, /* Contains a textual index like "1.2" */
- SearchSpec *searchSpecPtr, /* Contains other search parameters. */
- int *linePosPtr, /* For returning the line number. */
- int *offsetPosPtr) /* For returning the text offset in the
- * line. */
-{
- const TkTextIndex *indexPtr;
- int line;
- TkText *textPtr = searchSpecPtr->clientData;
-
- indexPtr = TkTextGetIndexFromObj(interp, textPtr, objPtr);
- if (indexPtr == NULL) {
- return TCL_ERROR;
- }
-
- line = TkBTreeLinesTo(textPtr, indexPtr->linePtr);
- if (line >= searchSpecPtr->numLines) {
- TkTextLine *linePtr;
- int count = 0;
- TkTextSegment *segPtr;
-
- line = searchSpecPtr->numLines-1;
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, line);
-
- /*
- * Count the number of bytes in this line.
- */
-
- for (segPtr=linePtr->segPtr ; segPtr!=NULL ; segPtr=segPtr->nextPtr) {
- count += segPtr->size;
- }
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr, linePtr, count);
- } else {
- *offsetPosPtr = TextSearchIndexInLine(searchSpecPtr,
- indexPtr->linePtr, indexPtr->byteIndex);
- }
-
- *linePosPtr = line;
-
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextSearchIndexInLine --
- *
- * Find textual index of 'byteIndex' in the searchable characters of
- * 'linePtr'.
- *
- * This means we ignore any embedded windows/images and elidden text
- * (unless we are searching that).
- *
- * Results:
- * The returned index is a utf-8 char* byte index for exact searches, and
- * a Unicode character index for regexp searches.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextSearchIndexInLine(
- const SearchSpec *searchSpecPtr,
- /* Search parameters. */
- TkTextLine *linePtr, /* The line we're looking at. */
- int byteIndex) /* Index into the line. */
-{
- TkTextSegment *segPtr;
- TkTextIndex curIndex;
- int index, leftToScan;
- TkText *textPtr = searchSpecPtr->clientData;
-
- index = 0;
- curIndex.tree = textPtr->sharedTextPtr->tree;
- curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
- for (segPtr = linePtr->segPtr, leftToScan = byteIndex;
- leftToScan > 0;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if ((segPtr->typePtr == &tkTextCharType) &&
- (searchSpecPtr->searchElide
- || !TkTextIsElided(textPtr, &curIndex, NULL))) {
- if (leftToScan < segPtr->size) {
- if (searchSpecPtr->exact) {
- index += leftToScan;
- } else {
- index += Tcl_NumUtfChars(segPtr->body.chars, leftToScan);
- }
- } else if (searchSpecPtr->exact) {
- index += segPtr->size;
- } else {
- index += Tcl_NumUtfChars(segPtr->body.chars, -1);
- }
- }
- leftToScan -= segPtr->size;
- }
- return index;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextSearchAddNextLine --
- *
- * Adds a line from the text widget to the object 'theLine'.
- *
- * Results:
- * A pointer to the TkTextLine corresponding to the given line, or NULL
- * if there was no available line.
- *
- * Also 'lenPtr' (if non-NULL) is filled in with the total length of
- * 'theLine' (not just what we added to it, but the length including what
- * was already in there). This is in bytes for an exact search and in
- * chars for a regexp search.
- *
- * Also 'extraLinesPtr' (if non-NULL) will have its value incremented by
- * 1 for each additional logical line we have added because a newline is
- * elided (this will only ever happen if we have chosen not to search
- * elided text, of course).
- *
- * Side effects:
- * Memory may be allocated or re-allocated for theLine's string
- * representation.
- *
- *----------------------------------------------------------------------
- */
-
-static ClientData
-TextSearchAddNextLine(
- int lineNum, /* Line we must add. */
- SearchSpec *searchSpecPtr, /* Search parameters. */
- Tcl_Obj *theLine, /* Object to append to. */
- int *lenPtr, /* For returning the total length. */
- int *extraLinesPtr) /* If non-NULL, will have its value
- * incremented by the number of additional
- * logical lines which are merged into this
- * one by newlines being elided. */
-{
- TkTextLine *linePtr, *thisLinePtr;
- TkTextIndex curIndex;
- TkTextSegment *segPtr;
- TkText *textPtr = searchSpecPtr->clientData;
- int nothingYet = 1;
-
- /*
- * Extract the text from the line.
- */
-
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr, lineNum);
- if (linePtr == NULL) {
- return NULL;
- }
- curIndex.tree = textPtr->sharedTextPtr->tree;
- thisLinePtr = linePtr;
-
- while (thisLinePtr != NULL) {
- int elideWraps = 0;
-
- curIndex.linePtr = thisLinePtr;
- curIndex.byteIndex = 0;
- for (segPtr = thisLinePtr->segPtr; segPtr != NULL;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if (!searchSpecPtr->searchElide
- && TkTextIsElided(textPtr, &curIndex, NULL)) {
- /*
- * If we reach the end of the logical line, and if we have at
- * least one character in the string, then we continue
- * wrapping to the next logical line. If there are no
- * characters yet, then the entire line of characters is
- * elided and there's no need to complicate matters by
- * wrapping - we'll look at the next line in due course.
- */
-
- if (segPtr->nextPtr == NULL && !nothingYet) {
- elideWraps = 1;
- }
- continue;
- }
- if (segPtr->typePtr != &tkTextCharType) {
- continue;
- }
- Tcl_AppendToObj(theLine, segPtr->body.chars, segPtr->size);
- nothingYet = 0;
- }
- if (!elideWraps) {
- break;
- }
- lineNum++;
- if (lineNum >= searchSpecPtr->numLines) {
- break;
- }
- thisLinePtr = TkBTreeNextLine(textPtr, thisLinePtr);
- if (thisLinePtr != NULL && extraLinesPtr != NULL) {
- /*
- * Tell our caller we have an extra line merged in.
- */
-
- *extraLinesPtr = (*extraLinesPtr) + 1;
- }
- }
-
- /*
- * If we're ignoring case, convert the line to lower case. There is no
- * need to do this for regexp searches, since they handle a flag for this
- * purpose.
- */
-
- if (searchSpecPtr->exact && searchSpecPtr->noCase) {
- 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);
- }
- }
- return linePtr;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextSearchFoundMatch --
- *
- * Stores information from a successful search.
- *
- * Results:
- * 1 if the information was stored, 0 if the position at which the match
- * was found actually falls outside the allowable search region (and
- * therefore the search is actually complete).
- *
- * Side effects:
- * Memory may be allocated in the 'countPtr' and 'resPtr' fields of
- * 'searchSpecPtr'. Each of those objects will have refCount zero and
- * must eventually be freed or stored elsewhere as appropriate.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextSearchFoundMatch(
- int lineNum, /* Line on which match was found. */
- SearchSpec *searchSpecPtr, /* Search parameters. */
- ClientData clientData, /* Token returned by the 'addNextLineProc',
- * TextSearchAddNextLine. May be NULL, in
- * which we case we must generate it (from
- * lineNum). */
- Tcl_Obj *theLine, /* Text from current line, only accessed for
- * exact searches, and is allowed to be NULL
- * for regexp searches. */
- int matchOffset, /* Offset of found item in utf-8 bytes for
- * exact search, Unicode chars for regexp. */
- int matchLength) /* Length also in bytes/chars as per search
- * type. */
-{
- int numChars;
- int leftToScan;
- TkTextIndex curIndex, foundIndex;
- TkTextSegment *segPtr;
- TkTextLine *linePtr;
- TkText *textPtr = searchSpecPtr->clientData;
-
- if (lineNum == searchSpecPtr->stopLine) {
- /*
- * If the current index is on the wrong side of the stopIndex, then
- * the item we just found is actually outside the acceptable range,
- * and the search is over.
- */
-
- if (searchSpecPtr->backwards ^
- (matchOffset >= searchSpecPtr->stopOffset)) {
- return 0;
- }
- }
-
- /*
- * Calculate the character count, which may need augmenting if there are
- * embedded windows or elidden text.
- */
-
- if (searchSpecPtr->exact) {
- const char *startOfLine = Tcl_GetString(theLine);
-
- numChars = Tcl_NumUtfChars(startOfLine + matchOffset, matchLength);
- } else {
- numChars = matchLength;
- }
-
- /*
- * If we're using strict limits checking, ensure that the match with its
- * full length fits inside the given range.
- */
-
- if (searchSpecPtr->strictLimits && lineNum == searchSpecPtr->stopLine) {
- if (searchSpecPtr->backwards ^
- ((matchOffset + numChars) > searchSpecPtr->stopOffset)) {
- return 0;
- }
- }
-
- /*
- * The index information returned by the regular expression parser only
- * considers textual information: it doesn't account for embedded windows,
- * elided text (when we are not searching elided text) or any other
- * non-textual info. Scan through the line's segments again to adjust both
- * matchChar and matchCount.
- *
- * We will walk through the segments of this line until we have either
- * reached the end of the match or we have reached the end of the line.
- */
-
- linePtr = clientData;
- if (linePtr == NULL) {
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, textPtr,
- lineNum);
- }
-
- curIndex.tree = textPtr->sharedTextPtr->tree;
-
- /*
- * Find the starting point.
- */
-
- leftToScan = matchOffset;
- while (1) {
- curIndex.linePtr = linePtr;
- curIndex.byteIndex = 0;
-
- /*
- * 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;
- } else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
- }
- }
- 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.
- */
-
- if (leftToScan == 0) {
- break;
- } else {
- Tcl_Panic("Reached end of text in a match");
- }
- }
-
- /*
- * We've wrapped to the beginning of the next logical line, which
- * has been merged with the previous one whose newline was elided.
- */
-
- lineNum++;
- matchOffset = 0;
- } else {
- break;
- }
- }
-
- /*
- * Calculate and store the found index in the result.
- */
-
- if (searchSpecPtr->exact) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
- } else {
- TkTextMakeCharIndex(textPtr->sharedTextPtr->tree, textPtr, lineNum,
- matchOffset, &foundIndex);
- }
-
- if (searchSpecPtr->all) {
- if (searchSpecPtr->resPtr == NULL) {
- searchSpecPtr->resPtr = Tcl_NewObj();
- }
- Tcl_ListObjAppendElement(NULL, searchSpecPtr->resPtr,
- TkTextNewIndexObj(textPtr, &foundIndex));
- } else {
- searchSpecPtr->resPtr = TkTextNewIndexObj(textPtr, &foundIndex);
- }
-
- /*
- * Find the end point. Here 'leftToScan' could be negative already as a
- * result of the above loop if the segment we reached spanned the start of
- * the string. When we add matchLength it will become non-negative.
- */
-
- for (leftToScan += matchLength; leftToScan > 0;
- curIndex.byteIndex += segPtr->size, segPtr = segPtr->nextPtr) {
- if (segPtr == NULL) {
- /*
- * 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);
- segPtr = linePtr->segPtr;
- curIndex.linePtr = linePtr; curIndex.byteIndex = 0;
- }
- if (segPtr->typePtr != &tkTextCharType) {
- /*
- * Anything we didn't count in the search needs adding.
- */
-
- numChars += segPtr->size;
- continue;
- } else if (!searchSpecPtr->searchElide
- && TkTextIsElided(textPtr, &curIndex, NULL)) {
- numChars += Tcl_NumUtfChars(segPtr->body.chars, -1);
- continue;
- }
- if (searchSpecPtr->exact) {
- leftToScan -= segPtr->size;
- } else {
- leftToScan -= Tcl_NumUtfChars(segPtr->body.chars, -1);
- }
- }
-
- /*
- * Now store the count result, if it is wanted.
- */
-
- if (searchSpecPtr->varPtr != NULL) {
- Tcl_Obj *tmpPtr = Tcl_NewIntObj(numChars);
- if (searchSpecPtr->all) {
- if (searchSpecPtr->countPtr == NULL) {
- searchSpecPtr->countPtr = Tcl_NewObj();
- }
- Tcl_ListObjAppendElement(NULL, searchSpecPtr->countPtr, tmpPtr);
- } else {
- searchSpecPtr->countPtr = tmpPtr;
- }
- }
- return 1;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TkTextGetTabs --
- *
- * Parses a string description of a set of tab stops.
- *
- * Results:
- * The return value is a pointer to a malloc'ed structure holding parsed
- * information about the tab stops. If an error occurred then the return
- * value is NULL and an error message is left in the interp's result.
- *
- * Side effects:
- * Memory is allocated for the structure that is returned. It is up to
- * the caller to free this structure when it is no longer needed.
- *
- *----------------------------------------------------------------------
- */
-
-TkTextTabArray *
-TkTextGetTabs(
- Tcl_Interp *interp, /* Used for error reporting. */
- TkText *textPtr, /* Information about the text widget. */
- Tcl_Obj *stringPtr) /* Description of the tab stops. See the text
- * manual entry for details. */
-{
- int objc, i, count;
- Tcl_Obj **objv;
- TkTextTabArray *tabArrayPtr;
- TkTextTab *tabPtr;
- int ch;
- double prevStop, lastStop;
- /*
- * Map these strings to TkTextTabAlign values.
- */
- static const char *const tabOptionStrings[] = {
- "left", "right", "center", "numeric", NULL
- };
-
- if (Tcl_ListObjGetElements(interp, stringPtr, &objc, &objv) != TCL_OK) {
- return NULL;
- }
-
- /*
- * First find out how many entries we need to allocate in the tab array.
- */
-
- count = 0;
- for (i = 0; i < objc; i++) {
- char c = Tcl_GetString(objv[i])[0];
-
- if ((c != 'l') && (c != 'r') && (c != 'c') && (c != 'n')) {
- count++;
- }
- }
-
- /*
- * Parse the elements of the list one at a time to fill in the array.
- */
-
- tabArrayPtr = ckalloc(sizeof(TkTextTabArray)
- + (count - 1) * sizeof(TkTextTab));
- tabArrayPtr->numTabs = 0;
- prevStop = 0.0;
- lastStop = 0.0;
- for (i = 0, tabPtr = &tabArrayPtr->tabs[0]; i < objc; i++, tabPtr++) {
- int index;
-
- /*
- * This will round fractional pixels above 0.5 upwards, and otherwise
- * downwards, to find the right integer pixel position.
- */
-
- if (Tk_GetPixelsFromObj(interp, textPtr->tkwin, objv[i],
- &tabPtr->location) != TCL_OK) {
- goto error;
- }
-
- if (tabPtr->location <= 0) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "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) {
- goto error;
- }
-
- if (i > 0 && (tabPtr->location <= (tabPtr-1)->location)) {
- /*
- * This tab is actually to the left of the previous one, which is
- * illegal.
- */
-
-#ifdef _TK_ALLOW_DECREASING_TABS
- /*
- * Force the tab to be a typical character width to the right of
- * the previous one, and update the 'lastStop' with the changed
- * position.
- */
-
- if (textPtr->charWidth > 0) {
- tabPtr->location = (tabPtr-1)->location + textPtr->charWidth;
- } else {
- tabPtr->location = (tabPtr-1)->location + 8;
- }
- lastStop = tabPtr->location;
-#else
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "tabs must be monotonically increasing, but \"%s\" is "
- "smaller than or equal to the previous tab",
- Tcl_GetString(objv[i])));
- Tcl_SetErrorCode(interp, "TK", "VALUE", "TAB_STOP", NULL);
- goto error;
-#endif /* _TK_ALLOW_DECREASING_TABS */
- }
-
- tabArrayPtr->numTabs++;
-
- /*
- * See if there is an explicit alignment in the next list element.
- * Otherwise just use "left".
- */
-
- tabPtr->alignment = LEFT;
- if ((i+1) == objc) {
- continue;
- }
-
- /*
- * There may be a more efficient way of getting this.
- */
-
- TkUtfToUniChar(Tcl_GetString(objv[i+1]), &ch);
- if (!Tcl_UniCharIsAlpha(ch)) {
- continue;
- }
- i += 1;
-
- if (Tcl_GetIndexFromObjStruct(interp, objv[i], tabOptionStrings,
- sizeof(char *), "tab alignment", 0, &index) != TCL_OK) {
- goto error;
- }
- tabPtr->alignment = (TkTextTabAlign) index;
- }
-
- /*
- * For when we need to interpolate tab stops, store these two so we know
- * the tab stop size to very high precision. With the above checks, we can
- * guarantee that tabIncrement is strictly positive here.
- */
-
- tabArrayPtr->lastTab = lastStop;
- tabArrayPtr->tabIncrement = lastStop - prevStop;
-
- return tabArrayPtr;
-
- error:
- ckfree(tabArrayPtr);
- return NULL;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextDumpCmd --
- *
- * Return information about the text, tags, marks, and embedded windows
- * and images in a text widget. See the man page for the description of
- * the text dump operation for all the details.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * Memory is allocated for the result, if needed (standard Tcl result
- * side effects).
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextDumpCmd(
- register TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. Someone else has already
- * parsed this command enough to know that
- * objv[1] is "dump". */
-{
- TkTextIndex index1, index2;
- int arg;
- int lineno; /* Current line number. */
- int what = 0; /* bitfield to select segment types. */
- int atEnd; /* True if dumping up to logical end. */
- 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",
- NULL
- };
- enum opts {
- DUMP_ALL, DUMP_CMD, DUMP_IMG, DUMP_MARK, DUMP_TAG, DUMP_TXT, DUMP_WIN
- };
-
- for (arg=2 ; arg < objc ; arg++) {
- int index;
- if (Tcl_GetString(objv[arg])[0] != '-') {
- break;
- }
- if (Tcl_GetIndexFromObjStruct(interp, objv[arg], optStrings,
- sizeof(char *), "option", 0, &index) != TCL_OK) {
- return TCL_ERROR;
- }
- switch ((enum opts) index) {
- case DUMP_ALL:
- what = TK_DUMP_ALL;
- 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;
- break;
- case DUMP_CMD:
- arg++;
- if (arg >= objc) {
- goto wrongArgs;
- }
- command = objv[arg];
- break;
- default:
- Tcl_Panic("unexpected switch fallthrough");
- }
- }
- 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 == 0) {
- what = TK_DUMP_ALL;
- }
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index1) != TCL_OK) {
- return TCL_ERROR;
- }
- arg++;
- atEnd = 0;
- if (objc == arg) {
- TkTextIndexForwChars(NULL, &index1, 1, &index2, COUNT_INDICES);
- } else {
- int length;
- const char *str;
-
- 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;
- }
- }
- if (TkTextIndexCmp(&index1, &index2) >= 0) {
- return TCL_OK;
- }
- lineno = TkBTreeLinesTo(textPtr, index1.linePtr);
- if (index1.linePtr == index2.linePtr) {
- DumpLine(interp, textPtr, what, index1.linePtr,
- index1.byteIndex, index2.byteIndex, lineno, command);
- } else {
- int textChanged;
- int lineend = TkBTreeLinesTo(textPtr, index2.linePtr);
- int endByteIndex = index2.byteIndex;
-
- textChanged = DumpLine(interp, textPtr, what, index1.linePtr,
- index1.byteIndex, 32000000, lineno, command);
- if (textChanged) {
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
- } else {
- linePtr = index1.linePtr;
- }
- while ((linePtr = TkBTreeNextLine(textPtr, linePtr)) != NULL) {
- lineno++;
- if (lineno == lineend) {
- break;
- }
- textChanged = DumpLine(interp, textPtr, what, linePtr, 0,
- 32000000, lineno, command);
- if (textChanged) {
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree,
- textPtr, lineno);
- textChanged = 0;
- }
- }
- if (linePtr != NULL) {
- DumpLine(interp, textPtr, what, linePtr, 0, endByteIndex, lineno,
- command);
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
- }
- }
-
- /*
- * Special case to get the leftovers hiding at the end mark.
- */
-
- if (atEnd) {
- if (textPtr->flags & DESTROYED) {
- return TCL_OK;
- }
-
- /*
- * Re-get the end index, in case it has changed.
- */
-
- if (TkTextGetObjIndex(interp, textPtr, objv[arg], &index2) != TCL_OK) {
- return TCL_ERROR;
- }
- DumpLine(interp, textPtr, what & ~TK_DUMP_TEXT, index2.linePtr,
- 0, 1, lineno, command);
- }
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * DumpLine
- *
- * Return information about a given text line from character position
- * "start" up to, but not including, "end".
- *
- * Results:
- * Returns 1 if the command callback made any changes to the text widget
- * which will have invalidated internal structures such as TkTextSegment,
- * TkTextIndex, pointers. Our caller can then take action to recompute
- * such entities. Returns 0 otherwise.
- *
- * Side effects:
- * None, but see DumpSegment which can have arbitrary side-effects
- *
- *----------------------------------------------------------------------
- */
-
-static int
-DumpLine(
- Tcl_Interp *interp,
- TkText *textPtr,
- int what, /* Bit flags to select segment types. */
- TkTextLine *linePtr, /* The current line. */
- int startByte, int endByte, /* Byte range to dump. */
- int lineno, /* Line number for indices dump. */
- Tcl_Obj *command) /* Script to apply to the segment. */
-{
- TkTextSegment *segPtr;
- TkTextIndex index;
- int offset = 0, textChanged = 0;
-
- /*
- * Must loop through line looking at its segments.
- * character
- * toggleOn, toggleOff
- * mark
- * image
- * window
- */
-
- segPtr = linePtr->segPtr;
- while ((offset < endByte) && (segPtr != NULL)) {
- int lineChanged = 0;
- int currentSize = segPtr->size;
-
- 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 (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.
- */
-
- int length = last - first;
- char *range = ckalloc(length + 1);
-
- memcpy(range, segPtr->body.chars + first, length);
- range[length] = '\0';
-
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset + first, &index);
- lineChanged = DumpSegment(textPtr, interp, "text", range,
- command, &index, what);
- ckfree(range);
- } else {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset + first, &index);
- lineChanged = DumpSegment(textPtr, interp, "text",
- segPtr->body.chars + first, command, &index, what);
- }
- } else if ((offset >= startByte)) {
- if ((what & TK_DUMP_MARK)
- && (segPtr->typePtr == &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 (name != NULL) {
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "mark", name,
- command, &index, what);
- }
- } else if ((what & TK_DUMP_TAG) &&
- (segPtr->typePtr == &tkTextToggleOnType)) {
- TkTextMakeByteIndex(textPtr->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);
- }
- TkTextMakeByteIndex(textPtr->sharedTextPtr->tree, textPtr,
- lineno, offset, &index);
- lineChanged = DumpSegment(textPtr, interp, "window", pathname,
- command, &index, what);
- }
- }
-
- offset += currentSize;
- if (lineChanged) {
- TkTextSegment *newSegPtr;
- int newOffset = 0;
-
- textChanged = 1;
-
- /*
- * Our indices are no longer valid.
- */
-
- 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;
- }
- }
- if (segPtr != newSegPtr && newOffset == offset
- && currentSize == 0) {
- TkTextSegment *searchPtr = newSegPtr;
-
- while (searchPtr != NULL && searchPtr->size == 0) {
- if (searchPtr == segPtr) {
- newSegPtr = searchPtr;
- break;
- }
- searchPtr = searchPtr->nextPtr;
- }
- }
- segPtr = newSegPtr;
- }
- }
- if (segPtr != NULL) {
- segPtr = segPtr->nextPtr;
- }
- }
- return textChanged;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * DumpSegment
- *
- * Either append information about the current segment to the result, or
- * make a script callback with that information as arguments.
- *
- * Results:
- * Returns 1 if the command callback made any changes to the text widget
- * which will have invalidated internal structures such as TkTextSegment,
- * TkTextIndex, pointers. Our caller can then take action to recompute
- * such entities. Returns 0 otherwise.
- *
- * Side effects:
- * Either evals the callback or appends elements to the result string.
- * The callback can have arbitrary side-effects.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-DumpSegment(
- TkText *textPtr,
- Tcl_Interp *interp,
- const char *key, /* Segment type key. */
- const char *value, /* Segment value. */
- Tcl_Obj *command, /* Script callback. */
- const TkTextIndex *index, /* index with line/byte position info. */
- int what) /* Look for TK_DUMP_INDEX bit. */
-{
- char buffer[TK_POS_CHARS];
- Tcl_Obj *values[3], *tuple;
-
- TkTextPrintIndex(textPtr, index, buffer);
- 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_ListObjAppendList(NULL, Tcl_GetObjResult(interp), tuple);
- Tcl_DecrRefCount(tuple);
- return 0;
- } else {
- int oldStateEpoch = TkBTreeEpoch(textPtr->sharedTextPtr->tree);
- Tcl_DString buf;
- int code;
-
- Tcl_DStringInit(&buf);
- Tcl_DStringAppend(&buf, Tcl_GetString(command), -1);
- Tcl_DStringAppend(&buf, " ", -1);
- Tcl_DStringAppend(&buf, Tcl_GetString(tuple), -1);
- 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_BackgroundException(interp, code);
- }
- Tcl_DecrRefCount(tuple);
- return ((textPtr->flags & DESTROYED) ||
- TkBTreeEpoch(textPtr->sharedTextPtr->tree) != oldStateEpoch);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextEditUndo --
- *
- * Undo the last change.
- *
- * 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).
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextEditUndo(
- TkText *textPtr) /* Overall information about text widget. */
-{
- int status;
-
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
- }
-
- /*
- * 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').
- */
-
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_UNDO;
- }
-
- status = TkUndoRevert(textPtr->sharedTextPtr->undoStack);
-
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
- }
- textPtr->sharedTextPtr->undo = 1;
-
- return status;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextEditRedo --
- *
- * Redo the last undone change.
- *
- * 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 redone).
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextEditRedo(
- TkText *textPtr) /* Overall information about text widget. */
-{
- int status;
-
- if (!textPtr->sharedTextPtr->undo) {
- return TCL_OK;
- }
-
- /*
- * 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').
- */
-
- textPtr->sharedTextPtr->undo = 0;
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_REDO;
- }
-
- status = TkUndoApply(textPtr->sharedTextPtr->undoStack);
-
- if (textPtr->sharedTextPtr->dirtyMode != TK_TEXT_DIRTY_FIXED) {
- textPtr->sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_NORMAL;
- }
- textPtr->sharedTextPtr->undo = 1;
- return status;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextEditCmd --
- *
- * Handle the subcommands to "$text edit ...". See documentation for
- * details.
- *
- * Results:
- * None
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-TextEditCmd(
- TkText *textPtr, /* Information about text widget. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument objects. */
-{
- int index, setModified, oldModified;
- int canRedo = 0;
- int canUndo = 0;
-
- static const char *const editOptionStrings[] = {
- "canundo", "canredo", "modified", "redo", "reset", "separator",
- "undo", NULL
- };
- enum editOptions {
- EDIT_CANUNDO, EDIT_CANREDO, EDIT_MODIFIED, EDIT_REDO, EDIT_RESET,
- EDIT_SEPARATOR, EDIT_UNDO
- };
-
- 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:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
- return TCL_ERROR;
- }
- if (textPtr->sharedTextPtr->undo) {
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- }
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canRedo));
- break;
- case EDIT_CANUNDO:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
- return TCL_ERROR;
- }
- if (textPtr->sharedTextPtr->undo) {
- canUndo = TkUndoCanUndo(textPtr->sharedTextPtr->undoStack);
- }
- Tcl_SetObjResult(interp, Tcl_NewBooleanObj(canUndo));
- break;
- case EDIT_MODIFIED:
- if (objc == 3) {
- Tcl_SetObjResult(interp,
- Tcl_NewBooleanObj(textPtr->sharedTextPtr->isDirty));
- 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) {
- return TCL_ERROR;
- }
-
- /*
- * Set or reset the dirty info, and trigger a Modified event.
- */
-
- setModified = setModified ? 1 : 0;
-
- 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;
- }
-
- /*
- * Only issue the <<Modified>> event if the flag actually changed.
- * However, degree of modified-ness doesn't matter. [Bug 1799782]
- */
-
- if ((!oldModified) != (!setModified)) {
- GenerateModifiedEvent(textPtr);
- }
- break;
- case EDIT_REDO:
- 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);
- return TCL_ERROR;
- }
- canRedo = TkUndoCanRedo(textPtr->sharedTextPtr->undoStack);
- if (!canUndo || !canRedo) {
- GenerateUndoStackEvent(textPtr);
- }
- break;
- case EDIT_RESET:
- if (objc != 3) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
- 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) {
- Tcl_WrongNumArgs(interp, 3, objv, NULL);
- return TCL_ERROR;
- }
- TkUndoInsertUndoSeparator(textPtr->sharedTextPtr->undoStack);
- 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);
- }
- break;
- }
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TextGetText --
- *
- * Returns the text from indexPtr1 to indexPtr2, placing that text in a
- * string object which is returned with a refCount of zero.
- *
- * Since the amount of text may potentially be several megabytes (e.g.
- * in text editors built on the text widget), efficiency is very
- * important. We may want to investigate the efficiency of the
- * Tcl_AppendToObj more carefully (e.g. if we know we are going to be
- * appending several thousand lines, we could attempt to pre-allocate a
- * larger space).
- *
- * Also the result is built up as a utf-8 string, but, if we knew we
- * wanted it as Unicode, we could potentially save a huge conversion by
- * building it up as Unicode directly. This could be as simple as
- * replacing Tcl_NewObj by Tcl_NewUnicodeObj.
- *
- * Results:
- * Tcl_Obj of string type containing the specified text. If the
- * visibleOnly flag is set to 1, then only those characters which are not
- * elided will be returned. Otherwise (flag is 0) all characters in the
- * given range are returned.
- *
- * Side effects:
- * Memory will be allocated for the new object. Remember to free it if it
- * isn't going to be stored appropriately.
- *
- *----------------------------------------------------------------------
- */
-
-static Tcl_Obj *
-TextGetText(
- const TkText *textPtr, /* Information about text widget. */
- const TkTextIndex *indexPtr1,
- /* Get text from this index... */
- const TkTextIndex *indexPtr2,
- /* ...to this index. */
- int visibleOnly) /* If non-zero, then only return non-elided
- * characters. */
-{
- TkTextIndex tmpIndex;
- Tcl_Obj *resultPtr = Tcl_NewObj();
-
- TkTextMakeByteIndex(indexPtr1->tree, textPtr,
- TkBTreeLinesTo(textPtr, indexPtr1->linePtr),
- indexPtr1->byteIndex, &tmpIndex);
-
- if (TkTextIndexCmp(indexPtr1, indexPtr2) < 0) {
- while (1) {
- int offset;
- TkTextSegment *segPtr = TkTextIndexToSeg(&tmpIndex, &offset);
- int last = segPtr->size, last2;
-
- 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.
- */
-
- if (indexPtr2->byteIndex == tmpIndex.byteIndex) {
- break;
- }
- last2 = indexPtr2->byteIndex - tmpIndex.byteIndex + offset;
- if (last2 < last) {
- last = last2;
- }
- }
- if (segPtr->typePtr == &tkTextCharType &&
- !(visibleOnly && TkTextIsElided(textPtr,&tmpIndex,NULL))){
- Tcl_AppendToObj(resultPtr, segPtr->body.chars + offset,
- last - offset);
- }
- TkTextIndexForwBytes(textPtr, &tmpIndex, last-offset, &tmpIndex);
- }
- }
- return resultPtr;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * GenerateModifiedEvent --
- *
- * Send an event that the text was modified. This is equivalent to:
- * event generate $textWidget <<Modified>>
- * for all peers of $textWidget.
- *
- * Results:
- * None
- *
- * Side effects:
- * May force the text window into existence.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-GenerateModifiedEvent(
- TkText *textPtr) /* Information about text widget. */
-{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
- Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "Modified", NULL);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * GenerateUndoStackEvent --
- *
- * 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.
- *
- * Results:
- * None
- *
- * Side effects:
- * May force the text window (and all peers) into existence.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-GenerateUndoStackEvent(
- TkText *textPtr) /* Information about text widget. */
-{
- for (textPtr = textPtr->sharedTextPtr->peers; textPtr != NULL;
- textPtr = textPtr->next) {
- Tk_MakeWindowExist(textPtr->tkwin);
- TkSendVirtualEvent(textPtr->tkwin, "UndoStack", NULL);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * UpdateDirtyFlag --
- *
- * Updates the dirtyness of the text widget
- *
- * Results:
- * None
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-UpdateDirtyFlag(
- TkSharedText *sharedTextPtr)/* Information about text widget. */
-{
- int oldDirtyFlag;
-
- /*
- * If we've been forced to be dirty, we stay dirty (until explicitly
- * reset, of course).
- */
-
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_FIXED) {
- return;
- }
-
- if (sharedTextPtr->isDirty < 0
- && sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_NORMAL) {
- /*
- * If dirty flag is negative, only redo operations can make it zero
- * again. If we do a normal operation, it can never become zero any
- * more (other than by explicit reset).
- */
-
- sharedTextPtr->dirtyMode = TK_TEXT_DIRTY_FIXED;
- return;
- }
-
- oldDirtyFlag = sharedTextPtr->isDirty;
- if (sharedTextPtr->dirtyMode == TK_TEXT_DIRTY_UNDO) {
- sharedTextPtr->isDirty--;
- } else {
- sharedTextPtr->isDirty++;
- }
-
- if (sharedTextPtr->isDirty == 0 || oldDirtyFlag == 0) {
- GenerateModifiedEvent(sharedTextPtr->peers);
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * RunAfterSyncCmd --
- *
- * This function is called by the event loop and executes the command
- * scheduled by [.text sync -command $cmd].
- *
- * Results:
- * None.
- *
- * Side effects:
- * Anything may happen, depending on $cmd contents.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-RunAfterSyncCmd(
- ClientData clientData) /* Information about text widget. */
-{
- register TkText *textPtr = (TkText *) clientData;
- int code;
-
- if ((textPtr->tkwin == NULL) || (textPtr->flags & DESTROYED)) {
- /*
- * The widget has been deleted. Don't do anything.
- */
-
- 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);
- }
- Tcl_Release((ClientData) textPtr->interp);
- Tcl_DecrRefCount(textPtr->afterSyncCmd);
- textPtr->afterSyncCmd = NULL;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * SearchPerform --
- *
- * Overall control of search process. Is given a pattern, a starting
- * index and an ending index, and attempts to perform a search. This
- * function is actually completely independent of Tk, and could in the
- * future be split off.
- *
- * Results:
- * Standard Tcl result code. In particular, if fromPtr or toPtr are not
- * considered valid by the 'lineIndexProc', an error will be thrown and
- * no search performed.
- *
- * Side effects:
- * See 'SearchCore'.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-SearchPerform(
- Tcl_Interp *interp, /* For error messages. */
- SearchSpec *searchSpecPtr, /* Search parameters. */
- Tcl_Obj *patObj, /* Contains an exact string or a regexp
- * pattern. Must have a refCount > 0. */
- Tcl_Obj *fromPtr, /* Contains information describing the first
- * index. */
- Tcl_Obj *toPtr) /* NULL or information describing the last
- * index. */
-{
- /*
- * 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) {
- return TCL_ERROR;
- }
-
- /*
- * Find the optional end location, similarly.
- */
-
- if (toPtr != NULL) {
- const TkTextIndex *indexToPtr, *indexFromPtr;
- TkText *textPtr = searchSpecPtr->clientData;
-
- indexToPtr = TkTextGetIndexFromObj(interp, textPtr, toPtr);
- if (indexToPtr == NULL) {
- return TCL_ERROR;
- }
- indexFromPtr = TkTextGetIndexFromObj(interp, textPtr, fromPtr);
-
- /*
- * Check for any empty search range here. It might be better in the
- * future to embed that in SearchCore (whose default behaviour is to
- * wrap when given a negative search range).
- */
-
- if (TkTextIndexCmp(indexFromPtr, indexToPtr) ==
- (searchSpecPtr->backwards ? -1 : 1)) {
- return TCL_OK;
- }
-
- if (searchSpecPtr->lineIndexProc(interp, toPtr, searchSpecPtr,
- &searchSpecPtr->stopLine,
- &searchSpecPtr->stopOffset) != TCL_OK) {
- return TCL_ERROR;
- }
- } else {
- searchSpecPtr->stopLine = -1;
- }
-
- /*
- * Scan through all of the lines of the text circularly, starting at the
- * given index. 'patObj' is the pattern which may be an exact string or a
- * regexp pattern depending on the flags in searchSpecPtr.
- */
-
- return SearchCore(interp, searchSpecPtr, patObj);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * SearchCore --
- *
- * The core of the search function. This function is actually completely
- * independent of Tk, and could in the future be split off.
- *
- * The function assumes regexp-based searches operate on Unicode strings,
- * and exact searches on utf-8 strings. Therefore the 'foundMatchProc'
- * and 'addLineProc' need to be aware of this distinction.
- *
- * Results:
- * Standard Tcl result code.
- *
- * Side effects:
- * Only those of the 'searchSpecPtr->foundMatchProc' which is called
- * whenever a match is found.
- *
- * Note that the way matching across multiple lines is implemented, we
- * start afresh with each line we have available, even though we may
- * already have examined the contents of that line (and further ones) if
- * we were attempting a multi-line match using the previous line. This
- * means there may be ways to speed this up a lot by not throwing away
- * all the multi-line information one has accumulated. Profiling should
- * be done to see where the bottlenecks lie before attempting this,
- * however. We would also need to be very careful such optimisation keep
- * within the specified search bounds.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-SearchCore(
- Tcl_Interp *interp, /* For error messages. */
- SearchSpec *searchSpecPtr, /* Search parameters. */
- Tcl_Obj *patObj) /* Contains an exact string or a regexp
- * pattern. Must have a refCount > 0. */
-{
- /*
- * For exact searches these are utf-8 char* offsets, for regexp searches
- * they are Unicode char offsets.
- */
-
- int firstOffset, lastOffset, matchOffset, matchLength;
- int passes;
- int lineNum = searchSpecPtr->startLine;
- int code = TCL_OK;
- Tcl_Obj *theLine;
- int alreadySearchOffset = -1;
-
- const char *pattern = NULL; /* For exact searches only. */
- int firstNewLine = -1; /* For exact searches only. */
- Tcl_RegExp regexp = NULL; /* For regexp searches only. */
-
- /*
- * These items are for backward regexp searches only. They are for two
- * purposes: to allow us to report backwards matches in the correct order,
- * even though the implementation uses repeated forward searches; and to
- * provide for overlap checking between backwards matches on different
- * text lines.
- */
-
-#define LOTS_OF_MATCHES 20
- int matchNum = LOTS_OF_MATCHES;
- int smArray[2 * LOTS_OF_MATCHES];
- int *storeMatch = smArray;
- int *storeLength = smArray + LOTS_OF_MATCHES;
- int lastBackwardsLineMatch = -1;
- int lastBackwardsMatchOffset = -1;
-
- if (searchSpecPtr->exact) {
- /*
- * Convert the pattern to lower-case if we're supposed to ignore case.
- */
-
- if (searchSpecPtr->noCase) {
- patObj = Tcl_DuplicateObj(patObj);
-
- /*
- * This can change the length of the string behind the object's
- * back, so ensure it is correctly synchronised.
- */
-
- Tcl_SetObjLength(patObj, Tcl_UtfToLower(Tcl_GetString(patObj)));
- }
- } else {
- /*
- * Compile the regular expression. We want '^$' to match after and
- * before \n respectively, so use the TCL_REG_NLANCH flag.
- */
-
- regexp = Tcl_GetRegExpFromObj(interp, patObj,
- (searchSpecPtr->noCase ? TCL_REG_NOCASE : 0)
- | (searchSpecPtr->noLineStop ? 0 : TCL_REG_NLSTOP)
- | TCL_REG_ADVANCED | TCL_REG_CANMATCH | TCL_REG_NLANCH);
- if (regexp == NULL) {
- return TCL_ERROR;
- }
- }
-
- /*
- * For exact strings, we want to know where the first newline is, and we
- * will also use this as a flag to test whether it is even possible to
- * match the pattern on a single line. If not we will have to search
- * across multiple lines.
- */
-
- if (searchSpecPtr->exact) {
- const char *nl;
-
- /*
- * We only need to set the matchLength once for exact searches, and we
- * do it here. It is also used below as the actual pattern length, so
- * it has dual purpose.
- */
-
- pattern = Tcl_GetString(patObj);
- matchLength = patObj->length;
- nl = strchr(pattern, '\n');
-
- /*
- * If there is no newline, or it is the very end of the string, then
- * we don't need any special treatment, since single-line matching
- * will work fine.
- */
-
- if (nl != NULL && nl[1] != '\0') {
- firstNewLine = (nl - pattern);
- }
- } else {
- matchLength = 0; /* Only needed to prevent compiler warnings. */
- }
-
- /*
- * Keep a reference here, so that we can be sure the object doesn't
- * disappear behind our backs and invalidate its contents which we are
- * using.
- */
-
- Tcl_IncrRefCount(patObj);
-
- /*
- * For building up the current line being checked.
- */
-
- theLine = Tcl_NewObj();
- Tcl_IncrRefCount(theLine);
-
- for (passes = 0; passes < 2; ) {
- ClientData lineInfo;
- int linesSearched = 1;
- int extraLinesSearched = 0;
-
- if (lineNum >= searchSpecPtr->numLines) {
- /*
- * Don't search the dummy last line of the text.
- */
-
- goto nextLine;
- }
-
- /*
- * Extract the text from the line, storing its length in 'lastOffset'
- * (in bytes if exact, chars if regexp), since obviously the length is
- * the maximum offset at which it is possible to find something on
- * this line, which is what 'lastOffset' represents.
- */
-
- lineInfo = searchSpecPtr->addLineProc(lineNum, searchSpecPtr, theLine,
- &lastOffset, &linesSearched);
-
- if (lineInfo == NULL) {
- /*
- * This should not happen, since 'lineNum' should be valid in the
- * call above. However, let's try to be flexible and not cause a
- * crash below.
- */
-
- goto nextLine;
- }
-
- if (lineNum == searchSpecPtr->stopLine && searchSpecPtr->backwards) {
- firstOffset = searchSpecPtr->stopOffset;
- } else {
- firstOffset = 0;
- }
-
- if (alreadySearchOffset != -1) {
- if (searchSpecPtr->backwards) {
- if (alreadySearchOffset < lastOffset) {
- lastOffset = alreadySearchOffset;
- }
- } else {
- if (alreadySearchOffset > firstOffset) {
- firstOffset = alreadySearchOffset;
- }
- }
- alreadySearchOffset = -1;
- }
-
- if (lineNum == searchSpecPtr->startLine) {
- /*
- * The starting line is tricky: the first time we see it we check
- * one part of the line, and the second pass through we check the
- * other part of the line.
- */
-
- passes++;
- if ((passes == 1) ^ searchSpecPtr->backwards) {
- /*
- * Forward search and first pass, or backward search and
- * second pass.
- *
- * Only use the last part of the line.
- */
-
- if (searchSpecPtr->startOffset > firstOffset) {
- firstOffset = searchSpecPtr->startOffset;
- }
- if ((firstOffset >= lastOffset)
- && ((lastOffset != 0) || searchSpecPtr->exact)) {
- goto nextLine;
- }
- } else {
- /*
- * Use only the first part of the line.
- */
-
- if (searchSpecPtr->startOffset < lastOffset) {
- lastOffset = searchSpecPtr->startOffset;
- }
- }
- }
-
- /*
- * Check for matches within the current line 'lineNum'. If so, and if
- * we're searching backwards or for all matches, repeat the search
- * until we find the last match in the line. The 'lastOffset' is one
- * beyond the last position in the line at which a match is allowed to
- * begin.
- */
-
- matchOffset = -1;
-
- if (searchSpecPtr->exact) {
- int maxExtraLines = 0;
- const char *startOfLine = Tcl_GetString(theLine);
-
- CLANG_ASSERT(pattern);
- do {
- int ch;
- const char *p;
- int lastFullLine = lastOffset;
-
- if (firstNewLine == -1) {
- if (searchSpecPtr->strictLimits
- && (firstOffset + matchLength > lastOffset)) {
- /*
- * Not enough characters to match.
- */
-
- break;
- }
-
- /*
- * Single line matching. We want to scan forwards or
- * backwards as appropriate.
- */
-
- if (searchSpecPtr->backwards) {
- /*
- * Search back either from the previous match or from
- * 'startOfLine + lastOffset - 1' until we find a
- * match.
- */
-
- const char c = pattern[0];
-
- if (alreadySearchOffset != -1) {
- p = startOfLine + alreadySearchOffset;
- alreadySearchOffset = -1;
- } else {
- p = startOfLine + lastOffset -1;
- }
- while (p >= startOfLine + firstOffset) {
- if (p[0] == c && !strncmp(p, pattern,
- (unsigned) matchLength)) {
- goto backwardsMatch;
- }
- p--;
- }
- break;
- } else {
- p = strstr(startOfLine + firstOffset, pattern);
- }
- if (p == NULL) {
- /*
- * Single line match failed.
- */
-
- break;
- }
- } else if (firstNewLine >= (lastOffset - firstOffset)) {
- /*
- * Multi-line match, but not enough characters to match.
- */
-
- break;
- } else {
- /*
- * Multi-line match has only one possible match position,
- * because we know where the '\n' is.
- */
-
- p = startOfLine + lastOffset - firstNewLine - 1;
- if (strncmp(p, pattern, (unsigned) firstNewLine + 1)) {
- /*
- * No match.
- */
-
- break;
- } else {
- int extraLines = 1;
-
- /*
- * If we find a match that overlaps more than one
- * line, we will use this value to determine the first
- * allowed starting offset for the following search
- * (to avoid overlapping results).
- */
-
- int lastTotal = lastOffset;
- int skipFirst = lastOffset - firstNewLine -1;
-
- /*
- * We may be able to match if given more text. The
- * following 'while' block handles multi-line exact
- * searches.
- */
-
- while (1) {
- lastFullLine = lastTotal;
-
- if (lineNum+extraLines>=searchSpecPtr->numLines) {
- p = NULL;
- break;
- }
-
- /*
- * Only add the line if we haven't already done so
- * already.
- */
-
- if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
- p = NULL;
- if (!searchSpecPtr->backwards) {
- extraLinesSearched = extraLines;
- }
- break;
- }
- maxExtraLines = extraLines;
- }
-
- startOfLine = Tcl_GetString(theLine);
- p = startOfLine + skipFirst;
-
- /*
- * Use the fact that 'matchLength = patLength' for
- * exact searches.
- */
-
- if ((lastTotal - skipFirst) >= matchLength) {
- /*
- * We now have enough text to match, so we
- * make a final test and break whatever the
- * result.
- */
-
- if (strncmp(p,pattern,(unsigned)matchLength)) {
- p = NULL;
- }
- break;
- } else {
- /*
- * Not enough text yet, but check the prefix.
- */
-
- if (strncmp(p, pattern,
- (unsigned)(lastTotal - skipFirst))) {
- p = NULL;
- break;
- }
-
- /*
- * The prefix matches, so keep looking.
- */
- }
- extraLines++;
- }
- /*
- * If we reach here, with p != NULL, we've found a
- * multi-line match, else we started a multi-match but
- * didn't finish it off, so we go to the next line.
- */
-
- if (p == NULL) {
- break;
- }
-
- /*
- * We've found a multi-line match.
- */
-
- if (extraLines > 0) {
- extraLinesSearched = extraLines - 1;
- }
- }
- }
-
- backwardsMatch:
- if ((p - startOfLine) >= lastOffset) {
- break;
- }
-
- /*
- * Remember the match.
- */
-
- matchOffset = p - startOfLine;
-
- if (searchSpecPtr->all &&
- !searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr,
- lineInfo, theLine, matchOffset, matchLength)) {
- /*
- * We reached the end of the search.
- */
-
- goto searchDone;
- }
-
- if (!searchSpecPtr->overlap) {
- if (searchSpecPtr->backwards) {
- alreadySearchOffset = p - startOfLine;
- if (firstNewLine != -1) {
- break;
- } else {
- alreadySearchOffset -= matchLength;
- }
- } else {
- firstOffset = p - startOfLine + matchLength;
- if (firstOffset >= lastOffset) {
- /*
- * Now, we have to be careful not to find
- * overlapping matches either on the same or
- * following lines. Assume that if we did find
- * something, it goes until the last extra line we
- * added.
- *
- * We can break out of the loop, since we know no
- * more will be found.
- */
-
- if (!searchSpecPtr->backwards) {
- alreadySearchOffset =
- firstOffset - lastFullLine;
- break;
- }
- }
- }
- } else {
- if (searchSpecPtr->backwards) {
- alreadySearchOffset = p - startOfLine - 1;
- if (alreadySearchOffset < 0) {
- break;
- }
- } else {
- firstOffset = p - startOfLine +
- TkUtfToUniChar(startOfLine+matchOffset,&ch);
- }
- }
- } while (searchSpecPtr->all);
- } else {
- int maxExtraLines = 0;
- int matches = 0;
- int lastNonOverlap = -1;
-
- do {
- Tcl_RegExpInfo info;
- int match;
- int lastFullLine = lastOffset;
-
- match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1, (firstOffset>0 ? TCL_REG_NOTBOL : 0));
- if (match < 0) {
- code = TCL_ERROR;
- goto searchDone;
- }
- Tcl_RegExpGetInfo(regexp, &info);
-
- /*
- * If we don't have a match, or if we do, but it extends to
- * the end of the line, we must try to add more lines to get a
- * full greedy match.
- */
-
- if (!match ||
- ((info.extendStart == info.matches[0].start)
- && (info.matches[0].end == lastOffset-firstOffset))) {
- int extraLines = 0;
- int prevFullLine;
-
- /*
- * If we find a match that overlaps more than one line, we
- * will use this value to determine the first allowed
- * starting offset for the following search (to avoid
- * overlapping results).
- */
-
- int lastTotal = lastOffset;
-
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch == (lineNum + 1))) {
- lastNonOverlap = lastTotal;
- }
-
- if (info.extendStart < 0) {
- /*
- * No multi-line match is possible.
- */
-
- break;
- }
-
- /*
- * We may be able to match if given more text. The
- * following 'while' block handles multi-line regexp
- * searches.
- */
-
- while (1) {
- prevFullLine = lastTotal;
-
- /*
- * Move firstOffset to first possible start.
- */
-
- if (!match) {
- firstOffset += info.extendStart;
- }
- if (firstOffset >= lastOffset) {
- /*
- * We're being told that the only possible new
- * match is starting after the end of the line.
- * But, that is the next line which we will handle
- * when we look at that line.
- */
-
- if (!match && !searchSpecPtr->backwards
- && (firstOffset == 0)) {
- extraLinesSearched = extraLines;
- }
- break;
- }
-
- if (lineNum + extraLines >= searchSpecPtr->numLines) {
- break;
- }
-
- /*
- * Add next line, provided we haven't already done so.
- */
-
- if (extraLines > maxExtraLines) {
- if (searchSpecPtr->addLineProc(lineNum
- + extraLines, searchSpecPtr, theLine,
- &lastTotal, &extraLines) == NULL) {
- /*
- * There are no more acceptable lines, so we
- * can say we have searched all of these.
- */
-
- if (!match && !searchSpecPtr->backwards) {
- extraLinesSearched = extraLines;
- }
- break;
- }
-
- maxExtraLines = extraLines;
- if ((lastBackwardsLineMatch != -1)
- && (lastBackwardsLineMatch
- == (lineNum + extraLines + 1))) {
- lastNonOverlap = lastTotal;
- }
- }
-
- match = Tcl_RegExpExecObj(interp, regexp, theLine,
- firstOffset, 1,
- ((firstOffset > 0) ? TCL_REG_NOTBOL : 0));
- if (match < 0) {
- code = TCL_ERROR;
- goto searchDone;
- }
- Tcl_RegExpGetInfo(regexp, &info);
-
- /*
- * Unfortunately there are bugs in Tcl's regexp
- * library, which tells us that info.extendStart is
- * zero when it should not be (should be -1), which
- * makes our task a bit more complicated here. We
- * check if there was a match, and the end of the
- * match leaves an entire extra line unmatched, then
- * we stop searching. Clearly it still might sometimes
- * be possible to add more text and match again, but
- * Tcl's regexp library doesn't tell us that.
- *
- * This means we often add and search one more line
- * than might be necessary if Tcl were able to give us
- * a correct value of info.extendStart under all
- * circumstances.
- */
-
- if ((match &&
- firstOffset+info.matches[0].end != lastTotal &&
- firstOffset+info.matches[0].end < prevFullLine)
- || info.extendStart < 0) {
- break;
- }
-
- /*
- * If there is a match, but that match starts after
- * the end of the first line, then we'll handle that
- * next time around, when we're actually looking at
- * that line.
- */
-
- if (match && (info.matches[0].start >= lastOffset)) {
- break;
- }
- if (match && ((firstOffset + info.matches[0].end)
- >= prevFullLine)) {
- if (extraLines > 0) {
- extraLinesSearched = extraLines - 1;
- }
- lastFullLine = prevFullLine;
- }
-
- /*
- * The prefix matches, so keep looking.
- */
-
- extraLines++;
- }
-
- /*
- * If we reach here with 'match == 1', we've found a
- * multi-line match, which we will record in the code
- * which follows directly else we started a multi-line
- * match but didn't finish it off, so we go to the next
- * line.
- */
-
- if (!match) {
- /*
- * Here is where we could perform an optimisation,
- * since we have already retrieved the contents of the
- * next line (perhaps many more), so we shouldn't
- * really throw it all away and start again. This
- * could be particularly important for complex regexp
- * searches.
- *
- * This 'break' will take us to just before the
- * 'nextLine:' below.
- */
-
- break;
- }
-
- if (lastBackwardsLineMatch != -1) {
- if ((lineNum + linesSearched + extraLinesSearched)
- == lastBackwardsLineMatch) {
- /*
- * Possible overlap or inclusion.
- */
-
- int thisOffset = firstOffset + info.matches[0].end
- - info.matches[0].start;
-
- if (lastNonOverlap != -1) {
- /*
- * Possible overlap or enclosure.
- */
-
- if (thisOffset-lastNonOverlap >=
- lastBackwardsMatchOffset+matchLength){
- /*
- * Totally encloses previous match, so
- * forget the previous match.
- */
-
- lastBackwardsLineMatch = -1;
- } else if ((thisOffset - lastNonOverlap)
- > lastBackwardsMatchOffset) {
- /*
- * Overlap. Previous match is ok, and the
- * current match is only ok if we are
- * searching with -overlap.
- */
-
- if (searchSpecPtr->overlap) {
- goto recordBackwardsMatch;
- } else {
- match = 0;
- break;
- }
- } else {
- /*
- * No overlap, although the same line was
- * reached.
- */
-
- goto recordBackwardsMatch;
- }
- } else {
- /*
- * No overlap.
- */
-
- goto recordBackwardsMatch;
- }
- } else if (lineNum+linesSearched+extraLinesSearched
- < lastBackwardsLineMatch) {
- /*
- * No overlap.
- */
-
- goto recordBackwardsMatch;
- } else {
- /*
- * Totally enclosed.
- */
-
- lastBackwardsLineMatch = -1;
- }
- }
-
- } else {
- /*
- * Matched in a single line.
- */
-
- if (lastBackwardsLineMatch != -1) {
- recordBackwardsMatch:
- searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
- lastBackwardsLineMatch = -1;
- if (!searchSpecPtr->all) {
- goto searchDone;
- }
- }
- }
-
- firstOffset += info.matches[0].start;
- if (firstOffset >= lastOffset) {
- break;
- }
-
- /*
- * Update our local variables with the match, if we haven't
- * yet found anything, or if we're doing '-all' or
- * '-backwards' _and_ this match isn't fully enclosed in the
- * previous match.
- */
-
- if (matchOffset == -1 ||
- ((searchSpecPtr->all || searchSpecPtr->backwards)
- && ((firstOffset < matchOffset)
- || ((firstOffset + info.matches[0].end
- - info.matches[0].start)
- > (matchOffset + matchLength))))) {
-
- matchOffset = firstOffset;
- matchLength = info.matches[0].end - info.matches[0].start;
-
- if (searchSpecPtr->backwards) {
- /*
- * To get backwards searches in the correct order, we
- * must store them away here.
- */
-
- if (matches == matchNum) {
- /*
- * We've run out of space in our normal store, so
- * we must allocate space for these backwards
- * matches on the heap.
- */
-
- int *newArray =
- ckalloc(4 * matchNum * sizeof(int));
- memcpy(newArray, storeMatch, matchNum*sizeof(int));
- memcpy(newArray + 2*matchNum, storeLength,
- matchNum * sizeof(int));
- if (storeMatch != smArray) {
- ckfree(storeMatch);
- }
- matchNum *= 2;
- storeMatch = newArray;
- storeLength = newArray + matchNum;
- }
- storeMatch[matches] = matchOffset;
- storeLength[matches] = matchLength;
- matches++;
- } else {
- /*
- * Now actually record the match, but only if we are
- * doing an '-all' search.
- */
-
- if (searchSpecPtr->all &&
- !searchSpecPtr->foundMatchProc(lineNum,
- searchSpecPtr, lineInfo, theLine, matchOffset,
- matchLength)) {
- /*
- * We reached the end of the search.
- */
-
- goto searchDone;
- }
- }
-
- /*
- * For forward matches, unless we allow overlaps, we move
- * this on by the length of the current match so that we
- * explicitly disallow overlapping matches.
- */
-
- if (matchLength > 0 && !searchSpecPtr->overlap
- && !searchSpecPtr->backwards) {
- firstOffset += matchLength;
- if (firstOffset >= lastOffset) {
- /*
- * Now, we have to be careful not to find
- * overlapping matches either on the same or
- * following lines. Assume that if we did find
- * something, it goes until the last extra line we
- * added.
- *
- * We can break out of the loop, since we know no
- * more will be found.
- */
-
- alreadySearchOffset = firstOffset - lastFullLine;
- break;
- }
-
- /*
- * We'll add this on again just below.
- */
-
- firstOffset --;
- }
- }
-
- /*
- * Move the starting point on, in case we are doing repeated
- * or backwards searches (for the latter, we actually do
- * repeated forward searches).
- */
-
- firstOffset++;
- } while (searchSpecPtr->backwards || searchSpecPtr->all);
-
- if (matches > 0) {
- /*
- * Now we have all the matches in our array, but not stored
- * with 'foundMatchProc' yet.
- */
-
- matches--;
- matchOffset = storeMatch[matches];
- matchLength = storeLength[matches];
- while (--matches >= 0) {
- if (lineNum == searchSpecPtr->stopLine) {
- /*
- * It appears as if a condition like:
- *
- * if (storeMatch[matches]<searchSpecPtr->stopOffset)
- * break;
- *
- * might be needed here, but no test case has been
- * found which would exercise such a problem.
- */
- }
- if (storeMatch[matches] + storeLength[matches]
- >= matchOffset + matchLength) {
- /*
- * The new match totally encloses the previous one, so
- * we overwrite the previous one.
- */
-
- matchOffset = storeMatch[matches];
- matchLength = storeLength[matches];
- continue;
- }
- if (!searchSpecPtr->overlap) {
- if (storeMatch[matches] + storeLength[matches]
- > matchOffset) {
- continue;
- }
- }
- searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr,
- lineInfo, theLine, matchOffset, matchLength);
- if (!searchSpecPtr->all) {
- goto searchDone;
- }
- matchOffset = storeMatch[matches];
- matchLength = storeLength[matches];
- }
- if (searchSpecPtr->all && matches > 0) {
- /*
- * We only need to do this for the '-all' case, because
- * just below we will call the foundMatchProc for the
- * non-all case.
- */
-
- searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr,
- lineInfo, theLine, matchOffset, matchLength);
- } else {
- lastBackwardsLineMatch = lineNum;
- lastBackwardsMatchOffset = matchOffset;
- }
- }
- }
-
- /*
- * If the 'all' flag is set, we will already have stored all matches,
- * so we just proceed to the next line.
- *
- * If not, and there is a match we need to store that information and
- * we are done.
- */
-
- if ((lastBackwardsLineMatch == -1) && (matchOffset >= 0)
- && !searchSpecPtr->all) {
- searchSpecPtr->foundMatchProc(lineNum, searchSpecPtr, lineInfo,
- theLine, matchOffset, matchLength);
- goto searchDone;
- }
-
- /*
- * Go to the next (or previous) line;
- */
-
- nextLine:
- linesSearched += extraLinesSearched;
-
- while (linesSearched-- > 0) {
- /*
- * If we have just completed the 'stopLine', we are done.
- */
-
- if (lineNum == searchSpecPtr->stopLine) {
- goto searchDone;
- }
-
- if (searchSpecPtr->backwards) {
- lineNum--;
-
- if (lastBackwardsLineMatch != -1
- && ((lineNum < 0)
- || (lineNum + 2 < lastBackwardsLineMatch))) {
- searchSpecPtr->foundMatchProc(lastBackwardsLineMatch,
- searchSpecPtr, NULL, NULL,
- lastBackwardsMatchOffset, matchLength);
- lastBackwardsLineMatch = -1;
- if (!searchSpecPtr->all) {
- goto searchDone;
- }
- }
-
- if (lineNum < 0) {
- lineNum = searchSpecPtr->numLines-1;
- }
- if (!searchSpecPtr->exact) {
- /*
- * The 'exact' search loops above are designed to give us
- * an accurate picture of the number of lines which we can
- * skip here. For 'regexp' searches, on the other hand,
- * which can match potentially variable lengths, we cannot
- * skip multiple lines when searching backwards. Therefore
- * we only allow one line to be skipped here.
- */
-
- break;
- }
- } else {
- lineNum++;
- if (lineNum >= searchSpecPtr->numLines) {
- lineNum = 0;
- }
- }
- if (lineNum == searchSpecPtr->startLine && linesSearched > 0) {
- /*
- * We've just searched all the way round and have gone right
- * through the start line without finding anything in the last
- * attempt.
- */
-
- break;
- }
- }
-
- Tcl_SetObjLength(theLine, 0);
- }
- searchDone:
-
- if (lastBackwardsLineMatch != -1) {
- searchSpecPtr->foundMatchProc(lastBackwardsLineMatch, searchSpecPtr,
- NULL, NULL, lastBackwardsMatchOffset, matchLength);
- }
-
- /*
- * Free up the cached line and pattern.
- */
-
- Tcl_DecrRefCount(theLine);
- Tcl_DecrRefCount(patObj);
-
- /*
- * Free up any extra space we allocated.
- */
-
- if (storeMatch != smArray) {
- ckfree(storeMatch);
- }
-
- return code;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * GetLineStartEnd -
- *
- * Converts an internal TkTextLine ptr into a Tcl string obj containing
- * the line number. (Handler for the 'line' configuration option type.)
- *
- * Results:
- * Tcl_Obj containing the string representation of the line value.
- *
- * Side effects:
- * Creates a new Tcl_Obj.
- *
- *----------------------------------------------------------------------
- */
-
-static Tcl_Obj *
-GetLineStartEnd(
- ClientData clientData,
- Tk_Window tkwin,
- char *recordPtr, /* Pointer to widget record. */
- int internalOffset) /* Offset within *recordPtr containing the
- * line value. */
-{
- TkTextLine *linePtr = *(TkTextLine **)(recordPtr + internalOffset);
-
- if (linePtr == NULL) {
- return Tcl_NewObj();
- }
- return Tcl_NewIntObj(1 + TkBTreeLinesTo(NULL, linePtr));
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * SetLineStartEnd --
- *
- * Converts a Tcl_Obj representing a widget's (start or end) line into a
- * TkTextLine* value. (Handler for the 'line' configuration option type.)
- *
- * Results:
- * Standard Tcl result.
- *
- * Side effects:
- * May store the TkTextLine* value into the internal representation
- * pointer. May change the pointer to the Tcl_Obj to NULL to indicate
- * that the specified string was empty and that is acceptable.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-SetLineStartEnd(
- ClientData clientData,
- Tcl_Interp *interp, /* Current interp; may be used for errors. */
- Tk_Window tkwin, /* Window for which option is being set. */
- Tcl_Obj **value, /* Pointer to the pointer to the value object.
- * We use a pointer to the pointer because we
- * may need to return a value (NULL). */
- char *recordPtr, /* Pointer to storage for the widget record. */
- int internalOffset, /* Offset within *recordPtr at which the
- * internal value is to be stored. */
- char *oldInternalPtr, /* Pointer to storage for the old value. */
- int flags) /* Flags for the option, set Tk_SetOptions. */
-{
- TkTextLine *linePtr = NULL;
- char *internalPtr;
- TkText *textPtr = (TkText *) recordPtr;
-
- if (internalOffset >= 0) {
- internalPtr = recordPtr + internalOffset;
- } else {
- internalPtr = NULL;
- }
-
- if (flags & TK_OPTION_NULL_OK && ObjectIsEmpty(*value)) {
- *value = NULL;
- } else {
- int line;
-
- if (Tcl_GetIntFromObj(interp, *value, &line) != TCL_OK) {
- return TCL_ERROR;
- }
- linePtr = TkBTreeFindLine(textPtr->sharedTextPtr->tree, NULL, line-1);
- }
-
- if (internalPtr != NULL) {
- *((TkTextLine **) oldInternalPtr) = *((TkTextLine **) internalPtr);
- *((TkTextLine **) internalPtr) = linePtr;
- }
- return TCL_OK;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * RestoreLineStartEnd --
- *
- * Restore a line option value from a saved value. (Handler for the
- * 'line' configuration option type.)
- *
- * Results:
- * None.
- *
- * Side effects:
- * Restores the old value.
- *
- *----------------------------------------------------------------------
- */
-
-static void
-RestoreLineStartEnd(
- ClientData clientData,
- Tk_Window tkwin,
- char *internalPtr, /* Pointer to storage for value. */
- char *oldInternalPtr) /* Pointer to old value. */
-{
- *(TkTextLine **)internalPtr = *(TkTextLine **)oldInternalPtr;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ObjectIsEmpty --
- *
- * This function tests whether the string value of an object is empty.
- *
- * Results:
- * The return value is 1 if the string value of objPtr has length zero,
- * and 0 otherwise.
- *
- * Side effects:
- * May cause object shimmering, since this function can force a
- * conversion to a string object.
- *
- *----------------------------------------------------------------------
- */
-
-static int
-ObjectIsEmpty(
- Tcl_Obj *objPtr) /* Object to test. May be NULL. */
-{
- if (objPtr == NULL) {
- return 1;
- }
- if (objPtr->bytes != NULL) {
- return (objPtr->length == 0);
- }
- (void)Tcl_GetString(objPtr);
- return (objPtr->length == 0);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * TkpTesttextCmd --
- *
- * This function implements the "testtext" command. It provides a set of
- * functions for testing text widgets and the associated functions in
- * tkText*.c.
- *
- * Results:
- * A standard Tcl result.
- *
- * Side effects:
- * Depends on option; see below.
- *
- *----------------------------------------------------------------------
- */
-
-int
-TkpTesttextCmd(
- ClientData clientData, /* Main window for application. */
- Tcl_Interp *interp, /* Current interpreter. */
- int objc, /* Number of arguments. */
- Tcl_Obj *const objv[]) /* Argument strings. */
-{
- TkText *textPtr;
- size_t len;
- int lineIndex, byteIndex, byteOffset;
- TkTextIndex index;
- char buf[64];
- Tcl_CmdInfo info;
-
- if (objc < 3) {
- return TCL_ERROR;
- }
-
- if (Tcl_GetCommandInfo(interp, Tcl_GetString(objv[1]), &info) == 0) {
- return TCL_ERROR;
- }
- textPtr = info.objClientData;
- len = strlen(Tcl_GetString(objv[2]));
- if (strncmp(Tcl_GetString(objv[2]), "byteindex", len) == 0) {
- if (objc != 5) {
- return TCL_ERROR;
- }
- lineIndex = atoi(Tcl_GetString(objv[3])) - 1;
- byteIndex = atoi(Tcl_GetString(objv[4]));
-
- 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) {
- return TCL_ERROR;
- }
- byteOffset = atoi(Tcl_GetString(objv[4]));
- TkTextIndexForwBytes(textPtr, &index, byteOffset, &index);
- } else if (strncmp(Tcl_GetString(objv[2]), "backbytes", len) == 0) {
- if (objc != 5) {
- return TCL_ERROR;
- }
- if (TkTextGetIndex(interp, textPtr, Tcl_GetString(objv[3]), &index) != TCL_OK) {
- return TCL_ERROR;
- }
- byteOffset = atoi(Tcl_GetString(objv[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));
- return TCL_OK;
-}
-
-/*
- * Local Variables:
- * mode: c
- * c-basic-offset: 4
- * fill-column: 78
- * End:
- */