tkTextIndex.c
上传用户:rrhhcc
上传日期:2015-12-11
资源大小:54129k
文件大小:30k
源码类别:

通讯编程

开发平台:

Visual C++

  1. /* 
  2.  * tkTextIndex.c --
  3.  *
  4.  * This module provides procedures that manipulate indices for
  5.  * text 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
  11.  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
  12.  *
  13.  * RCS: @(#) $Id: tkTextIndex.c,v 1.6 2002/08/05 04:30:40 dgp Exp $
  14.  */
  15. #include "default.h"
  16. #include "tkPort.h"
  17. #include "tkInt.h"
  18. #include "tkText.h"
  19. /*
  20.  * Index to use to select last character in line (very large integer):
  21.  */
  22. #define LAST_CHAR 1000000
  23. /*
  24.  * Forward declarations for procedures defined later in this file:
  25.  */
  26. static CONST char * ForwBack _ANSI_ARGS_((CONST char *string,
  27.     TkTextIndex *indexPtr));
  28. static CONST char * StartEnd _ANSI_ARGS_((CONST char *string,
  29.     TkTextIndex *indexPtr));
  30. /*
  31.  *---------------------------------------------------------------------------
  32.  *
  33.  * TkTextMakeByteIndex --
  34.  *
  35.  * Given a line index and a byte index, look things up in the B-tree
  36.  * and fill in a TkTextIndex structure.
  37.  *
  38.  * Results:
  39.  * The structure at *indexPtr is filled in with information about the
  40.  * character at lineIndex and byteIndex (or the closest existing
  41.  * character, if the specified one doesn't exist), and indexPtr is
  42.  * returned as result.
  43.  *
  44.  * Side effects:
  45.  * None.
  46.  *
  47.  *---------------------------------------------------------------------------
  48.  */
  49. TkTextIndex *
  50. TkTextMakeByteIndex(tree, lineIndex, byteIndex, indexPtr)
  51.     TkTextBTree tree; /* Tree that lineIndex and charIndex refer
  52.  * to. */
  53.     int lineIndex; /* Index of desired line (0 means first
  54.  * line of text). */
  55.     int byteIndex; /* Byte index of desired character. */
  56.     TkTextIndex *indexPtr; /* Structure to fill in. */
  57. {
  58.     TkTextSegment *segPtr;
  59.     int index;
  60.     CONST char *p, *start;
  61.     Tcl_UniChar ch;
  62.     indexPtr->tree = tree;
  63.     if (lineIndex < 0) {
  64. lineIndex = 0;
  65. byteIndex = 0;
  66.     }
  67.     if (byteIndex < 0) {
  68. byteIndex = 0;
  69.     }
  70.     indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex);
  71.     if (indexPtr->linePtr == NULL) {
  72. indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree));
  73. byteIndex = 0;
  74.     }
  75.     if (byteIndex == 0) {
  76. indexPtr->byteIndex = byteIndex;
  77. return indexPtr;
  78.     }
  79.     /*
  80.      * Verify that the index is within the range of the line and points
  81.      * to a valid character boundary.  
  82.      */
  83.     index = 0;
  84.     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
  85. if (segPtr == NULL) {
  86.     /*
  87.      * Use the index of the last character in the line.  Since
  88.      * the last character on the line is guaranteed to be a 'n',
  89.      * we can back up a constant sizeof(char) bytes.
  90.      */
  91.      
  92.     indexPtr->byteIndex = index - sizeof(char);
  93.     break;
  94. }
  95. if (index + segPtr->size > byteIndex) {
  96.     indexPtr->byteIndex = byteIndex;
  97.     if ((byteIndex > index) && (segPtr->typePtr == &tkTextCharType)) {
  98. /*
  99.  * Prevent UTF-8 character from being split up by ensuring
  100.  * that byteIndex falls on a character boundary.  If index
  101.  * falls in the middle of a UTF-8 character, it will be
  102.  * adjusted to the end of that UTF-8 character.
  103.  */
  104. start = segPtr->body.chars + (byteIndex - index);
  105. p = Tcl_UtfPrev(start, segPtr->body.chars);
  106. p += Tcl_UtfToUniChar(p, &ch);
  107. indexPtr->byteIndex += p - start;
  108.     }
  109.     break;
  110. }
  111. index += segPtr->size;
  112.     }
  113.     return indexPtr;
  114. }
  115. /*
  116.  *---------------------------------------------------------------------------
  117.  *
  118.  * TkTextMakeCharIndex --
  119.  *
  120.  * Given a line index and a character index, look things up in the
  121.  * B-tree and fill in a TkTextIndex structure.
  122.  *
  123.  * Results:
  124.  * The structure at *indexPtr is filled in with information about the
  125.  * character at lineIndex and charIndex (or the closest existing
  126.  * character, if the specified one doesn't exist), and indexPtr is
  127.  * returned as result.
  128.  *
  129.  * Side effects:
  130.  * None.
  131.  *
  132.  *---------------------------------------------------------------------------
  133.  */
  134. TkTextIndex *
  135. TkTextMakeCharIndex(tree, lineIndex, charIndex, indexPtr)
  136.     TkTextBTree tree; /* Tree that lineIndex and charIndex refer
  137.  * to. */
  138.     int lineIndex; /* Index of desired line (0 means first
  139.  * line of text). */
  140.     int charIndex; /* Index of desired character. */
  141.     TkTextIndex *indexPtr; /* Structure to fill in. */
  142. {
  143.     register TkTextSegment *segPtr;
  144.     char *p, *start, *end;
  145.     int index, offset;
  146.     Tcl_UniChar ch;
  147.     indexPtr->tree = tree;
  148.     if (lineIndex < 0) {
  149. lineIndex = 0;
  150. charIndex = 0;
  151.     }
  152.     if (charIndex < 0) {
  153. charIndex = 0;
  154.     }
  155.     indexPtr->linePtr = TkBTreeFindLine(tree, lineIndex);
  156.     if (indexPtr->linePtr == NULL) {
  157. indexPtr->linePtr = TkBTreeFindLine(tree, TkBTreeNumLines(tree));
  158. charIndex = 0;
  159.     }
  160.     /*
  161.      * Verify that the index is within the range of the line.
  162.      * If not, just use the index of the last character in the line.
  163.      */
  164.     index = 0;
  165.     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
  166. if (segPtr == NULL) {
  167.     /*
  168.      * Use the index of the last character in the line.  Since
  169.      * the last character on the line is guaranteed to be a 'n',
  170.      * we can back up a constant sizeof(char) bytes.
  171.      */
  172.      
  173.     indexPtr->byteIndex = index - sizeof(char);
  174.     break;
  175. }
  176. if (segPtr->typePtr == &tkTextCharType) {
  177.     /*
  178.      * Turn character offset into a byte offset.
  179.      */
  180.     start = segPtr->body.chars;
  181.     end = start + segPtr->size;
  182.     for (p = start; p < end; p += offset) {
  183. if (charIndex == 0) {
  184.     indexPtr->byteIndex = index;
  185.     return indexPtr;
  186. }
  187. charIndex--;
  188. offset = Tcl_UtfToUniChar(p, &ch);
  189. index += offset;
  190.     }
  191. } else {
  192.     if (charIndex < segPtr->size) {
  193. indexPtr->byteIndex = index;
  194. break;
  195.     }
  196.     charIndex -= segPtr->size;
  197.     index += segPtr->size;
  198. }
  199.     }
  200.     return indexPtr;
  201. }
  202. /*
  203.  *---------------------------------------------------------------------------
  204.  *
  205.  * TkTextIndexToSeg --
  206.  *
  207.  * Given an index, this procedure returns the segment and offset
  208.  * within segment for the index.
  209.  *
  210.  * Results:
  211.  * The return value is a pointer to the segment referred to by
  212.  * indexPtr; this will always be a segment with non-zero size.  The
  213.  * variable at *offsetPtr is set to hold the integer offset within
  214.  * the segment of the character given by indexPtr.
  215.  *
  216.  * Side effects:
  217.  * None.
  218.  *
  219.  *---------------------------------------------------------------------------
  220.  */
  221. TkTextSegment *
  222. TkTextIndexToSeg(indexPtr, offsetPtr)
  223.     CONST TkTextIndex *indexPtr;/* Text index. */
  224.     int *offsetPtr; /* Where to store offset within segment, or
  225.  * NULL if offset isn't wanted. */
  226. {
  227.     TkTextSegment *segPtr;
  228.     int offset;
  229.     for (offset = indexPtr->byteIndex, segPtr = indexPtr->linePtr->segPtr;
  230.     offset >= segPtr->size;
  231.     offset -= segPtr->size, segPtr = segPtr->nextPtr) {
  232. /* Empty loop body. */
  233.     }
  234.     if (offsetPtr != NULL) {
  235. *offsetPtr = offset;
  236.     }
  237.     return segPtr;
  238. }
  239. /*
  240.  *---------------------------------------------------------------------------
  241.  *
  242.  * TkTextSegToOffset --
  243.  *
  244.  * Given a segment pointer and the line containing it, this procedure
  245.  * returns the offset of the segment within its line.
  246.  *
  247.  * Results:
  248.  * The return value is the offset (within its line) of the first
  249.  * character in segPtr.
  250.  *
  251.  * Side effects:
  252.  * None.
  253.  *
  254.  *---------------------------------------------------------------------------
  255.  */
  256. int
  257. TkTextSegToOffset(segPtr, linePtr)
  258.     CONST TkTextSegment *segPtr;/* Segment whose offset is desired. */
  259.     CONST TkTextLine *linePtr; /* Line containing segPtr. */
  260. {
  261.     CONST TkTextSegment *segPtr2;
  262.     int offset;
  263.     offset = 0;
  264.     for (segPtr2 = linePtr->segPtr; segPtr2 != segPtr;
  265.     segPtr2 = segPtr2->nextPtr) {
  266. offset += segPtr2->size;
  267.     }
  268.     return offset;
  269. }
  270. /*
  271.  *---------------------------------------------------------------------------
  272.  *
  273.  * TkTextGetIndex --
  274.  *
  275.  * Given a string, return the index that is described.
  276.  *
  277.  * Results:
  278.  * The return value is a standard Tcl return result.  If TCL_OK is
  279.  * returned, then everything went well and the index at *indexPtr is
  280.  * filled in; otherwise TCL_ERROR is returned and an error message
  281.  * is left in the interp's result.
  282.  *
  283.  * Side effects:
  284.  * None.
  285.  *
  286.  *---------------------------------------------------------------------------
  287.  */
  288. int
  289. TkTextGetIndex(interp, textPtr, string, indexPtr)
  290.     Tcl_Interp *interp; /* Use this for error reporting. */
  291.     TkText *textPtr; /* Information about text widget. */
  292.     CONST char *string; /* Textual description of position. */
  293.     TkTextIndex *indexPtr; /* Index structure to fill in. */
  294. {
  295.     char *p, *end, *endOfBase;
  296.     Tcl_HashEntry *hPtr;
  297.     TkTextTag *tagPtr;
  298.     TkTextSearch search;
  299.     TkTextIndex first, last;
  300.     int wantLast, result;
  301.     char c;
  302.     CONST char *cp;
  303.     Tcl_DString copy;
  304.     /*
  305.      *---------------------------------------------------------------------
  306.      * Stage 1: check to see if the index consists of nothing but a mark
  307.      * name.  We do this check now even though it's also done later, in
  308.      * order to allow mark names that include funny characters such as
  309.      * spaces or "+1c".
  310.      *---------------------------------------------------------------------
  311.      */
  312.     if (TkTextMarkNameToIndex(textPtr, string, indexPtr) == TCL_OK) {
  313. return TCL_OK;
  314.     }
  315.     /*
  316.      *------------------------------------------------
  317.      * Stage 2: start again by parsing the base index.
  318.      *------------------------------------------------
  319.      */
  320.     indexPtr->tree = textPtr->tree;
  321.     /*
  322.      * First look for the form "tag.first" or "tag.last" where "tag"
  323.      * is the name of a valid tag.  Try to use up as much as possible
  324.      * of the string in this check (strrchr instead of strchr below).
  325.      * Doing the check now, and in this way, allows tag names to include
  326.      * funny characters like "@" or "+1c".
  327.      */
  328.     Tcl_DStringInit(&copy);
  329.     p = strrchr(Tcl_DStringAppend(&copy, string, -1), '.');
  330.     if (p != NULL) {
  331. if ((p[1] == 'f') && (strncmp(p+1, "first", 5) == 0)) {
  332.     wantLast = 0;
  333.     endOfBase = p+6;
  334. } else if ((p[1] == 'l') && (strncmp(p+1, "last", 4) == 0)) {
  335.     wantLast = 1;
  336.     endOfBase = p+5;
  337. } else {
  338.     goto tryxy;
  339. }
  340. *p = 0;
  341. hPtr = Tcl_FindHashEntry(&textPtr->tagTable, Tcl_DStringValue(&copy));
  342. *p = '.';
  343. if (hPtr == NULL) {
  344.     goto tryxy;
  345. }
  346. tagPtr = (TkTextTag *) Tcl_GetHashValue(hPtr);
  347. TkTextMakeByteIndex(textPtr->tree, 0, 0, &first);
  348. TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree), 0,
  349. &last);
  350. TkBTreeStartSearch(&first, &last, tagPtr, &search);
  351. if (!TkBTreeCharTagged(&first, tagPtr) && !TkBTreeNextTag(&search)) {
  352.     Tcl_ResetResult(interp);
  353.     Tcl_AppendResult(interp,
  354.     "text doesn't contain any characters tagged with "",
  355.     Tcl_GetHashKey(&textPtr->tagTable, hPtr), """,
  356.     (char *) NULL);
  357.     Tcl_DStringFree(&copy);
  358.     return TCL_ERROR;
  359. }
  360. *indexPtr = search.curIndex;
  361. if (wantLast) {
  362.     while (TkBTreeNextTag(&search)) {
  363. *indexPtr = search.curIndex;
  364.     }
  365. }
  366. goto gotBase;
  367.     }
  368.     tryxy:
  369.     if (string[0] == '@') {
  370. /*
  371.  * Find character at a given x,y location in the window.
  372.  */
  373. int x, y;
  374. cp = string+1;
  375. x = strtol(cp, &end, 0);
  376. if ((end == cp) || (*end != ',')) {
  377.     goto error;
  378. }
  379. cp = end+1;
  380. y = strtol(cp, &end, 0);
  381. if (end == cp) {
  382.     goto error;
  383. }
  384. TkTextPixelIndex(textPtr, x, y, indexPtr);
  385. endOfBase = end;
  386. goto gotBase; 
  387.     }
  388.     if (isdigit(UCHAR(string[0])) || (string[0] == '-')) {
  389. int lineIndex, charIndex;
  390. /*
  391.  * Base is identified with line and character indices.
  392.  */
  393. lineIndex = strtol(string, &end, 0) - 1;
  394. if ((end == string) || (*end != '.')) {
  395.     goto error;
  396. }
  397. p = end+1;
  398. if ((*p == 'e') && (strncmp(p, "end", 3) == 0)) {
  399.     charIndex = LAST_CHAR;
  400.     endOfBase = p+3;
  401. } else {
  402.     charIndex = strtol(p, &end, 0);
  403.     if (end == p) {
  404. goto error;
  405.     }
  406.     endOfBase = end;
  407. }
  408. TkTextMakeCharIndex(textPtr->tree, lineIndex, charIndex, indexPtr);
  409. goto gotBase;
  410.     }
  411.     for (p = Tcl_DStringValue(&copy); *p != 0; p++) {
  412. if (isspace(UCHAR(*p)) || (*p == '+') || (*p == '-')) {
  413.     break;
  414. }
  415.     }
  416.     endOfBase = p;
  417.     if (string[0] == '.') {
  418. /*
  419.  * See if the base position is the name of an embedded window.
  420.  */
  421. c = *endOfBase;
  422. *endOfBase = 0;
  423. result = TkTextWindowIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
  424. *endOfBase = c;
  425. if (result != 0) {
  426.     goto gotBase;
  427. }
  428.     }
  429.     if ((string[0] == 'e')
  430.     && (strncmp(string, "end",
  431.     (size_t) (endOfBase-Tcl_DStringValue(&copy))) == 0)) {
  432. /*
  433.  * Base position is end of text.
  434.  */
  435. TkTextMakeByteIndex(textPtr->tree, TkBTreeNumLines(textPtr->tree),
  436. 0, indexPtr);
  437. goto gotBase;
  438.     } else {
  439. /*
  440.  * See if the base position is the name of a mark.
  441.  */
  442. c = *endOfBase;
  443. *endOfBase = 0;
  444. result = TkTextMarkNameToIndex(textPtr, Tcl_DStringValue(&copy),
  445. indexPtr);
  446. *endOfBase = c;
  447. if (result == TCL_OK) {
  448.     goto gotBase;
  449. }
  450. /*
  451.  * See if the base position is the name of an embedded image
  452.  */
  453. c = *endOfBase;
  454. *endOfBase = 0;
  455. result = TkTextImageIndex(textPtr, Tcl_DStringValue(&copy), indexPtr);
  456. *endOfBase = c;
  457. if (result != 0) {
  458.     goto gotBase;
  459. }
  460.     }
  461.     goto error;
  462.     /*
  463.      *-------------------------------------------------------------------
  464.      * Stage 3: process zero or more modifiers.  Each modifier is either
  465.      * a keyword like "wordend" or "linestart", or it has the form
  466.      * "op count units" where op is + or -, count is a number, and units
  467.      * is "chars" or "lines".
  468.      *-------------------------------------------------------------------
  469.      */
  470.     gotBase:
  471.     cp = endOfBase;
  472.     while (1) {
  473. while (isspace(UCHAR(*cp))) {
  474.     cp++;
  475. }
  476. if (*cp == 0) {
  477.     break;
  478. }
  479.     
  480. if ((*cp == '+') || (*cp == '-')) {
  481.     cp = ForwBack(cp, indexPtr);
  482. } else {
  483.     cp = StartEnd(cp, indexPtr);
  484. }
  485. if (cp == NULL) {
  486.     goto error;
  487. }
  488.     }
  489.     Tcl_DStringFree(&copy);
  490.     return TCL_OK;
  491.     error:
  492.     Tcl_DStringFree(&copy);
  493.     Tcl_ResetResult(interp);
  494.     Tcl_AppendResult(interp, "bad text index "", string, """,
  495.     (char *) NULL);
  496.     return TCL_ERROR;
  497. }
  498. /*
  499.  *---------------------------------------------------------------------------
  500.  *
  501.  * TkTextPrintIndex --
  502.  *
  503.  * This procedure generates a string description of an index, suitable
  504.  * for reading in again later.
  505.  *
  506.  * Results:
  507.  * The characters pointed to by string are modified.
  508.  *
  509.  * Side effects:
  510.  * None.
  511.  *
  512.  *---------------------------------------------------------------------------
  513.  */
  514. void
  515. TkTextPrintIndex(indexPtr, string)
  516.     CONST TkTextIndex *indexPtr;/* Pointer to index. */
  517.     char *string; /* Place to store the position.  Must have
  518.  * at least TK_POS_CHARS characters. */
  519. {
  520.     TkTextSegment *segPtr;
  521.     int numBytes, charIndex;
  522.     numBytes = indexPtr->byteIndex;
  523.     charIndex = 0;
  524.     for (segPtr = indexPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
  525. if (numBytes <= segPtr->size) {
  526.     break;
  527. }
  528. if (segPtr->typePtr == &tkTextCharType) {
  529.     charIndex += Tcl_NumUtfChars(segPtr->body.chars, segPtr->size);
  530. } else {
  531.     charIndex += segPtr->size;
  532. }
  533. numBytes -= segPtr->size;
  534.     }
  535.     if (segPtr->typePtr == &tkTextCharType) {
  536. charIndex += Tcl_NumUtfChars(segPtr->body.chars, numBytes);
  537.     } else {
  538. charIndex += numBytes;
  539.     }
  540.     sprintf(string, "%d.%d", TkBTreeLineIndex(indexPtr->linePtr) + 1,
  541.     charIndex);
  542. }
  543. /*
  544.  *---------------------------------------------------------------------------
  545.  *
  546.  * TkTextIndexCmp --
  547.  *
  548.  * Compare two indices to see which one is earlier in the text.
  549.  *
  550.  * Results:
  551.  * The return value is 0 if index1Ptr and index2Ptr refer to the same
  552.  * position in the file, -1 if index1Ptr refers to an earlier position
  553.  * than index2Ptr, and 1 otherwise.
  554.  *
  555.  * Side effects:
  556.  * None.
  557.  *
  558.  *---------------------------------------------------------------------------
  559.  */
  560. int
  561. TkTextIndexCmp(index1Ptr, index2Ptr)
  562.     CONST TkTextIndex *index1Ptr; /* First index. */
  563.     CONST TkTextIndex *index2Ptr; /* Second index. */
  564. {
  565.     int line1, line2;
  566.     if (index1Ptr->linePtr == index2Ptr->linePtr) {
  567. if (index1Ptr->byteIndex < index2Ptr->byteIndex) {
  568.     return -1;
  569. } else if (index1Ptr->byteIndex > index2Ptr->byteIndex) {
  570.     return 1;
  571. } else {
  572.     return 0;
  573. }
  574.     }
  575.     line1 = TkBTreeLineIndex(index1Ptr->linePtr);
  576.     line2 = TkBTreeLineIndex(index2Ptr->linePtr);
  577.     if (line1 < line2) {
  578. return -1;
  579.     }
  580.     if (line1 > line2) {
  581. return 1;
  582.     }
  583.     return 0;
  584. }
  585. /*
  586.  *---------------------------------------------------------------------------
  587.  *
  588.  * ForwBack --
  589.  *
  590.  * This procedure handles +/- modifiers for indices to adjust the
  591.  * index forwards or backwards.
  592.  *
  593.  * Results:
  594.  * If the modifier in string is successfully parsed then the return
  595.  * value is the address of the first character after the modifier,
  596.  * and *indexPtr is updated to reflect the modifier.  If there is a
  597.  * syntax error in the modifier then NULL is returned.
  598.  *
  599.  * Side effects:
  600.  * None.
  601.  *
  602.  *---------------------------------------------------------------------------
  603.  */
  604. static CONST char *
  605. ForwBack(string, indexPtr)
  606.     CONST char *string; /* String to parse for additional info
  607.  * about modifier (count and units). 
  608.  * Points to "+" or "-" that starts
  609.  * modifier. */
  610.     TkTextIndex *indexPtr; /* Index to update as specified in string. */
  611. {
  612.     register CONST char *p, *units;
  613.     char *end;
  614.     int count, lineIndex;
  615.     size_t length;
  616.     /*
  617.      * Get the count (how many units forward or backward).
  618.      */
  619.     p = string+1;
  620.     while (isspace(UCHAR(*p))) {
  621. p++;
  622.     }
  623.     count = strtol(p, &end, 0);
  624.     if (end == p) {
  625. return NULL;
  626.     }
  627.     p = end;
  628.     while (isspace(UCHAR(*p))) {
  629. p++;
  630.     }
  631.     /*
  632.      * Find the end of this modifier (next space or + or - character),
  633.      * then parse the unit specifier and update the position
  634.      * accordingly.
  635.      */
  636.     units = p; 
  637.     while ((*p != '') && !isspace(UCHAR(*p)) && (*p != '+') && (*p != '-')) {
  638. p++;
  639.     }
  640.     length = p - units;
  641.     if ((*units == 'c') && (strncmp(units, "chars", length) == 0)) {
  642. if (*string == '+') {
  643.     TkTextIndexForwChars(indexPtr, count, indexPtr);
  644. } else {
  645.     TkTextIndexBackChars(indexPtr, count, indexPtr);
  646. }
  647.     } else if ((*units == 'l') && (strncmp(units, "lines", length) == 0)) {
  648. lineIndex = TkBTreeLineIndex(indexPtr->linePtr);
  649. if (*string == '+') {
  650.     lineIndex += count;
  651. } else {
  652.     lineIndex -= count;
  653.     /*
  654.      * The check below retains the character position, even
  655.      * if the line runs off the start of the file.  Without
  656.      * it, the character position will get reset to 0 by
  657.      * TkTextMakeIndex.
  658.      */
  659.     if (lineIndex < 0) {
  660. lineIndex = 0;
  661.     }
  662. }
  663. /*
  664.  * This doesn't work quite right if using a proportional font or
  665.  * UTF-8 characters with varying numbers of bytes.  The cursor will
  666.  * bop around, keeping a constant number of bytes (not characters)
  667.  * from the left edge (but making sure not to split any UTF-8
  668.  * characters), regardless of the x-position the index corresponds
  669.  * to.  The proper way to do this is to get the x-position of the
  670.  * index and then pick the character at the same x-position in the
  671.  * new line.
  672.  */
  673. TkTextMakeByteIndex(indexPtr->tree, lineIndex, indexPtr->byteIndex,
  674. indexPtr);
  675.     } else {
  676. return NULL;
  677.     }
  678.     return p;
  679. }
  680. /*
  681.  *---------------------------------------------------------------------------
  682.  *
  683.  * TkTextIndexForwBytes --
  684.  *
  685.  * Given an index for a text widget, this procedure creates a new
  686.  * index that points "count" bytes ahead of the source index.
  687.  *
  688.  * Results:
  689.  * *dstPtr is modified to refer to the character "count" bytes after
  690.  * srcPtr, or to the last character in the TkText if there aren't
  691.  * "count" bytes left.
  692.  *
  693.  * Side effects:
  694.  * None.
  695.  *
  696.  *---------------------------------------------------------------------------
  697.  */
  698. void
  699. TkTextIndexForwBytes(srcPtr, byteCount, dstPtr)
  700.     CONST TkTextIndex *srcPtr; /* Source index. */
  701.     int byteCount; /* How many bytes forward to move.  May be
  702.  * negative. */
  703.     TkTextIndex *dstPtr; /* Destination index: gets modified. */
  704. {
  705.     TkTextLine *linePtr;
  706.     TkTextSegment *segPtr;
  707.     int lineLength;
  708.     if (byteCount < 0) {
  709. TkTextIndexBackBytes(srcPtr, -byteCount, dstPtr);
  710. return;
  711.     }
  712.     *dstPtr = *srcPtr;
  713.     dstPtr->byteIndex += byteCount;
  714.     while (1) {
  715. /*
  716.  * Compute the length of the current line.
  717.  */
  718. lineLength = 0;
  719. for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
  720. segPtr = segPtr->nextPtr) {
  721.     lineLength += segPtr->size;
  722. }
  723. /*
  724.  * If the new index is in the same line then we're done.
  725.  * Otherwise go on to the next line.
  726.  */
  727. if (dstPtr->byteIndex < lineLength) {
  728.     return;
  729. }
  730. dstPtr->byteIndex -= lineLength;
  731. linePtr = TkBTreeNextLine(dstPtr->linePtr);
  732. if (linePtr == NULL) {
  733.     dstPtr->byteIndex = lineLength - 1;
  734.     return;
  735. }
  736. dstPtr->linePtr = linePtr;
  737.     }
  738. }
  739. /*
  740.  *---------------------------------------------------------------------------
  741.  *
  742.  * TkTextIndexForwChars --
  743.  *
  744.  * Given an index for a text widget, this procedure creates a new
  745.  * index that points "count" characters ahead of the source index.
  746.  *
  747.  * Results:
  748.  * *dstPtr is modified to refer to the character "count" characters
  749.  * after srcPtr, or to the last character in the TkText if there
  750.  * aren't "count" characters left in the file.
  751.  *
  752.  * Side effects:
  753.  * None.
  754.  *
  755.  *---------------------------------------------------------------------------
  756.  */
  757. void
  758. TkTextIndexForwChars(srcPtr, charCount, dstPtr)
  759.     CONST TkTextIndex *srcPtr; /* Source index. */
  760.     int charCount; /* How many characters forward to move.
  761.  * May be negative. */
  762.     TkTextIndex *dstPtr; /* Destination index: gets modified. */
  763. {
  764.     TkTextLine *linePtr;
  765.     TkTextSegment *segPtr;
  766.     int byteOffset;
  767.     char *start, *end, *p;
  768.     Tcl_UniChar ch;
  769.     if (charCount < 0) {
  770. TkTextIndexBackChars(srcPtr, -charCount, dstPtr);
  771. return;
  772.     }
  773.     *dstPtr = *srcPtr;
  774.     /*
  775.      * Find seg that contains src byteIndex.
  776.      * Move forward specified number of chars.
  777.      */
  778.     segPtr = TkTextIndexToSeg(dstPtr, &byteOffset);
  779.     while (1) {
  780. /*
  781.  * Go through each segment in line looking for specified character
  782.  * index.
  783.  */
  784. for ( ; segPtr != NULL; segPtr = segPtr->nextPtr) {
  785.     if (segPtr->typePtr == &tkTextCharType) {
  786. start = segPtr->body.chars + byteOffset;
  787. end = segPtr->body.chars + segPtr->size;
  788. for (p = start; p < end; p += Tcl_UtfToUniChar(p, &ch)) {
  789.     if (charCount == 0) {
  790. dstPtr->byteIndex += (p - start);
  791. return;
  792.     }
  793.     charCount--;
  794. }
  795.     } else {
  796. if (charCount < segPtr->size - byteOffset) {
  797.     dstPtr->byteIndex += charCount;
  798.     return;
  799. }
  800. charCount -= segPtr->size - byteOffset;
  801.     }
  802.     dstPtr->byteIndex += segPtr->size - byteOffset;
  803.     byteOffset = 0;
  804. }
  805. /*
  806.  * Go to the next line.  If we are at the end of the text item,
  807.  * back up one byte (for the terminal 'n' character) and return
  808.  * that index.
  809.  */
  810.  
  811. linePtr = TkBTreeNextLine(dstPtr->linePtr);
  812. if (linePtr == NULL) {
  813.     dstPtr->byteIndex -= sizeof(char);
  814.     return;
  815. }
  816. dstPtr->linePtr = linePtr;
  817. dstPtr->byteIndex = 0;
  818. segPtr = dstPtr->linePtr->segPtr;
  819.     }
  820. }
  821. /*
  822.  *---------------------------------------------------------------------------
  823.  *
  824.  * TkTextIndexBackBytes --
  825.  *
  826.  * Given an index for a text widget, this procedure creates a new
  827.  * index that points "count" bytes earlier than the source index.
  828.  *
  829.  * Results:
  830.  * *dstPtr is modified to refer to the character "count" bytes before
  831.  * srcPtr, or to the first character in the TkText if there aren't
  832.  * "count" bytes earlier than srcPtr.
  833.  *
  834.  * Side effects:
  835.  * None.
  836.  *
  837.  *---------------------------------------------------------------------------
  838.  */
  839. void
  840. TkTextIndexBackBytes(srcPtr, byteCount, dstPtr)
  841.     CONST TkTextIndex *srcPtr; /* Source index. */
  842.     int byteCount; /* How many bytes backward to move.  May be
  843.  * negative. */
  844.     TkTextIndex *dstPtr; /* Destination index: gets modified. */
  845. {
  846.     TkTextSegment *segPtr;
  847.     int lineIndex;
  848.     if (byteCount < 0) {
  849. TkTextIndexForwBytes(srcPtr, -byteCount, dstPtr);
  850. return;
  851.     }
  852.     *dstPtr = *srcPtr;
  853.     dstPtr->byteIndex -= byteCount;
  854.     lineIndex = -1;
  855.     while (dstPtr->byteIndex < 0) {
  856. /*
  857.  * Move back one line in the text.  If we run off the beginning
  858.  * of the file then just return the first character in the text.
  859.  */
  860. if (lineIndex < 0) {
  861.     lineIndex = TkBTreeLineIndex(dstPtr->linePtr);
  862. }
  863. if (lineIndex == 0) {
  864.     dstPtr->byteIndex = 0;
  865.     return;
  866. }
  867. lineIndex--;
  868. dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex);
  869. /*
  870.  * Compute the length of the line and add that to dstPtr->charIndex.
  871.  */
  872. for (segPtr = dstPtr->linePtr->segPtr; segPtr != NULL;
  873. segPtr = segPtr->nextPtr) {
  874.     dstPtr->byteIndex += segPtr->size;
  875. }
  876.     }
  877. }
  878. /*
  879.  *---------------------------------------------------------------------------
  880.  *
  881.  * TkTextIndexBackChars --
  882.  *
  883.  * Given an index for a text widget, this procedure creates a new
  884.  * index that points "count" characters earlier than the source index.
  885.  *
  886.  * Results:
  887.  * *dstPtr is modified to refer to the character "count" characters
  888.  * before srcPtr, or to the first character in the file if there
  889.  * aren't "count" characters earlier than srcPtr.
  890.  *
  891.  * Side effects:
  892.  * None.
  893.  *
  894.  *---------------------------------------------------------------------------
  895.  */
  896. void
  897. TkTextIndexBackChars(srcPtr, charCount, dstPtr)
  898.     CONST TkTextIndex *srcPtr; /* Source index. */
  899.     int charCount; /* How many characters backward to move.
  900.  * May be negative. */
  901.     TkTextIndex *dstPtr; /* Destination index: gets modified. */
  902. {
  903.     TkTextSegment *segPtr, *oldPtr;
  904.     int lineIndex, segSize;
  905.     CONST char *p, *start, *end;
  906.     if (charCount <= 0) {
  907. TkTextIndexForwChars(srcPtr, -charCount, dstPtr);
  908. return;
  909.     }
  910.     *dstPtr = *srcPtr;
  911.     /*
  912.      * Find offset within seg that contains byteIndex.
  913.      * Move backward specified number of chars.
  914.      */
  915.     lineIndex = -1;
  916.     
  917.     segSize = dstPtr->byteIndex;
  918.     for (segPtr = dstPtr->linePtr->segPtr; ; segPtr = segPtr->nextPtr) {
  919. if (segSize <= segPtr->size) {
  920.     break;
  921. }
  922. segSize -= segPtr->size;
  923.     }
  924.     while (1) {
  925. if (segPtr->typePtr == &tkTextCharType) {
  926.     start = segPtr->body.chars;
  927.     end = segPtr->body.chars + segSize;
  928.     for (p = end; ; p = Tcl_UtfPrev(p, start)) {
  929. if (charCount == 0) {
  930.     dstPtr->byteIndex -= (end - p);
  931.     return;
  932. }
  933. if (p == start) {
  934.     break;
  935. }
  936. charCount--;
  937.     }
  938. } else {
  939.     if (charCount <= segSize) {
  940. dstPtr->byteIndex -= charCount;
  941. return;
  942.     }
  943.     charCount -= segSize;
  944. }
  945. dstPtr->byteIndex -= segSize;
  946. /*
  947.  * Move back into previous segment.
  948.  */
  949. oldPtr = segPtr;
  950. segPtr = dstPtr->linePtr->segPtr;
  951. if (segPtr != oldPtr) {
  952.     for ( ; segPtr->nextPtr != oldPtr; segPtr = segPtr->nextPtr) {
  953. /* Empty body. */
  954.     }
  955.     segSize = segPtr->size;
  956.     continue;
  957. }
  958. /*
  959.  * Move back to previous line.
  960.  */
  961. if (lineIndex < 0) {
  962.     lineIndex = TkBTreeLineIndex(dstPtr->linePtr);
  963. }
  964. if (lineIndex == 0) {
  965.     dstPtr->byteIndex = 0;
  966.     return;
  967. }
  968. lineIndex--;
  969. dstPtr->linePtr = TkBTreeFindLine(dstPtr->tree, lineIndex);
  970. /*
  971.  * Compute the length of the line and add that to dstPtr->byteIndex.
  972.  */
  973. oldPtr = dstPtr->linePtr->segPtr;
  974. for (segPtr = oldPtr; segPtr != NULL; segPtr = segPtr->nextPtr) {
  975.     dstPtr->byteIndex += segPtr->size;
  976.     oldPtr = segPtr;
  977. }
  978. segPtr = oldPtr;
  979. segSize = segPtr->size;
  980.     }
  981. }
  982. /*
  983.  *----------------------------------------------------------------------
  984.  *
  985.  * StartEnd --
  986.  *
  987.  * This procedure handles modifiers like "wordstart" and "lineend"
  988.  * to adjust indices forwards or backwards.
  989.  *
  990.  * Results:
  991.  * If the modifier is successfully parsed then the return value
  992.  * is the address of the first character after the modifier, and
  993.  * *indexPtr is updated to reflect the modifier. If there is a
  994.  * syntax error in the modifier then NULL is returned.
  995.  *
  996.  * Side effects:
  997.  * None.
  998.  *
  999.  *----------------------------------------------------------------------
  1000.  */
  1001. static CONST char *
  1002. StartEnd(string, indexPtr)
  1003.     CONST char *string; /* String to parse for additional info
  1004.  * about modifier (count and units). 
  1005.  * Points to first character of modifer
  1006.  * word. */
  1007.     TkTextIndex *indexPtr; /* Index to mdoify based on string. */
  1008. {
  1009.     CONST char *p;
  1010.     int c, offset;
  1011.     size_t length;
  1012.     register TkTextSegment *segPtr;
  1013.     /*
  1014.      * Find the end of the modifier word.
  1015.      */
  1016.     for (p = string; isalnum(UCHAR(*p)); p++) {
  1017. /* Empty loop body. */
  1018.     }
  1019.     length = p-string;
  1020.     if ((*string == 'l') && (strncmp(string, "lineend", length) == 0)
  1021.     && (length >= 5)) {
  1022. indexPtr->byteIndex = 0;
  1023. for (segPtr = indexPtr->linePtr->segPtr; segPtr != NULL;
  1024. segPtr = segPtr->nextPtr) {
  1025.     indexPtr->byteIndex += segPtr->size;
  1026. }
  1027. indexPtr->byteIndex -= sizeof(char);
  1028.     } else if ((*string == 'l') && (strncmp(string, "linestart", length) == 0)
  1029.     && (length >= 5)) {
  1030. indexPtr->byteIndex = 0;
  1031.     } else if ((*string == 'w') && (strncmp(string, "wordend", length) == 0)
  1032.     && (length >= 5)) {
  1033. int firstChar = 1;
  1034. /*
  1035.  * If the current character isn't part of a word then just move
  1036.  * forward one character.  Otherwise move forward until finding
  1037.  * a character that isn't part of a word and stop there.
  1038.  */
  1039. segPtr = TkTextIndexToSeg(indexPtr, &offset);
  1040. while (1) {
  1041.     if (segPtr->typePtr == &tkTextCharType) {
  1042. c = segPtr->body.chars[offset];
  1043. if (!isalnum(UCHAR(c)) && (c != '_')) {
  1044.     break;
  1045. }
  1046. firstChar = 0;
  1047.     }
  1048.     offset += 1;
  1049.     indexPtr->byteIndex += sizeof(char);
  1050.     if (offset >= segPtr->size) {
  1051. segPtr = TkTextIndexToSeg(indexPtr, &offset);
  1052.     }
  1053. }
  1054. if (firstChar) {
  1055.     TkTextIndexForwChars(indexPtr, 1, indexPtr);
  1056. }
  1057.     } else if ((*string == 'w') && (strncmp(string, "wordstart", length) == 0)
  1058.     && (length >= 5)) {
  1059. int firstChar = 1;
  1060. /*
  1061.  * Starting with the current character, look for one that's not
  1062.  * part of a word and keep moving backward until you find one.
  1063.  * Then if the character found wasn't the first one, move forward
  1064.  * again one position.
  1065.  */
  1066. segPtr = TkTextIndexToSeg(indexPtr, &offset);
  1067. while (1) {
  1068.     if (segPtr->typePtr == &tkTextCharType) {
  1069. c = segPtr->body.chars[offset];
  1070. if (!isalnum(UCHAR(c)) && (c != '_')) {
  1071.     break;
  1072. }
  1073. firstChar = 0;
  1074.     }
  1075.     offset -= 1;
  1076.     indexPtr->byteIndex -= sizeof(char);
  1077.     if (offset < 0) {
  1078. if (indexPtr->byteIndex < 0) {
  1079.     indexPtr->byteIndex = 0;
  1080.     goto done;
  1081. }
  1082. segPtr = TkTextIndexToSeg(indexPtr, &offset);
  1083.     }
  1084. }
  1085. if (!firstChar) {
  1086.     TkTextIndexForwChars(indexPtr, 1, indexPtr);
  1087. }
  1088.     } else {
  1089. return NULL;
  1090.     }
  1091.     done:
  1092.     return p;
  1093. }