|
|
1.1 ! root 1: /* ! 2: * tkTextIndex.c -- ! 3: * ! 4: * This module provides procedures that manipulate indices for ! 5: * text widgets. ! 6: * ! 7: * Copyright 1992 Regents of the University of California. ! 8: * Permission to use, copy, modify, and distribute this ! 9: * software and its documentation for any purpose and without ! 10: * fee is hereby granted, provided that the above copyright ! 11: * notice appear in all copies. The University of California ! 12: * makes no representations about the suitability of this ! 13: * software for any purpose. It is provided "as is" without ! 14: * express or implied warranty. ! 15: */ ! 16: ! 17: #ifndef lint ! 18: static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkTextIndex.c,v 1.2 92/07/16 16:32:26 ouster Exp $ SPRITE (Berkeley)"; ! 19: #endif ! 20: ! 21: #include "default.h" ! 22: #include "tkconfig.h" ! 23: #include "tk.h" ! 24: #include "tktext.h" ! 25: ! 26: /* ! 27: * Forward declarations for procedures defined later in this file: ! 28: */ ! 29: ! 30: static void BackwardChars _ANSI_ARGS_((TkText *textPtr, ! 31: TkTextLine *linePtr, int *lineIndexPtr, ! 32: int *chPtr, int count)); ! 33: static char * ForwBack _ANSI_ARGS_((TkText *textPtr, ! 34: char *string, int *lineIndexPtr, int *chPtr)); ! 35: static void ForwardChars _ANSI_ARGS_((TkText *textPtr, ! 36: TkTextLine *linePtr, int *lineIndexPtr, ! 37: int *chPtr, int count)); ! 38: static char * StartEnd _ANSI_ARGS_((TkText *textPtr, ! 39: char *string, int *lineIndexPtr, int *chPtr)); ! 40: ! 41: /* ! 42: *---------------------------------------------------------------------- ! 43: * ! 44: * TkTextGetIndex -- ! 45: * ! 46: * Given a string, return the line and character indices that ! 47: * it describes. ! 48: * ! 49: * Results: ! 50: * The return value is a standard Tcl return result. If ! 51: * TCL_OK is returned, then everything went well and information ! 52: * is stored at *lineIndexPtr and *chPtr; otherwise TCL_ERROR ! 53: * is returned and an error message is left in interp->result. ! 54: * ! 55: * Side effects: ! 56: * None. ! 57: * ! 58: *---------------------------------------------------------------------- ! 59: */ ! 60: ! 61: int ! 62: TkTextGetIndex(interp, textPtr, string, lineIndexPtr, chPtr) ! 63: Tcl_Interp *interp; /* Use this for error reporting. */ ! 64: TkText *textPtr; /* Information about text widget. */ ! 65: char *string; /* Textual description of position. */ ! 66: int *lineIndexPtr; /* Store line number here. */ ! 67: int *chPtr; /* Store character position here. */ ! 68: { ! 69: register char *p; ! 70: char *end, *endOfBase; ! 71: TkTextLine *linePtr; ! 72: Tcl_HashEntry *hPtr; ! 73: TkAnnotation *markPtr; ! 74: TkTextTag *tagPtr; ! 75: TkTextSearch search; ! 76: int first; ! 77: char c; ! 78: ! 79: /* ! 80: *------------------------------------------------ ! 81: * Stage 1: parse the base index. ! 82: *------------------------------------------------ ! 83: */ ! 84: ! 85: if (string[0] == '@') { ! 86: /* ! 87: * Find character at a given x,y location in the window. ! 88: */ ! 89: ! 90: int x, y; ! 91: ! 92: p = string+1; ! 93: x = strtol(p, &end, 0); ! 94: if ((end == p) || (*end != ',')) { ! 95: goto error; ! 96: } ! 97: p = end+1; ! 98: y = strtol(p, &end, 0); ! 99: if (end == p) { ! 100: goto error; ! 101: } ! 102: *lineIndexPtr = TkBTreeLineIndex(TkTextCharAtLoc(textPtr, x, ! 103: y, chPtr)); ! 104: endOfBase = end; ! 105: goto gotBase; ! 106: } else if (isdigit(string[0]) || (string[0] == '-')) { ! 107: /* ! 108: * Base is identified with line and character indices. ! 109: */ ! 110: ! 111: *lineIndexPtr = strtol(string, &end, 0) - 1; ! 112: if ((end == string) || (*end != '.')) { ! 113: goto error; ! 114: } ! 115: p = end+1; ! 116: if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) { ! 117: linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); ! 118: if (linePtr == NULL) { ! 119: Tcl_AppendResult(interp, "bad text index \"", string, ! 120: "\": no such line in text", (char *) NULL); ! 121: return TCL_ERROR; ! 122: } ! 123: *chPtr = linePtr->numBytes - 1; ! 124: endOfBase = p+3; ! 125: goto gotBase; ! 126: } else { ! 127: *chPtr = strtol(p, &end, 0); ! 128: if (end == p) { ! 129: goto error; ! 130: } ! 131: endOfBase = end; ! 132: goto gotBase; ! 133: } ! 134: } ! 135: ! 136: for (p = string; *p != 0; p++) { ! 137: if (isspace(*p) || (*p == '+') || (*p == '-')) { ! 138: break; ! 139: } ! 140: } ! 141: endOfBase = p; ! 142: if ((string[0] == 'e') ! 143: && (strncmp(string, "end", endOfBase-string) == 0)) { ! 144: /* ! 145: * Base position is end of text. ! 146: */ ! 147: ! 148: *lineIndexPtr = TkBTreeNumLines(textPtr->tree) - 1; ! 149: linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); ! 150: *chPtr = linePtr->numBytes - 1; ! 151: goto gotBase; ! 152: } else { ! 153: /* ! 154: * See if the base position is the name of a mark. ! 155: */ ! 156: ! 157: c = *endOfBase; ! 158: *endOfBase = 0; ! 159: hPtr = Tcl_FindHashEntry(&textPtr->markTable, string); ! 160: *endOfBase = c; ! 161: if (hPtr != NULL) { ! 162: markPtr = (TkAnnotation *) Tcl_GetHashValue(hPtr); ! 163: *lineIndexPtr = TkBTreeLineIndex(markPtr->linePtr); ! 164: *chPtr = markPtr->ch; ! 165: goto gotBase; ! 166: } ! 167: } ! 168: ! 169: /* ! 170: * Nothing has worked so far. See if the base has the form ! 171: * "tag.first" or "tag.last" where "tag" is the name of a valid ! 172: * tag. ! 173: */ ! 174: ! 175: p = strchr(string, '.'); ! 176: if (p == NULL) { ! 177: goto error; ! 178: } ! 179: if ((p[1] == 'f') && (endOfBase == (p+6)) ! 180: && (strncmp(p+1, "first", endOfBase - (p+1)) == 0)) { ! 181: first = 1; ! 182: } else if ((p[1] == 'l') && (endOfBase == (p+5)) ! 183: && (strncmp(p+1, "last", endOfBase - (p+1)) == 0)) { ! 184: first = 0; ! 185: } else { ! 186: goto error; ! 187: } ! 188: *p = 0; ! 189: hPtr = Tcl_FindHashEntry(&textPtr->tagTable, string); ! 190: *p = '.'; ! 191: if (hPtr == NULL) { ! 192: goto error; ! 193: } ! 194: tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); ! 195: TkBTreeStartSearch(textPtr->tree, 0, 0, TkBTreeNumLines(textPtr->tree), ! 196: 0, tagPtr, &search); ! 197: if (!TkBTreeNextTag(&search)) { ! 198: Tcl_AppendResult(interp, ! 199: "text doesn't contain any characters tagged with \"", ! 200: Tcl_GetHashKey(&textPtr->tagTable, hPtr), "\"", (char *) NULL); ! 201: return TCL_ERROR; ! 202: } ! 203: if (first) { ! 204: *lineIndexPtr = search.line1; ! 205: *chPtr = search.ch1; ! 206: } else { ! 207: while (TkBTreeNextTag(&search)) { ! 208: *lineIndexPtr = search.line1; ! 209: *chPtr = search.ch1; ! 210: } ! 211: } ! 212: ! 213: /* ! 214: *------------------------------------------------------------------- ! 215: * Stage 2: process zero or more modifiers. Each modifier is either ! 216: * a keyword like "wordend" or "linestart", or it has the form ! 217: * "op count units" where op is + or -, count is a number, and units ! 218: * is "chars" or "lines". ! 219: *------------------------------------------------------------------- ! 220: */ ! 221: ! 222: gotBase: ! 223: p = endOfBase; ! 224: while (1) { ! 225: while (isspace(*p)) { ! 226: p++; ! 227: } ! 228: if (*p == 0) { ! 229: return TCL_OK; ! 230: } ! 231: ! 232: if ((*p == '+') || (*p == '-')) { ! 233: p = ForwBack(textPtr, p, lineIndexPtr, chPtr); ! 234: } else { ! 235: p = StartEnd(textPtr, p, lineIndexPtr, chPtr); ! 236: } ! 237: if (p == NULL) { ! 238: goto error; ! 239: } ! 240: } ! 241: ! 242: error: ! 243: Tcl_AppendResult(interp, "bad text index \"", string, "\"", ! 244: (char *) NULL); ! 245: return TCL_ERROR; ! 246: } ! 247: ! 248: /* ! 249: *---------------------------------------------------------------------- ! 250: * ! 251: * TkTextPrintIndex -- ! 252: * ! 253: * Given a line number and a character index, this procedure ! 254: * generates a string description of the position, which is ! 255: * suitable for reading in again later. ! 256: * ! 257: * Results: ! 258: * The characters pointed to by string are modified. ! 259: * ! 260: * Side effects: ! 261: * None. ! 262: * ! 263: *---------------------------------------------------------------------- ! 264: */ ! 265: ! 266: void ! 267: TkTextPrintIndex(line, ch, string) ! 268: int line; /* Line number. */ ! 269: int ch; /* Character position within line. */ ! 270: char *string; /* Place to store the position. Must have ! 271: * at least POS_CHARS characters. */ ! 272: { ! 273: sprintf(string, "%d.%d", line+1, ch); ! 274: } ! 275: ! 276: /* ! 277: *---------------------------------------------------------------------- ! 278: * ! 279: * TkTextRoundIndex -- ! 280: * ! 281: * Given a line index and a character index, this procedure ! 282: * adjusts those positions if necessary to correspond to the ! 283: * nearest actual character within the text. ! 284: * ! 285: * Results: ! 286: * The return value is a pointer to the line structure for ! 287: * the line of the text's B-tree that contains the indicated ! 288: * character. In addition, *lineIndexPtr and *chPtr are ! 289: * modified if necessary to refer to an existing character ! 290: * in the file. ! 291: * ! 292: * Side effects: ! 293: * None. ! 294: * ! 295: *---------------------------------------------------------------------- ! 296: */ ! 297: ! 298: ! 299: TkTextLine * ! 300: TkTextRoundIndex(textPtr, lineIndexPtr, chPtr) ! 301: TkText *textPtr; /* Information about text widget. */ ! 302: int *lineIndexPtr; /* Points to initial line index, ! 303: * which is overwritten with actual ! 304: * line index. */ ! 305: int *chPtr; /* Points to initial character index, ! 306: * which is overwritten with actual ! 307: * character index. */ ! 308: { ! 309: int line, ch, lastLine; ! 310: TkTextLine *linePtr; ! 311: ! 312: line = *lineIndexPtr; ! 313: ch = *chPtr; ! 314: if (line < 0) { ! 315: line = 0; ! 316: ch = 0; ! 317: } ! 318: lastLine = TkBTreeNumLines(textPtr->tree) - 1; ! 319: if (line > lastLine) { ! 320: line = lastLine; ! 321: linePtr = TkBTreeFindLine(textPtr->tree, line); ! 322: ch = linePtr->numBytes - 1; ! 323: } else { ! 324: linePtr = TkBTreeFindLine(textPtr->tree, line); ! 325: if (ch < 0) { ! 326: ch = 0; ! 327: } ! 328: if (ch >= linePtr->numBytes) { ! 329: if (line == lastLine) { ! 330: ch = linePtr->numBytes - 1; ! 331: } else { ! 332: line++; ! 333: linePtr = TkBTreeNextLine(linePtr); ! 334: ch = 0; ! 335: } ! 336: } ! 337: } ! 338: *lineIndexPtr = line; ! 339: *chPtr = ch; ! 340: return linePtr; ! 341: } ! 342: ! 343: /* ! 344: *---------------------------------------------------------------------- ! 345: * ! 346: * ForwBack -- ! 347: * ! 348: * This procedure handles +/- modifiers for indices to adjust ! 349: * the index forwards or backwards. ! 350: * ! 351: * Results: ! 352: * If the modifier is successfully parsed then the return value ! 353: * is the address of the first character after the modifier, and ! 354: * *lineIndexPtr and *chPtr are updated to reflect the modifier. ! 355: * If there is a syntax error in the modifier then NULL is returned. ! 356: * ! 357: * Side effects: ! 358: * None. ! 359: * ! 360: *---------------------------------------------------------------------- ! 361: */ ! 362: ! 363: static char * ! 364: ForwBack(textPtr, string, lineIndexPtr, chPtr) ! 365: TkText *textPtr; /* Information about widget that index ! 366: * refers to. */ ! 367: char *string; /* String to parse for additional info ! 368: * about modifier (count and units). ! 369: * Points to "+" or "-" that starts ! 370: * modifier. */ ! 371: int *lineIndexPtr; /* Points to current line index, which will ! 372: * be updated to reflect modifier. */ ! 373: int *chPtr; /* Points to current character index, which ! 374: * will be updated to reflect modifier. */ ! 375: { ! 376: register char *p; ! 377: char *end, *units; ! 378: int count, length, lastLine; ! 379: TkTextLine *linePtr; ! 380: ! 381: /* ! 382: * Get the count (how many units forward or backward). ! 383: */ ! 384: ! 385: p = string+1; ! 386: while (isspace(*p)) { ! 387: p++; ! 388: } ! 389: count = strtoul(p, &end, 0); ! 390: if (end == p) { ! 391: return NULL; ! 392: } ! 393: p = end; ! 394: while (isspace(*p)) { ! 395: p++; ! 396: } ! 397: ! 398: /* ! 399: * Find the end of this modifier (next space or + or - character), ! 400: * then parse the unit specifier and update the position ! 401: * accordingly. ! 402: */ ! 403: ! 404: units = p; ! 405: while ((*p != 0) && !isspace(*p) && (*p != '+') && (*p != '-')) { ! 406: p++; ! 407: } ! 408: length = p - units; ! 409: if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { ! 410: linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr); ! 411: if (*string == '+') { ! 412: ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count); ! 413: } else { ! 414: BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count); ! 415: } ! 416: } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { ! 417: if (*string == '+') { ! 418: *lineIndexPtr += count; ! 419: lastLine = TkBTreeNumLines(textPtr->tree) - 1; ! 420: if (*lineIndexPtr > lastLine) { ! 421: *lineIndexPtr = lastLine; ! 422: } ! 423: } else { ! 424: *lineIndexPtr -= count; ! 425: if (*lineIndexPtr < 0) { ! 426: *lineIndexPtr = 0; ! 427: } ! 428: } ! 429: linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); ! 430: if (*chPtr >= linePtr->numBytes) { ! 431: *chPtr = linePtr->numBytes - 1; ! 432: } ! 433: if (*chPtr < 0) { ! 434: *chPtr = 0; ! 435: } ! 436: } else { ! 437: return NULL; ! 438: } ! 439: return p; ! 440: } ! 441: ! 442: /* ! 443: *---------------------------------------------------------------------- ! 444: * ! 445: * ForwardChars -- ! 446: * ! 447: * Given a position in a text widget, this procedure computes ! 448: * a new position that is "count" characters ahead of the given ! 449: * position. ! 450: * ! 451: * Results: ! 452: * *LineIndexPtr and *chPtr are overwritten with new values ! 453: * corresponding to the new position. ! 454: * ! 455: * Side effects: ! 456: * None. ! 457: * ! 458: *---------------------------------------------------------------------- ! 459: */ ! 460: ! 461: /* ARGSUSED */ ! 462: static void ! 463: ForwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count) ! 464: TkText *textPtr; /* Information about text widget. */ ! 465: register TkTextLine *linePtr; /* Text line corresponding to ! 466: * *lineIndexPtr. */ ! 467: int *lineIndexPtr; /* Points to initial line index, ! 468: * which is overwritten with final ! 469: * line index. */ ! 470: int *chPtr; /* Points to initial character index, ! 471: * which is overwritten with final ! 472: * character index. */ ! 473: int count; /* How many characters forward to ! 474: * move. Must not be negative. */ ! 475: { ! 476: TkTextLine *nextPtr; ! 477: int bytesInLine; ! 478: ! 479: while (count > 0) { ! 480: bytesInLine = linePtr->numBytes - *chPtr; ! 481: if (bytesInLine > count) { ! 482: *chPtr += count; ! 483: return; ! 484: } ! 485: nextPtr = TkBTreeNextLine(linePtr); ! 486: if (nextPtr == NULL) { ! 487: *chPtr = linePtr->numBytes - 1; ! 488: return; ! 489: } ! 490: *chPtr = 0; ! 491: *lineIndexPtr += 1; ! 492: linePtr = nextPtr; ! 493: count -= bytesInLine; ! 494: } ! 495: } ! 496: ! 497: /* ! 498: *---------------------------------------------------------------------- ! 499: * ! 500: * BackwardChars -- ! 501: * ! 502: * Given a position in a text widget, this procedure computes ! 503: * a new position that is "count" characters earlier than the given ! 504: * position. ! 505: * ! 506: * Results: ! 507: * *LineIndexPtr and *chPtr are overwritten with new values ! 508: * corresponding to the new position. ! 509: * ! 510: * Side effects: ! 511: * None. ! 512: * ! 513: *---------------------------------------------------------------------- ! 514: */ ! 515: ! 516: static void ! 517: BackwardChars(textPtr, linePtr, lineIndexPtr, chPtr, count) ! 518: TkText *textPtr; /* Information about text widget. */ ! 519: register TkTextLine *linePtr; /* Text line corresponding to ! 520: * *lineIndexPtr. */ ! 521: int *lineIndexPtr; /* Points to initial line index, ! 522: * which is overwritten with final ! 523: * line index. */ ! 524: int *chPtr; /* Points to initial character index, ! 525: * which is overwritten with final ! 526: * character index. */ ! 527: int count; /* How many characters backward to ! 528: * move. Must not be negative. */ ! 529: { ! 530: int bytesInLine; ! 531: ! 532: while (count > 0) { ! 533: bytesInLine = *chPtr; ! 534: if (bytesInLine >= count) { ! 535: *chPtr -= count; ! 536: return; ! 537: } ! 538: if (*lineIndexPtr <= 0) { ! 539: *chPtr = 0; ! 540: return; ! 541: } ! 542: *lineIndexPtr -= 1; ! 543: linePtr = TkBTreeFindLine(textPtr->tree, *lineIndexPtr); ! 544: count -= bytesInLine; ! 545: *chPtr = linePtr->numBytes; ! 546: } ! 547: } ! 548: ! 549: /* ! 550: *---------------------------------------------------------------------- ! 551: * ! 552: * StartEnd -- ! 553: * ! 554: * This procedure handles modifiers like "wordstart" and "lineend" ! 555: * to adjust indices forwards or backwards. ! 556: * ! 557: * Results: ! 558: * If the modifier is successfully parsed then the return value ! 559: * is the address of the first character after the modifier, and ! 560: * *lineIndexPtr and *chPtr are updated to reflect the modifier. ! 561: * If there is a syntax error in the modifier then NULL is returned. ! 562: * ! 563: * Side effects: ! 564: * None. ! 565: * ! 566: *---------------------------------------------------------------------- ! 567: */ ! 568: ! 569: static char * ! 570: StartEnd(textPtr, string, lineIndexPtr, chPtr) ! 571: TkText *textPtr; /* Information about widget that index ! 572: * refers to. */ ! 573: char *string; /* String to parse for additional info ! 574: * about modifier (count and units). ! 575: * Points to first character of modifer ! 576: * word. */ ! 577: int *lineIndexPtr; /* Points to current line index, which will ! 578: * be updated to reflect modifier. */ ! 579: int *chPtr; /* Points to current character index, which ! 580: * will be updated to reflect modifier. */ ! 581: { ! 582: char *p, c; ! 583: int length; ! 584: register TkTextLine *linePtr; ! 585: ! 586: /* ! 587: * Find the end of the modifier word. ! 588: */ ! 589: ! 590: for (p = string; isalnum(*p); p++) { ! 591: /* Empty loop body. */ ! 592: } ! 593: length = p-string; ! 594: linePtr = TkTextRoundIndex(textPtr, lineIndexPtr, chPtr); ! 595: if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) ! 596: && (length >= 5)) { ! 597: *chPtr = linePtr->numBytes - 1; ! 598: } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) ! 599: && (length >= 5)) { ! 600: *chPtr = 0; ! 601: } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) ! 602: && (length >= 5)) { ! 603: c = linePtr->bytes[*chPtr]; ! 604: if (!isalnum(c) && (c != '_')) { ! 605: if (*chPtr >= (linePtr->numBytes - 1)) { ! 606: /* ! 607: * End of line: go to start of next line unless this is the ! 608: * last line in the text. ! 609: */ ! 610: ! 611: if (TkBTreeNextLine(linePtr) != NULL) { ! 612: *lineIndexPtr += 1; ! 613: *chPtr = 0; ! 614: } ! 615: } else { ! 616: *chPtr += 1; ! 617: } ! 618: } else { ! 619: do { ! 620: *chPtr += 1; ! 621: c = linePtr->bytes[*chPtr]; ! 622: } while (isalnum(c) || (c == '_')); ! 623: } ! 624: } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) ! 625: && (length >= 5)) { ! 626: c = linePtr->bytes[*chPtr]; ! 627: if (isalnum(c) || (c == '_')) { ! 628: while (*chPtr > 0) { ! 629: c = linePtr->bytes[(*chPtr) - 1]; ! 630: if (!isalnum(c) && (c != '_')) { ! 631: break; ! 632: } ! 633: *chPtr -= 1; ! 634: } ! 635: } ! 636: } else { ! 637: return NULL; ! 638: } ! 639: return p; ! 640: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.