archie/tk3.6/tkTextIndex.c

652 lines
17 KiB
C
Raw Normal View History

2024-05-27 16:13:40 +02:00
/*
* 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;
}