652 lines
17 KiB
C
652 lines
17 KiB
C
/*
|
||
* 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;
|
||
}
|