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