1 /*
2  * tkTextMark.c --
3  *
4  *	This file contains the procedure that implement marks for
5  *	text widgets.
6  *
7  * Copyright (c) 1994 The Regents of the University of California.
8  * Copyright (c) 1994-1995 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  * SCCS: @(#) tkTextMark.c 1.15 96/02/15 18:52:59
14  */
15 
16 #include "tkInt.h"
17 #include "tkText.h"
18 
19 /*
20  * Macro that determines the size of a mark segment:
21  */
22 
23 #define MSEG_SIZE ((unsigned) (Tk_Offset(TkTextSegment, body) \
24 	+ sizeof(TkTextMark)))
25 
26 /*
27  * Forward references for procedures defined in this file:
28  */
29 
30 static void		InsertUndisplayProc _ANSI_ARGS_((TkText *textPtr,
31 			    TkTextDispChunk *chunkPtr));
32 static int		MarkDeleteProc _ANSI_ARGS_((TkTextSegment *segPtr,
33 			    TkTextLine *linePtr, int treeGone));
34 static TkTextSegment *	MarkCleanupProc _ANSI_ARGS_((TkTextSegment *segPtr,
35 			    TkTextLine *linePtr));
36 static void		MarkCheckProc _ANSI_ARGS_((TkTextSegment *segPtr,
37 			    TkTextLine *linePtr));
38 static int		MarkLayoutProc _ANSI_ARGS_((TkText *textPtr,
39 			    TkTextIndex *indexPtr, TkTextSegment *segPtr,
40 			    int offset, int maxX, int maxChars,
41 			    int noCharsYet, Tk_Uid wrapMode,
42 			    TkTextDispChunk *chunkPtr));
43 static int		MarkFindNext _ANSI_ARGS_((Tcl_Interp *interp,
44 			    TkText *textPtr, char *markName));
45 static int		MarkFindPrev _ANSI_ARGS_((Tcl_Interp *interp,
46 			    TkText *textPtr, char *markName));
47 
48 
49 /*
50  * The following structures declare the "mark" segment types.
51  * There are actually two types for marks, one with left gravity
52  * and one with right gravity.  They are identical except for
53  * their gravity property.
54  */
55 
56 Tk_SegType tkTextRightMarkType = {
57     "mark",					/* name */
58     0,						/* leftGravity */
59     (Tk_SegSplitProc *) NULL,			/* splitProc */
60     MarkDeleteProc,				/* deleteProc */
61     MarkCleanupProc,				/* cleanupProc */
62     (Tk_SegLineChangeProc *) NULL,		/* lineChangeProc */
63     MarkLayoutProc,				/* layoutProc */
64     MarkCheckProc				/* checkProc */
65 };
66 
67 Tk_SegType tkTextLeftMarkType = {
68     "mark",					/* name */
69     1,						/* leftGravity */
70     (Tk_SegSplitProc *) NULL,			/* splitProc */
71     MarkDeleteProc,				/* deleteProc */
72     MarkCleanupProc,				/* cleanupProc */
73     (Tk_SegLineChangeProc *) NULL,		/* lineChangeProc */
74     MarkLayoutProc,				/* layoutProc */
75     MarkCheckProc				/* checkProc */
76 };
77 
78 /*
79  *--------------------------------------------------------------
80  *
81  * TkTextMarkCmd --
82  *
83  *	This procedure is invoked to process the "mark" options of
84  *	the widget command for text widgets. See the user documentation
85  *	for details on what it does.
86  *
87  * Results:
88  *	A standard Tcl result.
89  *
90  * Side effects:
91  *	See the user documentation.
92  *
93  *--------------------------------------------------------------
94  */
95 
96 int
TkTextMarkCmd(textPtr,interp,argc,argv)97 TkTextMarkCmd(textPtr, interp, argc, argv)
98     register TkText *textPtr;	/* Information about text widget. */
99     Tcl_Interp *interp;		/* Current interpreter. */
100     int argc;			/* Number of arguments. */
101     char **argv;		/* Argument strings.  Someone else has already
102 				 * parsed this command enough to know that
103 				 * argv[1] is "mark". */
104 {
105     int c, i;
106     size_t length;
107     Tcl_HashEntry *hPtr;
108     TkTextSegment *markPtr;
109     Tcl_HashSearch search;
110     TkTextIndex index;
111     Tk_SegType *newTypePtr;
112 
113     if (argc < 3) {
114 	Tcl_AppendResult(interp, "wrong # args: should be \"",
115 		argv[0], " mark option ?arg arg ...?\"", (char *) NULL);
116 	return TCL_ERROR;
117     }
118     c = argv[2][0];
119     length = strlen(argv[2]);
120     if ((c == 'g') && (strncmp(argv[2], "gravity", length) == 0)) {
121 	if (argc > 5) {
122 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
123 		    argv[0], " mark gravity markName ?gravity?",
124 		    (char *) NULL);
125 	    return TCL_ERROR;
126 	}
127 	hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[3]);
128 	if (hPtr == NULL) {
129 	    Tcl_AppendResult(interp, "there is no mark named \"",
130 		    argv[3], "\"", (char *) NULL);
131 	    return TCL_ERROR;
132 	}
133 	markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
134 	if (argc == 4) {
135 	    if (markPtr->typePtr == &tkTextRightMarkType) {
136 		interp->result = "right";
137 	    } else {
138 		interp->result = "left";
139 	    }
140 	    return TCL_OK;
141 	}
142 	length = strlen(argv[4]);
143 	c = argv[4][0];
144 	if ((c == 'l') && (strncmp(argv[4], "left", length) == 0)) {
145 	    newTypePtr = &tkTextLeftMarkType;
146 	} else if ((c == 'r') && (strncmp(argv[4], "right", length) == 0)) {
147 	    newTypePtr = &tkTextRightMarkType;
148 	} else {
149 	    Tcl_AppendResult(interp, "bad mark gravity \"",
150 		    argv[4], "\": must be left or right", (char *) NULL);
151 	    return TCL_ERROR;
152 	}
153 	TkTextMarkSegToIndex(textPtr, markPtr, &index);
154 	TkBTreeUnlinkSegment(textPtr->tree, markPtr,
155 		markPtr->body.mark.linePtr);
156 	markPtr->typePtr = newTypePtr;
157 	TkBTreeLinkSegment(markPtr, &index);
158     } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
159 	if (argc != 3) {
160 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
161 		    argv[0], " mark names\"", (char *) NULL);
162 	    return TCL_ERROR;
163 	}
164 	for (hPtr = Tcl_FirstHashEntry(&textPtr->markTable, &search);
165 		hPtr != NULL; hPtr = Tcl_NextHashEntry(&search)) {
166 	    Tcl_AppendElement(interp,
167 		    Tcl_GetHashKey(&textPtr->markTable, hPtr));
168 	}
169     } else if ((c == 'n') && (strncmp(argv[2], "next", length) == 0)) {
170 	if (argc != 4) {
171 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
172 		    argv[0], " mark next index\"", (char *) NULL);
173 	    return TCL_ERROR;
174 	}
175 	return MarkFindNext(interp, textPtr, argv[3]);
176     } else if ((c == 'p') && (strncmp(argv[2], "previous", length) == 0)) {
177 	if (argc != 4) {
178 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
179 		    argv[0], " mark previous index\"", (char *) NULL);
180 	    return TCL_ERROR;
181 	}
182 	return MarkFindPrev(interp, textPtr, argv[3]);
183     } else if ((c == 's') && (strncmp(argv[2], "set", length) == 0)) {
184 	if (argc != 5) {
185 	    Tcl_AppendResult(interp, "wrong # args: should be \"",
186 		    argv[0], " mark set markName index\"", (char *) NULL);
187 	    return TCL_ERROR;
188 	}
189 	if (TkTextGetIndex(interp, textPtr, argv[4], &index) != TCL_OK) {
190 	    return TCL_ERROR;
191 	}
192 	TkTextSetMark(textPtr, argv[3], &index);
193     } else if ((c == 'u') && (strncmp(argv[2], "unset", length) == 0)) {
194 	for (i = 3; i < argc; i++) {
195 	    hPtr = Tcl_FindHashEntry(&textPtr->markTable, argv[i]);
196 	    if (hPtr != NULL) {
197 		markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
198 		if ((markPtr == textPtr->insertMarkPtr)
199 			|| (markPtr == textPtr->currentMarkPtr)) {
200 		    continue;
201 		}
202 		TkBTreeUnlinkSegment(textPtr->tree, markPtr,
203 			markPtr->body.mark.linePtr);
204 		Tcl_DeleteHashEntry(hPtr);
205 		ckfree((char *) markPtr);
206 	    }
207 	}
208     } else {
209 	Tcl_AppendResult(interp, "bad mark option \"", argv[2],
210 		"\": must be gravity, names, next, previous, set, or unset",
211 		(char *) NULL);
212 	return TCL_ERROR;
213     }
214     return TCL_OK;
215 }
216 
217 /*
218  *----------------------------------------------------------------------
219  *
220  * TkTextSetMark --
221  *
222  *	Set a mark to a particular position, creating a new mark if
223  *	one doesn't already exist.
224  *
225  * Results:
226  *	The return value is a pointer to the mark that was just set.
227  *
228  * Side effects:
229  *	A new mark is created, or an existing mark is moved.
230  *
231  *----------------------------------------------------------------------
232  */
233 
234 TkTextSegment *
TkTextSetMark(textPtr,name,indexPtr)235 TkTextSetMark(textPtr, name, indexPtr)
236     TkText *textPtr;		/* Text widget in which to create mark. */
237     char *name;			/* Name of mark to set. */
238     TkTextIndex *indexPtr;	/* Where to set mark. */
239 {
240     Tcl_HashEntry *hPtr;
241     TkTextSegment *markPtr;
242     TkTextIndex insertIndex;
243     int new;
244 
245     hPtr = Tcl_CreateHashEntry(&textPtr->markTable, name, &new);
246     markPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
247     if (!new) {
248 	/*
249 	 * If this is the insertion point that's being moved, be sure
250 	 * to force a display update at the old position.  Also, don't
251 	 * let the insertion cursor be after the final newline of the
252 	 * file.
253 	 */
254 
255 	if (markPtr == textPtr->insertMarkPtr) {
256 	    TkTextIndex index, index2;
257 	    TkTextMarkSegToIndex(textPtr, textPtr->insertMarkPtr, &index);
258 	    TkTextIndexForwChars(&index, 1, &index2);
259 	    TkTextChanged(textPtr, &index, &index2);
260 	    if (TkBTreeLineIndex(indexPtr->linePtr)
261 		    == TkBTreeNumLines(textPtr->tree))  {
262 		TkTextIndexBackChars(indexPtr, 1, &insertIndex);
263 		indexPtr = &insertIndex;
264 	    }
265 	}
266 	TkBTreeUnlinkSegment(textPtr->tree, markPtr,
267 		markPtr->body.mark.linePtr);
268     } else {
269 	markPtr = (TkTextSegment *) ckalloc(MSEG_SIZE);
270 	markPtr->typePtr = &tkTextRightMarkType;
271 	markPtr->size = 0;
272 	markPtr->body.mark.textPtr = textPtr;
273 	markPtr->body.mark.linePtr = indexPtr->linePtr;
274 	markPtr->body.mark.hPtr = hPtr;
275 	Tcl_SetHashValue(hPtr, markPtr);
276     }
277     TkBTreeLinkSegment(markPtr, indexPtr);
278 
279     /*
280      * If the mark is the insertion cursor, then update the screen at the
281      * mark's new location.
282      */
283 
284     if (markPtr == textPtr->insertMarkPtr) {
285 	TkTextIndex index2;
286 
287 	TkTextIndexForwChars(indexPtr, 1, &index2);
288 	TkTextChanged(textPtr, indexPtr, &index2);
289     }
290     return markPtr;
291 }
292 
293 /*
294  *--------------------------------------------------------------
295  *
296  * TkTextMarkSegToIndex --
297  *
298  *	Given a segment that is a mark, create an index that
299  *	refers to the next text character (or other text segment
300  *	with non-zero size) after the mark.
301  *
302  * Results:
303  *	*IndexPtr is filled in with index information.
304  *
305  * Side effects:
306  *	None.
307  *
308  *--------------------------------------------------------------
309  */
310 
311 void
TkTextMarkSegToIndex(textPtr,markPtr,indexPtr)312 TkTextMarkSegToIndex(textPtr, markPtr, indexPtr)
313     TkText *textPtr;		/* Text widget containing mark. */
314     TkTextSegment *markPtr;	/* Mark segment. */
315     TkTextIndex *indexPtr;	/* Index information gets stored here.  */
316 {
317     TkTextSegment *segPtr;
318 
319     indexPtr->tree = textPtr->tree;
320     indexPtr->linePtr = markPtr->body.mark.linePtr;
321     indexPtr->charIndex = 0;
322     for (segPtr = indexPtr->linePtr->segPtr; segPtr != markPtr;
323 	    segPtr = segPtr->nextPtr) {
324 	indexPtr->charIndex += segPtr->size;
325     }
326 }
327 
328 /*
329  *--------------------------------------------------------------
330  *
331  * TkTextMarkNameToIndex --
332  *
333  *	Given the name of a mark, return an index corresponding
334  *	to the mark name.
335  *
336  * Results:
337  *	The return value is TCL_OK if "name" exists as a mark in
338  *	the text widget.  In this case *indexPtr is filled in with
339  *	the next segment whose after the mark whose size is
340  *	non-zero.  TCL_ERROR is returned if the mark doesn't exist
341  *	in the text widget.
342  *
343  * Side effects:
344  *	None.
345  *
346  *--------------------------------------------------------------
347  */
348 
349 int
TkTextMarkNameToIndex(textPtr,name,indexPtr)350 TkTextMarkNameToIndex(textPtr, name, indexPtr)
351     TkText *textPtr;		/* Text widget containing mark. */
352     char *name;			/* Name of mark. */
353     TkTextIndex *indexPtr;	/* Index information gets stored here. */
354 {
355     Tcl_HashEntry *hPtr;
356 
357     hPtr = Tcl_FindHashEntry(&textPtr->markTable, name);
358     if (hPtr == NULL) {
359 	return TCL_ERROR;
360     }
361     TkTextMarkSegToIndex(textPtr, (TkTextSegment *) Tcl_GetHashValue(hPtr),
362 	    indexPtr);
363     return TCL_OK;
364 }
365 
366 /*
367  *--------------------------------------------------------------
368  *
369  * MarkDeleteProc --
370  *
371  *	This procedure is invoked by the text B-tree code whenever
372  *	a mark lies in a range of characters being deleted.
373  *
374  * Results:
375  *	Returns 1 to indicate that deletion has been rejected.
376  *
377  * Side effects:
378  *	None (even if the whole tree is being deleted we don't
379  *	free up the mark;  it will be done elsewhere).
380  *
381  *--------------------------------------------------------------
382  */
383 
384 	/* ARGSUSED */
385 static int
MarkDeleteProc(segPtr,linePtr,treeGone)386 MarkDeleteProc(segPtr, linePtr, treeGone)
387     TkTextSegment *segPtr;		/* Segment being deleted. */
388     TkTextLine *linePtr;		/* Line containing segment. */
389     int treeGone;			/* Non-zero means the entire tree is
390 					 * being deleted, so everything must
391 					 * get cleaned up. */
392 {
393     return 1;
394 }
395 
396 /*
397  *--------------------------------------------------------------
398  *
399  * MarkCleanupProc --
400  *
401  *	This procedure is invoked by the B-tree code whenever a
402  *	mark segment is moved from one line to another.
403  *
404  * Results:
405  *	None.
406  *
407  * Side effects:
408  *	The linePtr field of the segment gets updated.
409  *
410  *--------------------------------------------------------------
411  */
412 
413 static TkTextSegment *
MarkCleanupProc(markPtr,linePtr)414 MarkCleanupProc(markPtr, linePtr)
415     TkTextSegment *markPtr;		/* Mark segment that's being moved. */
416     TkTextLine *linePtr;		/* Line that now contains segment. */
417 {
418     markPtr->body.mark.linePtr = linePtr;
419     return markPtr;
420 }
421 
422 /*
423  *--------------------------------------------------------------
424  *
425  * MarkLayoutProc --
426  *
427  *	This procedure is the "layoutProc" for mark segments.
428  *
429  * Results:
430  *	If the mark isn't the insertion cursor then the return
431  *	value is -1 to indicate that this segment shouldn't be
432  *	displayed.  If the mark is the insertion character then
433  *	1 is returned and the chunkPtr structure is filled in.
434  *
435  * Side effects:
436  *	None, except for filling in chunkPtr.
437  *
438  *--------------------------------------------------------------
439  */
440 
441 	/*ARGSUSED*/
442 static int
MarkLayoutProc(textPtr,indexPtr,segPtr,offset,maxX,maxChars,noCharsYet,wrapMode,chunkPtr)443 MarkLayoutProc(textPtr, indexPtr, segPtr, offset, maxX, maxChars,
444 	noCharsYet, wrapMode, chunkPtr)
445     TkText *textPtr;		/* Text widget being layed out. */
446     TkTextIndex *indexPtr;	/* Identifies first character in chunk. */
447     TkTextSegment *segPtr;	/* Segment corresponding to indexPtr. */
448     int offset;			/* Offset within segPtr corresponding to
449 				 * indexPtr (always 0). */
450     int maxX;			/* Chunk must not occupy pixels at this
451 				 * position or higher. */
452     int maxChars;		/* Chunk must not include more than this
453 				 * many characters. */
454     int noCharsYet;		/* Non-zero means no characters have been
455 				 * assigned to this line yet. */
456     Tk_Uid wrapMode;		/* Not used. */
457     register TkTextDispChunk *chunkPtr;
458 				/* Structure to fill in with information
459 				 * about this chunk.  The x field has already
460 				 * been set by the caller. */
461 {
462     if (segPtr != textPtr->insertMarkPtr) {
463 	return -1;
464     }
465 
466     chunkPtr->displayProc = TkTextInsertDisplayProc;
467     chunkPtr->undisplayProc = InsertUndisplayProc;
468     chunkPtr->measureProc = (Tk_ChunkMeasureProc *) NULL;
469     chunkPtr->bboxProc = (Tk_ChunkBboxProc *) NULL;
470     chunkPtr->numChars = 0;
471     chunkPtr->minAscent = 0;
472     chunkPtr->minDescent = 0;
473     chunkPtr->minHeight = 0;
474     chunkPtr->width = 0;
475 
476     /*
477      * Note: can't break a line after the insertion cursor:  this
478      * prevents the insertion cursor from being stranded at the end
479      * of a line.
480      */
481 
482     chunkPtr->breakIndex = -1;
483     chunkPtr->clientData = (ClientData) textPtr;
484     return 1;
485 }
486 
487 /*
488  *--------------------------------------------------------------
489  *
490  * TkTextInsertDisplayProc --
491  *
492  *	This procedure is called to display the insertion
493  *	cursor.
494  *
495  * Results:
496  *	None.
497  *
498  * Side effects:
499  *	Graphics are drawn.
500  *
501  *--------------------------------------------------------------
502  */
503 
504 	/* ARGSUSED */
505 void
TkTextInsertDisplayProc(chunkPtr,x,y,height,baseline,display,dst,screenY)506 TkTextInsertDisplayProc(chunkPtr, x, y, height, baseline, display, dst, screenY)
507     TkTextDispChunk *chunkPtr;		/* Chunk that is to be drawn. */
508     int x;				/* X-position in dst at which to
509 					 * draw this chunk (may differ from
510 					 * the x-position in the chunk because
511 					 * of scrolling). */
512     int y;				/* Y-position at which to draw this
513 					 * chunk in dst (x-position is in
514 					 * the chunk itself). */
515     int height;				/* Total height of line. */
516     int baseline;			/* Offset of baseline from y. */
517     Display *display;			/* Display to use for drawing. */
518     Drawable dst;			/* Pixmap or window in which to draw
519 					 * chunk. */
520     int screenY;			/* Y-coordinate in text window that
521 					 * corresponds to y. */
522 {
523     TkText *textPtr = (TkText *) chunkPtr->clientData;
524     int halfWidth = textPtr->insertWidth/2;
525 
526     if ((x + halfWidth) <= 0) {
527 	/*
528 	 * The insertion cursor is off-screen.  Just return.
529 	 */
530 
531 	return;
532     }
533 
534     /*
535      * As a special hack to keep the cursor visible on mono displays
536      * (or anywhere else that the selection and insertion cursors
537      * have the same color) write the default background in the cursor
538      * area (instead of nothing) when the cursor isn't on.  Otherwise
539      * the selection might hide the cursor.
540      */
541 
542     if (textPtr->flags & INSERT_ON) {
543 	Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->insertBorder,
544 		x - textPtr->insertWidth/2, y, textPtr->insertWidth,
545 		height, textPtr->insertBorderWidth, TK_RELIEF_RAISED);
546     } else if (textPtr->selBorder == textPtr->insertBorder) {
547 	Tk_Fill3DRectangle(textPtr->tkwin, dst, textPtr->border,
548 		x - textPtr->insertWidth/2, y, textPtr->insertWidth,
549 		height, 0, TK_RELIEF_FLAT);
550     }
551 }
552 
553 /*
554  *--------------------------------------------------------------
555  *
556  * InsertUndisplayProc --
557  *
558  *	This procedure is called when the insertion cursor is no
559  *	longer at a visible point on the display.  It does nothing
560  *	right now.
561  *
562  * Results:
563  *	None.
564  *
565  * Side effects:
566  *	None.
567  *
568  *--------------------------------------------------------------
569  */
570 
571 	/* ARGSUSED */
572 static void
InsertUndisplayProc(textPtr,chunkPtr)573 InsertUndisplayProc(textPtr, chunkPtr)
574     TkText *textPtr;			/* Overall information about text
575 					 * widget. */
576     TkTextDispChunk *chunkPtr;		/* Chunk that is about to be freed. */
577 {
578     return;
579 }
580 
581 /*
582  *--------------------------------------------------------------
583  *
584  * MarkCheckProc --
585  *
586  *	This procedure is invoked by the B-tree code to perform
587  *	consistency checks on mark segments.
588  *
589  * Results:
590  *	None.
591  *
592  * Side effects:
593  *	The procedure panics if it detects anything wrong with
594  *	the mark.
595  *
596  *--------------------------------------------------------------
597  */
598 
599 static void
MarkCheckProc(markPtr,linePtr)600 MarkCheckProc(markPtr, linePtr)
601     TkTextSegment *markPtr;		/* Segment to check. */
602     TkTextLine *linePtr;		/* Line containing segment. */
603 {
604     Tcl_HashSearch search;
605     Tcl_HashEntry *hPtr;
606 
607     if (markPtr->body.mark.linePtr != linePtr) {
608 	panic("MarkCheckProc: markPtr->body.mark.linePtr bogus");
609     }
610 
611     /*
612      * Make sure that the mark is still present in the text's mark
613      * hash table.
614      */
615 
616     for (hPtr = Tcl_FirstHashEntry(&markPtr->body.mark.textPtr->markTable,
617 	    &search); hPtr != markPtr->body.mark.hPtr;
618 	    hPtr = Tcl_NextHashEntry(&search)) {
619 	if (hPtr == NULL) {
620 	    panic("MarkCheckProc couldn't find hash table entry for mark");
621 	}
622     }
623 }
624 
625 /*
626  *--------------------------------------------------------------
627  *
628  * MarkFindNext --
629  *
630  *	This procedure searches forward for the next mark.
631  *
632  * Results:
633  *	A standard Tcl result, which is a mark name or an empty string.
634  *
635  * Side effects:
636  *	None.
637  *
638  *--------------------------------------------------------------
639  */
640 
641 static int
MarkFindNext(interp,textPtr,string)642 MarkFindNext(interp, textPtr, string)
643     Tcl_Interp *interp;			/* For error reporting */
644     TkText *textPtr;			/* The widget */
645     char *string;			/* The starting index or mark name */
646 {
647     TkTextIndex index;
648     Tcl_HashEntry *hPtr;
649     register TkTextSegment *segPtr;
650     int offset;
651 
652 
653     hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
654     if (hPtr != NULL) {
655 	/*
656 	 * If given a mark name, return the next mark in the list of
657 	 * segments, even if it happens to be at the same character position.
658 	 */
659 	segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
660 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
661 	segPtr = segPtr->nextPtr;
662     } else {
663 	/*
664 	 * For non-mark name indices we want to return any marks that
665 	 * are right at the index.
666 	 */
667 	if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
668 	    return TCL_ERROR;
669 	}
670 	for (offset = 0, segPtr = index.linePtr->segPtr;
671 		segPtr != NULL && offset < index.charIndex;
672 		offset += segPtr->size,	segPtr = segPtr->nextPtr) {
673 	    /* Empty loop body */ ;
674 	}
675     }
676     while (1) {
677 	/*
678 	 * segPtr points at the first possible candidate,
679 	 * or NULL if we ran off the end of the line.
680 	 */
681 	for ( ; segPtr != NULL ; segPtr = segPtr->nextPtr) {
682 	    if (segPtr->typePtr == &tkTextRightMarkType ||
683 		    segPtr->typePtr == &tkTextLeftMarkType) {
684 		Tcl_SetResult(interp,
685 		    Tcl_GetHashKey(&textPtr->markTable, segPtr->body.mark.hPtr),
686 		    TCL_STATIC);
687 		return TCL_OK;
688 	    }
689 	}
690 	index.linePtr = TkBTreeNextLine(index.linePtr);
691 	if (index.linePtr == (TkTextLine *) NULL) {
692 	    return TCL_OK;
693 	}
694 	index.charIndex = 0;
695 	segPtr = index.linePtr->segPtr;
696     }
697 }
698 
699 /*
700  *--------------------------------------------------------------
701  *
702  * MarkFindPrev --
703  *
704  *	This procedure searches backwards for the previous mark.
705  *
706  * Results:
707  *	A standard Tcl result, which is a mark name or an empty string.
708  *
709  * Side effects:
710  *	None.
711  *
712  *--------------------------------------------------------------
713  */
714 
715 static int
MarkFindPrev(interp,textPtr,string)716 MarkFindPrev(interp, textPtr, string)
717     Tcl_Interp *interp;			/* For error reporting */
718     TkText *textPtr;			/* The widget */
719     char *string;			/* The starting index or mark name */
720 {
721     TkTextIndex index;
722     Tcl_HashEntry *hPtr;
723     register TkTextSegment *segPtr, *seg2Ptr, *prevPtr;
724     int offset;
725 
726 
727     hPtr = Tcl_FindHashEntry(&textPtr->markTable, string);
728     if (hPtr != NULL) {
729 	/*
730 	 * If given a mark name, return the previous mark in the list of
731 	 * segments, even if it happens to be at the same character position.
732 	 */
733 	segPtr = (TkTextSegment *) Tcl_GetHashValue(hPtr);
734 	TkTextMarkSegToIndex(textPtr, segPtr, &index);
735     } else {
736 	/*
737 	 * For non-mark name indices we do not return any marks that
738 	 * are right at the index.
739 	 */
740 	if (TkTextGetIndex(interp, textPtr, string, &index) != TCL_OK) {
741 	    return TCL_ERROR;
742 	}
743 	for (offset = 0, segPtr = index.linePtr->segPtr;
744 		segPtr != NULL && offset < index.charIndex;
745 		offset += segPtr->size, segPtr = segPtr->nextPtr) {
746 	    /* Empty loop body */ ;
747 	}
748     }
749     while (1) {
750 	/*
751 	 * segPtr points just past the first possible candidate,
752 	 * or at the begining of the line.
753 	 */
754 	for (prevPtr = NULL, seg2Ptr = index.linePtr->segPtr;
755 		seg2Ptr != NULL && seg2Ptr != segPtr;
756 		seg2Ptr = seg2Ptr->nextPtr) {
757 	    if (seg2Ptr->typePtr == &tkTextRightMarkType ||
758 		    seg2Ptr->typePtr == &tkTextLeftMarkType) {
759 		prevPtr = seg2Ptr;
760 	    }
761 	}
762 	if (prevPtr != NULL) {
763 	    Tcl_SetResult(interp,
764 		Tcl_GetHashKey(&textPtr->markTable, prevPtr->body.mark.hPtr),
765 		TCL_STATIC);
766 	    return TCL_OK;
767 	}
768 	index.linePtr = TkBTreePreviousLine(index.linePtr);
769 	if (index.linePtr == (TkTextLine *) NULL) {
770 	    return TCL_OK;
771 	}
772 	segPtr = NULL;
773     }
774 }
775