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