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