/* * tkTextIndex.c -- * * This module provides procedures that manipulate indices for * text widgets. * * Copyright (c) 1992-1993 The Regents of the University of California. * All rights reserved. * * Permission is hereby granted, without written agreement and without * license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the * above copyright notice and the following two paragraphs appear in * all copies of this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY * AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ #ifndef lint static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextIndex.c,v 1.4 93/08/18 16:26:01 ouster Exp $ SPRITE (Berkeley)"; #endif #include "default.h" #include "tkConfig.h" #include "tkInt.h" #include "tkText.h" /* * Forward declarations for procedures defined later in this file: */ static void BackwardChars _ANSI_ARGS_((TkText *textPtr, TkTextLine *linePtr, int *lineIndexPtr, int *chPtr, int count)); static char * ForwBack _ANSI_ARGS_((TkText *textPtr, char *string, int *lineIndexPtr, int *chPtr)); static void ForwardChars _ANSI_ARGS_((TkText *textPtr, TkTextLine *linePtr, int *lineIndexPtr, int *chPtr, int count)); static char * StartEnd _ANSI_ARGS_((TkText *textPtr, char *string, int *lineIndexPtr, int *chPtr)); /* *---------------------------------------------------------------------- * * TkTextGetIndex -- * * Given a string, return the line and character indices that * it describes. * * Results: * The return value is a standard Tcl return result. If * TCL_OK is returned, then everything went well and information * is stored at *lineIndexPtr and *chPtr; otherwise TCL_ERROR * is returned and an error message is left in interp->result. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TkTextGetIndex(interp, textPtr, string, lineIndexPtr, chPtr) Tcl_Interp *interp; /* Use this for error reporting. */ TkText *textPtr; /* Information about text widget. */ char *string; /* Textual description of position. */ int *lineIndexPtr; /* Store line number here. */ int *chPtr; /* Store character position here. */ { register char *p; char *end, *endOfBase; TkTextLine *linePtr; Tcl_HashEntry *hPtr; TkAnnotation *markPtr; TkTextTag *tagPtr; TkTextSearch search; int first; char c; /* *------------------------------------------------ * Stage 1: parse the base index. *------------------------------------------------ */ if (string[0] == '@') { /* * Find character at a given x,y location in the window. */ int x, y; p = string+1; x = strtol(p, &end, 0); if ((end == p) || (*end != ',')) { goto error; } p = end+1; y = strtol(p, &end, 0); if (end == p) { goto error; } *lineIndexPtr = TkBTreeLineIndex(TkTextCharAtLoc(textPtr, x, y, chPtr)); endOfBase = end; goto gotBase; } else if (isdigit(UCHAR(string[0])) || (string[0] == '-')) { /* * Base is identified with line and character indices. */ *lineIndexPtr = strtol(string, &end, 0) - 1; if ((end == string) || (*end != '.')) { goto error; } p = end+1; if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) { linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); if (linePtr == NULL) { Tcl_AppendResult(interp, "bad text index \"", string, "\": no such line in text", (char *) NULL); return TCL_ERROR; } *chPtr = linePtr->numBytes - 1; endOfBase = p+3; goto gotBase; } else { *chPtr = strtol(p, &end, 0); if (end == p) { goto error; } endOfBase = end; goto gotBase; } } for (p = string; *p != 0; p++) { if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) { break; } } endOfBase = p; if ((string[0] == 'e') && (strncmp(string, "end", endOfBase-string) == 0)) { /* * Base position is end of text. */ *lineIndexPtr = TkBTreeNumLines(textPtr->tree) - 1; linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); *chPtr = linePtr->numBytes - 1; goto gotBase; } else { /* * See if the base position is the name of a mark. */ c = *endOfBase; *endOfBase = 0; hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); *endOfBase = c; if (hPtr != NULL) { markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr); *lineIndexPtr = TkBTreeLineIndex(markPtr->linePtr); *chPtr = markPtr->ch; goto gotBase; } } /* * Nothing has worked so far. See if the base has the form * "tag.first" or "tag.last" where "tag" is the name of a valid * tag. */ p = strchr(string, '.'); if (p == NULL) { goto error; } if ((p[1] == 'f') && (endOfBase == (p+6)) && (strncmp(p+1, "first", endOfBase - (p+1)) == 0)) { first = 1; } else if ((p[1] == 'l') && (endOfBase == (p+5)) && (strncmp(p+1, "last", endOfBase - (p+1)) == 0)) { first = 0; } else { goto error; } *p = 0; hPtr = Tcl_FindHashEntry(&textPtr->tagTable, string); *p = '.'; if (hPtr == NULL) { goto error; } tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree), 0, tagPtr, &search); if (!TkBTreeNextTag(&search)) { Tcl_AppendResult(interp, "text doesn't contain any characters tagged with \"", Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"", (char *) NULL); return TCL_ERROR; } if (first) { *lineIndexPtr = search.line1; *chPtr = search.ch1; } else { while (TkBTreeNextTag(&search)) { *lineIndexPtr = search.line1; *chPtr = search.ch1; } } /* *------------------------------------------------------------------- * Stage 2: process zero or more modifiers. Each modifier is either * a keyword like "wordend" or "linestart", or it has the form * "op count units" where op is + or -, count is a number, and units * is "chars" or "lines". *------------------------------------------------------------------- */ gotBase: p = endOfBase; while (1) { while (isspace(UCHAR(*p))) { p++; } if (*p == 0) { return TCL_OK; } if ((*p == '+') || (*p == '-')) { p = ForwBack(textPtr, p, lineIndexPtr, chPtr); } else { p = StartEnd(textPtr, p, lineIndexPtr, chPtr); } if (p == NULL) { goto error; } } error: Tcl_AppendResult(interp, "bad text index \"", string, "\"", (char *) NULL); return TCL_ERROR; } /* *---------------------------------------------------------------------- * * TkTextPrintIndex -- * * Given a line number and a character index, this procedure * generates a string description of the position, which is * suitable for reading in again later. * * Results: * The characters pointed to by string are modified. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TkTextPrintIndex(line, ch, string) int line; /* Line number. */ int ch; /* Character position within line. */ char *string; /* Place to store the position. Must have * at least POS_CHARS characters. */ { sprintf(string, "%d.%d", line+1, ch); } /* *---------------------------------------------------------------------- * * TkTextRoundIndex -- * * Given a line index and a character index, this procedure * adjusts those positions if necessary to correspond to the * nearest actual character within the text. * * Results: * The return value is a pointer to the line structure for * the line of the text's B-tree that contains the indicated * character. In addition, *lineIndexPtr and *chPtr are * modified if necessary to refer to an existing character * in the file. * * Side effects: * None. * *---------------------------------------------------------------------- */ TkTextLine * TkTextRoundIndex(textPtr, lineIndexPtr, chPtr) TkText *textPtr; /* Information about text widget. */ int *lineIndexPtr; /* Points to initial line index, * which is overwritten with actual * line index. */ int *chPtr; /* Points to initial character index, * which is overwritten with actual * character index. */ { int line, ch, lastLine; TkTextLine *linePtr; line = *lineIndexPtr; ch = *chPtr; if (line < 0) { line = 0; ch = 0; } lastLine = TkBTreeNumLines(textPtr->tree) - 1; if (line > lastLine) { line = lastLine; linePtr = TkBTreeFindLine(textPtr->tree, line); ch = linePtr->numBytes - 1; } else { linePtr = TkBTreeFindLine(textPtr->tree, line); if (ch < 0) { ch = 0; } if (ch >= linePtr->numBytes) { if (line == lastLine) { ch = linePtr->numBytes - 1; } else { line++; linePtr = TkBTreeNextLine(linePtr); ch = 0; } } } *lineIndexPtr = line; *chPtr = ch; return linePtr; } /* *---------------------------------------------------------------------- * * ForwBack -- * * This procedure handles +/- modifiers for indices to adjust * the index forwards or backwards. * * Results: * If the modifier is successfully parsed then the return value * is the address of the first character after the modifier, and * *lineIndexPtr and *chPtr are updated to reflect the modifier. * If there is a syntax error in the modifier then NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char * ForwBack(textPtr, string, lineIndexPtr, chPtr) TkText *textPtr; /* Information about widget that index * refers to. */ char *string; /* String to parse for additional info * about modifier (count and units). * Points to "+" or "-" that starts * modifier. */ int *lineIndexPtr; /* Points to current line index, which will * be updated to reflect modifier. */ int *chPtr; /* Points to current character index, which * will be updated to reflect modifier. */ { register char *p; char *end, *units; int count, length, lastLine; TkTextLine *linePtr; /* * Get the count (how many units forward or backward). */ p = string+1; while (isspace(UCHAR(*p))) { p++; } count = strtoul(p, &end, 0); if (end == p) { return NULL; } p = end; while (isspace(UCHAR(*p))) { p++; } /* * Find the end of this modifier (next space or + or - character), * then parse the unit specifier and update the position * accordingly. */ units = p; while ((*p != 0) && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { p++; } length = p - units; if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr); if (*string == '+') { ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count); } else { BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count); } } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { if (*string == '+') { *lineIndexPtr += count; lastLine = TkBTreeNumLines(textPtr->tree) - 1; if (*lineIndexPtr > lastLine) { *lineIndexPtr = lastLine; } } else { *lineIndexPtr -= count; if (*lineIndexPtr < 0) { *lineIndexPtr = 0; } } linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); if (*chPtr >= linePtr->numBytes) { *chPtr = linePtr->numBytes - 1; } if (*chPtr < 0) { *chPtr = 0; } } else { return NULL; } return p; } /* *---------------------------------------------------------------------- * * ForwardChars -- * * Given a position in a text widget, this procedure computes * a new position that is "count" characters ahead of the given * position. * * Results: * *LineIndexPtr and *chPtr are overwritten with new values * corresponding to the new position. * * Side effects: * None. * *---------------------------------------------------------------------- */ /* ARGSUSED */ static void ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count) TkText *textPtr; /* Information about text widget. */ register TkTextLine *linePtr; /* Text line corresponding to * *lineIndexPtr. */ int *lineIndexPtr; /* Points to initial line index, * which is overwritten with final * line index. */ int *chPtr; /* Points to initial character index, * which is overwritten with final * character index. */ int count; /* How many characters forward to * move. Must not be negative. */ { TkTextLine *nextPtr; int bytesInLine; while (count > 0) { bytesInLine = linePtr->numBytes - *chPtr; if (bytesInLine > count) { *chPtr += count; return; } nextPtr = TkBTreeNextLine(linePtr); if (nextPtr == NULL) { *chPtr = linePtr->numBytes - 1; return; } *chPtr = 0; *lineIndexPtr += 1; linePtr = nextPtr; count -= bytesInLine; } } /* *---------------------------------------------------------------------- * * BackwardChars -- * * Given a position in a text widget, this procedure computes * a new position that is "count" characters earlier than the given * position. * * Results: * *LineIndexPtr and *chPtr are overwritten with new values * corresponding to the new position. * * Side effects: * None. * *---------------------------------------------------------------------- */ static void BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count) TkText *textPtr; /* Information about text widget. */ register TkTextLine *linePtr; /* Text line corresponding to * *lineIndexPtr. */ int *lineIndexPtr; /* Points to initial line index, * which is overwritten with final * line index. */ int *chPtr; /* Points to initial character index, * which is overwritten with final * character index. */ int count; /* How many characters backward to * move. Must not be negative. */ { int bytesInLine; while (count > 0) { bytesInLine = *chPtr; if (bytesInLine >= count) { *chPtr -= count; return; } if (*lineIndexPtr <= 0) { *chPtr = 0; return; } *lineIndexPtr -= 1; linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); count -= bytesInLine; *chPtr = linePtr->numBytes; } } /* *---------------------------------------------------------------------- * * StartEnd -- * * This procedure handles modifiers like "wordstart" and "lineend" * to adjust indices forwards or backwards. * * Results: * If the modifier is successfully parsed then the return value * is the address of the first character after the modifier, and * *lineIndexPtr and *chPtr are updated to reflect the modifier. * If there is a syntax error in the modifier then NULL is returned. * * Side effects: * None. * *---------------------------------------------------------------------- */ static char * StartEnd(textPtr, string, lineIndexPtr, chPtr) TkText *textPtr; /* Information about widget that index * refers to. */ char *string; /* String to parse for additional info * about modifier (count and units). * Points to first character of modifer * word. */ int *lineIndexPtr; /* Points to current line index, which will * be updated to reflect modifier. */ int *chPtr; /* Points to current character index, which * will be updated to reflect modifier. */ { char *p, c; int length; register TkTextLine *linePtr; /* * Find the end of the modifier word. */ for (p = string; isalnum(UCHAR(*p)); p++) { /* Empty loop body. */ } length = p-string; linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr); if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) && (length >= 5)) { *chPtr = linePtr->numBytes - 1; } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) && (length >= 5)) { *chPtr = 0; } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) && (length >= 5)) { c = linePtr->bytes[*chPtr]; if (!isalnum(UCHAR(c)) && (c != '_')) { if (*chPtr >= (linePtr->numBytes - 1)) { /* * End of line: go to start of next line unless this is the * last line in the text. */ if (TkBTreeNextLine(linePtr) != NULL) { *lineIndexPtr += 1; *chPtr = 0; } } else { *chPtr += 1; } } else { do { *chPtr += 1; c = linePtr->bytes[*chPtr]; } while (isalnum(UCHAR(c)) || (c == '_')); } } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) && (length >= 5)) { c = linePtr->bytes[*chPtr]; if (isalnum(UCHAR(c)) || (c == '_')) { while (*chPtr > 0) { c = linePtr->bytes[(*chPtr) - 1]; if (!isalnum(UCHAR(c)) && (c != '_')) { break; } *chPtr -= 1; } } } else { return NULL; } return p; }