1/* 2 * tkTextIndex.c -- 3 * 4 * This module provides functions that manipulate indices for text 5 * widgets. 6 * 7 * Copyright (c) 1992-1994 The Regents of the University of California. 8 * Copyright (c) 1994-1997 Sun Microsystems, Inc. 9 * 10 * See the file "license.terms" for information on usage and redistribution of 11 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 12 * 13 * RCS: @(#) $Id$ 14 */ 15 16#include "default.h" 17#include "tkInt.h" 18#include "tkText.h" 19 20/* 21 * Index to use to select last character in line (very large integer): 22 */ 23 24#define LAST_CHAR 1000000 25 26/* 27 * Modifiers for index parsing: 'display', 'any' or nothing. 28 */ 29 30#define TKINDEX_NONE 0 31#define TKINDEX_DISPLAY 1 32#define TKINDEX_ANY 2 33 34/* 35 * Forward declarations for functions defined later in this file: 36 */ 37 38static CONST char * ForwBack(TkText *textPtr, CONST char *string, 39 TkTextIndex *indexPtr); 40static CONST char * StartEnd(TkText *textPtr, CONST char *string, 41 TkTextIndex *indexPtr); 42static int GetIndex(Tcl_Interp *interp, TkSharedText *sharedPtr, 43 TkText *textPtr, CONST char *string, 44 TkTextIndex *indexPtr, int *canCachePtr); 45 46/* 47 * The "textindex" Tcl_Obj definition: 48 */ 49 50static void DupTextIndexInternalRep(Tcl_Obj *srcPtr, 51 Tcl_Obj *copyPtr); 52static void FreeTextIndexInternalRep(Tcl_Obj *listPtr); 53static int SetTextIndexFromAny(Tcl_Interp *interp, 54 Tcl_Obj *objPtr); 55static void UpdateStringOfTextIndex(Tcl_Obj *objPtr); 56 57/* 58 * Accessor macros for the "textindex" type. 59 */ 60 61#define GET_TEXTINDEX(objPtr) \ 62 ((TkTextIndex *) (objPtr)->internalRep.twoPtrValue.ptr1) 63#define GET_INDEXEPOCH(objPtr) \ 64 (PTR2INT((objPtr)->internalRep.twoPtrValue.ptr2)) 65#define SET_TEXTINDEX(objPtr, indexPtr) \ 66 ((objPtr)->internalRep.twoPtrValue.ptr1 = (VOID *) (indexPtr)) 67#define SET_INDEXEPOCH(objPtr, epoch) \ 68 ((objPtr)->internalRep.twoPtrValue.ptr2 = INT2PTR(epoch)) 69 70/* 71 * Define the 'textindex' object type, which Tk uses to represent indices in 72 * text widgets internally. 73 */ 74 75Tcl_ObjType tkTextIndexType = { 76 "textindex", /* name */ 77 FreeTextIndexInternalRep, /* freeIntRepProc */ 78 DupTextIndexInternalRep, /* dupIntRepProc */ 79 NULL, /* updateStringProc */ 80 SetTextIndexFromAny /* setFromAnyProc */ 81}; 82 83static void 84FreeTextIndexInternalRep( 85 Tcl_Obj *indexObjPtr) /* TextIndex object with internal rep to 86 * free. */ 87{ 88 TkTextIndex *indexPtr = GET_TEXTINDEX(indexObjPtr); 89 if (indexPtr->textPtr != NULL) { 90 if (--indexPtr->textPtr->refCount == 0) { 91 /* 92 * The text widget has been deleted and we need to free it now. 93 */ 94 95 ckfree((char *) (indexPtr->textPtr)); 96 } 97 } 98 ckfree((char *) indexPtr); 99} 100 101static void 102DupTextIndexInternalRep( 103 Tcl_Obj *srcPtr, /* TextIndex obj with internal rep to copy. */ 104 Tcl_Obj *copyPtr) /* TextIndex obj with internal rep to set. */ 105{ 106 int epoch; 107 TkTextIndex *dupIndexPtr, *indexPtr; 108 109 dupIndexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex)); 110 indexPtr = GET_TEXTINDEX(srcPtr); 111 epoch = GET_INDEXEPOCH(srcPtr); 112 113 dupIndexPtr->tree = indexPtr->tree; 114 dupIndexPtr->linePtr = indexPtr->linePtr; 115 dupIndexPtr->byteIndex = indexPtr->byteIndex; 116 dupIndexPtr->textPtr = indexPtr->textPtr; 117 if (dupIndexPtr->textPtr != NULL) { 118 dupIndexPtr->textPtr->refCount++; 119 } 120 SET_TEXTINDEX(copyPtr, dupIndexPtr); 121 SET_INDEXEPOCH(copyPtr, epoch); 122 copyPtr->typePtr = &tkTextIndexType; 123} 124 125/* 126 * This will not be called except by TkTextNewIndexObj below. This is because 127 * if a TkTextIndex is no longer valid, it is not possible to regenerate the 128 * string representation. 129 */ 130 131static void 132UpdateStringOfTextIndex( 133 Tcl_Obj *objPtr) 134{ 135 char buffer[TK_POS_CHARS]; 136 register int len; 137 138 CONST TkTextIndex *indexPtr = GET_TEXTINDEX(objPtr); 139 140 len = TkTextPrintIndex(indexPtr->textPtr, indexPtr, buffer); 141 142 objPtr->bytes = ckalloc((unsigned) len + 1); 143 strcpy(objPtr->bytes, buffer); 144 objPtr->length = len; 145} 146 147static int 148SetTextIndexFromAny( 149 Tcl_Interp *interp, /* Used for error reporting if not NULL. */ 150 Tcl_Obj *objPtr) /* The object to convert. */ 151{ 152 Tcl_AppendToObj(Tcl_GetObjResult(interp), 153 "can't convert value to textindex except via TkTextGetIndexFromObj API", 154 -1); 155 return TCL_ERROR; 156} 157 158/* 159 *--------------------------------------------------------------------------- 160 * 161 * MakeObjIndex -- 162 * 163 * This function generates a Tcl_Obj description of an index, suitable 164 * for reading in again later. If the 'textPtr' is NULL then we still 165 * generate an index object, but it's internal description is deemed 166 * non-cacheable, and therefore effectively useless (apart from as a 167 * temporary memory storage). This is used for indices whose meaning is 168 * very temporary (like @0,0 or the name of a mark or tag). The mapping 169 * from such strings/objects to actual TkTextIndex pointers is not stable 170 * to minor text widget changes which we do not track (we track 171 * insertions and deletions). 172 * 173 * Results: 174 * A pointer to an allocated TkTextIndex which will be freed 175 * automatically when the Tcl_Obj is used for other purposes. 176 * 177 * Side effects: 178 * A small amount of memory is allocated. 179 * 180 *--------------------------------------------------------------------------- 181 */ 182 183static TkTextIndex * 184MakeObjIndex( 185 TkText *textPtr, /* Information about text widget. */ 186 Tcl_Obj *objPtr, /* Object containing description of 187 * position. */ 188 CONST TkTextIndex *origPtr) /* Pointer to index. */ 189{ 190 TkTextIndex *indexPtr = (TkTextIndex *) ckalloc(sizeof(TkTextIndex)); 191 192 indexPtr->tree = origPtr->tree; 193 indexPtr->linePtr = origPtr->linePtr; 194 indexPtr->byteIndex = origPtr->byteIndex; 195 SET_TEXTINDEX(objPtr, indexPtr); 196 objPtr->typePtr = &tkTextIndexType; 197 indexPtr->textPtr = textPtr; 198 199 if (textPtr != NULL) { 200 textPtr->refCount++; 201 SET_INDEXEPOCH(objPtr, textPtr->sharedTextPtr->stateEpoch); 202 } else { 203 SET_INDEXEPOCH(objPtr, 0); 204 } 205 return indexPtr; 206} 207 208CONST TkTextIndex * 209TkTextGetIndexFromObj( 210 Tcl_Interp *interp, /* Use this for error reporting. */ 211 TkText *textPtr, /* Information about text widget. */ 212 Tcl_Obj *objPtr) /* Object containing description of 213 * position. */ 214{ 215 TkTextIndex index; 216 TkTextIndex *indexPtr = NULL; 217 int cache; 218 219 if (objPtr->typePtr == &tkTextIndexType) { 220 int epoch; 221 222 indexPtr = GET_TEXTINDEX(objPtr); 223 epoch = GET_INDEXEPOCH(objPtr); 224 225 if (epoch == textPtr->sharedTextPtr->stateEpoch) { 226 if (indexPtr->textPtr == textPtr) { 227 return indexPtr; 228 } 229 } 230 } 231 232 /* 233 * The object is either not an index type or referred to a different text 234 * widget, or referred to the correct widget, but it is out of date (text 235 * has been added/deleted since). 236 */ 237 238 if (GetIndex(interp, NULL, textPtr, Tcl_GetString(objPtr), &index, 239 &cache) != TCL_OK) { 240 return NULL; 241 } 242 243 if (objPtr->typePtr != NULL) { 244 if (objPtr->bytes == NULL) { 245 objPtr->typePtr->updateStringProc(objPtr); 246 } 247 if ((objPtr->typePtr->freeIntRepProc) != NULL) { 248 (*objPtr->typePtr->freeIntRepProc)(objPtr); 249 } 250 } 251 252 return MakeObjIndex((cache ? textPtr : NULL), objPtr, &index); 253} 254 255/* 256 *--------------------------------------------------------------------------- 257 * 258 * TkTextNewIndexObj -- 259 * 260 * This function generates a Tcl_Obj description of an index, suitable 261 * for reading in again later. The index generated is effectively stable 262 * to all except insertion/deletion operations on the widget. 263 * 264 * Results: 265 * A new Tcl_Obj with refCount zero. 266 * 267 * Side effects: 268 * A small amount of memory is allocated. 269 * 270 *--------------------------------------------------------------------------- 271 */ 272 273Tcl_Obj * 274TkTextNewIndexObj( 275 TkText *textPtr, /* Text widget for this index */ 276 CONST TkTextIndex *indexPtr)/* Pointer to index. */ 277{ 278 Tcl_Obj *retVal; 279 280 retVal = Tcl_NewObj(); 281 retVal->bytes = NULL; 282 283 /* 284 * Assumption that the above call returns an object with: 285 * retVal->typePtr == NULL 286 */ 287 288 MakeObjIndex(textPtr, retVal, indexPtr); 289 290 /* 291 * Unfortunately, it isn't possible for us to regenerate the string 292 * representation so we have to create it here, while we can be sure the 293 * contents of the index are still valid. 294 */ 295 296 UpdateStringOfTextIndex(retVal); 297 return retVal; 298} 299 300/* 301 *--------------------------------------------------------------------------- 302 * 303 * TkTextMakePixelIndex -- 304 * 305 * Given a pixel index and a byte index, look things up in the B-tree and 306 * fill in a TkTextIndex structure. 307 * 308 * The valid input range for pixelIndex is from 0 to the number of pixels 309 * in the widget-1. Anything outside that range will be rounded to the 310 * closest acceptable value. 311 * 312 * Results: 313 * 314 * The structure at *indexPtr is filled in with information about the 315 * character at pixelIndex (or the closest existing character, if the 316 * specified one doesn't exist), and the number of excess pixels is 317 * returned as a result. This means if the given pixel index is exactly 318 * correct for the top-edge of the indexPtr, then zero will be returned, 319 * and otherwise we will return the calculation 'desired pixelIndex' - 320 * 'actual pixel index of indexPtr'. 321 * 322 * Side effects: 323 * None. 324 * 325 *--------------------------------------------------------------------------- 326 */ 327 328int 329TkTextMakePixelIndex( 330 TkText *textPtr, /* The Text Widget */ 331 int pixelIndex, /* Pixel-index of desired line (0 means first 332 * pixel of first line of text). */ 333 TkTextIndex *indexPtr) /* Structure to fill in. */ 334{ 335 int pixelOffset = 0; 336 337 indexPtr->tree = textPtr->sharedTextPtr->tree; 338 indexPtr->textPtr = textPtr; 339 340 if (pixelIndex < 0) { 341 pixelIndex = 0; 342 } 343 indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, 344 textPtr, pixelIndex, &pixelOffset); 345 346 /* 347 * 'pixelIndex' was too large, so we try again, just to find the last 348 * pixel in the window. 349 */ 350 351 if (indexPtr->linePtr == NULL) { 352 int lastMinusOne = TkBTreeNumPixels(textPtr->sharedTextPtr->tree, 353 textPtr)-1; 354 355 indexPtr->linePtr = TkBTreeFindPixelLine(textPtr->sharedTextPtr->tree, 356 textPtr, lastMinusOne, &pixelOffset); 357 indexPtr->byteIndex = 0; 358 return pixelOffset; 359 } 360 indexPtr->byteIndex = 0; 361 362 if (pixelOffset <= 0) { 363 return 0; 364 } 365 return TkTextMeasureDown(textPtr, indexPtr, pixelOffset); 366} 367 368/* 369 *--------------------------------------------------------------------------- 370 * 371 * TkTextMakeByteIndex -- 372 * 373 * Given a line index and a byte index, look things up in the B-tree and 374 * fill in a TkTextIndex structure. 375 * 376 * Results: 377 * The structure at *indexPtr is filled in with information about the 378 * character at lineIndex and byteIndex (or the closest existing 379 * character, if the specified one doesn't exist), and indexPtr is 380 * returned as result. 381 * 382 * Side effects: 383 * None. 384 * 385 *--------------------------------------------------------------------------- 386 */ 387 388TkTextIndex * 389TkTextMakeByteIndex( 390 TkTextBTree tree, /* Tree that lineIndex and byteIndex refer 391 * to. */ 392 CONST TkText *textPtr, 393 int lineIndex, /* Index of desired line (0 means first line 394 * of text). */ 395 int byteIndex, /* Byte index of desired character. */ 396 TkTextIndex *indexPtr) /* Structure to fill in. */ 397{ 398 TkTextSegment *segPtr; 399 int index; 400 CONST char *p, *start; 401 Tcl_UniChar ch; 402 403 indexPtr->tree = tree; 404 if (lineIndex < 0) { 405 lineIndex = 0; 406 byteIndex = 0; 407 } 408 if (byteIndex < 0) { 409 byteIndex = 0; 410 } 411 indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); 412 if (indexPtr->linePtr == NULL) { 413 indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, 414 TkBTreeNumLines(tree, textPtr)); 415 byteIndex = 0; 416 } 417 if (byteIndex == 0) { 418 indexPtr->byteIndex = byteIndex; 419 return indexPtr; 420 } 421 422 /* 423 * Verify that the index is within the range of the line and points to a 424 * valid character boundary. 425 */ 426 427 index = 0; 428 for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { 429 if (segPtr == NULL) { 430 /* 431 * Use the index of the last character in the line. Since the last 432 * character on the line is guaranteed to be a '\n', we can back 433 * up a constant sizeof(char) bytes. 434 */ 435 436 indexPtr->byteIndex = index - sizeof(char); 437 break; 438 } 439 if (index + segPtr->size > byteIndex) { 440 indexPtr->byteIndex = byteIndex; 441 if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) { 442 /* 443 * Prevent UTF-8 character from being split up by ensuring 444 * that byteIndex falls on a character boundary. If index 445 * falls in the middle of a UTF-8 character, it will be 446 * adjusted to the end of that UTF-8 character. 447 */ 448 449 start = segPtr->body.chars + (byteIndex - index); 450 p = Tcl_UtfPrev(start, segPtr->body.chars); 451 p += Tcl_UtfToUniChar(p, &ch); 452 indexPtr->byteIndex += p - start; 453 } 454 break; 455 } 456 index += segPtr->size; 457 } 458 return indexPtr; 459} 460 461/* 462 *--------------------------------------------------------------------------- 463 * 464 * TkTextMakeCharIndex -- 465 * 466 * Given a line index and a character index, look things up in the B-tree 467 * and fill in a TkTextIndex structure. 468 * 469 * Results: 470 * The structure at *indexPtr is filled in with information about the 471 * character at lineIndex and charIndex (or the closest existing 472 * character, if the specified one doesn't exist), and indexPtr is 473 * returned as result. 474 * 475 * Side effects: 476 * None. 477 * 478 *--------------------------------------------------------------------------- 479 */ 480 481TkTextIndex * 482TkTextMakeCharIndex( 483 TkTextBTree tree, /* Tree that lineIndex and charIndex refer 484 * to. */ 485 TkText *textPtr, 486 int lineIndex, /* Index of desired line (0 means first line 487 * of text). */ 488 int charIndex, /* Index of desired character. */ 489 TkTextIndex *indexPtr) /* Structure to fill in. */ 490{ 491 register TkTextSegment *segPtr; 492 char *p, *start, *end; 493 int index, offset; 494 Tcl_UniChar ch; 495 496 indexPtr->tree = tree; 497 if (lineIndex < 0) { 498 lineIndex = 0; 499 charIndex = 0; 500 } 501 if (charIndex < 0) { 502 charIndex = 0; 503 } 504 indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, lineIndex); 505 if (indexPtr->linePtr == NULL) { 506 indexPtr->linePtr = TkBTreeFindLine(tree, textPtr, 507 TkBTreeNumLines(tree, textPtr)); 508 charIndex = 0; 509 } 510 511 /* 512 * Verify that the index is within the range of the line. If not, just use 513 * the index of the last character in the line. 514 */ 515 516 index = 0; 517 for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) { 518 if (segPtr == NULL) { 519 /* 520 * Use the index of the last character in the line. Since the last 521 * character on the line is guaranteed to be a '\n', we can back 522 * up a constant sizeof(char) bytes. 523 */ 524 525 indexPtr->byteIndex = index - sizeof(char); 526 break; 527 } 528 if (segPtr->typePtr == &tkTextCharType) { 529 /* 530 * Turn character offset into a byte offset. 531 */ 532 533 start = segPtr->body.chars; 534 end = start + segPtr->size; 535 for (p = start; p < end; p += offset) { 536 if (charIndex == 0) { 537 indexPtr->byteIndex = index; 538 return indexPtr; 539 } 540 charIndex--; 541 offset = Tcl_UtfToUniChar(p, &ch); 542 index += offset; 543 } 544 } else { 545 if (charIndex < segPtr->size) { 546 indexPtr->byteIndex = index; 547 break; 548 } 549 charIndex -= segPtr->size; 550 index += segPtr->size; 551 } 552 } 553 return indexPtr; 554} 555 556/* 557 *--------------------------------------------------------------------------- 558 * 559 * TkTextIndexToSeg -- 560 * 561 * Given an index, this function returns the segment and offset within 562 * segment for the index. 563 * 564 * Results: 565 * The return value is a pointer to the segment referred to by indexPtr; 566 * this will always be a segment with non-zero size. The variable at 567 * *offsetPtr is set to hold the integer offset within the segment of the 568 * character given by indexPtr. 569 * 570 * Side effects: 571 * None. 572 * 573 *--------------------------------------------------------------------------- 574 */ 575 576TkTextSegment * 577TkTextIndexToSeg( 578 CONST TkTextIndex *indexPtr,/* Text index. */ 579 int *offsetPtr) /* Where to store offset within segment, or 580 * NULL if offset isn't wanted. */ 581{ 582 TkTextSegment *segPtr; 583 int offset; 584 585 for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr; 586 offset >= segPtr->size; 587 offset -= segPtr->size, segPtr = segPtr->nextPtr) { 588 /* Empty loop body. */ 589 } 590 if (offsetPtr != NULL) { 591 *offsetPtr = offset; 592 } 593 return segPtr; 594} 595 596/* 597 *--------------------------------------------------------------------------- 598 * 599 * TkTextSegToOffset -- 600 * 601 * Given a segment pointer and the line containing it, this function 602 * returns the offset of the segment within its line. 603 * 604 * Results: 605 * The return value is the offset (within its line) of the first 606 * character in segPtr. 607 * 608 * Side effects: 609 * None. 610 * 611 *--------------------------------------------------------------------------- 612 */ 613 614int 615TkTextSegToOffset( 616 CONST TkTextSegment *segPtr,/* Segment whose offset is desired. */ 617 CONST TkTextLine *linePtr) /* Line containing segPtr. */ 618{ 619 CONST TkTextSegment *segPtr2; 620 int offset = 0; 621 622 for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr; 623 segPtr2 = segPtr2->nextPtr) { 624 offset += segPtr2->size; 625 } 626 return offset; 627} 628 629/* 630 *--------------------------------------------------------------------------- 631 * 632 * TkTextGetObjIndex -- 633 * 634 * Simpler wrapper around the string based function, but could be 635 * enhanced with a new object type in the future. 636 * 637 * Results: 638 * see TkTextGetIndex 639 * 640 * Side effects: 641 * None. 642 * 643 *--------------------------------------------------------------------------- 644 */ 645 646int 647TkTextGetObjIndex( 648 Tcl_Interp *interp, /* Use this for error reporting. */ 649 TkText *textPtr, /* Information about text widget. */ 650 Tcl_Obj *idxObj, /* Object containing textual description of 651 * position. */ 652 TkTextIndex *indexPtr) /* Index structure to fill in. */ 653{ 654 return GetIndex(interp, NULL, textPtr, Tcl_GetString(idxObj), indexPtr, 655 NULL); 656} 657 658/* 659 *--------------------------------------------------------------------------- 660 * 661 * TkTextSharedGetObjIndex -- 662 * 663 * Simpler wrapper around the string based function, but could be 664 * enhanced with a new object type in the future. 665 * 666 * Results: 667 * see TkTextGetIndex 668 * 669 * Side effects: 670 * None. 671 * 672 *--------------------------------------------------------------------------- 673 */ 674 675int 676TkTextSharedGetObjIndex( 677 Tcl_Interp *interp, /* Use this for error reporting. */ 678 TkSharedText *sharedTextPtr,/* Information about text widget. */ 679 Tcl_Obj *idxObj, /* Object containing textual description of 680 * position. */ 681 TkTextIndex *indexPtr) /* Index structure to fill in. */ 682{ 683 return GetIndex(interp, sharedTextPtr, NULL, Tcl_GetString(idxObj), 684 indexPtr, NULL); 685} 686 687/* 688 *--------------------------------------------------------------------------- 689 * 690 * TkTextGetIndex -- 691 * 692 * Given a string, return the index that is described. 693 * 694 * Results: 695 * The return value is a standard Tcl return result. If TCL_OK is 696 * returned, then everything went well and the index at *indexPtr is 697 * filled in; otherwise TCL_ERROR is returned and an error message is 698 * left in the interp's result. 699 * 700 * Side effects: 701 * None. 702 * 703 *--------------------------------------------------------------------------- 704 */ 705 706int 707TkTextGetIndex( 708 Tcl_Interp *interp, /* Use this for error reporting. */ 709 TkText *textPtr, /* Information about text widget. */ 710 CONST char *string, /* Textual description of position. */ 711 TkTextIndex *indexPtr) /* Index structure to fill in. */ 712{ 713 return GetIndex(interp, NULL, textPtr, string, indexPtr, NULL); 714} 715 716/* 717 *--------------------------------------------------------------------------- 718 * 719 * GetIndex -- 720 * 721 * Given a string, return the index that is described. 722 * 723 * Results: 724 * The return value is a standard Tcl return result. If TCL_OK is 725 * returned, then everything went well and the index at *indexPtr is 726 * filled in; otherwise TCL_ERROR is returned and an error message is 727 * left in the interp's result. 728 * 729 * If *canCachePtr is non-NULL, and everything went well, the integer it 730 * points to is set to 1 if the indexPtr is something which can be 731 * cached, and zero otherwise. 732 * 733 * Side effects: 734 * None. 735 * 736 *--------------------------------------------------------------------------- 737 */ 738 739static int 740GetIndex( 741 Tcl_Interp *interp, /* Use this for error reporting. */ 742 TkSharedText *sharedPtr, 743 TkText *textPtr, /* Information about text widget. */ 744 CONST char *string, /* Textual description of position. */ 745 TkTextIndex *indexPtr, /* Index structure to fill in. */ 746 int *canCachePtr) /* Pointer to integer to store whether we can 747 * cache the index (or NULL). */ 748{ 749 char *p, *end, *endOfBase; 750 TkTextIndex first, last; 751 int wantLast, result; 752 char c; 753 CONST char *cp; 754 Tcl_DString copy; 755 int canCache = 0; 756 757 if (sharedPtr == NULL) { 758 sharedPtr = textPtr->sharedTextPtr; 759 } 760 761 /* 762 *--------------------------------------------------------------------- 763 * Stage 1: check to see if the index consists of nothing but a mark name. 764 * We do this check now even though it's also done later, in order to 765 * allow mark names that include funny characters such as spaces or "+1c". 766 *--------------------------------------------------------------------- 767 */ 768 769 if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) { 770 goto done; 771 } 772 773 /* 774 *------------------------------------------------ 775 * Stage 2: start again by parsing the base index. 776 *------------------------------------------------ 777 */ 778 779 indexPtr->tree = sharedPtr->tree; 780 781 /* 782 * First look for the form "tag.first" or "tag.last" where "tag" is the 783 * name of a valid tag. Try to use up as much as possible of the string in 784 * this check (strrchr instead of strchr below). Doing the check now, and 785 * in this way, allows tag names to include funny characters like "@" or 786 * "+1c". 787 */ 788 789 Tcl_DStringInit(©); 790 p = strrchr(Tcl_DStringAppend(©, string, -1), '.'); 791 if (p != NULL) { 792 TkTextSearch search; 793 TkTextTag *tagPtr; 794 Tcl_HashEntry *hPtr = NULL; 795 CONST char *tagName; 796 797 if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) { 798 wantLast = 0; 799 endOfBase = p+6; 800 } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) { 801 wantLast = 1; 802 endOfBase = p+5; 803 } else { 804 goto tryxy; 805 } 806 807 tagPtr = NULL; 808 tagName = Tcl_DStringValue(©); 809 if (((p - tagName) == 3) && !strncmp(tagName, "sel", 3)) { 810 /* 811 * Special case for sel tag which is not stored in the hash table. 812 */ 813 814 tagPtr = textPtr->selTagPtr; 815 } else { 816 *p = 0; 817 hPtr = Tcl_FindHashEntry(&sharedPtr->tagTable, tagName); 818 *p = '.'; 819 if (hPtr != NULL) { 820 tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr); 821 } 822 } 823 824 if (tagPtr == NULL) { 825 goto tryxy; 826 } 827 828 TkTextMakeByteIndex(sharedPtr->tree, textPtr, 0, 0, &first); 829 TkTextMakeByteIndex(sharedPtr->tree, textPtr, 830 TkBTreeNumLines(sharedPtr->tree, textPtr), 0, &last); 831 TkBTreeStartSearch(&first, &last, tagPtr, &search); 832 if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) { 833 if (tagPtr == textPtr->selTagPtr) { 834 tagName = "sel"; 835 } else { 836 tagName = Tcl_GetHashKey(&sharedPtr->tagTable, hPtr); 837 } 838 Tcl_ResetResult(interp); 839 Tcl_AppendResult(interp, 840 "text doesn't contain any characters tagged with \"", 841 tagName, "\"", NULL); 842 Tcl_DStringFree(©); 843 return TCL_ERROR; 844 } 845 *indexPtr = search.curIndex; 846 if (wantLast) { 847 while (TkBTreeNextTag(&search)) { 848 *indexPtr = search.curIndex; 849 } 850 } 851 goto gotBase; 852 } 853 854 tryxy: 855 if (string[0] == '@') { 856 /* 857 * Find character at a given x,y location in the window. 858 */ 859 860 int x, y; 861 862 cp = string+1; 863 x = strtol(cp, &end, 0); 864 if ((end == cp) || (*end != ',')) { 865 goto error; 866 } 867 cp = end+1; 868 y = strtol(cp, &end, 0); 869 if (end == cp) { 870 goto error; 871 } 872 TkTextPixelIndex(textPtr, x, y, indexPtr, NULL); 873 endOfBase = end; 874 goto gotBase; 875 } 876 877 if (isdigit(UCHAR(string[0])) || (string[0] == '-')) { 878 int lineIndex, charIndex; 879 880 /* 881 * Base is identified with line and character indices. 882 */ 883 884 lineIndex = strtol(string, &end, 0) - 1; 885 if ((end == string) || (*end != '.')) { 886 goto error; 887 } 888 p = end+1; 889 if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) { 890 charIndex = LAST_CHAR; 891 endOfBase = p+3; 892 } else { 893 charIndex = strtol(p, &end, 0); 894 if (end == p) { 895 goto error; 896 } 897 endOfBase = end; 898 } 899 TkTextMakeCharIndex(sharedPtr->tree, textPtr, lineIndex, charIndex, 900 indexPtr); 901 canCache = 1; 902 goto gotBase; 903 } 904 905 for (p = Tcl_DStringValue(©); *p != 0; p++) { 906 if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) { 907 break; 908 } 909 } 910 endOfBase = p; 911 if (string[0] == '.') { 912 /* 913 * See if the base position is the name of an embedded window. 914 */ 915 916 c = *endOfBase; 917 *endOfBase = 0; 918 result = TkTextWindowIndex(textPtr, Tcl_DStringValue(©), indexPtr); 919 *endOfBase = c; 920 if (result != 0) { 921 goto gotBase; 922 } 923 } 924 if ((string[0] == 'e') 925 && (strncmp(string, "end", 926 (size_t) (endOfBase-Tcl_DStringValue(©))) == 0)) { 927 /* 928 * Base position is end of text. 929 */ 930 931 TkTextMakeByteIndex(sharedPtr->tree, textPtr, 932 TkBTreeNumLines(sharedPtr->tree, textPtr), 0, indexPtr); 933 canCache = 1; 934 goto gotBase; 935 } else { 936 /* 937 * See if the base position is the name of a mark. 938 */ 939 940 c = *endOfBase; 941 *endOfBase = 0; 942 result = TkTextMarkNameToIndex(textPtr, Tcl_DStringValue(©), 943 indexPtr); 944 *endOfBase = c; 945 if (result == TCL_OK) { 946 goto gotBase; 947 } 948 949 /* 950 * See if the base position is the name of an embedded image. 951 */ 952 953 c = *endOfBase; 954 *endOfBase = 0; 955 result = TkTextImageIndex(textPtr, Tcl_DStringValue(©), indexPtr); 956 *endOfBase = c; 957 if (result != 0) { 958 goto gotBase; 959 } 960 } 961 goto error; 962 963 /* 964 *------------------------------------------------------------------- 965 * Stage 3: process zero or more modifiers. Each modifier is either a 966 * keyword like "wordend" or "linestart", or it has the form "op count 967 * units" where op is + or -, count is a number, and units is "chars" or 968 * "lines". 969 *------------------------------------------------------------------- 970 */ 971 972 gotBase: 973 cp = endOfBase; 974 while (1) { 975 while (isspace(UCHAR(*cp))) { 976 cp++; 977 } 978 if (*cp == 0) { 979 break; 980 } 981 982 if ((*cp == '+') || (*cp == '-')) { 983 cp = ForwBack(textPtr, cp, indexPtr); 984 } else { 985 cp = StartEnd(textPtr, cp, indexPtr); 986 } 987 if (cp == NULL) { 988 goto error; 989 } 990 } 991 Tcl_DStringFree(©); 992 993 done: 994 if (canCachePtr != NULL) { 995 *canCachePtr = canCache; 996 } 997 if (indexPtr->linePtr == NULL) { 998 Tcl_Panic("Bad index created"); 999 } 1000 return TCL_OK; 1001 1002 error: 1003 Tcl_DStringFree(©); 1004 Tcl_ResetResult(interp); 1005 Tcl_AppendResult(interp, "bad text index \"", string, "\"", NULL); 1006 return TCL_ERROR; 1007} 1008 1009/* 1010 *--------------------------------------------------------------------------- 1011 * 1012 * TkTextPrintIndex -- 1013 * 1014 * This function generates a string description of an index, suitable for 1015 * reading in again later. 1016 * 1017 * Results: 1018 * The characters pointed to by string are modified. Returns the number 1019 * of characters in the string. 1020 * 1021 * Side effects: 1022 * None. 1023 * 1024 *--------------------------------------------------------------------------- 1025 */ 1026 1027int 1028TkTextPrintIndex( 1029 CONST TkText *textPtr, 1030 CONST TkTextIndex *indexPtr,/* Pointer to index. */ 1031 char *string) /* Place to store the position. Must have at 1032 * least TK_POS_CHARS characters. */ 1033{ 1034 TkTextSegment *segPtr; 1035 TkTextLine *linePtr; 1036 int numBytes, charIndex; 1037 1038 numBytes = indexPtr->byteIndex; 1039 charIndex = 0; 1040 linePtr = indexPtr->linePtr; 1041 1042 for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { 1043 if (segPtr == NULL) { 1044 /* 1045 * Two logical lines merged into one display line through eliding 1046 * of a newline. 1047 */ 1048 1049 linePtr = TkBTreeNextLine(NULL, linePtr); 1050 segPtr = linePtr->segPtr; 1051 } 1052 if (numBytes <= segPtr->size) { 1053 break; 1054 } 1055 if (segPtr->typePtr == &tkTextCharType) { 1056 charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size); 1057 } else { 1058 charIndex += segPtr->size; 1059 } 1060 numBytes -= segPtr->size; 1061 } 1062 1063 if (segPtr->typePtr == &tkTextCharType) { 1064 charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes); 1065 } else { 1066 charIndex += numBytes; 1067 } 1068 1069 return sprintf(string, "%d.%d", 1070 TkBTreeLinesTo(textPtr, indexPtr->linePtr) + 1, charIndex); 1071} 1072 1073/* 1074 *--------------------------------------------------------------------------- 1075 * 1076 * TkTextIndexCmp -- 1077 * 1078 * Compare two indices to see which one is earlier in the text. 1079 * 1080 * Results: 1081 * The return value is 0 if index1Ptr and index2Ptr refer to the same 1082 * position in the file, -1 if index1Ptr refers to an earlier position 1083 * than index2Ptr, and 1 otherwise. 1084 * 1085 * Side effects: 1086 * None. 1087 * 1088 *--------------------------------------------------------------------------- 1089 */ 1090 1091int 1092TkTextIndexCmp( 1093 CONST TkTextIndex*index1Ptr,/* First index. */ 1094 CONST TkTextIndex*index2Ptr)/* Second index. */ 1095{ 1096 int line1, line2; 1097 1098 if (index1Ptr->linePtr == index2Ptr->linePtr) { 1099 if (index1Ptr->byteIndex < index2Ptr->byteIndex) { 1100 return -1; 1101 } else if (index1Ptr->byteIndex > index2Ptr->byteIndex) { 1102 return 1; 1103 } else { 1104 return 0; 1105 } 1106 } 1107 1108 /* 1109 * Assumption here that it is ok for comparisons to reflect the full 1110 * B-tree and not just the portion that is available to any client. This 1111 * should be true because the only indexPtr's we should be given are ones 1112 * which are valid for the current client. 1113 */ 1114 1115 line1 = TkBTreeLinesTo(NULL, index1Ptr->linePtr); 1116 line2 = TkBTreeLinesTo(NULL, index2Ptr->linePtr); 1117 if (line1 < line2) { 1118 return -1; 1119 } 1120 if (line1 > line2) { 1121 return 1; 1122 } 1123 return 0; 1124} 1125 1126/* 1127 *--------------------------------------------------------------------------- 1128 * 1129 * ForwBack -- 1130 * 1131 * This function handles +/- modifiers for indices to adjust the index 1132 * forwards or backwards. 1133 * 1134 * Results: 1135 * If the modifier in string is successfully parsed then the return value 1136 * is the address of the first character after the modifier, and 1137 * *indexPtr is updated to reflect the modifier. If there is a syntax 1138 * error in the modifier then NULL is returned. 1139 * 1140 * Side effects: 1141 * None. 1142 * 1143 *--------------------------------------------------------------------------- 1144 */ 1145 1146static CONST char * 1147ForwBack( 1148 TkText *textPtr, /* Information about text widget. */ 1149 CONST char *string, /* String to parse for additional info about 1150 * modifier (count and units). Points to "+" 1151 * or "-" that starts modifier. */ 1152 TkTextIndex *indexPtr) /* Index to update as specified in string. */ 1153{ 1154 register CONST char *p, *units; 1155 char *end; 1156 int count, lineIndex, modifier; 1157 size_t length; 1158 1159 /* 1160 * Get the count (how many units forward or backward). 1161 */ 1162 1163 p = string+1; 1164 while (isspace(UCHAR(*p))) { 1165 p++; 1166 } 1167 count = strtol(p, &end, 0); 1168 if (end == p) { 1169 return NULL; 1170 } 1171 p = end; 1172 while (isspace(UCHAR(*p))) { 1173 p++; 1174 } 1175 1176 /* 1177 * Find the end of this modifier (next space or + or - character), then 1178 * check if there is a textual 'display' or 'any' modifier. These 1179 * modifiers can be their own word (in which case they can be abbreviated) 1180 * or they can follow on to the actual unit in a single word (in which 1181 * case no abbreviation is allowed). So, 'display lines', 'd lines', 1182 * 'displaylin' are all ok, but 'dline' is not. 1183 */ 1184 1185 units = p; 1186 while ((*p != '\0') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) { 1187 p++; 1188 } 1189 length = p - units; 1190 if ((*units == 'd') && 1191 (strncmp(units, "display", (length > 7 ? 7 : length)) == 0)) { 1192 modifier = TKINDEX_DISPLAY; 1193 if (length > 7) { 1194 p -= (length - 7); 1195 } 1196 } else if ((*units == 'a') && 1197 (strncmp(units, "any", (length > 3 ? 3 : length)) == 0)) { 1198 modifier = TKINDEX_ANY; 1199 if (length > 3) { 1200 p -= (length - 3); 1201 } 1202 } else { 1203 modifier = TKINDEX_NONE; 1204 } 1205 1206 /* 1207 * If we had a modifier, which we interpreted ok, so now forward to the 1208 * actual units. 1209 */ 1210 1211 if (modifier != TKINDEX_NONE) { 1212 while (isspace(UCHAR(*p))) { 1213 p++; 1214 } 1215 units = p; 1216 while (*p!='\0' && !isspace(UCHAR(*p)) && *p!='+' && *p!='-') { 1217 p++; 1218 } 1219 length = p - units; 1220 } 1221 1222 /* 1223 * Finally parse the units. 1224 */ 1225 1226 if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) { 1227 TkTextCountType type; 1228 1229 if (modifier == TKINDEX_NONE) { 1230 type = COUNT_INDICES; 1231 } else if (modifier == TKINDEX_ANY) { 1232 type = COUNT_CHARS; 1233 } else { 1234 type = COUNT_DISPLAY_CHARS; 1235 } 1236 1237 if (*string == '+') { 1238 TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type); 1239 } else { 1240 TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type); 1241 } 1242 } else if ((*units == 'i') && (strncmp(units, "indices", length) == 0)) { 1243 TkTextCountType type; 1244 1245 if (modifier == TKINDEX_DISPLAY) { 1246 type = COUNT_DISPLAY_INDICES; 1247 } else { 1248 type = COUNT_INDICES; 1249 } 1250 1251 if (*string == '+') { 1252 TkTextIndexForwChars(textPtr, indexPtr, count, indexPtr, type); 1253 } else { 1254 TkTextIndexBackChars(textPtr, indexPtr, count, indexPtr, type); 1255 } 1256 } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) { 1257 if (modifier == TKINDEX_DISPLAY) { 1258 /* 1259 * Find the appropriate pixel offset of the current position 1260 * within its display line. This also has the side-effect of 1261 * moving indexPtr, but that doesn't matter since we will do it 1262 * again below. 1263 * 1264 * Then find the right display line, and finally calculated the 1265 * index we want in that display line, based on the original pixel 1266 * offset. 1267 */ 1268 1269 int xOffset, forward; 1270 1271 if (TkTextIsElided(textPtr, indexPtr, NULL)) { 1272 /* 1273 * Go forward to the first non-elided index. 1274 */ 1275 1276 TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, 1277 COUNT_DISPLAY_INDICES); 1278 } 1279 1280 /* 1281 * Unlike the Forw/BackChars code, the display line code is 1282 * sensitive to whether we are genuinely going forwards or 1283 * backwards. So, we need to determine that. This is important in 1284 * the case where we have "+ -3 displaylines", for example. 1285 */ 1286 1287 if ((count < 0) ^ (*string == '-')) { 1288 forward = 0; 1289 } else { 1290 forward = 1; 1291 } 1292 1293 count = abs(count); 1294 if (count == 0) { 1295 return p; 1296 } 1297 1298 if (forward) { 1299 TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, &xOffset); 1300 while (count-- > 0) { 1301 /* 1302 * Go to the end of the line, then forward one char/byte 1303 * to get to the beginning of the next line. 1304 */ 1305 1306 TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); 1307 TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, 1308 COUNT_DISPLAY_INDICES); 1309 } 1310 } else { 1311 TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, &xOffset); 1312 while (count-- > 0) { 1313 /* 1314 * Go to the beginning of the line, then backward one 1315 * char/byte to get to the end of the previous line. 1316 */ 1317 1318 TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); 1319 TkTextIndexBackChars(textPtr, indexPtr, 1, indexPtr, 1320 COUNT_DISPLAY_INDICES); 1321 } 1322 TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); 1323 } 1324 1325 /* 1326 * This call assumes indexPtr is the beginning of a display line 1327 * and moves it to the 'xOffset' position of that line, which is 1328 * just what we want. 1329 */ 1330 1331 TkTextIndexOfX(textPtr, xOffset, indexPtr); 1332 } else { 1333 lineIndex = TkBTreeLinesTo(textPtr, indexPtr->linePtr); 1334 if (*string == '+') { 1335 lineIndex += count; 1336 } else { 1337 lineIndex -= count; 1338 1339 /* 1340 * The check below retains the character position, even if the 1341 * line runs off the start of the file. Without it, the 1342 * character position will get reset to 0 by TkTextMakeIndex. 1343 */ 1344 1345 if (lineIndex < 0) { 1346 lineIndex = 0; 1347 } 1348 } 1349 1350 /* 1351 * This doesn't work quite right if using a proportional font or 1352 * UTF-8 characters with varying numbers of bytes, or if there are 1353 * embedded windows, images, etc. The cursor will bop around, 1354 * keeping a constant number of bytes (not characters) from the 1355 * left edge (but making sure not to split any UTF-8 characters), 1356 * regardless of the x-position the index corresponds to. The 1357 * proper way to do this is to get the x-position of the index and 1358 * then pick the character at the same x-position in the new line. 1359 */ 1360 1361 TkTextMakeByteIndex(indexPtr->tree, textPtr, lineIndex, 1362 indexPtr->byteIndex, indexPtr); 1363 } 1364 } else { 1365 return NULL; 1366 } 1367 return p; 1368} 1369 1370/* 1371 *--------------------------------------------------------------------------- 1372 * 1373 * TkTextIndexForwBytes -- 1374 * 1375 * Given an index for a text widget, this function creates a new index 1376 * that points "count" bytes ahead of the source index. 1377 * 1378 * Results: 1379 * *dstPtr is modified to refer to the character "count" bytes after 1380 * srcPtr, or to the last character in the TkText if there aren't "count" 1381 * bytes left. 1382 * 1383 * In this latter case, the function returns '1' to indicate that not all 1384 * of 'byteCount' could be used. 1385 * 1386 * Side effects: 1387 * None. 1388 * 1389 *--------------------------------------------------------------------------- 1390 */ 1391 1392int 1393TkTextIndexForwBytes( 1394 CONST TkText *textPtr, 1395 CONST TkTextIndex *srcPtr, /* Source index. */ 1396 int byteCount, /* How many bytes forward to move. May be 1397 * negative. */ 1398 TkTextIndex *dstPtr) /* Destination index: gets modified. */ 1399{ 1400 TkTextLine *linePtr; 1401 TkTextSegment *segPtr; 1402 int lineLength; 1403 1404 if (byteCount < 0) { 1405 TkTextIndexBackBytes(textPtr, srcPtr, -byteCount, dstPtr); 1406 return 0; 1407 } 1408 1409 *dstPtr = *srcPtr; 1410 dstPtr->byteIndex += byteCount; 1411 while (1) { 1412 /* 1413 * Compute the length of the current line. 1414 */ 1415 1416 lineLength = 0; 1417 for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL; 1418 segPtr = segPtr->nextPtr) { 1419 lineLength += segPtr->size; 1420 } 1421 1422 /* 1423 * If the new index is in the same line then we're done. Otherwise go 1424 * on to the next line. 1425 */ 1426 1427 if (dstPtr->byteIndex < lineLength) { 1428 return 0; 1429 } 1430 dstPtr->byteIndex -= lineLength; 1431 linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); 1432 if (linePtr == NULL) { 1433 dstPtr->byteIndex = lineLength - 1; 1434 return 1; 1435 } 1436 dstPtr->linePtr = linePtr; 1437 } 1438} 1439 1440/* 1441 *--------------------------------------------------------------------------- 1442 * 1443 * TkTextIndexForwChars -- 1444 * 1445 * Given an index for a text widget, this function creates a new index 1446 * that points "count" items of type given by "type" ahead of the source 1447 * index. "count" can be zero, which is useful in the case where one 1448 * wishes to move forward by display (non-elided) chars or indices or one 1449 * wishes to move forward by chars, skipping any intervening indices. In 1450 * this case dstPtr will point to the first acceptable index which is 1451 * encountered. 1452 * 1453 * Results: 1454 * *dstPtr is modified to refer to the character "count" items after 1455 * srcPtr, or to the last character in the TkText if there aren't 1456 * sufficient items left in the widget. 1457 * 1458 * Side effects: 1459 * None. 1460 * 1461 *--------------------------------------------------------------------------- 1462 */ 1463 1464void 1465TkTextIndexForwChars( 1466 CONST TkText *textPtr, /* Overall information about text widget. */ 1467 CONST TkTextIndex *srcPtr, /* Source index. */ 1468 int charCount, /* How many characters forward to move. May 1469 * be negative. */ 1470 TkTextIndex *dstPtr, /* Destination index: gets modified. */ 1471 TkTextCountType type) /* The type of item to count */ 1472{ 1473 TkTextLine *linePtr; 1474 TkTextSegment *segPtr; 1475 TkTextElideInfo *infoPtr = NULL; 1476 int byteOffset; 1477 char *start, *end, *p; 1478 Tcl_UniChar ch; 1479 int elide = 0; 1480 int checkElided = (type & COUNT_DISPLAY); 1481 1482 if (charCount < 0) { 1483 TkTextIndexBackChars(textPtr, srcPtr, -charCount, dstPtr, type); 1484 return; 1485 } 1486 if (checkElided) { 1487 infoPtr = (TkTextElideInfo *) 1488 ckalloc((unsigned) sizeof(TkTextElideInfo)); 1489 elide = TkTextIsElided(textPtr, srcPtr, infoPtr); 1490 } 1491 1492 *dstPtr = *srcPtr; 1493 1494 /* 1495 * Find seg that contains src byteIndex. Move forward specified number of 1496 * chars. 1497 */ 1498 1499 if (checkElided) { 1500 /* 1501 * In this case we have already calculated the information we need, so 1502 * no need to use TkTextIndexToSeg() 1503 */ 1504 1505 segPtr = infoPtr->segPtr; 1506 byteOffset = dstPtr->byteIndex - infoPtr->segOffset; 1507 } else { 1508 segPtr = TkTextIndexToSeg(dstPtr, &byteOffset); 1509 } 1510 1511 while (1) { 1512 /* 1513 * Go through each segment in line looking for specified character 1514 * index. 1515 */ 1516 1517 for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { 1518 /* 1519 * If we do need to pay attention to the visibility of 1520 * characters/indices, check that first. If the current segment 1521 * isn't visible, then we simply continue the loop. 1522 */ 1523 1524 if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType) 1525 || (segPtr->typePtr == &tkTextToggleOnType))) { 1526 TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; 1527 1528 /* 1529 * The elide state only changes if this tag is either the 1530 * current highest priority tag (and is therefore being 1531 * toggled off), or it's a new tag with higher priority. 1532 */ 1533 1534 if (tagPtr->elideString != NULL) { 1535 infoPtr->tagCnts[tagPtr->priority]++; 1536 if (infoPtr->tagCnts[tagPtr->priority] & 1) { 1537 infoPtr->tagPtrs[tagPtr->priority] = tagPtr; 1538 } 1539 1540 if (tagPtr->priority >= infoPtr->elidePriority) { 1541 if (segPtr->typePtr == &tkTextToggleOffType) { 1542 /* 1543 * If it is being toggled off, and it has an elide 1544 * string, it must actually be the current highest 1545 * priority tag, so this check is redundant: 1546 */ 1547 1548 if (tagPtr->priority != infoPtr->elidePriority) { 1549 Tcl_Panic("Bad tag priority being toggled off"); 1550 } 1551 1552 /* 1553 * Find previous elide tag, if any (if not then 1554 * elide will be zero, of course). 1555 */ 1556 1557 elide = 0; 1558 while (--infoPtr->elidePriority > 0) { 1559 if (infoPtr->tagCnts[infoPtr->elidePriority] 1560 & 1) { 1561 elide = infoPtr->tagPtrs 1562 [infoPtr->elidePriority]->elide; 1563 break; 1564 } 1565 } 1566 } else { 1567 elide = tagPtr->elide; 1568 infoPtr->elidePriority = tagPtr->priority; 1569 } 1570 } 1571 } 1572 } 1573 1574 if (!elide) { 1575 if (segPtr->typePtr == &tkTextCharType) { 1576 start = segPtr->body.chars + byteOffset; 1577 end = segPtr->body.chars + segPtr->size; 1578 for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) { 1579 if (charCount == 0) { 1580 dstPtr->byteIndex += (p - start); 1581 goto forwardCharDone; 1582 } 1583 charCount--; 1584 } 1585 } else if (type & COUNT_INDICES) { 1586 if (charCount < segPtr->size - byteOffset) { 1587 dstPtr->byteIndex += charCount; 1588 goto forwardCharDone; 1589 } 1590 charCount -= segPtr->size - byteOffset; 1591 } 1592 } 1593 1594 dstPtr->byteIndex += segPtr->size - byteOffset; 1595 byteOffset = 0; 1596 } 1597 1598 /* 1599 * Go to the next line. If we are at the end of the text item, back up 1600 * one byte (for the terminal '\n' character) and return that index. 1601 */ 1602 1603 linePtr = TkBTreeNextLine(textPtr, dstPtr->linePtr); 1604 if (linePtr == NULL) { 1605 dstPtr->byteIndex -= sizeof(char); 1606 goto forwardCharDone; 1607 } 1608 dstPtr->linePtr = linePtr; 1609 dstPtr->byteIndex = 0; 1610 segPtr = dstPtr->linePtr->segPtr; 1611 } 1612 1613 forwardCharDone: 1614 if (infoPtr != NULL) { 1615 TkTextFreeElideInfo(infoPtr); 1616 ckfree((char *) infoPtr); 1617 } 1618} 1619 1620/* 1621 *--------------------------------------------------------------------------- 1622 * 1623 * TkTextIndexCount -- 1624 * 1625 * Given an ordered pair of indices in a text widget, this function 1626 * counts how many characters (not bytes) are between the two indices. 1627 * 1628 * It is illegal to call this function with unordered indices. 1629 * 1630 * Note that 'textPtr' is only used if we need to check for elided 1631 * attributes, i.e. if type is COUNT_DISPLAY_INDICES or 1632 * COUNT_DISPLAY_CHARS. 1633 * 1634 * Results: 1635 * The number of characters in the given range, which meet the 1636 * appropriate 'type' attributes. 1637 * 1638 * Side effects: 1639 * None. 1640 * 1641 *--------------------------------------------------------------------------- 1642 */ 1643 1644int 1645TkTextIndexCount( 1646 CONST TkText *textPtr, /* Overall information about text widget. */ 1647 CONST TkTextIndex *indexPtr1, 1648 /* Index describing location of character from 1649 * which to count. */ 1650 CONST TkTextIndex *indexPtr2, 1651 /* Index describing location of last character 1652 * at which to stop the count. */ 1653 TkTextCountType type) /* The kind of indices to count. */ 1654{ 1655 TkTextLine *linePtr1; 1656 TkTextSegment *segPtr, *seg2Ptr = NULL; 1657 TkTextElideInfo *infoPtr = NULL; 1658 int byteOffset, maxBytes, count = 0, elide = 0; 1659 int checkElided = (type & COUNT_DISPLAY); 1660 1661 /* 1662 * Find seg that contains src index, and remember how many bytes not to 1663 * count in the given segment. 1664 */ 1665 1666 segPtr = TkTextIndexToSeg(indexPtr1, &byteOffset); 1667 linePtr1 = indexPtr1->linePtr; 1668 1669 seg2Ptr = TkTextIndexToSeg(indexPtr2, &maxBytes); 1670 1671 if (checkElided) { 1672 infoPtr = (TkTextElideInfo *) 1673 ckalloc((unsigned) sizeof(TkTextElideInfo)); 1674 elide = TkTextIsElided(textPtr, indexPtr1, infoPtr); 1675 } 1676 1677 while (1) { 1678 /* 1679 * Go through each segment in line adding up the number of characters. 1680 */ 1681 1682 for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) { 1683 /* 1684 * If we do need to pay attention to the visibility of 1685 * characters/indices, check that first. If the current segment 1686 * isn't visible, then we simply continue the loop. 1687 */ 1688 1689 if (checkElided) { 1690 if ((segPtr->typePtr == &tkTextToggleOffType) 1691 || (segPtr->typePtr == &tkTextToggleOnType)) { 1692 TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; 1693 1694 /* 1695 * The elide state only changes if this tag is either the 1696 * current highest priority tag (and is therefore being 1697 * toggled off), or it's a new tag with higher priority. 1698 */ 1699 1700 if (tagPtr->elideString != NULL) { 1701 infoPtr->tagCnts[tagPtr->priority]++; 1702 if (infoPtr->tagCnts[tagPtr->priority] & 1) { 1703 infoPtr->tagPtrs[tagPtr->priority] = tagPtr; 1704 } 1705 if (tagPtr->priority >= infoPtr->elidePriority) { 1706 if (segPtr->typePtr == &tkTextToggleOffType) { 1707 /* 1708 * If it is being toggled off, and it has an 1709 * elide string, it must actually be the 1710 * current highest priority tag, so this check 1711 * is redundant: 1712 */ 1713 1714 if (tagPtr->priority!=infoPtr->elidePriority) { 1715 Tcl_Panic("Bad tag priority being toggled off"); 1716 } 1717 1718 /* 1719 * Find previous elide tag, if any (if not 1720 * then elide will be zero, of course). 1721 */ 1722 1723 elide = 0; 1724 while (--infoPtr->elidePriority > 0) { 1725 if (infoPtr->tagCnts[ 1726 infoPtr->elidePriority] & 1) { 1727 elide = infoPtr->tagPtrs[ 1728 infoPtr->elidePriority]->elide; 1729 break; 1730 } 1731 } 1732 } else { 1733 elide = tagPtr->elide; 1734 infoPtr->elidePriority = tagPtr->priority; 1735 } 1736 } 1737 } 1738 } 1739 if (elide) { 1740 if (segPtr == seg2Ptr) { 1741 goto countDone; 1742 } 1743 byteOffset = 0; 1744 continue; 1745 } 1746 } 1747 1748 if (segPtr->typePtr == &tkTextCharType) { 1749 int byteLen = segPtr->size - byteOffset; 1750 register unsigned char *str = (unsigned char *) 1751 segPtr->body.chars + byteOffset; 1752 register int i; 1753 1754 if (segPtr == seg2Ptr) { 1755 if (byteLen > (maxBytes - byteOffset)) { 1756 byteLen = maxBytes - byteOffset; 1757 } 1758 } 1759 i = byteLen; 1760 1761 /* 1762 * This is a speed sensitive function, so run specially over 1763 * the string to count continuous ascii characters before 1764 * resorting to the Tcl_NumUtfChars call. This is a long form 1765 * of: 1766 * 1767 * stringPtr->numChars = 1768 * Tcl_NumUtfChars(objPtr->bytes, objPtr->length); 1769 */ 1770 1771 while (i && (*str < 0xC0)) { 1772 i--; 1773 str++; 1774 } 1775 count += byteLen - i; 1776 if (i) { 1777 count += Tcl_NumUtfChars(segPtr->body.chars + byteOffset 1778 + (byteLen - i), i); 1779 } 1780 } else { 1781 if (type & COUNT_INDICES) { 1782 int byteLen = segPtr->size - byteOffset; 1783 1784 if (segPtr == seg2Ptr) { 1785 if (byteLen > (maxBytes - byteOffset)) { 1786 byteLen = maxBytes - byteOffset; 1787 } 1788 } 1789 count += byteLen; 1790 } 1791 } 1792 if (segPtr == seg2Ptr) { 1793 goto countDone; 1794 } 1795 byteOffset = 0; 1796 } 1797 1798 /* 1799 * Go to the next line. If we are at the end of the text item, back up 1800 * one byte (for the terminal '\n' character) and return that index. 1801 */ 1802 1803 linePtr1 = TkBTreeNextLine(textPtr, linePtr1); 1804 if (linePtr1 == NULL) { 1805 Tcl_Panic("Reached end of text widget when counting characters"); 1806 } 1807 segPtr = linePtr1->segPtr; 1808 } 1809 1810 countDone: 1811 if (infoPtr != NULL) { 1812 TkTextFreeElideInfo(infoPtr); 1813 ckfree((char *) infoPtr); 1814 } 1815 return count; 1816} 1817 1818/* 1819 *--------------------------------------------------------------------------- 1820 * 1821 * TkTextIndexBackBytes -- 1822 * 1823 * Given an index for a text widget, this function creates a new index 1824 * that points "count" bytes earlier than the source index. 1825 * 1826 * Results: 1827 * *dstPtr is modified to refer to the character "count" bytes before 1828 * srcPtr, or to the first character in the TkText if there aren't 1829 * "count" bytes earlier than srcPtr. 1830 * 1831 * Returns 1 if we couldn't use all of 'byteCount' because we have run 1832 * into the beginning or end of the text, and zero otherwise. 1833 * 1834 * Side effects: 1835 * None. 1836 * 1837 *--------------------------------------------------------------------------- 1838 */ 1839 1840int 1841TkTextIndexBackBytes( 1842 CONST TkText *textPtr, 1843 CONST TkTextIndex *srcPtr, /* Source index. */ 1844 int byteCount, /* How many bytes backward to move. May be 1845 * negative. */ 1846 TkTextIndex *dstPtr) /* Destination index: gets modified. */ 1847{ 1848 TkTextSegment *segPtr; 1849 int lineIndex; 1850 1851 if (byteCount < 0) { 1852 return TkTextIndexForwBytes(textPtr, srcPtr, -byteCount, dstPtr); 1853 } 1854 1855 *dstPtr = *srcPtr; 1856 dstPtr->byteIndex -= byteCount; 1857 lineIndex = -1; 1858 while (dstPtr->byteIndex < 0) { 1859 /* 1860 * Move back one line in the text. If we run off the beginning of the 1861 * file then just return the first character in the text. 1862 */ 1863 1864 if (lineIndex < 0) { 1865 lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); 1866 } 1867 if (lineIndex == 0) { 1868 dstPtr->byteIndex = 0; 1869 return 1; 1870 } 1871 lineIndex--; 1872 dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); 1873 1874 /* 1875 * Compute the length of the line and add that to dstPtr->charIndex. 1876 */ 1877 1878 for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL; 1879 segPtr = segPtr->nextPtr) { 1880 dstPtr->byteIndex += segPtr->size; 1881 } 1882 } 1883 return 0; 1884} 1885 1886/* 1887 *--------------------------------------------------------------------------- 1888 * 1889 * TkTextIndexBackChars -- 1890 * 1891 * Given an index for a text widget, this function creates a new index 1892 * that points "count" items of type given by "type" earlier than the 1893 * source index. "count" can be zero, which is useful in the case where 1894 * one wishes to move backward by display (non-elided) chars or indices 1895 * or one wishes to move backward by chars, skipping any intervening 1896 * indices. In this case the returned index *dstPtr will point just 1897 * _after_ the first acceptable index which is encountered. 1898 * 1899 * Results: 1900 * *dstPtr is modified to refer to the character "count" items before 1901 * srcPtr, or to the first index in the window if there aren't sufficient 1902 * items earlier than srcPtr. 1903 * 1904 * Side effects: 1905 * None. 1906 * 1907 *--------------------------------------------------------------------------- 1908 */ 1909 1910void 1911TkTextIndexBackChars( 1912 CONST TkText *textPtr, /* Overall information about text widget. */ 1913 CONST TkTextIndex *srcPtr, /* Source index. */ 1914 int charCount, /* How many characters backward to move. May 1915 * be negative. */ 1916 TkTextIndex *dstPtr, /* Destination index: gets modified. */ 1917 TkTextCountType type) /* The type of item to count */ 1918{ 1919 TkTextSegment *segPtr, *oldPtr; 1920 TkTextElideInfo *infoPtr = NULL; 1921 int lineIndex, segSize; 1922 CONST char *p, *start, *end; 1923 int elide = 0; 1924 int checkElided = (type & COUNT_DISPLAY); 1925 1926 if (charCount < 0) { 1927 TkTextIndexForwChars(textPtr, srcPtr, -charCount, dstPtr, type); 1928 return; 1929 } 1930 if (checkElided) { 1931 infoPtr = (TkTextElideInfo *) ckalloc(sizeof(TkTextElideInfo)); 1932 elide = TkTextIsElided(textPtr, srcPtr, infoPtr); 1933 } 1934 1935 *dstPtr = *srcPtr; 1936 1937 /* 1938 * Find offset within seg that contains byteIndex. Move backward specified 1939 * number of chars. 1940 */ 1941 1942 lineIndex = -1; 1943 1944 segSize = dstPtr->byteIndex; 1945 1946 if (checkElided) { 1947 segPtr = infoPtr->segPtr; 1948 segSize -= infoPtr->segOffset; 1949 } else { 1950 TkTextLine *linePtr = dstPtr->linePtr; 1951 for (segPtr = linePtr->segPtr; ; segPtr = segPtr->nextPtr) { 1952 if (segPtr == NULL) { 1953 /* 1954 * Two logical lines merged into one display line through 1955 * eliding of a newline. 1956 */ 1957 1958 linePtr = TkBTreeNextLine(NULL, linePtr); 1959 segPtr = linePtr->segPtr; 1960 } 1961 if (segSize <= segPtr->size) { 1962 break; 1963 } 1964 segSize -= segPtr->size; 1965 } 1966 } 1967 1968 /* 1969 * Now segPtr points to the segment containing the starting index. 1970 */ 1971 1972 while (1) { 1973 /* 1974 * If we do need to pay attention to the visibility of 1975 * characters/indices, check that first. If the current segment isn't 1976 * visible, then we simply continue the loop. 1977 */ 1978 1979 if (checkElided && ((segPtr->typePtr == &tkTextToggleOffType) 1980 || (segPtr->typePtr == &tkTextToggleOnType))) { 1981 TkTextTag *tagPtr = segPtr->body.toggle.tagPtr; 1982 1983 /* 1984 * The elide state only changes if this tag is either the current 1985 * highest priority tag (and is therefore being toggled off), or 1986 * it's a new tag with higher priority. 1987 */ 1988 1989 if (tagPtr->elideString != NULL) { 1990 infoPtr->tagCnts[tagPtr->priority]++; 1991 if (infoPtr->tagCnts[tagPtr->priority] & 1) { 1992 infoPtr->tagPtrs[tagPtr->priority] = tagPtr; 1993 } 1994 if (tagPtr->priority >= infoPtr->elidePriority) { 1995 if (segPtr->typePtr == &tkTextToggleOnType) { 1996 /* 1997 * If it is being toggled on, and it has an elide 1998 * string, it must actually be the current highest 1999 * priority tag, so this check is redundant: 2000 */ 2001 2002 if (tagPtr->priority != infoPtr->elidePriority) { 2003 Tcl_Panic("Bad tag priority being toggled on"); 2004 } 2005 2006 /* 2007 * Find previous elide tag, if any (if not then elide 2008 * will be zero, of course). 2009 */ 2010 2011 elide = 0; 2012 while (--infoPtr->elidePriority > 0) { 2013 if (infoPtr->tagCnts[infoPtr->elidePriority] & 1) { 2014 elide = infoPtr->tagPtrs[ 2015 infoPtr->elidePriority]->elide; 2016 break; 2017 } 2018 } 2019 } else { 2020 elide = tagPtr->elide; 2021 infoPtr->elidePriority = tagPtr->priority; 2022 } 2023 } 2024 } 2025 } 2026 2027 if (!elide) { 2028 if (segPtr->typePtr == &tkTextCharType) { 2029 start = segPtr->body.chars; 2030 end = segPtr->body.chars + segSize; 2031 for (p = end; ; p = Tcl_UtfPrev(p, start)) { 2032 if (charCount == 0) { 2033 dstPtr->byteIndex -= (end - p); 2034 goto backwardCharDone; 2035 } 2036 if (p == start) { 2037 break; 2038 } 2039 charCount--; 2040 } 2041 } else { 2042 if (type & COUNT_INDICES) { 2043 if (charCount <= segSize) { 2044 dstPtr->byteIndex -= charCount; 2045 goto backwardCharDone; 2046 } 2047 charCount -= segSize; 2048 } 2049 } 2050 } 2051 dstPtr->byteIndex -= segSize; 2052 2053 /* 2054 * Move back into previous segment. 2055 */ 2056 2057 oldPtr = segPtr; 2058 segPtr = dstPtr->linePtr->segPtr; 2059 if (segPtr != oldPtr) { 2060 for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) { 2061 /* Empty body. */ 2062 } 2063 segSize = segPtr->size; 2064 continue; 2065 } 2066 2067 /* 2068 * Move back to previous line. 2069 */ 2070 2071 if (lineIndex < 0) { 2072 lineIndex = TkBTreeLinesTo(textPtr, dstPtr->linePtr); 2073 } 2074 if (lineIndex == 0) { 2075 dstPtr->byteIndex = 0; 2076 goto backwardCharDone; 2077 } 2078 lineIndex--; 2079 dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, textPtr, lineIndex); 2080 2081 /* 2082 * Compute the length of the line and add that to dstPtr->byteIndex. 2083 */ 2084 2085 oldPtr = dstPtr->linePtr->segPtr; 2086 for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) { 2087 dstPtr->byteIndex += segPtr->size; 2088 oldPtr = segPtr; 2089 } 2090 segPtr = oldPtr; 2091 segSize = segPtr->size; 2092 } 2093 2094 backwardCharDone: 2095 if (infoPtr != NULL) { 2096 TkTextFreeElideInfo(infoPtr); 2097 ckfree((char *) infoPtr); 2098 } 2099} 2100 2101/* 2102 *---------------------------------------------------------------------- 2103 * 2104 * StartEnd -- 2105 * 2106 * This function handles modifiers like "wordstart" and "lineend" to 2107 * adjust indices forwards or backwards. 2108 * 2109 * Results: 2110 * If the modifier is successfully parsed then the return value is the 2111 * address of the first character after the modifier, and *indexPtr is 2112 * updated to reflect the modifier. If there is a syntax error in the 2113 * modifier then NULL is returned. 2114 * 2115 * Side effects: 2116 * None. 2117 * 2118 *---------------------------------------------------------------------- 2119 */ 2120 2121static CONST char * 2122StartEnd( 2123 TkText *textPtr, /* Information about text widget. */ 2124 CONST char *string, /* String to parse for additional info about 2125 * modifier (count and units). Points to first 2126 * character of modifer word. */ 2127 TkTextIndex *indexPtr) /* Index to modify based on string. */ 2128{ 2129 CONST char *p; 2130 size_t length; 2131 register TkTextSegment *segPtr; 2132 int modifier; 2133 2134 /* 2135 * Find the end of the modifier word. 2136 */ 2137 2138 for (p = string; isalnum(UCHAR(*p)); p++) { 2139 /* Empty loop body. */ 2140 } 2141 2142 length = p-string; 2143 if ((*string == 'd') && 2144 (strncmp(string, "display", (length > 7 ? 7 : length)) == 0)) { 2145 modifier = TKINDEX_DISPLAY; 2146 if (length > 7) { 2147 p -= (length - 7); 2148 } 2149 } else if ((*string == 'a') && 2150 (strncmp(string, "any", (length > 3 ? 3 : length)) == 0)) { 2151 modifier = TKINDEX_ANY; 2152 if (length > 3) { 2153 p -= (length - 3); 2154 } 2155 } else { 2156 modifier = TKINDEX_NONE; 2157 } 2158 2159 /* 2160 * If we had a modifier, which we interpreted ok, so now forward to the 2161 * actual units. 2162 */ 2163 2164 if (modifier != TKINDEX_NONE) { 2165 while (isspace(UCHAR(*p))) { 2166 p++; 2167 } 2168 string = p; 2169 while ((*p!='\0') && !isspace(UCHAR(*p)) && (*p!='+') && (*p!='-')) { 2170 p++; 2171 } 2172 length = p - string; 2173 } 2174 2175 if ((*string == 'l') && (strncmp(string, "lineend", length) == 0) 2176 && (length >= 5)) { 2177 if (modifier == TKINDEX_DISPLAY) { 2178 TkTextFindDisplayLineEnd(textPtr, indexPtr, 1, NULL); 2179 } else { 2180 indexPtr->byteIndex = 0; 2181 for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL; 2182 segPtr = segPtr->nextPtr) { 2183 indexPtr->byteIndex += segPtr->size; 2184 } 2185 2186 /* 2187 * We know '\n' is encoded with a single byte index. 2188 */ 2189 2190 indexPtr->byteIndex -= sizeof(char); 2191 } 2192 } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0) 2193 && (length >= 5)) { 2194 if (modifier == TKINDEX_DISPLAY) { 2195 TkTextFindDisplayLineEnd(textPtr, indexPtr, 0, NULL); 2196 } else { 2197 indexPtr->byteIndex = 0; 2198 } 2199 } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0) 2200 && (length >= 5)) { 2201 int firstChar = 1; 2202 int offset; 2203 2204 /* 2205 * If the current character isn't part of a word then just move 2206 * forward one character. Otherwise move forward until finding a 2207 * character that isn't part of a word and stop there. 2208 */ 2209 2210 if (modifier == TKINDEX_DISPLAY) { 2211 TkTextIndexForwChars(textPtr, indexPtr, 0, indexPtr, 2212 COUNT_DISPLAY_INDICES); 2213 } 2214 segPtr = TkTextIndexToSeg(indexPtr, &offset); 2215 while (1) { 2216 int chSize = 1; 2217 2218 if (segPtr->typePtr == &tkTextCharType) { 2219 Tcl_UniChar ch; 2220 2221 chSize = Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch); 2222 if (!Tcl_UniCharIsWordChar(ch)) { 2223 break; 2224 } 2225 firstChar = 0; 2226 } 2227 offset += chSize; 2228 indexPtr->byteIndex += chSize; 2229 if (offset >= segPtr->size) { 2230 segPtr = TkTextIndexToSeg(indexPtr, &offset); 2231 } 2232 } 2233 if (firstChar) { 2234 if (modifier == TKINDEX_DISPLAY) { 2235 TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, 2236 COUNT_DISPLAY_INDICES); 2237 } else { 2238 TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr, 2239 COUNT_INDICES); 2240 } 2241 } 2242 } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0) 2243 && (length >= 5)) { 2244 int firstChar = 1; 2245 int offset; 2246 2247 if (modifier == TKINDEX_DISPLAY) { 2248 TkTextIndexForwChars(NULL, indexPtr, 0, indexPtr, 2249 COUNT_DISPLAY_INDICES); 2250 } 2251 2252 /* 2253 * Starting with the current character, look for one that's not part 2254 * of a word and keep moving backward until you find one. Then if the 2255 * character found wasn't the first one, move forward again one 2256 * position. 2257 */ 2258 2259 segPtr = TkTextIndexToSeg(indexPtr, &offset); 2260 while (1) { 2261 int chSize = 1; 2262 2263 if (segPtr->typePtr == &tkTextCharType) { 2264 Tcl_UniChar ch; 2265 2266 Tcl_UtfToUniChar(segPtr->body.chars + offset, &ch); 2267 if (!Tcl_UniCharIsWordChar(ch)) { 2268 break; 2269 } 2270 if (offset > 0) { 2271 chSize = (segPtr->body.chars + offset 2272 - Tcl_UtfPrev(segPtr->body.chars + offset, 2273 segPtr->body.chars)); 2274 } 2275 firstChar = 0; 2276 } 2277 offset -= chSize; 2278 indexPtr->byteIndex -= chSize; 2279 if (offset < 0) { 2280 if (indexPtr->byteIndex < 0) { 2281 indexPtr->byteIndex = 0; 2282 goto done; 2283 } 2284 segPtr = TkTextIndexToSeg(indexPtr, &offset); 2285 } 2286 } 2287 2288 if (!firstChar) { 2289 if (modifier == TKINDEX_DISPLAY) { 2290 TkTextIndexForwChars(textPtr, indexPtr, 1, indexPtr, 2291 COUNT_DISPLAY_INDICES); 2292 } else { 2293 TkTextIndexForwChars(NULL, indexPtr, 1, indexPtr, 2294 COUNT_INDICES); 2295 } 2296 } 2297 } else { 2298 return NULL; 2299 } 2300 2301 done: 2302 return p; 2303} 2304 2305/* 2306 * Local Variables: 2307 * mode: c 2308 * c-basic-offset: 4 2309 * fill-column: 78 2310 * End: 2311 */ 2312